From 9b72dc9719cb369b03d0f453e2a4cc67da561a42 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sun, 8 Jun 2025 16:45:16 +0100 Subject: [PATCH 1/4] more lenient markup --- src/textual/markup.py | 23 ++++++++++++++++++----- tests/test_content.py | 12 +----------- tests/test_markup.py | 6 ++++-- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/textual/markup.py b/src/textual/markup.py index c2a4571bde..25a8b77ce8 100644 --- a/src/textual/markup.py +++ b/src/textual/markup.py @@ -48,8 +48,9 @@ class MarkupError(Exception): variable_ref=VARIABLE_REF, whitespace=r"\s+", ) - .expect_eof(False) + .expect_eof(True) .expect_semicolon(False) + .extract_text(True) ) expect_markup = Expect( @@ -374,14 +375,26 @@ def process_text(template_text: str, /) -> str: elif token_name == "open_tag": tag_text = [] + eof = False + contains_text = False for token in iter_tokens: if token.name == "end_tag": break + elif token.name == "text": + contains_text = True + elif token.name == "eof": + eof = True tag_text.append(token.value) - opening_tag = "".join(tag_text).strip() - style_stack.append( - (position, opening_tag, normalize_markup_tag(opening_tag)) - ) + if contains_text or eof: + # "tag" was unparsable + text_content = f"[{''.join(tag_text)}" + ("" if eof else "]") + text_append(text_content) + position += len(text_content) + else: + opening_tag = "".join(tag_text).strip() + style_stack.append( + (position, opening_tag, normalize_markup_tag(opening_tag)) + ) elif token_name == "open_closing_tag": tag_text = [] diff --git a/tests/test_content.py b/tests/test_content.py index 9de62d21e4..a5c459f3f6 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -229,6 +229,7 @@ def test_assemble(): ("\\[/foo", "[/foo"), ("\\[/foo]", "[/foo]"), ("\\[]", "[]"), + ("\\[0]", "[0]"), ], ) def test_escape(markup: str, plain: str) -> None: @@ -273,14 +274,3 @@ def test_first_line(): first_line = content.first_line assert first_line.plain == "foo" assert first_line.spans == [Span(0, 3, "red")] - - -def test_errors(): - with pytest.raises(Exception): - Content.from_markup("[") - - with pytest.raises(Exception): - Content.from_markup("[:") - - with pytest.raises(Exception): - Content.from_markup("[foo") diff --git a/tests/test_markup.py b/tests/test_markup.py index 93e2a37b36..0f8d687a26 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -10,6 +10,10 @@ ["markup", "content"], [ ("", Content("")), + ("[", Content("[")), + ("[0", Content("[0")), + ("[0]", Content("[0]")), + ("[red", Content("[red")), ("foo", Content("foo")), ("foo\n", Content("foo\n")), ("foo\nbar", Content("foo\nbar")), @@ -152,8 +156,6 @@ def test_to_content(markup: str, content: Content): def test_content_parse_fail() -> None: - with pytest.raises(MarkupError): - to_content("[rgb(1,2,3,4)]foo") with pytest.raises(MarkupError): to_content("[foo]foo[/bar]") with pytest.raises(MarkupError): From 5ee3c6a4a988eec1834143e73cca083451916e20 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sun, 8 Jun 2025 16:53:54 +0100 Subject: [PATCH 2/4] More lenient markup --- CHANGELOG.md | 4 ++++ src/textual/markup.py | 18 ++++++++++++++---- tests/test_markup.py | 4 ++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 190c9923fb..700122603d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added experimental opt-in support for https://github.com/willmcgugan/textual-speedups +### Changed + +- Content markup is now more lenient; if a 'tag' doesn't contain a valid style it will be included verbatim. + ## [3.3.0] - 2025-06-01 ### Fixed diff --git a/src/textual/markup.py b/src/textual/markup.py index 25a8b77ce8..60abb9f5cb 100644 --- a/src/textual/markup.py +++ b/src/textual/markup.py @@ -391,10 +391,20 @@ def process_text(template_text: str, /) -> str: text_append(text_content) position += len(text_content) else: - opening_tag = "".join(tag_text).strip() - style_stack.append( - (position, opening_tag, normalize_markup_tag(opening_tag)) - ) + opening_tag = "".join(tag_text) + + if not opening_tag.strip(): + blank_tag = f"[{opening_tag}]" + text_append(blank_tag) + position += len(blank_tag) + else: + style_stack.append( + ( + position, + opening_tag, + normalize_markup_tag(opening_tag.strip()), + ) + ) elif token_name == "open_closing_tag": tag_text = [] diff --git a/tests/test_markup.py b/tests/test_markup.py index 0f8d687a26..ff8c3edb70 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -11,6 +11,10 @@ [ ("", Content("")), ("[", Content("[")), + ("[]", Content("[]")), + ("[ ", Content("[ ")), + ("[ ", Content("[ ")), + ("[ ]", Content("[ ]")), ("[0", Content("[0")), ("[0]", Content("[0]")), ("[red", Content("[red")), From ffd644972acf46f2ada215878eef562f5db804a1 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sun, 8 Jun 2025 16:54:36 +0100 Subject: [PATCH 3/4] Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 700122603d..5c7debf71d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Content markup is now more lenient; if a 'tag' doesn't contain a valid style it will be included verbatim. +- Content markup is now more lenient; if a 'tag' doesn't contain a valid style it will be included verbatim. https://github.com/Textualize/textual/pull/5851 ## [3.3.0] - 2025-06-01 From 2f8f5b9dbb695b489ab14c78f70b9f9f57139969 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sun, 8 Jun 2025 20:30:15 +0100 Subject: [PATCH 4/4] another test --- tests/test_markup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_markup.py b/tests/test_markup.py index ff8c3edb70..346b0a903c 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -18,6 +18,7 @@ ("[0", Content("[0")), ("[0]", Content("[0]")), ("[red", Content("[red")), + ("[red]", Content("")), ("foo", Content("foo")), ("foo\n", Content("foo\n")), ("foo\nbar", Content("foo\nbar")),