Skip to content

Commit

Permalink
feat: use codiff to get better diff result
Browse files Browse the repository at this point in the history
  • Loading branch information
zhanba committed Feb 8, 2025
1 parent ae9d721 commit bf4a411
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 141 deletions.
1 change: 1 addition & 0 deletions clients/tabby-agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"deep-equal": "^2.2.1",
"deepmerge-ts": "^5.1.0",
"diff": "^5.2.0",
"codiff": "^0.1.1",
"dot-prop": "^8.0.2",
"esbuild": "^0.19.12",
"esbuild-plugin-polyfill-node": "^0.3.0",
Expand Down
54 changes: 36 additions & 18 deletions clients/tabby-agent/src/codeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ClientCapabilities, ServerCapabilities, CodeLens, CodeLensType, Changes
import { TextDocuments } from "./lsp/textDocuments";
import { TextDocument } from "vscode-languageserver-textdocument";
import { getLogger } from "./logger";
import { calcCharDiffRange } from "./utils/diff";
import { codeDiff } from "./utils/diff";

const codeLensType: CodeLensType = "previewChanges";
const changesPreviewLineType = {
Expand Down Expand Up @@ -62,8 +62,9 @@ export class CodeLensProvider implements Feature {
let lineInPreviewBlock = -1;
let previewBlockMarkers = "";
const originLines: string[] = [];
const editLines: string[] = [];
const editCodeLenses: CodeLens[] = [];
const modifiedLines: string[] = [];
const modifiedCodeLenses: CodeLens[] = [];
const originCodeLenses: CodeLens[] = [];
for (let line = textDocument.lineCount - 1; line >= 0; line = line - 1) {
if (token.isCancellationRequested) {
return null;
Expand Down Expand Up @@ -174,8 +175,9 @@ export class CodeLensProvider implements Feature {
},
};
originLines.unshift(text);
editLines.unshift(text);
editCodeLenses.unshift(codeLens);
originCodeLenses.unshift(codeLens);
modifiedLines.unshift(text);
modifiedCodeLenses.unshift(codeLens);
break;
case "|":
codeLens = {
Expand All @@ -185,8 +187,8 @@ export class CodeLensProvider implements Feature {
line: changesPreviewLineType.inProgress,
},
};
editLines.unshift(text);
editCodeLenses.unshift(codeLens);
modifiedLines.unshift(text);
modifiedCodeLenses.unshift(codeLens);
break;
case "=":
codeLens = {
Expand All @@ -197,8 +199,9 @@ export class CodeLensProvider implements Feature {
},
};
originLines.unshift(text);
editLines.unshift(text);
editCodeLenses.unshift(codeLens);
originCodeLenses.unshift(codeLens);
modifiedLines.unshift(text);
modifiedCodeLenses.unshift(codeLens);
break;
case "+":
codeLens = {
Expand All @@ -208,8 +211,8 @@ export class CodeLensProvider implements Feature {
line: changesPreviewLineType.inserted,
},
};
editLines.unshift(text);
editCodeLenses.unshift(codeLens);
modifiedLines.unshift(text);
modifiedCodeLenses.unshift(codeLens);
break;
case "-":
codeLens = {
Expand All @@ -220,6 +223,7 @@ export class CodeLensProvider implements Feature {
},
};
originLines.unshift(text);
originCodeLenses.unshift(codeLens);
break;
default:
break;
Expand All @@ -237,20 +241,34 @@ export class CodeLensProvider implements Feature {
}
}
}
const charDiffDecorationLenses = calcCharDiffRange(
originLines.join(""),
editLines.join(""),
editCodeLenses.map((item) => item.range),
).map<CodeLens>((codeLensRange) => {

const { originRanges, modifiedRanges } = codeDiff(
originLines,
originCodeLenses.map((item) => item.range),
modifiedLines,
modifiedCodeLenses.map((item) => item.range),
);
const deletionDecorations = originRanges.map((range) => {
return {
range: codeLensRange,
range,
data: {
type: codeLensType,
text: "deleted" as const,
},
};
});

const insertionDecorations = modifiedRanges.map((range) => {
return {
range,
data: {
type: codeLensType,
text: "inserted" as const,
},
};
});
codeLenses.push(...charDiffDecorationLenses);

codeLenses.push(...deletionDecorations, ...insertionDecorations);
logger.debug(`codeLenses: ${JSON.stringify(codeLenses)}`);

workDoneProgress?.done();
Expand Down
78 changes: 0 additions & 78 deletions clients/tabby-agent/src/utils/diff.test.ts

This file was deleted.

122 changes: 77 additions & 45 deletions clients/tabby-agent/src/utils/diff.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,84 @@
import { Position, Range } from "vscode-languageserver";
import { diffChars } from "diff";
import { Range } from "vscode-languageserver";
import { linesDiffComputers } from "codiff";

export function calcCharDiffRange(originText: string, editText: string, editTextRanges: Range[]): Range[] {
const diffRanges: Range[] = [];
const changes = diffChars(originText, editText);
let index = 0;
changes.forEach((item) => {
if (item.added) {
const position = getPositionFromIndex(index, editTextRanges);
const addedRange: Range = {
start: position,
end: { line: position.line, character: position.character + (item.count ?? 0) },
};
diffRanges.push(addedRange);
index += item.count ?? 0;
} else if (item.removed) {
// nothing
} else {
index += item.count ?? 0;
}
});
return diffRanges;
interface CodeDiffResult {
originRanges: Range[];
modifiedRanges: Range[];
}

export function getPositionFromIndex(index: number, ranges: Range[]): Position {
let line = 0;
let character = 0;
let length = 0;
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
if (!range) {
continue;
}
const rangeLength = range.end.character - range.start.character + length + 1;
if (index >= length && index < rangeLength) {
line = range.start.line;
character = index - length;
return {
line,
character,
};
} else {
length = rangeLength;
}
}
/**
* Diff code and mapping the result range to editor range
*/
export function codeDiff(
originCode: string[],
originCodeRanges: Range[],
modifiedCode: string[],
modifiedCodeRanges: Range[],
): CodeDiffResult {
const originRanges: Range[] = [];
const modifiedRanges: Range[] = [];

const diffResult = linesDiffComputers.getDefault().computeDiff(originCode, modifiedCode, {
computeMoves: false,
ignoreTrimWhitespace: true,
maxComputationTimeMs: 100,
});

diffResult.changes.forEach((change) => {
change.innerChanges?.forEach((innerChange) => {
/**
* In most case, stat line and end line are equal in diff change.
* when start line and end line are different in change, it usually means a range that include a whole line.
* {
* "startLineNumber": 2,
* "startColumn": 1,
* "endLineNumber": 3,
* "endColumn": 1
* }
*
* In our case, the origin code and modified code are mixed tegather. so we should translate range to below to avoid wrong range mapping.
* {
* "startLineNumber": 2,
* "startColumn": 1,
* "endLineNumber": 2,
* "endColumn": // end of line 2
* }
*
*/
let originEndChar = innerChange.originalRange.endColumn - 1;
if (innerChange.originalRange.endLineNumber !== innerChange.originalRange.startLineNumber) {
originEndChar = originCodeRanges[innerChange.originalRange.startLineNumber - 1]?.end.character ?? 0;
}
originRanges.push({
start: {
line: originCodeRanges[innerChange.originalRange.startLineNumber - 1]?.start.line ?? 0,
character: innerChange.originalRange.startColumn - 1,
},
end: {
line: originCodeRanges[innerChange.originalRange.startLineNumber - 1]?.start.line ?? 0,
character: originEndChar,
},
});

let modifiedEndChar = innerChange.modifiedRange.endColumn - 1;
if (innerChange.modifiedRange.endLineNumber !== innerChange.modifiedRange.startLineNumber) {
modifiedEndChar = modifiedCodeRanges[innerChange.modifiedRange.startLineNumber - 1]?.end.character ?? 0;
}
modifiedRanges.push({
start: {
line: modifiedCodeRanges[innerChange.modifiedRange.startLineNumber - 1]?.start.line ?? 0,
character: innerChange.modifiedRange.startColumn - 1,
},
end: {
line: modifiedCodeRanges[innerChange.modifiedRange.startLineNumber - 1]?.start.line ?? 0,
character: modifiedEndChar,
},
});
});
});

return {
line,
character,
modifiedRanges,
originRanges,
};
}
6 changes: 6 additions & 0 deletions clients/vscode/src/lsp/CodeLensMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ const decorationTypeTextInserted = window.createTextEditorDecorationType({
isWholeLine: false,
rangeBehavior: DecorationRangeBehavior.ClosedOpen,
});
const decorationTypeTextDeleted = window.createTextEditorDecorationType({
backgroundColor: new ThemeColor("diffEditor.removedTextBackground"),
isWholeLine: false,
rangeBehavior: DecorationRangeBehavior.ClosedOpen,
});
const decorationTypeLineInserted = window.createTextEditorDecorationType({
backgroundColor: new ThemeColor("diffEditor.insertedLineBackground"),
isWholeLine: true,
Expand All @@ -68,6 +73,7 @@ const decorationTypes: Record<string, TextEditorDecorationType> = {

const textDecorationTypes: Record<string, TextEditorDecorationType> = {
inserted: decorationTypeTextInserted,
deleted: decorationTypeTextDeleted,
};

export class CodeLensMiddleware implements VscodeLspCodeLensMiddleware {
Expand Down

0 comments on commit bf4a411

Please sign in to comment.