mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
make everything revolve around model, not editor
This commit is contained in:
parent
1cf371fa8b
commit
1fe12d2bfd
2 changed files with 180 additions and 637 deletions
|
|
@ -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<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(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<DiffArea, 'diffareaid'>, 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<void>((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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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<s
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export type Diff = {
|
||||
diffid: number,
|
||||
diffareaid: number, // the diff area this diff belongs to, "computed"
|
||||
|
|
@ -66,8 +64,7 @@ type DiffArea = {
|
|||
endLine: number,
|
||||
|
||||
_diffs: Diff[],
|
||||
_modelid: string, // model id (document where this diffarea lives)
|
||||
_modeluri: URI,
|
||||
_model: ITextModel, // the model or "document" this diffarea lives on
|
||||
_generationid: number,
|
||||
_sweepLine: number | null,
|
||||
_sweepCol: number | null,
|
||||
|
|
@ -132,7 +129,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
|||
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
|
||||
) {
|
||||
super();
|
||||
|
|
@ -141,84 +137,94 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
|||
|
||||
|
||||
|
||||
private _addInlineDiffZone = (editor: ICodeEditor, originalText: string, greenRange: IRange) => {
|
||||
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<string>() // 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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue