Skip to content

Commit 1442dd7

Browse files
authored
Merge pull request #1756 from willmcgugan/overlap-markup
fix for overlapping markup
2 parents c57e1f5 + a4c9fdb commit 1442dd7

File tree

4 files changed

+76
-28
lines changed

4 files changed

+76
-28
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [10.16.1] - 2021-12-15
9+
10+
### Fixed
11+
12+
- Fixed issues with overlapping tags https://github.com/willmcgugan/rich/issues/1755
13+
814
## [10.16.0] - 2021-12-12
915

1016
### Fixed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "rich"
33
homepage = "https://github.com/willmcgugan/rich"
44
documentation = "https://rich.readthedocs.io/en/latest/"
5-
version = "10.16.0"
5+
version = "10.16.1"
66
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
77
authors = ["Will McGugan <[email protected]>"]
88
license = "MIT"

rich/text.py

+40-26
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
22
from functools import partial, reduce
33
from math import gcd
4-
from operator import attrgetter, itemgetter
4+
from operator import itemgetter
55
from rich.emoji import EmojiVariant
66
from typing import (
77
TYPE_CHECKING,
@@ -1033,6 +1033,7 @@ def divide(self, offsets: Iterable[int]) -> Lines:
10331033
Lines: New RichText instances between offsets.
10341034
"""
10351035
_offsets = list(offsets)
1036+
10361037
if not _offsets:
10371038
return Lines([self.copy()])
10381039

@@ -1056,33 +1057,46 @@ def divide(self, offsets: Iterable[int]) -> Lines:
10561057
)
10571058
if not self._spans:
10581059
return new_lines
1059-
order = {span: span_index for span_index, span in enumerate(self._spans)}
1060-
span_stack = sorted(self._spans, key=attrgetter("start"), reverse=True)
10611060

1062-
pop = span_stack.pop
1063-
push = span_stack.append
1061+
_line_appends = [line._spans.append for line in new_lines._lines]
1062+
line_count = len(line_ranges)
10641063
_Span = Span
1065-
get_order = order.__getitem__
1066-
1067-
for line, (start, end) in zip(new_lines, line_ranges):
1068-
if not span_stack:
1069-
break
1070-
append_span = line._spans.append
1071-
position = len(span_stack) - 1
1072-
while span_stack[position].start < end:
1073-
span = pop(position)
1074-
add_span, remaining_span = span.split(end)
1075-
if remaining_span:
1076-
push(remaining_span)
1077-
order[remaining_span] = order[span]
1078-
span_start, span_end, span_style = add_span
1079-
line_span = _Span(span_start - start, span_end - start, span_style)
1080-
order[line_span] = order[span]
1081-
append_span(line_span)
1082-
position -= 1
1083-
if position < 0 or not span_stack:
1084-
break # pragma: no cover
1085-
line._spans.sort(key=get_order)
1064+
1065+
for span_start, span_end, style in self._spans:
1066+
1067+
lower_bound = 0
1068+
upper_bound = line_count
1069+
start_line_no = (lower_bound + upper_bound) // 2
1070+
1071+
while True:
1072+
line_start, line_end = line_ranges[start_line_no]
1073+
if span_start < line_start:
1074+
upper_bound = start_line_no - 1
1075+
elif span_start > line_end:
1076+
lower_bound = start_line_no + 1
1077+
else:
1078+
break
1079+
start_line_no = (lower_bound + upper_bound) // 2
1080+
1081+
end_line_no = lower_bound = start_line_no
1082+
upper_bound = line_count
1083+
1084+
while True:
1085+
line_start, line_end = line_ranges[end_line_no]
1086+
if span_end < line_start:
1087+
upper_bound = end_line_no - 1
1088+
elif span_end > line_end:
1089+
lower_bound = end_line_no + 1
1090+
else:
1091+
break
1092+
end_line_no = (lower_bound + upper_bound) // 2
1093+
1094+
for line_no in range(start_line_no, end_line_no + 1):
1095+
line_start, line_end = line_ranges[line_no]
1096+
new_start = max(0, span_start - line_start)
1097+
new_end = min(span_end - line_start, line_end - line_start)
1098+
if new_end > new_start:
1099+
_line_appends[line_no](_Span(new_start, new_end, style))
10861100

10871101
return new_lines
10881102

tests/test_segment.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import sys
1+
from io import StringIO
22

33
import pytest
44

@@ -166,6 +166,34 @@ def test_divide():
166166
]
167167

168168

169+
# https://github.com/willmcgugan/rich/issues/1755
170+
def test_divide_complex():
171+
MAP = (
172+
"[on orange4] [on green]XX[on orange4] \n"
173+
" \n"
174+
" \n"
175+
" \n"
176+
" [bright_red on black]Y[on orange4] \n"
177+
"[on green]X[on orange4] [on green]X[on orange4] \n"
178+
" [on green]X[on orange4] [on green]X\n"
179+
"[on orange4] \n"
180+
" [on green]XX[on orange4] \n"
181+
)
182+
from rich.text import Text
183+
from rich.console import Console
184+
185+
text = Text.from_markup(MAP)
186+
console = Console(
187+
color_system="truecolor", width=30, force_terminal=True, file=StringIO()
188+
)
189+
console.print(text)
190+
result = console.file.getvalue()
191+
192+
print(repr(result))
193+
expected = "\x1b[48;5;94m \x1b[0m\x1b[42mXX\x1b[0m\x1b[48;5;94m \x1b[0m\n\x1b[48;5;94m \x1b[0m\n\x1b[48;5;94m \x1b[0m\n\x1b[48;5;94m \x1b[0m\n\x1b[48;5;94m \x1b[0m\x1b[91;40mY\x1b[0m\x1b[91;48;5;94m \x1b[0m\n\x1b[91;42mX\x1b[0m\x1b[91;48;5;94m \x1b[0m\x1b[91;42mX\x1b[0m\x1b[91;48;5;94m \x1b[0m\n\x1b[91;48;5;94m \x1b[0m\x1b[91;42mX\x1b[0m\x1b[91;48;5;94m \x1b[0m\x1b[91;42mX\x1b[0m\n\x1b[91;48;5;94m \x1b[0m\n\x1b[91;48;5;94m \x1b[0m\x1b[91;42mXX\x1b[0m\x1b[91;48;5;94m \x1b[0m\n\n"
194+
assert result == expected
195+
196+
169197
def test_divide_emoji():
170198
bold = Style(bold=True)
171199
italic = Style(italic=True)

0 commit comments

Comments
 (0)