import { visibleWidth } from "@earendil-works/pi-tui"; export interface CursorConfig { blinkMs: number; prompt: string; symbol: string; color: string; working: { symbol: string; }; } export const DEFAULT_CURSOR_CONFIG: Readonly = { blinkMs: 700, prompt: ">: ", symbol: "█", color: "accent", working: { symbol: "░", }, }; export function defaultCursorConfig(): CursorConfig { return { ...DEFAULT_CURSOR_CONFIG, working: { ...DEFAULT_CURSOR_CONFIG.working } }; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function isCursorSymbol(value: unknown): value is string { return typeof value === "string" && [...value].length === 1 && visibleWidth(value) === 1 && !/[\r\n]/.test(value); } function isPrompt(value: unknown): value is string { return typeof value === "string" && value.length <= 24 && !/[\r\n]/.test(value); } export function isHexColor(value: unknown): value is string { return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value); } function isColor(value: unknown): value is string { return isHexColor(value) || (typeof value === "string" && /^[a-z][a-zA-Z]*$/.test(value)); } export function parseCursorConfig(value: unknown): CursorConfig { if (!isRecord(value)) return defaultCursorConfig(); return { blinkMs: typeof value.blinkMs === "number" && Number.isInteger(value.blinkMs) && value.blinkMs >= 0 ? value.blinkMs : DEFAULT_CURSOR_CONFIG.blinkMs, prompt: isPrompt(value.prompt) ? value.prompt : DEFAULT_CURSOR_CONFIG.prompt, symbol: isCursorSymbol(value.symbol) ? value.symbol : DEFAULT_CURSOR_CONFIG.symbol, color: isColor(value.color) ? value.color : DEFAULT_CURSOR_CONFIG.color, working: isRecord(value.working) && isCursorSymbol(value.working.symbol) ? { symbol: value.working.symbol } : { ...DEFAULT_CURSOR_CONFIG.working }, }; }