diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py index ff499783d9..1405705b4e 100644 --- a/src/textual/_xterm_parser.py +++ b/src/textual/_xterm_parser.py @@ -216,9 +216,12 @@ def send_escape() -> None: bracketed_paste = False break if match := _re_in_band_window_resize.fullmatch(sequence): - height, width, _pixel_height, _pixel_width = match.groups() + height, width, pixel_height, pixel_width = [ + group.partition(":")[0] for group in match.groups() + ] resize_event = events.Resize.from_dimensions( - int(width), int(height) + (int(width), int(height)), + (int(pixel_width), int(pixel_height)), ) on_token(resize_event) break diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py index 3fefdf4fd6..1131235b26 100644 --- a/src/textual/drivers/linux_driver.py +++ b/src/textual/drivers/linux_driver.py @@ -143,6 +143,12 @@ def _query_in_band_window_resize(self) -> None: def _enable_in_band_window_resize(self) -> None: self.write("\x1b[?2048h") + def _enable_line_wrap(self) -> None: + self.write("x1b[?7h") + + def _disable_line_wrap(self) -> None: + self.write("x1b[?7l") + def _disable_in_band_window_resize(self) -> None: if self._in_band_window_resize: self.write("\x1b[?2048l") @@ -269,6 +275,7 @@ def on_terminal_resize(signum, stack) -> None: self._request_terminal_sync_mode_support() self._query_in_band_window_resize() self._enable_bracketed_paste() + self._disable_line_wrap() # Appears to fix an issue enabling mouse support in iTerm 3.5.0 self._enable_mouse_support() @@ -345,6 +352,7 @@ def disable_input(self) -> None: def stop_application_mode(self) -> None: """Stop application mode, restore state.""" self._disable_bracketed_paste() + self._enable_line_wrap() self._disable_in_band_window_resize() self.disable_input() diff --git a/src/textual/events.py b/src/textual/events.py index 51128ccfe0..e8fa1fa9e8 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -115,6 +115,7 @@ def __init__( size: Size, virtual_size: Size, container_size: Size | None = None, + pixel_size: Size | None = None, ) -> None: self.size = size """The new size of the Widget.""" @@ -122,20 +123,33 @@ def __init__( """The virtual size (scrollable size) of the Widget.""" self.container_size = size if container_size is None else container_size """The size of the Widget's container widget.""" + self.pixel_size = pixel_size + """Size of terminal window in pixels if known, or `None` if not known.""" super().__init__() @classmethod - def from_dimensions(cls, width: int, height: int) -> Resize: - size = Size(width, height) - return Resize(size, size, size) + def from_dimensions( + cls, cells: tuple[int, int], pixels: tuple[int, int] | None + ) -> Resize: + """Construct from basic dimensions. + + Args: + cells: tuple of (, ) in cells. + pixels: tuple of (, ) in pixels if known, or `None` if not known. + + """ + size = Size(*cells) + pixel_size = Size(*pixels) if pixels is not None else None + return Resize(size, size, size, pixel_size) def can_replace(self, message: "Message") -> bool: return isinstance(message, Resize) def __rich_repr__(self) -> rich.repr.Result: yield "size", self.size - yield "virtual_size", self.virtual_size + yield "virtual_size", self.virtual_size, self.size yield "container_size", self.container_size, self.size + yield "pixel_size", self.pixel_size, None class Compose(Event, bubble=False, verbose=True):