fix(tui): handle wide unicode input scrolling closes #1982
This commit is contained in:
@@ -3,7 +3,7 @@ import { decodeKittyPrintable } from "../keys.js";
|
||||
import { KillRing } from "../kill-ring.js";
|
||||
import { type Component, CURSOR_MARKER, type Focusable } from "../tui.js";
|
||||
import { UndoStack } from "../undo-stack.js";
|
||||
import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";
|
||||
import { getSegmenter, isPunctuationChar, isWhitespaceChar, sliceByColumn, visibleWidth } from "../utils.js";
|
||||
|
||||
const segmenter = getSegmenter();
|
||||
|
||||
@@ -442,56 +442,38 @@ export class Input implements Component, Focusable {
|
||||
|
||||
let visibleText = "";
|
||||
let cursorDisplay = this.cursor;
|
||||
const totalWidth = visibleWidth(this.value);
|
||||
|
||||
if (this.value.length < availableWidth) {
|
||||
if (totalWidth < availableWidth) {
|
||||
// Everything fits (leave room for cursor at end)
|
||||
visibleText = this.value;
|
||||
} else {
|
||||
// Need horizontal scrolling
|
||||
// Reserve one character for cursor if it's at the end
|
||||
// Reserve one column for cursor if it's at the end
|
||||
const scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;
|
||||
const halfWidth = Math.floor(scrollWidth / 2);
|
||||
const cursorCol = visibleWidth(this.value.slice(0, this.cursor));
|
||||
|
||||
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;
|
||||
if (scrollWidth > 0) {
|
||||
const halfWidth = Math.floor(scrollWidth / 2);
|
||||
let startCol = 0;
|
||||
|
||||
if (cursorCol < halfWidth) {
|
||||
// Cursor near start
|
||||
startCol = 0;
|
||||
} else if (cursorCol > totalWidth - halfWidth) {
|
||||
// Cursor near end
|
||||
startCol = Math.max(0, totalWidth - scrollWidth);
|
||||
} else {
|
||||
// Cursor in middle
|
||||
startCol = Math.max(0, cursorCol - halfWidth);
|
||||
}
|
||||
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, findValidEnd(scrollWidth));
|
||||
cursorDisplay = this.cursor;
|
||||
} else if (this.cursor > this.value.length - halfWidth) {
|
||||
// Cursor near end
|
||||
const start = findValidStart(this.value.length - scrollWidth);
|
||||
visibleText = this.value.slice(start);
|
||||
cursorDisplay = this.cursor - start;
|
||||
visibleText = sliceByColumn(this.value, startCol, scrollWidth);
|
||||
const beforeCursor = sliceByColumn(this.value, startCol, Math.max(0, cursorCol - startCol));
|
||||
cursorDisplay = beforeCursor.length;
|
||||
} else {
|
||||
// Cursor in middle
|
||||
const start = findValidStart(this.cursor - halfWidth);
|
||||
visibleText = this.value.slice(start, findValidEnd(start + scrollWidth));
|
||||
cursorDisplay = halfWidth;
|
||||
visibleText = "";
|
||||
cursorDisplay = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user