diff --git a/CHANGELOG.md b/CHANGELOG.md index e0e387bb74..ab6ce20e8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## Unreleased + +### Added + +- Added `Content.blank` https://github.com/Textualize/textual/pull/6264 + ## [6.7.1] - 2025-12-1 ### Fixed diff --git a/src/textual/content.py b/src/textual/content.py index 968d36e521..7176e82377 100644 --- a/src/textual/content.py +++ b/src/textual/content.py @@ -179,6 +179,20 @@ def __init__( def __str__(self) -> str: return self._text + @property + def _is_regular(self) -> bool: + """Check if the line is regular (spans.start > span.end for all spans). + + This is a debugging aid, and unlikely to be useful in your app. + + Returns: + `True` if the content is regular, `False` if it is not (and broken). + """ + for span in self.spans: + if span.end <= span.start: + return False + return True + @cached_property def markup(self) -> str: """Get content markup to render this Text. @@ -374,6 +388,26 @@ def styled( ) return new_content + @classmethod + def blank(cls, width: int, style: Style | str | None = None) -> Content: + """Get a Content instance consisting of spaces. + + Args: + width: Width of blank content (number of spaces). + style: Style of blank. + + Returns: + Content instance. + """ + if not width: + return EMPTY_CONTENT + blank = cls( + " " * width, + [Span(0, width, style)] if style else None, + cell_length=width, + ) + return blank + @classmethod def assemble( cls, @@ -431,7 +465,10 @@ def assemble( position += len(part.plain) if end: text_append(end) - return cls("".join(text), spans, strip_control_codes=strip_control_codes) + assembled_content = cls( + "".join(text), spans, strip_control_codes=strip_control_codes + ) + return assembled_content def simplify(self) -> Content: """Simplify spans by joining contiguous spans together. @@ -786,7 +823,7 @@ def get_text_at(offset: int) -> "Content": if stop >= len(self.plain): return self text = self.plain[:stop] - return Content( + sliced_content = Content( text, self._trim_spans(text, self._spans), strip_control_codes=False, @@ -794,11 +831,14 @@ def get_text_at(offset: int) -> "Content": else: text = self.plain[start:stop] spans = [ - span._shift(-start) for span in self._spans if span.end > start + span._shift(-start) + for span in self._spans + if span.end - start > 0 ] - return Content( + sliced_content = Content( text, self._trim_spans(text, spans), strip_control_codes=False ) + return sliced_content else: # This would be a bit of work to implement efficiently diff --git a/tests/test_content.py b/tests/test_content.py index 8e60755f1e..d261a5f61f 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -570,3 +570,20 @@ def test_fold(content: Content, width: int, expected: list[Content]) -> None: assert len(result) == len(expected) for line, expected_line in zip(result, expected): assert line.is_same(expected_line) + + +@pytest.mark.parametrize( + "width,style,text,spans,cell_length", + [ + (5, None, " ", [], 5), + (0, None, "", [], 0), + (5, "on red", " ", [Span(0, 5, "on red")], 5), + ], +) +def test_blank_method( + width: int, style: str | None, text: str, spans: list[Span], cell_length: int +) -> None: + blank = Content.blank(width, style) + assert blank.plain == text + assert blank.spans == spans + assert blank.cell_length == cell_length