// Derived from Node.js v24.16.0 (MIT; Copyright Node.js contributors, and Joyent, Inc. and // other Node contributors). See LICENSE-node. // readline's cursor helpers, after Node's lib/internal/readline/callbacks.js. Shared with // tty.WriteStream's cursorTo/moveCursor/clearLine/clearScreenDown. import { invalidArgType } from "../errors"; import { inspect } from "../inspect"; import { nextTick } from "../ticks"; export const kEscape = "\x1b"; /** A control sequence introducer: CSI`2K` is ESC [ 2 K. */ export function CSI(strings: TemplateStringsArray, ...args: unknown[]): string { let ret = `${kEscape}[`; for (let n = 0; n < strings.length; n++) { ret += strings[n]; if (n < args.length) ret += args[n]; } return ret; } export const kClearToLineBeginning = CSI`1K`; export const kClearToLineEnd = CSI`0K`; export const kClearLine = CSI`2K`; export const kClearScreenDown = CSI`0J`; interface Writable { write(data: string, cb?: (err?: Error | null) => void): boolean; } type Callback = (err?: Error | null) => void; function validateCallback(callback: unknown): void { if (callback !== undefined && typeof callback !== "function") throw invalidArgType("callback", "of type function", callback); } function invalidArgValue(name: string, value: unknown): TypeError { return Object.assign(new TypeError(`The argument '${name}' is invalid. Received ${inspect(value)}`), { code: "ERR_INVALID_ARG_VALUE" }); } /** Nothing to write: the callback still runs, a tick later. */ function noop(callback: unknown): true { if (typeof callback === "function") nextTick(() => (callback as Callback)(null)); return true; } export function cursorTo(stream: Writable | null | undefined, x: unknown, y?: unknown, callback?: unknown): boolean { validateCallback(callback); if (typeof y === "function") { callback = y; y = undefined; } if (Number.isNaN(x)) throw invalidArgValue("x", x); if (Number.isNaN(y)) throw invalidArgValue("y", y); if (stream == null || (typeof x !== "number" && typeof y !== "number")) return noop(callback); if (typeof x !== "number") { throw Object.assign(new TypeError("Cannot set cursor row without setting its column"), { code: "ERR_INVALID_CURSOR_POS" }); } const data = typeof y !== "number" ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; return stream.write(data, callback as Callback | undefined); } export function moveCursor(stream: Writable | null | undefined, dx: number, dy: number, callback?: unknown): boolean { validateCallback(callback); if (stream == null || !(dx || dy)) return noop(callback); let data = ""; if (dx < 0) data += CSI`${-dx}D`; else if (dx > 0) data += CSI`${dx}C`; if (dy < 0) data += CSI`${-dy}A`; else if (dy > 0) data += CSI`${dy}B`; return stream.write(data, callback as Callback | undefined); } /** -1 clears left of the cursor, 1 right of it, 0 the whole line. */ export function clearLine(stream: Writable | null | undefined, dir: number, callback?: unknown): boolean { validateCallback(callback); if (stream === null || stream === undefined) return noop(callback); const type = dir < 0 ? kClearToLineBeginning : dir > 0 ? kClearToLineEnd : kClearLine; return stream.write(type, callback as Callback | undefined); } export function clearScreenDown(stream: Writable | null | undefined, callback?: unknown): boolean { validateCallback(callback); if (stream === null || stream === undefined) return noop(callback); return stream.write(kClearScreenDown, callback as Callback | undefined); }