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()) {