Skip to content

Commit d5dcc74

Browse files
authored
Merge pull request #3506 from Textualize/fix-split-cells
fix split cells
2 parents 4101991 + 23c64f1 commit d5dcc74

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Rich will display tracebacks with finely grained error locations on python 3.11+ https://github.com/Textualize/rich/pull/3486
1313

14+
15+
### Fixed
16+
17+
- Fixed issue with Segment._split_cells https://github.com/Textualize/rich/pull/3506
18+
1419
## [13.8.1] - 2024-09-10
1520

1621
### Fixed

rich/segment.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,29 @@ def is_control(self) -> bool:
109109
@classmethod
110110
@lru_cache(1024 * 16)
111111
def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]:
112+
"""Split a segment in to two at a given cell position.
113+
114+
Note that splitting a double-width character, may result in that character turning
115+
into two spaces.
116+
117+
Args:
118+
segment (Segment): A segment to split.
119+
cut (int): A cell position to cut on.
120+
121+
Returns:
122+
A tuple of two segments.
123+
"""
112124
text, style, control = segment
113125
_Segment = Segment
114-
115126
cell_length = segment.cell_length
116127
if cut >= cell_length:
117128
return segment, _Segment("", style, control)
118129

119130
cell_size = get_character_cell_size
120131

121-
pos = int((cut / cell_length) * (len(text) - 1))
132+
pos = int((cut / cell_length) * (len(text))) - 1
133+
if pos < 0:
134+
pos = 0
122135

123136
before = text[:pos]
124137
cell_pos = cell_len(before)

tests/test_segment.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44

5+
from rich.cells import cell_len
56
from rich.segment import ControlType, Segment, SegmentLines, Segments
67
from rich.style import Style
78

@@ -284,6 +285,34 @@ def test_split_cells_emoji(text, split, result):
284285
assert Segment(text).split_cells(split) == result
285286

286287

288+
def test_split_cells_mixed() -> None:
289+
"""Check that split cells splits on cell positions."""
290+
# Caused https://github.com/Textualize/textual/issues/4996 in Textual
291+
test = Segment("早乙女リリエル (CV: 徳井青)")
292+
for position in range(1, test.cell_length):
293+
left, right = Segment.split_cells(test, position)
294+
assert cell_len(left.text) == position
295+
assert cell_len(right.text) == test.cell_length - position
296+
297+
298+
def test_split_cells_doubles() -> None:
299+
"""Check that split cells splits on cell positions with all double width characters."""
300+
test = Segment("早" * 20)
301+
for position in range(1, test.cell_length):
302+
left, right = Segment.split_cells(test, position)
303+
assert cell_len(left.text) == position
304+
assert cell_len(right.text) == test.cell_length - position
305+
306+
307+
def test_split_cells_single() -> None:
308+
"""Check that split cells splits on cell positions with all single width characters."""
309+
test = Segment("A" * 20)
310+
for position in range(1, test.cell_length):
311+
left, right = Segment.split_cells(test, position)
312+
assert cell_len(left.text) == position
313+
assert cell_len(right.text) == test.cell_length - position
314+
315+
287316
def test_segment_lines_renderable():
288317
lines = [[Segment("hello"), Segment(" "), Segment("world")], [Segment("foo")]]
289318
segment_lines = SegmentLines(lines)

0 commit comments

Comments
 (0)