case analysis on insert, delete, and replace

This commit is contained in:
Andrew 2024-09-22 18:56:00 -07:00
parent 315c5a65f2
commit aec1434a0c
3 changed files with 70 additions and 79 deletions

View file

@ -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)
}
}

View file

@ -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';

View file

@ -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)