From aec1434a0c4bf57798af10c11dae7d296dedf5d2 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 22 Sep 2024 18:56:00 -0700 Subject: [PATCH] case analysis on insert, delete, and replace --- .../void/src/ApprovalCodeLensProvider.ts | 132 ++++++++---------- extensions/void/src/extension.ts | 2 +- extensions/void/src/getDiffedLines.ts | 15 +- 3 files changed, 70 insertions(+), 79 deletions(-) diff --git a/extensions/void/src/ApprovalCodeLensProvider.ts b/extensions/void/src/ApprovalCodeLensProvider.ts index ddf2a2a9..80386fa1 100644 --- a/extensions/void/src/ApprovalCodeLensProvider.ts +++ b/extensions/void/src/ApprovalCodeLensProvider.ts @@ -1,32 +1,18 @@ import * as vscode from 'vscode'; +import { SuggestedEdit } from './getDiffedLines'; -export type SuggestedEdit = { - // start/end of current file - startLine: number; - endLine: number; - - // start/end of original file - originalStartLine: number, - originalEndLine: number, - - // original content (originalfile[originalStart...originalEnd]) - originalContent: string; - newContent: string; -} - - -// stored for later use +// each diff on the user's screen right now type DiffType = { - diffid: number, // unique id - range: vscode.Range, // current range - originalCode: string, // original code in case user wants to revert this + diffid: number, lenses: vscode.CodeLens[], + greenRange: vscode.Range, + originalCode: string, // If a revert happens, we replace the greenRange with this content. } // TODO in theory this should be disposed const greenDecoration = vscode.window.createTextEditorDecorationType({ backgroundColor: 'rgba(0 255 51 / 0.2)', - isWholeLine: true, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:... + isWholeLine: false, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:... }) export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { @@ -49,6 +35,7 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { return this._computedLensesOfDocument[docUriStr] } + // declared by us, registered with vscode.languages.registerCodeLensProvider() constructor() { // this acts as a useEffect. Every time text changes, clear the diffs in this editor vscode.workspace.onDidChangeTextDocument((e) => { @@ -66,6 +53,13 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { }) } + // used by us only + private refreshLenses = (editor: vscode.TextEditor, docUriStr: string) => { + editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.greenRange)) // refresh highlighting + this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) // recompute _computedLensesOfDocument (can optimize this later) + this._onDidChangeCodeLenses.fire() // fire event for vscode to refresh lenses + } + // used by us only public async addNewApprovals(editor: vscode.TextEditor, suggestedEdits: SuggestedEdit[]) { @@ -77,62 +71,55 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { if (!this._computedLensesOfDocument[docUriStr]) this._computedLensesOfDocument[docUriStr] = [] - // 0. create a diff for each suggested edit - const diffs: DiffType[] = [] - for (let suggestedEdit of suggestedEdits) { - // TODO we need to account for this case (deletions) - if (suggestedEdit.startLine > suggestedEdit.endLine) - continue - - const selectedRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine, Number.MAX_SAFE_INTEGER) - - // if any other codelens intersects with the selection, ignore this edit - for (let { range } of this._diffsOfDocument[docUriStr]) { - if (range.intersection(selectedRange)) { - vscode.window.showWarningMessage(`Changes have already been applied to this location. Please accept/reject them before applying new changes.`) - return // do not make any edits - } - } - - diffs.push({ diffid: this._diffidPool, range: selectedRange, originalCode: suggestedEdit.originalContent, lenses: [] }) - this._diffidPool += 1 - } - - this._diffsOfDocument[docUriStr].push(...diffs); - - // 1. apply each diff to the document + // 1. convert suggested edits (which are described using line numbers) into actual edits (described using vscode.Range, vscode.Uri) // must do this before adding codelenses or highlighting so that codelens and highlights will apply to the fresh code and not the old code // apply changes in reverse order so additions don't push down the line numbers of the next edit let workspaceEdit = new vscode.WorkspaceEdit(); - for (let i = suggestedEdits.length - 1; i > -1; i--) { + for (let i = suggestedEdits.length - 1; i > -1; i -= 1) { let suggestedEdit = suggestedEdits[i] - const originalRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine, Number.MAX_SAFE_INTEGER) - workspaceEdit.replace(docUri, originalRange, suggestedEdit.newContent); + + let greenRange: vscode.Range + + // INSERTIONS (e.g. {originalStartLine: 0, originalEndLine: -1}) + if (suggestedEdit.originalStartLine > suggestedEdit.originalEndLine) { + const originalPosition = new vscode.Position(suggestedEdit.originalStartLine, 0) + workspaceEdit.insert(docUri, originalPosition, suggestedEdit.newContent + '\n') // add back in the line we deleted when we made the startline->endline range go negative + greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine + 1, 0) + } + // DELETIONS + else if (suggestedEdit.startLine > suggestedEdit.endLine) { + const deleteRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine + 1, 0) + workspaceEdit.delete(docUri, deleteRange) + greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.startLine, 0) + suggestedEdit.originalContent += '\n' // add back in the line we deleted when we made the startline->endline range go negative + } + // REPLACEMENTS + else { + const originalRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine, Number.MAX_SAFE_INTEGER) + workspaceEdit.replace(docUri, originalRange, suggestedEdit.newContent) + greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine, Number.MAX_SAFE_INTEGER) + } + + this._diffsOfDocument[docUriStr].push({ + diffid: this._diffidPool, + greenRange: greenRange, + originalCode: suggestedEdit.originalContent, + lenses: [ + new vscode.CodeLens(greenRange, { title: 'Accept', command: 'void.approveDiff', arguments: [{ diffid: this._diffidPool }] }), + new vscode.CodeLens(greenRange, { title: 'Reject', command: 'void.discardDiff', arguments: [{ diffid: this._diffidPool }] }) + ] + }); + this._diffidPool += 1 } + this._weAreEditing = true await vscode.workspace.applyEdit(workspaceEdit) await vscode.workspace.save(docUri) this._weAreEditing = false - // 2. add the Yes/No codelenses - for (let diff of diffs) { - const { range, diffid, lenses: codeLenses } = diff - - let approveLens = new vscode.CodeLens(range, { title: 'Accept', command: 'void.approveDiff', arguments: [{ diffid }] }) - let discardLens = new vscode.CodeLens(range, { title: 'Reject', command: 'void.discardDiff', arguments: [{ diffid }] }) - - codeLenses.push(discardLens, approveLens) - } - - // 3. apply green highlighting for each (+) diff - editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.range)) - - // recompute _computedLensesOfDocument (can optimize this later) - this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) - // refresh - this._onDidChangeCodeLenses.fire() + this.refreshLenses(editor, docUriStr) console.log('diffs after added:', this._diffsOfDocument[docUriStr]) } @@ -156,14 +143,8 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { // remove this diff from the diffsOfDocument[docStr] (can change this behavior in future if add something like history) this._diffsOfDocument[docUriStr].splice(index, 1) - // clear the decoration in this diff's range - editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.range)) - - // recompute _computedLensesOfDocument (can optimize this later) - this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) - // refresh - this._onDidChangeCodeLenses.fire() + this.refreshLenses(editor, docUriStr) } @@ -183,13 +164,13 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { return } - const { range, lenses, originalCode } = this._diffsOfDocument[docUriStr][index] // do this before we splice and mess up index + const { greenRange: range, lenses, originalCode } = this._diffsOfDocument[docUriStr][index] // do this before we splice and mess up index // remove this diff from the diffsOfDocument[docStr] (can change this behavior in future if add something like history) this._diffsOfDocument[docUriStr].splice(index, 1) // clear the decoration in this diffs range - editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.range)) + editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.greenRange)) // REVERT THE CHANGE (this is the only part that's different from approveDiff) let workspaceEdit = new vscode.WorkspaceEdit(); @@ -199,10 +180,7 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider { await vscode.workspace.save(docUri) this._weAreEditing = false - // recompute _computedLensesOfDocument (can optimize this later) - this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) - // refresh - this._onDidChangeCodeLenses.fire() + this.refreshLenses(editor, docUriStr) } } diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index 4cd8ac26..01ea4aea 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import { WebviewMessage } from './shared_types'; import { CtrlKCodeLensProvider } from './CtrlKCodeLensProvider'; import { getDiffedLines } from './getDiffedLines'; -import { ApprovalCodeLensProvider, SuggestedEdit } from './ApprovalCodeLensProvider'; +import { ApprovalCodeLensProvider } from './ApprovalCodeLensProvider'; import { SidebarWebviewProvider } from './SidebarWebviewProvider'; import { ApiConfig } from './common/sendLLMMessage'; diff --git a/extensions/void/src/getDiffedLines.ts b/extensions/void/src/getDiffedLines.ts index 5b1fa194..56fa119e 100644 --- a/extensions/void/src/getDiffedLines.ts +++ b/extensions/void/src/getDiffedLines.ts @@ -1,5 +1,18 @@ import { diffLines, Change } from 'diff'; -import { SuggestedEdit } from './ApprovalCodeLensProvider'; + +export type SuggestedEdit = { + // start/end of current file + startLine: number; + endLine: number; + + // start/end of original file + originalStartLine: number, + originalEndLine: number, + + // original content (originalfile[originalStart...originalEnd]) + originalContent: string; + newContent: string; +} export function getDiffedLines(oldStr: string, newStr: string) { // an ordered list of every original line, line added to the new file, and line removed from the old file (order is unambiguous, think about it)