feat: toggle hidden directories in cd-browser with . key
This commit is contained in:
@@ -26,6 +26,7 @@ Working in the terminal often means:
|
||||
- 🖥 Native terminal interface
|
||||
- 🔁 Works with Bash, Zsh and other shells
|
||||
- ⚡ Returns the selected path to the shell
|
||||
- 🔎 Press `.` to toggle hidden directories
|
||||
|
||||
## Quick Demo
|
||||
|
||||
@@ -102,6 +103,8 @@ Then use:
|
||||
cd_
|
||||
```
|
||||
|
||||
- Dentro de `cd_browser`, presiona `.` para alternar la visualización de carpetas ocultas.
|
||||
|
||||
## Uninstall
|
||||
|
||||
If the project was installed with `pip`, remove it with:
|
||||
|
||||
@@ -13,28 +13,41 @@ class DirectoryEntry:
|
||||
has_children: bool
|
||||
|
||||
|
||||
def list_directories(path: Path) -> list[DirectoryEntry]:
|
||||
def list_directories(path: Path, show_hidden: bool = False) -> list[DirectoryEntry]:
|
||||
"""Return direct child directories sorted by name."""
|
||||
|
||||
directory_path = path.expanduser().resolve()
|
||||
entries: list[DirectoryEntry] = []
|
||||
|
||||
for child in directory_path.iterdir():
|
||||
if child.is_dir():
|
||||
entries.append(
|
||||
DirectoryEntry(
|
||||
name=child.name,
|
||||
path=child,
|
||||
has_children=has_subdirectories(child),
|
||||
)
|
||||
if not child.is_dir():
|
||||
continue
|
||||
if not show_hidden and child.name.startswith("."):
|
||||
continue
|
||||
|
||||
entries.append(
|
||||
DirectoryEntry(
|
||||
name=child.name,
|
||||
path=child,
|
||||
has_children=has_subdirectories(child, show_hidden=show_hidden),
|
||||
)
|
||||
)
|
||||
|
||||
return sorted(entries, key=lambda entry: entry.name.casefold())
|
||||
|
||||
|
||||
def has_subdirectories(path: Path) -> bool:
|
||||
def has_subdirectories(path: Path, show_hidden: bool = False) -> bool:
|
||||
"""Return whether the directory contains at least one subdirectory."""
|
||||
|
||||
directory_path = path.expanduser().resolve()
|
||||
|
||||
return any(child.is_dir() for child in directory_path.iterdir())
|
||||
for child in directory_path.iterdir():
|
||||
if not child.is_dir():
|
||||
continue
|
||||
|
||||
if not show_hidden and child.name.startswith("."):
|
||||
continue
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -28,6 +28,7 @@ class Navigator:
|
||||
current_path: Path = field(init=False)
|
||||
selected_index: int = field(init=False, default=0)
|
||||
_expanded_paths: set[Path] = field(init=False, default_factory=set)
|
||||
show_hidden: bool = False
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.current_path = self.start_path.expanduser().resolve()
|
||||
@@ -144,6 +145,13 @@ class Navigator:
|
||||
self._set_current_path(path)
|
||||
return path
|
||||
|
||||
def toggle_hidden(self) -> None:
|
||||
"""Toggle showing/hiding hidden directory entries."""
|
||||
|
||||
self.show_hidden = not self.show_hidden
|
||||
self.selected_index = 0
|
||||
self._expanded_paths.clear()
|
||||
|
||||
def select_history_entry(self, index: int) -> Path:
|
||||
"""Jump to a directory stored in session history."""
|
||||
|
||||
@@ -154,7 +162,7 @@ class Navigator:
|
||||
def _build_entries(self, path: Path, depth: int) -> list[VisibleEntry]:
|
||||
entries: list[VisibleEntry] = []
|
||||
|
||||
for directory in list_directories(path):
|
||||
for directory in list_directories(path, show_hidden=self.show_hidden):
|
||||
is_expanded = directory.path in self._expanded_paths
|
||||
entries.append(
|
||||
VisibleEntry(
|
||||
|
||||
@@ -145,7 +145,8 @@ class TerminalUI:
|
||||
stdscr.refresh()
|
||||
return
|
||||
|
||||
path_text = str(navigator.current_path)
|
||||
hidden_status = "on" if navigator.show_hidden else "off"
|
||||
path_text = f"{navigator.current_path} [hidden: {hidden_status}]"
|
||||
stdscr.addnstr(0, 0, path_text, width - 1)
|
||||
|
||||
lines = build_render_lines(navigator)
|
||||
@@ -282,6 +283,11 @@ class TerminalUI:
|
||||
self._tree_scroll_offset = 0
|
||||
return None
|
||||
|
||||
if key == ord("."):
|
||||
navigator.toggle_hidden()
|
||||
self._tree_scroll_offset = 0
|
||||
return None
|
||||
|
||||
if key in (curses.KEY_ENTER, 10, 13):
|
||||
return navigator.selected_entry.path
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ import pytest
|
||||
|
||||
from app.cli import run_cli
|
||||
from app.navigator import Navigator
|
||||
from app.ui import TerminalUI
|
||||
|
||||
|
||||
class StubUI:
|
||||
class StubUI(TerminalUI):
|
||||
def __init__(self, result: Path) -> None:
|
||||
self.result = result
|
||||
self.navigator: Navigator | None = None
|
||||
|
||||
@@ -25,6 +25,31 @@ def test_navigator_initial_state_includes_parent_entry(tmp_path: Path) -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_toggle_hidden_directories(tmp_path: Path) -> None:
|
||||
root = tmp_path / "root"
|
||||
root.mkdir()
|
||||
visible = root / "visible"
|
||||
visible.mkdir()
|
||||
hidden = root / ".hidden"
|
||||
hidden.mkdir()
|
||||
|
||||
navigator = Navigator(root)
|
||||
|
||||
assert [entry.name for entry in navigator.visible_entries] == ["..", "visible"]
|
||||
|
||||
navigator.toggle_hidden()
|
||||
assert navigator.show_hidden is True
|
||||
assert [entry.name for entry in navigator.visible_entries] == [
|
||||
"..",
|
||||
".hidden",
|
||||
"visible",
|
||||
]
|
||||
|
||||
navigator.toggle_hidden()
|
||||
assert navigator.show_hidden is False
|
||||
assert [entry.name for entry in navigator.visible_entries] == ["..", "visible"]
|
||||
|
||||
|
||||
def test_expand_selected_directory_reveals_nested_entries(tmp_path: Path) -> None:
|
||||
current = tmp_path / "workspace"
|
||||
current.mkdir()
|
||||
|
||||
Reference in New Issue
Block a user