feat(tui): hardware cursor positioning for IME support

- Add Focusable interface for components that need hardware cursor positioning
- Add CURSOR_MARKER (APC escape sequence) for marking cursor position in render output
- Editor and Input components implement Focusable and emit marker when focused
- TUI extracts cursor position from rendered output and positions hardware cursor
- Track hardwareCursorRow separately from cursorRow for differential rendering
- visibleWidth() and extractAnsiCode() now handle APC sequences
- Update overlay-test.ts example to demonstrate Focusable usage
- Add documentation for Focusable interface in docs/tui.md

Closes #719, closes #525
This commit is contained in:
Mario Zechner 2026-01-16 04:30:07 +01:00
parent d9464383ec
commit 07fad1362c
8 changed files with 193 additions and 17 deletions

View file

@ -112,6 +112,8 @@ export function visibleWidth(str: string): number {
clean = clean.replace(/\x1b\[[0-9;]*[mGKHJ]/g, "");
// Strip OSC 8 hyperlinks: \x1b]8;;URL\x07 and \x1b]8;;\x07
clean = clean.replace(/\x1b\]8;;[^\x07]*\x07/g, "");
// Strip APC sequences: \x1b_...\x07 or \x1b_...\x1b\\ (used for cursor marker)
clean = clean.replace(/\x1b_[^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
}
// Calculate width
@ -160,6 +162,18 @@ export function extractAnsiCode(str: string, pos: number): { code: string; lengt
return null;
}
// APC sequence: ESC _ ... BEL or ESC _ ... ST (ESC \)
// Used for cursor marker and application-specific commands
if (next === "_") {
let j = pos + 2;
while (j < str.length) {
if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
if (str[j] === "\x1b" && str[j + 1] === "\\") return { code: str.substring(pos, j + 2), length: j + 2 - pos };
j++;
}
return null;
}
return null;
}