diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d992331a..d580acd5d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.56.1] - 2024-04-07 + +### Fixed + +- Fixed flicker when non-current screen updates https://github.com/Textualize/textual/pull/4401 + +### Changed + +- Removed additional line at the end of an inline app https://github.com/Textualize/textual/pull/4401 + ## [0.56.0] - 2024-04-06 ### Added @@ -1843,6 +1853,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040 - New handler system for messages that doesn't require inheritance - Improved traceback handling +[0.56.1]: https://github.com/Textualize/textual/compare/v0.56.0...v0.56.1 [0.56.0]: https://github.com/Textualize/textual/compare/v0.55.1...v0.56.0 [0.55.1]: https://github.com/Textualize/textual/compare/v0.55.0...v0.55.1 [0.55.0]: https://github.com/Textualize/textual/compare/v0.54.0...v0.55.0 diff --git a/pyproject.toml b/pyproject.toml index bf1363ff55..0feb663810 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "0.56.0" +version = "0.56.1" homepage = "https://github.com/Textualize/textual" repository = "https://github.com/Textualize/textual" documentation = "https://textual.textualize.io/" diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 420d643ca7..6c515625f7 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -148,8 +148,9 @@ def __rich_repr__(self) -> rich.repr.Result: class InlineUpdate(CompositorUpdate): """A renderable to write an inline update.""" - def __init__(self, strips: list[Strip]) -> None: + def __init__(self, strips: list[Strip], clear: bool = False) -> None: self.strips = strips + self.clear = clear def __rich_console__( self, console: Console, options: ConsoleOptions @@ -175,14 +176,15 @@ def render_segments(self, console: Console) -> str: append(strip.render(console)) if not last: append("\n") - append("\n\x1b[J") # Clear down + if self.clear: + append("\n\x1b[J") # Clear down if len(self.strips) > 1: - append( - f"\x1b[{len(self.strips)}A\r" - ) # Move cursor back to original position + back_lines = len(self.strips) if self.clear else len(self.strips) - 1 + append(f"\x1b[{back_lines}A\r") # Move cursor back to original position else: append("\r") append("\x1b[6n") # Query new cursor position + return "".join(sequences) @@ -336,6 +338,9 @@ def __init__(self) -> None: # Mapping of line numbers on to lists of widget and regions self._layers_visible: list[list[tuple[Widget, Region, Region]]] | None = None + # Size of previous inline update + self._previous_inline_height: int | None = None + @classmethod def _regions_to_spans( cls, regions: Iterable[Region] @@ -1030,7 +1035,13 @@ def render_inline( A renderable. """ visible_screen_stack.set([] if screen_stack is None else screen_stack) - return InlineUpdate(self.render_strips(size)) + strips = self.render_strips(size) + clear = ( + self._previous_inline_height is not None + and len(strips) < self._previous_inline_height + ) + self._previous_inline_height = len(strips) + return InlineUpdate(strips, clear=clear) def render_full_update(self) -> LayoutUpdate: """Render a full update. diff --git a/src/textual/command.py b/src/textual/command.py index c7c240fcaa..7cf5faf0ea 100644 --- a/src/textual/command.py +++ b/src/textual/command.py @@ -430,6 +430,10 @@ class CommandPalette(_SystemModalScreen[CallbackType]): """ DEFAULT_CSS = """ + CommandPalette:inline { + /* If the command palette is invoked in inline mode, we may need additional lines. */ + min-height: 20; + } CommandPalette { background: $background 30%; align-horizontal: center; diff --git a/src/textual/drivers/linux_inline_driver.py b/src/textual/drivers/linux_inline_driver.py index 7ea7dbb30a..01fc44b0df 100644 --- a/src/textual/drivers/linux_inline_driver.py +++ b/src/textual/drivers/linux_inline_driver.py @@ -207,6 +207,17 @@ def on_terminal_resize(signum, stack) -> None: self._key_thread = Thread(target=self._run_input_thread) send_size_event() self._key_thread.start() + self._request_terminal_sync_mode_support() + self._enable_bracketed_paste() + + def _request_terminal_sync_mode_support(self) -> None: + """Writes an escape sequence to query the terminal support for the sync protocol.""" + # Terminals should ignore this sequence if not supported. + # Apple terminal doesn't, and writes a single 'p' in to the terminal, + # so we will make a special case for Apple terminal (which doesn't support sync anyway). + if os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal": + self.write("\033[?2026$p") + self.flush() @classmethod def _patch_lflag(cls, attrs: int) -> int: diff --git a/src/textual/screen.py b/src/textual/screen.py index 341135bba7..4f42ee8f35 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -673,24 +673,25 @@ def _compositor_refresh(self) -> None: """Perform a compositor refresh.""" app = self.app - if app.is_inline: - app._display( - self, - self._compositor.render_inline( - app.size.with_height(app._get_inline_height()), - screen_stack=app._background_screens, - ), - ) - self._dirty_widgets.clear() - self._compositor._dirty_regions.clear() - elif self is app.screen: - # Top screen - update = self._compositor.render_update( - screen_stack=app._background_screens - ) - app._display(self, update) - self._dirty_widgets.clear() + if self is app.screen: + if app.is_inline: + app._display( + self, + self._compositor.render_inline( + app.size.with_height(app._get_inline_height()), + screen_stack=app._background_screens, + ), + ) + self._dirty_widgets.clear() + self._compositor._dirty_regions.clear() + else: + # Top screen + update = self._compositor.render_update( + screen_stack=app._background_screens + ) + app._display(self, update) + self._dirty_widgets.clear() elif self in self.app._background_screens and self._compositor._dirty_regions: # Background screen app.screen.refresh(*self._compositor._dirty_regions) @@ -892,7 +893,7 @@ def _get_inline_height(self, size: Size) -> int: inline_height = max(inline_height, int(min_height.resolve(size, size))) if max_height is not None: inline_height = min(inline_height, int(max_height.resolve(size, size))) - inline_height = min(self.app.size.height - 1, inline_height) + inline_height = min(self.app.size.height, inline_height) return inline_height def _screen_resized(self, size: Size):