Skip to content

Commit 167c654

Browse files
committed
refactor(base): rewrite diff computation logic using line-by-line diffing and cleanup debug logs
1 parent 286cf51 commit 167c654

4 files changed

Lines changed: 139 additions & 121 deletions

File tree

anycode-base/src/actions.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ export const handleCopy = async (ctx: ActionContext): Promise<ActionResult> => {
281281

282282
let content = ctx.code.getIntervalContent2(start, end);
283283
await copyToClipboard(content);
284-
console.log('Copied:', content);
285284
} catch (err) {
286285
console.error('Failed to copy:', err);
287286
}
@@ -474,7 +473,6 @@ export const handleCut = async (ctx: ActionContext): Promise<ActionResult> => {
474473

475474
let content = ctx.code.getIntervalContent2(start, end);
476475
await copyToClipboard(content);
477-
console.log('Cut:', content);
478476

479477
ctx.code.tx();
480478
ctx.code.setStateBefore(ctx.offset, ctx.selection);

anycode-base/src/code.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,6 @@ export class Code {
185185
} catch (e) {
186186
console.error(e);
187187
}
188-
189-
console.log('injection language initialized', name);
190188
}
191189
}
192190
}

anycode-base/src/diff.ts

Lines changed: 81 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -15,130 +15,94 @@ export type Edit = {
1515
export type ChangeType = 'added' | 'modified' | 'deleted';
1616

1717
export type DiffInfo = {
18-
changeType: ChangeType;
19-
oldLineNumbers?: number[];
20-
ghostAnchorLine?: number;
21-
hunkId: number;
18+
changeType: ChangeType;
19+
oldLineNumbers?: number[];
20+
ghostAnchorLine?: number;
21+
hunkId: number;
2222
};
2323

2424
export function computeGitChanges(
25-
original: string, current: string
25+
original: string, current: string
2626
): Map<number, DiffInfo> {
27-
const changes = new Map<number, DiffInfo>();
28-
const currentLineCount = current === '' ? 1 : current.split('\n').length;
29-
const patch = JsDiff.createTwoFilesPatch(
30-
'a', 'b', original, current, '', '', { context: 0 }
31-
);
32-
33-
const lines = patch.split('\n');
34-
let hunkId = 0;
35-
let lastChangeWasConsecutive = false;
36-
37-
for (let i = 0; i < lines.length; i++) {
38-
const line = lines[i];
39-
40-
if (line.startsWith('@@ -')) {
41-
const headerMatch = line.match(/ \+(\d+)(?:,(\d+))?/);
42-
if (headerMatch) {
43-
const oldHeaderMatch = line.match(/@@ -(\d+)(?:,(\d+))?/);
44-
let newLine = parseInt(headerMatch[1], 10);
45-
let oldLine = oldHeaderMatch ? parseInt(oldHeaderMatch[1], 10) : 1;
46-
i++;
47-
lastChangeWasConsecutive = false;
48-
49-
let iterations = 0;
50-
while (i < lines.length && !lines[i].startsWith('@@')) {
51-
iterations++;
52-
if (iterations > 1000) {
53-
console.error('INFINITE LOOP DETECTED at i =', i, 'line:', lines[i]);
54-
break;
55-
}
56-
57-
const currentLine = lines[i];
58-
59-
if (currentLine.startsWith('\\')) {
60-
i++;
61-
continue;
62-
}
63-
64-
if (currentLine.startsWith('-') || currentLine.startsWith('+')) {
65-
const deletedLineNumbers: number[] = [];
66-
const addedLineNumbers: number[] = [];
67-
68-
while (i < lines.length && lines[i].startsWith('-')) {
69-
deletedLineNumbers.push(oldLine);
70-
oldLine++;
71-
i++;
72-
}
73-
74-
if (i < lines.length && lines[i].startsWith('\\')) {
75-
i++;
76-
}
77-
78-
while (i < lines.length && lines[i].startsWith('+')) {
79-
addedLineNumbers.push(newLine);
80-
newLine++;
81-
i++;
82-
}
83-
84-
if (i < lines.length && lines[i].startsWith('\\')) {
85-
i++;
86-
}
87-
88-
if (deletedLineNumbers.length > 0 && addedLineNumbers.length > 0) {
89-
for (const lineNum of addedLineNumbers) {
90-
changes.set(lineNum, {
91-
changeType: 'modified',
92-
oldLineNumbers: deletedLineNumbers,
93-
hunkId: hunkId,
94-
});
95-
}
96-
} else if (addedLineNumbers.length > 0) {
97-
// added
98-
for (const lineNum of addedLineNumbers) {
99-
changes.set(lineNum, {
100-
changeType: 'added',
101-
hunkId: hunkId,
102-
});
103-
}
104-
} else if (deletedLineNumbers.length > 0) {
105-
// deleted
106-
// JsDiff can emit +0 for deletions before the first line.
107-
// ghostAnchorLine is the line BEFORE which ghost lines appear.
108-
// markerLine is the line where the deletion marker appears in gutter
109-
// (aligned with the ghost anchor line in our renderer model).
110-
const ghostAnchorLine = Math.max(1, newLine + 1);
111-
const markerLine = Math.max(1, Math.min(ghostAnchorLine, currentLineCount));
112-
changes.set(markerLine, {
113-
changeType: 'deleted',
114-
oldLineNumbers: deletedLineNumbers,
115-
ghostAnchorLine,
116-
hunkId: hunkId,
117-
});
27+
const changes = new Map<number, DiffInfo>();
28+
const diffs = JsDiff.diffLines(original, current);
29+
const currentLineCount = current === '' ? 1 : current.split('\n').length;
30+
31+
let oldLine = 1;
32+
let newLine = 1;
33+
let hunkId = 0;
34+
let inChangeBlock = false;
35+
36+
const countLines = (value: string): number => {
37+
if (value === '') {
38+
return 0;
39+
}
40+
const parts = value.split('\n');
41+
return value.endsWith('\n') ? parts.length - 1 : parts.length;
42+
};
43+
44+
for (let i = 0; i < diffs.length; i++) {
45+
const diff = diffs[i];
46+
const { added, removed } = diff;
47+
const count = countLines(diff.value);
48+
49+
if (added || removed) {
50+
inChangeBlock = true;
51+
52+
if (removed) {
53+
const deletedLineNumbers: number[] = [];
54+
for (let j = 0; j < count; j++) {
55+
deletedLineNumbers.push(oldLine + j);
56+
}
57+
oldLine += count;
58+
59+
const next = diffs[i + 1];
60+
if (next?.added) {
61+
const addedCount = countLines(next.value);
62+
for (let j = 0; j < addedCount; j++) {
63+
changes.set(newLine + j, {
64+
changeType: 'modified',
65+
oldLineNumbers: deletedLineNumbers,
66+
hunkId: hunkId,
67+
});
68+
}
69+
newLine += addedCount;
70+
i++; // skip the added block
71+
} else {
72+
const ghostAnchorLine = Math.max(1, newLine);
73+
const markerLine = Math.max(1, Math.min(ghostAnchorLine, currentLineCount));
74+
75+
changes.set(markerLine, {
76+
changeType: 'deleted',
77+
oldLineNumbers: deletedLineNumbers,
78+
ghostAnchorLine,
79+
hunkId: hunkId,
80+
});
81+
}
82+
} else {
83+
// Pure addition
84+
for (let j = 0; j < count; j++) {
85+
changes.set(newLine + j, {
86+
changeType: 'added',
87+
hunkId: hunkId,
88+
});
89+
}
90+
newLine += count;
11891
}
119-
120-
lastChangeWasConsecutive = true;
121-
continue;
122-
} else if (currentLine.startsWith(' ')) {
123-
if (lastChangeWasConsecutive) {
124-
hunkId++;
125-
lastChangeWasConsecutive = false;
92+
} else {
93+
// Context lines (unchanged)
94+
if (inChangeBlock) {
95+
hunkId++;
96+
inChangeBlock = false;
12697
}
127-
oldLine++;
128-
newLine++;
129-
i++;
130-
} else {
131-
i++;
132-
}
133-
}
134-
// At the end of a @@ hunk, reset for next hunk
135-
if (lastChangeWasConsecutive) {
136-
hunkId++;
98+
oldLine += count;
99+
newLine += count;
137100
}
138-
i--;
139-
}
140101
}
141-
}
142102

143-
return changes;
103+
if (inChangeBlock) {
104+
hunkId++;
105+
}
106+
107+
return changes;
144108
}

anycode-base/tests/diff.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { computeGitChanges } from '../src/diff';
3+
4+
describe('computeGitChanges', () => {
5+
it('should handle simple addition', () => {
6+
const original = 'line1\nline2';
7+
const current = 'line1\nadded\nline2';
8+
const result = computeGitChanges(original, current);
9+
10+
expect(result.get(2)).toEqual({
11+
changeType: 'added',
12+
hunkId: 0
13+
});
14+
});
15+
16+
it('should handle simple deletion', () => {
17+
const original = 'line1\nline2\nline3';
18+
const current = 'line1\nline3';
19+
const result = computeGitChanges(original, current);
20+
21+
expect(result.get(2)).toEqual({
22+
changeType: 'deleted',
23+
oldLineNumbers: [2],
24+
ghostAnchorLine: 2,
25+
hunkId: 0
26+
});
27+
});
28+
29+
it('should handle modification', () => {
30+
const original = 'line1\nold\nline3';
31+
const current = 'line1\nnew\nline3';
32+
const result = computeGitChanges(original, current);
33+
34+
expect(result.get(2)).toEqual({
35+
changeType: 'modified',
36+
oldLineNumbers: [2],
37+
hunkId: 0
38+
});
39+
});
40+
41+
it('should handle multiple consecutive additions', () => {
42+
const original = 'line1\nline2';
43+
const current = 'line1\nadded1\nadded2\nline2';
44+
const result = computeGitChanges(original, current);
45+
46+
expect(result.get(2)).toEqual({ changeType: 'added', hunkId: 0 });
47+
expect(result.get(3)).toEqual({ changeType: 'added', hunkId: 0 });
48+
});
49+
50+
it('should handle separate hunks', () => {
51+
const original = '1\n2\n3\n4\n5\n6\n7';
52+
const current = '1\n2\nadded2.5\n3\n4\n5\n6\nadded6.5\n7';
53+
const result = computeGitChanges(original, current);
54+
55+
expect(result.get(3)).toEqual({ changeType: 'added', hunkId: 0 });
56+
expect(result.get(8)).toEqual({ changeType: 'added', hunkId: 1 });
57+
});
58+
});

0 commit comments

Comments
 (0)