Skip to content

fix: correct terminal display width for Thai sara am and similar SpacingMark characters - #2

Draft
admannon with Copilot wants to merge 6 commits into
mainfrom
copilot/fix-ui-bug-width-calculation
Draft

fix: correct terminal display width for Thai sara am and similar SpacingMark characters#2
admannon with Copilot wants to merge 6 commits into
mainfrom
copilot/fix-ui-bug-width-calculation

Conversation

Copilot AI commented Feb 22, 2026

Copy link
Copy Markdown

Summary

string-width v8 uses Intl.Segmenter and treats "ทำ" as a single grapheme cluster (width 1), but Thai sara am (ำ, U+0E33) occupies its own terminal column — correct width is 2. InputPrompt.tsx called stringWidth() directly on multi-character strings, causing ghost text wrapping and mouse click detection to be off by 1 column per sara am occurrence.

Details

Root cause: stringWidth("ทำ") === 1 (string-width v8, wrong) vs stringWidth("ท") + stringWidth("ำ") === 2 (per-code-point, correct). Unicode NFD decomposition is not a viable alternative: Thai sara am (U+0E33) has no canonical decomposition so NFD leaves it unchanged, and a naive NFD-then-per-code-point approach breaks emoji width (e.g. 👍🏿 → 4 instead of 2 by expanding skin-tone modifiers).

New getStringDisplayWidth(str) in textUtils.ts — hybrid strategy per grapheme cluster:

  • Single code-point or emoji cluster (ZWJ sequences, skin-tone modifiers): delegate to getCachedStringWidth(cluster) — emoji remain correctly 2
  • Multi-code-point non-emoji cluster (e.g. "ทำ"): sum per-code-point widths — each SpacingMark character contributes its own terminal column
// Before — wrong for Thai sara am
const usedWidth = stringWidth(textBeforeCursor); // "ทำ" → 1

// After — correct
const usedWidth = getStringDisplayWidth(textBeforeCursor); // "ทำ" → 2

InputPrompt.tsx: All stringWidth(str) calls replaced with getStringDisplayWidth(str); direct string-width import and getCachedStringWidth import both removed — InputPrompt.tsx now depends on only getStringDisplayWidth for all width calculations.

Related Issues

How to Validate

  1. Type Thai text with sara am (e.g. ทำงาน) into the input prompt
  2. Trigger ghost/autocomplete text containing ทำ — confirm it wraps at the correct column
  3. Mouse-click on a line containing ทำ — confirm click lands on the correct character
npm test -w @google/gemini-cli -- src/ui/utils/textUtils.test.ts
npm test -w @google/gemini-cli -- src/ui/components/InputPrompt.test.tsx

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
    • Linux
      • npm run
      • npx
      • Docker

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI and others added 2 commits February 22, 2026 17:24
Co-authored-by: admannon <1027194+admannon@users.noreply.github.com>
Co-authored-by: admannon <1027194+admannon@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix UI bug with width calculation for สระอำ fix: correct visual width for Thai sara am and other grapheme-combining characters Feb 22, 2026
Copilot AI requested a review from admannon February 22, 2026 17:43
@admannon

Copy link
Copy Markdown
Owner

@copilot "ทำ" should be 2 graphemes not 1

Co-authored-by: admannon <1027194+admannon@users.noreply.github.com>

Copilot AI commented Feb 22, 2026

Copy link
Copy Markdown
Author

@copilot "ทำ" should be 2 graphemes not 1

You're right. Thai sara am (ำ) occupies its own terminal column, so "ทำ" should be width 2, not 1. My use of Intl.Segmenter to collapse them into a single cluster was incorrect. I've reverted the change in 6096252 — the original per-code-point getCachedStringWidth approach is the correct behaviour.

Copilot AI changed the title fix: correct visual width for Thai sara am and other grapheme-combining characters fix: investigate UI width bug with Thai sara am characters Feb 22, 2026
… and click detection

Co-authored-by: admannon <1027194+admannon@users.noreply.github.com>
Copilot AI changed the title fix: investigate UI width bug with Thai sara am characters fix: correct terminal display width for Thai sara am and similar SpacingMark characters Feb 22, 2026
Comment thread packages/cli/src/ui/components/InputPrompt.tsx Outdated
Co-authored-by: admannon <1027194+admannon@users.noreply.github.com>
@admannon

Copy link
Copy Markdown
Owner

@copilot should we use unicode decomposition to determine the width?

Copilot AI commented Feb 22, 2026

Copy link
Copy Markdown
Author

@copilot should we use unicode decomposition to determine the width?

No — NFD decomposition would not help here and would actually break emoji width calculation.

Thai sara am (U+0E33) has no canonical decomposition"ทำ".normalize('NFD') still yields [U+0E17, U+0E33], unchanged. NFD only decomposes precomposed Latin characters like é (U+00E9 → e + U+0301), which our current approach already handles correctly.

More critically, NFD breaks emoji: a naive NFD-then-per-code-point sum gives 👍🏿 → 4 (expected 2) and 👨‍👩‍👧‍👦 → 8 (expected 2), because NFD expands skin-tone modifiers and ZWJ sequences into individual code points.

The current hybrid in getStringDisplayWidth — per-code-point for non-emoji multi-code-point clusters, cluster-level for emoji — is the right approach.

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.

2 participants