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)))) {

Reply via email to