From 99c78b91cb17ced82caf804e1c079f6f8fd86b2d Mon Sep 17 00:00:00 2001 From: haoqixu Date: Wed, 4 Feb 2026 01:51:55 +0800 Subject: [PATCH] fix(tui): avoid split of emojis when scrolling input --- packages/tui/src/components/input.ts | 37 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/tui/src/components/input.ts b/packages/tui/src/components/input.ts index 99ae2b78..ac310bfd 100644 --- a/packages/tui/src/components/input.ts +++ b/packages/tui/src/components/input.ts @@ -289,18 +289,45 @@ export class Input implements Component, Focusable { const scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth; const halfWidth = Math.floor(scrollWidth / 2); + const findValidStart = (start: number) => { + while (start < this.value.length) { + const charCode = this.value.charCodeAt(start); + // this is low surrogate, not a valid start + if (charCode >= 0xdc00 && charCode < 0xe000) { + start++; + continue; + } + break; + } + return start; + }; + + const findValidEnd = (end: number) => { + while (end > 0) { + const charCode = this.value.charCodeAt(end - 1); + // this is high surrogate, might be split. + if (charCode >= 0xd800 && charCode < 0xdc00) { + end--; + continue; + } + break; + } + return end; + }; + if (this.cursor < halfWidth) { // Cursor near start - visibleText = this.value.slice(0, scrollWidth); + visibleText = this.value.slice(0, findValidEnd(scrollWidth)); cursorDisplay = this.cursor; } else if (this.cursor > this.value.length - halfWidth) { // Cursor near end - visibleText = this.value.slice(this.value.length - scrollWidth); - cursorDisplay = scrollWidth - (this.value.length - this.cursor); + const start = findValidStart(this.value.length - scrollWidth); + visibleText = this.value.slice(start); + cursorDisplay = this.cursor - start; } else { // Cursor in middle - const start = this.cursor - halfWidth; - visibleText = this.value.slice(start, start + scrollWidth); + const start = findValidStart(this.cursor - halfWidth); + visibleText = this.value.slice(start, findValidEnd(start + scrollWidth)); cursorDisplay = halfWidth; } }