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 fd1a81074fa4 Camel TUI: auto-skip empty middle folders when browsing
fd1a81074fa4 is described below

commit fd1a81074fa4fdaaf8b876a7ab8eea6d30c1d576
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 2 10:32:51 2026 +0200

    Camel TUI: auto-skip empty middle folders when browsing
    
    In the folder/file browsers, navigating forward into a folder that has
    no files and exactly one sub folder now auto-descends through it, and
    ".."/Backspace auto-ascends back past those empty middle folders in one
    step. This is common in Java projects (e.g. src/main/java/com/foo/).
    
    Applied consistently to the Open Project popup (FolderBrowser), the
    Ctrl+F Browse Files popup (FilesBrowser) and the Source tab (SourceTab).
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/FilesBrowser.java  | 80 ++++++++++++++++++++--
 .../dsl/jbang/core/commands/tui/FolderBrowser.java | 39 ++++++++++-
 .../dsl/jbang/core/commands/tui/SourceTab.java     | 74 ++++++++++++++++++--
 3 files changed, 180 insertions(+), 13 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 ddf3c62baa13..3a70c9a7042a 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
@@ -92,6 +92,10 @@ class FilesBrowser {
     }
 
     private boolean loadDirectory(Path dir) {
+        return loadDirectory(dir, null);
+    }
+
+    private boolean loadDirectory(Path dir, String selectName) {
         List<FileEntry> dirs = new ArrayList<>();
         List<FileEntry> files = new ArrayList<>();
         try (var stream = Files.list(dir)) {
@@ -117,6 +121,12 @@ class FilesBrowser {
         dirs.sort(Comparator.comparing(FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
         files.sort(Comparator.comparing(FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
 
+        // auto-descend through empty middle folders (no files and exactly one 
sub folder)
+        // when navigating forward (not when restoring position while 
navigating back)
+        if (selectName == null && files.isEmpty() && dirs.size() == 1) {
+            return loadDirectory(Path.of(dirs.get(0).path()));
+        }
+
         List<FileEntry> found = new ArrayList<>();
         if (!dir.equals(rootDir)) {
             found.add(new FileEntry(TuiIcons.FOLDER, "..", -1, 
dir.getParent().toString(), true));
@@ -128,11 +138,63 @@ class FilesBrowser {
             return false;
         }
         entries = found;
-        listState.select(0);
+        int sel = 0;
+        if (selectName != null) {
+            for (int i = 0; i < found.size(); i++) {
+                if (found.get(i).name().equals(selectName)) {
+                    sel = i;
+                    break;
+                }
+            }
+        }
+        listState.select(sel);
         currentDir = dir;
         return true;
     }
 
+    private void navigateBack() {
+        if (currentDir == null || currentDir.equals(rootDir)) {
+            return;
+        }
+        Path child = currentDir;
+        Path parent = currentDir.getParent();
+        // skip back through empty middle folders (parent has no files and 
only this one sub folder),
+        // but never above the root directory
+        while (parent != null && !parent.equals(rootDir) && parent.getParent() 
!= null
+                && isEmptyMiddleFolder(parent)) {
+            child = parent;
+            parent = parent.getParent();
+        }
+        loadDirectory(parent, child.getFileName().toString());
+    }
+
+    private boolean isEmptyMiddleFolder(Path dir) {
+        int dirCount = 0;
+        try (var stream = Files.list(dir)) {
+            var it = stream.iterator();
+            while (it.hasNext()) {
+                Path p = it.next();
+                String name = p.getFileName().toString();
+                if (Files.isDirectory(p)) {
+                    if (name.startsWith(".")) {
+                        // hidden directories are not shown
+                        continue;
+                    }
+                    dirCount++;
+                    if (dirCount > 1) {
+                        return false;
+                    }
+                } else if (Files.isRegularFile(p)) {
+                    // has at least one visible file
+                    return false;
+                }
+            }
+        } catch (IOException e) {
+            return false;
+        }
+        return dirCount == 1;
+    }
+
     boolean handleMouseEvent(MouseEvent me) {
         if (sourceViewer.isVisible()) {
             return sourceViewer.handleMouseEvent(me);
@@ -152,7 +214,11 @@ class FilesBrowser {
                 listState.select(clicked);
                 FileEntry entry = entries.get(clicked);
                 if (entry.directory()) {
-                    loadDirectory(Path.of(entry.path()));
+                    if ("..".equals(entry.name())) {
+                        navigateBack();
+                    } else {
+                        loadDirectory(Path.of(entry.path()));
+                    }
                 } else {
                     sourceViewer.loadFile(Path.of(entry.path()));
                 }
@@ -206,9 +272,7 @@ class FilesBrowser {
                 return true;
             }
             if (ke.isDeleteBackward()) {
-                if (currentDir != null && !currentDir.equals(rootDir)) {
-                    loadDirectory(currentDir.getParent());
-                }
+                navigateBack();
                 return true;
             }
             if (ke.isConfirm()) {
@@ -216,7 +280,11 @@ class FilesBrowser {
                 if (sel != null && sel < entries.size()) {
                     FileEntry entry = entries.get(sel);
                     if (entry.directory()) {
-                        loadDirectory(Path.of(entry.path()));
+                        if ("..".equals(entry.name())) {
+                            navigateBack();
+                        } else {
+                            loadDirectory(Path.of(entry.path()));
+                        }
                     } else {
                         sourceViewer.loadFile(Path.of(entry.path()));
                     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
index 7a535e0a6965..afa358b68989 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
@@ -123,6 +123,12 @@ class FolderBrowser {
         dirs.sort(Comparator.comparing(DirEntry::name, 
String.CASE_INSENSITIVE_ORDER));
         files.sort(Comparator.comparing(DirEntry::name, 
String.CASE_INSENSITIVE_ORDER));
 
+        // auto-descend through empty middle folders (no files and exactly one 
sub folder)
+        // when navigating forward (not when restoring position while 
navigating back)
+        if (selectName == null && files.isEmpty() && dirs.size() == 1) {
+            return loadDirectory(Path.of(dirs.get(0).path()));
+        }
+
         List<DirEntry> found = new ArrayList<>();
         Path parent = dir.getParent();
         if (parent != null) {
@@ -152,13 +158,42 @@ class FolderBrowser {
     }
 
     private void navigateBack() {
-        String childName = currentDir.getFileName().toString();
-        if (loadDirectory(currentDir.getParent(), childName)) {
+        Path child = currentDir;
+        Path parent = currentDir.getParent();
+        // skip back through empty middle folders (parent has no files and 
only this one sub folder)
+        while (parent != null && parent.getParent() != null && 
isEmptyMiddleFolder(parent)) {
+            child = parent;
+            parent = parent.getParent();
+        }
+        String childName = child.getFileName().toString();
+        if (loadDirectory(parent, childName)) {
             int savedOffset = offsetStack.isEmpty() ? 0 : offsetStack.pop();
             listState.setOffset(savedOffset);
         }
     }
 
+    private boolean isEmptyMiddleFolder(Path dir) {
+        int dirCount = 0;
+        try (var stream = Files.list(dir)) {
+            var it = stream.filter(p -> 
!p.getFileName().toString().startsWith(".")).iterator();
+            while (it.hasNext()) {
+                Path p = it.next();
+                if (Files.isDirectory(p)) {
+                    dirCount++;
+                    if (dirCount > 1) {
+                        return false;
+                    }
+                } else {
+                    // has at least one file
+                    return false;
+                }
+            }
+        } catch (IOException e) {
+            return false;
+        }
+        return dirCount == 1;
+    }
+
     boolean handleMouseEvent(MouseEvent me) {
         if (sourceViewer.isVisible()) {
             return sourceViewer.handleMouseEvent(me);
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 1f568084bbe5..9093fd8e583b 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
@@ -588,6 +588,10 @@ class SourceTab extends AbstractTab {
     }
 
     private boolean loadDirectory(Path dir) {
+        return loadDirectory(dir, null);
+    }
+
+    private boolean loadDirectory(Path dir, String selectName) {
         List<FilesBrowser.FileEntry> dirs = new ArrayList<>();
         List<FilesBrowser.FileEntry> files = new ArrayList<>();
         try (var stream = Files.list(dir)) {
@@ -613,6 +617,12 @@ class SourceTab extends AbstractTab {
         dirs.sort(Comparator.comparing(FilesBrowser.FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
         files.sort(Comparator.comparing(FilesBrowser.FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
 
+        // auto-descend through empty middle folders (no files and exactly one 
sub folder)
+        // when navigating forward (not when restoring position while 
navigating back)
+        if (selectName == null && files.isEmpty() && dirs.size() == 1) {
+            return loadDirectory(Path.of(dirs.get(0).path()));
+        }
+
         List<FilesBrowser.FileEntry> found = new ArrayList<>();
         if (!dir.equals(rootDir)) {
             found.add(new FilesBrowser.FileEntry(TuiIcons.FOLDER, "..", -1, 
dir.getParent().toString(), true));
@@ -624,12 +634,64 @@ class SourceTab extends AbstractTab {
             return false;
         }
         entries = found;
-        listState.select(0);
+        int sel = 0;
+        if (selectName != null) {
+            for (int i = 0; i < found.size(); i++) {
+                if (found.get(i).name().equals(selectName)) {
+                    sel = i;
+                    break;
+                }
+            }
+        }
+        listState.select(sel);
         currentDir = dir;
         buildRouteIndex();
         return true;
     }
 
+    private void navigateBack() {
+        if (currentDir == null || currentDir.equals(rootDir)) {
+            return;
+        }
+        Path child = currentDir;
+        Path parent = currentDir.getParent();
+        // skip back through empty middle folders (parent has no files and 
only this one sub folder),
+        // but never above the root directory
+        while (parent != null && !parent.equals(rootDir) && parent.getParent() 
!= null
+                && isEmptyMiddleFolder(parent)) {
+            child = parent;
+            parent = parent.getParent();
+        }
+        loadDirectory(parent, child.getFileName().toString());
+    }
+
+    private boolean isEmptyMiddleFolder(Path dir) {
+        int dirCount = 0;
+        try (var stream = Files.list(dir)) {
+            var it = stream.iterator();
+            while (it.hasNext()) {
+                Path p = it.next();
+                String name = p.getFileName().toString();
+                if (Files.isDirectory(p)) {
+                    if (name.startsWith(".")) {
+                        // hidden directories are not shown
+                        continue;
+                    }
+                    dirCount++;
+                    if (dirCount > 1) {
+                        return false;
+                    }
+                } else if (Files.isRegularFile(p)) {
+                    // has at least one visible file
+                    return false;
+                }
+            }
+        } catch (IOException e) {
+            return false;
+        }
+        return dirCount == 1;
+    }
+
     private boolean handleFileListKey(KeyEvent ke) {
         if (ke.isUp()) {
             listState.selectPrevious();
@@ -660,9 +722,7 @@ class SourceTab extends AbstractTab {
             return true;
         }
         if (ke.isDeleteBackward()) {
-            if (currentDir != null && !currentDir.equals(rootDir)) {
-                loadDirectory(currentDir.getParent());
-            }
+            navigateBack();
             return true;
         }
         if (ke.isConfirm()) {
@@ -687,7 +747,11 @@ class SourceTab extends AbstractTab {
         if (sel != null && sel < entries.size()) {
             FilesBrowser.FileEntry entry = entries.get(sel);
             if (entry.directory()) {
-                loadDirectory(Path.of(entry.path()));
+                if ("..".equals(entry.name())) {
+                    navigateBack();
+                } else {
+                    loadDirectory(Path.of(entry.path()));
+                }
             } else {
                 Path filePath = Path.of(entry.path());
                 if (isCamelSourceFile(filePath)) {

Reply via email to