diff --git a/src/terminal.ts b/src/terminal.ts index df203b3..81a4717 100644 --- a/src/terminal.ts +++ b/src/terminal.ts @@ -1,29 +1,39 @@ -// Terminal styling utilities. +// Terminal styling and rendering utilities. // Respects NO_COLOR convention, --no-color flag, and TTY detection. import { isStdoutTTY } from "./tty"; +// ── Color support ───────────────────────────────────────────────────── + let _enabled: boolean | null = null; export function setColorEnabled(enabled: boolean): void { - _enabled = enabled; + _enabled = enabled; } export function isColorEnabled(): boolean { - if (_enabled !== null) return _enabled; + if (_enabled !== null) return _enabled; - // Respect NO_COLOR: https://no-color.org/ - if (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== "") { - return false; - } - if (!isStdoutTTY()) return false; - if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") return true; + // Respect NO_COLOR: https://no-color.org/ + if (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== "") { + _enabled = false; + return false; + } + if (!isStdoutTTY()) { + _enabled = false; + return false; + } + if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") { + _enabled = true; + return true; + } - return true; + _enabled = true; + return true; } function s(code: string): string { - return isColorEnabled() ? code : ""; + return isColorEnabled() ? code : ""; } export const BOLD = () => s("\x1b[1m"); @@ -33,3 +43,35 @@ export const YELLOW = () => s("\x1b[33m"); export const CYAN = () => s("\x1b[36m"); export const RED = () => s("\x1b[31m"); export const RESET = () => s("\x1b[0m"); + +// ── Terminal rendering helpers ─────────────────────────────────────── + +export function hideCursor(): void { + process.stdout.write("\x1b[?25l"); +} + +export function showCursor(): void { + process.stdout.write("\x1b[?25h"); +} + +export function clearLine(): void { + process.stdout.write("\r\x1b[2K"); +} + +export function moveUp(lines: number): void { + if (lines > 0) process.stdout.write(`\x1b[${lines}A`); +} + +export function clearScreen(): void { + process.stdout.write("\x1b[2J\x1b[H"); +} + +/** Calculate visible length of a string, stripping ANSI escape codes. */ +export function visibleLength(value: string): number { + return value.replace(/\x1b\[[0-9;]*m/g, "").length; +} + +/** Pad a string to the given visible width (accounting for ANSI codes). */ +export function padRight(value: string, width: number): string { + return value + " ".repeat(Math.max(0, width - visibleLength(value))); +}