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 38097ece3b9e chore(tui): Fix Source tab refresh and improve edit mode
save
38097ece3b9e is described below
commit 38097ece3b9eb56f0268323b1bd8bfd70a28ccfc
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Aug 3 15:20:12 2026 +0200
chore(tui): Fix Source tab refresh and improve edit mode save
Fix Source tab showing empty when navigating to it by adding
onTabSelected() dispatch in TabRegistry.handleTabKey().
Add Shift+F5 save-and-continue-editing in SourceViewer so users
can save without closing edit mode. F5 remains save-and-close.
Remove global Shift+F5 screenshot shortcut (still available via
F2 actions menu). Use notification system for save feedback
instead of footer message.
chore(tui): Fix horizontal scroll in source editor and update save
notification test
Remove duplicate ensureCursorVisible call in SourceViewer that had swapped
parameters (width,height instead of rows,cols), causing premature horizontal
scrolling. TextArea.render() already calls ensureCursorVisible correctly.
Update test to verify save notification via callback instead of footer
spans.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 4 --
.../dsl/jbang/core/commands/tui/SourceTab.java | 5 ++
.../dsl/jbang/core/commands/tui/SourceViewer.java | 67 +++++++++++-----------
.../dsl/jbang/core/commands/tui/TabRegistry.java | 3 +
.../core/commands/tui/SourceViewerEditTest.java | 15 +++--
5 files changed, 51 insertions(+), 43 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 0f388e8a49ba..f153a4adb666 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
@@ -800,10 +800,6 @@ public class CamelMonitor extends CamelCommand {
}
MonitorTab activeMonitorTab = tabRegistry.activeTab();
boolean overlayActive = activeMonitorTab != null &&
activeMonitorTab.isOverlayActive();
- if (ke.isKey(KeyCode.F5) && ke.hasShift()) {
- recordingManager.takeScreenshot();
- return true;
- }
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.
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 920791f1b574..13e04b3ba9f5 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
@@ -106,6 +106,11 @@ class SourceTab extends AbstractTab {
SourceTab(MonitorContext ctx) {
super(ctx);
+ sourceViewer.setNotificationCallback((msg, error) -> {
+ if (ctx.notificationCallback != null) {
+ ctx.notificationCallback.accept(msg, error);
+ }
+ });
}
boolean isSourceViewerEditMode() {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
index 7e981b42f99c..7f28e7afd4f8 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
@@ -27,6 +27,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
import java.util.function.IntConsumer;
import dev.tamboui.layout.Constraint;
@@ -124,10 +125,9 @@ class SourceViewer {
private Path editableFile;
private boolean editMode;
private final TextAreaState editState = new TextAreaState();
- private String saveMessage;
- private boolean saveError;
/** Markdown render mode prior to entering edit; restored on cancel. */
private boolean markdownModeBeforeEdit;
+ private BiConsumer<String, Boolean> notificationCallback;
private record CachedSource(
List<String> lines, List<JsonObject> codeData,
@@ -150,6 +150,10 @@ class SourceViewer {
this.focused = focused;
}
+ void setNotificationCallback(BiConsumer<String, Boolean> callback) {
+ this.notificationCallback = callback;
+ }
+
void hide() {
exitEditMode();
visible = false;
@@ -158,8 +162,6 @@ class SourceViewer {
quickDocEntries = Collections.emptyMap();
deprecatedLines = Collections.emptySet();
editableFile = null;
- saveMessage = null;
- saveError = false;
}
void reset() {
@@ -191,8 +193,6 @@ class SourceViewer {
deprecatedLineScanner = null;
deprecatedLines = Collections.emptySet();
editableFile = null;
- saveMessage = null;
- saveError = false;
}
boolean isMarkdownMode() {
@@ -389,6 +389,10 @@ class SourceViewer {
exitEditMode();
return true;
}
+ if (ke.isKey(KeyCode.F5) && ke.hasShift()) {
+ saveContinueEdit();
+ return true;
+ }
if (ke.isKey(KeyCode.F5)) {
saveEdit();
return true;
@@ -465,8 +469,6 @@ class SourceViewer {
markdownMode = false;
quickDocEnabled = false;
search.reset();
- saveMessage = null;
- saveError = false;
editMode = true;
}
@@ -495,12 +497,27 @@ class SourceViewer {
if (isMarkdownFile) {
markdownMode = restoreMarkdownMode;
}
- // Preserve save feedback after reload
- saveMessage = "Saved";
- saveError = false;
+ notifySave("Saved: " + editableFile.getFileName(), false);
+ } catch (IOException e) {
+ notifySave("Save failed: " + e.getMessage(), true);
+ }
+ }
+
+ private void saveContinueEdit() {
+ if (!editMode || editableFile == null) {
+ return;
+ }
+ try {
+ Files.writeString(editableFile, editState.text(),
StandardCharsets.UTF_8);
+ notifySave("Saved: " + editableFile.getFileName(), false);
} catch (IOException e) {
- saveMessage = "Save failed: " + e.getMessage();
- saveError = true;
+ notifySave("Save failed: " + e.getMessage(), true);
+ }
+ }
+
+ private void notifySave(String message, boolean error) {
+ if (notificationCallback != null) {
+ notificationCallback.accept(message, error);
}
}
@@ -747,9 +764,6 @@ class SourceViewer {
List<Span> titleSpans = new ArrayList<>();
String info = title != null ? title : "";
titleSpans.add(Span.styled(" Edit [" + info + "] ", ts));
- if (saveMessage != null) {
- titleSpans.add(Span.styled(saveMessage + " ", saveError ?
Theme.error() : Theme.success()));
- }
Block.Builder blockBuilder = Block.builder()
.borderType(BorderType.ROUNDED).borders(Borders.ALL)
.title(Title.from(Line.from(titleSpans)));
@@ -762,7 +776,6 @@ class SourceViewer {
lastVisibleLines = Math.max(1, inner.height());
frame.renderWidget(block, area);
- editState.ensureCursorVisible(inner.width(), inner.height());
TextArea textArea = TextArea.builder()
.cursorStyle(Style.EMPTY.reversed())
.showLineNumbers(true)
@@ -774,11 +787,9 @@ class SourceViewer {
void renderFooter(List<Span> spans) {
if (editMode) {
TuiHelper.hint(spans, "Esc", "cancel");
- TuiHelper.hint(spans, "F5", "save");
+ TuiHelper.hint(spans, "F5", "save & close");
+ TuiHelper.hint(spans, "Shift+F5", "save");
TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "move");
- if (saveMessage != null) {
- spans.add(Span.styled(" " + saveMessage, saveError ?
Theme.error() : Theme.success()));
- }
return;
}
if (markdownMode) {
@@ -815,9 +826,6 @@ class SourceViewer {
if (onLineSelected != null) {
TuiHelper.hint(spans, "Enter", "select node");
}
- if (saveMessage != null) {
- spans.add(Span.styled(" " + saveMessage, saveError ?
Theme.error() : Theme.success()));
- }
}
/**
@@ -832,8 +840,6 @@ class SourceViewer {
editMode = false;
editState.clear();
markdownModeBeforeEdit = false;
- saveMessage = null;
- saveError = false;
String fileName = filePath.getFileName().toString();
boolean isMd = fileName.toLowerCase().endsWith(".md");
try {
@@ -894,8 +900,6 @@ class SourceViewer {
editableFile = null;
editMode = false;
editState.clear();
- saveMessage = null;
- saveError = false;
if (ctx.selectedPid == null || ctx.runner == null) {
return;
@@ -1108,12 +1112,7 @@ class SourceViewer {
return Title.from(Line.from(spans));
}
if (currentRouteId == null) {
- List<Span> spans = new ArrayList<>();
- spans.add(Span.styled(" Source [" + info + "] ", ts));
- if (saveMessage != null) {
- spans.add(Span.styled(saveMessage + " ", saveError ?
Theme.error() : Theme.success()));
- }
- return Title.from(Line.from(spans));
+ return Title.from(Span.styled(" Source [" + info + "] ", ts));
}
List<Span> spans = new ArrayList<>();
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
index abbc1770e79c..80b783a1b2d2 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
@@ -302,6 +302,9 @@ class TabRegistry {
routesTab.preloadDiagram();
diagramTab.preloadDiagram();
}
+ if (tab == TAB_SOURCE) {
+ sourceTab.onTabSelected();
+ }
if (tab == TAB_LOG) {
callbacks.refreshLogData();
logTab.onTabSelected();
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 3b0f6974ce94..02c344e678b6 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
@@ -48,11 +48,17 @@ class SourceViewerEditTest {
private SourceViewer viewer;
private Path sourceFile;
+ private final AtomicReference<String> lastNotification = new
AtomicReference<>();
+ private final AtomicReference<Boolean> lastNotificationError = new
AtomicReference<>();
@BeforeEach
void setUp() throws Exception {
Theme.resetForTesting();
viewer = new SourceViewer();
+ viewer.setNotificationCallback((msg, error) -> {
+ lastNotification.set(msg);
+ lastNotificationError.set(error);
+ });
sourceFile = tempDir.resolve("route.camel.yaml");
Files.writeString(sourceFile, """
- route:
@@ -398,21 +404,20 @@ class SourceViewerEditTest {
}
@Test
- void escDismissClearsSaveMessageBeforeReopen() {
+ void saveNotifiesViaCallback() {
viewer.loadFile(sourceFile);
viewer.enterEditMode();
viewer.handleKeyEvent(KeyEvent.ofChar('x', KeyModifiers.NONE));
viewer.handleKeyEvent(KeyEvent.ofKey(KeyCode.F5, KeyModifiers.NONE));
- List<Span> spans = new ArrayList<>();
- viewer.renderFooter(spans);
- assertThat(spansToString(spans)).contains("Saved");
+ assertThat(lastNotification.get()).contains("Saved");
+ assertThat(lastNotificationError.get()).isFalse();
viewer.handleKeyEvent(KeyEvent.ofKey(KeyCode.ESCAPE,
KeyModifiers.NONE));
assertThat(viewer.isVisible()).isFalse();
+ List<Span> spans = new ArrayList<>();
viewer.loadFile(sourceFile);
- spans.clear();
viewer.renderFooter(spans);
assertThat(spansToString(spans)).doesNotContain("Saved");
}