From 00aba7c6465ae97ce8ca026f7cbdda7095753b7e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Dec 2020 17:35:15 +0530 Subject: [PATCH] Add basic editing support for the broadcast kitten --- kittens/broadcast/main.py | 34 ++++++++++++++++++++++------------ kittens/tui/line_edit.py | 25 +++++++++++++++++++++---- kittens/tui/operations.py | 11 +++++++++++ 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/kittens/broadcast/main.py b/kittens/broadcast/main.py index e9b7947f5..ccc670ae8 100644 --- a/kittens/broadcast/main.py +++ b/kittens/broadcast/main.py @@ -12,11 +12,12 @@ from kitty.cli_stub import BroadcastCLIOptions from kitty.key_encoding import RELEASE, encode_key_event, key_defs as K from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION from kitty.remote_control import create_basic_command, encode_send -from kitty.typing import KeyEventType +from kitty.typing import KeyEventType, ScreenSize from ..tui.handler import Handler +from ..tui.line_edit import LineEdit from ..tui.loop import Loop -from ..tui.operations import styled +from ..tui.operations import RESTORE_CURSOR, SAVE_CURSOR, styled class Broadcast(Handler): @@ -25,6 +26,7 @@ class Broadcast(Handler): self.opts = opts self.initial_strings = initial_strings self.payload = {'exclude_active': True, 'data': '', 'match': opts.match_tab, 'match_tab': opts.match_tab} + self.line_edit = LineEdit() if not opts.match and not opts.match_tab: self.payload['all'] = True @@ -32,10 +34,21 @@ class Broadcast(Handler): self.print('Type the text to broadcast below, press', styled('Ctrl+c', fg='yellow'), 'to quit:') for x in self.initial_strings: self.write_broadcast_text(x) + self.write(SAVE_CURSOR) + + def commit_line(self) -> None: + self.write(RESTORE_CURSOR + SAVE_CURSOR) + self.cmd.clear_to_end_of_screen() + self.line_edit.write(self.write, screen_cols=self.screen_size.cols) + + def on_resize(self, screen_size: ScreenSize) -> None: + super().on_resize(screen_size) + self.commit_line() def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: self.write_broadcast_text(text) - self.write(text) + self.line_edit.on_text(text, in_bracketed_paste) + self.commit_line() def on_interrupt(self) -> None: self.quit_loop(0) @@ -44,19 +57,16 @@ class Broadcast(Handler): self.write_broadcast_text('\x04') def on_key(self, key_event: KeyEventType) -> None: + if self.line_edit.on_key(key_event): + self.commit_line() if key_event.type is not RELEASE and not key_event.mods: - if key_event.key is K['TAB']: - self.write_broadcast_text('\t') - self.write('\t') - return - elif key_event.key is K['BACKSPACE']: - self.write_broadcast_text('\177') - self.write('\x08\x1b[X') - return - elif key_event.key is K['ENTER']: + if key_event.key is K['ENTER']: self.write_broadcast_text('\r') self.print('') + self.line_edit.clear() + self.write(SAVE_CURSOR) return + ek = encode_key_event(key_event) self.write_broadcast_data('kitty-key:' + ek) diff --git a/kittens/tui/line_edit.py b/kittens/tui/line_edit.py index 495d106b7..8f77a8ab1 100644 --- a/kittens/tui/line_edit.py +++ b/kittens/tui/line_edit.py @@ -7,6 +7,8 @@ from typing import Callable, Tuple from kitty.fast_data_types import truncate_point_for_length, wcswidth from kitty.key_encoding import RELEASE, KeyEvent, key_defs as K +from .operations import RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by + HOME = K['HOME'] END = K['END'] BACKSPACE = K['BACKSPACE'] @@ -31,13 +33,28 @@ class LineEdit: before, after = self.current_input[:x], self.current_input[x:] return before, after - def write(self, write: Callable[[str], None], prompt: str = '') -> None: + def write(self, write: Callable[[str], None], prompt: str = '', screen_cols: int = 0) -> None: if self.pending_bell: write('\a') self.pending_bell = False - write(prompt) - write(self.current_input) - write('\r\x1b[{}C'.format(self.cursor_pos + wcswidth(prompt))) + text = prompt + self.current_input + cursor_pos = self.cursor_pos + wcswidth(prompt) + if screen_cols: + write(SAVE_CURSOR + text + RESTORE_CURSOR) + used_lines, last_line_cursor_pos = divmod(cursor_pos, screen_cols) + if used_lines == 0: + if last_line_cursor_pos: + write(move_cursor_by(last_line_cursor_pos, 'right')) + else: + if used_lines: + write(move_cursor_by(used_lines, 'down')) + if last_line_cursor_pos: + write(move_cursor_by(last_line_cursor_pos, 'right')) + else: + write(text) + write('\r') + if cursor_pos: + write(move_cursor_by(cursor_pos, 'right')) def add_text(self, text: str) -> None: if self.current_input: diff --git a/kittens/tui/operations.py b/kittens/tui/operations.py index 8bb241ecf..4a9dab14e 100644 --- a/kittens/tui/operations.py +++ b/kittens/tui/operations.py @@ -68,6 +68,11 @@ def clear_screen() -> str: return '\033[H\033[2J' +@cmd +def clear_to_end_of_screen() -> str: + return '\033[J' + + @cmd def clear_to_eol() -> str: return '\033[K' @@ -108,6 +113,12 @@ def set_cursor_position(x: int, y: int) -> str: # (0, 0) is top left return '\033[{};{}H'.format(y + 1, x + 1) +@cmd +def move_cursor_by(amt: int, direction: str) -> str: + suffix = {'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D'}[direction] + return f'\033[{amt}{suffix}' + + @cmd def set_cursor_shape(shape: str = 'block', blink: bool = True) -> str: val = {'block': 1, 'underline': 3, 'bar': 5}.get(shape, 1)