fix(tui): leverage Intl.Segmenter for proper Unicode word boundaries (#5022)

This commit is contained in:
xu0o0
2026-05-27 06:28:47 +08:00
committed by GitHub
parent 8fb1e877cd
commit 4402100830
4 changed files with 141 additions and 68 deletions

View File

@@ -591,6 +591,82 @@ describe("Editor component", () => {
editor.handleInput("\x1b[1;5C"); // Ctrl+Right
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 6 }); // after 'foo'
});
it("stops at fullwidth Chinese punctuation (issue #4972)", () => {
const editor = new Editor(createTestTUI(), defaultEditorTheme);
// 你好,世界 = 你好(0-2) (2-3) 世界(3-5)
editor.setText("你好,世界");
// Cursor at end (col 5)
// Move left over 世界
editor.handleInput("\x1b[1;5D"); // Ctrl+Left
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 3 }); // after
// Move left over
editor.handleInput("\x1b[1;5D"); // Ctrl+Left
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 2 }); // after 你好
// Move left over 你好
editor.handleInput("\x1b[1;5D"); // Ctrl+Left
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 }); // start
// Move right over 你好
editor.handleInput("\x1b[1;5C"); // Ctrl+Right
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 2 }); // after 你好
// Move right over
editor.handleInput("\x1b[1;5C"); // Ctrl+Right
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 3 }); // after
// Move right over 世界
editor.handleInput("\x1b[1;5C"); // Ctrl+Right
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 5 }); // end
});
it("handles mixed CJK and ASCII word movement", () => {
const editor = new Editor(createTestTUI(), defaultEditorTheme);
// "hello你好world世界" = hello(0-5) 你好(5-7) (7-8) world(8-13) 世界(13-15)
editor.setText("hello你好world世界");
// Cursor at end (col 15)
// Move left over 世界
editor.handleInput("\x1b[1;5D"); // Ctrl+Left
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 13 }); // after 'world'
// Move left over world
editor.handleInput("\x1b[1;5D"); // Ctrl+Left
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 8 }); // after
// Move left over
editor.handleInput("\x1b[1;5D"); // Ctrl+Left
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 7 }); // after 你好
// Move left over 你好
editor.handleInput("\x1b[1;5D"); // Ctrl+Left
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 5 }); // after 'hello'
// Move left over hello
editor.handleInput("\x1b[1;5D"); // Ctrl+Left
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 }); // start
// Forward from start
editor.handleInput("\x1b[1;5C"); // Ctrl+Right
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 5 }); // after 'hello'
editor.handleInput("\x1b[1;5C"); // Ctrl+Right
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 7 }); // after 你好
editor.handleInput("\x1b[1;5C"); // Ctrl+Right
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 8 }); // after
editor.handleInput("\x1b[1;5C"); // Ctrl+Right
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 13 }); // after 'world'
editor.handleInput("\x1b[1;5C"); // Ctrl+Right
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 15 }); // end
});
});
describe("Grapheme-aware text wrapping", () => {