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 6c2d07a67439 CAMEL-23771: Add DSL format tabs to TUI source viewer
(#24056)
6c2d07a67439 is described below
commit 6c2d07a67439026744f86daffb77e5fc9a3464e1
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jun 16 19:35:32 2026 +0200
CAMEL-23771: Add DSL format tabs to TUI source viewer (#24056)
* CAMEL-23771: Add DSL format tabs to TUI source viewer
The source viewer (c key) now shows YAML/Java/XML format tabs in the
title bar when viewing route source code. Press Y/J/X to switch between
DSL representations. The original source format is marked with *.
Original tab loads from SourceDevConsole (real file), converted tabs
load from RouteDumpDevConsole (model dump). Format tabs only appear
for route source viewing, not generic file viewing.
Co-Authored-By: Claude <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* CAMEL-23771: Pass uriAsParameters for route-dump action in TUI
Co-Authored-By: Claude <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---------
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude <[email protected]>
---
.../dsl/jbang/core/commands/tui/SourceViewer.java | 224 ++++++++++++++++++++-
1 file changed, 223 insertions(+), 1 deletion(-)
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 8a10d434c6a3..19c4108b3d5c 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
@@ -39,6 +39,7 @@ import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
+import dev.tamboui.widgets.block.Title;
import dev.tamboui.widgets.paragraph.Paragraph;
import dev.tamboui.widgets.scrollbar.Scrollbar;
import dev.tamboui.widgets.scrollbar.ScrollbarState;
@@ -74,6 +75,11 @@ class SourceViewer {
private final Map<String, CachedSource> sourceCache = new
ConcurrentHashMap<>();
private boolean wordWrap;
private final SearchHighlighter search = new SearchHighlighter();
+ private String currentFormat;
+ private String originalFormat;
+ private String currentRouteId;
+ private MonitorContext currentCtx;
+ private String currentPid;
private record CachedSource(
List<String> lines, List<JsonObject> codeData,
@@ -102,6 +108,11 @@ class SourceViewer {
sourceCache.clear();
wordWrap = false;
search.reset();
+ currentFormat = null;
+ originalFormat = null;
+ currentRouteId = null;
+ currentCtx = null;
+ currentPid = null;
}
void setOnLineSelected(IntConsumer callback) {
@@ -133,6 +144,20 @@ class SourceViewer {
onLineSelected = null;
return true;
}
+ if (currentRouteId != null) {
+ if (ke.isChar('Y') || ke.isChar('y')) {
+ switchFormat("yaml");
+ return true;
+ }
+ if (ke.isChar('J') || ke.isChar('j')) {
+ switchFormat("java");
+ return true;
+ }
+ if (ke.isChar('X') || ke.isChar('x')) {
+ switchFormat("xml");
+ return true;
+ }
+ }
if (search.handleKeyEvent(ke)) {
int matchLine = search.currentMatchLine();
if (matchLine >= 0) {
@@ -199,7 +224,7 @@ class SourceViewer {
void render(Frame frame, Rect area) {
Block block = Block.builder()
.borderType(BorderType.ROUNDED)
- .title(" Source [" + (title != null ? title : "") + "] ")
+ .title(buildTitle())
.build();
Rect inner = block.inner(area);
frame.renderWidget(block, area);
@@ -282,6 +307,11 @@ class SourceViewer {
MonitorContext.hint(spans, "Esc/c", "close");
}
MonitorContext.hint(spans, "↑↓", "navigate");
+ if (currentRouteId != null) {
+ MonitorContext.hint(spans, "Y", "yaml");
+ MonitorContext.hint(spans, "J", "java");
+ MonitorContext.hint(spans, "X", "xml");
+ }
search.renderSearchHints(spans);
MonitorContext.hint(spans, "w", "wrap" + (wordWrap ? " [on]" : "
[off]"));
if (!wordWrap) {
@@ -297,6 +327,11 @@ class SourceViewer {
* Load source for a route, scrolling to the given source line number.
*/
void loadFile(Path filePath) {
+ currentRouteId = null;
+ currentFormat = null;
+ originalFormat = null;
+ currentCtx = null;
+ currentPid = null;
String fileName = filePath.getFileName().toString();
try {
List<String> rawLines = java.nio.file.Files.readAllLines(filePath,
java.nio.charset.StandardCharsets.UTF_8);
@@ -342,6 +377,9 @@ class SourceViewer {
}
String pid = ctx.selectedPid;
+ currentRouteId = routeId;
+ currentCtx = ctx;
+ currentPid = pid;
String cacheKey = pid + ":" + routeId;
CachedSource cached = sourceCache.get(cacheKey);
if (cached == null && sourceLocationHint != null) {
@@ -407,6 +445,11 @@ class SourceViewer {
scrollX = 0;
pendingScroll = true;
visible = true;
+ String fmt = languageToFormat(cached.language);
+ if (fmt != null) {
+ originalFormat = fmt;
+ currentFormat = fmt;
+ }
}
private void loadInBackground(MonitorContext ctx, String pid, String
routeId, int targetLine) {
@@ -489,6 +532,10 @@ class SourceViewer {
if (sourceLocation != null) {
sourceCache.put(pid + ":loc:" + sourceLocation, cached);
}
+ String fmt = languageToFormat(lang);
+ if (fmt != null) {
+ sourceCache.put(pid + ":" + routeId + ":" + fmt, cached);
+ }
applyResult(ctx, routeId, sourceLocation, result, codeLines, scrollTo,
cursorLine);
}
@@ -511,9 +558,184 @@ class SourceViewer {
selectedLine = Math.max(0, cursorLine);
scrollY = 0;
pendingScroll = true;
+ if (currentRouteId != null) {
+ String fmt = languageToFormat(language);
+ if (fmt != null) {
+ originalFormat = fmt;
+ currentFormat = fmt;
+ }
+ }
+ });
+ }
+
+ private Title buildTitle() {
+ String info = title != null ? title : "";
+ if (currentRouteId == null) {
+ return Title.from(Line.from(List.of(Span.raw(" Source [" + info +
"] "))));
+ }
+
+ List<Span> spans = new ArrayList<>();
+ spans.add(Span.raw(" Source [" + info + "] "));
+
+ String[] formats = { "yaml", "java", "xml" };
+ String[] labels = { "YAML", "Java", "XML" };
+
+ for (int i = 0; i < formats.length; i++) {
+ if (i > 0) {
+ spans.add(Span.styled(" │ ", Style.EMPTY.dim()));
+ }
+ String label = labels[i];
+ if (formats[i].equals(originalFormat)) {
+ label += "*";
+ }
+ if (formats[i].equals(currentFormat)) {
+ spans.add(Span.styled(label, Style.EMPTY.bold()));
+ } else {
+ spans.add(Span.styled(label, Style.EMPTY.dim()));
+ }
+ }
+ spans.add(Span.raw(" "));
+
+ return Title.from(Line.from(spans));
+ }
+
+ private void switchFormat(String format) {
+ if (format.equals(currentFormat)) {
+ return;
+ }
+ if (currentPid == null || currentRouteId == null || currentCtx ==
null) {
+ return;
+ }
+
+ String cacheKey = currentPid + ":" + currentRouteId + ":" + format;
+ CachedSource cached = sourceCache.get(cacheKey);
+ if (cached != null) {
+ lines = cached.lines;
+ codeData = cached.codeData;
+ language = cached.language;
+ currentFormat = format;
+ selectedLine = findLicenseHeaderEnd(codeData);
+ scrollY = 0;
+ scrollX = 0;
+ pendingScroll = true;
+ search.reset();
+ return;
+ }
+
+ if (!loading.compareAndSet(false, true)) {
+ return;
+ }
+
+ lines = List.of("(Loading " + format + " format...)");
+ currentFormat = format;
+ scrollY = 0;
+ scrollX = 0;
+
+ MonitorContext ctx = currentCtx;
+ String pid = currentPid;
+ String routeId = currentRouteId;
+ ctx.runner.scheduler().execute(() -> {
+ try {
+ if (format.equals(originalFormat)) {
+ loadInBackground(ctx, pid, routeId, 0);
+ } else {
+ loadFormatInBackground(ctx, pid, routeId, format);
+ }
+ } finally {
+ loading.set(false);
+ }
+ });
+ }
+
+ private void loadFormatInBackground(MonitorContext ctx, String pid, String
routeId, String format) {
+ Path outputFile = ctx.getOutputFile(pid);
+ PathUtils.deleteFile(outputFile);
+
+ JsonObject root = new JsonObject();
+ root.put("action", "route-dump");
+ root.put("filter", routeId);
+ root.put("format", format);
+ root.put("uriAsParameters", "yaml".equals(format) ? "true" : "false");
+
+ Path actionFile = ctx.getActionFile(pid);
+ PathUtils.writeTextSafely(root.toJson(), actionFile);
+
+ JsonObject jo = pollJsonResponse(outputFile, 5000);
+ PathUtils.deleteFile(outputFile);
+
+ if (jo == null) {
+ applyFormatResult(ctx, format, List.of("(No response from
integration)"), Collections.emptyList());
+ return;
+ }
+
+ JsonArray routes = (JsonArray) jo.get("routes");
+ if (routes == null || routes.isEmpty()) {
+ applyFormatResult(ctx, format,
+ List.of("(No dump available for route: " + routeId + ")"),
Collections.emptyList());
+ return;
+ }
+
+ JsonObject routeObj = (JsonObject) routes.get(0);
+ List<JsonObject> codeLines = routeObj.getCollection("code");
+ if (codeLines == null || codeLines.isEmpty()) {
+ applyFormatResult(ctx, format, List.of("(No code available)"),
Collections.emptyList());
+ return;
+ }
+
+ List<String> result = new ArrayList<>();
+ int lineNumWidth = String.valueOf(codeLines.size()).length();
+ for (int i = 0; i < codeLines.size(); i++) {
+ String code =
Jsoner.unescape(objToString(codeLines.get(i).get("code")));
+ result.add(String.format("%" + lineNumWidth + "d %s", i + 1,
code));
+ }
+
+ SyntaxHighlighter.Language lang = formatToLanguage(format);
+ CachedSource cached = new CachedSource(result, codeLines, null, lang);
+ sourceCache.put(pid + ":" + routeId + ":" + format, cached);
+
+ applyFormatResult(ctx, format, result, codeLines);
+ }
+
+ private void applyFormatResult(
+ MonitorContext ctx, String format,
+ List<String> resultLines, List<JsonObject> codeLines) {
+ if (ctx.runner == null) {
+ return;
+ }
+ ctx.runner.runOnRenderThread(() -> {
+ if (!visible) {
+ return;
+ }
+ language = formatToLanguage(format);
+ lines = resultLines;
+ codeData = codeLines;
+ currentFormat = format;
+ selectedLine = findLicenseHeaderEnd(codeLines);
+ scrollY = 0;
+ scrollX = 0;
+ pendingScroll = true;
+ search.reset();
});
}
+ private static String languageToFormat(SyntaxHighlighter.Language lang) {
+ return switch (lang) {
+ case YAML -> "yaml";
+ case JAVA -> "java";
+ case XML -> "xml";
+ default -> null;
+ };
+ }
+
+ private static SyntaxHighlighter.Language formatToLanguage(String format) {
+ return switch (format) {
+ case "yaml" -> SyntaxHighlighter.Language.YAML;
+ case "java" -> SyntaxHighlighter.Language.JAVA;
+ case "xml" -> SyntaxHighlighter.Language.XML;
+ default -> SyntaxHighlighter.Language.PLAIN;
+ };
+ }
+
private Line highlightSourceLine(String raw, int hSkip, boolean
isSelected, int viewportWidth) {
int prefixEnd = 0;
while (prefixEnd < raw.length() && (raw.charAt(prefixEnd) == ' ' ||
Character.isDigit(raw.charAt(prefixEnd)))) {