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