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

DirectoryValidator

Classes

FilterableDirectoryTree

DirectoryTree subclass that supports filtering hidden files.

DirectoryBrowserScreen

Modal screen for browsing and selecting a directory.

Functions

default_validator(path)

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.DirectoryTree

DirectoryTree subclass that supports filtering hidden files.

By default, hidden files (those starting with ‘.’) are not shown. Toggle visibility with the show_hidden attribute.

Initialize filterable directory tree.

Parameters:
  • path (str) – Root path for the tree

  • show_hidden (bool) – If True, show hidden files/directories

  • **kwargs (Any) – Additional arguments passed to DirectoryTree

property show_hidden: bool

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:

collections.abc.Iterable[pathlib.Path]

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:

tuple[bool, str, str]

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 validator callable that takes a Path and returns a tuple of (is_valid, type_label, message):

  • is_valid: True if the directory can be selected

  • type_label: Short label for valid directories (e.g., “corpus”)

  • message: Error message if invalid, or description if valid

Navigation:

  • j / down: Move cursor down

  • k / up: Move cursor up

  • h / left: Collapse directory

  • l / 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
show_hidden = False
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

select_pressed()[source]

Handle Select button press.

Dismisses the modal with the selected path.

cancel_pressed()[source]

Handle Cancel button press.

Dismisses the modal with None.

action_cursor_down()[source]

Move cursor down in directory tree (j key).

action_cursor_up()[source]

Move cursor up in directory tree (k key).

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.

action_cancel()[source]

Cancel and close the dialog (escape key).

action_toggle_hidden()[source]

Toggle visibility of hidden files (> key).