Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Comment thread
ammachado marked this conversation as resolved.
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");
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
20 changes: 20 additions & 0 deletions dsl/camel-jbang/camel-launcher/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,28 @@
<httpcore-version>4.4.16</httpcore-version>
<commons-codec-version>1.22.0</commons-codec-version>
<jcl-over-slf4j-version>${slf4j-version}</jcl-over-slf4j-version>
<commonmark-version>0.28.0</commonmark-version>
</properties>

<dependencyManagement>
<dependencies>
<!--
This launcher bundles both camel-jbang-plugin-generate and camel-jbang-plugin-tui on a
single flat classpath. The generate plugin drags in commonmark 0.21.0 (via openapi-generator),
while the tui plugin (via tamboui-markdown) needs commonmark 0.28.0 and its renderer.markdown
classes that do not exist in 0.21.0. Without this pin Maven mediation keeps the older 0.21.0
core next to the 0.28.0 extensions, which makes the TUI markdown help throw NoClassDefFoundError.
Pin the core to 0.28.0 so it matches the commonmark-ext-* modules; openapi-generator only uses
the stable Parser/HtmlRenderer APIs, which are unchanged in 0.28.0.
-->
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>${commonmark-version}</version>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
Expand Down
Loading