Skip to content

Terminal: count East Asian Wide characters as two columns - #2894

Open
insjang wants to merge 3 commits into
eclipse-platform:masterfrom
insjang:eaw-width
Open

insjang wants to merge 3 commits into
eclipse-platform:masterfrom
insjang:eaw-width

Conversation

@insjang

@insjang insjang commented Sep 2, 2026

Copy link
Copy Markdown

The terminal emulator assumes every character occupies one cell. Hangul, Han and Kana text, fullwidth forms and emoji are East Asian Wide and occupy two, so every such character put the cursor one column short and the screen fell apart as soon as a program laid text out for a real terminal — line editors, curses UIs, Ink-based CLIs.

This adds CharWidth, a UAX #11 East Asian Width lookup (Wide/Fullwidth = 2, combining marks and controls = 0, Ambiguous = 1 as UAX #11 recommends outside an East Asian legacy context), and makes the emulator and renderer follow it:

  • the cursor advances by width; the second cell of a wide character holds a NUL filler
  • a wide character is never split across the right margin
  • overwriting either half of a wide character blanks the other half
  • insert mode counts cells, not characters
  • the renderer skips fillers so a fixed-width font draws the glyph over both cells, falls back to placing each character at its own cell when the font does not advance exactly one cell per column, and draws characters beyond the BMP whole
  • a partial repaint starting on the second cell of a wide character is widened to its first
  • copying drops the fillers

Tests: CharWidthTest (width table) and four cases in VT100EmulatorBackendTest (placement, margin, overwriting halves, insert mode). All existing terminal tests pass.

Verified on Windows 11 with a Korean shell session, vim, htop and Claude Code inside the Eclipse Terminal; compared against Windows Terminal for column alignment.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Combining characters are discarded, and supplementary or edited wide characters can corrupt storage, rendering, cursor positioning, and copied text.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 4 Medium severity · 1 Low severity

Open (5)
What changed in this PR

Adds East Asian wide-character support to the terminal’s model, emulator, rendering, copying, and repaint behavior.

Changes:

  • Introduces Unicode width classification and filler cells.
  • Updates emulator positioning, wrapping, insertion, rendering, and copying.
  • Adds width and emulator tests plus the required bundle version increment.
File Description
CharWidth.java Defines Unicode display widths and filler detection.
VT100EmulatorBackend.java Adds cell-aware writing and wide-character handling.
TextLineRenderer.java Renders wide and supplementary characters.
TextCanvas.java Expands repaint ranges across wide glyphs.
AbstractTextCanvasModel.java Removes fillers from copied text.
CharWidthTest.java Tests width classification and fillers.
AllTestSuite.java Registers the new width tests.
VT100EmulatorBackendTest.java Tests placement, wrapping, overwriting, and insertion.
META-INF/​MANIFEST.MF Increments the bundle service version.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    54 files  ± 0      54 suites  ±0   59m 16s ⏱️ + 2m 59s
 4 853 tests + 9   4 831 ✅ + 9   22 💤 ±0  0 ❌ ±0 
12 450 runs  +27  12 296 ✅ +27  154 💤 ±0  0 ❌ ±0 

Results for commit c8ba950. ± Comparison against base commit aa5a77c.

♻️ This comment has been updated with latest results.

@insjang

insjang commented Sep 28, 2026

Copy link
Copy Markdown
Author

Rebased on current master and addressed the Copilot review in 5bb7086.

The GitHub Actions failures on the previous head were not from this change: the build stopped in org.eclipse.core.filesystem with "Baseline and reactor have the same fully qualified version, but different content", because the old base still had 1.11.500 while master has moved to 1.12.0. The rebase should clear it.

@akurtakov thanks for triggering the review.

The emulator assumed every character occupies one cell, so Hangul, Han
and Kana text, fullwidth forms and emoji were placed one column short
per character and the screen fell apart as soon as a program laid text
out for a real terminal (line editors, curses UIs, Ink based CLIs).

Add CharWidth, a UAX eclipse-platform#11 East Asian Width lookup: Wide and Fullwidth
count as two columns, combining marks and controls as zero, Ambiguous as
one, as UAX eclipse-platform#11 recommends outside an East Asian legacy context.

The emulator advances the cursor by that width and stores a NUL filler
in the second cell of a wide character, never splits one across the
right margin, blanks the other half when either half is overwritten and
counts insert mode in cells. The renderer skips the fillers so a fixed
width font draws a wide glyph over both cells, falls back to placing
each character at its own cell when the font does not advance exactly
one cell per column, and draws a character beyond the BMP whole. A
partial repaint that starts on the second cell of a wide character is
widened to its first, and copying drops the fillers.

Tests cover the width table, placement, the margin, overwriting halves
and insert mode.
A supplementary character is stored as its two surrogates, one per cell,
so it covers both of its cells itself. The null after it is an ordinary
empty cell, not a filler, and copying must keep it as a space. A repaint
that starts on the low surrogate is widened to the high one, like a
repaint starting on a filler. The proportional font path draws the
surrogate pair as one string instead of one half at a time.

Found by review of eclipse-platform#2894.
@akurtakov

Copy link
Copy Markdown
Member

I would like to be able to test this myself before going on further. Do you think you can give me clear instruction how to test and see the effect of this change?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@insjang

insjang commented Sep 30, 2026 •

Copy link
Copy Markdown
Author

Thanks for looking at it! Here is a way to see the effect.

Setup

Run an Eclipse application with org.eclipse.terminal.control from this branch (or install the built bundle), open a local terminal (bash or WSL), and compare with the same commands in any other terminal (xterm, GNOME Terminal, Windows Terminal, VS Code). The terminal font needs CJK glyphs; the default monospace font falls back to a system CJK font on Windows and Linux.

1. Column alignment: every | should end up in the same column, since each Korean/Japanese/full-width character takes two cells:

printf '%s|\n' 'abcd' '가나' '日本' 'ab'

Without the change the | characters are not aligned and wide characters overlap the following text.

2. Cursor movement over wide characters: move the cursor 4 cells to the right from the line start and overwrite:

printf 'ab가cd\r\033[4CX\n'

Expected output is ab가Xd (a, b and 가 fill 4 cells). Without the change the X lands on the wrong cell.

3. Line wrap: this wraps at the right edge, after half as many characters as the terminal has columns:

python3 -c "print('가' * 100)"

Without the change the line does not wrap: each character is drawn one cell after the previous one, so they overlap and only about half of them can be read.

4. Interactive: type echo 한글 test at the prompt, then press Left, Backspace and Delete a few times over the Korean letters. The cursor should stay on the character it edits. The same applies to programs that draw full-screen layouts such as vim, tmux (split borders) or ls with CJK file names.

The model tests in the PR (CharWidthTest and the wide character cases in VT100EmulatorBackendTest) cover the same cases without the UI.

Before and after, with the three commands above (Linux, D2Coding 11pt):
eaw-before-after

Insert mode made room with CharWidth.ofString, while the write path puts
every printable surrogate pair in two cells, so inserting a narrow
character beyond the BMP (U+1D400) overwrote the next character. Both now
use the same cell width. Writing over one cell of a surrogate pair also
blanks the other half, as for a wide character.
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.

3 participants