Refactor and prepare to create addNewChanges

This commit is contained in:
Mathew P 2024-10-03 00:33:17 -07:00
parent d64e9c93f5
commit f4a407d80f
9 changed files with 74 additions and 44 deletions

View file

@ -72,11 +72,11 @@
"title": "Show Selection Lens"
},
{
"command": "void.approveDiff",
"command": "void.acceptDiff",
"title": "Approve Diff"
},
{
"command": "void.discardDiff",
"command": "void.rejectDiff",
"title": "Discard Diff"
},
{

View file

@ -15,17 +15,17 @@ const greenDecoration = vscode.window.createTextEditorDecorationType({
isWholeLine: false, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:...
})
export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
// responsible for displaying diffs and showing accept/reject buttons
export class ApplyChangesProvider implements vscode.CodeLensProvider {
private _diffsOfDocument: { [docUriStr: string]: DiffType[] } = {};
private _computedLensesOfDocument: { [docUriStr: string]: vscode.CodeLens[] } = {} // computed from diffsOfDocument[docUriStr].lenses
private _diffidPool = 0
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>(); // signals a UI refresh on .fire() events
private _weAreEditing: boolean = false
// used internally by vscode
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>(); // signals a UI refresh on .fire() events
public readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
@ -40,16 +40,19 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
// this acts as a useEffect. Every time text changes, clear the diffs in this editor
vscode.workspace.onDidChangeTextDocument((e) => {
const editor = vscode.window.activeTextEditor
if (!editor)
return
if (this._weAreEditing)
return
const docUri = editor.document.uri
const docUriStr = docUri.toString()
this._diffsOfDocument[docUriStr].splice(0) // clear diffs
editor.setDecorations(greenDecoration, []) // clear decorations
this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) // recompute
this._onDidChangeCodeLenses.fire() // refresh
this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) // recompute codelenses
this._onDidChangeCodeLenses.fire() // rerender codelenses
})
}
@ -61,13 +64,15 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
}
// used by us only
public async addNewApprovals(editor: vscode.TextEditor, suggestedEdits: SuggestedEdit[]) {
public async addNewChanges(editor: vscode.TextEditor, suggestedEdits: SuggestedEdit[]) {
const docUri = editor.document.uri
const docUriStr = docUri.toString()
// if no diffs, set diffs to []
if (!this._diffsOfDocument[docUriStr])
this._diffsOfDocument[docUriStr] = []
// if no codelenses, set codelenses to []
if (!this._computedLensesOfDocument[docUriStr])
this._computedLensesOfDocument[docUriStr] = []
@ -84,7 +89,7 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
// INSERTIONS (e.g. {originalStartLine: 0, originalEndLine: -1})
if (suggestedEdit.originalStartLine > suggestedEdit.originalEndLine) {
const originalPosition = new vscode.Position(suggestedEdit.originalStartLine, 0)
workspaceEdit.insert(docUri, originalPosition, suggestedEdit.newContent + '\n') // add back in the line we deleted when we made the startline->endline range go negative
workspaceEdit.insert(docUri, originalPosition, suggestedEdit.afterCode + '\n') // add back in the line we deleted when we made the startline->endline range go negative
greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine + 1, 0)
}
// DELETIONS
@ -92,22 +97,22 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
const deleteRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine + 1, 0)
workspaceEdit.delete(docUri, deleteRange)
greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.startLine, 0)
suggestedEdit.originalContent += '\n' // add back in the line we deleted when we made the startline->endline range go negative
suggestedEdit.beforeCode += '\n' // add back in the line we deleted when we made the startline->endline range go negative
}
// REPLACEMENTS
else {
const originalRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine, Number.MAX_SAFE_INTEGER)
workspaceEdit.replace(docUri, originalRange, suggestedEdit.newContent)
workspaceEdit.replace(docUri, originalRange, suggestedEdit.afterCode)
greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine, Number.MAX_SAFE_INTEGER)
}
this._diffsOfDocument[docUriStr].push({
diffid: this._diffidPool,
greenRange: greenRange,
originalCode: suggestedEdit.originalContent,
originalCode: suggestedEdit.beforeCode,
lenses: [
new vscode.CodeLens(greenRange, { title: 'Accept', command: 'void.approveDiff', arguments: [{ diffid: this._diffidPool }] }),
new vscode.CodeLens(greenRange, { title: 'Reject', command: 'void.discardDiff', arguments: [{ diffid: this._diffidPool }] })
new vscode.CodeLens(greenRange, { title: 'Accept', command: 'void.acceptDiff', arguments: [{ diffid: this._diffidPool }] }),
new vscode.CodeLens(greenRange, { title: 'Reject', command: 'void.rejectDiff', arguments: [{ diffid: this._diffidPool }] })
]
});
this._diffidPool += 1
@ -124,12 +129,13 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
console.log('diffs after added:', this._diffsOfDocument[docUriStr])
}
// called on void.approveDiff
public async approveDiff({ diffid }: { diffid: number }) {
// called on void.acceptDiff
public async acceptDiff({ diffid }: { diffid: number }) {
const editor = vscode.window.activeTextEditor
if (!editor)
return
// get document uri
const docUri = editor.document.uri
const docUriStr = docUri.toString()
@ -148,8 +154,8 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
}
// called on void.discardDiff
public async discardDiff({ diffid }: { diffid: number }) {
// called on void.rejectDiff
public async rejectDiff({ diffid }: { diffid: number }) {
const editor = vscode.window.activeTextEditor
if (!editor)
return
@ -172,7 +178,7 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
// clear the decoration in this diffs range
editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.greenRange))
// REVERT THE CHANGE (this is the only part that's different from approveDiff)
// REVERT THE CHANGE (this is the only part that's different from acceptDiff)
let workspaceEdit = new vscode.WorkspaceEdit();
workspaceEdit.replace(docUri, range, originalCode);
this._weAreEditing = true

View file

@ -64,9 +64,14 @@ const sendClaudeMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
const anthropic = new Anthropic({ apiKey: apiConfig.anthropic.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"]
console.log('max_tokens:' + apiConfig.anthropic.maxTokens)
let max_tokens = parseInt(apiConfig.anthropic.maxTokens)
if (isNaN(max_tokens)) { max_tokens = 4000 } // TODO make a default max_tokens
const stream = anthropic.messages.stream({
model: apiConfig.anthropic.model,
max_tokens: parseInt(apiConfig.anthropic.maxTokens),
max_tokens: max_tokens,
messages: messages,
});
@ -221,6 +226,8 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, apiConfig }) => {
if (!apiConfig) return { abort: () => { } }
console.log(`void: sending LLMMessage to ${apiConfig.whichApi}`)
const whichApi = apiConfig.whichApi
if (whichApi === 'anthropic') {

View file

@ -2,7 +2,7 @@ import * as vscode from 'vscode';
import { WebviewMessage } from './shared_types';
import { CtrlKCodeLensProvider } from './CtrlKCodeLensProvider';
import { getDiffedLines } from './getDiffedLines';
import { ApprovalCodeLensProvider } from './ApprovalCodeLensProvider';
import { ApplyChangesProvider as DisplayChangesProvider } from './DisplayChangesProvider';
import { SidebarWebviewProvider } from './SidebarWebviewProvider';
import { ApiConfig } from './common/sendLLMMessage';
@ -71,22 +71,23 @@ export function activate(context: vscode.ExtensionContext) {
);
// 3. Show an approve/reject codelens above each change
const approvalCodeLensProvider = new ApprovalCodeLensProvider();
context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', approvalCodeLensProvider));
const displayChangesProvider = new DisplayChangesProvider();
console.log(`void: Creating DisplayChangesProvider`)
context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', displayChangesProvider));
// 4. Add approve/reject commands
context.subscriptions.push(vscode.commands.registerCommand('void.approveDiff', async (params) => {
approvalCodeLensProvider.approveDiff(params)
context.subscriptions.push(vscode.commands.registerCommand('void.acceptDiff', async (params) => {
displayChangesProvider.acceptDiff(params)
}));
context.subscriptions.push(vscode.commands.registerCommand('void.discardDiff', async (params) => {
approvalCodeLensProvider.discardDiff(params)
context.subscriptions.push(vscode.commands.registerCommand('void.rejectDiff', async (params) => {
displayChangesProvider.rejectDiff(params)
}));
context.subscriptions.push(vscode.commands.registerCommand('void.openSettings', async () => {
vscode.commands.executeCommand('workbench.action.openSettings', '@ext:void.void');
}));
// 5.
// 5. Receive messages from sidebar
webviewProvider.webview.then(
webview => {
@ -112,16 +113,21 @@ export function activate(context: vscode.ExtensionContext) {
// send contents to webview
webview.postMessage({ type: 'files', files, } satisfies WebviewMessage)
} else if (m.type === 'applyCode') {
} else if (m.type === 'applyChanges') {
const editor = vscode.window.activeTextEditor
if (!editor) {
vscode.window.showInformationMessage('No active editor!')
return
}
const oldContents = await readFileContentOfUri(editor.document.uri)
const suggestedEdits = getDiffedLines(oldContents, m.code)
await approvalCodeLensProvider.addNewApprovals(editor, suggestedEdits)
const beforeCode = await readFileContentOfUri(editor.document.uri)
// TODO change this to be animated
const suggestedEdits = getDiffedLines(beforeCode, m.code)
// when changes have been created
await displayChangesProvider.addNewChanges(editor, suggestedEdits)
}
else if (m.type === 'getApiConfig') {

View file

@ -2,16 +2,16 @@ import { diffLines, Change } from 'diff';
export type SuggestedEdit = {
// start/end of current file
startLine: number;
endLine: number;
startLine: number,
endLine: number,
// start/end of original file
originalStartLine: number,
originalEndLine: number,
// original content (originalfile[originalStart...originalEnd])
originalContent: string;
newContent: string;
beforeCode: string;
afterCode: string;
}
export function getDiffedLines(oldStr: string, newStr: string) {
@ -46,7 +46,7 @@ export function getDiffedLines(oldStr: string, newStr: string) {
const originalEndLine = oldFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
const originalContent = oldStrLines.slice(originalStartLine, originalEndLine + 1).join('\n')
const replacement: SuggestedEdit = { startLine, endLine, newContent, originalStartLine, originalEndLine, originalContent }
const replacement: SuggestedEdit = { beforeCode: originalContent, afterCode: newContent, startLine, endLine, originalStartLine, originalEndLine, }
replacements.push(replacement)
streakStartInNewFile = undefined

View file

@ -13,7 +13,7 @@ type WebviewMessage = (
| { type: 'ctrl+l', selection: Selection } // user presses ctrl+l in the editor
// sidebar -> editor
| { type: 'applyCode', code: string } // user clicks "apply" in the sidebar
| { type: 'applyChanges', code: string } // user clicks "apply" in the sidebar
// sidebar -> editor
| { type: 'requestFiles', filepaths: vscode.Uri[] }

View file

@ -8,7 +8,7 @@ export const BlockCode = ({ text, disableApplyButton = false }: { text: string,
return <div className='py-1'>
{disableApplyButton ? null : <div className='text-sm'>
<button className='btn btn-secondary px-3 py-1 text-sm rounded-t-sm'
onClick={async () => { getVSCodeAPI().postMessage({ type: 'applyCode', code: text }) }}>Apply</button>
onClick={async () => { getVSCodeAPI().postMessage({ type: 'applyChanges', code: text }) }}>Apply</button>
</div>}
<div className={`overflow-x-auto rounded-sm text-vscode-editor-fg bg-vscode-editor-bg ${disableApplyButton ? '' : 'rounded-tl-none'}`}>
<pre className='p-3'>

View file

@ -186,10 +186,13 @@ const Sidebar = () => {
const formRef = useRef<HTMLFormElement | null>(null)
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
console.log(`11111`)
e.preventDefault()
if (isLoading) return
console.log(`2222222`)
setIsLoading(true)
setInstructions('');
@ -197,9 +200,14 @@ const Sidebar = () => {
setSelection(null)
setFiles([])
console.log(`AAAAAA`)
// request file content from vscode and await response
getVSCodeAPI().postMessage({ type: 'requestFiles', filepaths: files })
console.log(`BBBBB`)
const relevantFiles = await awaitVSCodeResponse('files')
console.log(`CCCCCC`)
// add message to chat history
const content = userInstructionsStr(instructions, relevantFiles.files, selection)
@ -207,7 +215,8 @@ const Sidebar = () => {
const newHistoryElt: ChatMessage = { role: 'user', content, displayContent: instructions, selection, files }
setChatHistory(chatMessageHistory => [...chatMessageHistory, newHistoryElt])
// send message to claude
// send message to LLM
console.log(`DDDDD`)
let { abort } = sendLLMMessage({
messages: [...chatMessageHistory.map(m => ({ role: m.role, content: m.content })), { role: 'user', content }],
onText: (newText, fullText) => setMessageStream(fullText),
@ -223,7 +232,9 @@ const Sidebar = () => {
},
apiConfig: apiConfig
})
console.log(`EEEEE`)
abortFnRef.current = abort
console.log(`FFFF`)
}
@ -266,7 +277,7 @@ const Sidebar = () => {
{!selection?.selectionStr ? null
: (
<div className="relative">
<button
<button
onClick={clearSelection}
className="absolute top-2 right-2 text-white hover:text-gray-300 z-10"
>
@ -274,7 +285,7 @@ const Sidebar = () => {
</button>
<BlockCode text={selection.selectionStr} disableApplyButton={true} />
</div>
)}
)}
</div>
<form
ref={formRef}

View file

@ -5,7 +5,7 @@ import { Command, WebviewMessage } from "../shared_types";
// message -> res[]
const awaiting: { [c in Command]: ((res: any) => void)[] } = {
"ctrl+l": [],
"applyCode": [],
"applyChanges": [],
"requestFiles": [],
"files": [],
"apiConfig": [],