mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
checkpoints revert/restore diffareas!!!
This commit is contained in:
parent
bde51106a1
commit
a2a9cdba60
10 changed files with 290 additions and 269 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Function> // these don't remove diffs or this diffArea, only their styles
|
||||
|
||||
} & CommonZoneProps
|
||||
|
||||
|
||||
export type DiffZone = {
|
||||
type: 'DiffZone',
|
||||
originalCode: string;
|
||||
_diffOfId: Record<string, Diff>; // 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<Function> // these don't remove diffs or this diffArea, only their styles
|
||||
} & CommonZoneProps
|
||||
|
||||
|
||||
|
||||
type TrackingZone<T> = {
|
||||
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<any>
|
||||
|
||||
const diffAreaSnapshotKeys = [
|
||||
'type',
|
||||
'diffareaid',
|
||||
'originalCode',
|
||||
'startLine',
|
||||
'endLine',
|
||||
'editorId',
|
||||
|
||||
] as const satisfies (keyof DiffArea)[]
|
||||
|
||||
type DiffAreaSnapshot<DiffAreaType extends DiffArea = DiffArea> = Pick<DiffAreaType, typeof diffAreaSnapshotKeys[number]>
|
||||
|
||||
|
||||
|
||||
type HistorySnapshot = {
|
||||
snapshottedDiffAreaOfId: Record<string, DiffAreaSnapshot>;
|
||||
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<string, DiffAreaSnapshotEntry> = {}
|
||||
|
||||
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<DiffZone>,
|
||||
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<CtrlKZone>,
|
||||
_URI: uri,
|
||||
_removeStylesFns: new Set<Function>(),
|
||||
_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<string, DiffAreaSnapshot> = {}
|
||||
|
||||
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<DiffZone>,
|
||||
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<CtrlKZone>,
|
||||
_URI: uri,
|
||||
_removeStylesFns: new Set<Function>(),
|
||||
_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) => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<T> } = {
|
|||
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 <div
|
||||
className='pointer-events-auto cursor-pointer select-none hover:brightness-125 flex items-center justify-center'
|
||||
onClick={() => {
|
||||
// reject all current changes and then jump back
|
||||
commandBarService.acceptOrRejectAllFiles({ behavior: 'accept' })
|
||||
// commandBarService.acceptOrRejectAllFiles({ behavior: 'accept' })
|
||||
chatThreadService.jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified: true })
|
||||
}}>
|
||||
<div className='bg-void-border-1 h-[1px] flex-grow'></div>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<T extends ToolName> = {
|
|||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
119
src/vs/workbench/contrib/void/common/editCodeServiceTypes.ts
Normal file
119
src/vs/workbench/contrib/void/common/editCodeServiceTypes.ts
Normal file
|
|
@ -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<Function> // these don't remove diffs or this diffArea, only their styles
|
||||
} & CommonZoneProps
|
||||
|
||||
|
||||
export type TrackingZone<T> = {
|
||||
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<any>
|
||||
|
||||
|
||||
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<string, Diff>; // 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<Function> // 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<DiffAreaType extends DiffArea = DiffArea> = Pick<DiffAreaType, typeof diffAreaSnapshotKeys[number]>
|
||||
|
||||
export type VoidFileSnapshot = {
|
||||
snapshottedDiffAreaOfId: Record<string, DiffAreaSnapshotEntry>;
|
||||
entireFileCode: string;
|
||||
}
|
||||
|
||||
|
|
@ -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.' },
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue