diff --git a/src/vs/workbench/contrib/void/browser/DiffProvider.ts b/src/vs/workbench/contrib/void/browser/DiffProvider.ts index 032b0892..f0f9bed9 100644 --- a/src/vs/workbench/contrib/void/browser/DiffProvider.ts +++ b/src/vs/workbench/contrib/void/browser/DiffProvider.ts @@ -1,15 +1,8 @@ import * as vscode from 'vscode'; -import { findDiffs } from './src/extension/findDiffs'; -import { throttle } from 'lodash'; -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'; +import { findDiffs } from './findDiffs'; +import { DiffArea, Diff } from '../common/shared_types'; -const THROTTLE_TIME = 100 - // TODO in theory this should be disposed const lightGrayDecoration = vscode.window.createTextEditorDecorationType({ backgroundColor: 'rgba(218 218 218 / .2)', @@ -48,52 +41,52 @@ export class DiffProvider implements vscode.CodeLensProvider { console.log('Creating DisplayChangesProvider') - // this acts as a useEffect every time text changes - vscode.workspace.onDidChangeTextDocument((e) => { + // // this acts as a useEffect every time text changes + // 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 changes = e.contentChanges.map(c => ({ startLine: c.range.start.line, endLine: c.range.end.line, text: c.text, })) + // 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') + // // on user change, grow/shrink/merge/delete diff areas + // this.resizeDiffAreas(docUriStr, changes, 'currentFile') - // refresh the diffAreas - this.refreshStylesAndDiffs(docUriStr) + // // refresh the diffAreas + // this.refreshStylesAndDiffs(docUriStr) - }) + // }) } // used by us only - public createDiffArea(uri: vscode.Uri, partialDiffArea: Omit, originalFile: string) { + // public createDiffArea(uri: vscode.Uri, partialDiffArea: Omit, originalFile: string) { - const uriStr = uri.toString() + // const uriStr = uri.toString() - this._originalFileOfDocument[uriStr] = originalFile + // this._originalFileOfDocument[uriStr] = originalFile - // make sure array is defined - if (!this._diffAreasOfDocument[uriStr]) this._diffAreasOfDocument[uriStr] = [] + // // 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 - }) + // // 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 + // // add `diffArea` to storage + // const diffArea = { + // ...partialDiffArea, + // diffareaid: this._diffareaidPool + // } + // this._diffAreasOfDocument[uriStr].push(diffArea) + // this._diffareaidPool += 1 - return diffArea - } + // 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 @@ -244,205 +237,205 @@ export class DiffProvider implements vscode.CodeLensProvider { } - // called on void.acceptDiff - public async acceptDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) { - const editor = vscode.window.activeTextEditor - if (!editor) - return + // // 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 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 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 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) + // 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'); + // // 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); + // // 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) - ]; + // // 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'); + // 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') + // // 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 + // // 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 originalArea = newOriginalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 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') - if (originalArea === currentArea) { - const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid) - this._diffAreasOfDocument[docUriStr].splice(index, 1) - } + // if (originalArea === currentArea) { + // const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid) + // this._diffAreasOfDocument[docUriStr].splice(index, 1) + // } - this.refreshStylesAndDiffs(docUriStr) - } + // this.refreshStylesAndDiffs(docUriStr) + // } - // called on void.rejectDiff - public async rejectDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) { - const editor = vscode.window.activeTextEditor - if (!editor) - return + // // 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 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 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 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 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) + // // 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'); + // // 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') + // 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) - } + // if (originalArea === currentArea) { + // const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid) + // 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 = `\ -ORIGINAL_FILE -\`\`\` -${oldFileStr} -\`\`\` + // const promptContent = `\ + // ORIGINAL_FILE + // \`\`\` + // ${oldFileStr} + // \`\`\` -DIFF -\`\`\` -${diffRepr} -\`\`\` + // 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. + // INSTRUCTIONS + // Please finish writing the new file by applying the diff to the original file. Return ONLY the completion of the file, without any explanation. -` - // make LLM complete the file to include the diff - await new Promise((resolve, reject) => { - sendLLMMessage({ - logging: { loggingName: 'streamChunk' }, - messages: [ - { role: 'system', content: writeFileWithDiffInstructions, }, - // TODO include more context too - { role: 'user', content: promptContent, } - ], - onText: (newText, fullText) => { - this._updateStream(docUri.toString(), diffArea, fullText) - }, - onFinalMessage: (fullText) => { - this._updateStream(docUri.toString(), diffArea, fullText) - resolve(); - }, - onError: (e) => { - console.error('Error rewriting file with diff', e); - resolve(); - }, - voidConfig, - abortRef, - }) - }) + // ` + // // make LLM complete the file to include the diff + // await new Promise((resolve, reject) => { + // sendLLMMessage({ + // logging: { loggingName: 'streamChunk' }, + // messages: [ + // { role: 'system', content: writeFileWithDiffInstructions, }, + // // TODO include more context too + // { role: 'user', content: promptContent, } + // ], + // onText: (newText, fullText) => { + // this._updateStream(docUri.toString(), diffArea, fullText) + // }, + // onFinalMessage: (fullText) => { + // this._updateStream(docUri.toString(), diffArea, fullText) + // resolve(); + // }, + // onError: (e) => { + // console.error('Error rewriting file with diff', e); + // resolve(); + // }, + // voidConfig, + // abortRef, + // }) + // }) - } + // } - // used by us only - private _updateStream = throttle(async (docUriStr: string, diffArea: DiffArea, newDiffAreaCode: string) => { + // // 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; - } + // 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') + // // 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 + // // 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...] + // // 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}`) - } - } + // 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') + // // 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 + // let newCode = `${newFileTop}\n${oldFileBottom}` + // diffArea.sweepIndex = newFileEndLine + // // 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) - workspaceEdit.replace(editor.document.uri, diffareaRange, newCode) - await vscode.workspace.applyEdit(workspaceEdit) - }, THROTTLE_TIME) + // const diffareaRange = new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER) + // workspaceEdit.replace(editor.document.uri, diffareaRange, newCode) + // await vscode.workspace.applyEdit(workspaceEdit) + // }, THROTTLE_TIME) } diff --git a/src/vs/workbench/contrib/void/browser/findDiffs.ts b/src/vs/workbench/contrib/void/browser/findDiffs.ts index a823b0d0..dc6f5e6b 100644 --- a/src/vs/workbench/contrib/void/browser/findDiffs.ts +++ b/src/vs/workbench/contrib/void/browser/findDiffs.ts @@ -50,7 +50,7 @@ export function findDiffs(oldStr: string, newStr: string) { let originalStartLine = streakStartInOldFile! 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 newContent = newStrLines.slice(startLine, endLine + 1).join('\n') @@ -70,13 +70,15 @@ export function findDiffs(oldStr: string, newStr: string) { else if (originalEndLine === originalStartLine - 1) { type = 'insertion' originalEndLine = originalStartLine - originalStartCol = 0 + // originalStartCol = 0 // originalEndCol = 0 } const replacement: BaseDiff = { type, - startLine, startCol, endLine, endCol, + startLine, endLine, + startCol, endCol, + originalStartLine, originalEndLine, // code: newContent, // originalRange: new Range(originalStartLine, originalStartCol, originalEndLine, originalEndCol), originalCode: originalContent, diff --git a/src/vs/workbench/contrib/void/browser/react/src/prompt/stringifyFiles.ts b/src/vs/workbench/contrib/void/browser/prompt/stringifyFiles.ts similarity index 75% rename from src/vs/workbench/contrib/void/browser/react/src/prompt/stringifyFiles.ts rename to src/vs/workbench/contrib/void/browser/prompt/stringifyFiles.ts index 42d2fd39..7cde3999 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/prompt/stringifyFiles.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/stringifyFiles.ts @@ -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 }) => ` ${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 = ''; if (files.length > 0) { diff --git a/src/vs/workbench/contrib/void/browser/react/src/prompt/systemPrompts.ts b/src/vs/workbench/contrib/void/browser/prompt/systemPrompts.ts similarity index 100% rename from src/vs/workbench/contrib/void/browser/react/src/prompt/systemPrompts.ts rename to src/vs/workbench/contrib/void/browser/prompt/systemPrompts.ts diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx index 6b34342d..4773d645 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useCallback, useEffect, useState } from "react" +import React, { ReactNode } from "react" import SyntaxHighlighter from "react-syntax-highlighter"; import { atomOneDarkReasonable } from "react-syntax-highlighter/dist/esm/styles/hljs"; @@ -16,9 +16,9 @@ export const BlockCode = ({ text, buttonsOnHover, language }: { text: string, bu return (<>
- {!toolbar ? null : ( + {buttonsOnHover === null ? null : (
-
{buttonsOnHover === null ? null : buttonsOnHover}
+
{buttonsOnHover}
)} diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/MarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/MarkdownRender.tsx index 2f141c48..3709c517 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/MarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/MarkdownRender.tsx @@ -1,5 +1,5 @@ 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" diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index f6c6ae85..54829333 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -1,39 +1,23 @@ import React, { FormEvent, useCallback, useRef, useState } from 'react'; -import { generateDiffInstructions } from '../prompt/systemPrompts.js'; import { useConfigState, useService, useSidebarState, useThreadsState } from '../util/contextForServices.js'; import { URI } from '../../../../../../../base/common/uri.js'; -import { IFileService } from '../../../../../../../platform/files/common/files.js'; -import { userInstructionsStr } from '../prompt/stringifyFiles.js'; +import { VSReadFile } from '../../../registerInlineDiffs.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 { MarkdownRender } from '../markdown/MarkdownRender.js'; -// read files from VSCode -let VSReadFile = async (fileService: IFileService, filepath: URI): Promise => { - 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 = | { role: 'user'; content: string; // content sent to the llm 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 } | { @@ -147,7 +131,7 @@ export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject(null) // the code the user is selecting + const [selection, setSelection] = useState(null) // the code the user is selecting const [files, setFiles] = useState([]) // the names of the files in the chat const [instructions, setInstructions] = useState('') // the user's instructions @@ -181,9 +165,9 @@ export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject VSReadFile(fileService, filepath)) + files.map(async (filepath) => ({ content: await VSReadFile(fileService, filepath), filepath })) ).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 diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/sendLLMMessage.ts b/src/vs/workbench/contrib/void/browser/react/src/util/sendLLMMessage.ts index ac88156c..c895b04e 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/sendLLMMessage.ts +++ b/src/vs/workbench/contrib/void/browser/react/src/util/sendLLMMessage.ts @@ -2,12 +2,11 @@ import Anthropic from '@anthropic-ai/sdk'; import OpenAI from 'openai'; import { Ollama } from 'ollama/browser' import { Content, GoogleGenerativeAI, GoogleGenerativeAIFetchError } from '@google/generative-ai'; +import { VoidConfig } from '../../../registerConfig.js'; // import { VoidConfig } from '../webviews/common/contextForConfig' // import { captureEvent } from '../webviews/common/posthog'; // import { ChatMessage } from './shared_types'; -type VoidConfig = any - export type AbortRef = { current: (() => void) | null } export type OnText = (newText: string, fullText: string) => void diff --git a/src/vs/workbench/contrib/void/browser/react/tsconfig.json b/src/vs/workbench/contrib/void/browser/react/tsconfig.json index 8966c393..22af6ba3 100644 --- a/src/vs/workbench/contrib/void/browser/react/tsconfig.json +++ b/src/vs/workbench/contrib/void/browser/react/tsconfig.json @@ -6,6 +6,6 @@ "esModuleInterop": true, }, "include": [ - "./src/**/*.ts", + "./src/**/*.ts" ] } diff --git a/src/vs/workbench/contrib/void/browser/registerInlineDiff.ts b/src/vs/workbench/contrib/void/browser/registerInlineDiff.ts deleted file mode 100644 index c26168cf..00000000 --- a/src/vs/workbench/contrib/void/browser/registerInlineDiff.ts +++ /dev/null @@ -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 = { -// 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('inlineDiffService'); - -class InlineDiffService extends Disposable implements IInlineDiffService { - private readonly _diffDecorations = new Map(); - private readonly _diffZones = new Map(); - - - _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'; diff --git a/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts b/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts index 36b3d829..46f9f41e 100644 --- a/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts +++ b/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts @@ -6,62 +6,78 @@ import { ICodeEditor, IViewZone } from '../../../../editor/browser/editorBrowser 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 { EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { IBulkEditService, ResourceTextEdit } from '../../../../editor/browser/services/bulkEditService.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 => { + 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 -editor.changeViewZones(accessor => { - // Get the editor's font info - const fontInfo = editor.getOption(EditorOption.fontInfo); +// // 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`; +// 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 +// // div +// const lineContent = document.createElement('div'); +// lineContent.className = 'view-line'; // .monaco-editor .inline-deleted-text - // span - const contentSpan = document.createElement('span'); +// // span +// const contentSpan = document.createElement('span'); - // span - const codeSpan = document.createElement('span'); - codeSpan.className = 'mtk1'; // char-delete - codeSpan.textContent = originalText; +// // span +// const codeSpan = document.createElement('span'); +// codeSpan.className = 'mtk1'; // char-delete +// codeSpan.textContent = originalText; - // Mount - contentSpan.appendChild(codeSpan); - lineContent.appendChild(contentSpan); - domNode.appendChild(lineContent); +// // 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); +// // 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 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]); -}); +// const zoneId = accessor.addZone(viewZone); +// // editor.layout(); +// this._diffZones.set(editor, [zoneId]); +// }); @@ -94,10 +110,16 @@ editor.changeViewZones(accessor => { -// override dispose(): void { -// super.dispose(); -// this._diffDecorations.clear(); -// this._diffZones.clear(); +// public removeAllDiffs(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); // } @@ -105,41 +127,32 @@ editor.changeViewZones(accessor => { -public removeAllDiffs(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); -} - - - - - - -// _ means computed / temporary +// _ means computed later, temporary, or part of current state type DiffArea = { - diffareaid: string, + diffareaid: number, + originalStartLine: number, + originalEndLine: number, startLine: number, endLine: number, + _uri: URI, // document uri + _streamId: number, _diffIds: string[], - _sweepIdx: number | null, + _sweepLine: number | null, + _sweepCol: number | null, } export type Diff = { - diffid: string, - diffareaid: string, // the diff area this diff belongs to, "computed" + diffid: number, + diffareaid: number, // the diff area this diff belongs to, "computed" type: 'edit' | 'insertion' | 'deletion'; originalCode: string; + startLine: number; endLine: number; + originalStartLine: number; + originalEndLine: number; startCol: 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 { @@ -174,43 +193,49 @@ export const IInlineDiffsService = createDecorator('inlineD class InlineDiffsService extends Disposable implements IInlineDiffsService { _serviceBrand: undefined; + // state of each document (uri) + diffAreasOfURI: Record = {} // uriStr -> diffAreaId[] + originalFileOfURI: Record = {} // uriStr -> originalFile + streamingStateOfURI: Record = {} // uriStr -> state + diffAreaOfId: Map = new Map(); diffOfId: Map = new Map(); - - streamingState: { - type: 'streaming'; - editGroup: UndoRedoGroup; - } | { type: 'idle' } - = { type: 'idle' } - + _streamIdPool = 0 + _diffareaIdPool = 0 private readonly _onDidFinishStreaming = new Emitter(); - constructor( // @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, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z @IBulkEditService private readonly _bulkEditService: IBulkEditService, + @IFileService private readonly _fileService: IFileService, + @IVoidConfigStateService private readonly _voidConfigStateService: IVoidConfigStateService, ) { 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() { - 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(), - } + private _addToHistory(uri: URI, editGroup: UndoRedoGroup) { const beforeSnapshot: HistorySnapshot = { diffAreaOfId: new Map(this.diffAreaOfId), @@ -232,7 +257,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const elt: IUndoRedoElement = { type: UndoRedoElementType.Resource, - resource: model.uri, + resource: uri, label: 'Add Diffs', code: 'undoredo.inlineDiffs', // called when undoing this state @@ -249,41 +274,314 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { this.diffOfId = new Map(afterSnapshot.diffOfId) } } - - this._undoRedoService.pushElement(elt, this.streamingState.editGroup) - - - - // ---------- START ---------- - editor.updateOptions({ readOnly: true }) - - - // ---------- WHEN DONE ---------- - editor.updateOptions({ readOnly: false }) - + this._undoRedoService.pushElement(elt, editGroup) } + 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 - this._bulkEditService.apply(edit, { undoRedoGroupId: this._streamingState.editGroup.id, }) - - } + const uri = model.uri + 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((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() + } + + // 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);