SPB Git

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%
3.2 KB · 109 lines typescript
Raw Blame History
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