Skip to content

Commit

Permalink
feat: show inline diff of charactors
Browse files Browse the repository at this point in the history
  • Loading branch information
zhanba committed Jan 16, 2025
1 parent 5aa27b5 commit 7f88130
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 4 deletions.
80 changes: 80 additions & 0 deletions clients/tabby-agent/src/codeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import {
WorkDoneProgressReporter,
ResultProgressReporter,
CodeLensParams,
Position,
} from "vscode-languageserver";
import { ClientCapabilities, ServerCapabilities, CodeLens, CodeLensType, ChangesPreviewLineType } from "./protocol";
import { TextDocuments } from "./lsp/textDocuments";
import { TextDocument } from "vscode-languageserver-textdocument";
import { getLogger } from "./logger";
import { diffChars } from "diff";

const codeLensType: CodeLensType = "previewChanges";
const changesPreviewLineType = {
Expand All @@ -25,6 +28,8 @@ const changesPreviewLineType = {
deleted: "deleted" as ChangesPreviewLineType,
};

const logger = getLogger('CodeLensProvider');

export class CodeLensProvider implements Feature {
constructor(private readonly documents: TextDocuments<TextDocument>) {}

Expand Down Expand Up @@ -57,6 +62,9 @@ export class CodeLensProvider implements Feature {
const codeLenses: CodeLens[] = [];
let lineInPreviewBlock = -1;
let previewBlockMarkers = "";
const originLines: string[] = [];
const editLines: string[] = [];
const editCodeLenses: CodeLens[] = []
for (let line = textDocument.lineCount - 1; line >= 0; line = line - 1) {
if (token.isCancellationRequested) {
return null;
Expand Down Expand Up @@ -166,6 +174,9 @@ export class CodeLensProvider implements Feature {
line: changesPreviewLineType.waiting,
},
};
originLines.unshift(text)
editLines.unshift(text)
editCodeLenses.unshift(codeLens)
break;
case "|":
codeLens = {
Expand All @@ -175,6 +186,8 @@ export class CodeLensProvider implements Feature {
line: changesPreviewLineType.inProgress,
},
};
editLines.unshift(text)
editCodeLenses.unshift(codeLens)
break;
case "=":
codeLens = {
Expand All @@ -184,6 +197,9 @@ export class CodeLensProvider implements Feature {
line: changesPreviewLineType.unchanged,
},
};
originLines.unshift(text)
editLines.unshift(text)
editCodeLenses.unshift(codeLens)
break;
case "+":
codeLens = {
Expand All @@ -193,6 +209,8 @@ export class CodeLensProvider implements Feature {
line: changesPreviewLineType.inserted,
},
};
editLines.unshift(text)
editCodeLenses.unshift(codeLens)
break;
case "-":
codeLens = {
Expand All @@ -202,6 +220,7 @@ export class CodeLensProvider implements Feature {
line: changesPreviewLineType.deleted,
},
};
originLines.unshift(text)
break;
default:
break;
Expand All @@ -219,11 +238,72 @@ export class CodeLensProvider implements Feature {
}
}
}
const charDecorationLenses = this.getCharDiffDecoration(originLines, editLines, editCodeLenses)
codeLenses.push(...charDecorationLenses)
logger.debug(`codeLenses: ${JSON.stringify(codeLenses)}`)

workDoneProgress?.done();
if (resultProgress) {
return null;
} else {
return codeLenses;
}
}

getCharDiffDecoration(originLines: string[], editLines: string[], editCodeLenses: CodeLens[]) {
const editCharCodeLenses: CodeLens[] = []
const changes = diffChars(originLines.join(''), editLines.join(''))
let index = 0;
changes.forEach(item => {
if (item.added) {
const position = this.getPositionFromIndex(index, editCodeLenses)
const codeLensRange: Range = {
start: position,
end: { line: position.line, character: position.character + (item.count??0) },
};
const codeLens = {
range: codeLensRange,
data: {
type: codeLensType,
text: 'inserted' as const,
},
};
editCharCodeLenses.push(codeLens)
} else if(item.removed) {
// nothing
} else {
index += item.count ?? 0
}
})
return editCharCodeLenses
}

getPositionFromIndex(index: number, editCodeLenses: CodeLens[]): Position {
let line = 0;
let character = 0;
let length = 0;
for (let i = 0; i < editCodeLenses.length; i++) {
const item = editCodeLenses[i];
const range = item?.range;
if (!range) {
continue
}
const rangeLength = range.end.character - range.start.character + length
if (index >= length && index <= rangeLength) {
line = range.start.line
character = index - length
return {
line,
character
}
} else {
length = rangeLength
}
}

return {
line,
character
}
}
}
3 changes: 3 additions & 0 deletions clients/tabby-agent/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ export type CodeLens = LspCodeLens & {
data?: {
type: CodeLensType;
line?: ChangesPreviewLineType;
text?: ChangesPreviewTextType;
};
};

Expand All @@ -290,6 +291,8 @@ export type ChangesPreviewLineType =
| "inserted"
| "deleted";

export type ChangesPreviewTextType = "inserted"| "deleted"

/**
* Extends LSP method Completion Request(↩️)
*
Expand Down
24 changes: 20 additions & 4 deletions clients/vscode/src/lsp/CodeLensMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,18 @@ const decorationTypePending = window.createTextEditorDecorationType({
isWholeLine: true,
rangeBehavior: DecorationRangeBehavior.ClosedClosed,
});
const decorationTypeInserted = window.createTextEditorDecorationType({
const decorationTypeTextInserted = window.createTextEditorDecorationType({
backgroundColor: new ThemeColor("diffEditor.insertedTextBackground"),
isWholeLine: false,
rangeBehavior: DecorationRangeBehavior.ClosedOpen,
});
const decorationTypeLineInserted = window.createTextEditorDecorationType({
backgroundColor: new ThemeColor("diffEditor.insertedLineBackground"),
isWholeLine: true,
rangeBehavior: DecorationRangeBehavior.ClosedClosed,
});
const decorationTypeDeleted = window.createTextEditorDecorationType({
backgroundColor: new ThemeColor("diffEditor.removedTextBackground"),
backgroundColor: new ThemeColor("diffEditor.removedLineBackground"),
isWholeLine: true,
rangeBehavior: DecorationRangeBehavior.ClosedClosed,
});
Expand All @@ -55,12 +60,16 @@ const decorationTypes: Record<string, TextEditorDecorationType> = {
commentsFirstLine: decorationTypeComments,
comments: decorationTypeComments,
waiting: decorationTypePending,
inProgress: decorationTypeInserted,
inProgress: decorationTypeLineInserted,
unchanged: decorationTypeUnchanged,
inserted: decorationTypeInserted,
inserted: decorationTypeLineInserted,
deleted: decorationTypeDeleted,
};

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

export class CodeLensMiddleware implements VscodeLspCodeLensMiddleware {
private readonly decorationMap = new Map<TextEditor, Map<TextEditorDecorationType, Range[]>>();

Expand Down Expand Up @@ -100,6 +109,13 @@ export class CodeLensMiddleware implements VscodeLspCodeLensMiddleware {
this.addDecorationRange(editor, decorationType, decorationRange);
}
}
const textType = codeLens.data.text
if (typeof textType === "string" && textType in textDecorationTypes) {
const decorationType = textDecorationTypes[textType];
if (decorationType) {
this.addDecorationRange(editor, decorationType, decorationRange);
}
}
if (codeLens.data.line === "header") {
return codeLens;
}
Expand Down

0 comments on commit 7f88130

Please sign in to comment.