build_tools.tui_common.controls.browsers
Directory browser modal widget.
This module provides the DirectoryBrowserScreen modal for selecting directories with customizable validation.
Features:
Textual DirectoryTree widget for file system navigation
Vim-style keybindings (h/j/k/l) for navigation
Customizable validation callback
Visual feedback for valid/invalid selections
Select/Cancel buttons with state management
Example Usage:
from pathlib import Path
from build_tools.tui_common.controls import DirectoryBrowserScreen
# Custom validator for source directories
def validate_source_dir(path: Path) -> tuple[bool, str, str]:
txt_files = list(path.glob("*.txt"))
if txt_files:
return (True, "source", f"Found {len(txt_files)} text files")
return (False, "", "No .txt files found")
# In your App
async def select_directory(self) -> None:
result = await self.push_screen_wait(
DirectoryBrowserScreen(
title="Select Source Directory",
validator=validate_source_dir,
initial_dir=Path.cwd(),
)
)
if result:
print(f"Selected: {result}")
Attributes
Classes
DirectoryTree subclass that supports filtering hidden files. |
|
Modal screen for browsing and selecting a directory. |
Functions
|
Default validator that accepts any directory. |
Module Contents
- build_tools.tui_common.controls.browsers.DirectoryValidator
- class build_tools.tui_common.controls.browsers.FilterableDirectoryTree(path, show_hidden=False, **kwargs)[source]
Bases:
textual.widgets.DirectoryTreeDirectoryTree subclass that supports filtering hidden files.
By default, hidden files (those starting with ‘.’) are not shown. Toggle visibility with the
show_hiddenattribute.Initialize filterable directory tree.
- Parameters:
Whether hidden files are shown.
- filter_paths(paths)[source]
Filter paths to optionally exclude hidden files.
- Parameters:
paths (collections.abc.Iterable[pathlib.Path]) – Iterable of paths to filter
- Returns:
Filtered iterable of paths
- Return type:
- build_tools.tui_common.controls.browsers.default_validator(path)[source]
Default validator that accepts any directory.
- Parameters:
path (pathlib.Path) – Directory path to validate
- Returns:
Tuple of (True, “directory”, path name) for any directory
- Return type:
- class build_tools.tui_common.controls.browsers.DirectoryBrowserScreen(title='Select Directory', validator=None, initial_dir=None, help_text=None, root_dir=None)[source]
Bases:
textual.screen.ModalScreen[pathlib.Path | None]Modal screen for browsing and selecting a directory.
A modal dialog displaying a directory tree for navigation with customizable validation. Returns the selected directory path when user confirms, or None if cancelled.
Validation:
Provide a
validatorcallable that takes a Path and returns a tuple of(is_valid, type_label, message):is_valid: True if the directory can be selectedtype_label: Short label for valid directories (e.g., “corpus”)message: Error message if invalid, or description if valid
Navigation:
j/down: Move cursor downk/up: Move cursor uph/left: Collapse directoryl/right: Expand directory>(Shift+.): Toggle hidden files
- browser_title
Header text displayed at top of modal
- validator
Callback function to validate selected directories
- initial_dir
Starting directory for the browser
- selected_path
Currently selected path (or None)
- Returns:
Selected Path when “Select” is pressed, or None if cancelled
Example
result = await self.app.push_screen_wait( DirectoryBrowserScreen( title="Select Corpus Directory", validator=validate_corpus_directory, initial_dir=Path.home() / "corpora", ) ) if result: self.load_corpus(result)
Initialize directory browser.
- Parameters:
title (str) – Header text displayed at top of modal
validator (DirectoryValidator | None) – Callback function to validate directories. Signature:
(Path) -> (is_valid, type_label, message)If None, uses default_validator which accepts any directory.initial_dir (pathlib.Path | None) – Starting directory for browser (defaults to home directory)
help_text (str | None) – Custom help text to display. If None, uses default help text.
root_dir (pathlib.Path | None) – Root directory for the tree. If None, uses home directory. Set this higher than initial_dir to allow navigating up.
- BINDINGS = [('j', 'cursor_down', 'Down'), ('k', 'cursor_up', 'Up'), ('h', 'cursor_left', 'Collapse'), ('l',...
A list of key bindings.
- CSS = Multiline-String
Show Value
""" DirectoryBrowserScreen { align: center middle; } #browser-container { width: 80; height: 30; background: $panel; border: thick $primary; padding: 1; } #browser-header { text-align: center; text-style: bold; color: $accent; margin-bottom: 1; } #directory-tree { width: 100%; height: 1fr; border: solid $primary; margin-bottom: 1; } #help-text { height: 2; width: 100%; color: $text-muted; text-align: center; margin-bottom: 1; } #validation-status { height: 3; width: 100%; border: solid $primary; padding: 0 1; margin-bottom: 1; } .status-valid { color: $success; } .status-invalid { color: $error; } .status-none { color: $text-muted; } #button-bar { width: 100%; height: auto; align: center middle; } #button-bar Button { margin: 0 1; } """
Inline CSS, useful for quick scripts. Rules here take priority over CSS_PATH.
Note
This CSS applies to the whole app.
- browser_title = 'Select Directory'
- validator
- initial_dir
- root_dir
- help_text = 'Expand a directory to validate it. Click Select when valid.'
- selected_path: pathlib.Path | None = None
- compose()[source]
Create browser UI layout.
Layout: - Header with title - Help text - Directory tree (expandable/collapsible) - Validation status display - Select/Cancel buttons
- Yields:
Composed widget tree for the modal
- async on_mount()[source]
Handle screen mount event.
If initial_dir differs from root_dir, attempt to expand the tree to show and select initial_dir after a brief delay to let the tree load its initial content.
- directory_selected(event)[source]
Handle directory selection in tree.
Validates the selected directory using the configured validator and updates the UI accordingly. Triggered when user clicks on a directory NAME (not the expand arrow).
- Parameters:
event (textual.widgets.DirectoryTree.DirectorySelected) – Directory selection event from DirectoryTree
- node_expanded(event)[source]
Handle directory expansion in tree.
When a user expands a directory (via arrow click or ‘l’ key), validate it and allow selection if valid. This improves UX by letting users select a directory after navigating into it, without needing to click on the directory name again.
Note: We use Tree.NodeExpanded because DirectoryTree inherits from Tree and doesn’t define its own NodeExpanded message class.
- Parameters:
event (textual.widgets.Tree.NodeExpanded) – Node expanded event from Tree (parent class of DirectoryTree)
- file_selected(event)[source]
Handle file selection in tree.
When a file is clicked, we don’t clear the current selection since the user may have already validated the parent directory by expanding into it. Instead, we provide a gentle reminder that the parent directory is what will be selected.
- Parameters:
event (textual.widgets.DirectoryTree.FileSelected) – File selection event from DirectoryTree
- action_cursor_left()[source]
Collapse directory in tree (h key).
Collapses the current directory node if expanded.
- action_cursor_right()[source]
Expand directory in tree (l key).
Expands the current directory node if collapsed.
- action_toggle_node()[source]
Toggle expand/collapse of current node (space key).
Expands collapsed nodes, collapses expanded nodes.
- action_select_node()[source]
Select the current node (enter key).
If the current node is a directory, validates it. If already valid, confirms the selection.
Toggle visibility of hidden files (> key).