Repository navigation
Fix _split_cells to handle non-unit width characters correctly - #4155
nisha-muthurajan wants to merge 3 commits into
Conversation
Fixes Textualize#3299 The previous implementation used a proportional heuristic to estimate the starting character position, which overshot for multi-cell characters like emoji. Replace with a linear scan that accumulates real cell widths.
| cell_size = get_character_cell_size | ||
| cell_pos = 0 | ||
|
|
||
| for pos, char in enumerate(text): |
There was a problem hiding this comment.
this drops the proportional-guess seek for a scan from index 0, so it is now O(pos) on every call where the old code usually converged in a step or two. on Segment("早"*400+"x") cut at 790 with the lru_cache cleared each time I get ~25x slower (0.074s vs 0.0029s for 2000 splits) — and since split_cells short-circuits single-width text, this path only ever sees the wide/mixed strings where that matters. also cell_size = get_character_cell_size is assigned twice, line 128 and 129.
There was a problem hiding this comment.
Good catch on both. Fixed the duplicate cell_size line. On the perf point — you're right that scanning from 0 unconditionally regresses the case you benchmarked. I've reworked it to keep a cheap initial guess (assuming ~1 cell per character) and do a bounded correction walk from that guess rather than from index 0, while still fixing the emoji overshoot bug from #3299.
Re-ran your exact benchmark (Segment("早"*400+"x"), cut at 790, 2000 iterations, cache cleared each time) locally: 0.0029s, matching the original baseline you cited. Also ran the full test suite (985 passed, 23 skipped) to confirm nothing else regressed.
Wkhanex
left a comment
There was a problem hiding this comment.
I exercised the new split algorithm at f11bf2f against combinations of single-cell ASCII, double-cell CJK, a zero-width combining mark, and newline. For every string of length 1 through 4 over that alphabet, I tried every legal cell cut and checked that the left width equals the cut, the right width equals the remaining width, and both styles retain their original identity. All 2,558 assertions passed, as did the 62 upstream segment tests on Linux/Python 3.12.14.
That adds coverage for the zero-width characters interacting with the guessed position and correction walk, beyond consecutive emoji. These are correctness checks only; I did not independently benchmark the performance claim in the discussion or run the complete suite.
AI-assisted review using OpenAI Codex; checks were executed locally.
Fixes #3299
The previous implementation used a proportional heuristic to estimate the starting character position, which overshot for multi-cell characters like emoji. Replace with a linear scan that accumulates real cell widths.
Type of changes
-✅ Bug fix
AI?
AI generated PRs may be accepted, but only if @willmcgugan has responded on an issue or discussion.
Checklist
Description
Fixes #3299
Segment._split_cellsused a proportional heuristic to guess the startingcharacter position:
This overshot for multi-cell characters (emoji, CJK) because it assumed all
characters have equal width. The fallback loop then couldn't recover correctly,
producing wrong splits like
('🦊🦊 ', ' abcdef')instead of('🦊 ', ' abcdef').Fixed by replacing the heuristic + loop with a simple linear scan that
accumulates real cell widths character by character, stopping precisely at
the cut point.
Added a regression test
test_split_cells_emojicovering the two examplesfrom the issue plus an exact-boundary case.