Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(vscode): use codelens for inline edit actions / decorations #3000

Merged
merged 19 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion clients/tabby-agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"types": "./dist/protocol.d.ts",
"scripts": {
"build": "tsup --minify",
"watch": "tsup --watch",
"watch": "tsup --watch --onSuccess \"echo '' >> dist/protocol.d.ts\"",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@icycodes this is a hack to trigger pnpm watch for vscode

"vscode:dev": "pnpm watch",
"openapi-codegen": "openapi-typescript ./openapi/tabby.json -o ./src/types/tabbyApi.d.ts",
"test": "mocha",
"lint": "eslint --ext .ts ./src && prettier --check .",
Expand Down
109 changes: 77 additions & 32 deletions clients/tabby-agent/src/lsp/ChatEditProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,25 +183,35 @@ export class ChatEditProvider {
}

async resolveEdit(params: ChatEditResolveParams): Promise<boolean> {
if (params.action === "cancel") {
this.mutexAbortController?.abort();
return false;
}

const document = this.documents.get(params.location.uri);
if (!document) {
return false;
}
const header = document.getText({
start: {
line: params.location.range.start.line,
character: 0,
},
end: {
line: params.location.range.start.line + 1,
character: 0,
},
});
const match = /^<<<<<<<.+(<.*>)\[(tabby-[0-9|a-z|A-Z]{6})\]/g.exec(header);
const markers = match?.[1];
if (!match || !markers) {

let markers;
let line = params.location.range.start.line;
for (; line < document.lineCount; line++) {
const lineText = document.getText({
start: { line, character: 0 },
end: { line: line + 1, character: 0 },
});

const match = /^>>>>>>> (tabby-[0-9|a-z|A-Z]{6}) (\[.*\])/g.exec(lineText);
markers = match?.[2];
if (markers) {
break;
}
}

if (!markers) {
return false;
}

const previewRange = {
start: {
line: params.location.range.start.line,
Expand Down Expand Up @@ -257,6 +267,32 @@ export class ChatEditProvider {
responseCommentTag?: string[],
): Promise<void> {
const applyEdit = async (edit: Edit, isFirst: boolean = false, isLast: boolean = false) => {
if (isFirst) {
const workspaceEdit: WorkspaceEdit = {
changes: {
[edit.location.uri]: [
{
range: edit.editedRange,
newText: `<<<<<<< ${edit.id}\n`,
},
],
},
};

await this.applyWorkspaceEdit({
edit: workspaceEdit,
options: {
undoStopBefore: true,
undoStopAfter: false,
},
});

edit.editedRange = {
start: { line: edit.editedRange.start.line + 1, character: 0 },
end: { line: edit.editedRange.start.line + 1, character: 0 },
};
}

const editedLines = this.generateChangesPreview(edit);
const workspaceEdit: WorkspaceEdit = {
changes: {
Expand All @@ -272,7 +308,7 @@ export class ChatEditProvider {
await this.applyWorkspaceEdit({
edit: workspaceEdit,
options: {
undoStopBefore: isFirst,
undoStopBefore: false,
undoStopAfter: isLast,
},
});
Expand Down Expand Up @@ -321,11 +357,17 @@ export class ChatEditProvider {
};

try {
if (!this.currentEdit) {
throw new Error("No current edit");
}

let inTag: "document" | "comment" | false = false;
let isFirstEdit = true;

// Insert the first line as early as possible so codelens can be shown
await applyEdit(this.currentEdit, true, false);

for await (const delta of stream) {
if (!this.currentEdit || !this.mutexAbortController || this.mutexAbortController.signal.aborted) {
if (!this.mutexAbortController || this.mutexAbortController.signal.aborted) {
break;
}

Expand All @@ -341,8 +383,9 @@ export class ChatEditProvider {
const closeTag = inTag === "document" ? responseDocumentTag[1] : responseCommentTag?.[1];
if (!closeTag || !openTag) break;
inTag = processBuffer(edit, inTag, openTag, closeTag);
await applyEdit(edit, isFirstEdit, false);
isFirstEdit = false;
if (delta.includes("\n")) {
await applyEdit(edit, false, false);
}
}
}

Expand Down Expand Up @@ -393,20 +436,14 @@ export class ChatEditProvider {
// [+] inserted
// [-] deleted
// [>] footer
// [x] stopped
// footer line
// >>>>>>> End of changes
private generateChangesPreview(edit: Edit): string[] {
const lines: string[] = [];
let markers = "";
// header
let stateDescription = "Editing in progress";
if (edit.state === "stopped") {
stateDescription = "Editing stopped";
} else if (edit.state == "completed") {
stateDescription = "Editing completed";
}
lines.push(`<<<<<<< ${stateDescription} {{markers}}[${edit.id}]`);
markers += "<";
// lines.push(`<<<<<<< ${stateDescription} {{markers}}[${edit.id}]`);
markers += "[";
// comments: split by new line or 80 chars
const commentLines = edit.comments
.trim()
Expand Down Expand Up @@ -468,22 +505,30 @@ export class ChatEditProvider {
lineIndex++;
}
if (inProgressChunk && lastDiff) {
pushDiffValue(lastDiff.value, "|");
if (edit.state === "stopped") {
pushDiffValue(lastDiff.value, "x");
} else {
pushDiffValue(lastDiff.value, "|");
}
}
while (lineIndex < diffs.length - inProgressChunk) {
const diff = diffs[lineIndex];
if (!diff) {
break;
}
pushDiffValue(diff.value, ".");
if (edit.state === "stopped") {
pushDiffValue(diff.value, "x");
} else {
pushDiffValue(diff.value, ".");
}
lineIndex++;
}
}
// footer
lines.push(`>>>>>>> ${stateDescription} {{markers}}[${edit.id}]`);
markers += ">";
lines.push(`>>>>>>> ${edit.id} {{markers}}`);
markers += "]";
// replace markers
lines[0] = lines[0]!.replace("{{markers}}", markers);
// lines[0] = lines[0]!.replace("{{markers}}", markers);
lines[lines.length - 1] = lines[lines.length - 1]!.replace("{{markers}}", markers);
return lines;
}
Expand Down
95 changes: 61 additions & 34 deletions clients/tabby-agent/src/lsp/CodeLensProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class CodeLensProvider {
const codeLenses: CodeLens[] = [];
let lineInPreviewBlock = -1;
let previewBlockMarkers = "";
for (let line = 0; line < textDocument.lineCount; line++) {
for (let line = textDocument.lineCount - 1; line >= 0; line = line - 1) {
if (token.isCancellationRequested) {
return null;
}
Expand All @@ -64,56 +64,83 @@ export class CodeLensProvider {
start: { line: line, character: 0 },
end: { line: line, character: text.length - 1 },
};

const codeLensLocation: Location = { uri: uri, range: codeLensRange };
const lineCodeLenses: CodeLens[] = [];
if (lineInPreviewBlock < 0) {
const match = /^<<<<<<<.+(<.*>)\[(tabby-[0-9|a-z|A-Z]{6})\]/g.exec(text);
const markers = match?.[1];
const editId = match?.[2];
const match = /^>>>>>>> (tabby-[0-9|a-z|A-Z]{6}) (\[.*\])/g.exec(text);
const editId = match?.[1];
const markers = match?.[2];
if (match && markers && editId) {
lineInPreviewBlock = 0;
previewBlockMarkers = markers;

lineCodeLenses.push({
range: codeLensRange,
command: {
title: "Accept",
command: "tabby/chat/edit/resolve",
arguments: [{ location: codeLensLocation, action: "accept" }],
},
data: {
type: codeLensType,
line: changesPreviewLineType.header,
},
});
lineInPreviewBlock = 0;
lineCodeLenses.push({
range: codeLensRange,
command: {
title: "Discard",
command: "tabby/chat/edit/resolve",
arguments: [{ location: codeLensLocation, action: "discard" }],
},
data: {
type: codeLensType,
line: changesPreviewLineType.header,
line: changesPreviewLineType.footer,
},
});
}
} else {
const match = /^>>>>>>>.+(<.*>)\[(tabby-[0-9|a-z|A-Z]{6})\]/g.exec(text);
const editId = match?.[2];
const match = /^<<<<<<< (tabby-[0-9|a-z|A-Z]{6})/g.exec(text);
const editId = match?.[1];
if (match && editId) {
lineInPreviewBlock = -1;
lineCodeLenses.push({
range: codeLensRange,
data: {
type: codeLensType,
line: changesPreviewLineType.footer,
},
});

if (previewBlockMarkers.includes(".")) {
lineCodeLenses.push({
range: codeLensRange,
command: {
title: "$(sync~spin) Tabby is working...",
command: " ",
},
data: {
type: codeLensType,
line: changesPreviewLineType.header,
},
});
lineCodeLenses.push({
range: codeLensRange,
command: {
title: "Cancel",
command: "tabby/chat/edit/resolve",
arguments: [{ location: codeLensLocation, action: "cancel" }],
},
data: {
type: codeLensType,
line: changesPreviewLineType.header,
},
});
} else if (!previewBlockMarkers.includes("x")) {
lineCodeLenses.push({
range: codeLensRange,
command: {
title: "$(check)Accept",
command: "tabby/chat/edit/resolve",
arguments: [{ location: codeLensLocation, action: "accept" }],
},
data: {
type: codeLensType,
line: changesPreviewLineType.header,
},
});
lineCodeLenses.push({
range: codeLensRange,
command: {
title: "$(remove-close)Discard",
command: "tabby/chat/edit/resolve",
arguments: [{ location: codeLensLocation, action: "discard" }],
},
data: {
type: codeLensType,
line: changesPreviewLineType.header,
},
});
}
} else {
lineInPreviewBlock++;
const marker = previewBlockMarkers[lineInPreviewBlock];
const marker = previewBlockMarkers[previewBlockMarkers.length - lineInPreviewBlock - 1];
let codeLens: CodeLens | undefined = undefined;
switch (marker) {
case "#":
Expand Down
2 changes: 1 addition & 1 deletion clients/tabby-agent/src/lsp/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ export type ChatEditResolveParams = {
/**
* The action to take for this edit.
*/
action: "accept" | "discard";
action: "accept" | "discard" | "cancel";
};

/**
Expand Down
3 changes: 2 additions & 1 deletion clients/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,10 @@
}
},
"scripts": {
"build": "tsc --noEmit && tsup --minify",
"build": "tsc -p ./tsconfig.build.json --noEmit && tsup --minify",
"watch": "tsc-watch --noEmit --onSuccess \"tsup\"",
"dev": "code --extensionDevelopmentPath=$PWD --disable-extensions && pnpm watch",
"vscode:dev": "pnpm run dev",
"dev:browser": "vscode-test-web --extensionDevelopmentPath=$PWD --browserType=chromium --port=3000 && pnpm watch",
"lint": "eslint --ext .ts ./src && prettier --check .",
"lint:fix": "eslint --fix --ext .ts ./src && prettier --write .",
Expand Down
Loading
Loading