From a2a9cdba6019eeed75b4ffba982fe3b43e87e394 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 3 Apr 2025 17:12:18 -0700 Subject: [PATCH] checkpoints revert/restore diffareas!!! --- .../contrib/void/browser/chatThreadService.ts | 92 +++--- .../contrib/void/browser/editCodeService.ts | 283 ++++++------------ .../void/browser/editCodeServiceInterface.ts | 7 +- .../contrib/void/browser/helpers/findDiffs.ts | 27 +- .../src/markdown/ApplyBlockHoverButtons.tsx | 7 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 8 +- .../void/browser/react/src/util/services.tsx | 2 +- .../void/common/chatThreadServiceTypes.ts | 12 +- .../void/common/editCodeServiceTypes.ts | 119 ++++++++ .../contrib/void/common/prompt/prompts.ts | 2 +- 10 files changed, 290 insertions(+), 269 deletions(-) create mode 100644 src/vs/workbench/contrib/void/common/editCodeServiceTypes.ts diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index a7387e29..a02ff9ab 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -30,6 +30,8 @@ import { IVoidModelService } from '../common/voidModelService.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { findLastIdx } from '../../../../base/common/arraysFind.js'; +import { IEditCodeService } from './editCodeServiceInterface.js'; +import { VoidFileSnapshot } from '../common/editCodeServiceTypes.js'; /* @@ -238,6 +240,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { @IMetricsService private readonly _metricsService: IMetricsService, @IEditorService private readonly _editorService: IEditorService, @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IEditCodeService private readonly _editCodeService: IEditCodeService, // @IModelService private readonly _modelService: IModelService, ) { @@ -787,13 +790,13 @@ class ChatThreadService extends Disposable implements IChatThreadService { const { model } = this._voidModelService.getModel(uri) if (!model) return // should never happen - const currValue = model.getValue() // afterStr = the value of the file right after the edit + const diffAreasSnapshot = this._editCodeService.getVoidFileSnapshot(uri) this._addCheckpoint(threadId, { role: 'checkpoint', type: 'tool_edit', - beforeStrOfURI: { [uri.fsPath]: currValue, }, - userModifications: { beforeStrOfURI: {} }, + voidFileSnapshotOfURI: { [uri.fsPath]: diffAreasSnapshot }, + userModifications: { voidFileSnapshotOfURI: {} }, }) } @@ -821,13 +824,13 @@ class ChatThreadService extends Disposable implements IChatThreadService { - private _computeNeededCheckpointChanges({ threadId }: { threadId: string }) { + private _computeCheckpointInfo({ threadId }: { threadId: string }) { const thread = this.state.allThreads[threadId] if (!thread) return const { currCheckpointIdx } = thread.state if (currCheckpointIdx === null) return - const currStrOfFsPath: { [fsPath: string]: string | undefined } = {} + const voidFileSnapshotOfURI: { [fsPath: string]: VoidFileSnapshot | undefined } = {} // add a change for all the URIs in the checkpoint history const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: 0, hiIdx: currCheckpointIdx, }) ?? {} @@ -837,10 +840,14 @@ class ChatThreadService extends Disposable implements IChatThreadService { const checkpoint2 = thread.messages[lastIdxOfURI[fsPath]] || null if (!checkpoint2) continue if (checkpoint2.role !== 'checkpoint') continue - const oldStr = this._getBeforeStrAtCheckpoint(checkpoint2, fsPath, { includeUserModifiedChanges: false }) - const currStr = model.getValue() - if (oldStr === currStr) continue - currStrOfFsPath[fsPath] = currStr + const res = this._getCheckpointInfo(checkpoint2, fsPath, { includeUserModifiedChanges: false }) + if (!res) continue + const { voidFileSnapshot: oldVoidFileSnapshot } = res + + // if there was any change to the str or diffAreaSnapshot, update. rough approximation of equality, oldDiffAreasSnapshot === diffAreasSnapshot is not perfect + const voidFileSnapshot = this._editCodeService.getVoidFileSnapshot(URI.file(fsPath)) + if (oldVoidFileSnapshot === voidFileSnapshot) continue + voidFileSnapshotOfURI[fsPath] = voidFileSnapshot } // // add a change for all user-edited files (that aren't in the history) @@ -851,27 +858,30 @@ class ChatThreadService extends Disposable implements IChatThreadService { // currStrOfFsPath[fsPath] = model.getValue() // } - return currStrOfFsPath + return { voidFileSnapshotOfURI } } // call this right before user sends message or reverts private _addUserCheckpoint({ threadId }: { threadId: string }) { - const changes = this._computeNeededCheckpointChanges({ threadId }) + const { voidFileSnapshotOfURI } = this._computeCheckpointInfo({ threadId }) ?? {} this._addCheckpoint(threadId, { role: 'checkpoint', type: 'user_edit', - beforeStrOfURI: changes ?? {}, - userModifications: { beforeStrOfURI: {} }, + voidFileSnapshotOfURI: voidFileSnapshotOfURI ?? {}, + userModifications: { + voidFileSnapshotOfURI: {}, + }, }) } private _addUserModificationsToCurrCheckpoint({ threadId }: { threadId: string }) { - const changes = this._computeNeededCheckpointChanges({ threadId }) + const { voidFileSnapshotOfURI } = this._computeCheckpointInfo({ threadId }) ?? {} + const res = this._getCurrentCheckpoint(threadId) if (!res) return const [checkpoint, checkpointIdx] = res this._editMessageInThread(threadId, checkpointIdx, { ...checkpoint, - userModifications: { beforeStrOfURI: changes ?? {} }, + userModifications: { voidFileSnapshotOfURI: voidFileSnapshotOfURI ?? {}, }, }) } @@ -908,29 +918,30 @@ class ChatThreadService extends Disposable implements IChatThreadService { for (let i = loIdx; i <= hiIdx; i += 1) { const message = thread.messages[i] if (message.role !== 'checkpoint') continue - for (const fsPath in message.beforeStrOfURI) { // do not include userModified.beforeStrOfURI here, jumping should not include those changes + for (const fsPath in message.voidFileSnapshotOfURI) { // do not include userModified.beforeStrOfURI here, jumping should not include those changes lastIdxOfURI[fsPath] = i } } return { lastIdxOfURI } } - private _getBeforeStrAtCheckpoint = (checkpointMessage: ChatMessage & { role: 'checkpoint' }, fsPath: string, opts: { includeUserModifiedChanges: boolean }) => { - const beforeStr = fsPath in checkpointMessage.beforeStrOfURI ? checkpointMessage.beforeStrOfURI[fsPath] ?? null : null - if (!opts.includeUserModifiedChanges) return beforeStr - const userModifiedBeforeStr = fsPath in checkpointMessage.userModifications.beforeStrOfURI ? checkpointMessage.userModifications.beforeStrOfURI[fsPath] ?? null : null - return userModifiedBeforeStr ?? beforeStr + private _getCheckpointInfo = (checkpointMessage: ChatMessage & { role: 'checkpoint' }, fsPath: string, opts: { includeUserModifiedChanges: boolean }) => { + const voidFileSnapshot = checkpointMessage.voidFileSnapshotOfURI ? checkpointMessage.voidFileSnapshotOfURI[fsPath] ?? null : null + if (!opts.includeUserModifiedChanges) { return { voidFileSnapshot, } } + + const userModifiedVoidFileSnapshot = fsPath in checkpointMessage.userModifications.voidFileSnapshotOfURI ? checkpointMessage.userModifications.voidFileSnapshotOfURI[fsPath] ?? null : null + return { voidFileSnapshot: userModifiedVoidFileSnapshot ?? voidFileSnapshot, } } - private _writeFullFile = ({ fsPath, text }: { fsPath: string, text: string }) => { - const { model } = this._voidModelService.getModelFromFsPath(fsPath) - if (!model) return // should never happen - model.applyEdits([{ - range: { startLineNumber: 1, startColumn: 1, endLineNumber: model.getLineCount(), endColumn: Number.MAX_SAFE_INTEGER }, // whole file - text - }]) - } + // private _writeFullFile = ({ fsPath, text }: { fsPath: string, text: string }) => { + // const { model } = this._voidModelService.getModelFromFsPath(fsPath) + // if (!model) return // should never happen + // model.applyEdits([{ + // range: { startLineNumber: 1, startColumn: 1, endLineNumber: model.getLineCount(), endColumn: Number.MAX_SAFE_INTEGER }, // whole file + // text + // }]) + // } jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified }: { threadId: string, messageIdx: number, jumpToUserModified: boolean }) { const thread = this.state.allThreads[threadId] @@ -978,11 +989,12 @@ We only need to do it for files that were edited since `to`, ie files between to for (let k = toIdx; k >= 0; k -= 1) { const message = thread.messages[k] if (message.role !== 'checkpoint') continue - const beforeStr = this._getBeforeStrAtCheckpoint(message, fsPath, { includeUserModifiedChanges: jumpToUserModified }) - if (beforeStr !== null) { - this._writeFullFile({ fsPath, text: beforeStr }) - break - } + const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified }) + if (!res) continue + const { voidFileSnapshot } = res + if (!voidFileSnapshot) continue + this._editCodeService.restoreVoidFileSnapshot(URI.file(fsPath), voidFileSnapshot) + break } } } @@ -1011,11 +1023,13 @@ We only need to do it for files that were edited since `from`, ie files between for (let k = toIdx; k >= fromIdx + 1; k -= 1) { const message = thread.messages[k] if (message.role !== 'checkpoint') continue - const beforeStr = this._getBeforeStrAtCheckpoint(message, fsPath, { includeUserModifiedChanges: jumpToUserModified }) - if (beforeStr !== null) { - this._writeFullFile({ fsPath, text: beforeStr }) - break - } + const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified }) + if (!res) continue + const { voidFileSnapshot } = res + if (!voidFileSnapshot) continue + + this._editCodeService.restoreVoidFileSnapshot(URI.file(fsPath), voidFileSnapshot) + break } } } diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 58b3a76c..a8c15870 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/brows // import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; // import { throttle } from '../../../../base/common/decorators.js'; -import { ComputedDiff, findDiffs } from './helpers/findDiffs.js'; +import { findDiffs } from './helpers/findDiffs.js'; import { EndOfLinePreference, IModelDecorationOptions, ITextModel } from '../../../../editor/common/model.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { registerColor } from '../../../../platform/theme/common/colorUtils.js'; @@ -40,13 +40,14 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { ILLMMessageService } from '../common/sendLLMMessageService.js'; import { LLMChatMessage, OnError, errorDetails } from '../common/sendLLMMessageTypes.js'; import { IMetricsService } from '../common/metricsService.js'; -import { IEditCodeService, AddCtrlKOpts, StartApplyingOpts, CallBeforeStartApplyingOpts } from './editCodeServiceInterface.js'; +import { IEditCodeService, AddCtrlKOpts, StartApplyingOpts, CallBeforeStartApplyingOpts, } from './editCodeServiceInterface.js'; import { IVoidSettingsService } from '../common/voidSettingsService.js'; import { FeatureName } from '../common/voidSettingsTypes.js'; import { IVoidModelService } from '../common/voidModelService.js'; import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; import { deepClone } from '../../../../base/common/objects.js'; import { acceptBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../common/helpers/colors.js'; +import { DiffArea, Diff, CtrlKZone, VoidFileSnapshot, DiffAreaSnapshotEntry, diffAreaSnapshotKeys, DiffZone, TrackingZone, ComputedDiff } from '../common/editCodeServiceTypes.js'; const configOfBG = (color: Color) => { return { dark: color, light: color, hcDark: color, hcLight: color, } @@ -107,7 +108,6 @@ const getLeadingWhitespacePx = (editor: ICodeEditor, startLine: number): number }; - // finds block.orig in fileContents and return its range in file // startingAtLine is 1-indexed and inclusive const findTextInCode = (text: string, fileContents: string, startingAtLine?: number) => { @@ -127,108 +127,6 @@ const findTextInCode = (text: string, fileContents: string, startingAtLine?: num - -// // TODO diffArea should be removed if we just discovered it has no more diffs in it -// for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { -// const diffArea = this.diffAreaOfId[diffareaid] -// if (Object.keys(diffArea._diffOfId).length === 0 && !diffArea._sweepState.isStreaming) { -// const { onFinishEdit } = this._addToHistory(uri) -// this._deleteDiffArea(diffArea) -// onFinishEdit() -// } -// } - - -export type Diff = { - diffid: number; - diffareaid: number; // the diff area this diff belongs to, "computed" -} & ComputedDiff - - - -// _ means anything we don't include if we clone it -// DiffArea.originalStartLine is the line in originalCode (not the file) - -type CommonZoneProps = { - diffareaid: number; - startLine: number; - endLine: number; - - _URI: URI; // typically we get the URI from model - -} - -type CtrlKZone = { - type: 'CtrlKZone'; - originalCode?: undefined; - - editorId: string; // the editor the input lives on - - _mountInfo: null | { - textAreaRef: { current: HTMLTextAreaElement | null } - dispose: () => void; - refresh: () => void; - } - - _linkedStreamingDiffZone: number | null; // diffareaid of the diffZone currently streaming here - _removeStylesFns: Set // these don't remove diffs or this diffArea, only their styles - -} & CommonZoneProps - - -export type DiffZone = { - type: 'DiffZone', - originalCode: string; - _diffOfId: Record; // diffid -> diff in this DiffArea - _streamState: { - isStreaming: true; - streamRequestIdRef: { current: string | null }; - line: number; - } | { - isStreaming: false; - streamRequestIdRef?: undefined; - line?: undefined; - }; - editorId?: undefined; - linkedStreamingDiffZone?: undefined; - _removeStylesFns: Set // these don't remove diffs or this diffArea, only their styles -} & CommonZoneProps - - - -type TrackingZone = { - type: 'TrackingZone'; - metadata: T; - originalCode?: undefined; - editorId?: undefined; - _removeStylesFns?: undefined; -} & CommonZoneProps - - -// called DiffArea for historical purposes, we can rename to something like TextRegion if we want -export type DiffArea = CtrlKZone | DiffZone | TrackingZone - -const diffAreaSnapshotKeys = [ - 'type', - 'diffareaid', - 'originalCode', - 'startLine', - 'endLine', - 'editorId', - -] as const satisfies (keyof DiffArea)[] - -type DiffAreaSnapshot = Pick - - - -type HistorySnapshot = { - snapshottedDiffAreaOfId: Record; - entireFileCode: string; -} - - - // line/col is the location, originalCodeStartLine is the start line of the original code being displayed type StreamLocationMutable = { line: number, col: number, addedSplitYet: boolean, originalCodeStartLine: number } @@ -259,7 +157,7 @@ class EditCodeService extends Disposable implements IEditCodeService { // ctrlKZone: [uri], isStreaming // listen on change streaming private readonly _onDidChangeStreamingInCtrlKZone = new Emitter<{ uri: URI; diffareaid: number }>(); - onDidChangeStreamingInCtrlKZone = this._onDidChangeStreamingInCtrlKZone.event + onDidChangeStreamingInCtrlKZone = this._onDidChangeStreamingInCtrlKZone.event; constructor( @@ -722,98 +620,98 @@ class EditCodeService extends Disposable implements IEditCodeService { + + + + private _getCurrentVoidFileSnapshot = (uri: URI): VoidFileSnapshot => { + const { model } = this._voidModelService.getModel(uri) + const snapshottedDiffAreaOfId: Record = {} + + for (const diffareaid in this.diffAreaOfId) { + const diffArea = this.diffAreaOfId[diffareaid] + + if (diffArea._URI.fsPath !== uri.fsPath) continue + + snapshottedDiffAreaOfId[diffareaid] = deepClone( + Object.fromEntries(diffAreaSnapshotKeys.map(key => [key, diffArea[key]])) + ) as DiffAreaSnapshotEntry + } + + const entireFileCode = model ? model.getValue(EndOfLinePreference.LF) : '' + + // this._noLongerNeedModelReference(uri) + return { + snapshottedDiffAreaOfId, + entireFileCode, // the whole file's code + } + } + + + private _restoreVoidFileSnapshot = async (uri: URI, snapshot: VoidFileSnapshot) => { + // for each diffarea in this uri, stop streaming if currently streaming + for (const diffareaid in this.diffAreaOfId) { + const diffArea = this.diffAreaOfId[diffareaid] + if (diffArea.type === 'DiffZone') + this._stopIfStreaming(diffArea) + } + + // delete all diffareas on this uri (clearing their styles) + this._deleteAllDiffAreas(uri) + + const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = deepClone(snapshot) // don't want to destroy the snapshot + + // restore diffAreaOfId and diffAreasOfModelId + for (const diffareaid in snapshottedDiffAreaOfId) { + + const snapshottedDiffArea = snapshottedDiffAreaOfId[diffareaid] + + if (snapshottedDiffArea.type === 'DiffZone') { + this.diffAreaOfId[diffareaid] = { + ...snapshottedDiffArea as DiffAreaSnapshotEntry, + type: 'DiffZone', + _diffOfId: {}, + _URI: uri, + _streamState: { isStreaming: false }, // when restoring, we will never be streaming + _removeStylesFns: new Set(), + } + } + else if (snapshottedDiffArea.type === 'CtrlKZone') { + this.diffAreaOfId[diffareaid] = { + ...snapshottedDiffArea as DiffAreaSnapshotEntry, + _URI: uri, + _removeStylesFns: new Set(), + _mountInfo: null, + _linkedStreamingDiffZone: null, // when restoring, we will never be streaming + } + } + this._addOrInitializeDiffAreaAtURI(uri, diffareaid) + } + this._onDidAddOrDeleteDiffZones.fire({ uri }) + + // restore file content + this._writeURIText(uri, entireModelCode, + 'wholeFileRange', + { shouldRealignDiffAreas: false } + ) + // this._noLongerNeedModelReference(uri) + } + private _addToHistory(uri: URI, opts?: { onWillUndo?: () => void }) { - - const getCurrentSnapshot = (): HistorySnapshot => { - - const { model } = this._voidModelService.getModel(uri) - const snapshottedDiffAreaOfId: Record = {} - - for (const diffareaid in this.diffAreaOfId) { - const diffArea = this.diffAreaOfId[diffareaid] - - if (diffArea._URI.fsPath !== uri.fsPath) continue - - snapshottedDiffAreaOfId[diffareaid] = deepClone( - Object.fromEntries(diffAreaSnapshotKeys.map(key => [key, diffArea[key]])) - ) as DiffAreaSnapshot - } - - const entireFileCode = model ? model.getValue(EndOfLinePreference.LF) : '' - - // this._noLongerNeedModelReference(uri) - return { - snapshottedDiffAreaOfId, - entireFileCode, // the whole file's code - } - } - - const restoreDiffAreas = async (snapshot: HistorySnapshot) => { - - // for each diffarea in this uri, stop streaming if currently streaming - for (const diffareaid in this.diffAreaOfId) { - const diffArea = this.diffAreaOfId[diffareaid] - if (diffArea.type === 'DiffZone') - this._stopIfStreaming(diffArea) - } - - // delete all diffareas on this uri (clearing their styles) - this._deleteAllDiffAreas(uri) - this.diffAreasOfURI[uri.fsPath]?.clear() - - const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = deepClone(snapshot) // don't want to destroy the snapshot - - // restore diffAreaOfId and diffAreasOfModelId - for (const diffareaid in snapshottedDiffAreaOfId) { - - const snapshottedDiffArea = snapshottedDiffAreaOfId[diffareaid] - - if (snapshottedDiffArea.type === 'DiffZone') { - this.diffAreaOfId[diffareaid] = { - ...snapshottedDiffArea as DiffAreaSnapshot, - type: 'DiffZone', - _diffOfId: {}, - _URI: uri, - _streamState: { isStreaming: false }, // when restoring, we will never be streaming - _removeStylesFns: new Set(), - } - } - else if (snapshottedDiffArea.type === 'CtrlKZone') { - this.diffAreaOfId[diffareaid] = { - ...snapshottedDiffArea as DiffAreaSnapshot, - _URI: uri, - _removeStylesFns: new Set(), - _mountInfo: null, - _linkedStreamingDiffZone: null, // when restoring, we will never be streaming - } - } - this._addOrInitializeDiffAreaAtURI(uri, diffareaid) - } - this._onDidAddOrDeleteDiffZones.fire({ uri }) - - // restore file content - this._writeURIText(uri, entireModelCode, - 'wholeFileRange', - { shouldRealignDiffAreas: false } - ) - // this._noLongerNeedModelReference(uri) - } - - const beforeSnapshot: HistorySnapshot = getCurrentSnapshot() - let afterSnapshot: HistorySnapshot | null = null + const beforeSnapshot: VoidFileSnapshot = this._getCurrentVoidFileSnapshot(uri) + let afterSnapshot: VoidFileSnapshot | null = null const elt: IUndoRedoElement = { type: UndoRedoElementType.Resource, resource: uri, label: 'Void Agent', code: 'undoredo.editCode', - undo: () => { opts?.onWillUndo?.(); restoreDiffAreas(beforeSnapshot); }, - redo: () => { if (afterSnapshot) restoreDiffAreas(afterSnapshot) } + undo: () => { opts?.onWillUndo?.(); this._restoreVoidFileSnapshot(uri, beforeSnapshot); }, + redo: () => { if (afterSnapshot) this._restoreVoidFileSnapshot(uri, afterSnapshot) } } this._undoRedoService.pushElement(elt) const onFinishEdit = async () => { - afterSnapshot = getCurrentSnapshot() + afterSnapshot = this._getCurrentVoidFileSnapshot(uri) await this._textFileService.save(uri, { // we want [our change] -> [save] so it's all treated as one change. skipSaveParticipants: true // avoid triggering extensions etc (if they reformat the page, it will add another item to the undo stack) }) @@ -822,6 +720,16 @@ class EditCodeService extends Disposable implements IEditCodeService { } + public getVoidFileSnapshot(uri: URI) { + return this._getCurrentVoidFileSnapshot(uri) + } + + + public restoreVoidFileSnapshot(uri: URI, snapshot: VoidFileSnapshot): void { + this._restoreVoidFileSnapshot(uri, snapshot) + } + + // delete diffOfId and diffArea._diffOfId private _deleteDiff(diff: Diff) { const diffArea = this.diffAreaOfId[diff.diffareaid] @@ -886,6 +794,7 @@ class EditCodeService extends Disposable implements IEditCodeService { else if (diffArea.type === 'CtrlKZone') this._deleteCtrlKZone(diffArea) }) + this.diffAreasOfURI[uri.fsPath]?.clear() } private _addOrInitializeDiffAreaAtURI = (uri: URI, diffareaid: string | number) => { diff --git a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts index 4be6e23c..a63fde7e 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts @@ -7,8 +7,7 @@ import { Event } from '../../../../base/common/event.js'; import { URI } from '../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { Diff, DiffArea } from './editCodeService.js'; - +import { Diff, DiffArea, VoidFileSnapshot } from '../common/editCodeServiceTypes.js'; export type StartBehavior = 'accept-conflicts' | 'reject-conflicts' | 'keep-conflicts' @@ -32,8 +31,6 @@ export type StartApplyingOpts = { startBehavior: StartBehavior; } - - export type AddCtrlKOpts = { startLine: number, endLine: number, @@ -70,4 +67,6 @@ export interface IEditCodeService { interruptURIStreaming(opts: { uri: URI }): void; // testDiffs(): void; + getVoidFileSnapshot(uri: URI): VoidFileSnapshot; + restoreVoidFileSnapshot(uri: URI, snapshot: VoidFileSnapshot): void; } diff --git a/src/vs/workbench/contrib/void/browser/helpers/findDiffs.ts b/src/vs/workbench/contrib/void/browser/helpers/findDiffs.ts index c9235c14..703b2775 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/findDiffs.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/findDiffs.ts @@ -3,34 +3,9 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ +import { ComputedDiff } from '../../common/editCodeServiceTypes.js'; import { diffLines } from '../react/out/diff/index.js' -export type ComputedDiff = { - type: 'edit'; - originalCode: string; - originalStartLine: number; - originalEndLine: number; - code: string; - startLine: number; // 1-indexed - endLine: number; -} | { - type: 'insertion'; - // originalCode: string; - originalStartLine: number; // insertion starts on column 0 of this - // originalEndLine: number; - code: string; - startLine: number; - endLine: number; -} | { - type: 'deletion'; - originalCode: string; - originalStartLine: number; - originalEndLine: number; - // code: string; - startLine: number; // deletion starts on column 0 of this - // endLine: number; -} - export function findDiffs(oldStr: string, newStr: string) { // this makes it so the end of the file always ends with a \n (if you don't have this, then diffing E vs E\n gives an "edit". With it, you end up diffing E\n vs E\n\n which now properly gives an insertion) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 054d1fc9..7dfbf73c 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -1,3 +1,8 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + import { useState, useEffect, useCallback } from 'react' import { useAccessor, useCommandBarState, useCommandBarURIListener, useSettingsState } from '../util/services.js' import { usePromise, useRefState } from '../util/helpers.js' @@ -144,7 +149,6 @@ export const useApplyButtonState = ({ applyBoxId, uri }: { applyBoxId: string, u const getStreamState = useCallback(() => { const uri = getUriBeingApplied(applyBoxId) - console.log('uri',uri?.fsPath) if (!uri) return 'idle-no-changes' return voidCommandBarService.getStreamState(uri) }, [voidCommandBarService, applyBoxId]) @@ -162,7 +166,6 @@ export const useApplyButtonState = ({ applyBoxId, uri }: { applyBoxId: string, u }, [applyBoxId, applyBoxId, uri])) const currStreamState = getStreamState() - console.log('curr stream state', currStreamState) return { getStreamState, 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 22242e3a..0092cd24 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,7 +1,3 @@ -//!!!! merged - - - /*-------------------------------------------------------------------------------------- * Copyright 2025 Glass Devtools, Inc. All rights reserved. * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. @@ -1821,12 +1817,12 @@ const toolNameToComponent: { [T in ToolName]: ToolComponent } = { const Checkpoint = ({ threadId, messageIdx }: { threadId: string; messageIdx: number }) => { const accessor = useAccessor() const chatThreadService = accessor.get('IChatThreadService') - const commandBarService = accessor.get('IVoidCommandBarService') + // const commandBarService = accessor.get('IVoidCommandBarService') return
{ // reject all current changes and then jump back - commandBarService.acceptOrRejectAllFiles({ behavior: 'accept' }) + // commandBarService.acceptOrRejectAllFiles({ behavior: 'accept' }) chatThreadService.jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified: true }) }}>
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index 493b87bb..6cd180a0 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -22,7 +22,6 @@ import { IThemeService } from '../../../../../../../platform/theme/common/themeS import { ILLMMessageService } from '../../../../common/sendLLMMessageService.js'; import { IRefreshModelService } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js'; import { IVoidSettingsService } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'; -import { IEditCodeService } from '../../../editCodeServiceInterface.js' import { ISidebarStateService } from '../../../sidebarStateService.js'; import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js' @@ -47,6 +46,7 @@ import { IVoidModelService } from '../../../../common/voidModelService.js' import { IWorkspaceContextService } from '../../../../../../../platform/workspace/common/workspace.js' import { IVoidCommandBarService } from '../../../voidCommandBarService.js' import { INativeHostService } from '../../../../../../../platform/native/common/native.js'; +import { IEditCodeService } from '../../../editCodeServiceInterface.js' // normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes diff --git a/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts b/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts index 64d8976d..b95bc67d 100644 --- a/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts @@ -1,5 +1,11 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + import { URI } from '../../../../base/common/uri.js'; import { IRange } from '../../../../editor/common/core/range.js'; +import { VoidFileSnapshot } from './editCodeServiceTypes.js'; import { AnthropicReasoning } from './sendLLMMessageTypes.js'; import { ToolName, ToolCallParams, ToolResultType } from './toolsServiceTypes.js'; @@ -29,11 +35,11 @@ export type ToolRequestApproval = { export type CheckpointEntry = { role: 'checkpoint'; type: 'user_edit' | 'tool_edit'; - beforeStrOfURI: { [fsPath: string]: string | undefined }; + voidFileSnapshotOfURI: { [fsPath: string]: VoidFileSnapshot | undefined }; + userModifications: { - beforeStrOfURI: { [fsPath: string]: string | undefined }; + voidFileSnapshotOfURI: { [fsPath: string]: VoidFileSnapshot | undefined }; }; - // diffAreas: null; } diff --git a/src/vs/workbench/contrib/void/common/editCodeServiceTypes.ts b/src/vs/workbench/contrib/void/common/editCodeServiceTypes.ts new file mode 100644 index 00000000..4aa09de3 --- /dev/null +++ b/src/vs/workbench/contrib/void/common/editCodeServiceTypes.ts @@ -0,0 +1,119 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { URI } from '../../../../base/common/uri.js'; + +export type ComputedDiff = { + type: 'edit'; + originalCode: string; + originalStartLine: number; + originalEndLine: number; + code: string; + startLine: number; // 1-indexed + endLine: number; +} | { + type: 'insertion'; + // originalCode: string; + originalStartLine: number; // insertion starts on column 0 of this + // originalEndLine: number; + code: string; + startLine: number; + endLine: number; +} | { + type: 'deletion'; + originalCode: string; + originalStartLine: number; + originalEndLine: number; + // code: string; + startLine: number; // deletion starts on column 0 of this + // endLine: number; +} + +// ---------- Diff types ---------- + +export type CommonZoneProps = { + diffareaid: number; + startLine: number; + endLine: number; + + _URI: URI; // typically we get the URI from model + +} + + +export type CtrlKZone = { + type: 'CtrlKZone'; + originalCode?: undefined; + + editorId: string; // the editor the input lives on + + // _ means anything we don't include if we clone it + _mountInfo: null | { + textAreaRef: { current: HTMLTextAreaElement | null } + dispose: () => void; + refresh: () => void; + } + _linkedStreamingDiffZone: number | null; // diffareaid of the diffZone currently streaming here + _removeStylesFns: Set // these don't remove diffs or this diffArea, only their styles +} & CommonZoneProps + + +export type TrackingZone = { + type: 'TrackingZone'; + metadata: T; + originalCode?: undefined; + editorId?: undefined; + _removeStylesFns?: undefined; +} & CommonZoneProps + + +// called DiffArea for historical purposes, we can rename to something like TextRegion if we want +export type DiffArea = CtrlKZone | DiffZone | TrackingZone + + +export type Diff = { + diffid: number; + diffareaid: number; // the diff area this diff belongs to, "computed" +} & ComputedDiff + + +export type DiffZone = { + type: 'DiffZone', + originalCode: string; + _diffOfId: Record; // diffid -> diff in this DiffArea + _streamState: { + isStreaming: true; + streamRequestIdRef: { current: string | null }; + line: number; + } | { + isStreaming: false; + streamRequestIdRef?: undefined; + line?: undefined; + }; + editorId?: undefined; + linkedStreamingDiffZone?: undefined; + _removeStylesFns: Set // these don't remove diffs or this diffArea, only their styles +} & CommonZoneProps + + +export const diffAreaSnapshotKeys = [ + 'type', + 'diffareaid', + 'originalCode', + 'startLine', + 'endLine', + 'editorId', + +] as const satisfies (keyof DiffArea)[] + + + +export type DiffAreaSnapshotEntry = Pick + +export type VoidFileSnapshot = { + snapshottedDiffAreaOfId: Record; + entireFileCode: string; +} + diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index b3b921df..9ca15419 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -128,7 +128,7 @@ Here's an example of a good description:\n${editToolDescription}.` name: 'terminal_command', description: `Executes a terminal command.`, params: { - command: { type: 'string', description: 'The terminal command to execute.' }, + command: { type: 'string', description: 'The terminal command to execute. Typically you should pipe to cat to avoid pagination.' }, waitForCompletion: { type: 'string', description: `Whether or not to await the command to complete and get the final result. Default is true. Make this value false when you want a command to run indefinitely without waiting for it.` }, terminalId: { type: 'string', description: 'Optional (value must be an integer >= 1, or empty which will go with the default). This is the ID of the terminal instance to execute the command in. The primary purpose of this is to start a new terminal for background processes or tasks that run indefinitely (e.g. if you want to run a server locally). Fails gracefully if a terminal ID does not exist, by creating a new terminal instance. Defaults to the preferred terminal ID.' }, },