Skip to content

Commit

Permalink
Merge pull request trypear#55 from voideditor/issue-7
Browse files Browse the repository at this point in the history
Fixes trypear#7: case analysis on insert, delete, and replace
  • Loading branch information
andrewpareles authored Sep 23, 2024
2 parents 315c5a6 + aec1434 commit b83c5fc
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 77 deletions.
128 changes: 53 additions & 75 deletions extensions/void/src/ApprovalCodeLensProvider.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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) => {
Expand All @@ -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[]) {

Expand All @@ -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
// 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 -= 1) {
let suggestedEdit = suggestedEdits[i]

const selectedRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine, Number.MAX_SAFE_INTEGER)
let greenRange: vscode.Range

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

diffs.push({ diffid: this._diffidPool, range: selectedRange, originalCode: suggestedEdit.originalContent, lenses: [] })
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._diffsOfDocument[docUriStr].push(...diffs);

// 1. apply each diff to the document
// 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--) {
let suggestedEdit = suggestedEdits[i]
const originalRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine, Number.MAX_SAFE_INTEGER)
workspaceEdit.replace(docUri, originalRange, suggestedEdit.newContent);
}
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])
}
Expand All @@ -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)
}


Expand All @@ -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();
Expand All @@ -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)
}
}
2 changes: 1 addition & 1 deletion extensions/void/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
15 changes: 14 additions & 1 deletion extensions/void/src/getDiffedLines.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down

0 comments on commit b83c5fc

Please sign in to comment.