Repository navigation
feat: wide character support (Windows input + widget layout) - #42
Merged
Merged
Conversation
KEY_EVENT_RECORD.UnicodeChar carries a UTF-16 code unit, so a non-BMP character (emoji) arrives as two consecutive KEY_EVENTs: a leading surrogate followed by a trailing one. The code-unit guard (unicode_char < 0xD800) rejected both halves and the character was silently dropped - emoji picker (Win+.) and pasting emoji produced no event, while BMP input worked. Buffer the leading surrogate and combine it with the trailing half (same approach as conhost's _leadingSurrogate in terminalInput.cpp). A stray leader is dropped when the next unit is not a trailer, and a lone trailer emits nothing. The pairing and state machine are plain functions (combineSurrogates, resolveSurrogate) covered by unit tests, so they also run on non-Windows CI. Fixes adxdits#40.
CJK and emoji occupy two terminal columns, but render advanced one column per code point. The next glyph was then written into the trailing cell of a wide character, and the buffer cleared the pair - wide characters never appeared in the field (the value itself was correct, only the rendering was wrong). Cursor column, scroll window and glyph placement now all count display columns via codepointWidth, matching the buffer's cell model. Covered by tests for wide-character layout, cursor-on-wide-char, column-based scrolling and placeholder clamping.
Tab titles advanced one column per code point, so a title containing CJK or emoji was written into the previous character's trailing cell and the cell buffer cleared the pair - wide titles rendered as blanks. Advance by codepointWidth and stop before a wide glyph that would straddle the right edge. Zero-width code points are skipped.
Same one-column-per-code-point bug as TextInput and Tabs: CJK and emoji occupy two columns, so text containing wide characters rendered as blanks or wrapped at the wrong column. Advance and wrap by codepointWidth. Previously this widget had no tests; add coverage for wide-character layout, wrapping at the edge, and newline/non-wrap handling.
This was referenced Sep 20, 2026
Owner
|
Thank you for the great work |
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.
Fixes #40.
Two related problems made wide characters (CJK, emoji) unusable on Windows, found while building a TUI on zigtui.
Input (
windows.zig):KEY_EVENT_RECORD.UnicodeCharis a UTF-16 code unit, so a non-BMP character arrives as two consecutive KEY_EVENTs (leading + trailing surrogate). Theunicode_char < 0xD800guard rejected both halves and the character was silently dropped. The leading surrogate is now buffered and combined with the trailing one, matching conhost's_leadingSurrogatebehaviour.combineSurrogatesandresolveSurrogateare plain functions with unit tests, so the state machine runs on non-Windows CI too.Layout (
text_input.zig,tabs.zig,paragraph.zig): all three widgets advanced one column per code point, but CJK and emoji occupy two terminal columns. The glyph after a wide character was written into its trailing cell and the cell buffer cleared the pair, so wide characters never appeared - the stored value was always correct, only the rendering was wrong. Each widget now counts display columns viacodepointWidth(glyph placement, wrap/edge decisions, and for TextInput the cursor column and scroll window). Tabs and Paragraph had the same pattern in their own layout loops; Paragraph had no tests at all before this.Verified on Windows Terminal / ConPTY: the emoji picker (Win+.) and pasting emoji now produce events, and CJK/emoji render correctly in all three widgets. 84 tests pass (was 72), plus
zig build examplesand a-target x86_64-linux-gnucompile check.before:
after: