singularity-forge/packages/tui/src/hardware-cursor.ts

55 lines
1.5 KiB
TypeScript

import type { Terminal } from "./terminal.js";
/** Logical cursor row/col extracted from overlay-stripped rendered lines */
export type HardwareCursorLogicalPos = { row: number; col: number };
/**
* Move the terminal cursor so IME anchors to the rendered logical cursor (`CURSOR_MARKER`).
*
* Purpose: IME placement stays testable without the differential render pipeline.
*
* Consumer: `TUI.doRender` after marker strip (`extractCursorPosition`).
*
* Returns the next logical hardware cursor row used for incremental cursor-move math.
*/
export function syncHardwareCursorForIME(
terminal: Pick<Terminal, "hideCursor" | "showCursor" | "write">,
options: {
cursorPos: HardwareCursorLogicalPos | null;
totalLines: number;
hardwareCursorRow: number;
showHardwareCursor: boolean;
},
): number {
const { cursorPos, totalLines, hardwareCursorRow, showHardwareCursor } =
options;
if (!cursorPos || totalLines <= 0) {
terminal.hideCursor();
return hardwareCursorRow;
}
const targetRow = Math.max(0, Math.min(cursorPos.row, totalLines - 1));
const targetCol = Math.max(0, cursorPos.col);
const rowDelta = targetRow - hardwareCursorRow;
let buffer = "";
if (rowDelta > 0) {
buffer += `\x1b[${rowDelta}B`;
} else if (rowDelta < 0) {
buffer += `\x1b[${-rowDelta}A`;
}
buffer += `\x1b[${targetCol + 1}G`;
if (buffer) {
terminal.write(buffer);
}
if (showHardwareCursor) {
terminal.showCursor();
} else {
terminal.hideCursor();
}
return targetRow;
}