Files
gniza4linux/tui/widgets/snapshot_browser.py
shuki 5b4bf33520 Add tree-based file browser for snapshot contents
Replace flat file list with a Tree widget that shows directory
structure. Strips remote path prefix to show relative paths only.
Folders shown in bold with trailing /, sorted dirs-first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 05:11:53 +02:00

65 lines
2.1 KiB
Python

from __future__ import annotations
from pathlib import PurePosixPath
from textual.app import ComposeResult
from textual.screen import ModalScreen
from textual.widgets import Tree, Static, Button
from textual.containers import Vertical, Horizontal
class SnapshotBrowser(ModalScreen[None]):
"""Modal file browser for remote snapshot contents."""
BINDINGS = [("escape", "close", "Close")]
def __init__(self, title: str, file_list: list[str]):
super().__init__()
self._title = title
self._file_list = file_list
def compose(self) -> ComposeResult:
with Vertical(id="snapshot-browser"):
yield Static(self._title, id="sb-title")
yield Tree("snapshot", id="sb-tree")
with Horizontal(id="sb-buttons"):
yield Button("Close", variant="primary", id="sb-close")
def on_mount(self) -> None:
tree = self.query_one("#sb-tree", Tree)
tree.root.expand()
self._build_tree(tree)
def _build_tree(self, tree: Tree) -> None:
# Build a nested dict from file paths
root: dict = {}
for filepath in self._file_list:
filepath = filepath.strip()
if not filepath:
continue
parts = PurePosixPath(filepath).parts
node = root
for part in parts:
if part not in node:
node[part] = {}
node = node[part]
# Add to tree widget recursively
self._add_nodes(tree.root, root)
tree.root.expand_all()
def _add_nodes(self, parent, structure: dict) -> None:
# Sort: directories first, then files
dirs = sorted(k for k, v in structure.items() if v)
files = sorted(k for k, v in structure.items() if not v)
for name in dirs:
branch = parent.add(f"[bold]{name}/[/bold]")
self._add_nodes(branch, structure[name])
for name in files:
parent.add_leaf(name)
def on_button_pressed(self, event: Button.Pressed) -> None:
self.dismiss(None)
def action_close(self) -> None:
self.dismiss(None)