diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc index bc77fe6ebddcf..bfe2225b3b38f 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc @@ -58,7 +58,7 @@ If an example requires infrastructure (like Kafka or a database), the TUI automa required Docker containers before launching the example. A notification in the footer shows the progress. -TIP: Press *F1* on any screen for context-sensitive help. +TIP: Press *F1* or *?* on any screen for context-sensitive help. Keyboard shortcuts are always shown in the footer bar. == Tabs Overview @@ -313,7 +313,7 @@ The Doctor checks your development environment and reports issues: | *1* - *0* | Jump to tab by number | *Tab* / *Shift+Tab* | Next / previous tab -| *F1* | Context-sensitive help (toggle) +| *F1* / *?* | Context-sensitive help (toggle) | *F2* | Actions menu | *F3* | Switch between integrations (when multiple running) | *Shift+F5* | Take screenshot 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 adb40965dca78..e9113704af78e 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 @@ -610,16 +610,14 @@ private boolean handleGlobalKeys(KeyEvent ke, TuiRunner runner) { takeScreenshot(); return true; } - if (ke.isKey(KeyCode.F1)) { - if (helpOverlay.isVisible()) { - helpOverlay.close(); - } else { - MonitorTab tab = activeTab(); - if (tab != null) { - String help = tab.getHelpText(); - if (help != null) { - helpOverlay.open(help); - } + if (opensHelp(ke, textEditing)) { + // Only opens the overlay: while it is visible, dispatch delegates to + // helpOverlay.handleKeyEvent (which handles F1/?/q/Esc to close) before reaching here. + MonitorTab tab = activeTab(); + if (tab != null) { + String help = tab.getHelpText(); + if (help != null) { + helpOverlay.open(help); } } return true; @@ -655,6 +653,14 @@ private boolean handleGlobalKeys(KeyEvent ke, TuiRunner runner) { return false; } + /** + * Whether the key event should open the help overlay. F1 always opens it; '?' opens it too, but only when no text + * input is focused, so the character is not swallowed while the user is typing in a search or probe field. + */ + static boolean opensHelp(KeyEvent ke, boolean textEditing) { + return ke.isKey(KeyCode.F1) || (!textEditing && ke.isChar('?')); + } + private boolean handleTabKeys(KeyEvent ke) { MonitorTab activeTab = activeTab(); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java index cdc0252050110..d242b30b0ae9f 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java @@ -59,7 +59,7 @@ boolean handleKeyEvent(KeyEvent ke) { if (!visible) { return false; } - if (ke.isCancel() || ke.isChar('q') || ke.isKey(KeyCode.F1)) { + if (ke.isCancel() || ke.isChar('q') || ke.isChar('?') || ke.isKey(KeyCode.F1)) { close(); return true; } @@ -94,7 +94,7 @@ void render(Frame frame, Rect area) { .borderType(BorderType.ROUNDED) .title(" Help ") .titleBottom(Title.from(Line.from( - Span.styled(" F1", MonitorContext.HINT_KEY_STYLE), Span.raw(" close "), + Span.styled(" F1/?", MonitorContext.HINT_KEY_STYLE), Span.raw(" close "), Span.styled(" ↑↓", MonitorContext.HINT_KEY_STYLE), Span.raw(" scroll ")))) .build(); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitorTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitorTest.java new file mode 100644 index 0000000000000..9aa48bbc87bc5 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitorTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.commands.tui; + +import dev.tamboui.tui.event.KeyCode; +import dev.tamboui.tui.event.KeyEvent; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CamelMonitorTest { + + // '?' is an alternative to F1 for opening the help overlay. Unlike F1, it must be suppressed + // while a text input is focused, otherwise typing '?' in a search or probe field would + // pop up help instead of inserting the character. + + @Test + void f1OpensHelpEvenWhileTextEditing() { + assertTrue(CamelMonitor.opensHelp(KeyEvent.ofKey(KeyCode.F1), true), "F1 must open help regardless of text editing"); + assertTrue(CamelMonitor.opensHelp(KeyEvent.ofKey(KeyCode.F1), false), "F1 must open help"); + } + + @Test + void questionMarkOpensHelpWhenNotTextEditing() { + assertTrue(CamelMonitor.opensHelp(KeyEvent.ofChar('?'), false), "'?' must open help when no input is focused"); + } + + @Test + void questionMarkIsSuppressedWhileTextEditing() { + assertFalse(CamelMonitor.opensHelp(KeyEvent.ofChar('?'), true), + "'?' must not open help while a text input is focused"); + } + + @Test + void unrelatedKeyDoesNotOpenHelp() { + assertFalse(CamelMonitor.opensHelp(KeyEvent.ofChar('x'), false), "an unrelated key must not open help"); + } +} diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlayTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlayTest.java new file mode 100644 index 0000000000000..cc8c5be006d58 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlayTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.commands.tui; + +import dev.tamboui.tui.event.KeyCode; +import dev.tamboui.tui.event.KeyEvent; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class HelpOverlayTest { + + // The help overlay is opened with either F1 or '?'. Pressing the same keys again must close it, + // so the binding is a true toggle. '?' was previously ignored by the overlay (CAMEL: TUI help). + + @Test + void questionMarkClosesOverlay() { + HelpOverlay overlay = new HelpOverlay(); + overlay.open("# Help"); + assertTrue(overlay.isVisible()); + + boolean handled = overlay.handleKeyEvent(KeyEvent.ofChar('?')); + + assertTrue(handled, "the overlay must consume the key while visible"); + assertFalse(overlay.isVisible(), "'?' must close the help overlay it opened"); + } + + @Test + void f1ClosesOverlay() { + HelpOverlay overlay = new HelpOverlay(); + overlay.open("# Help"); + + overlay.handleKeyEvent(KeyEvent.ofKey(KeyCode.F1)); + + assertFalse(overlay.isVisible(), "F1 must still close the help overlay"); + } + + @Test + void quitKeyClosesOverlay() { + HelpOverlay overlay = new HelpOverlay(); + overlay.open("# Help"); + + overlay.handleKeyEvent(KeyEvent.ofChar('q')); + + assertFalse(overlay.isVisible(), "'q' must still close the help overlay"); + } + + @Test + void unrelatedKeyKeepsOverlayOpen() { + HelpOverlay overlay = new HelpOverlay(); + overlay.open("# Help"); + + overlay.handleKeyEvent(KeyEvent.ofChar('x')); + + assertTrue(overlay.isVisible(), "an unrelated key must not close the help overlay"); + } +} diff --git a/dsl/camel-jbang/camel-launcher/pom.xml b/dsl/camel-jbang/camel-launcher/pom.xml index 00be949d3f02c..37568d0ca06ae 100644 --- a/dsl/camel-jbang/camel-launcher/pom.xml +++ b/dsl/camel-jbang/camel-launcher/pom.xml @@ -54,8 +54,28 @@ 4.4.16 1.22.0 ${slf4j-version} + 0.28.0 + + + + + org.commonmark + commonmark + ${commonmark-version} + + + + org.apache.camel