from __future__ import annotations import re from typing import TYPE_CHECKING from ..items.Folder import Folder from ..core.Prompt import Prompt if TYPE_CHECKING: from .App import App BACKSPACE_KEYS = ("KEY_BACKSPACE", "\x7f", "\x08") ENTER_KEYS = ("\n", "KEY_ENTER") ESCAPE_KEY = "\x1b" class Input: def __init__(self, app: App) -> None: self._app = app self._actions = { "quit": self._quit, "down": self._down, "up": self._up, "enter": self._enter, "back": self._back, "parent": self._parent, "sort_direction": self._sort_direction, "sort_grouping": self._sort_grouping, "new_folder": self._new_folder, "delete_folder": self._delete_folder, "new_file": self._new_file, "search": self._search, "search_next": self._search_next, "search_previous": self._search_previous, } def handle(self, key: str) -> bool: if self.app.prompt is not None: return self._handle_prompt(key) action = self.app.config.action_for(key) handler = self._actions.get(action) return handler() if handler else True def _handle_prompt(self, key: str) -> bool: prompt = self.app.prompt if prompt.kind == "error": self.app.close_prompt() prompt.on_submit(None) return True if prompt.kind == "confirm": if key in ("y", "Y"): self.app.close_prompt() prompt.on_submit(True) elif key in ("n", "N", ESCAPE_KEY): self.app.close_prompt() return True if key in ENTER_KEYS: self.app.close_prompt() prompt.on_submit(prompt.buffer) elif key == ESCAPE_KEY: self.app.close_prompt() elif key in BACKSPACE_KEYS: prompt.buffer = prompt.buffer[:-1] elif len(key) == 1 and key.isprintable(): prompt.buffer += key return True def _quit(self) -> bool: return False def _down(self) -> bool: navigator = self.app.navigator navigator.cursor.down(len(navigator.items)) return True def _up(self) -> bool: self.app.navigator.cursor.up() return True def _maybe_show_error(self) -> None: error = self.app.navigator.error if error: self.app.open_prompt(Prompt.error(error)) def _enter(self) -> bool: navigator = self.app.navigator items = navigator.items if items: navigator.enter(items[navigator.cursor.pos]) self._maybe_show_error() return True def _back(self) -> bool: self.app.navigator.back() self._maybe_show_error() return True def _parent(self) -> bool: self.app.navigator.up() self._maybe_show_error() return True def _sort_direction(self) -> bool: self.app.navigator.cycle_sort_direction() return True def _sort_grouping(self) -> bool: self.app.navigator.cycle_sort_grouping() return True def _new_folder(self) -> bool: navigator = self.app.navigator def on_submit(name: str) -> None: if not name: return try: self.app.file_actions.create_folder(navigator.current.path, name) except OSError as e: self.app.open_prompt(Prompt.error(str(e))) self.app.open_prompt(Prompt("New folder name: ", "text", on_submit)) return True def _delete_folder(self) -> bool: navigator = self.app.navigator items = navigator.items if not items: return True item = items[navigator.cursor.pos] if not isinstance(item, Folder): return True def on_confirm(confirmed: bool) -> None: if not confirmed: return try: self.app.file_actions.delete_folder(item.path) except OSError as e: self.app.open_prompt(Prompt.error(str(e))) self.app.open_prompt( Prompt(f"Delete '{item.name}'? (y/n) ", "confirm", on_confirm)) return True def _new_file(self) -> bool: navigator = self.app.navigator def on_submit(name: str) -> None: if not name: return try: self.app.file_actions.create_file(navigator.current.path, name) except OSError as e: self.app.open_prompt(Prompt.error(str(e))) self.app.open_prompt(Prompt("New file name: ", "text", on_submit)) return True def _search(self) -> bool: navigator = self.app.navigator def on_submit(pattern: str) -> None: if not pattern: return try: regex = re.compile(pattern) except re.error as e: self.app.open_prompt(Prompt.error(f"Invalid regex: {e}")) return if not navigator.search(regex): self.app.open_prompt(Prompt.error(f"No match: {pattern}")) self.app.open_prompt(Prompt("Search: ", "text", on_submit)) return True def _search_next(self) -> bool: navigator = self.app.navigator if navigator.last_pattern is None: return True if not navigator.search_next(): self.app.open_prompt(Prompt.error(f"No match: {navigator.last_pattern}")) return True def _search_previous(self) -> bool: navigator = self.app.navigator if navigator.last_pattern is None: return True if not navigator.search_previous(): self.app.open_prompt(Prompt.error(f"No match: {navigator.last_pattern}")) return True @property def app(self) -> App: return self._app