diff --git a/CHANGELOG.md b/CHANGELOG.md index 7032ca1ee4..fe3dddb20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed duplicated key displays in the help panel https://github.com/Textualize/textual/issues/5037 +### Added + +- Added support for in-band terminal resize protocol https://github.com/Textualize/textual/pull/5217 + +### Changed + +- `Driver.process_event` is now `Driver.process_message` https://github.com/Textualize/textual/pull/5217 + ## [0.85.2] - 2024-11-02 - Fixed broken focus-within https://github.com/Textualize/textual/pull/5190 diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py index 1405705b4e..0391cfcded 100644 --- a/src/textual/_xterm_parser.py +++ b/src/textual/_xterm_parser.py @@ -283,7 +283,7 @@ def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]: Keys """ - if (match := _re_extended_key.match(sequence)) is not None: + if (match := _re_extended_key.fullmatch(sequence)) is not None: number, modifiers, end = match.groups() number = number or 1 if not (key := FUNCTIONAL_KEYS.get(f"{number}{end}", "")): diff --git a/src/textual/driver.py b/src/textual/driver.py index 43880153fc..f2ecc4d621 100644 --- a/src/textual/driver.py +++ b/src/textual/driver.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Literal, TextIO -from textual import events, log +from textual import events, log, messages from textual.events import MouseUp if TYPE_CHECKING: @@ -74,36 +74,36 @@ def send_event(self, event: events.Event) -> None: self._app._post_message(event), loop=self._loop ) - def process_event(self, event: events.Event) -> None: - """Perform additional processing on an event, prior to sending. + def process_message(self, message: messages.Message) -> None: + """Perform additional processing on a message, prior to sending. Args: event: An event to send. """ # NOTE: This runs in a thread. # Avoid calling methods on the app. - event.set_sender(self._app) + message.set_sender(self._app) if self.cursor_origin is None: offset_x = 0 offset_y = 0 else: offset_x, offset_y = self.cursor_origin - if isinstance(event, events.MouseEvent): - event.x -= offset_x - event.y -= offset_y - event.screen_x -= offset_x - event.screen_y -= offset_y - - if isinstance(event, events.MouseDown): - if event.button: - self._down_buttons.append(event.button) - elif isinstance(event, events.MouseUp): - if event.button and event.button in self._down_buttons: - self._down_buttons.remove(event.button) - elif isinstance(event, events.MouseMove): + if isinstance(message, events.MouseEvent): + message.x -= offset_x + message.y -= offset_y + message.screen_x -= offset_x + message.screen_y -= offset_y + + if isinstance(message, events.MouseDown): + if message.button: + self._down_buttons.append(message.button) + elif isinstance(message, events.MouseUp): + if message.button and message.button in self._down_buttons: + self._down_buttons.remove(message.button) + elif isinstance(message, events.MouseMove): if ( self._down_buttons - and not event.button + and not message.button and self._last_move_event is not None ): # Deduplicate self._down_buttons while preserving order. @@ -118,17 +118,17 @@ def process_event(self, event: events.Event) -> None: delta_x=0, delta_y=0, button=button, - shift=event.shift, - meta=event.meta, - ctrl=event.ctrl, + shift=message.shift, + meta=message.meta, + ctrl=message.ctrl, screen_x=move_event.screen_x, screen_y=move_event.screen_y, - style=event.style, + style=message.style, ) ) - self._last_move_event = event + self._last_move_event = message - self.send_event(event) + self.send_event(message) @abstractmethod def write(self, data: str) -> None: diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py index 1131235b26..f1eabd8246 100644 --- a/src/textual/drivers/linux_driver.py +++ b/src/textual/drivers/linux_driver.py @@ -144,10 +144,10 @@ def _enable_in_band_window_resize(self) -> None: self.write("\x1b[?2048h") def _enable_line_wrap(self) -> None: - self.write("x1b[?7h") + self.write("\x1b[?7h") def _disable_line_wrap(self) -> None: - self.write("x1b[?7l") + self.write("\x1b[?7l") def _disable_in_band_window_resize(self) -> None: if self._in_band_window_resize: @@ -425,9 +425,9 @@ def process_selector_events( # This can occur if the stdin is piped break for event in feed(unicode_data): - self.process_event(event) + self.process_message(event) for event in tick(): - self.process_event(event) + self.process_message(event) try: while not self.exit_event.is_set(): @@ -443,12 +443,18 @@ def process_selector_events( except ParseError: pass - def process_event(self, event: events.Event) -> None: + def process_message(self, event: events.Event) -> None: if isinstance(event, TerminalSupportInBandWindowResize): if event.supported and not event.enabled: self._enable_in_band_window_resize() self._in_band_window_resize = event.supported elif event.enabled: self._in_band_window_resize = event.supported + super().process_message( + TerminalSupportInBandWindowResize( + event.supported, self._in_band_window_resize + ) + ) + return - super().process_event(event) + super().process_message(event) diff --git a/src/textual/drivers/linux_inline_driver.py b/src/textual/drivers/linux_inline_driver.py index 22bb349505..03e55d528f 100644 --- a/src/textual/drivers/linux_inline_driver.py +++ b/src/textual/drivers/linux_inline_driver.py @@ -155,12 +155,12 @@ def process_selector_events( if isinstance(event, events.CursorPosition): self.cursor_origin = (event.x, event.y) else: - self.process_event(event) + self.process_message(event) for event in tick(): if isinstance(event, events.CursorPosition): self.cursor_origin = (event.x, event.y) else: - self.process_event(event) + self.process_message(event) try: while not self.exit_event.is_set(): diff --git a/src/textual/drivers/web_driver.py b/src/textual/drivers/web_driver.py index 7e1e0ff0b4..2dd319f54d 100644 --- a/src/textual/drivers/web_driver.py +++ b/src/textual/drivers/web_driver.py @@ -195,12 +195,12 @@ def run_input_thread(self) -> None: if packet_type == "D": # Treat as stdin for event in parser.feed(decode(payload)): - self.process_event(event) + self.process_message(event) else: # Process meta information separately self._on_meta(packet_type, payload) for event in parser.tick(): - self.process_event(event) + self.process_message(event) except _ExitInput: pass except Exception: diff --git a/src/textual/drivers/windows_driver.py b/src/textual/drivers/windows_driver.py index 6c2f8ae392..3a908afbe7 100644 --- a/src/textual/drivers/windows_driver.py +++ b/src/textual/drivers/windows_driver.py @@ -101,7 +101,7 @@ def start_application_mode(self) -> None: self._enable_bracketed_paste() self._event_thread = win32.EventMonitor( - loop, self._app, self.exit_event, self.process_event + loop, self._app, self.exit_event, self.process_message ) self._event_thread.start() diff --git a/src/textual/messages.py b/src/textual/messages.py index 145e5e9522..1a9f951302 100644 --- a/src/textual/messages.py +++ b/src/textual/messages.py @@ -110,7 +110,7 @@ class TerminalSupportInBandWindowResize(Message): https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83""" def __init__(self, supported: bool, enabled: bool) -> None: - """_summary_ + """Initialize message. Args: supported: Is the protocol supported? diff --git a/tests/test_driver.py b/tests/test_driver.py index e3b5feba81..0c37f6c86f 100644 --- a/tests/test_driver.py +++ b/tests/test_driver.py @@ -18,8 +18,8 @@ def handle(self, event): app = MyApp() async with app.run_test() as pilot: - app._driver.process_event(MouseDown(0, 0, 0, 0, 1, False, False, False)) - app._driver.process_event(MouseUp(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseDown(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseUp(0, 0, 0, 0, 1, False, False, False)) await pilot.pause() assert len(app.messages) == 3 assert isinstance(app.messages[0], MouseDown) @@ -41,8 +41,8 @@ def on_button_pressed(self, event): app = MyApp() async with app.run_test() as pilot: - app._driver.process_event(MouseDown(0, 0, 0, 0, 1, False, False, False)) - app._driver.process_event(MouseUp(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseDown(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseUp(0, 0, 0, 0, 1, False, False, False)) await pilot.pause() assert len(app.messages) == 1 @@ -69,8 +69,8 @@ def on_button_pressed(self, event): assert (width, height) == (button_width, button_height) # Mouse down on the button, then move the mouse inside the button, then mouse up. - app._driver.process_event(MouseDown(0, 0, 0, 0, 1, False, False, False)) - app._driver.process_event( + app._driver.process_message(MouseDown(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message( MouseUp( button_width - 1, button_height - 1, @@ -108,8 +108,8 @@ def on_button_pressed(self, event): assert (width, height) == (button_width, button_height) # Mouse down on the button, then move the mouse outside the button, then mouse up. - app._driver.process_event(MouseDown(0, 0, 0, 0, 1, False, False, False)) - app._driver.process_event( + app._driver.process_message(MouseDown(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message( MouseUp( button_width + 1, button_height + 1,