From 15e0957b045d9e0d49253b2285cb585cf3a75c55 Mon Sep 17 00:00:00 2001 From: Fero Date: Mon, 9 Mar 2026 23:19:26 +0100 Subject: [PATCH] fix(tui): stop wide input slice overflow closes #1982 (#2006) --- packages/tui/CHANGELOG.md | 2 +- packages/tui/src/components/input.ts | 4 ++-- packages/tui/test/input.test.ts | 25 +++++++++++++++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index fa9156ab..a28830f5 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed `Input` horizontal scrolling for wide Unicode text (CJK, fullwidth characters) to use visual column width instead of string length, preventing rendered line overflow and TUI crashes ([#1982](https://github.com/badlogic/pi-mono/issues/1982)) +- Fixed `Input` horizontal scrolling for wide Unicode text (CJK, fullwidth characters) to use visual column width and strict slice boundaries, preventing rendered line overflow and TUI crashes ([#1982](https://github.com/badlogic/pi-mono/issues/1982)) ## [0.57.1] - 2026-03-07 diff --git a/packages/tui/src/components/input.ts b/packages/tui/src/components/input.ts index 750218cc..0ff795f1 100644 --- a/packages/tui/src/components/input.ts +++ b/packages/tui/src/components/input.ts @@ -468,8 +468,8 @@ export class Input implements Component, Focusable { startCol = Math.max(0, cursorCol - halfWidth); } - visibleText = sliceByColumn(this.value, startCol, scrollWidth); - const beforeCursor = sliceByColumn(this.value, startCol, Math.max(0, cursorCol - startCol)); + visibleText = sliceByColumn(this.value, startCol, scrollWidth, true); + const beforeCursor = sliceByColumn(this.value, startCol, Math.max(0, cursorCol - startCol), true); cursorDisplay = beforeCursor.length; } else { visibleText = ""; diff --git a/packages/tui/test/input.test.ts b/packages/tui/test/input.test.ts index d2f4047a..3edd73ca 100644 --- a/packages/tui/test/input.test.ts +++ b/packages/tui/test/input.test.ts @@ -43,15 +43,28 @@ describe("Input component", () => { "这是一段测试文本,用于验证中文字符在终端中的显示宽度是否被正确计算,如果不正确就会导致用户界面崩溃的问题", "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklm", ]; + const cursorPositions = [ + { label: "start", move: (_input: Input) => {} }, + { + label: "middle", + move: (input: Input) => { + for (let i = 0; i < 10; i++) input.handleInput("\x1b[C"); + }, + }, + { label: "end", move: (input: Input) => input.handleInput("\x05") }, + ]; for (const text of cases) { - const input = new Input(); - input.setValue(text); - input.focused = true; + for (const { label, move } of cursorPositions) { + const input = new Input(); + input.setValue(text); + input.focused = true; + move(input); - const [line] = input.render(width); - assert.ok(line); - assert.ok(visibleWidth(line) <= width, `rendered line overflowed for ${text}`); + const [line] = input.render(width); + assert.ok(line); + assert.ok(visibleWidth(line) <= width, `rendered line overflowed for ${text} at ${label}`); + } } });