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
commit fa6f59a1afdbcfbbba42fa0401450ee565f166e1 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Oct 15 12:55:52 2026 +0200 chore: camel-jbang - the F-key hints no longer split a field typed in the footer The F-keys every screen has (F2 actions, F10 run) go into a footer after the screen's first hint. While a term is typed (a table filter, find or highlight in the source viewer) the footer starts with the field instead, so they landed between the field and its own Enter and Esc, and inside the term when the caret was in its middle. While a screen takes typed text they now go after the field's keys. The Source tab also leaves Tab out of that footer, as it does the other keys of the viewer while a term is typed. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../dsl/jbang/core/commands/tui/CamelMonitor.java | 4 +-- .../dsl/jbang/core/commands/tui/FooterHints.java | 16 +++++++++++ .../dsl/jbang/core/commands/tui/SourceTab.java | 19 +++++++------ .../jbang/core/commands/tui/FooterHintsTest.java | 30 +++++++++++++++++++++ .../core/commands/tui/SourceViewerEditTest.java | 31 ++++++++++++++++++++++ 5 files changed, 88 insertions(+), 12 deletions(-) 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 13ee96c2dfc0..d3da8b8cdae6 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 @@ -2531,9 +2531,9 @@ public class CamelMonitor extends CamelCommand { private int fKeyStart; private int insertFKeyHints(List<Span> spans) { - int insertPos = Math.min(2, spans.size()); - List<Span> fKeySpans = new ArrayList<>(); MonitorTab tab = tabRegistry.activeTab(); + int insertPos = FooterHints.fKeyPosition(spans, tab != null && tab.isTextInputActive()); + List<Span> fKeySpans = new ArrayList<>(); // F1 opens the help of every tab, but is seldom pressed: the Overview, the first screen, names it if (tab == tabRegistry.overviewTab() && tab.getHelpText() != null) { hint(fKeySpans, "F1", "help"); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FooterHints.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FooterHints.java index 2dd5a9ed9b14..5961975063c0 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FooterHints.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FooterHints.java @@ -30,6 +30,22 @@ final class FooterHints { private FooterHints() { } + /** + * Where the F-key hints go in a screen's footer: after its first hint, its most important one. While text is typed + * the footer is the field with its own keys (Enter, Esc): the F-keys go after those, as after the first two spans + * they would part the field from its keys, or cut it in two when the caret is inside it. The field's last key has + * no gap after it, so one is added. + */ + static int fKeyPosition(List<Span> spans, boolean typing) { + if (!typing) { + return Math.min(2, spans.size()); + } + if (!spans.isEmpty() && !spans.get(spans.size() - 1).content().endsWith(" ")) { + spans.add(Span.raw(" ")); + } + return spans.size(); + } + /** * @param spans the footer: the screen's hints, with the F-key hints inserted at {@code fKeyStart}; the hints * dropped are removed from it diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java index 1d0fbcdc8cd3..18c068bc338d 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java @@ -469,17 +469,16 @@ class SourceTab extends AbstractTab { } if (focusOnViewer && sourceViewer.isVisible()) { sourceViewer.renderFooter(spans); - if (!sourceViewer.isEditMode()) { + // also after a jump, which moves the focus to the viewer; not while a term is typed into find or + // highlight, as the footer is then that field with its keys + if (!sourceViewer.isEditMode() && !sourceViewer.isTextInputActive()) { TuiHelper.hint(spans, "Tab", "files"); - // also after a jump, which moves the focus to the viewer; not while g is typed into search - if (!sourceViewer.isTextInputActive()) { - if (!routeIndex.isEmpty()) { - TuiHelper.hint(spans, "g", "go to route"); - } - TuiHelper.hint(spans, "Ctrl+G", "go to"); - if (endpointOnSelectedLine() != null) { - TuiHelper.hint(spans, "u", "usages"); - } + if (!routeIndex.isEmpty()) { + TuiHelper.hint(spans, "g", "go to route"); + } + TuiHelper.hint(spans, "Ctrl+G", "go to"); + if (endpointOnSelectedLine() != null) { + TuiHelper.hint(spans, "u", "usages"); } } } else { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/FooterHintsTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/FooterHintsTest.java index e974a8dc180f..c453ffb7f3fe 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/FooterHintsTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/FooterHintsTest.java @@ -127,6 +127,36 @@ class FooterHintsTest { assertThat(keys(spans)).containsExactly("Enter", "Esc"); } + @Test + void theFKeysGoAfterTheFirstHintOfAScreen() { + List<Span> spans = new ArrayList<>(); + hint(spans, "Esc", "back"); + hint(spans, "/", "find"); + + assertThat(FooterHints.fKeyPosition(spans, false)).isEqualTo(2); + assertThat(spans).hasSize(4); + } + + @Test + void whileTextIsTypedTheFKeysGoAfterTheFieldAndItsKeys() { + // as a filter draws it with the caret inside the term: "ord|ers", where the caret splits the field in three + List<Span> spans = new ArrayList<>(); + spans.add(Span.styled(" /", Theme.label().bold())); + spans.add(Span.raw("ord")); + spans.add(Span.styled("e", Style.EMPTY.reversed())); + spans.add(Span.raw("rs")); + spans.add(Span.raw(" ")); + hint(spans, "Enter", "filter"); + TuiHelper.hintLast(spans, "Esc", "cancel"); + + int position = FooterHints.fKeyPosition(spans, true); + + // after Esc, which has no gap after it as the last key: one is added + assertThat(position).isEqualTo(spans.size()); + assertThat(spans.get(position - 2).content()).isEqualTo(" cancel"); + assertThat(spans.get(position - 1).content()).isEqualTo(" "); + } + private static List<String> keys(List<Span> spans) { return spans.stream().filter(s -> Theme.hintKey().equals(s.style())).map(s -> s.content().strip()).toList(); } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewerEditTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewerEditTest.java index d3f92c2f3a3d..138f00b9f163 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewerEditTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewerEditTest.java @@ -456,6 +456,37 @@ class SourceViewerEditTest { assertThat(Files.readString(md, StandardCharsets.UTF_8)).endsWith("!"); } + @Test + void sourceTabFooterIsTheFieldWhileATermIsTyped() { + IntegrationInfo info = new IntegrationInfo(); + info.pid = "1234"; + info.name = "test-app"; + info.directory = tempDir.toString(); + MonitorContext ctx = new MonitorContext( + new AtomicReference<>(List.of(info)), new AtomicReference<>(List.of())); + ctx.selectedPid = "1234"; + SourceTab tab = new SourceTab(ctx); + tab.onTabSelected(); + + // the file opened, the viewer's footer offers Tab back to the files + assertThat(tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER, KeyModifiers.NONE))).isTrue(); + assertThat(footer(tab)).contains(" Tab "); + + // h opens the highlight field: the footer is the field and its keys, not the viewer's navigation + tab.handleKeyEvent(KeyEvent.ofChar('h', KeyModifiers.NONE)); + tab.handleKeyEvent(KeyEvent.ofChar('a', KeyModifiers.NONE)); + assertThat(tab.isTextInputActive()).isTrue(); + assertThat(footer(tab)).contains(" h:", "a█", " Enter ", " Esc ").doesNotContain(" Tab "); + } + + private static String footer(SourceTab tab) { + List<Span> spans = new ArrayList<>(); + tab.renderFooter(spans); + StringBuilder sb = new StringBuilder(); + spans.forEach(s -> sb.append(s.content())); + return sb.toString(); + } + @Test void sourceTabForwardsKeysToViewerWhileEditing() throws Exception { IntegrationInfo info = new IntegrationInfo();
