This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch feature/CAMEL-23857-tui-runtime-chooser
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 64542e253c9691911c45a14f35f00225bcf16802
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jun 29 20:51:55 2026 +0200

    CAMEL-23857: TUI Browse Files support navigating subdirectories
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/FilesBrowser.java  | 92 ++++++++++++++++------
 1 file changed, 68 insertions(+), 24 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
index b6089cf01d64..e6cc0837027f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
@@ -45,7 +45,7 @@ import dev.tamboui.widgets.list.ScrollMode;
 
 class FilesBrowser {
 
-    record FileEntry(String emoji, String name, long size, String path) {
+    record FileEntry(String emoji, String name, long size, String path, 
boolean directory) {
     }
 
     private static final String[] CAMEL_YAML_MARKERS = {
@@ -65,6 +65,8 @@ class FilesBrowser {
 
     private boolean visible;
     private String title;
+    private Path rootDir;
+    private Path currentDir;
     private final ListState listState = new ListState();
     private List<FileEntry> entries = Collections.emptyList();
     private final SourceViewer sourceViewer = new SourceViewer();
@@ -83,6 +85,8 @@ class FilesBrowser {
 
     void reset() {
         visible = false;
+        rootDir = null;
+        currentDir = null;
         entries = Collections.emptyList();
         sourceViewer.reset();
     }
@@ -92,33 +96,56 @@ class FilesBrowser {
         if (dir == null || !Files.isDirectory(dir)) {
             return;
         }
-        List<FileEntry> found = new ArrayList<>();
+        rootDir = dir;
+        currentDir = dir;
+        title = info.name != null ? info.name : "?";
+        sourceViewer.reset();
+        if (!loadDirectory(dir)) {
+            return;
+        }
+        visible = true;
+    }
+
+    private boolean loadDirectory(Path dir) {
+        List<FileEntry> dirs = new ArrayList<>();
+        List<FileEntry> files = new ArrayList<>();
         try (var stream = Files.list(dir)) {
-            stream.filter(Files::isRegularFile)
-                    .limit(99)
+            stream.limit(200)
                     .forEach(p -> {
                         String name = p.getFileName().toString();
-                        String emoji = fileEmoji(p);
-                        long size = 0;
-                        try {
-                            size = Files.size(p);
-                        } catch (IOException e) {
-                            // ignore
+                        if (Files.isDirectory(p)) {
+                            dirs.add(new FileEntry("📁", name, -1, 
p.toString(), true));
+                        } else if (Files.isRegularFile(p)) {
+                            String emoji = fileEmoji(p);
+                            long size = 0;
+                            try {
+                                size = Files.size(p);
+                            } catch (IOException e) {
+                                // ignore
+                            }
+                            files.add(new FileEntry(emoji, name, size, 
p.toString(), false));
                         }
-                        found.add(new FileEntry(emoji, name, size, 
p.toString()));
                     });
         } catch (IOException e) {
-            return;
+            return false;
+        }
+        dirs.sort(Comparator.comparing(FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
+        files.sort(Comparator.comparing(FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
+
+        List<FileEntry> found = new ArrayList<>();
+        if (!dir.equals(rootDir)) {
+            found.add(new FileEntry("📁", "..", -1, dir.getParent().toString(), 
true));
         }
+        found.addAll(dirs);
+        found.addAll(files);
+
         if (found.isEmpty()) {
-            return;
+            return false;
         }
-        found.sort(Comparator.comparing(FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
         entries = found;
-        title = info.name != null ? info.name : "?";
         listState.select(0);
-        visible = true;
-        sourceViewer.reset();
+        currentDir = dir;
+        return true;
     }
 
     boolean handleKeyEvent(KeyEvent ke) {
@@ -148,7 +175,11 @@ class FilesBrowser {
                 Integer sel = listState.selected();
                 if (sel != null && sel < entries.size()) {
                     FileEntry entry = entries.get(sel);
-                    sourceViewer.loadFile(Path.of(entry.path()));
+                    if (entry.directory()) {
+                        loadDirectory(Path.of(entry.path()));
+                    } else {
+                        sourceViewer.loadFile(Path.of(entry.path()));
+                    }
                 }
                 return true;
             }
@@ -168,7 +199,9 @@ class FilesBrowser {
         }
 
         int nameWidth = entries.stream().mapToInt(e -> 
e.name().length()).max().orElse(10);
-        int sizeWidth = entries.stream().mapToInt(e -> 
formatFileSize(e.size()).length()).max().orElse(4);
+        int sizeWidth = entries.stream()
+                .filter(e -> !e.directory())
+                .mapToInt(e -> 
formatFileSize(e.size()).length()).max().orElse(4);
         int itemWidth = 4 + nameWidth + 2 + sizeWidth + 2;
         int popupW = Math.min(area.width() - 4, Math.max(30, itemWidth + 4));
         int popupH = Math.min(area.height() - 4, entries.size() + 2);
@@ -182,10 +215,21 @@ class FilesBrowser {
         ListItem[] items = new ListItem[entries.size()];
         for (int i = 0; i < entries.size(); i++) {
             FileEntry entry = entries.get(i);
-            String sizeStr = formatFileSize(entry.size());
-            String label = String.format("  %s %-" + nameWidth + "s  %s", 
entry.emoji(), entry.name(), sizeStr);
-            items[i] = ListItem.from(label);
+            if (entry.directory()) {
+                String label = String.format("  %s %-" + nameWidth + "s", 
entry.emoji(), entry.name());
+                items[i] = ListItem.from(label);
+            } else {
+                String sizeStr = formatFileSize(entry.size());
+                String label = String.format("  %s %-" + nameWidth + "s  %s", 
entry.emoji(), entry.name(), sizeStr);
+                items[i] = ListItem.from(label);
+            }
+        }
+
+        String popupTitle = " Files: " + title;
+        if (rootDir != null && currentDir != null && 
!currentDir.equals(rootDir)) {
+            popupTitle += "/" + rootDir.relativize(currentDir);
         }
+        popupTitle += " ";
 
         ListWidget list = ListWidget.builder()
                 .items(items)
@@ -195,7 +239,7 @@ class FilesBrowser {
                 .block(Block.builder()
                         .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                         .title(Title.from(Line
-                                .from(Span.styled(" Files: " + title + " ", 
Style.EMPTY.fg(Color.YELLOW).bold()))))
+                                .from(Span.styled(popupTitle, 
Style.EMPTY.fg(Color.YELLOW).bold()))))
                         .build())
                 .build();
         frame.renderStatefulWidget(list, popup, listState);
@@ -205,7 +249,7 @@ class FilesBrowser {
         if (sourceViewer.isVisible()) {
             sourceViewer.renderFooter(spans);
         } else {
-            MonitorContext.hint(spans, "Up/Down", "navigate");
+            MonitorContext.hint(spans, "↑↓", "navigate");
             MonitorContext.hint(spans, "Enter", "open");
             MonitorContext.hint(spans, "Esc", "close");
         }

Reply via email to