Skip to content

feat: wide character support (Windows input + widget layout) - #42

Merged
adxdits merged 4 commits into
adxdits:masterfrom
NeonMedusa:wide-char-support
Sep 21, 2026
Merged

adxdits merged 4 commits into
adxdits:masterfrom
NeonMedusa:wide-char-support

Conversation

@NeonMedusa

Copy link
Copy Markdown
Contributor

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.UnicodeChar is a UTF-16 code unit, so a non-BMP character arrives as two consecutive KEY_EVENTs (leading + trailing surrogate). The unicode_char < 0xD800 guard rejected both halves and the character was silently dropped. The leading surrogate is now buffered and combined with the trailing one, matching conhost's _leadingSurrogate behaviour. combineSurrogates and resolveSurrogate are 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 via codepointWidth (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 examples and a -target x86_64-linux-gnu compile check.

before:

2994e1eaf53120cdd6439cbe57ee7ead

after:

07c18ffc312c372f50baadae908d86bf

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.
@adxdits
adxdits merged commit e1c7c4a into adxdits:master Sep 21, 2026
1 check passed
@adxdits

adxdits commented Sep 21, 2026

Copy link
Copy Markdown
Owner

Thank you for the great work

@NeonMedusa
NeonMedusa deleted the wide-char-support branch October 3, 2026 03:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Non-BMP characters (emoji, some symbols) typed or pasted on Windows produce no event. BMP input works fine.

2 participants