From: Christian Heller Date: Wed, 4 Jun 2025 00:01:17 +0000 (+0200) Subject: Refactor TUI commands into their respective widgets. X-Git-Url: https://plomlompom.com/repos/%22https:/validator.w3.org/process?a=commitdiff_plain;h=b53608489642a22aea562a1ced63f28f8a8d21fc;p=ircplom Refactor TUI commands into their respective widgets. --- diff --git a/ircplom.py b/ircplom.py index 1a50b5c..e527214 100755 --- a/ircplom.py +++ b/ircplom.py @@ -24,12 +24,12 @@ TIMEOUT_LOOP = 0.1 INPUT_PROMPT = '> ' KEYBINDINGS = { - 'KEY_BACKSPACE': ('prompt_backspace',), + 'KEY_BACKSPACE': ('window.prompt.backspace',), 'KEY_ENTER': ('prompt_enter',), - 'KEY_UP': ('prompt_scroll', 'up'), - 'KEY_DOWN': ('prompt_scroll', 'down'), - 'KEY_PGUP': ('log_scroll', 'up'), - 'KEY_PGDOWN': ('log_scroll', 'down'), + 'KEY_UP': ('window.prompt.scroll', 'up'), + 'KEY_DOWN': ('window.prompt.scroll', 'down'), + 'KEY_PGUP': ('window.log.scroll', 'up'), + 'KEY_PGDOWN': ('window.log.scroll', 'down'), '[91, 49, 59, 51, 68]': ('window', 'left'), '[91, 49, 59, 51, 67]': ('window', 'right'), } @@ -399,9 +399,9 @@ class ScrollableWidget(Widget): def _scroll(self, up=True) -> None: pass - def scroll(self, up=True) -> None: + def cmd__scroll(self, direction: str) -> None: 'Scroll through stored content/history.' - self._scroll(up) + self._scroll(up=direction == 'up') self.draw() @@ -445,7 +445,7 @@ class PromptWidget(ScrollableWidget): self._input_buffer = self._history[self._history_idx][:] self.draw() - def backspace(self) -> None: + def cmd__backspace(self) -> None: 'Truncate current content by one character, if possible.' self._input_buffer = self._input_buffer[:-1] self._history_idx = 0 @@ -565,6 +565,21 @@ class TuiLoop(Loop): self._term.flush() super().__init__(*args, **kwargs) + def _cmd_name_to_cmd(self, cmd_name: str) -> Optional[Callable]: + cmd_parent = self + while True: + cmd_name_toks = cmd_name.split('.', maxsplit=1) + if len(cmd_name_toks) == 1: + break + if not hasattr(cmd_parent, cmd_name_toks[0]): + return None + cmd_parent = getattr(cmd_parent, cmd_name_toks[0]) + cmd_name = cmd_name_toks[1] + cmd_name = f'cmd__{cmd_name}' + if not hasattr(cmd_parent, cmd_name): + return None + return getattr(cmd_parent, cmd_name) + def process_main(self, event: Event) -> bool: if not super().process_main(event): return False @@ -574,26 +589,29 @@ class TuiLoop(Loop): self._windows[1].log.append(f'<- {event.payload.raw}') elif event.type_ == 'SEND': self._windows[1].log.append(f'-> {event.payload.raw}') - self._window.log.draw() + self.window.log.draw() elif event.type_ == 'KEYBINDING': - getattr(self, f'_cmd__{event.payload[0]}')(*event.payload[1:]) + cmd = self._cmd_name_to_cmd(event.payload[0]) + assert cmd is not None + cmd(*event.payload[1:]) elif event.type_ == 'INPUT_CHAR': - self._window.prompt.append(event.payload) + self.window.prompt.append(event.payload) elif event.type_ == 'SIGWINCH': self._calc_and_draw_all() # elif event.type_ == 'DEBUG': # from traceback import format_exception # for line in '\n'.join(format_exception(event.payload) # ).split('\n'): - # self._window.log.append(f'DEBUG {line}') - # self._window.log.draw() + # self.window.log.append(f'DEBUG {line}') + # self.window.log.draw() else: return True self._term.flush() return True @property - def _window(self) -> Window: + def window(self) -> Window: + 'Currently selected Window.' return self._windows[self._window_idx] def _calc_and_draw_all(self) -> None: @@ -605,30 +623,31 @@ class TuiLoop(Loop): self._term.write_yx(YX(y_prompt, 0), INPUT_PROMPT) for window in self._windows: window.set_geometry((y_separator, y_prompt)) - self._window.draw() + self.window.draw() - def _cmd__prompt_backspace(self) -> None: - self._window.prompt.backspace() + def cmd__disconnect(self, quit_msg: str = 'ircplom says bye') -> None: + 'Send QUIT command to server.' + self.broadcast('SEND', IrcMessage('QUIT', [quit_msg])) - def _cmd__prompt_enter(self) -> None: - to_parse = self._window.prompt.enter() + def cmd__prompt_enter(self) -> None: + 'Get prompt content from .window.prompt.enter, parse to & run command.' + to_parse = self.window.prompt.enter() if not to_parse: return alert: Optional[str] = None if to_parse[0:1] == '/': toks = to_parse[1:].split(maxsplit=1) - method_name = f'_cmd__{toks[0]}' alert = f'{toks[0]} unknown' - if hasattr(self, method_name)\ - and method_name != stack()[0].function: - method = getattr(self, method_name) - params = signature(method).parameters + cmd_name = toks[0] + cmd = self._cmd_name_to_cmd(cmd_name) + if cmd and cmd.__name__ != stack()[0].function: + params = signature(cmd).parameters n_args_max = len(params) n_args_min = len([p for p in params.values() if p.default == inspect_empty]) alert = f'{toks[0]} needs {n_args_min} - {n_args_max} args' if len(toks) == 1 and not n_args_min: - alert = method() + alert = cmd() elif len(toks) > 1 and params\ and n_args_min <= len(toks[1].split()): args = [] @@ -636,25 +655,18 @@ class TuiLoop(Loop): toks = toks[1].split(maxsplit=1) args += [toks[0]] n_args_max -= 1 - alert = method(*args) + alert = cmd(*args) else: alert = 'not prefixed by /' if alert: self.broadcast('ALERT', f'invalid prompt command: {alert}') - def _cmd__prompt_scroll(self, direction: str) -> None: - self._window.prompt.scroll(up=direction == 'up') - - def _cmd__log_scroll(self, direction: str) -> None: - self._window.log.scroll(up=direction == 'up') - - def _cmd__disconnect(self, quit_msg: str = 'ircplom says bye') -> None: - self.broadcast('SEND', IrcMessage('QUIT', [quit_msg])) - - def _cmd__quit(self) -> None: + def cmd__quit(self) -> None: + 'Send QUIT to all threads.' self.broadcast('QUIT') - def _cmd__window(self, towards: str) -> Optional[str]: + def cmd__window(self, towards: str) -> Optional[str]: + 'Switch window selection.' n_windows = len(self._windows) if n_windows < 2: return 'no alternate window to move into' @@ -670,7 +682,7 @@ class TuiLoop(Loop): if not 0 <= window_idx < n_windows: return f'unavailable window idx: {window_idx}' self._window_idx = window_idx - self._window.draw() + self.window.draw() return None