From 1fe12d2bfd67767187b5849af48e2f6773ce5d92 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 13 Nov 2024 23:58:28 -0800 Subject: [PATCH] make everything revolve around model, not editor --- .../contrib/void/browser/DiffProvider.ts | 443 ------------------ .../void/browser/registerInlineDiffs.ts | 374 +++++++-------- 2 files changed, 180 insertions(+), 637 deletions(-) delete mode 100644 src/vs/workbench/contrib/void/browser/DiffProvider.ts diff --git a/src/vs/workbench/contrib/void/browser/DiffProvider.ts b/src/vs/workbench/contrib/void/browser/DiffProvider.ts deleted file mode 100644 index 6f2675ef..00000000 --- a/src/vs/workbench/contrib/void/browser/DiffProvider.ts +++ /dev/null @@ -1,443 +0,0 @@ -import * as vscode from 'vscode'; -import { findDiffs } from './findDiffs'; -import { DiffArea, Diff } from '../common/shared_types'; - - -// TODO in theory this should be disposed -const lightGrayDecoration = vscode.window.createTextEditorDecorationType({ - backgroundColor: 'rgba(218 218 218 / .2)', - isWholeLine: true, -}) -const darkGrayDecoration = vscode.window.createTextEditorDecorationType({ - backgroundColor: 'rgb(148 148 148 / .2)', - isWholeLine: true, -}) - -// responsible for displaying diffs and showing accept/reject buttons -export class DiffProvider implements vscode.CodeLensProvider { - - private _originalFileOfDocument: { [docUriStr: string]: string } = {} - private _diffAreasOfDocument: { [docUriStr: string]: DiffArea[] } = {} - private _diffsOfDocument: { [docUriStr: string]: Diff[] } = {} - - private _diffareaidPool = 0 - private _diffidPool = 0 - - private _extensionUri: vscode.Uri - - // used internally by vscode - private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); // signals a UI refresh on .fire() events - public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event; - - // used internally by vscode - public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult { - const docUriStr = document.uri.toString() - return this._diffsOfDocument[docUriStr]?.flatMap(diff => diff.lenses) ?? [] - } - - // declared by us, registered with vscode.languages.registerCodeLensProvider() - constructor(context: vscode.ExtensionContext) { - this._extensionUri = context.extensionUri - - console.log('Creating DisplayChangesProvider') - - // // this acts as a useEffect every time text changes - // vscode.workspace.onDidChangeTextDocument((e) => { - - // const editor = vscode.window.activeTextEditor - - // if (!editor) return - - // const docUriStr = editor.document.uri.toString() - // const changes = e.contentChanges.map(c => ({ startLine: c.range.start.line, endLine: c.range.end.line, text: c.text, })) - - // // on user change, grow/shrink/merge/delete diff areas - // this.resizeDiffAreas(docUriStr, changes, 'currentFile') - - // // refresh the diffAreas - // this.refreshStylesAndDiffs(docUriStr) - - // }) - } - - // used by us only - // public createDiffArea(uri: vscode.Uri, partialDiffArea: Omit, originalFile: string) { - - // const uriStr = uri.toString() - - // this._originalFileOfDocument[uriStr] = originalFile - - // // 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 > partialDiffArea.endLine || da.endLine < partialDiffArea.startLine - // if (!noOverlap) return false - // return true - // }) - - // // add `diffArea` to storage - // const diffArea = { - // ...partialDiffArea, - // diffareaid: this._diffareaidPool - // } - // this._diffAreasOfDocument[uriStr].push(diffArea) - // this._diffareaidPool += 1 - - // return diffArea - // } - - // used by us only - // changes the start/line locations based on the changes that were recently made. does not change any of the diffs in the diff areas - // changes tells us how many lines were inserted/deleted so we can grow/shrink the diffAreas accordingly - // public resizeDiffAreas(docUriStr: string, changes: { text: string, startLine: number, endLine: number }[], changesTo: 'originalFile' | 'currentFile') { - - // const diffAreas = this._diffAreasOfDocument[docUriStr] || [] - - // let endLine: 'originalEndLine' | 'endLine' - // let startLine: 'originalStartLine' | 'startLine' - - // if (changesTo === 'originalFile') { - // endLine = 'originalEndLine' as const - // startLine = 'originalStartLine' as const - // } else { - // endLine = 'endLine' as const - // startLine = 'startLine' as const - // } - - // for (const change of changes) { - - // // 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.endLine - change.startLine - // 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.startLine >= diffArea[startLine] && change.endLine <= 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.endLine) { - // diffArea[startLine] += deltaNewlines - // diffArea[endLine] += deltaNewlines - // } - // } - - // // TODO merge any diffAreas if they overlap with each other as a result from the shift - - // } - // } - - - // // used by us only - // // refreshes all the diffs inside each diff area, and refreshes the styles - // public refreshStylesAndDiffs(docUriStr: string) { - - // 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 originalFile = this._originalFileOfDocument[docUriStr] - // if (!originalFile) { - // console.log('Error: No original file!') - // return; - // } - - // const diffAreas = this._diffAreasOfDocument[docUriStr] || [] - - // // reset all diffs (we update them below) - // this._diffsOfDocument[docUriStr] = [] - - // // TODO!!!! - // // vscode.languages.clearInlineDiffs(editor) - - // // for each diffArea - // for (const diffArea of diffAreas) { - - // // get code inside of diffArea - // const originalCode = originalFile.split('\n').slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n') - // 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(originalCode, currentCode) - - // // add the diffs to `this._diffsOfDocument[docUriStr]` - - // // 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.range, { title: 'Accept', command: 'void.acceptDiff', arguments: [{ diffid: this._diffidPool, diffareaid: diffArea.diffareaid }] }), - // new vscode.CodeLens(suggestedDiff.range, { title: 'Reject', command: 'void.rejectDiff', arguments: [{ diffid: this._diffidPool, diffareaid: diffArea.diffareaid }] }) - // ] - // }); - // vscode.languages.addInlineDiff(editor, suggestedDiff.originalCode, suggestedDiff.range) - // this._diffidPool += 1 - // } - - // } - - - // // for each diffArea, highlight its sweepIndex in dark gray - // editor.setDecorations( - // darkGrayDecoration, - // (this._diffAreasOfDocument[docUriStr] - // .filter(diffArea => diffArea.sweepIndex !== null) - // .map(diffArea => { - // let s = diffArea.sweepIndex! - // return new vscode.Range(s, 0, s, 0) - // }) - // ) - // ) - - // // for each diffArea, highlight sweepIndex+1...end in light gray - // editor.setDecorations( - // lightGrayDecoration, - // (this._diffAreasOfDocument[docUriStr] - // .filter(diffArea => diffArea.sweepIndex !== null) - // .map(diffArea => { - // return new vscode.Range(diffArea.sweepIndex! + 1, 0, diffArea.endLine, 0) - // }) - // ) - // ) - - - // // update code lenses - // this._onDidChangeCodeLenses.fire() - - // } - - - // // called on void.acceptDiff - // public async acceptDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) { - // const editor = vscode.window.activeTextEditor - // if (!editor) - // return - - // const docUriStr = editor.document.uri.toString() - - // 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; } - - // 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 originalFile = this._originalFileOfDocument[docUriStr] - // const currentFile = await readFileContentOfUri(editor.document.uri) - - // // Fixed: Handle newlines properly by splitting into lines and joining with proper newlines - // const originalLines = originalFile.split('\n'); - // const currentLines = currentFile.split('\n'); - - // // Get the changed lines from current file - // const changedLines = currentLines.slice(diff.range.start.line, diff.range.end.line + 1); - - // // Create new original file content by replacing the affected lines - // const newOriginalLines = [ - // ...originalLines.slice(0, diff.originalRange.start.line), - // ...changedLines, - // ...originalLines.slice(diff.originalRange.end.line + 1) - // ]; - - // this._originalFileOfDocument[docUriStr] = newOriginalLines.join('\n'); - - // // Update diff areas based on the change - // this.resizeDiffAreas(docUriStr, [{ - // text: changedLines.join('\n'), - // startLine: diff.originalRange.start.line, - // endLine: diff.originalRange.end.line - // }], 'originalFile') - - // // Check if diffArea should be removed - - // const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx] - - // const currentArea = currentLines.slice(diffArea.startLine, diffArea.endLine + 1).join('\n') - // const originalArea = newOriginalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n') - - // if (originalArea === currentArea) { - // const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid) - // this._diffAreasOfDocument[docUriStr].splice(index, 1) - // } - - // this.refreshStylesAndDiffs(docUriStr) - // } - - // // called on void.rejectDiff - // public async rejectDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) { - // const editor = vscode.window.activeTextEditor - // if (!editor) - // return - - // const docUriStr = editor.document.uri.toString() - - // 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; } - - // 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] - - // // Apply the rejection by replacing with original code - // // we don't have to edit the original or final file; just do a workspace edit so the code equals the original code - // const workspaceEdit = new vscode.WorkspaceEdit(); - // workspaceEdit.replace(editor.document.uri, diff.range, diff.originalCode) - // await vscode.workspace.applyEdit(workspaceEdit) - - // // Check if diffArea should be removed - // const originalFile = this._originalFileOfDocument[docUriStr] - // const currentFile = await readFileContentOfUri(editor.document.uri) - // const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx] - // const currentLines = currentFile.split('\n'); - // const originalLines = originalFile.split('\n'); - - // const currentArea = currentLines.slice(diffArea.startLine, diffArea.endLine + 1).join('\n') - // const originalArea = originalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n') - - // if (originalArea === currentArea) { - // const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid) - // this._diffAreasOfDocument[docUriStr].splice(index, 1) - // } - - // this.refreshStylesAndDiffs(docUriStr) - // } - - // async startStreamingInDiffArea({ docUri, oldFileStr, diffRepr, diffArea, voidConfig, abortRef }: { docUri: vscode.Uri, oldFileStr: string, diffRepr: string, voidConfig: VoidConfig, diffArea: DiffArea, abortRef: AbortRef }) { - - - // const promptContent = `\ - // ORIGINAL_FILE - // \`\`\` - // ${oldFileStr} - // \`\`\` - - // DIFF - // \`\`\` - // ${diffRepr} - // \`\`\` - - // INSTRUCTIONS - // Please finish writing the new file by applying the diff to the original file. Return ONLY the completion of the file, without any explanation. - - // ` - // // make LLM complete the file to include the diff - // await new Promise((resolve, reject) => { - // sendLLMMessage({ - // logging: { loggingName: 'streamChunk' }, - // messages: [ - // { role: 'system', content: writeFileWithDiffInstructions, }, - // // TODO include more context too - // { role: 'user', content: promptContent, } - // ], - // onText: (newText, fullText) => { - // this._updateStream(docUri.toString(), diffArea, fullText) - // }, - // onFinalMessage: (fullText) => { - // this._updateStream(docUri.toString(), diffArea, fullText) - // resolve(); - // }, - // onError: (e) => { - // console.error('Error rewriting file with diff', e); - // resolve(); - // }, - // voidConfig, - // abortRef, - // }) - // }) - - // } - - - // // used by us only - // private _updateStream = throttle(async (docUriStr: string, diffArea: DiffArea, newDiffAreaCode: string) => { - - // 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; - // } - - // // original code all diffs are based on in the code - // const originalDiffAreaCode = (this._originalFileOfDocument[docUriStr] || '').split('\n').slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n') - - // // figure out where to highlight based on where the AI is in the stream right now, use the last diff in findDiffs to figure that out - // const diffs = findDiffs(originalDiffAreaCode, newDiffAreaCode) - // const lastDiff = diffs?.[diffs.length - 1] ?? null - - // // these are two different coordinate systems - new and old line number - // let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted - // let oldFileStartLine: number // get original[oldStartingPoint...] - - // if (!lastDiff) { - // // if the writing is identical so far, display no changes - // newFileEndLine = 0 - // oldFileStartLine = 0 - // } - // else { - // if (lastDiff.type === 'insertion') { - // newFileEndLine = lastDiff.range.end.line - // oldFileStartLine = lastDiff.originalRange.start.line - // } - // else if (lastDiff.type === 'deletion') { - // newFileEndLine = lastDiff.range.start.line - // oldFileStartLine = lastDiff.originalRange.start.line - // } - // else if (lastDiff.type === 'edit') { - // newFileEndLine = lastDiff.range.end.line - // oldFileStartLine = lastDiff.originalRange.start.line - // } - // else { - // throw new Error(`updateStream: diff.type not recognized: ${lastDiff.type}`) - // } - // } - - // // display - // const newFileTop = newDiffAreaCode.split('\n').slice(0, newFileEndLine + 1).join('\n') - // const oldFileBottom = originalDiffAreaCode.split('\n').slice(oldFileStartLine + 1, Infinity).join('\n') - - // let newCode = `${newFileTop}\n${oldFileBottom}` - // diffArea.sweepIndex = newFileEndLine - // // replace oldDACode with newDACode with a vscode edit - - // const workspaceEdit = new vscode.WorkspaceEdit(); - - // const diffareaRange = new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER) - // workspaceEdit.replace(editor.document.uri, diffareaRange, newCode) - // await vscode.workspace.applyEdit(workspaceEdit) - // }, THROTTLE_TIME) - -} - - - diff --git a/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts b/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts index a5f990de..8da0674b 100644 --- a/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts +++ b/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts @@ -14,10 +14,10 @@ import { URI } from '../../../../base/common/uri.js'; import { IVoidConfigStateService } from './registerConfig.js'; import { writeFileWithDiffInstructions } from './prompt/systemPrompts.js'; import { findDiffs } from './findDiffs.js'; -import { EndOfLinePreference, IModelDeltaDecoration } from '../../../../editor/common/model.js'; +import { EndOfLinePreference, IModelDeltaDecoration, ITextModel } from '../../../../editor/common/model.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; -import { IModelService } from '../../../../editor/common/services/model.js'; + // read files from VSCode @@ -33,8 +33,6 @@ export const VSReadFile = async (fileService: IFileService, uri: URI): Promise { + private _addInlineDiffZone = (model: ITextModel, originalText: string, greenRange: IRange) => { - // green decoration and gutter decoration - const greenDecoration: IModelDeltaDecoration[] = [{ - range: greenRange, - options: { - className: 'line-insert', // .monaco-editor .line-insert - description: 'line-insert', - isWholeLine: true, - minimap: { - color: { id: 'minimapGutter.addedBackground' }, - position: 2 - }, - overviewRuler: { - color: { id: 'editorOverviewRuler.addedForeground' }, - position: 7 + const _addInlineDiffZoneToEditor = (editor: ICodeEditor) => { + // green decoration and gutter decoration + const greenDecoration: IModelDeltaDecoration[] = [{ + range: greenRange, + options: { + className: 'line-insert', // .monaco-editor .line-insert + description: 'line-insert', + isWholeLine: true, + minimap: { + color: { id: 'minimapGutter.addedBackground' }, + position: 2 + }, + overviewRuler: { + color: { id: 'editorOverviewRuler.addedForeground' }, + position: 7 + } } - } - }]; - const decorationIds = editor.deltaDecorations([], greenDecoration) + }]; + const decorationIds = editor.deltaDecorations([], greenDecoration) - // red in a view zone - let zoneId: string | null = null - editor.changeViewZones(accessor => { - // Get the editor's font info - const fontInfo = editor.getOption(EditorOption.fontInfo); - - const domNode = document.createElement('div'); - domNode.className = 'monaco-editor view-zones line-delete monaco-mouse-cursor-text'; - domNode.style.fontSize = `${fontInfo.fontSize}px`; - domNode.style.fontFamily = fontInfo.fontFamily; - domNode.style.lineHeight = `${fontInfo.lineHeight}px`; - - // div - const lineContent = document.createElement('div'); - lineContent.className = 'view-line'; // .monaco-editor .inline-deleted-text - - // span - const contentSpan = document.createElement('span'); - - // span - const codeSpan = document.createElement('span'); - codeSpan.className = 'mtk1'; // char-delete - codeSpan.textContent = originalText; - - // Mount - contentSpan.appendChild(codeSpan); - lineContent.appendChild(contentSpan); - domNode.appendChild(lineContent); - - // Gutter (thing to the left) - const gutterDiv = document.createElement('div'); - gutterDiv.className = 'inline-diff-gutter'; - const minusDiv = document.createElement('div'); - minusDiv.className = 'inline-diff-deleted-gutter'; - // minusDiv.textContent = '-'; - gutterDiv.appendChild(minusDiv); - - const viewZone: IViewZone = { - afterLineNumber: greenRange.startLineNumber - 1, - heightInLines: originalText.split('\n').length + 1, - domNode: domNode, - suppressMouseDown: true, - marginDomNode: gutterDiv - }; - - zoneId = accessor.addZone(viewZone); - // editor.layout(); - // this._diffZones.set(editor, [zoneId]); - }); - - - const dispose = () => { - editor.deltaDecorations(decorationIds, []); + // red in a view zone + let zoneId: string | null = null editor.changeViewZones(accessor => { - if (zoneId) accessor.removeZone(zoneId); + // Get the editor's font info + const fontInfo = editor.getOption(EditorOption.fontInfo); + + const domNode = document.createElement('div'); + domNode.className = 'monaco-editor view-zones line-delete monaco-mouse-cursor-text'; + domNode.style.fontSize = `${fontInfo.fontSize}px`; + domNode.style.fontFamily = fontInfo.fontFamily; + domNode.style.lineHeight = `${fontInfo.lineHeight}px`; + + // div + const lineContent = document.createElement('div'); + lineContent.className = 'view-line'; // .monaco-editor .inline-deleted-text + + // span + const contentSpan = document.createElement('span'); + + // span + const codeSpan = document.createElement('span'); + codeSpan.className = 'mtk1'; // char-delete + codeSpan.textContent = originalText; + + // Mount + contentSpan.appendChild(codeSpan); + lineContent.appendChild(contentSpan); + domNode.appendChild(lineContent); + + // Gutter (thing to the left) + const gutterDiv = document.createElement('div'); + gutterDiv.className = 'inline-diff-gutter'; + const minusDiv = document.createElement('div'); + minusDiv.className = 'inline-diff-deleted-gutter'; + // minusDiv.textContent = '-'; + gutterDiv.appendChild(minusDiv); + + const viewZone: IViewZone = { + afterLineNumber: greenRange.startLineNumber - 1, + heightInLines: originalText.split('\n').length + 1, + domNode: domNode, + suppressMouseDown: true, + marginDomNode: gutterDiv + }; + + zoneId = accessor.addZone(viewZone); + // editor.layout(); + // this._diffZones.set(editor, [zoneId]); }); + + + const dispose = () => { + editor.deltaDecorations(decorationIds, []); + editor.changeViewZones(accessor => { + if (zoneId) accessor.removeZone(zoneId); + }); + } + return dispose + } + + const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.id === model.id) + + const disposeFns = editors.map(editor => _addInlineDiffZoneToEditor(editor)) + const dispose = () => { + disposeFns.forEach(fn => fn()) } return dispose @@ -287,16 +293,15 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { private _deleteDiffArea(diffArea: DiffArea) { this._deleteDiffs(diffArea) delete this.diffAreaOfId[diffArea.diffareaid] - this.diffAreasOfModelId[diffArea._modelid].delete(diffArea.diffareaid.toString()) + this.diffAreasOfModelId[diffArea._model.id].delete(diffArea.diffareaid.toString()) } + + + // for every diffarea in this document, recompute its diffs and restyle it (the two are coupled) - private _refreshAllDiffsAndStyles(editor: ICodeEditor) { - - const model = editor.getModel() - if (!model) return - + private _refreshAllDiffsAndStyles(model: ITextModel) { const modelid = model.id @@ -322,7 +327,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { for (let computedDiff of computedDiffs) { // add the view zone const greenRange: IRange = { startLineNumber: diffArea.startLine, startColumn: 0, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER, } - const dispose = this._addInlineDiffZone(editor, computedDiff.originalCode, greenRange) + const dispose = this._addInlineDiffZone(model, computedDiff.originalCode, greenRange) // create a Diff of it const diffid = this._diffidPool++ @@ -339,34 +344,45 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { } } - private _refreshSweepStyles(editor: ICodeEditor) { + private _refreshSweepStyles(model: ITextModel) { - const model = editor.getModel() - if (!model) return - const modelid = model.id + // const model = editor.getModel() + // if (!model) return + // const modelid = model.id - // for each diffArea, highlight its sweepIndex in dark gray - editor.setDecorations( - darkGrayDecoration, - (this._diffAreasOfDocument[modelid] - .filter(diffArea => diffArea.sweepIndex !== null) - .map(diffArea => { - let s = diffArea.sweepIndex! - return new vscode.Range(s, 0, s, 0) - }) - ) - ) + // const lightGrayDecoration = vscode.window.createTextEditorDecorationType({ + // backgroundColor: 'rgba(218 218 218 / .2)', + // isWholeLine: true, + // }) + // const darkGrayDecoration = vscode.window.createTextEditorDecorationType({ + // backgroundColor: 'rgb(148 148 148 / .2)', + // isWholeLine: true, + // }) - // for each diffArea, highlight sweepIndex+1...end in light gray - editor.setDecorations( - lightGrayDecoration, - (this._diffAreasOfDocument[modelid] - .filter(diffArea => diffArea.sweepIndex !== null) - .map(diffArea => { - return new vscode.Range(diffArea.sweepIndex! + 1, 0, diffArea.endLine, 0) - }) - ) - ) + + + // // for each diffArea, highlight its sweepIndex in dark gray + // editor.setDecorations( + // darkGrayDecoration, + // (this._diffAreasOfDocument[modelid] + // .filter(diffArea => diffArea.sweepIndex !== null) + // .map(diffArea => { + // let s = diffArea.sweepIndex! + // return new vscode.Range(s, 0, s, 0) + // }) + // ) + // ) + + // // for each diffArea, highlight sweepIndex+1...end in light gray + // editor.setDecorations( + // lightGrayDecoration, + // (this._diffAreasOfDocument[modelid] + // .filter(diffArea => diffArea.sweepIndex !== null) + // .map(diffArea => { + // return new vscode.Range(diffArea.sweepIndex! + 1, 0, diffArea.endLine, 0) + // }) + // ) + // ) } @@ -427,10 +443,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { private _registeredListeners = new Set() // set of model IDs - private _registerTextChangeListener(editor: ICodeEditor) { - const model = editor.getModel() - if (!model) return - + private _registerTextChangeListener(model: ITextModel) { const modelid = model.id if (this._registeredListeners.has(modelid)) return @@ -441,7 +454,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { model.onDidChangeContent(e => { const changes = e.changes.map(c => ({ startLine: c.range.startLineNumber, endLine: c.range.endLineNumber, text: c.text, })) this._resizeOnTextChange(modelid, changes, 'currentFile') - this._refreshAllDiffsAndStyles(editor) + this._refreshAllDiffsAndStyles(model) }) ) @@ -450,29 +463,14 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { this._registeredListeners.delete(modelid) }) ) - - // listen for document changes - - // // // this acts as a useEffect every time text changes - // vscode.workspace.onDidChangeTextDocument((e) => { - // const editor = vscode.window.activeTextEditor - // if (!editor) return - // const modelid = editor.document.uri.toString() - // const changes = e.contentChanges.map(c => ({ startLine: c.range.start.line, endLine: c.range.end.line, text: c.text, })) - // // on user change, grow/shrink/merge/delete diff areas - // // refresh the diffAreas - // this._refreshStylesAndDiffs(modelid) - // }) - } @throttle(100) - private async _updateDiffAreaText(diffArea: DiffArea, llmCodeSoFar: string, streamingGroup: UndoRedoGroup) { + private async _updateDiffAreaText(diffArea: DiffArea, llmCodeSoFar: string) { // clear all diffs in this diffarea and recompute them - const uri = diffArea._modeluri - const modelid = diffArea._modelid + const modelid = diffArea._model.id if (this.streamingStateOfModelId[modelid].type !== 'streaming') return @@ -518,18 +516,14 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { let newCode = `${newFileTop}\n${oldFileBottom}` diffArea._sweepLine = newFileEndLine - this._bulkEditService.apply( - [new ResourceTextEdit(uri, { - range: { - startLineNumber: diffArea.startLine, - startColumn: 0, - endLineNumber: diffArea.endLine, - endColumn: Number.MAX_SAFE_INTEGER, - }, + + // applies edits without adding them to undo/redo stack + const model = diffArea._model + if (!model.isDisposed()) + model.applyEdits([{ + range: { startLineNumber: diffArea.startLine, startColumn: 0, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER, }, text: newCode - })], - // count all changes towards the group - { undoRedoGroupId: streamingGroup.id }); + }]) // TODO resize diffAreas?? Or is this handled already by the listener? } @@ -537,11 +531,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { - private async _initializeStream(editor: ICodeEditor, diffRepr: string, streamingGroup: UndoRedoGroup) { - - // do all the checks first - const model = editor.getModel() - if (!model) return + private async _initializeStream(model: ITextModel, diffRepr: string, streamingGroup: UndoRedoGroup) { const uri = model.uri const modelid = uri.toString() @@ -566,7 +556,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { } // start listening for text changes - this._registerTextChangeListener(editor) + this._registerTextChangeListener(model) // add to history const { finishHistorySnapshot } = this._addToHistory(model.uri, streamingGroup) @@ -582,8 +572,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { originalEndLine: endLine, startLine: beginLine, endLine: endLine, // starts out the same as the current file - _modelid: model.id, - _modeluri: model.uri, + _model: model, _sweepLine: null, _sweepCol: null, _generationid: generationid, @@ -625,14 +614,14 @@ Please finish writing the new file by applying the diff to the original file. Re { role: 'user', content: promptContent, } ], onText: (newText, fullText) => { - this._updateDiffAreaText(diffArea, fullText, streamingGroup) - this._refreshAllDiffsAndStyles(editor) - this._refreshSweepStyles(editor) + this._updateDiffAreaText(diffArea, fullText) + this._refreshAllDiffsAndStyles(model) + this._refreshSweepStyles(model) }, onFinalMessage: (fullText) => { - this._updateDiffAreaText(diffArea, fullText, streamingGroup) - this._refreshAllDiffsAndStyles(editor) - this._refreshSweepStyles(editor) + this._updateDiffAreaText(diffArea, fullText) + this._refreshAllDiffsAndStyles(model) + this._refreshSweepStyles(model) resolve(); }, onError: (e) => { @@ -666,7 +655,7 @@ Please finish writing the new file by applying the diff to the original file. Re this.streamingStateOfModelId[model.id] = streamingState // initialize stream - this._initializeStream(editor, userMessage, streamingGroup) + this._initializeStream(model, userMessage, streamingGroup) } @@ -691,8 +680,8 @@ Please finish writing the new file by applying the diff to the original file. Re const diffArea = this.diffAreaOfId[diffareaid] if (!diffArea) return - const modelid = diffArea._modelid - const uri = diffArea._modeluri + const model = diffArea._model + const { id: modelid, uri } = model const originalFile = this.originalFileStrOfModelId[modelid] const currentFile = await VSReadFile(this._fileService, uri) @@ -730,10 +719,7 @@ Please finish writing the new file by applying the diff to the original file. Re const shouldDeleteDiffArea = originalArea === currentArea if (shouldDeleteDiffArea) { this._deleteDiffArea(diffArea) - // TODO if editor is visible we should update it too, or focus it first - const editor = this._editorService.getActiveCodeEditor() - if (editor?.getModel()?.id === modelid) - this._refreshAllDiffsAndStyles(editor) + this._refreshAllDiffsAndStyles(model) } finishHistorySnapshot() @@ -744,51 +730,51 @@ Please finish writing the new file by applying the diff to the original file. Re // called on void.rejectDiff - public async rejectDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) { - const editor = vscode.window.activeTextEditor - if (!editor) - return + public async rejectDiff({ diffid }: { diffid: number }) { - const modelid = editor.document.uri.toString() + const diff = this.diffOfId[diffid] + if (!diff) return - const diffIdx = this._diffsOfDocument[modelid].findIndex(diff => diff.diffid === diffid); - if (diffIdx === -1) { console.error('Error: DiffID could not be found: ', diffid, diffareaid, this._diffsOfDocument[modelid], this._diffAreasOfDocument[modelid]); return; } + const { diffareaid } = diff + const diffArea = this.diffAreaOfId[diffareaid] + if (!diffArea) return - const diffareaIdx = this._diffAreasOfDocument[modelid].findIndex(diff => diff.diffareaid === diffareaid); - if (diffareaIdx === -1) { console.error('Error: DiffAreaID could not be found: ', diffid, diffareaid, this._diffsOfDocument[modelid], this._diffAreasOfDocument[modelid]); return; } + const model = diffArea._model + const { id: modelid, uri } = model - const diff = this._diffsOfDocument[modelid][diffIdx] + const originalFile = this.originalFileStrOfModelId[modelid] + const currentFile = await VSReadFile(this._fileService, uri) + if (currentFile === null) return - // Apply the rejection by replacing with original code - // we don't have to edit the original or final file; just do a workspace edit so the code equals the original code - const workspaceEdit = new vscode.WorkspaceEdit(); - workspaceEdit.replace(editor.document.uri, diff.range, diff.originalCode) - await vscode.workspace.applyEdit(workspaceEdit) + + // add to history + const { finishHistorySnapshot } = this._addToHistory(uri) + + // Apply the rejection by replacing with original code (without putting it on the undo/redo stack, this is OK because we put it on the stack ourselves) + if (!model.isDisposed()) + model.applyEdits([{ + range: { startLineNumber: diffArea.startLine, startColumn: 0, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER, }, + text: diff.originalCode + }]) // Check if diffArea should be removed - const originalFile = this._originalFileOfDocument[modelid] - const currentFile = await readFileContentOfUri(editor.document.uri) - const diffArea = this._diffAreasOfDocument[modelid][diffareaIdx] const currentLines = currentFile.split('\n'); const originalLines = originalFile.split('\n'); const currentArea = currentLines.slice(diffArea.startLine, diffArea.endLine + 1).join('\n') const originalArea = originalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n') - - if (originalArea === currentArea) { - const index = this._diffAreasOfDocument[modelid].findIndex(da => da.diffareaid === diffArea.diffareaid) - this._diffAreasOfDocument[modelid].splice(index, 1) + const shouldDeleteDiffArea = originalArea === currentArea + if (shouldDeleteDiffArea) { + this._deleteDiffArea(diffArea) } + const editor = this._editorService.getActiveCodeEditor() + if (editor?.getModel()?.id === modelid) + this._refreshAllDiffsAndStyles(model) + + finishHistorySnapshot() - this.refreshStylesAndDiffs(modelid) } - - - - - - } registerSingleton(IInlineDiffsService, InlineDiffsService, InstantiationType.Eager);