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"); }
