diff --git a/src/textual/markup.py b/src/textual/markup.py index 06a0effd98..c2a4571bde 100644 --- a/src/textual/markup.py +++ b/src/textual/markup.py @@ -5,6 +5,8 @@ from __future__ import annotations +from operator import itemgetter + from textual.css.parse import substitute_references from textual.css.tokenizer import UnexpectedEnd @@ -407,16 +409,25 @@ def process_text(template_text: str, /) -> str: if not style_stack: raise MarkupError("auto closing tag ('[/]') has nothing to close") open_position, tag_body, _ = style_stack.pop() - spans.append(Span(open_position, position, tag_body)) + if open_position != position: + spans.append(Span(open_position, position, tag_body)) content_text = "".join(text) text_length = len(content_text) - for position, tag_body, _ in style_stack: - spans.append(Span(position, text_length, tag_body)) + if style_stack and text_length: + spans.extend( + [ + Span(position, text_length, tag_body) + for position, tag_body, _ in reversed(style_stack) + if position != text_length + ] + ) + spans.reverse() + spans.sort(key=itemgetter(0)) # Zeroth item of Span is 'start' attribute content = Content( content_text, - [Span(0, len(content_text), style), *spans] if style else spans, + [Span(0, text_length, style), *spans] if (style and text_length) else spans, ) return content diff --git a/tests/test_markup.py b/tests/test_markup.py index 718c61752c..93e2a37b36 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -35,9 +35,9 @@ Content( "What is up with you?", spans=[ + Span(0, 20, style="b"), Span(0, 10, style="on red"), Span(5, 20, style="i"), - Span(0, 20, style="b"), ], ), ), @@ -84,6 +84,64 @@ spans=[Span(0, 35, style="#ff0000"), Span(7, 35, style="#ffffff")], ), ), + ( + "[blue][green][red]R[/red]G[/green]B[/blue]", + Content( + "RGB", + spans=[ + Span(0, 3, "blue"), + Span(0, 2, "green"), + Span(0, 1, "red"), + ], + ), + ), + ( + "[red][blue]X[/blue][/red]", + Content( + "X", + spans=[ + Span(0, 1, "red"), + Span(0, 1, "blue"), + ], + ), + ), + # Non-nested tags + ( + "[red][blue]X[/red][/blue]", + Content( + "X", + spans=[ + Span(0, 1, "blue"), + Span(0, 1, "red"), + ], + ), + ), + ( + "[red][blue]X[/red]", + Content( + "X", + spans=[ + Span(0, 1, "blue"), + Span(0, 1, "red"), + ], + ), + ), + ( + "[red][blue]X", + Content( + "X", + spans=[ + Span(0, 1, "red"), + Span(0, 1, "blue"), + ], + ), + ), + # Edge cases + ("[bold][/bold]", Content("")), + ("[bold][/]", Content("")), + ("[bold]", Content("")), + ("", Content("")), + ("[red][green][/red]", Content("")), ], ) def test_to_content(markup: str, content: Content):