Treat VS16 emoji-presentation sequences as double width#32
Open
Junyi-99 wants to merge 1 commit into
Open
Conversation
A text-presentation base codepoint followed by U+FE0F (variation selector-16) requests emoji presentation, which terminals and unicode-width's string-level width() render as two columns. The cell's wide flag was computed from the base char alone (width 1) and the VS16 was appended as a zero-width combining char without re-checking, so the sequence was stored as a single narrow cell, shifting every column after it by one. In Screen::text's combining-character branch, when appending the combiner grows the cell's contents to double width by UnicodeWidthStr measure while the cell is still flagged narrow, promote it to wide and insert a wide-continuation cell. If that continuation column already held the first half of an existing wide glyph, clear that glyph's continuation too, mirroring the width > 1 placement path so nothing is left orphaned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes a width-handling bug where a base character followed by U+FE0F (VS16) was stored as a single narrow cell, causing subsequent text to shift and leave on-screen residue. The fix detects when a combining character promotes a base to double-width emoji presentation and widens the cell, adding a wide continuation while cleaning up any glyph it clobbers.
Changes:
- In
screen.rs, after appending a zero-width char to the base cell, detect when the resulting string width exceeds 1 and the cell is still narrow; widen it, add a wide-continuation cell, and clear any orphaned continuation from a pre-existing wide glyph. - Expose
Cell::set_wideaspub(crate)soScreencan promote a cell to wide. - Add regression tests in
tests/emoji_width.rscovering basic VS16 promotion and promotion over an existing wide character.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/screen.rs | Implements VS16 width promotion logic when a combining char widens the base cell. |
| src/cell.rs | Makes set_wide crate-visible so screen.rs can flag the promoted cell as wide. |
| tests/emoji_width.rs | Adds regression tests for VS16 emoji width handling, including the orphaned-continuation case. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+789
to
+790
| let cont_col = base_col + 1; | ||
| let promote = cont_col < size.cols && { |
Comment on lines
+57
to
+61
| assert_eq!(c[1].2, true, "col1 = ❤️ continuation"); | ||
| assert!( | ||
| !c[2].2, | ||
| "col2 (clobbered 中's old continuation) must be cleared, not orphaned" | ||
| ); |
Comment on lines
+842
to
+844
| if pos.col == cont_col { | ||
| self.grid_mut().col_inc(1); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A text-presentation base codepoint followed by
U+FE0F(variation selector-16) requests emoji presentation, which modern terminals andunicode-width's string-levelwidth()render as two columns — e.g.❤️(U+2764 U+FE0F),⚠️(U+26A0 U+FE0F).vt100sets a cell's wide flag from the basecharalone (c.width() > 1inCell::set) and appends the VS16 as a zero-width combining char without re-checking, so the sequence is stored as a single narrow cell. Every column after the emoji is then shifted by one, which shows up as on-screen residue when the line is redrawn or scrolled.Repro:
Fix
In
Screen::text's combining-character (width == 0) branch: after appending the combiner, if the cell's full contents now measure two columns byUnicodeWidthStr::widthwhile the cell is still flagged narrow, promote it to wide and insert a wide-continuation cell. If that continuation column already held the first half of an existing wide glyph, clear that glyph's continuation too — mirroring the existingwidth > 1placement path so no continuation is left orphaned.Keying on the string-level width keeps the parser in agreement with
unicode-width(and the terminals driving it).Tests
tests/emoji_width.rscovers the core promotion and the overlapping-wide-glyph (orphan-continuation) case. Existing suite stays green.🤖 Generated with Claude Code