mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #6138 from Textualize/long-placeholder
long placeholder
This commit is contained in:
@@ -24,11 +24,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Added `Screen.size` https://github.com/Textualize/textual/pull/6105
|
||||
- Added `compact` to Binding.Group https://github.com/Textualize/textual/pull/6132
|
||||
- Added `Screen.get_hover_widgets_at` https://github.com/Textualize/textual/pull/6132
|
||||
- Added `Content.wrap` https://github.com/Textualize/textual/pull/6138
|
||||
|
||||
### Fixed
|
||||
|
||||
- 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.placeholder not handling multi-lines https://github.com/Textualize/textual/pull/6138
|
||||
|
||||
## [6.1.0] - 2025-08-01
|
||||
|
||||
|
||||
@@ -869,6 +869,26 @@ class Content(Visual):
|
||||
|
||||
return Content("".join(text), spans, total_cell_length)
|
||||
|
||||
def wrap(
|
||||
self, width: int, *, align: TextAlign = "left", overflow: TextOverflow = "fold"
|
||||
) -> list[Content]:
|
||||
"""Wrap text so that it fits within the given dimensions.
|
||||
|
||||
Note that Textual will automatically wrap Content in widgets.
|
||||
This method is only required if you need some additional processing to lines.
|
||||
|
||||
Args:
|
||||
width: Maximum width of the line (in cells).
|
||||
align: Alignment of lines.
|
||||
overflow: Overflow of lines (what happens when the text doesn't fit).
|
||||
|
||||
Returns:
|
||||
A list of Content objects, one per line.
|
||||
"""
|
||||
lines = self._wrap_and_format(width, align, overflow)
|
||||
content_lines = [line.content for line in lines]
|
||||
return content_lines
|
||||
|
||||
def get_style_at_offset(self, offset: int) -> Style:
|
||||
"""Get the style of a character at give offset.
|
||||
|
||||
|
||||
@@ -1206,24 +1206,24 @@ TextArea {
|
||||
Returns:
|
||||
A rendered line.
|
||||
"""
|
||||
if y == 0 and not self.text and self.placeholder:
|
||||
style = self.get_visual_style("text-area--placeholder")
|
||||
content = (
|
||||
Content(self.placeholder)
|
||||
if isinstance(self.placeholder, str)
|
||||
else self.placeholder
|
||||
)
|
||||
content = content.stylize(style)
|
||||
if self._draw_cursor:
|
||||
theme = self._theme
|
||||
cursor_style = theme.cursor_style if theme else None
|
||||
if cursor_style:
|
||||
content = content.stylize(
|
||||
ContentStyle.from_rich_style(cursor_style), 0, 1
|
||||
)
|
||||
return Strip(
|
||||
content.render_segments(self.visual_style), content.cell_length
|
||||
|
||||
if not self.text and self.placeholder:
|
||||
placeholder_lines = Content.from_text(self.placeholder).wrap(
|
||||
self.content_size.width
|
||||
)
|
||||
if y < len(placeholder_lines):
|
||||
style = self.get_visual_style("text-area--placeholder")
|
||||
content = placeholder_lines[y].stylize(style)
|
||||
if self._draw_cursor and y == 0:
|
||||
theme = self._theme
|
||||
cursor_style = theme.cursor_style if theme else None
|
||||
if cursor_style:
|
||||
content = content.stylize(
|
||||
ContentStyle.from_rich_style(cursor_style), 0, 1
|
||||
)
|
||||
return Strip(
|
||||
content.render_segments(self.visual_style), content.cell_length
|
||||
)
|
||||
|
||||
scroll_x, scroll_y = self.scroll_offset
|
||||
absolute_y = scroll_y + y
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 28 KiB |
@@ -4626,3 +4626,31 @@ def test_header_format(snap_compare):
|
||||
yield Header()
|
||||
|
||||
assert snap_compare(HeaderApp())
|
||||
|
||||
|
||||
def test_long_textarea_placeholder(snap_compare) -> None:
|
||||
"""Test multi-line placeholders are wrapped and rendered.
|
||||
|
||||
You should see a TextArea at 50% width, with several lines of wrapped text.
|
||||
"""
|
||||
|
||||
TEXT = """I must not fear.
|
||||
Fear is the mind-killer.
|
||||
Fear is the little-death that brings total obliteration.
|
||||
I will face my fear.
|
||||
I will permit it to pass over me and through me.
|
||||
And when it has gone past, I will turn the inner eye to see its path.
|
||||
Where the fear has gone there will be nothing. Only I will remain."""
|
||||
|
||||
class PlaceholderApp(App):
|
||||
|
||||
CSS = """
|
||||
TextArea {
|
||||
width: 50%;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield TextArea(placeholder=TEXT)
|
||||
|
||||
assert snap_compare(PlaceholderApp())
|
||||
|
||||
@@ -349,3 +349,19 @@ def test_add_spans() -> None:
|
||||
Span(7, 9, style="blue"),
|
||||
]
|
||||
assert content.spans == expected
|
||||
|
||||
|
||||
def test_wrap() -> None:
|
||||
content = Content.from_markup("[green]Hello, [b]World, One two three[/b]")
|
||||
wrapped = content.wrap(6)
|
||||
print(wrapped)
|
||||
expected = [
|
||||
Content("Hello,", spans=[Span(0, 6, style="green")]),
|
||||
Content("World,", spans=[Span(0, 6, style="green"), Span(0, 6, style="b")]),
|
||||
Content("One", spans=[Span(0, 3, style="green"), Span(0, 3, style="b")]),
|
||||
Content("two", spans=[Span(0, 3, style="green"), Span(0, 3, style="b")]),
|
||||
Content("three", spans=[Span(0, 5, style="green"), Span(0, 5, style="b")]),
|
||||
]
|
||||
assert len(wrapped) == len(expected)
|
||||
for line1, line2 in zip(wrapped, expected):
|
||||
assert line1.is_same(line2)
|
||||
|
||||
Reference in New Issue
Block a user