diff --git a/CHANGELOG.md b/CHANGELOG.md index e02df616f8..bb8955001f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed issue where Segments with a style of `None` aren't rendered https://github.com/Textualize/textual/pull/6109 - Fixed visual glitches and crash when changing `DataTable.header_height` https://github.com/Textualize/textual/pull/6128 +- Fixed `TextArea` rendering with a multi-line `placeholder` https://github.com/Textualize/textual/issues/6122 ## [6.1.0] - 2025-08-01 diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 880efc2fb7..d851459a3c 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1176,6 +1176,15 @@ def _has_cursor(self) -> bool: """Is there a usable cursor?""" return not (self.read_only and not self.show_cursor) + @property + def _placeholder_lines(self) -> list[Content]: + content = ( + Content(self.placeholder) + if isinstance(self.placeholder, str) + else self.placeholder + ) + return content.split() + def get_line(self, line_index: int) -> Text: """Retrieve the line at the given line index. @@ -1206,15 +1215,11 @@ def render_line(self, y: int) -> Strip: Returns: A rendered line. """ - if y == 0 and not self.text and self.placeholder: + if not self.text and self.placeholder and y < len(self._placeholder_lines): style = self.get_visual_style("text-area--placeholder") - content = ( - Content(self.placeholder) - if isinstance(self.placeholder, str) - else self.placeholder - ) + content = self._placeholder_lines[y] content = content.stylize(style) - if self._draw_cursor: + if y == 0 and self._draw_cursor: theme = self._theme cursor_style = theme.cursor_style if theme else None if cursor_style: diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_textarea_multiline_placeholder.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_textarea_multiline_placeholder.svg new file mode 100644 index 0000000000..9e6d9f9169 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_textarea_multiline_placeholder.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaMultilinePlaceholder + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +Line 1 +Line 2 +Line 3 + + + + + + + + + + + + + + + + + + + +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 2b3eade06c..a61851d3ad 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -4626,3 +4626,16 @@ def compose(self) -> ComposeResult: yield Header() assert snap_compare(HeaderApp()) + + +def test_textarea_multiline_placeholder(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/6122 + + Test that a TextArea with a multi-line placeholder is rendered correctly. + """ + + class TextAreaMultilinePlaceholder(App): + def compose(self) -> ComposeResult: + yield TextArea(placeholder="Line 1\nLine 2\nLine 3") + + assert snap_compare(TextAreaMultilinePlaceholder())