{"version":3,"file":"DiffView.d.ts","sourceRoot":"","sources":["../../src/components/DiffView.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzE,MAAM,WAAW,QAAQ;IACxB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAClC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACpC;AAcD,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,cAAc,CAAC;AAEpD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEpD;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,+BAA+B;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,qBAAa,QAAS,YAAW,SAAS;IACzC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,eAAe,CAAU;IAEjC,YAAY,OAAO,EAAE,eAAe,EAKnC;IAED,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAEhC;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAG9B;IAED,OAAO,CAAC,aAAa;IA4BrB,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,YAAY;CAMpB;AAuBD,UAAU,QAAQ;IACjB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAyCzD","sourcesContent":["/**\n * Diff view — side-by-side ≥ 100 cols, unified otherwise.\n *\n * Implements the \"Diff rendering: side-by-side ≥ 100 cols, unified otherwise,\n * AAA contrast\" deliverable from WS10. This component takes a pre-computed\n * line diff (additions / deletions / context) and lays it out for a given\n * terminal width. Hunk parsing is the caller's job — the existing edit-format\n * renderers in `coding-agent/src/core/edit-formats/` (WS8 territory) will\n * produce these line-tagged inputs.\n */\nimport type { Component } from \"../tui.js\";\nimport { truncateToWidth, visibleWidth } from \"../utils.js\";\n\nexport type DiffLineKind = \"context\" | \"add\" | \"del\" | \"header\" | \"hunk\";\n\nexport interface DiffLine {\n\tkind: DiffLineKind;\n\ttext: string;\n\toldLine?: number;\n\tnewLine?: number;\n}\n\nexport interface DiffViewTheme {\n\tadd: (text: string) => string;\n\tdel: (text: string) => string;\n\tcontext: (text: string) => string;\n\theader: (text: string) => string;\n\thunk: (text: string) => string;\n\tgutterAdd: (text: string) => string;\n\tgutterDel: (text: string) => string;\n\tgutterContext: (text: string) => string;\n\tseparator: (text: string) => string;\n}\n\nconst IDENTITY_THEME: DiffViewTheme = {\n\tadd: (s) => s,\n\tdel: (s) => s,\n\tcontext: (s) => s,\n\theader: (s) => s,\n\thunk: (s) => s,\n\tgutterAdd: (s) => s,\n\tgutterDel: (s) => s,\n\tgutterContext: (s) => s,\n\tseparator: (s) => s,\n};\n\n/** Threshold below which we render unified rather than side-by-side. */\nexport const SIDE_BY_SIDE_MIN_WIDTH = 100;\n\nexport type DiffLayout = \"unified\" | \"side-by-side\";\n\nexport function pickLayout(width: number): DiffLayout {\n\treturn width >= SIDE_BY_SIDE_MIN_WIDTH ? \"side-by-side\" : \"unified\";\n}\n\nexport interface DiffViewOptions {\n\tlines: DiffLine[];\n\ttheme?: DiffViewTheme;\n\t/** Force a specific layout. Default: width-based per `pickLayout`. */\n\tforceLayout?: DiffLayout;\n\t/** Show line-number gutter. */\n\tshowLineNumbers?: boolean;\n}\n\nexport class DiffView implements Component {\n\tprivate lines: DiffLine[];\n\tprivate theme: DiffViewTheme;\n\tprivate forceLayout?: DiffLayout;\n\tprivate showLineNumbers: boolean;\n\n\tconstructor(options: DiffViewOptions) {\n\t\tthis.lines = options.lines;\n\t\tthis.theme = options.theme ?? IDENTITY_THEME;\n\t\tthis.forceLayout = options.forceLayout;\n\t\tthis.showLineNumbers = options.showLineNumbers ?? true;\n\t}\n\n\tsetLines(lines: DiffLine[]): void {\n\t\tthis.lines = lines;\n\t}\n\n\tinvalidate(): void {\n\t\t// Stateless — recomputed each render call.\n\t}\n\n\trender(width: number): string[] {\n\t\tconst layout = this.forceLayout ?? pickLayout(width);\n\t\treturn layout === \"side-by-side\" ? this.renderSideBySide(width) : this.renderUnified(width);\n\t}\n\n\tprivate renderUnified(width: number): string[] {\n\t\tconst out: string[] = [];\n\t\tconst gutterW = this.showLineNumbers ? 5 : 0;\n\t\tconst sepW = 1; // \" \"\n\t\tconst prefixW = 1; // \"+\", \"-\", \" \"\n\t\tconst contentW = Math.max(1, width - gutterW - sepW - prefixW);\n\n\t\tfor (const line of this.lines) {\n\t\t\tif (line.kind === \"header\") {\n\t\t\t\tout.push(this.theme.header(padToWidth(truncateToWidth(line.text, width), width)));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (line.kind === \"hunk\") {\n\t\t\t\tout.push(this.theme.hunk(padToWidth(truncateToWidth(line.text, width), width)));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst gutter = this.showLineNumbers ? this.formatGutter(line.oldLine, line.newLine, line.kind) : \"\";\n\t\t\tconst prefix = line.kind === \"add\" ? \"+\" : line.kind === \"del\" ? \"-\" : \" \";\n\t\t\tconst body = truncateToWidth(line.text, contentW);\n\n\t\t\tlet row = `${gutter}${prefix} ${body}`;\n\t\t\trow = padToWidth(row, width);\n\t\t\trow = applyKind(row, line.kind, this.theme);\n\t\t\tout.push(row);\n\t\t}\n\t\treturn out;\n\t}\n\n\tprivate renderSideBySide(width: number): string[] {\n\t\tconst sepW = 3; // \" │ \"\n\t\tconst colW = Math.floor((width - sepW) / 2);\n\t\tconst out: string[] = [];\n\t\tconst pairs = pairUpHunks(this.lines);\n\n\t\tfor (const pair of pairs) {\n\t\t\tif (pair.kind === \"header\") {\n\t\t\t\tout.push(this.theme.header(padToWidth(truncateToWidth(pair.text, width), width)));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (pair.kind === \"hunk\") {\n\t\t\t\tout.push(this.theme.hunk(padToWidth(truncateToWidth(pair.text, width), width)));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst left = pair.left ? this.formatHalf(pair.left, colW) : padToWidth(\"\", colW);\n\t\t\tconst right = pair.right ? this.formatHalf(pair.right, colW) : padToWidth(\"\", colW);\n\t\t\tconst sep = this.theme.separator(\" │ \");\n\t\t\tout.push(`${left}${sep}${right}`);\n\t\t}\n\t\treturn out;\n\t}\n\n\tprivate formatHalf(line: DiffLine, colW: number): string {\n\t\tconst prefix = line.kind === \"add\" ? \"+\" : line.kind === \"del\" ? \"-\" : \" \";\n\t\tconst body = truncateToWidth(line.text, Math.max(1, colW - 2));\n\t\tconst row = padToWidth(`${prefix} ${body}`, colW);\n\t\treturn applyKind(row, line.kind, this.theme);\n\t}\n\n\tprivate formatGutter(oldLine: number | undefined, newLine: number | undefined, kind: DiffLineKind): string {\n\t\tconst o = oldLine !== undefined ? String(oldLine) : \"\";\n\t\tconst n = newLine !== undefined ? String(newLine) : \"\";\n\t\tconst cell = kind === \"add\" ? n : kind === \"del\" ? o : n || o;\n\t\treturn `${cell.padStart(4, \" \")} `;\n\t}\n}\n\nfunction applyKind(row: string, kind: DiffLineKind, theme: DiffViewTheme): string {\n\tswitch (kind) {\n\t\tcase \"add\":\n\t\t\treturn theme.add(row);\n\t\tcase \"del\":\n\t\t\treturn theme.del(row);\n\t\tcase \"context\":\n\t\t\treturn theme.context(row);\n\t\tcase \"header\":\n\t\t\treturn theme.header(row);\n\t\tcase \"hunk\":\n\t\t\treturn theme.hunk(row);\n\t}\n}\n\nfunction padToWidth(s: string, width: number): string {\n\tconst w = visibleWidth(s);\n\tif (w >= width) return s;\n\treturn s + \" \".repeat(width - w);\n}\n\ninterface DiffPair {\n\tkind: DiffLineKind;\n\ttext: string;\n\tleft?: DiffLine;\n\tright?: DiffLine;\n}\n\n/**\n * Pair up consecutive del/add runs for side-by-side layout. Within a hunk:\n *\n *   [del A, del B, add C, add D, ctx X]\n *\n * becomes:\n *\n *   [del A | add C, del B | add D, ctx X | ctx X]\n *\n * Mismatched lengths are padded with empty halves.\n */\nexport function pairUpHunks(lines: DiffLine[]): DiffPair[] {\n\tconst out: DiffPair[] = [];\n\tlet i = 0;\n\twhile (i < lines.length) {\n\t\tconst line = lines[i];\n\t\tif (line.kind === \"header\") {\n\t\t\tout.push({ kind: \"header\", text: line.text });\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (line.kind === \"hunk\") {\n\t\t\tout.push({ kind: \"hunk\", text: line.text });\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (line.kind === \"context\") {\n\t\t\tout.push({ kind: \"context\", text: line.text, left: line, right: line });\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Collect run of dels then adds.\n\t\tconst dels: DiffLine[] = [];\n\t\tconst adds: DiffLine[] = [];\n\t\twhile (i < lines.length && lines[i].kind === \"del\") {\n\t\t\tdels.push(lines[i]);\n\t\t\ti++;\n\t\t}\n\t\twhile (i < lines.length && lines[i].kind === \"add\") {\n\t\t\tadds.push(lines[i]);\n\t\t\ti++;\n\t\t}\n\t\tconst max = Math.max(dels.length, adds.length);\n\t\tfor (let k = 0; k < max; k++) {\n\t\t\tconst left = dels[k];\n\t\t\tconst right = adds[k];\n\t\t\tconst kind: DiffLineKind = left ? \"del\" : \"add\";\n\t\t\tout.push({ kind, text: (left ?? right ?? { text: \"\" }).text, left, right });\n\t\t}\n\t}\n\treturn out;\n}\n"]}