From cf6e91017a5484d0efa465b0cc7f176ffcfaaf46 Mon Sep 17 00:00:00 2001 From: JustAnyone Date: Fri, 21 Aug 2026 17:08:29 +0300 Subject: [PATCH] feat: add support for jumping to specific item This also cleans up some type checker issues --- random_file_picker/tui.py | 66 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/random_file_picker/tui.py b/random_file_picker/tui.py index 9e5c186..c4819a8 100644 --- a/random_file_picker/tui.py +++ b/random_file_picker/tui.py @@ -9,13 +9,15 @@ Here's an example usage: import pathlib import random -from typing import cast, override +from typing import ClassVar, cast, final, override import typed_argparse as tap from textual import work from textual.app import App, ComposeResult from textual.binding import BindingType -from textual.widgets import Footer, Header, Label +from textual.containers import Grid +from textual.screen import ModalScreen +from textual.widgets import Footer, Header, Input, Label from textual.worker import Worker, WorkerState from random_file_picker.core import IMG_EXTENSIONS, collect_files, move_file_to_move_folder, trash_file, upload_file @@ -38,12 +40,50 @@ class Args(tap.TypedArgs): blacklist: list[str] | None = tap.arg("--blacklist", nargs="*", help="List of glob patterns to blacklist files") whitelist: list[str] | None = tap.arg("--whitelist", nargs="*", help="List of glob patterns to whitelist files") +@final +class JumpModal(ModalScreen[int | None]): + """Modal screen to prompt the user for an item index to skip to.""" + + # css in the terminal before gta6 + DEFAULT_CSS = """ + JumpModal { + align: center middle; + } + #dialog { + grid-size: 1; + padding: 1 2; + width: 40; + height: 11; + border: thick $background 80%; + background: $surface; + } + """ + + @override + def compose(self) -> ComposeResult: + yield Grid( + Label("Enter item index to jump to:"), + Input(placeholder="e.g. 5", id="index-input", type="integer"), + id="dialog", + ) + + def on_input_submitted(self, event: Input.Submitted) -> None: + """Handle the submission of the input field, dismissing the modal with the entered index.""" + val = event.value.strip() + if val.isdigit(): + _ = self.dismiss(int(val)) + return + _ = self.dismiss(None) + + class MenuApp(App[None]): # This defines the keys shown in the footer - BINDINGS: list[BindingType] = [ + BINDINGS: ClassVar[list[BindingType]] = [ ("left", "prev_file", "Previous File"), ("right", "next_file", "Next File"), ("r", "reopen_file", "Reopen"), + ("colon", "jump_to_item", "Jump to Item"), + ("g", "jump_to_item", "Jump to Item"), ("u", "upload", "Upload"), ("d", "delete", "Delete"), ("m", "move_to_folder", "Move File"), @@ -53,6 +93,7 @@ class MenuApp(App[None]): def __init__( self, + *, directory: str = ".", file_limit: int = 10, recurse: bool = False, @@ -60,6 +101,7 @@ class MenuApp(App[None]): whitelist: list[str] | None = None, use_geeqie: bool = False, use_trash: bool = True, + no_random: bool = False, ): super().__init__() self.directory: pathlib.Path = pathlib.Path(directory) @@ -69,6 +111,7 @@ class MenuApp(App[None]): self.whitelist_patterns: list[str] = whitelist or [] self.use_geeqie: bool = use_geeqie self.use_trash: bool = use_trash + self.no_random: bool = no_random self.usable_files: list[pathlib.Path] = [] self.current_index: int = 0 self.current_file: pathlib.Path | None = None @@ -82,6 +125,7 @@ class MenuApp(App[None]): yield Footer() def on_mount(self) -> None: + """On application mount, start gathering files in the background.""" self.start_gathering_files() @override @@ -307,6 +351,22 @@ class MenuApp(App[None]): self.open_file() self.query_one("#status", Label).update("🔄 Reopened file") + def action_jump_to_item(self) -> None: + """Prompt for item number and jump to it.""" + if not self.usable_files: + return + + def check_jump(target_1based: int | None) -> None: + if target_1based is None: + return + if 1 <= target_1based <= len(self.usable_files): + self.current_index = target_1based - 1 + self.display_current_file() + else: + self.query_one("#status", Label).update(f"❌ Index out of range (1 - {len(self.usable_files)})") + + _ = self.push_screen(JumpModal(), check_jump) + def main_with_tap(args: Args) -> None: if args.use_geeqie and not GEEQIE_AVAILABLE: