From 6bf92166a4a11dadfdab2439503bfa4c226a2c85 Mon Sep 17 00:00:00 2001 From: ShiLiguo123 <2669380883@qq.com> Date: Tue, 12 May 2026 20:54:51 +0800 Subject: [PATCH] Fix Segment._split_cells for non-unit-width characters The old algorithm used a heuristic initial position estimate int((cut / cell_length) * len(text)) and then adjusted in a while loop. This fails for strings with mixed single/double cell-width characters (emoji, CJK). Replace with a simple character-by-character walk-through that correctly tracks cumulative cell positions. Fixes #3299 --- rich/segment.py | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/rich/segment.py b/rich/segment.py index dd1e8c2a4f..68a91a036b 100644 --- a/rich/segment.py +++ b/rich/segment.py @@ -126,32 +126,25 @@ def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment cell_size = get_character_cell_size - pos = int((cut / cell_length) * len(text)) - - while True: - before = text[:pos] - cell_pos = cell_len(before) - out_by = cell_pos - cut - if not out_by: - return ( - _Segment(before, style, control), - _Segment(text[pos:], style, control), - ) - if out_by == -1 and cell_size(text[pos]) == 2: + # Walk through characters tracking cell position, + # avoids the heuristic + while-loop which fails on + # non-unit-width characters (emoji, CJK, etc.) + cell_pos = 0 + for i, char in enumerate(text): + char_cells = cell_size(char) + if cell_pos + char_cells > cut: + if char_cells == 2: + return ( + _Segment(text[:i] + " ", style, control), + _Segment(" " + text[i + 1 :], style, control), + ) return ( - _Segment(text[:pos] + " ", style, control), - _Segment(" " + text[pos + 1 :], style, control), + _Segment(text[:i], style, control), + _Segment(text[i:], style, control), ) - if out_by == +1 and cell_size(text[pos - 1]) == 2: - return ( - _Segment(text[: pos - 1] + " ", style, control), - _Segment(" " + text[pos:], style, control), - ) - if cell_pos < cut: - pos += 1 - else: - pos -= 1 + cell_pos += char_cells + return segment, _Segment("", style, control) def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]: """Split segment in to two segments at the specified column.