This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new c8c0f6288d6b camel-jbang-tui: Add file chooser to Send Message and 
Ctrl+F shortcut
c8c0f6288d6b is described below

commit c8c0f6288d6b0963b2412dfa49f47261037d4539
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 9 13:00:21 2026 +0200

    camel-jbang-tui: Add file chooser to Send Message and Ctrl+F shortcut
    
    Add file browser to the Send Message dialog so users can browse and
    pick a file instead of manually typing file:path. Ctrl+F opens the
    file browser when in the body field, and selecting a file populates
    the body with file:<path>. Also adds Ctrl+F as a global shortcut
    for Browse Files.
    
    camel-jbang-tui: Consistent footer hint ordering with Esc first across all 
tabs
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  | 12 ++++---
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |  4 +++
 .../dsl/jbang/core/commands/tui/FolderBrowser.java | 18 ++++++++--
 .../camel/dsl/jbang/core/commands/tui/LogTab.java  |  4 +--
 .../dsl/jbang/core/commands/tui/MemoryLeakTab.java |  4 +--
 .../dsl/jbang/core/commands/tui/MetricsTab.java    |  2 +-
 .../dsl/jbang/core/commands/tui/ProcessTab.java    |  2 +-
 .../jbang/core/commands/tui/SendMessagePopup.java  | 38 ++++++++++++++++++++++
 .../dsl/jbang/core/commands/tui/SqlQueryTab.java   |  4 ++-
 9 files changed, 73 insertions(+), 15 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index ce9f6f4c043e..eb9ec12a4d07 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -123,6 +123,7 @@ class ActionsPopup {
     private final AiLogPopup aiLogPopup = new AiLogPopup();
 
     private final DoctorPopup doctorPopup = new DoctorPopup();
+    private final FolderBrowser sendFileBrowser = new FolderBrowser();
     private final SendMessagePopup sendMessagePopup = new SendMessagePopup();
     private final StopAllPopup stopAllPopup;
     private final CaptionOverlay captionOverlay;
@@ -160,6 +161,7 @@ class ActionsPopup {
         folderInputPopup.setBurstCallback(burstCallback);
         folderInputPopup.setOnFolderConfirmed(this::openFolderRunOptionsForm);
         
folderInputPopup.setInfraCatalogClearer(infraBrowserPopup::clearCatalog);
+        sendMessagePopup.setFileBrowser(sendFileBrowser);
     }
 
     void setContext(MonitorContext ctx) {
@@ -403,7 +405,7 @@ class ActionsPopup {
         labels.add("Run an Example...");
         labels.add("Run from Folder...");
         labels.add("Run Dev/Infra Service...");
-        labels.add("Browse Files...");
+        labels.add("Browse Files... (Ctrl+F)");
         labels.add("Stop All");
         labels.add("───");
         labels.add("Run Doctor");
@@ -462,7 +464,7 @@ class ActionsPopup {
 
     boolean handleKeyEvent(KeyEvent ke) {
         if (sendMessagePopup.isVisible()) {
-            if (ke.isKey(KeyCode.F5)) {
+            if (ke.isKey(KeyCode.F5) && !sendFileBrowser.isVisible()) {
                 sendMessagePopup.doSend(ctx, scheduler);
             } else {
                 sendMessagePopup.handleKeyEvent(ke);
@@ -930,8 +932,8 @@ class ActionsPopup {
         items.add(ListItem.from(TuiIcons.menuItem(TuiIcons.FOLDER_OPEN, "Run 
from Folder...")));
         items.add(ListItem.from(TuiIcons.menuItem(TuiIcons.INFRA, "Run 
Dev/Infra Service...")));
         items.add(hasSelection
-                ? ListItem.from(TuiIcons.menuItem(TuiIcons.FOLDER, "Browse 
Files..."))
-                : ListItem.from(TuiIcons.menuItem(TuiIcons.FOLDER, "Browse 
Files...")).style(Style.EMPTY.dim()));
+                ? ListItem.from(TuiIcons.menuItem(TuiIcons.FOLDER, "Browse 
Files... (Ctrl+F)"))
+                : ListItem.from(TuiIcons.menuItem(TuiIcons.FOLDER, "Browse 
Files... (Ctrl+F)")).style(Style.EMPTY.dim()));
         items.add(ListItem.from(stopLabel));
         items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
         // Group 2: Diagnostics & Screen
@@ -1082,7 +1084,7 @@ class ActionsPopup {
             setNotification("No routes available", true);
             return;
         }
-        sendMessagePopup.open(ctx, pid, info.name, info.routes, 
preSelectedRouteId);
+        sendMessagePopup.open(ctx, pid, info.name, info.routes, 
preSelectedRouteId, info.directory);
         preSelectedRouteId = null;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index db02c46c6b1e..0ae4cf4dff5c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -742,6 +742,10 @@ public class CamelMonitor extends CamelCommand {
             popupManager.openSwitchPopup(ctx.selectedPid, 
getNonVanishingIntegrations());
             return true;
         }
+        if (ke.hasCtrl() && ke.isChar('f')) {
+            openFilesPopup();
+            return true;
+        }
         return false;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
index cb9980b8cb1a..03ae0b951c22 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
@@ -57,6 +57,7 @@ class FolderBrowser {
     private final Deque<Integer> offsetStack = new ArrayDeque<>();
     private List<DirEntry> entries = Collections.emptyList();
     private Consumer<String> onSelect;
+    private boolean fileSelectMode;
     private char lastJumpChar;
     private int lastJumpIndex = -1;
     private final SourceViewer sourceViewer = new SourceViewer();
@@ -74,6 +75,10 @@ class FolderBrowser {
         this.onSelect = onSelect;
     }
 
+    void setFileSelectMode(boolean fileSelectMode) {
+        this.fileSelectMode = fileSelectMode;
+    }
+
     void open(String startPath) {
         Path start = null;
         if (startPath != null && !startPath.isEmpty()) {
@@ -241,7 +246,12 @@ class FolderBrowser {
             Integer sel = listState.selected();
             if (sel != null && sel < entries.size()) {
                 DirEntry entry = entries.get(sel);
-                if (!entry.directory()) {
+                if (!entry.directory() && fileSelectMode) {
+                    visible = false;
+                    if (onSelect != null) {
+                        onSelect.accept(entry.path());
+                    }
+                } else if (!entry.directory()) {
                     sourceViewer.loadFile(Path.of(entry.path()));
                 } else if ("..".equals(entry.name()) && currentDir != null) {
                     navigateBack();
@@ -365,8 +375,10 @@ class FolderBrowser {
             return;
         }
         TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
-        TuiHelper.hint(spans, "Enter", "open");
-        TuiHelper.hint(spans, "Tab", "select");
+        TuiHelper.hint(spans, "Enter", fileSelectMode ? "select" : "open");
+        if (!fileSelectMode) {
+            TuiHelper.hint(spans, "Tab", "select");
+        }
         TuiHelper.hintLast(spans, "Esc", "close");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
index cf166ab645fe..afd96c85ef84 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
@@ -406,9 +406,9 @@ class LogTab extends AbstractTab {
             return;
         }
         if (showLogLevelPopup) {
+            hint(spans, "Esc", "cancel");
             hint(spans, TuiIcons.HINT_SCROLL, "navigate");
-            hint(spans, "Enter", "set level");
-            hintLast(spans, "Esc", "cancel");
+            hintLast(spans, "Enter", "set level");
             return;
         }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
index de45796c3a1c..bbba3c472d6d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
@@ -721,14 +721,14 @@ class MemoryLeakTab extends AbstractTab {
         String modeLabel = recordingMode == RecordingMode.DUAL ? "dual" : 
"single";
         switch (state) {
             case IDLE -> {
+                hint(spans, "Esc", "back");
                 hint(spans, "R", "record");
                 hint(spans, "d", "mode [" + modeLabel + "]");
                 hint(spans, "+/-", "duration [" + duration + "s]");
-                hintLast(spans, "Esc", "back");
             }
             case RECORDING -> {
+                hint(spans, "Esc", "back");
                 hint(spans, "X", "stop");
-                hintLast(spans, "Esc", "back");
             }
             case HAS_RESULTS -> {
                 hint(spans, "Esc", "back");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
index 580f1c3b93bd..316421f5c93b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
@@ -626,10 +626,10 @@ class MetricsTab extends AbstractTableTab {
     @Override
     public void renderFooter(List<Span> spans) {
         if (showRaw) {
+            hint(spans, "Esc", "close");
             hint(spans, TuiIcons.HINT_SCROLL, "scroll");
             hint(spans, "PgUp/Dn", "page");
             hint(spans, "F5", "refresh");
-            hintLast(spans, "Esc", "close");
             return;
         }
         hint(spans, "Esc", "back");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
index 396a2d8aaeb7..5eb1cb033b47 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
@@ -198,9 +198,9 @@ class ProcessTab extends AbstractTab {
 
     @Override
     public void renderFooter(List<Span> spans) {
+        hint(spans, "Esc", "back");
         hint(spans, TuiIcons.HINT_SCROLL, "scroll");
         hint(spans, "w", "wrap [" + (wrap ? "on" : "off") + "]");
-        hintLast(spans, "Esc", "back");
     }
 
     @Override
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
index 3539b9f95571..7f20e25510f4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
@@ -96,11 +96,21 @@ class SendMessagePopup {
     private final List<SendHistoryEntry> history = new ArrayList<>();
     private int historyIndex;
 
+    // File chooser
+    private FolderBrowser fileBrowser;
+    private String sourceDirectory;
+
     boolean isVisible() {
         return visible;
     }
 
     void open(MonitorContext ctx, String pid, String name, List<RouteInfo> 
routes, String preSelectRouteId) {
+        open(ctx, pid, name, routes, preSelectRouteId, null);
+    }
+
+    void open(
+            MonitorContext ctx, String pid, String name, List<RouteInfo> 
routes,
+            String preSelectRouteId, String sourceDirectory) {
         if (pid == null || routes == null || routes.isEmpty()) {
             return;
         }
@@ -115,6 +125,7 @@ class SendMessagePopup {
         this.headers = null;
         this.selectedHeader = 0;
         this.editingHeaderKey = true;
+        this.sourceDirectory = sourceDirectory;
         clearResponse();
         this.historyIndex = 0;
         this.visible = true;
@@ -124,10 +135,17 @@ class SendMessagePopup {
         visible = false;
     }
 
+    void setFileBrowser(FolderBrowser fileBrowser) {
+        this.fileBrowser = fileBrowser;
+    }
+
     boolean handleKeyEvent(KeyEvent ke) {
         if (!visible) {
             return false;
         }
+        if (fileBrowser != null && fileBrowser.isVisible()) {
+            return fileBrowser.handleKeyEvent(ke);
+        }
         if (sending) {
             return true;
         }
@@ -199,6 +217,10 @@ class SendMessagePopup {
                 }
                 return true;
             }
+            if (ke.hasCtrl() && ke.isChar('f') && fileBrowser != null) {
+                openFileBrowser();
+                return true;
+            }
             if (ke.isUp()) {
                 if (bodyState.cursorRow() == 0) {
                     if (routes.size() > 1) {
@@ -437,6 +459,13 @@ class SendMessagePopup {
         return headers != null && !headers.isEmpty();
     }
 
+    private void openFileBrowser() {
+        fileBrowser.setFileSelectMode(true);
+        fileBrowser.setOnSelect(path -> bodyState.setText("file:" + path));
+        String startDir = sourceDirectory != null ? sourceDirectory : 
System.getProperty("user.dir");
+        fileBrowser.open(startDir);
+    }
+
     void handlePaste(String text) {
         if (!visible || sending || text == null || text.isEmpty()) {
             return;
@@ -725,6 +754,10 @@ class SendMessagePopup {
         if (!visible) {
             return;
         }
+        if (fileBrowser != null && fileBrowser.isVisible()) {
+            fileBrowser.render(frame, area);
+            return;
+        }
 
         frame.renderWidget(Clear.INSTANCE, area);
 
@@ -1028,9 +1061,14 @@ class SendMessagePopup {
     }
 
     void renderFooter(List<Span> spans) {
+        if (fileBrowser != null && fileBrowser.isVisible()) {
+            fileBrowser.renderFooter(spans);
+            return;
+        }
         hint(spans, "Esc", "back");
         hint(spans, "Tab", "fields");
         hint(spans, "F5", "send");
+        hint(spans, "Ctrl+F", "file");
         hint(spans, "+", "header");
         hint(spans, "b", "body" + (showResponseBody ? " [on]" : ""));
         hint(spans, "h", "headers" + (showResponseHeaders ? " [on]" : ""));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
index d18f28f61cc4..a9fd72cb452c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
@@ -827,9 +827,10 @@ class SqlQueryTab extends AbstractTab {
     @Override
     public void renderFooter(List<Span> spans) {
         if (editMode) {
-            hint(spans, "F5", "save");
             hint(spans, "Esc", "cancel");
+            hint(spans, "F5", "save");
         } else if (focusOnInput) {
+            hint(spans, "Esc", "back");
             hint(spans, "F5", "execute");
             if (!sqlHistory.isEmpty()) {
                 hint(spans, "C-e", "history");
@@ -841,6 +842,7 @@ class SqlQueryTab extends AbstractTab {
                 hint(spans, "Tab", "results");
             }
         } else {
+            hint(spans, "Esc", "back");
             hint(spans, "Tab", "input");
             hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             if (isEditable()) {

Reply via email to