From d8aa103633b0c2765ebf7e8c0bcc533a6171a754 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 20 May 2022 13:52:03 +0100 Subject: [PATCH] Simple file search live filter sandbox example --- sandbox/file_search.py | 74 +++++++++++++++++++++++++++++++ sandbox/file_search.scss | 21 +++++++++ src/textual/widgets/text_input.py | 13 +----- 3 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 sandbox/file_search.py create mode 100644 sandbox/file_search.scss diff --git a/sandbox/file_search.py b/sandbox/file_search.py new file mode 100644 index 000000000..bf391db74 --- /dev/null +++ b/sandbox/file_search.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Iterable + +from rich.console import RenderableType +from rich.style import Style +from rich.table import Table +from rich.text import Text + +from textual.app import App +from textual.geometry import Size +from textual.reactive import Reactive +from textual.widget import Widget +from textual.widgets.text_input import TextInput, TextWidgetBase + + +def get_files() -> list[Path]: + files = [file for file in Path.cwd().iterdir()] + return files + + +class FileTable(Widget): + filter = Reactive("", layout=True) + + def __init__(self, *args, files: Iterable[Path] | None = None, **kwargs): + super().__init__(*args, **kwargs) + self.files = files if files is not None else [] + + @property + def filtered_files(self) -> list[Path]: + return [ + file + for file in self.files + if self.filter == "" or (self.filter and self.filter in file.name) + ] + + def get_content_height(self, container: Size, viewport: Size, width: int) -> int: + return len(self.filtered_files) + + def render(self, style: Style) -> RenderableType: + grid = Table.grid() + grid.add_column() + for file in self.filtered_files: + file_text = Text(" " + file.name) + file_text.highlight_regex(self.filter, "black on yellow") + grid.add_row(file_text) + return grid + + +class FileSearchApp(App[str]): + dark = True + + def on_mount(self) -> None: + self.file_table = FileTable(id="file_table", files=list(Path.cwd().iterdir())) + self.mount(file_table_wrapper=Widget(self.file_table)) + + self.search_bar = TextInput(placeholder="Search for files...") + self.search_bar.cursor_blink_enabled = True + self.search_bar.focus() + + self.mount(search_bar=self.search_bar) + + def handle_changed(self, event: TextWidgetBase.Changed) -> None: + if event.sender == self.search_bar: + self.file_table.filter = event.value + + +app = FileSearchApp( + log_path="textual.log", css_path="file_search.scss", watch_css=True, log_verbosity=2 +) + +if __name__ == "__main__": + result = app.run() diff --git a/sandbox/file_search.scss b/sandbox/file_search.scss new file mode 100644 index 000000000..5163bd5b1 --- /dev/null +++ b/sandbox/file_search.scss @@ -0,0 +1,21 @@ +Screen { + layout: dock; + docks: top=top bottom=bottom; +} + +#file_table_wrapper { + dock: bottom; + height: auto; + overflow: auto auto; + scrollbar-color: $accent-darken-1; +} + +#file_table { + height: auto; +} + +#search_bar { + dock: bottom; + background: $accent; + height: 1; +} diff --git a/src/textual/widgets/text_input.py b/src/textual/widgets/text_input.py index caaab6674..da10fd5bc 100644 --- a/src/textual/widgets/text_input.py +++ b/src/textual/widgets/text_input.py @@ -115,21 +115,10 @@ class TextInput(TextWidgetBase, can_focus=True): CSS = """ TextInput { width: auto; - background: $primary; + background: $background; height: 3; padding: 0 1; content-align: left middle; - background: $primary-darken-1; - } - - TextInput:hover { - background: $primary-darken-2; - } - - TextInput:focus { - background: $primary-darken-2; - border: heavy $primary-lighten-1; - padding: 0; } """