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()