Skip to content

Commit 3290999

Browse files
authored
Merge pull request #5829 from Textualize/overlapping-tags
Fix for overlapping tags
2 parents a4432fe + 93738b6 commit 3290999

File tree

2 files changed

+74
-5
lines changed

2 files changed

+74
-5
lines changed

src/textual/markup.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from __future__ import annotations
77

8+
from operator import itemgetter
9+
810
from textual.css.parse import substitute_references
911
from textual.css.tokenizer import UnexpectedEnd
1012

@@ -407,16 +409,25 @@ def process_text(template_text: str, /) -> str:
407409
if not style_stack:
408410
raise MarkupError("auto closing tag ('[/]') has nothing to close")
409411
open_position, tag_body, _ = style_stack.pop()
410-
spans.append(Span(open_position, position, tag_body))
412+
if open_position != position:
413+
spans.append(Span(open_position, position, tag_body))
411414

412415
content_text = "".join(text)
413416
text_length = len(content_text)
414-
for position, tag_body, _ in style_stack:
415-
spans.append(Span(position, text_length, tag_body))
417+
if style_stack and text_length:
418+
spans.extend(
419+
[
420+
Span(position, text_length, tag_body)
421+
for position, tag_body, _ in reversed(style_stack)
422+
if position != text_length
423+
]
424+
)
425+
spans.reverse()
426+
spans.sort(key=itemgetter(0)) # Zeroth item of Span is 'start' attribute
416427

417428
content = Content(
418429
content_text,
419-
[Span(0, len(content_text), style), *spans] if style else spans,
430+
[Span(0, text_length, style), *spans] if (style and text_length) else spans,
420431
)
421432

422433
return content

tests/test_markup.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535
Content(
3636
"What is up with you?",
3737
spans=[
38+
Span(0, 20, style="b"),
3839
Span(0, 10, style="on red"),
3940
Span(5, 20, style="i"),
40-
Span(0, 20, style="b"),
4141
],
4242
),
4343
),
@@ -84,6 +84,64 @@
8484
spans=[Span(0, 35, style="#ff0000"), Span(7, 35, style="#ffffff")],
8585
),
8686
),
87+
(
88+
"[blue][green][red]R[/red]G[/green]B[/blue]",
89+
Content(
90+
"RGB",
91+
spans=[
92+
Span(0, 3, "blue"),
93+
Span(0, 2, "green"),
94+
Span(0, 1, "red"),
95+
],
96+
),
97+
),
98+
(
99+
"[red][blue]X[/blue][/red]",
100+
Content(
101+
"X",
102+
spans=[
103+
Span(0, 1, "red"),
104+
Span(0, 1, "blue"),
105+
],
106+
),
107+
),
108+
# Non-nested tags
109+
(
110+
"[red][blue]X[/red][/blue]",
111+
Content(
112+
"X",
113+
spans=[
114+
Span(0, 1, "blue"),
115+
Span(0, 1, "red"),
116+
],
117+
),
118+
),
119+
(
120+
"[red][blue]X[/red]",
121+
Content(
122+
"X",
123+
spans=[
124+
Span(0, 1, "blue"),
125+
Span(0, 1, "red"),
126+
],
127+
),
128+
),
129+
(
130+
"[red][blue]X",
131+
Content(
132+
"X",
133+
spans=[
134+
Span(0, 1, "red"),
135+
Span(0, 1, "blue"),
136+
],
137+
),
138+
),
139+
# Edge cases
140+
("[bold][/bold]", Content("")),
141+
("[bold][/]", Content("")),
142+
("[bold]", Content("")),
143+
("", Content("")),
144+
("[red][green][/red]", Content("")),
87145
],
88146
)
89147
def test_to_content(markup: str, content: Content):

0 commit comments

Comments
 (0)