From a8e84800ef6824cecef35133bbe68961471aec05 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 12 Aug 2025 13:27:22 +0100 Subject: [PATCH] fix for expand tabs --- src/textual/content.py | 12 +++++++----- src/textual/style.py | 2 +- tests/test_content.py | 44 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/textual/content.py b/src/textual/content.py index 490ec2011..6c4d24ca1 100644 --- a/src/textual/content.py +++ b/src/textual/content.py @@ -334,10 +334,7 @@ class Content(Visual): """ if not text: return Content("") - span_length = cell_len(text) if cell_length is None else cell_length - new_content = cls( - text, [Span(0, span_length, style)] if style else None, span_length - ) + new_content = cls(text, [Span(0, len(text), style)] if style else None) return new_content @classmethod @@ -844,6 +841,8 @@ class Content(Visual): total_cell_length: int | None = self._cell_length for content in iter_content(): + if not content: + continue extend_text(content._text) extend_spans( _Span(offset + start, offset + end, style) @@ -1437,6 +1436,9 @@ class Content(Visual): if "\t" not in self.plain: return self + if not self._spans: + return Content(self.plain.expandtabs(tab_size)) + new_text: list[Content] = [] append = new_text.append @@ -1449,7 +1451,7 @@ class Content(Visual): for part in parts: if part.plain.endswith("\t"): part = Content( - part._text[-1][:-1] + " ", part._spans, part._cell_length + part._text[:-1] + " ", part._spans, part._cell_length ) cell_position += part.cell_length tab_remainder = cell_position % tab_size diff --git a/src/textual/style.py b/src/textual/style.py index 43c4be092..1a96b8102 100644 --- a/src/textual/style.py +++ b/src/textual/style.py @@ -41,7 +41,7 @@ _get_hash_attributes = attrgetter( ) -@rich.repr.auto(angular=True) +@rich.repr.auto() @dataclass(frozen=True) class Style: """Represents a style in the Visual interface (color and other attributes). diff --git a/tests/test_content.py b/tests/test_content.py index c5034054c..1ca13423b 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -294,3 +294,47 @@ def test_simplify(): assert content.spans == [Span(0, 3, "bold"), Span(3, 6, "bold")] content.simplify() assert content.spans == [Span(0, 6, "bold")] + + +@pytest.mark.parametrize( + ["input", "tab_width", "expected"], + [ + (Content(""), 8, Content("")), + (Content("H"), 8, Content("H")), + (Content("Hello"), 8, Content("Hello")), + (Content("\t"), 8, Content(" " * 8)), + (Content("A\t"), 8, Content("A" + " " * 7)), + (Content("ABCD\t"), 8, Content("ABCD" + " " * 4)), + (Content("ABCDEFG\t"), 8, Content("ABCDEFG ")), + (Content("ABCDEFGH\t"), 8, Content("ABCDEFGH" + " " * 8)), + (Content("Hel\tlo!"), 4, Content("Hel lo!")), + (Content("\t\t"), 4, Content(" " * 8)), + (Content("FO\t\t"), 4, Content("FO ")), + (Content("FO\tOB\t"), 4, Content("FO OB ")), + ( + Content("FOO", spans=[Span(0, 3, "red")]), + 4, + Content("FOO", spans=[Span(0, 3, "red")]), + ), + ( + Content("FOO\tBAR", spans=[Span(0, 3, "red")]), + 8, + Content("FOO BAR", spans=[Span(0, 3, "red")]), + ), + ( + Content("FOO\tBAR", spans=[Span(0, 3, "red"), Span(4, 8, "blue")]), + 8, + Content("FOO BAR", spans=[Span(0, 3, "red"), Span(8, 11, "blue")]), + ), + ( + Content("foo\tbar\nbaz", spans=[Span(0, 11, "red")]), + 8, + Content("foo bar\nbaz", spans=[Span(0, 15, "red")]), + ), + ], +) +def test_expand_tabs(input: Content, tab_width: int, expected: Content): + output = input.expand_tabs(tab_width).simplify() + print(repr(output)) + assert output.plain == expected.plain + assert output._spans == expected._spans