From 9b9d58f7166c2f58e2116c66300aa2e4bb14ed46 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 31 Oct 2022 11:03:34 +0000 Subject: [PATCH 1/5] fix issue with maxlines and textlog --- sandbox/will/tl.py | 20 +++ src/textual/widgets/_text_log.py | 9 +- .../__snapshots__/test_snapshots.ambr | 155 ++++++++++++++++++ tests/snapshot_tests/test_snapshots.py | 10 +- tests/snapshots/textlog_max_lines.py | 20 +++ 5 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 sandbox/will/tl.py create mode 100644 tests/snapshots/textlog_max_lines.py diff --git a/sandbox/will/tl.py b/sandbox/will/tl.py new file mode 100644 index 000000000..b11cc10b6 --- /dev/null +++ b/sandbox/will/tl.py @@ -0,0 +1,20 @@ +from textual.app import App +from textual.widgets import TextLog + + +class TextLogLines(App): + count = 0 + + def compose(self): + yield TextLog(max_lines=5) + + async def on_key(self): + self.count += 1 + log_widget = self.query_one(TextLog) + log_widget.write(f"Key press #{self.count}") + + +app = TextLogLines() + +if __name__ == "__main__": + app.run() diff --git a/src/textual/widgets/_text_log.py b/src/textual/widgets/_text_log.py index 05f1f0d5e..a383182f1 100644 --- a/src/textual/widgets/_text_log.py +++ b/src/textual/widgets/_text_log.py @@ -46,6 +46,7 @@ class TextLog(ScrollView, can_focus=True): ) -> None: super().__init__(name=name, id=id, classes=classes) self.max_lines = max_lines + self.start_line: int = 0 self.lines: list[list[Segment]] = [] self._line_cache: LRUCache[tuple[int, int, int, int], list[Segment]] self._line_cache = LRUCache(1024) @@ -95,7 +96,9 @@ class TextLog(ScrollView, can_focus=True): ) self.lines.extend(lines) - if self.max_lines is not None: + if self.max_lines is not None and len(self.lines) > self.max_lines: + self.start_line += len(self.lines) - self.max_lines + self.refresh() self.lines = self.lines[-self.max_lines :] self.virtual_size = Size(self.max_width, len(self.lines)) self.scroll_end(animate=False, speed=100) @@ -103,8 +106,10 @@ class TextLog(ScrollView, can_focus=True): def clear(self) -> None: """Clear the text log.""" del self.lines[:] + self.start_line = 0 self.max_width = 0 self.virtual_size = Size(self.max_width, len(self.lines)) + self.refresh() def render_line(self, y: int) -> list[Segment]: scroll_x, scroll_y = self.scroll_offset @@ -129,7 +134,7 @@ class TextLog(ScrollView, can_focus=True): if y >= len(self.lines): return [Segment(" " * width, self.rich_style)] - key = (y, scroll_x, width, self.max_width) + key = (y + self.start_line, scroll_x, width, self.max_width) if key in self._line_cache: return self._line_cache[key] diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index e01117b7d..7739c6bc9 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -6009,6 +6009,161 @@ ''' # --- +# name: test_textlog_max_lines + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextLogLines + + + + + + + + + + Key press #3                                                                   + Key press #4                                                                   + Key press #5                                                                   + + + + + + + + + + + + + + + + + + + + + + + + + + ''' +# --- # name: test_vertical_layout ''' diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 2f04c9314..8f969a206 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -8,6 +8,7 @@ from textual.widgets import Input, Button # --- Layout related stuff --- + def test_grid_layout_basic(snap_compare): assert snap_compare("docs/examples/guide/layout/grid_layout1.py") @@ -41,6 +42,7 @@ def test_dock_layout_sidebar(snap_compare): # When adding a new widget, ideally we should also create a snapshot test # from these examples which test rendering and simple interactions with it. + def test_checkboxes(snap_compare): """Tests checkboxes but also acts a regression test for using width: auto in a Horizontal layout context.""" @@ -94,12 +96,18 @@ def test_header_render(snap_compare): assert snap_compare("docs/examples/widgets/header.py") +def test_textlog_max_lines(snap_compare): + assert snap_compare("tests/snapshots/textlog_max_lines.py", press=list("abcde")) + + # --- CSS properties --- # We have a canonical example for each CSS property that is shown in their docs. # If any of these change, something has likely broken, so snapshot each of them. PATHS = [ - str(PurePosixPath(path)) for path in Path("docs/examples/styles").iterdir() if path.suffix == ".py" + str(PurePosixPath(path)) + for path in Path("docs/examples/styles").iterdir() + if path.suffix == ".py" ] diff --git a/tests/snapshots/textlog_max_lines.py b/tests/snapshots/textlog_max_lines.py new file mode 100644 index 000000000..3f31b3005 --- /dev/null +++ b/tests/snapshots/textlog_max_lines.py @@ -0,0 +1,20 @@ +from textual.app import App +from textual.widgets import TextLog + + +class TextLogLines(App): + count = 0 + + def compose(self): + yield TextLog(max_lines=3) + + async def on_key(self): + self.count += 1 + log_widget = self.query_one(TextLog) + log_widget.write(f"Key press #{self.count}") + + +app = TextLogLines() + +if __name__ == "__main__": + app.run() From ff21a5168921fccb4bc719883b4b6d6a3e1896f1 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 31 Oct 2022 11:05:31 +0000 Subject: [PATCH 2/5] rename var --- src/textual/widgets/_text_log.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/textual/widgets/_text_log.py b/src/textual/widgets/_text_log.py index a383182f1..7041a1dfe 100644 --- a/src/textual/widgets/_text_log.py +++ b/src/textual/widgets/_text_log.py @@ -46,7 +46,7 @@ class TextLog(ScrollView, can_focus=True): ) -> None: super().__init__(name=name, id=id, classes=classes) self.max_lines = max_lines - self.start_line: int = 0 + self._start_line: int = 0 self.lines: list[list[Segment]] = [] self._line_cache: LRUCache[tuple[int, int, int, int], list[Segment]] self._line_cache = LRUCache(1024) @@ -97,7 +97,7 @@ class TextLog(ScrollView, can_focus=True): self.lines.extend(lines) if self.max_lines is not None and len(self.lines) > self.max_lines: - self.start_line += len(self.lines) - self.max_lines + self._start_line += len(self.lines) - self.max_lines self.refresh() self.lines = self.lines[-self.max_lines :] self.virtual_size = Size(self.max_width, len(self.lines)) @@ -106,7 +106,7 @@ class TextLog(ScrollView, can_focus=True): def clear(self) -> None: """Clear the text log.""" del self.lines[:] - self.start_line = 0 + self._start_line = 0 self.max_width = 0 self.virtual_size = Size(self.max_width, len(self.lines)) self.refresh() @@ -134,7 +134,7 @@ class TextLog(ScrollView, can_focus=True): if y >= len(self.lines): return [Segment(" " * width, self.rich_style)] - key = (y + self.start_line, scroll_x, width, self.max_width) + key = (y + self._start_line, scroll_x, width, self.max_width) if key in self._line_cache: return self._line_cache[key] From 8f38275256b2c28f7e976d24a1fb7f41133758b0 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 31 Oct 2022 13:51:48 +0000 Subject: [PATCH 3/5] Update the ChangeLog with some recent changes/fixes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69b41ae86..ead6bc414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fixed issue where scrollbars weren't being unmounted +- Fixed how the app title in a `Header` is centred https://github.com/Textualize/textual/issues/1060 +- Fixed the swapping of button variants https://github.com/Textualize/textual/issues/1048 +- Fixed reserved characters in screenshots https://github.com/Textualize/textual/issues/993 ### Changed - DOMQuery now raises InvalidQueryFormat in response to invalid query strings, rather than cryptic CSS error - Dropped quit_after, screenshot, and screenshot_title from App.run, which can all be done via auto_pilot - Widgets are now closed in reversed DOM order +- Changed `textual run` so that it patches `argv` in more situations ### Added From d37c5ed3da71b8ce32140d5cc5094eb99a44155d Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 31 Oct 2022 13:53:28 +0000 Subject: [PATCH 4/5] test fix for windows --- tests/snapshot_tests/test_snapshots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 87cfe801a..e60112c8a 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -86,7 +86,7 @@ def test_header_render(snap_compare): def test_textlog_max_lines(snap_compare): - assert snap_compare("tests/snapshots/textlog_max_lines.py", press=list("abcde")) + assert snap_compare("tests/snapshots/textlog_max_lines.py", press=[*"abcde", "_"]) def test_fr_units(snap_compare): From b274894dbccc3da44e024072be9d43adbb21a614 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 31 Oct 2022 14:24:20 +0000 Subject: [PATCH 5/5] remove sandbox --- sandbox/will/fr.py | 96 ---------------------------------------------- sandbox/will/tl.py | 20 ---------- 2 files changed, 116 deletions(-) delete mode 100644 sandbox/will/fr.py delete mode 100644 sandbox/will/tl.py diff --git a/sandbox/will/fr.py b/sandbox/will/fr.py deleted file mode 100644 index e82b895db..000000000 --- a/sandbox/will/fr.py +++ /dev/null @@ -1,96 +0,0 @@ -from textual.app import App, ComposeResult -from textual.containers import Horizontal, Vertical -from textual.widgets import Static - - -class StaticText(Static): - pass - - -class Header(Static): - pass - - -class Footer(Static): - pass - - -class FrApp(App): - - CSS = """ - Screen { - layout: horizontal; - align: center middle; - - } - - Vertical { - - } - - Header { - background: $boost; - - content-align: center middle; - text-align: center; - color: $text; - height: 3; - border: tall $warning; - } - - Horizontal { - height: 1fr; - align: center middle; - } - - Footer { - background: $boost; - - content-align: center middle; - text-align: center; - - color: $text; - height: 6; - border: tall $warning; - } - - StaticText { - background: $boost; - height: 8; - content-align: center middle; - text-align: center; - color: $text; - } - - #foo { - width: 10; - border: tall $primary; - } - - #bar { - width: 1fr; - border: tall $error; - - } - - #baz { - width: 20; - border: tall $success; - } - - """ - - def compose(self) -> ComposeResult: - yield Vertical( - Header("HEADER"), - Horizontal( - StaticText("foo", id="foo"), - StaticText("bar", id="bar"), - StaticText("baz", id="baz"), - ), - Footer("FOOTER"), - ) - - -app = FrApp() -app.run() diff --git a/sandbox/will/tl.py b/sandbox/will/tl.py deleted file mode 100644 index b11cc10b6..000000000 --- a/sandbox/will/tl.py +++ /dev/null @@ -1,20 +0,0 @@ -from textual.app import App -from textual.widgets import TextLog - - -class TextLogLines(App): - count = 0 - - def compose(self): - yield TextLog(max_lines=5) - - async def on_key(self): - self.count += 1 - log_widget = self.query_one(TextLog) - log_widget.write(f"Key press #{self.count}") - - -app = TextLogLines() - -if __name__ == "__main__": - app.run()