mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
registerInlineDiffs progress
This commit is contained in:
parent
a5912ba538
commit
ee72a2c3cd
11 changed files with 622 additions and 677 deletions
|
|
@ -1,15 +1,8 @@
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { findDiffs } from './src/extension/findDiffs';
|
import { findDiffs } from './findDiffs';
|
||||||
import { throttle } from 'lodash';
|
import { DiffArea, Diff } from '../common/shared_types';
|
||||||
import { DiffArea, BaseDiff, Diff } from '../common/shared_types';
|
|
||||||
import { readFileContentOfUri } from './src/extension/extensionLib/readFileContentOfUri';
|
|
||||||
import { AbortRef, sendLLMMessage } from '../common/sendLLMMessage';
|
|
||||||
import { writeFileWithDiffInstructions } from '../common/systemPrompts';
|
|
||||||
import { VoidConfig } from './src/webviews/common/contextForConfig';
|
|
||||||
|
|
||||||
|
|
||||||
const THROTTLE_TIME = 100
|
|
||||||
|
|
||||||
// TODO in theory this should be disposed
|
// TODO in theory this should be disposed
|
||||||
const lightGrayDecoration = vscode.window.createTextEditorDecorationType({
|
const lightGrayDecoration = vscode.window.createTextEditorDecorationType({
|
||||||
backgroundColor: 'rgba(218 218 218 / .2)',
|
backgroundColor: 'rgba(218 218 218 / .2)',
|
||||||
|
|
@ -48,52 +41,52 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||||
|
|
||||||
console.log('Creating DisplayChangesProvider')
|
console.log('Creating DisplayChangesProvider')
|
||||||
|
|
||||||
// this acts as a useEffect every time text changes
|
// // this acts as a useEffect every time text changes
|
||||||
vscode.workspace.onDidChangeTextDocument((e) => {
|
// vscode.workspace.onDidChangeTextDocument((e) => {
|
||||||
|
|
||||||
const editor = vscode.window.activeTextEditor
|
// const editor = vscode.window.activeTextEditor
|
||||||
|
|
||||||
if (!editor) return
|
// if (!editor) return
|
||||||
|
|
||||||
const docUriStr = editor.document.uri.toString()
|
// 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, }))
|
// 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
|
// // on user change, grow/shrink/merge/delete diff areas
|
||||||
this.resizeDiffAreas(docUriStr, changes, 'currentFile')
|
// this.resizeDiffAreas(docUriStr, changes, 'currentFile')
|
||||||
|
|
||||||
// refresh the diffAreas
|
// // refresh the diffAreas
|
||||||
this.refreshStylesAndDiffs(docUriStr)
|
// this.refreshStylesAndDiffs(docUriStr)
|
||||||
|
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
// used by us only
|
// used by us only
|
||||||
public createDiffArea(uri: vscode.Uri, partialDiffArea: Omit<DiffArea, 'diffareaid'>, originalFile: string) {
|
// public createDiffArea(uri: vscode.Uri, partialDiffArea: Omit<DiffArea, 'diffareaid'>, originalFile: string) {
|
||||||
|
|
||||||
const uriStr = uri.toString()
|
// const uriStr = uri.toString()
|
||||||
|
|
||||||
this._originalFileOfDocument[uriStr] = originalFile
|
// this._originalFileOfDocument[uriStr] = originalFile
|
||||||
|
|
||||||
// make sure array is defined
|
// // make sure array is defined
|
||||||
if (!this._diffAreasOfDocument[uriStr]) this._diffAreasOfDocument[uriStr] = []
|
// if (!this._diffAreasOfDocument[uriStr]) this._diffAreasOfDocument[uriStr] = []
|
||||||
|
|
||||||
// remove all diffAreas that the new `diffArea` is overlapping with
|
// // remove all diffAreas that the new `diffArea` is overlapping with
|
||||||
this._diffAreasOfDocument[uriStr] = this._diffAreasOfDocument[uriStr].filter(da => {
|
// this._diffAreasOfDocument[uriStr] = this._diffAreasOfDocument[uriStr].filter(da => {
|
||||||
const noOverlap = da.startLine > partialDiffArea.endLine || da.endLine < partialDiffArea.startLine
|
// const noOverlap = da.startLine > partialDiffArea.endLine || da.endLine < partialDiffArea.startLine
|
||||||
if (!noOverlap) return false
|
// if (!noOverlap) return false
|
||||||
return true
|
// return true
|
||||||
})
|
// })
|
||||||
|
|
||||||
// add `diffArea` to storage
|
// // add `diffArea` to storage
|
||||||
const diffArea = {
|
// const diffArea = {
|
||||||
...partialDiffArea,
|
// ...partialDiffArea,
|
||||||
diffareaid: this._diffareaidPool
|
// diffareaid: this._diffareaidPool
|
||||||
}
|
// }
|
||||||
this._diffAreasOfDocument[uriStr].push(diffArea)
|
// this._diffAreasOfDocument[uriStr].push(diffArea)
|
||||||
this._diffareaidPool += 1
|
// this._diffareaidPool += 1
|
||||||
|
|
||||||
return diffArea
|
// return diffArea
|
||||||
}
|
// }
|
||||||
|
|
||||||
// used by us only
|
// 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 the start/line locations based on the changes that were recently made. does not change any of the diffs in the diff areas
|
||||||
|
|
@ -244,205 +237,205 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// called on void.acceptDiff
|
// // called on void.acceptDiff
|
||||||
public async acceptDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
|
// public async acceptDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
|
||||||
const editor = vscode.window.activeTextEditor
|
// const editor = vscode.window.activeTextEditor
|
||||||
if (!editor)
|
// if (!editor)
|
||||||
return
|
// return
|
||||||
|
|
||||||
const docUriStr = editor.document.uri.toString()
|
// const docUriStr = editor.document.uri.toString()
|
||||||
|
|
||||||
const diffIdx = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
// 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; }
|
// 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);
|
// 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; }
|
// 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 diff = this._diffsOfDocument[docUriStr][diffIdx]
|
||||||
const originalFile = this._originalFileOfDocument[docUriStr]
|
// const originalFile = this._originalFileOfDocument[docUriStr]
|
||||||
const currentFile = await readFileContentOfUri(editor.document.uri)
|
// const currentFile = await readFileContentOfUri(editor.document.uri)
|
||||||
|
|
||||||
// Fixed: Handle newlines properly by splitting into lines and joining with proper newlines
|
// // Fixed: Handle newlines properly by splitting into lines and joining with proper newlines
|
||||||
const originalLines = originalFile.split('\n');
|
// const originalLines = originalFile.split('\n');
|
||||||
const currentLines = currentFile.split('\n');
|
// const currentLines = currentFile.split('\n');
|
||||||
|
|
||||||
// Get the changed lines from current file
|
// // Get the changed lines from current file
|
||||||
const changedLines = currentLines.slice(diff.range.start.line, diff.range.end.line + 1);
|
// const changedLines = currentLines.slice(diff.range.start.line, diff.range.end.line + 1);
|
||||||
|
|
||||||
// Create new original file content by replacing the affected lines
|
// // Create new original file content by replacing the affected lines
|
||||||
const newOriginalLines = [
|
// const newOriginalLines = [
|
||||||
...originalLines.slice(0, diff.originalRange.start.line),
|
// ...originalLines.slice(0, diff.originalRange.start.line),
|
||||||
...changedLines,
|
// ...changedLines,
|
||||||
...originalLines.slice(diff.originalRange.end.line + 1)
|
// ...originalLines.slice(diff.originalRange.end.line + 1)
|
||||||
];
|
// ];
|
||||||
|
|
||||||
this._originalFileOfDocument[docUriStr] = newOriginalLines.join('\n');
|
// this._originalFileOfDocument[docUriStr] = newOriginalLines.join('\n');
|
||||||
|
|
||||||
// Update diff areas based on the change
|
// // Update diff areas based on the change
|
||||||
this.resizeDiffAreas(docUriStr, [{
|
// this.resizeDiffAreas(docUriStr, [{
|
||||||
text: changedLines.join('\n'),
|
// text: changedLines.join('\n'),
|
||||||
startLine: diff.originalRange.start.line,
|
// startLine: diff.originalRange.start.line,
|
||||||
endLine: diff.originalRange.end.line
|
// endLine: diff.originalRange.end.line
|
||||||
}], 'originalFile')
|
// }], 'originalFile')
|
||||||
|
|
||||||
// Check if diffArea should be removed
|
// // Check if diffArea should be removed
|
||||||
|
|
||||||
const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
|
// const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
|
||||||
|
|
||||||
const currentArea = currentLines.slice(diffArea.startLine, diffArea.endLine + 1).join('\n')
|
// const currentArea = currentLines.slice(diffArea.startLine, diffArea.endLine + 1).join('\n')
|
||||||
const originalArea = newOriginalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n')
|
// const originalArea = newOriginalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n')
|
||||||
|
|
||||||
if (originalArea === currentArea) {
|
// if (originalArea === currentArea) {
|
||||||
const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
|
// const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
|
||||||
this._diffAreasOfDocument[docUriStr].splice(index, 1)
|
// this._diffAreasOfDocument[docUriStr].splice(index, 1)
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.refreshStylesAndDiffs(docUriStr)
|
// this.refreshStylesAndDiffs(docUriStr)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// called on void.rejectDiff
|
// // called on void.rejectDiff
|
||||||
public async rejectDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
|
// public async rejectDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
|
||||||
const editor = vscode.window.activeTextEditor
|
// const editor = vscode.window.activeTextEditor
|
||||||
if (!editor)
|
// if (!editor)
|
||||||
return
|
// return
|
||||||
|
|
||||||
const docUriStr = editor.document.uri.toString()
|
// const docUriStr = editor.document.uri.toString()
|
||||||
|
|
||||||
const diffIdx = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
// 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; }
|
// 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);
|
// 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; }
|
// 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 diff = this._diffsOfDocument[docUriStr][diffIdx]
|
||||||
|
|
||||||
// Apply the rejection by replacing with original code
|
// // 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
|
// // 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();
|
// const workspaceEdit = new vscode.WorkspaceEdit();
|
||||||
workspaceEdit.replace(editor.document.uri, diff.range, diff.originalCode)
|
// workspaceEdit.replace(editor.document.uri, diff.range, diff.originalCode)
|
||||||
await vscode.workspace.applyEdit(workspaceEdit)
|
// await vscode.workspace.applyEdit(workspaceEdit)
|
||||||
|
|
||||||
// Check if diffArea should be removed
|
// // Check if diffArea should be removed
|
||||||
const originalFile = this._originalFileOfDocument[docUriStr]
|
// const originalFile = this._originalFileOfDocument[docUriStr]
|
||||||
const currentFile = await readFileContentOfUri(editor.document.uri)
|
// const currentFile = await readFileContentOfUri(editor.document.uri)
|
||||||
const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
|
// const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
|
||||||
const currentLines = currentFile.split('\n');
|
// const currentLines = currentFile.split('\n');
|
||||||
const originalLines = originalFile.split('\n');
|
// const originalLines = originalFile.split('\n');
|
||||||
|
|
||||||
const currentArea = currentLines.slice(diffArea.startLine, diffArea.endLine + 1).join('\n')
|
// const currentArea = currentLines.slice(diffArea.startLine, diffArea.endLine + 1).join('\n')
|
||||||
const originalArea = originalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n')
|
// const originalArea = originalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n')
|
||||||
|
|
||||||
if (originalArea === currentArea) {
|
// if (originalArea === currentArea) {
|
||||||
const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
|
// const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
|
||||||
this._diffAreasOfDocument[docUriStr].splice(index, 1)
|
// this._diffAreasOfDocument[docUriStr].splice(index, 1)
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.refreshStylesAndDiffs(docUriStr)
|
// this.refreshStylesAndDiffs(docUriStr)
|
||||||
}
|
// }
|
||||||
|
|
||||||
async startStreamingInDiffArea({ docUri, oldFileStr, diffRepr, diffArea, voidConfig, abortRef }: { docUri: vscode.Uri, oldFileStr: string, diffRepr: string, voidConfig: VoidConfig, diffArea: DiffArea, abortRef: AbortRef }) {
|
// async startStreamingInDiffArea({ docUri, oldFileStr, diffRepr, diffArea, voidConfig, abortRef }: { docUri: vscode.Uri, oldFileStr: string, diffRepr: string, voidConfig: VoidConfig, diffArea: DiffArea, abortRef: AbortRef }) {
|
||||||
|
|
||||||
|
|
||||||
const promptContent = `\
|
// const promptContent = `\
|
||||||
ORIGINAL_FILE
|
// ORIGINAL_FILE
|
||||||
\`\`\`
|
// \`\`\`
|
||||||
${oldFileStr}
|
// ${oldFileStr}
|
||||||
\`\`\`
|
// \`\`\`
|
||||||
|
|
||||||
DIFF
|
// DIFF
|
||||||
\`\`\`
|
// \`\`\`
|
||||||
${diffRepr}
|
// ${diffRepr}
|
||||||
\`\`\`
|
// \`\`\`
|
||||||
|
|
||||||
INSTRUCTIONS
|
// 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.
|
// 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
|
// // make LLM complete the file to include the diff
|
||||||
await new Promise<void>((resolve, reject) => {
|
// await new Promise<void>((resolve, reject) => {
|
||||||
sendLLMMessage({
|
// sendLLMMessage({
|
||||||
logging: { loggingName: 'streamChunk' },
|
// logging: { loggingName: 'streamChunk' },
|
||||||
messages: [
|
// messages: [
|
||||||
{ role: 'system', content: writeFileWithDiffInstructions, },
|
// { role: 'system', content: writeFileWithDiffInstructions, },
|
||||||
// TODO include more context too
|
// // TODO include more context too
|
||||||
{ role: 'user', content: promptContent, }
|
// { role: 'user', content: promptContent, }
|
||||||
],
|
// ],
|
||||||
onText: (newText, fullText) => {
|
// onText: (newText, fullText) => {
|
||||||
this._updateStream(docUri.toString(), diffArea, fullText)
|
// this._updateStream(docUri.toString(), diffArea, fullText)
|
||||||
},
|
// },
|
||||||
onFinalMessage: (fullText) => {
|
// onFinalMessage: (fullText) => {
|
||||||
this._updateStream(docUri.toString(), diffArea, fullText)
|
// this._updateStream(docUri.toString(), diffArea, fullText)
|
||||||
resolve();
|
// resolve();
|
||||||
},
|
// },
|
||||||
onError: (e) => {
|
// onError: (e) => {
|
||||||
console.error('Error rewriting file with diff', e);
|
// console.error('Error rewriting file with diff', e);
|
||||||
resolve();
|
// resolve();
|
||||||
},
|
// },
|
||||||
voidConfig,
|
// voidConfig,
|
||||||
abortRef,
|
// abortRef,
|
||||||
})
|
// })
|
||||||
})
|
// })
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
// used by us only
|
// // used by us only
|
||||||
private _updateStream = throttle(async (docUriStr: string, diffArea: DiffArea, newDiffAreaCode: string) => {
|
// 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
|
// const editor = vscode.window.activeTextEditor // TODO the editor should be that of `docUri` and not necessarily the current editor
|
||||||
if (!editor) {
|
// if (!editor) {
|
||||||
console.log('Error: No active editor!')
|
// console.log('Error: No active editor!')
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// original code all diffs are based on in the code
|
// // 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')
|
// 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
|
// // 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 diffs = findDiffs(originalDiffAreaCode, newDiffAreaCode)
|
||||||
const lastDiff = diffs?.[diffs.length - 1] ?? null
|
// const lastDiff = diffs?.[diffs.length - 1] ?? null
|
||||||
|
|
||||||
// these are two different coordinate systems - new and old line number
|
// // these are two different coordinate systems - new and old line number
|
||||||
let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted
|
// let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted
|
||||||
let oldFileStartLine: number // get original[oldStartingPoint...]
|
// let oldFileStartLine: number // get original[oldStartingPoint...]
|
||||||
|
|
||||||
if (!lastDiff) {
|
// if (!lastDiff) {
|
||||||
// if the writing is identical so far, display no changes
|
// // if the writing is identical so far, display no changes
|
||||||
newFileEndLine = 0
|
// newFileEndLine = 0
|
||||||
oldFileStartLine = 0
|
// oldFileStartLine = 0
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
if (lastDiff.type === 'insertion') {
|
// if (lastDiff.type === 'insertion') {
|
||||||
newFileEndLine = lastDiff.range.end.line
|
// newFileEndLine = lastDiff.range.end.line
|
||||||
oldFileStartLine = lastDiff.originalRange.start.line
|
// oldFileStartLine = lastDiff.originalRange.start.line
|
||||||
}
|
// }
|
||||||
else if (lastDiff.type === 'deletion') {
|
// else if (lastDiff.type === 'deletion') {
|
||||||
newFileEndLine = lastDiff.range.start.line
|
// newFileEndLine = lastDiff.range.start.line
|
||||||
oldFileStartLine = lastDiff.originalRange.start.line
|
// oldFileStartLine = lastDiff.originalRange.start.line
|
||||||
}
|
// }
|
||||||
else if (lastDiff.type === 'edit') {
|
// else if (lastDiff.type === 'edit') {
|
||||||
newFileEndLine = lastDiff.range.end.line
|
// newFileEndLine = lastDiff.range.end.line
|
||||||
oldFileStartLine = lastDiff.originalRange.start.line
|
// oldFileStartLine = lastDiff.originalRange.start.line
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
throw new Error(`updateStream: diff.type not recognized: ${lastDiff.type}`)
|
// throw new Error(`updateStream: diff.type not recognized: ${lastDiff.type}`)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// display
|
// // display
|
||||||
const newFileTop = newDiffAreaCode.split('\n').slice(0, newFileEndLine + 1).join('\n')
|
// const newFileTop = newDiffAreaCode.split('\n').slice(0, newFileEndLine + 1).join('\n')
|
||||||
const oldFileBottom = originalDiffAreaCode.split('\n').slice(oldFileStartLine + 1, Infinity).join('\n')
|
// const oldFileBottom = originalDiffAreaCode.split('\n').slice(oldFileStartLine + 1, Infinity).join('\n')
|
||||||
|
|
||||||
let newCode = `${newFileTop}\n${oldFileBottom}`
|
// let newCode = `${newFileTop}\n${oldFileBottom}`
|
||||||
diffArea.sweepIndex = newFileEndLine
|
// diffArea.sweepIndex = newFileEndLine
|
||||||
// replace oldDACode with newDACode with a vscode edit
|
// // replace oldDACode with newDACode with a vscode edit
|
||||||
|
|
||||||
const workspaceEdit = new vscode.WorkspaceEdit();
|
// const workspaceEdit = new vscode.WorkspaceEdit();
|
||||||
|
|
||||||
const diffareaRange = new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER)
|
// const diffareaRange = new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER)
|
||||||
workspaceEdit.replace(editor.document.uri, diffareaRange, newCode)
|
// workspaceEdit.replace(editor.document.uri, diffareaRange, newCode)
|
||||||
await vscode.workspace.applyEdit(workspaceEdit)
|
// await vscode.workspace.applyEdit(workspaceEdit)
|
||||||
}, THROTTLE_TIME)
|
// }, THROTTLE_TIME)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export function findDiffs(oldStr: string, newStr: string) {
|
||||||
|
|
||||||
let originalStartLine = streakStartInOldFile!
|
let originalStartLine = streakStartInOldFile!
|
||||||
let originalEndLine = oldFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
|
let originalEndLine = oldFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
|
||||||
let originalStartCol = 0
|
// let originalStartCol = 0
|
||||||
// let originalEndCol = Number.MAX_SAFE_INTEGER
|
// let originalEndCol = Number.MAX_SAFE_INTEGER
|
||||||
|
|
||||||
let newContent = newStrLines.slice(startLine, endLine + 1).join('\n')
|
let newContent = newStrLines.slice(startLine, endLine + 1).join('\n')
|
||||||
|
|
@ -70,13 +70,15 @@ export function findDiffs(oldStr: string, newStr: string) {
|
||||||
else if (originalEndLine === originalStartLine - 1) {
|
else if (originalEndLine === originalStartLine - 1) {
|
||||||
type = 'insertion'
|
type = 'insertion'
|
||||||
originalEndLine = originalStartLine
|
originalEndLine = originalStartLine
|
||||||
originalStartCol = 0
|
// originalStartCol = 0
|
||||||
// originalEndCol = 0
|
// originalEndCol = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const replacement: BaseDiff = {
|
const replacement: BaseDiff = {
|
||||||
type,
|
type,
|
||||||
startLine, startCol, endLine, endCol,
|
startLine, endLine,
|
||||||
|
startCol, endCol,
|
||||||
|
originalStartLine, originalEndLine,
|
||||||
// code: newContent,
|
// code: newContent,
|
||||||
// originalRange: new Range(originalStartLine, originalStartCol, originalEndLine, originalEndCol),
|
// originalRange: new Range(originalStartLine, originalStartCol, originalEndLine, originalEndCol),
|
||||||
originalCode: originalContent,
|
originalCode: originalContent,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
|
|
||||||
import { ChatFile, ChatCodeSelection } from '../sidebar-tsx/SidebarChat.js';
|
import { URI } from '../../../../../base/common/uri.js';
|
||||||
|
|
||||||
export const filesStr = (fullFiles: ChatFile[]) => {
|
|
||||||
|
export type LLMCodeSelection = { selectionStr: string; filePath: URI }
|
||||||
|
export type LLMFile = { content: string, filepath: URI }
|
||||||
|
|
||||||
|
export const filesStr = (fullFiles: LLMFile[]) => {
|
||||||
return fullFiles.map(({ filepath, content }) =>
|
return fullFiles.map(({ filepath, content }) =>
|
||||||
`
|
`
|
||||||
${filepath.fsPath}
|
${filepath.fsPath}
|
||||||
|
|
@ -11,7 +15,7 @@ ${content}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const userInstructionsStr = (instructions: string, files: ChatFile[], selection: ChatCodeSelection | null) => {
|
export const userInstructionsStr = (instructions: string, files: LLMFile[], selection: LLMCodeSelection | null) => {
|
||||||
let str = '';
|
let str = '';
|
||||||
|
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { ReactNode, useCallback, useEffect, useState } from "react"
|
import React, { ReactNode } from "react"
|
||||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||||
import { atomOneDarkReasonable } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
import { atomOneDarkReasonable } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||||
|
|
||||||
|
|
@ -16,9 +16,9 @@ export const BlockCode = ({ text, buttonsOnHover, language }: { text: string, bu
|
||||||
return (<>
|
return (<>
|
||||||
<div className={`relative group w-full bg-vscode-sidebar-bg overflow-hidden isolate`}>
|
<div className={`relative group w-full bg-vscode-sidebar-bg overflow-hidden isolate`}>
|
||||||
|
|
||||||
{!toolbar ? null : (
|
{buttonsOnHover === null ? null : (
|
||||||
<div className="absolute top-0 right-0 opacity-0 group-hover:opacity-100 duration-200">
|
<div className="absolute top-0 right-0 opacity-0 group-hover:opacity-100 duration-200">
|
||||||
<div className="flex space-x-2 p-2">{buttonsOnHover === null ? null : buttonsOnHover}</div>
|
<div className="flex space-x-2 p-2">{buttonsOnHover}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { JSX, useCallback, useEffect, useState } from "react"
|
import React, { JSX, useCallback, useEffect, useState } from "react"
|
||||||
import { marked, MarkedToken, Token, TokensList } from "marked"
|
import { marked, MarkedToken, Token } from "marked"
|
||||||
import { BlockCode } from "./BlockCode.js"
|
import { BlockCode } from "./BlockCode.js"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,23 @@
|
||||||
import React, { FormEvent, useCallback, useRef, useState } from 'react';
|
import React, { FormEvent, useCallback, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { generateDiffInstructions } from '../prompt/systemPrompts.js';
|
|
||||||
|
|
||||||
import { useConfigState, useService, useSidebarState, useThreadsState } from '../util/contextForServices.js';
|
import { useConfigState, useService, useSidebarState, useThreadsState } from '../util/contextForServices.js';
|
||||||
import { URI } from '../../../../../../../base/common/uri.js';
|
import { URI } from '../../../../../../../base/common/uri.js';
|
||||||
import { IFileService } from '../../../../../../../platform/files/common/files.js';
|
import { VSReadFile } from '../../../registerInlineDiffs.js';
|
||||||
import { userInstructionsStr } from '../prompt/stringifyFiles.js';
|
|
||||||
import { sendLLMMessage } from '../util/sendLLMMessage.js';
|
import { sendLLMMessage } from '../util/sendLLMMessage.js';
|
||||||
|
import { generateDiffInstructions } from '../../../prompt/systemPrompts.js';
|
||||||
|
import { LLMCodeSelection, userInstructionsStr } from '../../../prompt/stringifyFiles.js';
|
||||||
|
|
||||||
import { BlockCode } from '../markdown/BlockCode.js';
|
import { BlockCode } from '../markdown/BlockCode.js';
|
||||||
import { MarkdownRender } from '../markdown/MarkdownRender.js';
|
import { MarkdownRender } from '../markdown/MarkdownRender.js';
|
||||||
|
|
||||||
// read files from VSCode
|
|
||||||
let VSReadFile = async (fileService: IFileService, filepath: URI): Promise<ChatFile | null> => {
|
|
||||||
try {
|
|
||||||
const fileObj = await fileService.readFile(filepath)
|
|
||||||
const content = fileObj.value.toString()
|
|
||||||
return { filepath, content }
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to read ${filepath}:`, error);
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export type ChatCodeSelection = { selectionStr: string; filePath: URI }
|
|
||||||
|
|
||||||
export type ChatFile = { filepath: URI; content: string }
|
|
||||||
|
|
||||||
export type ChatMessage =
|
export type ChatMessage =
|
||||||
| {
|
| {
|
||||||
role: 'user';
|
role: 'user';
|
||||||
content: string; // content sent to the llm
|
content: string; // content sent to the llm
|
||||||
displayContent: string; // content displayed to user
|
displayContent: string; // content displayed to user
|
||||||
selection: ChatCodeSelection | null; // the user's selection
|
selection: LLMCodeSelection | null; // the user's selection
|
||||||
files: URI[]; // the files sent in the message
|
files: URI[]; // the files sent in the message
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
|
@ -147,7 +131,7 @@ export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject<HT
|
||||||
|
|
||||||
// ----- SIDEBAR CHAT state (local) -----
|
// ----- SIDEBAR CHAT state (local) -----
|
||||||
// state of current message
|
// state of current message
|
||||||
const [selection, setSelection] = useState<ChatCodeSelection | null>(null) // the code the user is selecting
|
const [selection, setSelection] = useState<LLMCodeSelection | null>(null) // the code the user is selecting
|
||||||
const [files, setFiles] = useState<URI[]>([]) // the names of the files in the chat
|
const [files, setFiles] = useState<URI[]>([]) // the names of the files in the chat
|
||||||
const [instructions, setInstructions] = useState('') // the user's instructions
|
const [instructions, setInstructions] = useState('') // the user's instructions
|
||||||
|
|
||||||
|
|
@ -181,9 +165,9 @@ export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject<HT
|
||||||
|
|
||||||
|
|
||||||
const relevantFiles = await Promise.all(
|
const relevantFiles = await Promise.all(
|
||||||
files.map((filepath) => VSReadFile(fileService, filepath))
|
files.map(async (filepath) => ({ content: await VSReadFile(fileService, filepath), filepath }))
|
||||||
).then(
|
).then(
|
||||||
(files) => files.filter(file => file !== null)
|
(files) => files.filter(file => file.content !== null) as {content:string, filepath:URI}[]
|
||||||
)
|
)
|
||||||
|
|
||||||
// add system message to chat history
|
// add system message to chat history
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ import Anthropic from '@anthropic-ai/sdk';
|
||||||
import OpenAI from 'openai';
|
import OpenAI from 'openai';
|
||||||
import { Ollama } from 'ollama/browser'
|
import { Ollama } from 'ollama/browser'
|
||||||
import { Content, GoogleGenerativeAI, GoogleGenerativeAIFetchError } from '@google/generative-ai';
|
import { Content, GoogleGenerativeAI, GoogleGenerativeAIFetchError } from '@google/generative-ai';
|
||||||
|
import { VoidConfig } from '../../../registerConfig.js';
|
||||||
// import { VoidConfig } from '../webviews/common/contextForConfig'
|
// import { VoidConfig } from '../webviews/common/contextForConfig'
|
||||||
// import { captureEvent } from '../webviews/common/posthog';
|
// import { captureEvent } from '../webviews/common/posthog';
|
||||||
// import { ChatMessage } from './shared_types';
|
// import { ChatMessage } from './shared_types';
|
||||||
|
|
||||||
type VoidConfig = any
|
|
||||||
|
|
||||||
export type AbortRef = { current: (() => void) | null }
|
export type AbortRef = { current: (() => void) | null }
|
||||||
|
|
||||||
export type OnText = (newText: string, fullText: string) => void
|
export type OnText = (newText: string, fullText: string) => void
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,6 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src/**/*.ts",
|
"./src/**/*.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,335 +0,0 @@
|
||||||
|
|
||||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
|
||||||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
|
||||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
|
||||||
import { ICodeEditor, IViewZone } from '../../../../editor/browser/editorBrowser.js';
|
|
||||||
|
|
||||||
import { IUndoRedoElement, IUndoRedoService, UndoRedoElementType, UndoRedoGroup } from '../../../../platform/undoRedo/common/undoRedo.js';
|
|
||||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
|
||||||
import { IBulkEditService } from '../../../../editor/browser/services/bulkEditService.js';
|
|
||||||
import { WorkspaceEdit } from 'vscode';
|
|
||||||
import { IModelDeltaDecoration } from '../../../../editor/common/model.js';
|
|
||||||
import { IRange } from '../../../../editor/common/core/range.js';
|
|
||||||
import { EditorOption } from '../../../../editor/common/config/editorOptions.js';
|
|
||||||
|
|
||||||
|
|
||||||
// if (m.type === 'applyChanges') {
|
|
||||||
|
|
||||||
// const editor = vscode.window.activeTextEditor
|
|
||||||
// if (!editor) {
|
|
||||||
// vscode.window.showInformationMessage('No active editor!')
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// // create an area to show diffs
|
|
||||||
// const partialDiffArea: Omit<DiffArea, 'diffareaid'> = {
|
|
||||||
// startLine: 0, // in ctrl+L the start and end lines are the full document
|
|
||||||
// endLine: editor.document.lineCount,
|
|
||||||
// originalStartLine: 0,
|
|
||||||
// originalEndLine: editor.document.lineCount,
|
|
||||||
// sweepIndex: null,
|
|
||||||
// }
|
|
||||||
// const diffArea = diffProvider.createDiffArea(editor.document.uri, partialDiffArea, await readFileContentOfUri(editor.document.uri))
|
|
||||||
|
|
||||||
// const docUri = editor.document.uri
|
|
||||||
// const fileStr = await readFileContentOfUri(docUri)
|
|
||||||
// const voidConfig = getVoidConfigFromPartial(context.globalState.get('partialVoidConfig') ?? {})
|
|
||||||
|
|
||||||
// await diffProvider.startStreamingInDiffArea({ docUri, oldFileStr: fileStr, diffRepr: m.diffRepr, voidConfig, diffArea, abortRef: abortApplyRef })
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// // an area that is currently being diffed
|
|
||||||
type DiffArea = {
|
|
||||||
diffareaid: number,
|
|
||||||
startLine: number,
|
|
||||||
endLine: number,
|
|
||||||
originalStartLine: number,
|
|
||||||
originalEndLine: number,
|
|
||||||
sweepIndex: number | null // null iff not sweeping
|
|
||||||
}
|
|
||||||
|
|
||||||
// the return type of diff creator
|
|
||||||
type BaseDiff = {
|
|
||||||
type: 'edit' | 'insertion' | 'deletion';
|
|
||||||
// repr: string; // representation of the diff in text
|
|
||||||
originalRange: IRange;
|
|
||||||
originalCode: string;
|
|
||||||
range: IRange;
|
|
||||||
code: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// each diff on the user's screen
|
|
||||||
type Diff = BaseDiff & {
|
|
||||||
diffid: number,
|
|
||||||
lenses: CodeLens[],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface IInlineDiffService {
|
|
||||||
readonly _serviceBrand: undefined;
|
|
||||||
addDiff(editor: ICodeEditor, originalText: string, modifiedRange: IRange): void;
|
|
||||||
removeDiffs(editor: ICodeEditor): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IInlineDiffService = createDecorator<IInlineDiffService>('inlineDiffService');
|
|
||||||
|
|
||||||
class InlineDiffService extends Disposable implements IInlineDiffService {
|
|
||||||
private readonly _diffDecorations = new Map<ICodeEditor, string[]>();
|
|
||||||
private readonly _diffZones = new Map<ICodeEditor, string[]>();
|
|
||||||
|
|
||||||
|
|
||||||
_serviceBrand: undefined;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
initStream() {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public addDiff: IInlineDiffService['addDiff'] = (editor, originalText, modifiedRange) => {
|
|
||||||
// Clear existing diffs
|
|
||||||
this.removeDiffs(editor);
|
|
||||||
|
|
||||||
// green decoration and gutter decoration
|
|
||||||
const greenDecoration: IModelDeltaDecoration[] = [{
|
|
||||||
range: modifiedRange,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
this._diffDecorations.set(editor, editor.deltaDecorations([], greenDecoration));
|
|
||||||
|
|
||||||
// red in a view zone
|
|
||||||
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 element
|
|
||||||
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: modifiedRange.startLineNumber - 1,
|
|
||||||
heightInLines: originalText.split('\n').length + 1,
|
|
||||||
domNode: domNode,
|
|
||||||
suppressMouseDown: true,
|
|
||||||
marginDomNode: gutterDiv
|
|
||||||
};
|
|
||||||
|
|
||||||
const zoneId = accessor.addZone(viewZone);
|
|
||||||
// editor.layout();
|
|
||||||
this._diffZones.set(editor, [zoneId]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public removeDiffs(editor: ICodeEditor): void {
|
|
||||||
const decorationIds = this._diffDecorations.get(editor) || [];
|
|
||||||
editor.deltaDecorations(decorationIds, []);
|
|
||||||
this._diffDecorations.delete(editor);
|
|
||||||
|
|
||||||
editor.changeViewZones(accessor => {
|
|
||||||
const zoneIds = this._diffZones.get(editor) || [];
|
|
||||||
zoneIds.forEach(id => accessor.removeZone(id));
|
|
||||||
});
|
|
||||||
this._diffZones.delete(editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
override dispose(): void {
|
|
||||||
super.dispose();
|
|
||||||
this._diffDecorations.clear();
|
|
||||||
this._diffZones.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerSingleton(IInlineDiffService, InlineDiffService, InstantiationType.Eager);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class StreamManager extends Disposable {
|
|
||||||
|
|
||||||
|
|
||||||
// private readonly _disposables = new DisposableStore();
|
|
||||||
|
|
||||||
_streamingState: { type: 'streaming'; editGroup: UndoRedoGroup } | { type: 'idle' } = { type: 'idle' }
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@IInlineDiffService private readonly _inlineDiff: IInlineDiffService,
|
|
||||||
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
|
||||||
// @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right
|
|
||||||
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z
|
|
||||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
|
||||||
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
startStreaming(editorId: string) {
|
|
||||||
const editor = this._getEditor(editorId)
|
|
||||||
if (!editor) return
|
|
||||||
|
|
||||||
const model = editor.getModel()
|
|
||||||
if (!model) return
|
|
||||||
|
|
||||||
// all changes made when streaming should be a part of the group so we can undo them all together
|
|
||||||
this._streamingState = {
|
|
||||||
type: 'streaming',
|
|
||||||
editGroup: new UndoRedoGroup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO probably need to convert this to a stack
|
|
||||||
const diffsSnapshotBefore = { placeholder: '' }
|
|
||||||
const diffsSnapshotAfter = { placeholder: '' }
|
|
||||||
|
|
||||||
const elt: IUndoRedoElement = {
|
|
||||||
type: UndoRedoElementType.Resource,
|
|
||||||
resource: model.uri,
|
|
||||||
label: 'Add Diffs',
|
|
||||||
code: 'undoredo.inlineDiff',
|
|
||||||
undo: () => {
|
|
||||||
// reapply diffareas and diffs here
|
|
||||||
console.log('reverting diffareas...', diffsSnapshotBefore.placeholder)
|
|
||||||
},
|
|
||||||
redo: () => {
|
|
||||||
// reapply diffareas and diffs here
|
|
||||||
// when done, need to record diffSnapshotAfter
|
|
||||||
console.log('re-applying diffareas...', diffsSnapshotAfter.placeholder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._undoRedoService.pushElement(elt, this._streamingState.editGroup)
|
|
||||||
|
|
||||||
// ---------- START ----------
|
|
||||||
editor.updateOptions({ readOnly: true })
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ---------- WHEN DONE ----------
|
|
||||||
editor.updateOptions({ readOnly: false })
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
streamChange(editorId: string, edit: WorkspaceEdit) {
|
|
||||||
const editor = this._getEditor(editorId)
|
|
||||||
if (!editor) return
|
|
||||||
|
|
||||||
if (this._streamingState.type !== 'streaming') {
|
|
||||||
console.error('Expected streamChange to be in state \'streaming\'.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// count all changes towards the group
|
|
||||||
this._bulkEditService.apply(edit, { undoRedoGroupId: this._streamingState.editGroup.id, })
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_getEditor = (editorId: string): ICodeEditor | undefined => {
|
|
||||||
|
|
||||||
let editor: ICodeEditor | undefined;
|
|
||||||
editorId = editorId.substr(0, editorId.indexOf(',')); //todo@jrieken HACK
|
|
||||||
|
|
||||||
for (const candidate of this._editorService.listCodeEditors()) {
|
|
||||||
if (candidate.getId() === editorId
|
|
||||||
// && candidate.hasModel() && isEqual(candidate.getModel().uri, URI.revive(uri))
|
|
||||||
) {
|
|
||||||
editor = candidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return editor
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$addDiff(editorId: string, originalText: string, range: IRange): void {
|
|
||||||
|
|
||||||
const editor = this._getEditor(editorId);
|
|
||||||
if (!editor) return
|
|
||||||
|
|
||||||
this._inlineDiff.addDiff(editor, originalText, range)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// // Void created this file
|
|
||||||
// // it comes from mainThreadCodeInsets.ts
|
|
||||||
|
|
||||||
// import { Disposable } from '../../../base/common/lifecycle.js';
|
|
||||||
// import { ICodeEditorService } from '../../../editor/browser/services/codeEditorService.js';
|
|
||||||
// import { MainContext, MainThreadInlineDiffShape } from '../common/extHost.protocol.js';
|
|
||||||
// import { IInlineDiffService } from '../../../editor/browser/services/inlineDiffService/inlineDiffService.js';
|
|
||||||
// import { ICodeEditor } from '../../../editor/browser/editorBrowser.js';
|
|
||||||
// import { IRange } from '../../../editor/common/core/range.js';
|
|
||||||
// import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';
|
|
||||||
// import { IUndoRedoElement, IUndoRedoService, UndoRedoElementType, UndoRedoGroup } from '../../../platform/undoRedo/common/undoRedo.js';
|
|
||||||
// import { IBulkEditService } from '../../../editor/browser/services/bulkEditService.js';
|
|
||||||
// import { WorkspaceEdit } from '../../../editor/common/languages.js';
|
|
||||||
// // import { IHistoryService } from '../../services/history/common/history.js';
|
|
||||||
|
|
@ -6,62 +6,78 @@ import { ICodeEditor, IViewZone } from '../../../../editor/browser/editorBrowser
|
||||||
|
|
||||||
import { IUndoRedoElement, IUndoRedoService, UndoRedoElementType, UndoRedoGroup } from '../../../../platform/undoRedo/common/undoRedo.js';
|
import { IUndoRedoElement, IUndoRedoService, UndoRedoElementType, UndoRedoGroup } from '../../../../platform/undoRedo/common/undoRedo.js';
|
||||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||||
import { IBulkEditService } from '../../../../editor/browser/services/bulkEditService.js';
|
import { IBulkEditService, ResourceTextEdit } from '../../../../editor/browser/services/bulkEditService.js';
|
||||||
import { WorkspaceEdit } from 'vscode';
|
|
||||||
import { EditorOption } from '../../../../editor/common/config/editorOptions.js';
|
|
||||||
import { Emitter } from '../../../../base/common/event.js';
|
import { Emitter } from '../../../../base/common/event.js';
|
||||||
|
import { sendLLMMessage } from './out/util/sendLLMMessage.js';
|
||||||
|
import { throttle } from '../../../../base/common/decorators.js';
|
||||||
|
import { IFileService } from '../../../../platform/files/common/files.js';
|
||||||
|
import { URI } from '../../../../base/common/uri.js';
|
||||||
|
import { IVoidConfigStateService } from './registerConfig.js';
|
||||||
|
import { writeFileWithDiffInstructions } from './prompt/systemPrompts.js';
|
||||||
|
import { findDiffs } from './findDiffs.js';
|
||||||
|
|
||||||
|
|
||||||
|
// read files from VSCode
|
||||||
|
export const VSReadFile = async (fileService: IFileService, uri: URI): Promise<string | null> => {
|
||||||
|
try {
|
||||||
|
const fileObj = await fileService.readFile(uri)
|
||||||
|
const content = fileObj.value.toString()
|
||||||
|
return content
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`VSReadFile (Void) - Failed to read URI`, uri, error);
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// red in a view zone
|
// // red in a view zone
|
||||||
editor.changeViewZones(accessor => {
|
// editor.changeViewZones(accessor => {
|
||||||
// Get the editor's font info
|
// // Get the editor's font info
|
||||||
const fontInfo = editor.getOption(EditorOption.fontInfo);
|
// const fontInfo = editor.getOption(EditorOption.fontInfo);
|
||||||
|
|
||||||
const domNode = document.createElement('div');
|
// const domNode = document.createElement('div');
|
||||||
domNode.className = 'monaco-editor view-zones line-delete monaco-mouse-cursor-text';
|
// domNode.className = 'monaco-editor view-zones line-delete monaco-mouse-cursor-text';
|
||||||
domNode.style.fontSize = `${fontInfo.fontSize}px`;
|
// domNode.style.fontSize = `${fontInfo.fontSize}px`;
|
||||||
domNode.style.fontFamily = fontInfo.fontFamily;
|
// domNode.style.fontFamily = fontInfo.fontFamily;
|
||||||
domNode.style.lineHeight = `${fontInfo.lineHeight}px`;
|
// domNode.style.lineHeight = `${fontInfo.lineHeight}px`;
|
||||||
|
|
||||||
// div
|
// // div
|
||||||
const lineContent = document.createElement('div');
|
// const lineContent = document.createElement('div');
|
||||||
lineContent.className = 'view-line'; // .monaco-editor .inline-deleted-text
|
// lineContent.className = 'view-line'; // .monaco-editor .inline-deleted-text
|
||||||
|
|
||||||
// span
|
// // span
|
||||||
const contentSpan = document.createElement('span');
|
// const contentSpan = document.createElement('span');
|
||||||
|
|
||||||
// span
|
// // span
|
||||||
const codeSpan = document.createElement('span');
|
// const codeSpan = document.createElement('span');
|
||||||
codeSpan.className = 'mtk1'; // char-delete
|
// codeSpan.className = 'mtk1'; // char-delete
|
||||||
codeSpan.textContent = originalText;
|
// codeSpan.textContent = originalText;
|
||||||
|
|
||||||
// Mount
|
// // Mount
|
||||||
contentSpan.appendChild(codeSpan);
|
// contentSpan.appendChild(codeSpan);
|
||||||
lineContent.appendChild(contentSpan);
|
// lineContent.appendChild(contentSpan);
|
||||||
domNode.appendChild(lineContent);
|
// domNode.appendChild(lineContent);
|
||||||
|
|
||||||
// gutter element
|
// // gutter element
|
||||||
const gutterDiv = document.createElement('div');
|
// const gutterDiv = document.createElement('div');
|
||||||
gutterDiv.className = 'inline-diff-gutter';
|
// gutterDiv.className = 'inline-diff-gutter';
|
||||||
const minusDiv = document.createElement('div');
|
// const minusDiv = document.createElement('div');
|
||||||
minusDiv.className = 'inline-diff-deleted-gutter';
|
// minusDiv.className = 'inline-diff-deleted-gutter';
|
||||||
// minusDiv.textContent = '-';
|
// // minusDiv.textContent = '-';
|
||||||
gutterDiv.appendChild(minusDiv);
|
// gutterDiv.appendChild(minusDiv);
|
||||||
|
|
||||||
const viewZone: IViewZone = {
|
// const viewZone: IViewZone = {
|
||||||
afterLineNumber: modifiedRange.startLineNumber - 1,
|
// afterLineNumber: modifiedRange.startLineNumber - 1,
|
||||||
heightInLines: originalText.split('\n').length + 1,
|
// heightInLines: originalText.split('\n').length + 1,
|
||||||
domNode: domNode,
|
// domNode: domNode,
|
||||||
suppressMouseDown: true,
|
// suppressMouseDown: true,
|
||||||
marginDomNode: gutterDiv
|
// marginDomNode: gutterDiv
|
||||||
};
|
// };
|
||||||
|
|
||||||
const zoneId = accessor.addZone(viewZone);
|
// const zoneId = accessor.addZone(viewZone);
|
||||||
// editor.layout();
|
// // editor.layout();
|
||||||
this._diffZones.set(editor, [zoneId]);
|
// this._diffZones.set(editor, [zoneId]);
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -94,10 +110,16 @@ editor.changeViewZones(accessor => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// override dispose(): void {
|
// public removeAllDiffs(editor: ICodeEditor): void {
|
||||||
// super.dispose();
|
// const decorationIds = this._diffDecorations.get(editor) || [];
|
||||||
// this._diffDecorations.clear();
|
// editor.deltaDecorations(decorationIds, []);
|
||||||
// this._diffZones.clear();
|
// this._diffDecorations.delete(editor);
|
||||||
|
|
||||||
|
// editor.changeViewZones(accessor => {
|
||||||
|
// const zoneIds = this._diffZones.get(editor) || [];
|
||||||
|
// zoneIds.forEach(id => accessor.removeZone(id));
|
||||||
|
// });
|
||||||
|
// this._diffZones.delete(editor);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -105,41 +127,32 @@ editor.changeViewZones(accessor => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public removeAllDiffs(editor: ICodeEditor): void {
|
// _ means computed later, temporary, or part of current state
|
||||||
const decorationIds = this._diffDecorations.get(editor) || [];
|
|
||||||
editor.deltaDecorations(decorationIds, []);
|
|
||||||
this._diffDecorations.delete(editor);
|
|
||||||
|
|
||||||
editor.changeViewZones(accessor => {
|
|
||||||
const zoneIds = this._diffZones.get(editor) || [];
|
|
||||||
zoneIds.forEach(id => accessor.removeZone(id));
|
|
||||||
});
|
|
||||||
this._diffZones.delete(editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// _ means computed / temporary
|
|
||||||
type DiffArea = {
|
type DiffArea = {
|
||||||
diffareaid: string,
|
diffareaid: number,
|
||||||
|
originalStartLine: number,
|
||||||
|
originalEndLine: number,
|
||||||
startLine: number,
|
startLine: number,
|
||||||
endLine: number,
|
endLine: number,
|
||||||
|
|
||||||
|
_uri: URI, // document uri
|
||||||
|
_streamId: number,
|
||||||
_diffIds: string[],
|
_diffIds: string[],
|
||||||
_sweepIdx: number | null,
|
_sweepLine: number | null,
|
||||||
|
_sweepCol: number | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type Diff = {
|
export type Diff = {
|
||||||
diffid: string,
|
diffid: number,
|
||||||
diffareaid: string, // the diff area this diff belongs to, "computed"
|
diffareaid: number, // the diff area this diff belongs to, "computed"
|
||||||
type: 'edit' | 'insertion' | 'deletion';
|
type: 'edit' | 'insertion' | 'deletion';
|
||||||
originalCode: string;
|
originalCode: string;
|
||||||
|
|
||||||
startLine: number;
|
startLine: number;
|
||||||
endLine: number;
|
endLine: number;
|
||||||
|
originalStartLine: number;
|
||||||
|
originalEndLine: number;
|
||||||
|
|
||||||
startCol: number;
|
startCol: number;
|
||||||
endCol: number;
|
endCol: number;
|
||||||
|
|
@ -163,6 +176,12 @@ type HistorySnapshot = {
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
type StreamingState = {
|
||||||
|
type: 'streaming';
|
||||||
|
editGroup: UndoRedoGroup; // all changes made by us when streaming should be a part of the group so we can undo them all together
|
||||||
|
} | {
|
||||||
|
type: 'idle';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IInlineDiffsService {
|
export interface IInlineDiffsService {
|
||||||
|
|
@ -174,43 +193,49 @@ export const IInlineDiffsService = createDecorator<IInlineDiffsService>('inlineD
|
||||||
class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
_serviceBrand: undefined;
|
_serviceBrand: undefined;
|
||||||
|
|
||||||
|
// state of each document (uri)
|
||||||
|
diffAreasOfURI: Record<string, string[]> = {} // uriStr -> diffAreaId[]
|
||||||
|
originalFileOfURI: Record<string, string> = {} // uriStr -> originalFile
|
||||||
|
streamingStateOfURI: Record<string, StreamingState> = {} // uriStr -> state
|
||||||
|
|
||||||
diffAreaOfId: Map<string, DiffArea> = new Map();
|
diffAreaOfId: Map<string, DiffArea> = new Map();
|
||||||
diffOfId: Map<string, Diff> = new Map();
|
diffOfId: Map<string, Diff> = new Map();
|
||||||
|
|
||||||
|
_streamIdPool = 0
|
||||||
streamingState: {
|
_diffareaIdPool = 0
|
||||||
type: 'streaming';
|
|
||||||
editGroup: UndoRedoGroup;
|
|
||||||
} | { type: 'idle' }
|
|
||||||
= { type: 'idle' }
|
|
||||||
|
|
||||||
|
|
||||||
private readonly _onDidFinishStreaming = new Emitter<void>();
|
private readonly _onDidFinishStreaming = new Emitter<void>();
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
// @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right
|
// @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right
|
||||||
@IInlineDiffsService private readonly _inlineDiff: IInlineDiffsService,
|
|
||||||
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
||||||
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z
|
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z
|
||||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||||
|
@IFileService private readonly _fileService: IFileService,
|
||||||
|
@IVoidConfigStateService private readonly _voidConfigStateService: IVoidConfigStateService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
|
||||||
|
// // 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)
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// listen for document changes, and re-add the diffAreas of this document
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
startStreaming() {
|
private _addToHistory(uri: URI, editGroup: UndoRedoGroup) {
|
||||||
const editor = this._editorService.getActiveCodeEditor()
|
|
||||||
if (!editor) return
|
|
||||||
|
|
||||||
const model = editor.getModel()
|
|
||||||
if (!model) return
|
|
||||||
|
|
||||||
// all changes made by us when streaming should be a part of the group so we can undo them all together
|
|
||||||
this.streamingState = {
|
|
||||||
type: 'streaming',
|
|
||||||
editGroup: new UndoRedoGroup(),
|
|
||||||
}
|
|
||||||
|
|
||||||
const beforeSnapshot: HistorySnapshot = {
|
const beforeSnapshot: HistorySnapshot = {
|
||||||
diffAreaOfId: new Map(this.diffAreaOfId),
|
diffAreaOfId: new Map(this.diffAreaOfId),
|
||||||
|
|
@ -232,7 +257,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
|
|
||||||
const elt: IUndoRedoElement = {
|
const elt: IUndoRedoElement = {
|
||||||
type: UndoRedoElementType.Resource,
|
type: UndoRedoElementType.Resource,
|
||||||
resource: model.uri,
|
resource: uri,
|
||||||
label: 'Add Diffs',
|
label: 'Add Diffs',
|
||||||
code: 'undoredo.inlineDiffs',
|
code: 'undoredo.inlineDiffs',
|
||||||
// called when undoing this state
|
// called when undoing this state
|
||||||
|
|
@ -249,41 +274,314 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
this.diffOfId = new Map(afterSnapshot.diffOfId)
|
this.diffOfId = new Map(afterSnapshot.diffOfId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this._undoRedoService.pushElement(elt, editGroup)
|
||||||
this._undoRedoService.pushElement(elt, this.streamingState.editGroup)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ---------- START ----------
|
|
||||||
editor.updateOptions({ readOnly: true })
|
|
||||||
|
|
||||||
|
|
||||||
// ---------- WHEN DONE ----------
|
|
||||||
editor.updateOptions({ readOnly: false })
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private async _initializeStream(editor: ICodeEditor, diffRepr: string) {
|
||||||
|
|
||||||
private _streamChange(editor: ICodeEditor, edit: WorkspaceEdit) {
|
const model = editor.getModel()
|
||||||
|
if (!model) return
|
||||||
|
|
||||||
// count all changes towards the group
|
const uri = model.uri
|
||||||
this._bulkEditService.apply(edit, { undoRedoGroupId: this._streamingState.editGroup.id, })
|
const uriStr = uri.toString()
|
||||||
|
console.log('Model URI:', uriStr)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
endStreaming() {
|
// create a diffArea for the stream
|
||||||
|
const diffareaid = this._diffareaIdPool++
|
||||||
|
const streamId = this._streamIdPool++
|
||||||
|
|
||||||
|
// in ctrl+L the start and end lines are the full document
|
||||||
|
const lineCount = model.getLineCount()
|
||||||
|
const diffArea: DiffArea = {
|
||||||
|
diffareaid: diffareaid,
|
||||||
|
originalStartLine: 0,
|
||||||
|
originalEndLine: lineCount,
|
||||||
|
startLine: 0,
|
||||||
|
endLine: lineCount, // starts out the same as the current file
|
||||||
|
_uri: uri,
|
||||||
|
_sweepLine: null,
|
||||||
|
_sweepCol: null,
|
||||||
|
_streamId: streamId,
|
||||||
|
_diffIds: [], // added later
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const originalFileStr = await VSReadFile(this._fileService, uri)
|
||||||
|
if (originalFileStr === null) return
|
||||||
|
|
||||||
|
this.originalFileOfURI[uriStr] = originalFileStr
|
||||||
|
|
||||||
|
// make sure array is defined
|
||||||
|
if (!(uriStr in this.diffAreasOfURI))
|
||||||
|
this.diffAreasOfURI[uriStr] = []
|
||||||
|
|
||||||
|
// remove all diffAreas that the new `diffArea` is overlapping with
|
||||||
|
this.diffAreasOfURI[uriStr] = this.diffAreasOfURI[uriStr].filter(diffareaid => {
|
||||||
|
const da2 = this.diffAreaOfId.get(diffareaid)
|
||||||
|
if (!da2) return false
|
||||||
|
const noOverlap = da2.startLine > diffArea.endLine || da2.endLine < diffArea.startLine
|
||||||
|
if (!noOverlap) return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// add `diffArea` to storage
|
||||||
|
this.diffAreasOfURI[uriStr].push(diffArea.diffareaid.toString())
|
||||||
|
|
||||||
|
// actually call the LLM
|
||||||
|
const voidConfig = this._voidConfigStateService.state
|
||||||
|
const promptContent = `\
|
||||||
|
ORIGINAL_FILE
|
||||||
|
\`\`\`
|
||||||
|
${originalFileStr}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
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.
|
||||||
|
`
|
||||||
|
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._onStreamChunk(uri, diffArea, fullText)
|
||||||
|
},
|
||||||
|
onFinalMessage: (fullText) => {
|
||||||
|
this._onStreamChunk(uri, diffArea, fullText)
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
console.error('Error rewriting file with diff', e);
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
voidConfig,
|
||||||
|
abortRef,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
this._onDidFinishStreaming.fire()
|
this._onDidFinishStreaming.fire()
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// used by us only
|
||||||
|
@throttle(100)
|
||||||
|
private async _onStreamChunk(uri: URI, diffArea: DiffArea, newDiffAreaCode: string) {
|
||||||
|
const docUriStr = uri.toString()
|
||||||
|
|
||||||
|
if (this.streamingStateOfURI[docUriStr].type !== 'streaming')
|
||||||
|
return
|
||||||
|
|
||||||
|
// original code all diffs are based on in the code
|
||||||
|
const originalDiffAreaCode = (this.originalFileOfURI[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.endLine
|
||||||
|
oldFileStartLine = lastDiff.originalStartLine
|
||||||
|
}
|
||||||
|
else if (lastDiff.type === 'deletion') {
|
||||||
|
newFileEndLine = lastDiff.startLine
|
||||||
|
oldFileStartLine = lastDiff.originalStartLine
|
||||||
|
}
|
||||||
|
else if (lastDiff.type === 'edit') {
|
||||||
|
newFileEndLine = lastDiff.endLine
|
||||||
|
oldFileStartLine = lastDiff.originalStartLine
|
||||||
|
}
|
||||||
|
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._sweepLine = newFileEndLine
|
||||||
|
|
||||||
|
this._bulkEditService.apply(
|
||||||
|
[new ResourceTextEdit(uri, {
|
||||||
|
range: {
|
||||||
|
startLineNumber: diffArea.startLine,
|
||||||
|
startColumn: 0,
|
||||||
|
endLineNumber: diffArea.endLine,
|
||||||
|
endColumn: Number.MAX_SAFE_INTEGER,
|
||||||
|
},
|
||||||
|
text: newCode
|
||||||
|
})],
|
||||||
|
// count all changes towards the group
|
||||||
|
{ undoRedoGroupId: this.streamingStateOfURI[docUriStr].editGroup.id });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
startStreaming(type: 'ctrl+k' | 'ctrl+l', userMessage: string) {
|
||||||
|
|
||||||
|
const editor = this._editorService.getActiveCodeEditor()
|
||||||
|
if (!editor) return
|
||||||
|
|
||||||
|
const model = editor.getModel()
|
||||||
|
if (!model) return
|
||||||
|
|
||||||
|
// update streaming state
|
||||||
|
const streamingState: StreamingState = { type: 'streaming', editGroup: new UndoRedoGroup(), }
|
||||||
|
this.streamingStateOfURI[model.uri.toString()] = streamingState
|
||||||
|
|
||||||
|
// add to history
|
||||||
|
this._addToHistory(model.uri, streamingState.editGroup)
|
||||||
|
|
||||||
|
// initialize stream
|
||||||
|
this._initializeStream(editor, userMessage)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// called on void.acceptDiff
|
||||||
|
public async acceptDiff({ diffid }: { diffid: number }) {
|
||||||
|
|
||||||
|
const diff = this.diffOfId.get(diffid + '')!
|
||||||
|
if (!diff) return
|
||||||
|
|
||||||
|
const { diffareaid } = diff
|
||||||
|
const diffArea = this.diffAreaOfId.get(diffareaid + '')
|
||||||
|
if (!diffArea) return
|
||||||
|
|
||||||
|
const uri = diffArea._uri
|
||||||
|
const uriStr = uri.toString()
|
||||||
|
|
||||||
|
const originalFile = this.originalFileOfURI[uriStr]
|
||||||
|
const currentFile = await VSReadFile(this._fileService, uri)
|
||||||
|
if (!currentFile) return
|
||||||
|
|
||||||
|
// 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.startLine, diff.endLine + 1);
|
||||||
|
|
||||||
|
// Create new original file content by replacing the affected lines
|
||||||
|
const newOriginalLines = [
|
||||||
|
...originalLines.slice(0, diff.originalStartLine),
|
||||||
|
...changedLines,
|
||||||
|
...originalLines.slice(diff.originalEndLine + 1)
|
||||||
|
];
|
||||||
|
|
||||||
|
this.originalFileOfURI[uriStr] = newOriginalLines.join('\n');
|
||||||
|
|
||||||
|
// // Update diff areas based on the change (this)
|
||||||
|
// this.resizeDiffAreas(uriStr, [{
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerSingleton(IInlineDiffsService, InlineDiffsService, InstantiationType.Eager);
|
registerSingleton(IInlineDiffsService, InlineDiffsService, InstantiationType.Eager);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue