mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
Merge pull request #101 from voideditor/diff-selections
Refactor and fix diff highlighting
This commit is contained in:
commit
68c5ebb000
11 changed files with 691 additions and 306 deletions
13
extensions/void/package-lock.json
generated
13
extensions/void/package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.27.1",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"diff": "^7.0.0",
|
||||
"ollama": "^0.5.9",
|
||||
"openai": "^4.57.0"
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@types/diff": "^5.2.2",
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/mocha": "^10.0.8",
|
||||
"@types/node": "^22.5.1",
|
||||
|
|
@ -610,6 +612,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/diff-match-patch": {
|
||||
"version": "1.0.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
|
||||
"integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
|
|
@ -2154,6 +2162,11 @@
|
|||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/diff-match-patch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
||||
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
|
||||
},
|
||||
"node_modules/diff-sequences": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
|
||||
|
|
|
|||
|
|
@ -274,11 +274,11 @@
|
|||
"title": "Show Selection Lens"
|
||||
},
|
||||
{
|
||||
"command": "void.approveDiff",
|
||||
"command": "void.acceptDiff",
|
||||
"title": "Approve Diff"
|
||||
},
|
||||
{
|
||||
"command": "void.discardDiff",
|
||||
"command": "void.rejectDiff",
|
||||
"title": "Discard Diff"
|
||||
},
|
||||
{
|
||||
|
|
@ -357,6 +357,7 @@
|
|||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@types/diff": "^5.2.2",
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/mocha": "^10.0.8",
|
||||
"@types/node": "^22.5.1",
|
||||
|
|
@ -385,6 +386,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.27.1",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"ollama": "^0.5.9",
|
||||
"openai": "^4.57.0",
|
||||
"diff": "^7.0.0"
|
||||
|
|
|
|||
|
|
@ -1,186 +0,0 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { SuggestedEdit } from './getDiffedLines';
|
||||
|
||||
// each diff on the user's screen right now
|
||||
type DiffType = {
|
||||
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: 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 {
|
||||
|
||||
private _diffsOfDocument: { [docUriStr: string]: DiffType[] } = {};
|
||||
private _computedLensesOfDocument: { [docUriStr: string]: vscode.CodeLens[] } = {} // computed from diffsOfDocument[docUriStr].lenses
|
||||
private _diffidPool = 0
|
||||
|
||||
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>(); // signals a UI refresh on .fire() events
|
||||
|
||||
private _weAreEditing: boolean = false
|
||||
|
||||
// used internally by vscode
|
||||
public readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
|
||||
|
||||
|
||||
// used internally by vscode
|
||||
public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens[]> {
|
||||
const docUriStr = document.uri.toString()
|
||||
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) => {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor)
|
||||
return
|
||||
if (this._weAreEditing)
|
||||
return
|
||||
const docUri = editor.document.uri
|
||||
const docUriStr = docUri.toString()
|
||||
this._diffsOfDocument[docUriStr].splice(0) // clear diffs
|
||||
editor.setDecorations(greenDecoration, []) // clear decorations
|
||||
this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) // recompute
|
||||
this._onDidChangeCodeLenses.fire() // refresh
|
||||
})
|
||||
}
|
||||
|
||||
// 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[]) {
|
||||
|
||||
const docUri = editor.document.uri
|
||||
const docUriStr = docUri.toString()
|
||||
|
||||
if (!this._diffsOfDocument[docUriStr])
|
||||
this._diffsOfDocument[docUriStr] = []
|
||||
if (!this._computedLensesOfDocument[docUriStr])
|
||||
this._computedLensesOfDocument[docUriStr] = []
|
||||
|
||||
|
||||
// 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]
|
||||
|
||||
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
|
||||
|
||||
// refresh
|
||||
this.refreshLenses(editor, docUriStr)
|
||||
|
||||
console.log('diffs after added:', this._diffsOfDocument[docUriStr])
|
||||
}
|
||||
|
||||
// called on void.approveDiff
|
||||
public async approveDiff({ diffid }: { diffid: number }) {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor)
|
||||
return
|
||||
|
||||
const docUri = editor.document.uri
|
||||
const docUriStr = docUri.toString()
|
||||
|
||||
// get index of this diff in diffsOfDocument
|
||||
const index = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
||||
if (index === -1) {
|
||||
console.error('Error: DiffID could not be found: ', diffid, this._diffsOfDocument[docUriStr])
|
||||
return
|
||||
}
|
||||
|
||||
// remove this diff from the diffsOfDocument[docStr] (can change this behavior in future if add something like history)
|
||||
this._diffsOfDocument[docUriStr].splice(index, 1)
|
||||
|
||||
// refresh
|
||||
this.refreshLenses(editor, docUriStr)
|
||||
}
|
||||
|
||||
|
||||
// called on void.discardDiff
|
||||
public async discardDiff({ diffid }: { diffid: number }) {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor)
|
||||
return
|
||||
|
||||
const docUri = editor.document.uri
|
||||
const docUriStr = docUri.toString()
|
||||
|
||||
// get index of this diff in diffsOfDocument
|
||||
const index = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
||||
if (index === -1) {
|
||||
console.error('Void error: DiffID could not be found: ', diffid, this._diffsOfDocument[docUriStr])
|
||||
return
|
||||
}
|
||||
|
||||
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.greenRange))
|
||||
|
||||
// REVERT THE CHANGE (this is the only part that's different from approveDiff)
|
||||
let workspaceEdit = new vscode.WorkspaceEdit();
|
||||
workspaceEdit.replace(docUri, range, originalCode);
|
||||
this._weAreEditing = true
|
||||
await vscode.workspace.applyEdit(workspaceEdit)
|
||||
await vscode.workspace.save(docUri)
|
||||
this._weAreEditing = false
|
||||
|
||||
// refresh
|
||||
this.refreshLenses(editor, docUriStr)
|
||||
}
|
||||
}
|
||||
315
extensions/void/src/DisplayChangesProvider.ts
Normal file
315
extensions/void/src/DisplayChangesProvider.ts
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { findDiffs } from './findDiffs';
|
||||
import { Diff, BaseDiffArea, BaseDiff, DiffArea } from './shared_types';
|
||||
|
||||
|
||||
|
||||
// TODO in theory this should be disposed
|
||||
const greenDecoration = vscode.window.createTextEditorDecorationType({
|
||||
backgroundColor: 'rgba(0 255 51 / 0.2)',
|
||||
isWholeLine: false, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:...
|
||||
})
|
||||
|
||||
|
||||
// responsible for displaying diffs and showing accept/reject buttons
|
||||
export class DisplayChangesProvider implements vscode.CodeLensProvider {
|
||||
|
||||
private _diffAreasOfDocument: { [docUriStr: string]: DiffArea[] } = {}
|
||||
private _diffsOfDocument: { [docUriStr: string]: Diff[] } = {}
|
||||
|
||||
private _diffareaidPool = 0
|
||||
private _diffidPool = 0
|
||||
private _weAreEditing: boolean = false
|
||||
|
||||
// used internally by vscode
|
||||
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>(); // signals a UI refresh on .fire() events
|
||||
public readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
|
||||
|
||||
// used internally by vscode
|
||||
public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens[]> {
|
||||
const docUriStr = document.uri.toString()
|
||||
return this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses)
|
||||
}
|
||||
|
||||
// declared by us, registered with vscode.languages.registerCodeLensProvider()
|
||||
constructor() {
|
||||
|
||||
console.log('Creating DisplayChangesProvider')
|
||||
|
||||
// this acts as a useEffect. Every time text changes, clear the diffs in this editor
|
||||
vscode.workspace.onDidChangeTextDocument((e) => {
|
||||
|
||||
const editor = vscode.window.activeTextEditor
|
||||
|
||||
if (!editor)
|
||||
return
|
||||
if (this._weAreEditing)
|
||||
return
|
||||
|
||||
const docUri = editor.document.uri
|
||||
const docUriStr = docUri.toString()
|
||||
const diffAreas = this._diffAreasOfDocument[docUriStr] || []
|
||||
|
||||
// loop through each change
|
||||
for (const change of e.contentChanges) {
|
||||
|
||||
// here, `change.range` is the range of the original file that gets replaced with `change.text`
|
||||
|
||||
|
||||
// compute net number of newlines lines that were added/removed
|
||||
const numNewLines = (change.text.match(/\n/g) || []).length
|
||||
const numLineDeletions = change.range.end.line - change.range.start.line
|
||||
const deltaNewlines = numNewLines - numLineDeletions
|
||||
|
||||
// compute overlap with each diffArea and shrink/elongate the diffArea accordingly
|
||||
for (const diffArea of diffAreas) {
|
||||
|
||||
// if the change is fully within the diffArea, elongate it by the delta amount of newlines
|
||||
if (change.range.start.line >= diffArea.startLine && change.range.end.line <= diffArea.endLine) {
|
||||
diffArea.endLine += deltaNewlines
|
||||
}
|
||||
// check if the `diffArea` was fully deleted and remove it if so
|
||||
if (diffArea.startLine > diffArea.endLine) {
|
||||
//remove it
|
||||
const index = diffAreas.findIndex(da => da === diffArea)
|
||||
diffAreas.splice(index, 1)
|
||||
}
|
||||
|
||||
// TODO handle other cases where eg. the change overlaps many diffAreas
|
||||
}
|
||||
|
||||
|
||||
// if a diffArea is below the last character of the change, shift the diffArea up/down by the delta amount of newlines
|
||||
for (const diffArea of diffAreas) {
|
||||
if (diffArea.startLine > change.range.end.line) {
|
||||
diffArea.startLine += deltaNewlines
|
||||
diffArea.endLine += deltaNewlines
|
||||
}
|
||||
}
|
||||
|
||||
// TODO merge any diffAreas if they overlap with each other as a result from the shift
|
||||
|
||||
}
|
||||
|
||||
// refresh the diffAreas
|
||||
this.refreshDiffAreas(docUri)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// used by us only
|
||||
public addDiffArea(uri: vscode.Uri, diffArea: BaseDiffArea) {
|
||||
|
||||
const uriStr = uri.toString()
|
||||
|
||||
// make sure array is defined
|
||||
if (!this._diffAreasOfDocument[uriStr])
|
||||
this._diffAreasOfDocument[uriStr] = []
|
||||
|
||||
// remove all diffAreas that the new `diffArea` is overlapping with
|
||||
this._diffAreasOfDocument[uriStr] = this._diffAreasOfDocument[uriStr].filter(da => {
|
||||
|
||||
const noOverlap = da.startLine > diffArea.endLine || da.endLine < diffArea.startLine
|
||||
|
||||
if (!noOverlap) return false
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// add `diffArea` to storage
|
||||
this._diffAreasOfDocument[uriStr].push({
|
||||
...diffArea,
|
||||
diffareaid: this._diffareaidPool
|
||||
})
|
||||
this._diffareaidPool += 1
|
||||
}
|
||||
|
||||
|
||||
// used by us only
|
||||
public refreshDiffAreas(docUri: vscode.Uri) {
|
||||
|
||||
const editor = vscode.window.activeTextEditor // TODO the editor should be that of `docUri` and not necessarily the current editor
|
||||
if (!editor) {
|
||||
console.log('Error: No active editor!')
|
||||
return;
|
||||
}
|
||||
|
||||
const docUriStr = docUri.toString()
|
||||
const diffAreas = this._diffAreasOfDocument[docUriStr] || []
|
||||
|
||||
// reset all diffs (we update them below)
|
||||
this._diffsOfDocument[docUriStr] = []
|
||||
|
||||
// for each diffArea
|
||||
for (const diffArea of diffAreas) {
|
||||
|
||||
// get code inside of diffArea
|
||||
const currentCode = editor.document.getText(new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER)).replace(/\r\n/g, '\n')
|
||||
|
||||
// compute the diffs
|
||||
const diffs = findDiffs(diffArea.originalCode, currentCode)
|
||||
|
||||
// print diffs
|
||||
console.log('!CODEBefore:', JSON.stringify(diffArea.originalCode))
|
||||
console.log('!CODEAfter:', JSON.stringify(currentCode))
|
||||
|
||||
// add the diffs to `this._diffsOfDocument[docUriStr]`
|
||||
this.addDiffs(editor.document.uri, diffs, diffArea)
|
||||
|
||||
for (const diff of this._diffsOfDocument[docUriStr]) {
|
||||
console.log('------------')
|
||||
console.log('deletedCode:', JSON.stringify(diff.deletedCode))
|
||||
console.log('insertedCode:', JSON.stringify(diff.insertedCode))
|
||||
console.log('deletedRange:', diff.deletedRange.start.line, diff.deletedRange.end.line,)
|
||||
console.log('insertedRange:', diff.insertedRange.start.line, diff.insertedRange.end.line,)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// update green highlighting
|
||||
editor.setDecorations(
|
||||
greenDecoration,
|
||||
(this._diffsOfDocument[docUriStr]
|
||||
.filter(diff => diff.insertedRange !== undefined)
|
||||
.map(diff => diff.insertedRange)
|
||||
)
|
||||
);
|
||||
|
||||
// TODO update red highlighting
|
||||
// this._diffsOfDocument[docUriStr].map(diff => diff.deletedCode)
|
||||
|
||||
// update code lenses
|
||||
this._onDidChangeCodeLenses.fire()
|
||||
|
||||
}
|
||||
|
||||
// used by us only
|
||||
public addDiffs(docUri: vscode.Uri, diffs: BaseDiff[], diffArea: DiffArea) {
|
||||
|
||||
const docUriStr = docUri.toString()
|
||||
|
||||
// if no diffs, set diffs to []
|
||||
if (!this._diffsOfDocument[docUriStr])
|
||||
this._diffsOfDocument[docUriStr] = []
|
||||
|
||||
// add each diff and its codelens to the document
|
||||
for (let i = diffs.length - 1; i > -1; i -= 1) {
|
||||
let suggestedDiff = diffs[i]
|
||||
|
||||
this._diffsOfDocument[docUriStr].push({
|
||||
...suggestedDiff,
|
||||
diffid: this._diffidPool,
|
||||
// originalCode: suggestedDiff.deletedText,
|
||||
lenses: [
|
||||
new vscode.CodeLens(suggestedDiff.insertedRange, { title: 'Accept', command: 'void.acceptDiff', arguments: [{ diffid: this._diffidPool, diffareaid: diffArea.diffareaid }] }),
|
||||
new vscode.CodeLens(suggestedDiff.insertedRange, { title: 'Reject', command: 'void.rejectDiff', arguments: [{ diffid: this._diffidPool, diffareaid: diffArea.diffareaid }] })
|
||||
]
|
||||
});
|
||||
this._diffidPool += 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// called on void.acceptDiff
|
||||
public async acceptDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor)
|
||||
return
|
||||
|
||||
// get document uri
|
||||
const docUri = editor.document.uri
|
||||
const docUriStr = docUri.toString()
|
||||
|
||||
// get relevant diff
|
||||
// TODO speed up with hashmap
|
||||
const diffIdx = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
||||
if (diffIdx === -1) {
|
||||
console.error('Error: DiffID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return;
|
||||
}
|
||||
|
||||
// get relevant diffArea
|
||||
const diffareaIdx = this._diffAreasOfDocument[docUriStr].findIndex(diff => diff.diffareaid === diffareaid);
|
||||
if (diffareaIdx === -1) {
|
||||
console.error('Error: DiffAreaID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return;
|
||||
}
|
||||
|
||||
const diff = this._diffsOfDocument[docUriStr][diffIdx]
|
||||
const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
|
||||
|
||||
// replace `originalCode[diff.deletedRange]` with diff.insertedCode
|
||||
// TODO add a history event to undo this change
|
||||
const originalLines = diffArea.originalCode.split('\n');
|
||||
const relativeStart = diff.deletedRange.start.line - diffArea.originalStartLine
|
||||
const relativeEnd = diff.deletedRange.end.line - diffArea.originalStartLine
|
||||
diffArea.originalCode = [
|
||||
...originalLines.slice(0, relativeStart), // lines before the deleted range
|
||||
...diff.insertedCode.split('\n'), // inserted lines
|
||||
...originalLines.slice(relativeEnd + 1) // lines after the deleted range
|
||||
].join('\n')
|
||||
|
||||
// if the diffArea has no changes, remove it
|
||||
const currentDiffAreaCode = editor.document.getText()
|
||||
.replace(/\r\n/g, '\n')
|
||||
.split('\n')
|
||||
.slice(diffArea.startLine, diffArea.endLine + 1)
|
||||
.join('\n')
|
||||
if (diffArea.originalCode === currentDiffAreaCode) { // if the currentDiffAreaCode === diffArea.originalCode, remove the diffArea
|
||||
const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
|
||||
this._diffAreasOfDocument[docUriStr].splice(index, 1)
|
||||
}
|
||||
|
||||
// refresh the diff area
|
||||
this.refreshDiffAreas(docUri)
|
||||
}
|
||||
|
||||
|
||||
// called on void.rejectDiff
|
||||
public async rejectDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor)
|
||||
return
|
||||
|
||||
// get document uri
|
||||
const docUri = editor.document.uri
|
||||
const docUriStr = docUri.toString()
|
||||
|
||||
// get relevant diff
|
||||
// TODO speed up with hashmap
|
||||
const diffIdx = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
||||
if (diffIdx === -1) {
|
||||
console.error('Error: DiffID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return;
|
||||
}
|
||||
|
||||
// get relevant diffArea
|
||||
const diffareaIdx = this._diffAreasOfDocument[docUriStr].findIndex(diff => diff.diffareaid === diffareaid);
|
||||
if (diffareaIdx === -1) {
|
||||
console.error('Error: DiffAreaID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return;
|
||||
}
|
||||
|
||||
const diff = this._diffsOfDocument[docUriStr][diffIdx]
|
||||
const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
|
||||
|
||||
// replace `editorCode[diff.insertedRange]` with diff.deletedCode
|
||||
const workspaceEdit = new vscode.WorkspaceEdit();
|
||||
workspaceEdit.replace(docUri, diff.insertedRange, diff.deletedCode)
|
||||
this._weAreEditing = true
|
||||
await vscode.workspace.applyEdit(workspaceEdit)
|
||||
this._weAreEditing = false
|
||||
|
||||
// if the diffArea has no changes, remove it
|
||||
const currentDiffAreaCode = editor.document.getText()
|
||||
.replace(/\r\n/g, '\n')
|
||||
.split('\n')
|
||||
.slice(diffArea.startLine, diffArea.endLine + 1)
|
||||
.join('\n')
|
||||
if (diffArea.originalCode === currentDiffAreaCode) { // if the currentDiffAreaCode === diffArea.originalCode, remove the diffArea
|
||||
const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
|
||||
this._diffAreasOfDocument[docUriStr].splice(index, 1)
|
||||
}
|
||||
|
||||
// refresh the diff area
|
||||
this.refreshDiffAreas(docUri)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { ChatThreads, WebviewMessage } from './shared_types';
|
||||
import { getDiffedLines } from './getDiffedLines';
|
||||
import { ApprovalCodeLensProvider } from './ApprovalCodeLensProvider';
|
||||
import { DisplayChangesProvider } from './DisplayChangesProvider';
|
||||
import { BaseDiffArea, ChatThreads, WebviewMessage } from './shared_types';
|
||||
import { SidebarWebviewProvider } from './SidebarWebviewProvider';
|
||||
import { ApiConfig } from './common/sendLLMMessage';
|
||||
|
||||
const readFileContentOfUri = async (uri: vscode.Uri) => {
|
||||
return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8').replace(/\r\n/g, '\n'); // must remove windows \r or every line will appear different because of it
|
||||
return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8')
|
||||
.replace(/\r\n/g, '\n') // replace windows \r\n with \n
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -84,22 +84,22 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
);
|
||||
|
||||
// 3. Show an approve/reject codelens above each change
|
||||
const approvalCodeLensProvider = new ApprovalCodeLensProvider();
|
||||
context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', approvalCodeLensProvider));
|
||||
const displayChangesProvider = new DisplayChangesProvider();
|
||||
context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', displayChangesProvider));
|
||||
|
||||
// 4. Add approve/reject commands
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.approveDiff', async (params) => {
|
||||
approvalCodeLensProvider.approveDiff(params)
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.acceptDiff', async (params) => {
|
||||
displayChangesProvider.acceptDiff(params)
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.discardDiff', async (params) => {
|
||||
approvalCodeLensProvider.discardDiff(params)
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.rejectDiff', async (params) => {
|
||||
displayChangesProvider.rejectDiff(params)
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.openSettings', async () => {
|
||||
vscode.commands.executeCommand('workbench.action.openSettings', '@ext:void.void');
|
||||
}));
|
||||
|
||||
// 5.
|
||||
// 5. Receive messages from sidebar
|
||||
webviewProvider.webview.then(
|
||||
webview => {
|
||||
|
||||
|
|
@ -133,17 +133,38 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
// send contents to webview
|
||||
webview.postMessage({ type: 'files', files, } satisfies WebviewMessage)
|
||||
|
||||
}
|
||||
else if (m.type === 'applyCode') {
|
||||
} else if (m.type === 'applyChanges') {
|
||||
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) {
|
||||
vscode.window.showInformationMessage('No active editor!')
|
||||
return
|
||||
}
|
||||
const oldContents = await readFileContentOfUri(editor.document.uri)
|
||||
const suggestedEdits = getDiffedLines(oldContents, m.code)
|
||||
await approvalCodeLensProvider.addNewApprovals(editor, suggestedEdits)
|
||||
|
||||
// create an area to show diffs
|
||||
const diffArea: BaseDiffArea = {
|
||||
startLine: 0, // in ctrl+L the start and end lines are the full document
|
||||
endLine: editor.document.lineCount,
|
||||
originalStartLine: 0,
|
||||
originalEndLine: editor.document.lineCount,
|
||||
originalCode: await readFileContentOfUri(editor.document.uri),
|
||||
}
|
||||
displayChangesProvider.addDiffArea(editor.document.uri, diffArea)
|
||||
|
||||
|
||||
// write new code `m.code` to the document
|
||||
// TODO update like this:
|
||||
// this._weAreEditing = true
|
||||
// await vscode.workspace.applyEdit(workspaceEdit)
|
||||
// await vscode.workspace.save(docUri)
|
||||
// this._weAreEditing = false
|
||||
await editor.edit(editBuilder => {
|
||||
editBuilder.replace(new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER), m.code);
|
||||
});
|
||||
|
||||
// rediff the changes based on the diffAreas
|
||||
displayChangesProvider.refreshDiffAreas(editor.document.uri)
|
||||
|
||||
}
|
||||
else if (m.type === 'getApiConfig') {
|
||||
const apiConfig = getApiConfig()
|
||||
|
|
|
|||
277
extensions/void/src/findDiffs.ts
Normal file
277
extensions/void/src/findDiffs.ts
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
// import { diffLines, Change } from 'diff';
|
||||
import { BaseDiff } from './shared_types';
|
||||
|
||||
import { diff_match_patch } from 'diff-match-patch';
|
||||
|
||||
|
||||
const diffLines = (text1: string, text2: string) => {
|
||||
var dmp = new diff_match_patch();
|
||||
var a = dmp.diff_linesToChars_(text1, text2);
|
||||
var lineText1 = a.chars1;
|
||||
var lineText2 = a.chars2;
|
||||
var lineArray = a.lineArray;
|
||||
var diffs = dmp.diff_main(lineText1, lineText2, false);
|
||||
dmp.diff_charsToLines_(diffs, lineArray);
|
||||
// dmp.diff_cleanupSemantic(diffs);
|
||||
return diffs;
|
||||
}
|
||||
|
||||
|
||||
// TODO use a better diff algorithm
|
||||
export const findDiffs = (oldText: string, newText: string): BaseDiff[] => {
|
||||
|
||||
const diffs = diffLines(oldText, newText);
|
||||
|
||||
const blocks: BaseDiff[] = [];
|
||||
let reprBlock: string[] = [];
|
||||
let deletedBlock: string[] = [];
|
||||
let insertedBlock: string[] = [];
|
||||
let insertedLine = 0;
|
||||
let deletedLine = 0;
|
||||
let insertedStart = 0;
|
||||
let deletedStart = 0;
|
||||
|
||||
diffs.forEach(([operation, text]) => {
|
||||
|
||||
const lines = text.split('\n');
|
||||
|
||||
switch (operation) {
|
||||
|
||||
// insertion
|
||||
case 1:
|
||||
if (reprBlock.length === 0) { reprBlock.push('@@@@'); }
|
||||
if (insertedBlock.length === 0) insertedStart = insertedLine;
|
||||
insertedLine += lines.length - 1; // Update only the line count for new text
|
||||
insertedBlock.push(text);
|
||||
reprBlock.push(lines.map(line => `+ ${line}`).join('\n'));
|
||||
break;
|
||||
|
||||
// deletion
|
||||
case -1:
|
||||
if (reprBlock.length === 0) { reprBlock.push('@@@@'); }
|
||||
if (deletedBlock.length === 0) deletedStart = deletedLine;
|
||||
deletedLine += lines.length - 1; // Update only the line count for old text
|
||||
deletedBlock.push(text);
|
||||
reprBlock.push(lines.map(line => `- ${line}`).join('\n'));
|
||||
break;
|
||||
|
||||
// no change
|
||||
case 0:
|
||||
// If we have a pending block, add it to the blocks array
|
||||
if (insertedBlock.length > 0 || deletedBlock.length > 0) {
|
||||
blocks.push({
|
||||
code: reprBlock.join(''),
|
||||
deletedCode: deletedBlock.join(''),
|
||||
insertedCode: insertedBlock.join(''),
|
||||
deletedRange: new vscode.Range(deletedStart, 0, deletedLine, Number.MAX_SAFE_INTEGER),
|
||||
insertedRange: new vscode.Range(insertedStart, 0, insertedLine, Number.MAX_SAFE_INTEGER),
|
||||
});
|
||||
}
|
||||
|
||||
// Reset the block variables
|
||||
reprBlock = [];
|
||||
deletedBlock = [];
|
||||
insertedBlock = [];
|
||||
|
||||
// Update line counts for unchanged text
|
||||
insertedLine += lines.length - 1;
|
||||
deletedLine += lines.length - 1;
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Add any remaining blocks after the loop ends
|
||||
if (insertedBlock.length > 0 || deletedBlock.length > 0) {
|
||||
blocks.push({
|
||||
code: reprBlock.join(''),
|
||||
deletedCode: deletedBlock.join(''),
|
||||
insertedCode: insertedBlock.join(''),
|
||||
deletedRange: new vscode.Range(deletedStart, 0, deletedLine, Number.MAX_SAFE_INTEGER),
|
||||
insertedRange: new vscode.Range(insertedStart, 0, insertedLine, Number.MAX_SAFE_INTEGER),
|
||||
});
|
||||
}
|
||||
|
||||
return blocks;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// export const findDiffs = (oldText: string, newText: string): DiffBlock[] => {
|
||||
|
||||
// const diffs = diffLines(oldText, newText);
|
||||
|
||||
// const blocks: DiffBlock[] = [];
|
||||
|
||||
// let reprBlock: string[] = [];
|
||||
// let deletedBlock: string[] = [];
|
||||
// let insertedBlock: string[] = [];
|
||||
|
||||
// let insertedEnd = 0;
|
||||
// let deletedEnd = 0;
|
||||
// let insertedStart = 0;
|
||||
// let deletedStart = 0;
|
||||
|
||||
// diffs.forEach(part => {
|
||||
|
||||
// part.count = part.count ?? 0
|
||||
|
||||
// // if the part is an addition or deletion, add it to the current block
|
||||
// if (part.added || part.removed) {
|
||||
// if (reprBlock.length === 0) { reprBlock.push('@@@@'); }
|
||||
// if (part.added) {
|
||||
// if (insertedBlock.length === 0) insertedStart = insertedEnd;
|
||||
// insertedEnd += part.count
|
||||
// insertedBlock.push(part.value);
|
||||
// reprBlock.push(part.value.split('\n').map(line => `+ ${line}`).join('\n'));
|
||||
// }
|
||||
// if (part.removed) {
|
||||
// if (deletedBlock.length === 0) deletedStart = deletedEnd;
|
||||
// deletedEnd += part.count
|
||||
// deletedBlock.push(part.value);
|
||||
// reprBlock.push(part.value.split('\n').map(line => `- ${line}`).join('\n'));
|
||||
// }
|
||||
// }
|
||||
|
||||
// // if the part is unchanged, finalize the block and add it to the array
|
||||
// else {
|
||||
// // if the block is not null, add it to the array
|
||||
// if (insertedBlock.length > 0 || deletedBlock.length > 0) {
|
||||
// blocks.push({
|
||||
// code: reprBlock.join('\n'),
|
||||
// deletedCode: deletedBlock.join(''),
|
||||
// insertedCode: insertedBlock.join(''),
|
||||
// deletedRange: new vscode.Range(deletedStart, 0, deletedEnd, Number.MAX_SAFE_INTEGER),
|
||||
// insertedRange: new vscode.Range(insertedStart, 0, insertedEnd, Number.MAX_SAFE_INTEGER),
|
||||
// });
|
||||
// }
|
||||
|
||||
// // update block variables
|
||||
// reprBlock = [];
|
||||
// deletedBlock = [];
|
||||
// insertedBlock = [];
|
||||
// insertedEnd += part.count;
|
||||
// deletedEnd += part.count;
|
||||
|
||||
// }
|
||||
|
||||
// })
|
||||
|
||||
// // finally, add the last block to the array
|
||||
// if (insertedBlock.length > 0 || deletedBlock.length > 0) {
|
||||
// blocks.push({
|
||||
// code: reprBlock.join('\n'),
|
||||
// deletedCode: deletedBlock.join(''),
|
||||
// insertedCode: insertedBlock.join(''),
|
||||
// deletedRange: new vscode.Range(deletedStart, 0, deletedEnd, Number.MAX_SAFE_INTEGER),
|
||||
// insertedRange: new vscode.Range(insertedStart, 0, insertedEnd, Number.MAX_SAFE_INTEGER),
|
||||
// });
|
||||
// }
|
||||
|
||||
// return blocks;
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// import { diffLines, Change } from 'diff';
|
||||
|
||||
// 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)
|
||||
// const lineByLineChanges: Change[] = diffLines(oldStr, newStr);
|
||||
// console.debug('Line by line changes', lineByLineChanges)
|
||||
|
||||
// lineByLineChanges.push({ value: '' }) // add a dummy so we flush any streaks we haven't yet at the very end (!line.added && !line.removed)
|
||||
|
||||
// let oldFileLineNum: number = 0;
|
||||
// let newFileLineNum: number = 0;
|
||||
|
||||
// let streakStartInNewFile: number | undefined = undefined
|
||||
// let streakStartInOldFile: number | undefined = undefined
|
||||
|
||||
// let oldStrLines = oldStr.split('\n')
|
||||
// let newStrLines = newStr.split('\n')
|
||||
|
||||
// const replacements: SuggestedEdit[] = []
|
||||
|
||||
// for (let line of lineByLineChanges) {
|
||||
// // no change on this line
|
||||
// if (!line.added && !line.removed) {
|
||||
// // if we were on a streak, add it
|
||||
// if (streakStartInNewFile !== undefined) {
|
||||
|
||||
// const startLine = streakStartInNewFile
|
||||
// const endLine = newFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
|
||||
// const newContent = newStrLines.slice(startLine, endLine + 1).join('\n')
|
||||
|
||||
// const originalStartLine = streakStartInOldFile!
|
||||
// const originalEndLine = oldFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
|
||||
// const originalContent = oldStrLines.slice(originalStartLine, originalEndLine + 1).join('\n')
|
||||
|
||||
// const replacement: SuggestedEdit = { startLine, endLine, newContent, originalStartLine, originalEndLine, originalContent }
|
||||
|
||||
// replacements.push(replacement)
|
||||
// streakStartInNewFile = undefined
|
||||
// streakStartInOldFile = undefined
|
||||
// }
|
||||
|
||||
// oldFileLineNum += line.count ?? 0;
|
||||
// newFileLineNum += line.count ?? 0;
|
||||
// }
|
||||
|
||||
|
||||
// // line was removed from old file
|
||||
// else if (line.removed) {
|
||||
|
||||
// // if we weren't on a streak, start one on this current line num
|
||||
// if (streakStartInNewFile === undefined) {
|
||||
// streakStartInNewFile = newFileLineNum
|
||||
// streakStartInOldFile = oldFileLineNum
|
||||
// }
|
||||
|
||||
// oldFileLineNum += line.count ?? 0 // we processed the line so add 1
|
||||
// }
|
||||
|
||||
// // line was added to new file
|
||||
// else if (line.added) {
|
||||
|
||||
// // if we weren't on a streak, start one on this current line num
|
||||
// if (streakStartInNewFile === undefined) {
|
||||
// streakStartInNewFile = newFileLineNum
|
||||
// streakStartInOldFile = oldFileLineNum
|
||||
// }
|
||||
|
||||
// newFileLineNum += line.count ?? 0; // we processed the line so add 1
|
||||
// }
|
||||
|
||||
// } // end for
|
||||
|
||||
// console.debug('Replacements', replacements)
|
||||
|
||||
// return replacements
|
||||
|
||||
// }
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
import { diffLines, Change } from 'diff';
|
||||
|
||||
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)
|
||||
const lineByLineChanges: Change[] = diffLines(oldStr, newStr);
|
||||
console.debug('Line by line changes', lineByLineChanges)
|
||||
|
||||
lineByLineChanges.push({ value: '' }) // add a dummy so we flush any streaks we haven't yet at the very end (!line.added && !line.removed)
|
||||
|
||||
let oldFileLineNum: number = 0;
|
||||
let newFileLineNum: number = 0;
|
||||
|
||||
let streakStartInNewFile: number | undefined = undefined
|
||||
let streakStartInOldFile: number | undefined = undefined
|
||||
|
||||
let oldStrLines = oldStr.split('\n')
|
||||
let newStrLines = newStr.split('\n')
|
||||
|
||||
const replacements: SuggestedEdit[] = []
|
||||
|
||||
for (let line of lineByLineChanges) {
|
||||
// no change on this line
|
||||
if (!line.added && !line.removed) {
|
||||
// if we were on a streak, add it
|
||||
if (streakStartInNewFile !== undefined) {
|
||||
|
||||
const startLine = streakStartInNewFile
|
||||
const endLine = newFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
|
||||
const newContent = newStrLines.slice(startLine, endLine + 1).join('\n')
|
||||
|
||||
const originalStartLine = streakStartInOldFile!
|
||||
const originalEndLine = oldFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
|
||||
const originalContent = oldStrLines.slice(originalStartLine, originalEndLine + 1).join('\n')
|
||||
|
||||
const replacement: SuggestedEdit = { startLine, endLine, newContent, originalStartLine, originalEndLine, originalContent }
|
||||
|
||||
replacements.push(replacement)
|
||||
streakStartInNewFile = undefined
|
||||
streakStartInOldFile = undefined
|
||||
}
|
||||
|
||||
oldFileLineNum += line.count ?? 0;
|
||||
newFileLineNum += line.count ?? 0;
|
||||
}
|
||||
|
||||
|
||||
// line was removed from old file
|
||||
else if (line.removed) {
|
||||
|
||||
// if we weren't on a streak, start one on this current line num
|
||||
if (streakStartInNewFile === undefined) {
|
||||
streakStartInNewFile = newFileLineNum
|
||||
streakStartInOldFile = oldFileLineNum
|
||||
}
|
||||
|
||||
oldFileLineNum += line.count ?? 0 // we processed the line so add 1
|
||||
}
|
||||
|
||||
// line was added to new file
|
||||
else if (line.added) {
|
||||
|
||||
// if we weren't on a streak, start one on this current line num
|
||||
if (streakStartInNewFile === undefined) {
|
||||
streakStartInNewFile = newFileLineNum
|
||||
streakStartInOldFile = oldFileLineNum
|
||||
}
|
||||
|
||||
newFileLineNum += line.count ?? 0; // we processed the line so add 1
|
||||
}
|
||||
|
||||
} // end for
|
||||
|
||||
console.debug('Replacements', replacements)
|
||||
|
||||
return replacements
|
||||
|
||||
}
|
||||
|
|
@ -2,18 +2,49 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { ApiConfig } from './common/sendLLMMessage';
|
||||
|
||||
|
||||
|
||||
// a selection is a frozen snapshot
|
||||
type Selection = { selectionStr: string, selectionRange: vscode.Range, filePath: vscode.Uri }
|
||||
type CodeSelection = { selectionStr: string, selectionRange: vscode.Range, filePath: vscode.Uri }
|
||||
|
||||
type File = { filepath: vscode.Uri, content: string }
|
||||
|
||||
// an area that is currently being diffed
|
||||
type BaseDiffArea = {
|
||||
// use `startLine` and `endLine` instead of `range` for mutibility
|
||||
// bounds are relative to the file, inclusive
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
originalStartLine: number,
|
||||
originalEndLine: number,
|
||||
originalCode: string, // the original chunk of code (not necessarily the whole file)
|
||||
// `newCode: string,` is not included because it is the code in the actual file, `document.text()[startline: endLine + 1]`
|
||||
}
|
||||
|
||||
type DiffArea = BaseDiffArea & { diffareaid: number }
|
||||
|
||||
// the return type of diff creator
|
||||
type BaseDiff = {
|
||||
code: string; // representation of the diff in text
|
||||
deletedRange: vscode.Range; // relative to the file, inclusive
|
||||
insertedRange: vscode.Range;
|
||||
deletedCode: string;
|
||||
insertedCode: string;
|
||||
}
|
||||
|
||||
// each diff on the user's screen
|
||||
type Diff = {
|
||||
diffid: number,
|
||||
lenses: vscode.CodeLens[],
|
||||
} & BaseDiff
|
||||
|
||||
type WebviewMessage = (
|
||||
|
||||
// editor -> sidebar
|
||||
| { type: 'ctrl+l', selection: Selection } // user presses ctrl+l in the editor
|
||||
| { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor
|
||||
|
||||
// sidebar -> editor
|
||||
| { type: 'applyCode', code: string } // user clicks "apply" in the sidebar
|
||||
| { type: 'applyChanges', code: string } // user clicks "apply" in the sidebar
|
||||
|
||||
// sidebar -> editor
|
||||
| { type: 'requestFiles', filepaths: vscode.Uri[] }
|
||||
|
|
@ -44,6 +75,7 @@ type WebviewMessage = (
|
|||
|
||||
)
|
||||
|
||||
|
||||
type Command = WebviewMessage['type']
|
||||
|
||||
type ChatThreads = {
|
||||
|
|
@ -59,7 +91,7 @@ type ChatMessage =
|
|||
role: "user";
|
||||
content: string; // content sent to the llm
|
||||
displayContent: string; // content displayed to user
|
||||
selection: Selection | null; // the user's selection
|
||||
selection: CodeSelection | null; // the user's selection
|
||||
files: vscode.Uri[]; // the files sent in the message
|
||||
}
|
||||
| {
|
||||
|
|
@ -69,7 +101,9 @@ type ChatMessage =
|
|||
}
|
||||
|
||||
export {
|
||||
Selection,
|
||||
BaseDiff, BaseDiffArea,
|
||||
Diff, DiffArea,
|
||||
CodeSelection,
|
||||
File,
|
||||
WebviewMessage,
|
||||
Command,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect, useRef, useCallback, FormEvent } from "react"
|
||||
import { ApiConfig, sendLLMMessage } from "../common/sendLLMMessage"
|
||||
import { ChatMessage, File, Selection, WebviewMessage } from "../shared_types"
|
||||
import { File, CodeSelection, WebviewMessage, ChatMessage } from "../shared_types"
|
||||
import { awaitVSCodeResponse, getVSCodeAPI, resolveAwaitingVSCodeResponse } from "./getVscodeApi"
|
||||
|
||||
import { marked } from 'marked';
|
||||
|
|
@ -21,7 +21,7 @@ ${content}
|
|||
\`\`\``).join('\n')
|
||||
}
|
||||
|
||||
const userInstructionsStr = (instructions: string, files: File[], selection: Selection | null) => {
|
||||
const userInstructionsStr = (instructions: string, files: File[], selection: CodeSelection | null) => {
|
||||
return `
|
||||
${filesStr(files)}
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ const Sidebar = () => {
|
|||
const { allThreads, currentThread, addMessageToHistory, startNewThread, } = useThreads()
|
||||
|
||||
// state of current message
|
||||
const [selection, setSelection] = useState<Selection | null>(null) // the code the user is selecting
|
||||
const [selection, setSelection] = useState<CodeSelection | null>(null) // the code the user is selecting
|
||||
const [files, setFiles] = useState<vscode.Uri[]>([]) // the names of the files in the chat
|
||||
const [instructions, setInstructions] = useState('') // the user's instructions
|
||||
|
||||
|
|
@ -194,7 +194,7 @@ const Sidebar = () => {
|
|||
const newHistoryElt: ChatMessage = { role: 'user', content, displayContent: instructions, selection, files }
|
||||
addMessageToHistory(newHistoryElt)
|
||||
|
||||
// send message to claude
|
||||
// send message to LLM
|
||||
let { abort } = sendLLMMessage({
|
||||
messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })), { role: 'user', content }],
|
||||
onText: (newText, fullText) => setMessageStream(fullText),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Command, WebviewMessage } from "../shared_types";
|
|||
// message -> res[]
|
||||
const awaiting: { [c in Command]: ((res: any) => void)[] } = {
|
||||
"ctrl+l": [],
|
||||
"applyCode": [],
|
||||
"applyChanges": [],
|
||||
"requestFiles": [],
|
||||
"files": [],
|
||||
"apiConfig": [],
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const BlockCode = ({
|
|||
<button
|
||||
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||
onClick={async () => {
|
||||
getVSCodeAPI().postMessage({ type: "applyCode", code: text })
|
||||
getVSCodeAPI().postMessage({ type: "applyChanges", code: text })
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
|
|
|
|||
Loading…
Reference in a new issue