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/components/diff.ts4 * Description: Inline edit summaries and unified diff blocks — +/− glyphs preserved for monochrome (TUI_DESIGN §7.1).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { highlightLine } from "../markdown/highlight.js";11import { truncateAnsi } from "../renderer/ansi.js";12import { rule } from "./tool-line.js";13import type { Theme } from "../theme.js";1415export interface DiffStats {16 added: number;17 removed: number;18}1920/** Never bare "Edited file": `✓ path +31 −12 d expand diff`. */21export function renderEditSummary(22 path: string,23 stats: DiffStats,24 width: number,25 theme: Theme,26 hint = true,27): string {28 let line =29 ` ${theme.paint("success", "✓")} ${path} ` +30 `${theme.paint("success", `+${stats.added}`)} ${theme.paint("error", `−${stats.removed}`)}`;31 if (hint) line += theme.paint("dim", " d expand diff");32 return truncateAnsi(line, width);33}3435const EXT_LANG: Record<string, string> = {36 ts: "ts",37 tsx: "tsx",38 js: "js",39 jsx: "jsx",40 py: "python",41 sh: "bash",42 rs: "rust",43 go: "go",44 json: "json",45 yaml: "yaml",46 yml: "yaml",47 toml: "toml",48};4950function langFor(path: string): string | null {51 const ext = /\.([a-z]+)$/i.exec(path)?.[1]?.toLowerCase();52 return ext !== undefined ? (EXT_LANG[ext] ?? null) : null;53}5455function highlight(code: string, lang: string | null, theme: Theme): string {56 return highlightLine(code, lang)57 .map((s) => (s.role === "text" ? s.text : theme.paint(s.role, s.text)))58 .join("");59}6061/**62 * Unified diff as a settled block: `+` lines in the added color, `−` in the63 * removed color, glyphs preserved so monochrome terminals keep the meaning.64 * Syntax highlighting rides on the same theme palette.65 */66export function renderDiffBlock(67 path: string,68 unifiedDiff: string,69 stats: DiffStats,70 width: number,71 theme: Theme,72): string[] {73 const lang = langFor(path);74 const out: string[] = [rule(` diff · ${path} · +${stats.added} −${stats.removed} `, width, theme)];75 for (const raw of unifiedDiff.split("\n")) {76 if (raw === "" || raw.startsWith("diff ") || raw.startsWith("index ")) continue;77 let line: string;78 if (raw.startsWith("+++") || raw.startsWith("---")) {79 line = theme.paint("dim", raw);80 } else if (raw.startsWith("@@")) {81 line = theme.paint("dim", raw);82 } else if (raw.startsWith("+")) {83 line = theme.tintLine("add", theme.paint("success", "+") + highlight(raw.slice(1), lang, theme));84 } else if (raw.startsWith("-")) {85 line = theme.tintLine("del", theme.paint("error", "-") + highlight(raw.slice(1), lang, theme));86 } else {87 line = theme.paint("dim", raw);88 }89 out.push(truncateAnsi(" " + line, width));90 }91 out.push(theme.paint("dim", "─".repeat(Math.max(10, Math.min(width - 1, 60)))));92 return out;93}94