spb/khaelor Public
KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.
TypeScript 82.9%
HTML 14.9%
CSS 1.1%
JavaScript 0.7%
1/**2 * KHAELOR3 * File: src/tui/renderer/ansi.ts4 * Description: ANSI escape primitives and pure string-measurement helpers (strip, width, truncate, wrap).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910export const ESC = "\x1b";11export const CSI = "\x1b[";1213/** DEC 2026 synchronized-output frame markers (TUI_DESIGN §1.3). */14export const SYNC_ON = "\x1b[?2026h";15export const SYNC_OFF = "\x1b[?2026l";1617export const HIDE_CURSOR = "\x1b[?25l";18export const SHOW_CURSOR = "\x1b[?25h";1920export const BRACKETED_PASTE_ON = "\x1b[?2004h";21export const BRACKETED_PASTE_OFF = "\x1b[?2004l";2223export const ERASE_LINE = "\x1b[2K";24export const ERASE_DOWN = "\x1b[0J";25export const SGR_RESET = "\x1b[0m";2627/** Matches CSI sequences, OSC sequences (BEL or ST terminated), and lone two-byte escapes. */28const ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b./g;2930export function stripAnsi(s: string): string {31 return s.replace(ANSI_RE, "");32}3334/**35 * Visible width in terminal columns. Code-point counting: correct for the36 * ASCII/Latin/box-drawing/symbol repertoire KHAELOR emits; East-Asian37 * double-width content may over-run by design in V1 (documented limitation).38 */39export function visibleWidth(s: string): number {40 return [...stripAnsi(s)].length;41}4243/**44 * Truncate a (possibly styled) string to `width` visible columns.45 * Escape sequences are preserved; if any styling was emitted before the cut,46 * a reset is appended so truncation never leaks styles into following text.47 */48export function truncateAnsi(s: string, width: number): string {49 if (width <= 0) return "";50 if (visibleWidth(s) <= width) return s;51 let out = "";52 let visible = 0;53 let sawEscape = false;54 let i = 0;55 while (i < s.length && visible < width) {56 if (s[i] === ESC) {57 ANSI_RE.lastIndex = i;58 const m = ANSI_RE.exec(s);59 if (m && m.index === i) {60 out += m[0];61 sawEscape = true;62 i += m[0].length;63 continue;64 }65 }66 const cp = s.codePointAt(i);67 const ch = cp !== undefined ? String.fromCodePoint(cp) : (s[i] as string);68 out += ch;69 visible += 1;70 i += ch.length;71 }72 return sawEscape ? out + SGR_RESET : out;73}7475/** Greedy word wrap for plain (unstyled) text; hard-splits words longer than the width. */76export function wrapText(text: string, width: number): string[] {77 const w = Math.max(1, width);78 const out: string[] = [];79 for (const raw of text.split("\n")) {80 if ([...raw].length <= w) {81 out.push(raw);82 continue;83 }84 let line = "";85 for (const word of raw.split(" ")) {86 const candidate = line === "" ? word : line + " " + word;87 if ([...candidate].length <= w) {88 line = candidate;89 } else {90 if (line !== "") out.push(line);91 let rest = word;92 while ([...rest].length > w) {93 out.push([...rest].slice(0, w).join(""));94 rest = [...rest].slice(w).join("");95 }96 line = rest;97 }98 }99 out.push(line);100 }101 return out;102}103104/** Pad a (possibly styled) string with spaces to `width` visible columns. */105export function padEndAnsi(s: string, width: number): string {106 const w = visibleWidth(s);107 return w >= width ? s : s + " ".repeat(width - w);108}109