From 951e44055145c3c516c21611bb25f30d0c8273a8 Mon Sep 17 00:00:00 2001 From: TomJGooding <101601846+TomJGooding@users.noreply.github.com> Date: Thu, 18 Sep 2025 14:55:38 +0100 Subject: [PATCH] fix(text area): fix rendering with a multi-line placeholder Fixes #6122 --- CHANGELOG.md | 1 + src/textual/widgets/_text_area.py | 19 ++- .../test_textarea_multiline_placeholder.svg | 153 ++++++++++++++++++ tests/snapshot_tests/test_snapshots.py | 13 ++ 4 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 tests/snapshot_tests/__snapshots__/test_snapshots/test_textarea_multiline_placeholder.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f269007d..d3c0c0f0fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fixed issue where Segments with a style of `None` aren't rendered https://github.com/Textualize/textual/pull/6109 +- 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())