@heyhuynhgiabuu/pi-diff 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Full pi-diff.json schema.
3
+ * All fields are optional — missing fields fall through to env → defaults.
4
+ */
5
+ export interface PiDiffJson {
6
+ /** Hunk separator style. */
7
+ sepStyle?: "auto" | "simple" | "gap" | "context" | "metadata";
8
+ /** Show line numbers in gutter. */
9
+ lineNumbers?: boolean;
10
+ /** Diff indicator style on left edge. */
11
+ indicatorStyle?: "bar" | "classic" | "none";
12
+ /** How to render long lines. */
13
+ longLines?: "wrap" | "scroll";
14
+ /** Show file-header with filename and stats. */
15
+ fileHeader?: boolean;
16
+ /** Named diff color preset. */
17
+ theme?: string;
18
+ /** Shiki syntax theme name. */
19
+ shikiTheme?: string;
20
+ /** Per-color hex overrides. */
21
+ colors?: Partial<{
22
+ bgAdd: string;
23
+ bgDel: string;
24
+ bgAddHighlight: string;
25
+ bgDelHighlight: string;
26
+ bgGutterAdd: string;
27
+ bgGutterDel: string;
28
+ bgEmpty: string;
29
+ fgAdd: string;
30
+ fgDel: string;
31
+ fgDim: string;
32
+ fgLnum: string;
33
+ fgRule: string;
34
+ fgStripe: string;
35
+ fgSafeMuted: string;
36
+ }>;
37
+ }
38
+ /**
39
+ * Load pi-diff.json from project or global paths.
40
+ * Returns {} if neither file exists.
41
+ */
42
+ export declare function loadPiDiffConfig(cwd?: string): PiDiffJson;
43
+ /**
44
+ * Invalidate the cached config (useful for testing).
45
+ */
46
+ export declare function invalidatePiDiffConfig(): void;
47
+ export declare function configSepStyle(cwd?: string): PiDiffJson["sepStyle"];
48
+ export declare function configLineNumbers(cwd?: string): boolean | undefined;
49
+ export declare function configIndicatorStyle(cwd?: string): PiDiffJson["indicatorStyle"];
50
+ export declare function configLongLines(cwd?: string): PiDiffJson["longLines"];
51
+ export declare function configFileHeader(cwd?: string): boolean | undefined;
52
+ export declare function configTheme(cwd?: string): string | undefined;
53
+ export declare function configShikiTheme(cwd?: string): string | undefined;
54
+ export declare function configColors(cwd?: string): PiDiffJson["colors"];
55
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAuBA;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,UAAU,CAAC;IAC9D,mCAAmC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yCAAyC;IACzC,cAAc,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;IAC5C,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC9B,gDAAgD;IAChD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACH;AAQD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CA0CzD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAuBD,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAEnE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAEnE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAE/E;AAED,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAErE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAElE;AAED,wBAAgB,WAAW,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE5D;AAED,wBAAgB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEjE;AAED,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAE/D"}
@@ -0,0 +1,119 @@
1
+ // ---------------------------------------------------------------------------
2
+ // pi-diff.json config loader
3
+ //
4
+ // Config priority (highest first):
5
+ // 1. Environment variables (PI_DIFF_*) — for runtime/tool overrides
6
+ // 2. Project-level pi-diff.json (<cwd>/pi-diff.json)
7
+ // 3. Project-level .pi/pi-diff.json (pi-standard hidden config dir)
8
+ // 4. Global-level pi-diff.json (~/.pi/agent/pi-diff.json)
9
+ // 5. Global-level pi-diff.json (~/.pi/pi-diff.json)
10
+ // 6. Hardcoded defaults
11
+ //
12
+ // The env vars are read by the individual resolvers in diff.ts;
13
+ // this module handles file-based config only.
14
+ // ---------------------------------------------------------------------------
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ import { homedir } from "node:os";
17
+ import { join } from "node:path";
18
+ // ---------------------------------------------------------------------------
19
+ // Module state — singleton cache
20
+ // ---------------------------------------------------------------------------
21
+ let _cachedConfig = undefined; // null = not loaded, undefined = attempted/no-file
22
+ /**
23
+ * Load pi-diff.json from project or global paths.
24
+ * Returns {} if neither file exists.
25
+ */
26
+ export function loadPiDiffConfig(cwd) {
27
+ if (_cachedConfig !== undefined)
28
+ return _cachedConfig ?? {};
29
+ // When a specific cwd is provided (e.g. for testing), only search that path.
30
+ // When omitted, search project root then global.
31
+ const searchPaths = cwd
32
+ ? [
33
+ join(cwd, "pi-diff.json"),
34
+ join(cwd, ".pi", "pi-diff.json"),
35
+ ]
36
+ : [
37
+ join(process.cwd(), "pi-diff.json"),
38
+ join(process.cwd(), ".pi", "pi-diff.json"),
39
+ join(homedir(), ".pi", "agent", "pi-diff.json"),
40
+ join(homedir(), ".pi", "pi-diff.json"),
41
+ ];
42
+ // Deduplicate by resolving to absolute paths
43
+ const seen = new Set();
44
+ const uniquePaths = [];
45
+ for (const p of searchPaths) {
46
+ const abs = p.startsWith("/") ? p : join(process.cwd(), p);
47
+ if (!seen.has(abs)) {
48
+ seen.add(abs);
49
+ uniquePaths.push(abs);
50
+ }
51
+ }
52
+ // Priority: project first, then global. Later files extend/override earlier ones.
53
+ let merged = {};
54
+ for (const filePath of uniquePaths) {
55
+ try {
56
+ if (!existsSync(filePath))
57
+ continue;
58
+ const raw = JSON.parse(readFileSync(filePath, "utf-8"));
59
+ merged = deepMerge(merged, raw);
60
+ }
61
+ catch {
62
+ // Skip invalid files silently
63
+ }
64
+ }
65
+ _cachedConfig = Object.keys(merged).length > 0 ? merged : null;
66
+ return merged;
67
+ }
68
+ /**
69
+ * Invalidate the cached config (useful for testing).
70
+ */
71
+ export function invalidatePiDiffConfig() {
72
+ _cachedConfig = undefined;
73
+ }
74
+ /**
75
+ * Deep-merge two PiDiffJson objects. Later values win.
76
+ */
77
+ function deepMerge(a, b) {
78
+ const result = { ...a };
79
+ for (const key of Object.keys(b)) {
80
+ const bVal = b[key];
81
+ if (bVal === undefined)
82
+ continue;
83
+ if (key === "colors" && typeof bVal === "object" && bVal !== null) {
84
+ result.colors = { ...(a.colors || {}), ...bVal };
85
+ }
86
+ else {
87
+ result[key] = bVal;
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // Config value extractors — return the config value or undefined
94
+ // ---------------------------------------------------------------------------
95
+ export function configSepStyle(cwd) {
96
+ return loadPiDiffConfig(cwd).sepStyle;
97
+ }
98
+ export function configLineNumbers(cwd) {
99
+ return loadPiDiffConfig(cwd).lineNumbers;
100
+ }
101
+ export function configIndicatorStyle(cwd) {
102
+ return loadPiDiffConfig(cwd).indicatorStyle;
103
+ }
104
+ export function configLongLines(cwd) {
105
+ return loadPiDiffConfig(cwd).longLines;
106
+ }
107
+ export function configFileHeader(cwd) {
108
+ return loadPiDiffConfig(cwd).fileHeader;
109
+ }
110
+ export function configTheme(cwd) {
111
+ return loadPiDiffConfig(cwd).theme;
112
+ }
113
+ export function configShikiTheme(cwd) {
114
+ return loadPiDiffConfig(cwd).shikiTheme;
115
+ }
116
+ export function configColors(cwd) {
117
+ return loadPiDiffConfig(cwd).colors;
118
+ }
119
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /** A single merge conflict region within a file. */
2
+ export interface ConflictRegion {
3
+ /** The ref/branch name from <<<<<<< header. */
4
+ currentRef: string;
5
+ /** Lines in the current (ours) side. */
6
+ current: string[];
7
+ /** Lines in the base/ancestor side (3-way merge only). */
8
+ base: string[];
9
+ /** Lines in the incoming (theirs) side. */
10
+ incoming: string[];
11
+ /** The ref/branch name from >>>>>>> footer. */
12
+ incomingRef: string;
13
+ /** Whether this is a 3-way conflict (has base section). */
14
+ hasBase: boolean;
15
+ /** Starting line number in the file (1-based). */
16
+ startLine: number;
17
+ }
18
+ /** Result of parsing a file for merge conflicts. */
19
+ export interface ConflictParseResult {
20
+ regions: ConflictRegion[];
21
+ hasConflicts: boolean;
22
+ }
23
+ /**
24
+ * Parse a string of file content for merge conflict markers.
25
+ * Returns all conflict regions found.
26
+ */
27
+ export declare function parseConflicts(content: string): ConflictParseResult;
28
+ /**
29
+ * Check if a string contains merge conflict markers.
30
+ * Fast check without full parsing.
31
+ */
32
+ export declare function hasConflictMarkers(content: string): boolean;
33
+ /**
34
+ * Build a human-readable summary of a conflict region.
35
+ */
36
+ export declare function formatConflictSummary(region: ConflictRegion): string;
37
+ //# sourceMappingURL=conflicts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflicts.d.ts","sourceRoot":"","sources":["../../src/core/conflicts.ts"],"names":[],"mappings":"AAgBA,oDAAoD;AACpD,MAAM,WAAW,cAAc;IAC9B,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACtB;AAWD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAkEnE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE3D;AAMD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAUpE"}
@@ -0,0 +1,109 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Merge conflict parsing and rendering
3
+ //
4
+ // Detects and renders Git merge conflict markers:
5
+ // <<<<<<< <ref>
6
+ // <current changes>
7
+ // ||||||| <base ref>
8
+ // <base changes>
9
+ // =======
10
+ // <incoming changes>
11
+ // >>>>>>> <ref>
12
+ //
13
+ // Supports both 2-way (current + incoming) and 3-way (current + base + incoming)
14
+ // conflict styles.
15
+ // ---------------------------------------------------------------------------
16
+ // ---------------------------------------------------------------------------
17
+ // Parser
18
+ // ---------------------------------------------------------------------------
19
+ const CONFLICT_START_RE = /^<<<<<<<[ \t]*(.*)$/;
20
+ const CONFLICT_SEP_RE = /^=======$/;
21
+ const CONFLICT_BASE_RE = /^\|\|\|\|\|\|\|[ \t]*(.*)$/;
22
+ const CONFLICT_END_RE = /^>>>>>>>[ \t]*(.*)$/;
23
+ /**
24
+ * Parse a string of file content for merge conflict markers.
25
+ * Returns all conflict regions found.
26
+ */
27
+ export function parseConflicts(content) {
28
+ const lines = content.split("\n");
29
+ const regions = [];
30
+ let i = 0;
31
+ while (i < lines.length) {
32
+ const startMatch = lines[i].match(CONFLICT_START_RE);
33
+ if (!startMatch) {
34
+ i++;
35
+ continue;
36
+ }
37
+ // Found a conflict start
38
+ const startLine = i + 1; // 1-based
39
+ const currentRef = (startMatch[1] ?? "").trim();
40
+ const current = [];
41
+ const base = [];
42
+ const incoming = [];
43
+ let hasBase = false;
44
+ let state = "current";
45
+ let endRef = "";
46
+ i++; // advance past <<<<<<<
47
+ while (i < lines.length) {
48
+ const line = lines[i];
49
+ if (CONFLICT_BASE_RE.test(line)) {
50
+ state = "base";
51
+ hasBase = true;
52
+ i++;
53
+ continue;
54
+ }
55
+ if (CONFLICT_SEP_RE.test(line)) {
56
+ state = "incoming";
57
+ i++;
58
+ continue;
59
+ }
60
+ const endMatch = lines[i].match(CONFLICT_END_RE);
61
+ if (endMatch) {
62
+ endRef = (endMatch[1] ?? "").trim();
63
+ i++;
64
+ break;
65
+ }
66
+ if (state === "current")
67
+ current.push(line);
68
+ else if (state === "base")
69
+ base.push(line);
70
+ else
71
+ incoming.push(line);
72
+ i++;
73
+ }
74
+ regions.push({
75
+ currentRef,
76
+ current,
77
+ base,
78
+ incoming,
79
+ incomingRef: endRef,
80
+ hasBase,
81
+ startLine,
82
+ });
83
+ }
84
+ return { regions, hasConflicts: regions.length > 0 };
85
+ }
86
+ /**
87
+ * Check if a string contains merge conflict markers.
88
+ * Fast check without full parsing.
89
+ */
90
+ export function hasConflictMarkers(content) {
91
+ return /^<<<<<<<[ \t]?/m.test(content);
92
+ }
93
+ // ---------------------------------------------------------------------------
94
+ // Rendering helpers
95
+ // ---------------------------------------------------------------------------
96
+ /**
97
+ * Build a human-readable summary of a conflict region.
98
+ */
99
+ export function formatConflictSummary(region) {
100
+ const currentLabel = region.currentRef || "current";
101
+ const incomingLabel = region.incomingRef || "incoming";
102
+ const currentLines = region.current.length;
103
+ const incomingLines = region.incoming.length;
104
+ if (region.hasBase) {
105
+ return `Conflict: ${currentLabel} (${currentLines} lines) vs ${incomingLabel} (${incomingLines} lines) [3-way]`;
106
+ }
107
+ return `Conflict: ${currentLabel} (${currentLines} lines) vs ${incomingLabel} (${incomingLines} lines)`;
108
+ }
109
+ //# sourceMappingURL=conflicts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflicts.js","sourceRoot":"","sources":["../../src/core/conflicts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * resolve-lines.ts — Hunk-based line number resolution
3
+ *
4
+ * Matches an `existing_code` snippet from an LLM review comment against
5
+ * parsed diff hunks to backfill `startLine`/`endLine`. Ported from
6
+ * alibaba/open-code-review's internal/diff/resolver.go three-tier approach.
7
+ *
8
+ * Tiers:
9
+ * 1. New-side match (context + added lines with new-file line numbers)
10
+ * 2. Old-side match (context + deleted lines with old-file line numbers)
11
+ * 3. File-content scan (consecutive match in full new-file content)
12
+ */
13
+ import type { ParsedDiff } from "./diff.js";
14
+ export interface ResolvedLines {
15
+ startLine: number;
16
+ endLine: number;
17
+ }
18
+ export interface Unresolved {
19
+ unresolved: true;
20
+ }
21
+ export type LinesResult = ResolvedLines | Unresolved;
22
+ /**
23
+ * Resolve line numbers for an `existing_code` snippet against a parsed diff.
24
+ *
25
+ * Tries three tiers:
26
+ * 1. New-side hunk match (context + added lines)
27
+ * 2. Old-side hunk match (context + deleted lines)
28
+ * 3. Full file-content scan (if `fileContent` is provided)
29
+ *
30
+ * @param existingCode - The code snippet from the LLM comment
31
+ * @param parsedDiff - Parsed diff for the file (from parsePatchFiles or parseDiff)
32
+ * @param fileContent - Optional full new-file content for fallback scan
33
+ * @returns Resolved line numbers or Unresolved
34
+ */
35
+ export declare function resolveLines(existingCode: string, parsedDiff: ParsedDiff, fileContent?: string): LinesResult;
36
+ /**
37
+ * Convenience wrapper: resolve from a raw unified diff string instead of
38
+ * a pre-parsed ParsedDiff. Parses the diff internally.
39
+ *
40
+ * @param existingCode - The code snippet from the LLM comment
41
+ * @param unifiedDiff - Raw unified diff text (single file)
42
+ * @param fileContent - Optional full new-file content for fallback scan
43
+ */
44
+ export declare function resolveLinesFromPatch(existingCode: string, unifiedDiff: string, fileContent?: string): LinesResult;
45
+ //# sourceMappingURL=resolve-lines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-lines.d.ts","sourceRoot":"","sources":["../../src/core/resolve-lines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAY,UAAU,EAAE,MAAM,WAAW,CAAC;AAOtD,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IAC1B,UAAU,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,UAAU,CAAC;AAoKrD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC3B,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,EACtB,WAAW,CAAC,EAAE,MAAM,GAClB,WAAW,CAkCb;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GAClB,WAAW,CASb"}
@@ -0,0 +1,204 @@
1
+ /**
2
+ * resolve-lines.ts — Hunk-based line number resolution
3
+ *
4
+ * Matches an `existing_code` snippet from an LLM review comment against
5
+ * parsed diff hunks to backfill `startLine`/`endLine`. Ported from
6
+ * alibaba/open-code-review's internal/diff/resolver.go three-tier approach.
7
+ *
8
+ * Tiers:
9
+ * 1. New-side match (context + added lines with new-file line numbers)
10
+ * 2. Old-side match (context + deleted lines with old-file line numbers)
11
+ * 3. File-content scan (consecutive match in full new-file content)
12
+ */
13
+ import { parsePatchFiles } from "./diff.js";
14
+ // ---------------------------------------------------------------------------
15
+ // Normalization
16
+ // ---------------------------------------------------------------------------
17
+ /**
18
+ * Normalize a single code line for comparison.
19
+ * - Trims leading/trailing whitespace
20
+ * - Strips leading `+` or `-` diff markers
21
+ * - Re-trims after stripping
22
+ */
23
+ function normalizeLine(s) {
24
+ s = s.trim();
25
+ s = s.startsWith("+") || s.startsWith("-") ? s.slice(1).trim() : s.trim();
26
+ return s;
27
+ }
28
+ /**
29
+ * Split `existing_code` into normalized non-empty lines.
30
+ * Empty lines after normalization are dropped (mirrors OCR behavior).
31
+ */
32
+ function splitAndNormalize(code) {
33
+ const raw = code.split("\n");
34
+ const result = [];
35
+ for (const line of raw) {
36
+ const n = normalizeLine(line);
37
+ if (n === "")
38
+ continue;
39
+ result.push(n);
40
+ }
41
+ return result;
42
+ }
43
+ /**
44
+ * Reconstruct hunks from a flat ParsedDiff.
45
+ * Each `sep` marker with HunkMeta starts a new hunk. Context lines
46
+ * appear on both sides; `add` lines on new-side only; `del` lines on
47
+ * old-side only.
48
+ */
49
+ function reconstructHunks(parsed) {
50
+ const hunks = [];
51
+ let currentHunk = null;
52
+ let oldNum = 0;
53
+ let newNum = 0;
54
+ for (const line of parsed.lines) {
55
+ if (line.type === "sep" && line.hunkMeta) {
56
+ // Start a new hunk (or the first one)
57
+ currentHunk = { newSide: [], oldSide: [] };
58
+ hunks.push(currentHunk);
59
+ oldNum = line.hunkMeta.oldStart;
60
+ newNum = line.hunkMeta.newStart;
61
+ continue;
62
+ }
63
+ if (!currentHunk)
64
+ continue; // lines before first sep — shouldn't happen
65
+ switch (line.type) {
66
+ case "ctx":
67
+ currentHunk.newSide.push({ lineNum: newNum++, content: normalizeLine(line.content) });
68
+ currentHunk.oldSide.push({ lineNum: oldNum++, content: normalizeLine(line.content) });
69
+ break;
70
+ case "add":
71
+ currentHunk.newSide.push({ lineNum: newNum++, content: normalizeLine(line.content) });
72
+ break;
73
+ case "del":
74
+ currentHunk.oldSide.push({ lineNum: oldNum++, content: normalizeLine(line.content) });
75
+ break;
76
+ // "sep" without hunkMeta (e.g., multi-edit separators) — skip
77
+ }
78
+ }
79
+ return hunks;
80
+ }
81
+ // ---------------------------------------------------------------------------
82
+ // Sliding-window match
83
+ // ---------------------------------------------------------------------------
84
+ /**
85
+ * Scan `sideLines` for a consecutive run matching all `targetLines`.
86
+ * Returns the matching file line range, or undefined.
87
+ */
88
+ function matchConsecutive(sideLines, targetLines) {
89
+ if (targetLines.length === 0 || sideLines.length < targetLines.length)
90
+ return undefined;
91
+ for (let i = 0; i <= sideLines.length - targetLines.length; i++) {
92
+ let matched = true;
93
+ for (let j = 0; j < targetLines.length; j++) {
94
+ if (sideLines[i + j].content !== targetLines[j]) {
95
+ matched = false;
96
+ break;
97
+ }
98
+ }
99
+ if (matched) {
100
+ return {
101
+ startLine: sideLines[i].lineNum,
102
+ endLine: sideLines[i + targetLines.length - 1].lineNum,
103
+ };
104
+ }
105
+ }
106
+ return undefined;
107
+ }
108
+ // ---------------------------------------------------------------------------
109
+ // File-content fallback
110
+ // ---------------------------------------------------------------------------
111
+ /**
112
+ * Scan full file content line-by-line for a consecutive match of target lines.
113
+ * Returns 1-based line numbers.
114
+ */
115
+ function matchInFileContent(fileContent, targetLines) {
116
+ if (!fileContent || targetLines.length === 0)
117
+ return undefined;
118
+ const fileLines = fileContent.split("\n");
119
+ if (fileLines.length < targetLines.length)
120
+ return undefined;
121
+ for (let i = 0; i <= fileLines.length - targetLines.length; i++) {
122
+ let matched = true;
123
+ for (let j = 0; j < targetLines.length; j++) {
124
+ const fileLine = fileLines[i + j].replace(/\r$/, "").trim();
125
+ if (fileLine !== targetLines[j]) {
126
+ matched = false;
127
+ break;
128
+ }
129
+ }
130
+ if (matched) {
131
+ return { startLine: i + 1, endLine: i + targetLines.length };
132
+ }
133
+ }
134
+ return undefined;
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // Public API
138
+ // ---------------------------------------------------------------------------
139
+ /**
140
+ * Resolve line numbers for an `existing_code` snippet against a parsed diff.
141
+ *
142
+ * Tries three tiers:
143
+ * 1. New-side hunk match (context + added lines)
144
+ * 2. Old-side hunk match (context + deleted lines)
145
+ * 3. Full file-content scan (if `fileContent` is provided)
146
+ *
147
+ * @param existingCode - The code snippet from the LLM comment
148
+ * @param parsedDiff - Parsed diff for the file (from parsePatchFiles or parseDiff)
149
+ * @param fileContent - Optional full new-file content for fallback scan
150
+ * @returns Resolved line numbers or Unresolved
151
+ */
152
+ export function resolveLines(existingCode, parsedDiff, fileContent) {
153
+ if (!existingCode || !parsedDiff || parsedDiff.lines.length === 0) {
154
+ return { unresolved: true };
155
+ }
156
+ const targetLines = splitAndNormalize(existingCode);
157
+ if (targetLines.length === 0) {
158
+ return { unresolved: true };
159
+ }
160
+ const hunks = reconstructHunks(parsedDiff);
161
+ if (hunks.length === 0) {
162
+ return { unresolved: true };
163
+ }
164
+ // Tier 1: New-side match (context + added → new-file line numbers)
165
+ for (const hunk of hunks) {
166
+ const result = matchConsecutive(hunk.newSide, targetLines);
167
+ if (result)
168
+ return result;
169
+ }
170
+ // Tier 2: Old-side match (context + deleted → old-file line numbers)
171
+ for (const hunk of hunks) {
172
+ const result = matchConsecutive(hunk.oldSide, targetLines);
173
+ if (result)
174
+ return result;
175
+ }
176
+ // Tier 3: File-content fallback
177
+ if (fileContent) {
178
+ const result = matchInFileContent(fileContent, targetLines);
179
+ if (result)
180
+ return result;
181
+ }
182
+ return { unresolved: true };
183
+ }
184
+ /**
185
+ * Convenience wrapper: resolve from a raw unified diff string instead of
186
+ * a pre-parsed ParsedDiff. Parses the diff internally.
187
+ *
188
+ * @param existingCode - The code snippet from the LLM comment
189
+ * @param unifiedDiff - Raw unified diff text (single file)
190
+ * @param fileContent - Optional full new-file content for fallback scan
191
+ */
192
+ export function resolveLinesFromPatch(existingCode, unifiedDiff, fileContent) {
193
+ const parsed = parsePatchFiles(unifiedDiff);
194
+ if (parsed.length === 0)
195
+ return { unresolved: true };
196
+ // For multi-file patches, try each file's diff
197
+ for (const pd of parsed) {
198
+ const result = resolveLines(existingCode, pd, fileContent);
199
+ if (!("unresolved" in result))
200
+ return result;
201
+ }
202
+ return { unresolved: true };
203
+ }
204
+ //# sourceMappingURL=resolve-lines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-lines.js","sourceRoot":"","sources":["../../src/core/resolve-lines.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,OAAO,CAAC;AAG3D,OAAO,EAAE,iBAAiB,EAAiB,KAAK,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAkC,MAAM,gBAAgB,CAAC;AAC7K,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AA2cpE,oFAAoF;AACpF,UAAU,UAAU;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAuKD,iBAAS,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIpD;AAuVD,iBAAe,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,GAAG,SAAmB,EACtB,EAAE,GAAE,UAAgC,GAClC,OAAO,CAAC,MAAM,CAAC,CA6HjB;AAMD,iBAAe,WAAW,CACzB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,GAAG,SAAoB,EACvB,EAAE,GAAE,UAAgC,GAClC,OAAO,CAAC,MAAM,CAAC,CAkKjB;AAMD,eAAO,MAAM,SAAS;;;;;;;;;CASrB,CAAC;AAIF,wBAA8B,qBAAqB,CAAC,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4gBnF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,OAAO,CAAC;AAG3D,OAAO,EAAE,iBAAiB,EAAiB,KAAK,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAkC,MAAM,gBAAgB,CAAC;AAE7K,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AA8cpE,oFAAoF;AACpF,UAAU,UAAU;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAuKD,iBAAS,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIpD;AAuVD,iBAAe,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,GAAG,SAAmB,EACtB,EAAE,GAAE,UAAgC,GAClC,OAAO,CAAC,MAAM,CAAC,CA6HjB;AAMD,iBAAe,WAAW,CACzB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,GAAG,SAAoB,EACvB,EAAE,GAAE,UAAgC,GAClC,OAAO,CAAC,MAAM,CAAC,CAkKjB;AAMD,eAAO,MAAM,SAAS;;;;;;;;;CASrB,CAAC;AAIF,wBAA8B,qBAAqB,CAAC,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4gBnF"}
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ import { extname, relative } from "node:path";
25
25
  import { codeToANSI } from "@shikijs/cli";
26
26
  import * as Diff from "diff";
27
27
  import { computeHunkBlocks, getSepStyle, parseDiff, parsePatchFiles, resolveSepStyle, sepLabelSplit, sepLabelUnified } from "./core/diff.js";
28
+ import { configIndicatorStyle } from "./core/config.js";
28
29
  import { resolveLinesFromPatch } from "./core/resolve-lines.js";
29
30
  import { registerReviewDiffCommand } from "./review/command.js";
30
31
  import { applyDiffPalette as applySharedDiffPalette, lang as detectDiffLanguage, renderSplit as renderSharedSplit, resolveDiffColors as resolveSharedDiffColors, themeCacheKey as sharedThemeCacheKey, } from "./review/hunk-preview.js";
@@ -381,7 +382,10 @@ let FG_LNUM = "\x1b[38;2;100;100;100m";
381
382
  let FG_RULE = "\x1b[38;2;50;50;50m";
382
383
  let FG_SAFE_MUTED = "\x1b[38;2;139;148;158m";
383
384
  let FG_STRIPE = "\x1b[38;2;40;40;40m"; // gray diagonal stripes on terminal default bg
384
- const BORDER_BAR = "▌";
385
+ function getBorderBar() {
386
+ const style = configIndicatorStyle();
387
+ return style === "none" ? " " : "▌";
388
+ }
385
389
  /** Generate a dense diagonal stripe fill for empty filler cells.
386
390
  * Solid ╱ characters — uniform direction like CSS diagonal hatching. */
387
391
  function stripes(w, _rowOffset) {
@@ -913,7 +917,7 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
913
917
  /** Emit a single stacked row with compact gutter + left border bar. */
914
918
  function emitRow(num, sign, gutterBg, signFg, body, bodyBg = "") {
915
919
  const borderFg = sign === "-" ? dc.fgDel : sign === "+" ? dc.fgAdd : "";
916
- const border = borderFg ? `${borderFg}${BORDER_BAR}${RST}` : `${BG_BASE} `;
920
+ const border = borderFg ? `${borderFg}${getBorderBar()}${RST}` : `${BG_BASE} `;
917
921
  const numFg = borderFg || FG_LNUM;
918
922
  const gutter = `${border}${gutterBg}${lnum(num, nw, numFg)}${signFg}${sign}${RST} ${DIVIDER} `;
919
923
  const contGutter = `${border}${gutterBg}${" ".repeat(nw + 1)}${RST} ${DIVIDER} `;
@@ -1072,7 +1076,7 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
1072
1076
  const num = isDel ? line.oldNum : isAdd ? line.newNum : side === "left" ? line.oldNum : line.newNum;
1073
1077
  // Border bar + colored line numbers for changed lines
1074
1078
  const borderFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : "";
1075
- const border = borderFg ? `${borderFg}${BORDER_BAR}${RST}` : ` ${BG_BASE}`;
1079
+ const border = borderFg ? `${borderFg}${getBorderBar()}${RST}` : ` ${BG_BASE}`;
1076
1080
  const numFg = borderFg || FG_LNUM;
1077
1081
  let body;
1078
1082
  if (ranges && ranges.length > 0) {