mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
editCodeService and voidCommandBar service!!! + VoidCommandBar.tsx
This commit is contained in:
parent
ec505b653b
commit
744d387fe1
20 changed files with 864 additions and 674 deletions
|
|
@ -3,10 +3,10 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { ICodeEditor, IOverlayWidget, IViewZone, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/browser/editorBrowser.js';
|
||||
|
||||
// import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js';
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
|
|
@ -40,12 +40,13 @@ 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, URIStreamState, AddCtrlKOpts, StartApplyingOpts } from './editCodeServiceInterface.js';
|
||||
import { IEditCodeService, AddCtrlKOpts, StartApplyingOpts } 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';
|
||||
|
||||
const configOfBG = (color: Color) => {
|
||||
return { dark: color, light: color, hcDark: color, hcLight: color, }
|
||||
|
|
@ -236,32 +237,29 @@ type StreamLocationMutable = { line: number, col: number, addedSplitYet: boolean
|
|||
class EditCodeService extends Disposable implements IEditCodeService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
|
||||
// URI <--> model
|
||||
diffAreasOfURI: Record<string, Set<string> | undefined> = {}; // uri -> diffareaId
|
||||
|
||||
diffAreaOfId: Record<string, DiffArea> = {}; // diffareaId -> diffArea
|
||||
diffOfId: Record<string, Diff> = {}; // diffid -> diff (redundant with diffArea._diffOfId)
|
||||
|
||||
_sortedUrisWithDiffs: URI[] = [] // derivative of diffAreaOfId (computed from it)
|
||||
_sortedDiffsOfFspath: { [uriString: string]: Diff[] | undefined } = {} // derivative of diffAreaOfId (computed from it)
|
||||
|
||||
// only applies to diffZones
|
||||
// streamingDiffZones: Set<number> = new Set()
|
||||
private readonly _onDidChangeDiffZoneStreaming = new Emitter<{ uri: URI; diffareaid: number }>();
|
||||
// events
|
||||
|
||||
|
||||
// uri: diffZones // listen on change diffZones
|
||||
private readonly _onDidAddOrDeleteDiffZones = new Emitter<{ uri: URI }>();
|
||||
onDidAddOrDeleteDiffZones = this._onDidAddOrDeleteDiffZones.event;
|
||||
|
||||
private readonly _onDidFinishAddOrDeleteDiffInDiffZone = new Emitter<{ uri: URI }>();
|
||||
onDidAddOrDeleteDiffInDiffZone = this._onDidFinishAddOrDeleteDiffInDiffZone.event;
|
||||
|
||||
private readonly _onDidChangeCtrlKZoneStreaming = new Emitter<{ uri: URI; diffareaid: number }>();
|
||||
onDidChangeCtrlKZoneStreaming = this._onDidChangeCtrlKZoneStreaming.event
|
||||
|
||||
private readonly _onDidChangeURIStreamState = new Emitter<{ uri: URI; state: URIStreamState }>();
|
||||
onDidChangeURIStreamState = this._onDidChangeURIStreamState.event
|
||||
|
||||
// diffZone: [uri], diffs, isStreaming // listen on change diffs, change streaming (uri is const)
|
||||
private readonly _onDidChangeDiffsInDiffZone = new Emitter<{ uri: URI, diffareaid: number }>();
|
||||
private readonly _onDidChangeStreamingInDiffZone = new Emitter<{ uri: URI, diffareaid: number }>();
|
||||
onDidChangeDiffsInDiffZone = this._onDidChangeDiffsInDiffZone.event;
|
||||
onDidChangeStreamingInDiffZone = this._onDidChangeStreamingInDiffZone.event;
|
||||
|
||||
// ctrlKZone: [uri], isStreaming // listen on change streaming
|
||||
private readonly _onDidChangeStreamingInCtrlKZone = new Emitter<{ uri: URI; diffareaid: number }>();
|
||||
onDidChangeStreamingInCtrlKZone = this._onDidChangeStreamingInCtrlKZone.event
|
||||
|
||||
|
||||
constructor(
|
||||
|
|
@ -284,14 +282,14 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
super();
|
||||
|
||||
// this function initializes data structures and listens for changes
|
||||
const registeredModelListeners = new Set<string>()
|
||||
const registeredModelURIs = new Set<string>()
|
||||
const initializeModel = async (model: ITextModel) => {
|
||||
|
||||
await this._voidModelService.initializeModel(model.uri)
|
||||
|
||||
// do not add listeners to the same model twice - important, or will see duplicates
|
||||
if (registeredModelListeners.has(model.uri.fsPath)) return
|
||||
registeredModelListeners.add(model.uri.fsPath)
|
||||
if (registeredModelURIs.has(model.uri.fsPath)) return
|
||||
registeredModelURIs.add(model.uri.fsPath)
|
||||
|
||||
if (!(model.uri.fsPath in this.diffAreasOfURI)) {
|
||||
this.diffAreasOfURI[model.uri.fsPath] = new Set();
|
||||
|
|
@ -307,30 +305,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
})
|
||||
)
|
||||
|
||||
// when a stream starts or ends, fire the event for onDidChangeURIStreamState
|
||||
let prevStreamState = this.getURIStreamState({ uri: null })
|
||||
const updateAcceptRejectAllUI = () => {
|
||||
const state = this.getURIStreamState({ uri: model.uri })
|
||||
let prevStateActual = prevStreamState
|
||||
prevStreamState = state
|
||||
if (state === prevStateActual) return
|
||||
this._onDidChangeURIStreamState.fire({ uri: model.uri, state })
|
||||
}
|
||||
|
||||
|
||||
let _removeAcceptRejectAllUI: (() => void) | null = null
|
||||
this._register(this._onDidChangeURIStreamState.event(({ uri, state }) => {
|
||||
if (uri.fsPath !== model.uri.fsPath) return
|
||||
if (state === 'acceptRejectAll') {
|
||||
if (!_removeAcceptRejectAllUI)
|
||||
_removeAcceptRejectAllUI = this._addAcceptRejectAllUI(model.uri) ?? null
|
||||
} else {
|
||||
_removeAcceptRejectAllUI?.()
|
||||
_removeAcceptRejectAllUI = null
|
||||
}
|
||||
}))
|
||||
this._register(this._onDidChangeDiffZoneStreaming.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) updateAcceptRejectAllUI() }))
|
||||
this._register(this._onDidAddOrDeleteDiffZones.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) updateAcceptRejectAllUI() }))
|
||||
// when the model first mounts, refresh any diffs that might be on it (happens if diffs were added in the BG)
|
||||
this._refreshStylesAndDiffsInURI(model.uri)
|
||||
}
|
||||
|
|
@ -350,44 +324,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
this._register(this._codeEditorService.onCodeEditorAdd(editor => { initializeEditor(editor) }))
|
||||
|
||||
|
||||
// update `_sortedUrisWithDiffs` and `_sortedDiffsOfUri` on all changes to diffzones
|
||||
this._register(this._onDidFinishAddOrDeleteDiffInDiffZone.event(({ uri }) => {
|
||||
|
||||
|
||||
// 1. Update _sortedUrisWithDiffs
|
||||
const hasDiffZones = Array.from(this.diffAreasOfURI[uri.fsPath] || [])
|
||||
.some(diffAreaId => this.diffAreaOfId[diffAreaId]?.type === 'DiffZone');
|
||||
|
||||
// Add or remove this URI from _sortedUrisWithDiffs
|
||||
const currentIndex = this._sortedUrisWithDiffs.findIndex(u => u.fsPath === uri.fsPath);
|
||||
if (hasDiffZones && currentIndex === -1) {
|
||||
// Add URI and maintain sort
|
||||
this._sortedUrisWithDiffs.push(uri);
|
||||
this._sortedUrisWithDiffs.sort((a, b) => a.fsPath.localeCompare(b.fsPath));
|
||||
} else if (!hasDiffZones && currentIndex !== -1) {
|
||||
// Remove URI
|
||||
this._sortedUrisWithDiffs.splice(currentIndex, 1);
|
||||
}
|
||||
|
||||
// 2. Update _sortedDiffsOfUri only for this URI
|
||||
const diffsInUri: Diff[] = [];
|
||||
|
||||
// Collect all diffs from DiffZones in this URI
|
||||
for (const diffAreaId of this.diffAreasOfURI[uri.fsPath] || []) {
|
||||
const diffArea = this.diffAreaOfId[diffAreaId];
|
||||
if (diffArea?.type === 'DiffZone') {
|
||||
diffsInUri.push(...Object.values(diffArea._diffOfId));
|
||||
}
|
||||
}
|
||||
|
||||
// Update or remove the entry for this URI
|
||||
if (diffsInUri.length > 0) {
|
||||
this._sortedDiffsOfFspath[uri.fsPath] = diffsInUri.sort((a, b) => a.startLine - b.startLine);
|
||||
} else {
|
||||
delete this._sortedDiffsOfFspath[uri.fsPath];
|
||||
}
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -494,40 +430,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
}
|
||||
|
||||
private _addAcceptRejectAllUI(uri: URI) {
|
||||
|
||||
// find all diffzones that aren't streaming
|
||||
const diffZones: DiffZone[] = []
|
||||
for (let diffareaid of this.diffAreasOfURI[uri.fsPath] || []) {
|
||||
const diffArea = this.diffAreaOfId[diffareaid]
|
||||
if (diffArea.type !== 'DiffZone') continue
|
||||
if (diffArea._streamState.isStreaming) continue
|
||||
diffZones.push(diffArea)
|
||||
}
|
||||
if (diffZones.length === 0) return
|
||||
|
||||
const consistentItemId = this._consistentItemService.addConsistentItemToURI({
|
||||
uri,
|
||||
fn: (editor) => {
|
||||
const buttonsWidget = new AcceptAllRejectAllWidget({
|
||||
editor,
|
||||
onAcceptAll: () => {
|
||||
this.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false, _addToHistory: true })
|
||||
this._metricsService.capture('Accept All', {})
|
||||
},
|
||||
onRejectAll: () => {
|
||||
this.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false, _addToHistory: true })
|
||||
this._metricsService.capture('Reject All', {})
|
||||
},
|
||||
instantiationService: this._instantiationService,
|
||||
})
|
||||
return () => { buttonsWidget.dispose() }
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return () => { this._consistentItemService.removeConsistentItemFromURI(consistentItemId) }
|
||||
}
|
||||
|
||||
|
||||
mostRecentTextOfCtrlKZoneId: Record<string, string | undefined> = {}
|
||||
|
|
@ -564,9 +466,9 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
})
|
||||
|
||||
// mount react
|
||||
let disposablesRef: IDisposable[] | undefined = undefined
|
||||
let disposeFn: (() => void) | undefined = undefined
|
||||
this._instantiationService.invokeFunction(accessor => {
|
||||
disposablesRef = mountCtrlK(domNode, accessor, {
|
||||
disposeFn = mountCtrlK(domNode, accessor, {
|
||||
|
||||
diffareaid: ctrlKZone.diffareaid,
|
||||
|
||||
|
|
@ -591,14 +493,13 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
this.mostRecentTextOfCtrlKZoneId[ctrlKZone.diffareaid] = text;
|
||||
},
|
||||
initText: this.mostRecentTextOfCtrlKZoneId[ctrlKZone.diffareaid] ?? null,
|
||||
} satisfies QuickEditPropsType)
|
||||
|
||||
} satisfies QuickEditPropsType)?.dispose
|
||||
})
|
||||
|
||||
// cleanup
|
||||
return () => {
|
||||
editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) })
|
||||
disposablesRef?.forEach(d => d.dispose())
|
||||
disposeFn?.()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -624,7 +525,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
if (diffArea.type !== 'CtrlKZone') continue
|
||||
if (!diffArea._mountInfo) {
|
||||
diffArea._mountInfo = this._addCtrlKZoneInput(diffArea)
|
||||
// console.log('MOUNTED', diffArea.diffareaid)
|
||||
console.log('MOUNTED CTRLK', diffArea.diffareaid)
|
||||
}
|
||||
else {
|
||||
diffArea._mountInfo.refresh()
|
||||
|
|
@ -748,15 +649,15 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
else { throw new Error('Void 1') }
|
||||
|
||||
const buttonsWidget = new AcceptRejectWidget({
|
||||
const buttonsWidget = new AcceptRejectInlineWidget({
|
||||
editor,
|
||||
onAccept: () => {
|
||||
this.acceptDiff({ diffid })
|
||||
this._metricsService.capture('Accept Diff', {})
|
||||
this._metricsService.capture('Accept Diff', { diffid })
|
||||
},
|
||||
onReject: () => {
|
||||
this.rejectDiff({ diffid })
|
||||
this._metricsService.capture('Reject Diff', {})
|
||||
this._metricsService.capture('Reject Diff', { diffid })
|
||||
},
|
||||
diffid: diffid.toString(),
|
||||
startLine,
|
||||
|
|
@ -927,10 +828,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
if (diffArea.type !== 'DiffZone') return
|
||||
delete diffArea._diffOfId[diff.diffid]
|
||||
delete this.diffOfId[diff.diffid]
|
||||
|
||||
if (!diffArea._streamState.isStreaming) {
|
||||
this._onDidFinishAddOrDeleteDiffInDiffZone.fire({ uri: diffArea._URI })
|
||||
}
|
||||
}
|
||||
|
||||
private _deleteDiffs(diffZone: DiffZone) {
|
||||
|
|
@ -1023,10 +920,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
this.diffOfId[diffid] = newDiff
|
||||
diffZone._diffOfId[diffid] = newDiff
|
||||
|
||||
if (!diffZone._streamState.isStreaming) {
|
||||
this._onDidFinishAddOrDeleteDiffInDiffZone.fire({ uri })
|
||||
}
|
||||
|
||||
return newDiff
|
||||
}
|
||||
|
||||
|
|
@ -1094,6 +987,19 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
|
||||
|
||||
private _fireChangeDiffsIfNotStreaming(uri: URI) {
|
||||
for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) {
|
||||
const diffArea = this.diffAreaOfId[diffareaid]
|
||||
if (diffArea?.type !== 'DiffZone') continue
|
||||
// fire changed diffs (this is the only place Diffs are added)
|
||||
if (!diffArea._streamState.isStreaming) {
|
||||
this._onDidChangeDiffsInDiffZone.fire({ uri, diffareaid: diffArea.diffareaid })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private _refreshStylesAndDiffsInURI(uri: URI) {
|
||||
|
||||
// 1. clear DiffArea styles and Diffs
|
||||
|
|
@ -1107,6 +1013,9 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
// 4. refresh ctrlK zones
|
||||
this._refreshCtrlKInputs(uri)
|
||||
|
||||
// 5. this is the only place where diffs are changed, so can fire here only
|
||||
this._fireChangeDiffsIfNotStreaming(uri)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1301,34 +1210,48 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
private _startStreamingDiffZone({
|
||||
uri,
|
||||
startRange,
|
||||
startBehavior,
|
||||
streamRequestIdRef,
|
||||
onUndo,
|
||||
linkedCtrlKZone,
|
||||
}: {
|
||||
uri: URI,
|
||||
startRange: 'fullFile' | [number, number],
|
||||
startBehavior: 'accept-conflicts' | 'reject-conflicts' | 'keep-conflicts',
|
||||
streamRequestIdRef: { current: string | null },
|
||||
linkedCtrlKZone: CtrlKZone | null,
|
||||
onUndo: () => void,
|
||||
}) {
|
||||
const { model } = this._voidModelService.getModel(uri)
|
||||
if (!model) return
|
||||
|
||||
const startLine = startRange === 'fullFile' ? 1 : startRange[0]
|
||||
const endLine = startRange === 'fullFile' ? model.getLineCount() : startRange[1]
|
||||
let originalCode = startRange === 'fullFile' ? model.getValue(EndOfLinePreference.LF) : model.getValue(EndOfLinePreference.LF).split('\n').slice((startLine - 1), (endLine - 1) + 1).join('\n')
|
||||
// treat like full file, unless linkedCtrlKZone was provided in which case use its diff's range
|
||||
|
||||
|
||||
|
||||
const startLine = linkedCtrlKZone ? linkedCtrlKZone.startLine : 1
|
||||
const endLine = linkedCtrlKZone ? linkedCtrlKZone.endLine : model.getLineCount()
|
||||
const range = { startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: Number.MAX_SAFE_INTEGER }
|
||||
|
||||
const originalFileStr = model.getValue(EndOfLinePreference.LF)
|
||||
let originalCode = model.getValueInRange(range, EndOfLinePreference.LF)
|
||||
|
||||
// add to history as a checkpoint, before we start modifying
|
||||
const { onFinishEdit } = this._addToHistory(uri, { onUndo })
|
||||
|
||||
// clear diffZones so no conflict
|
||||
if (startBehavior === 'keep-conflicts') {
|
||||
// delete them then re-apply their change
|
||||
this.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: 'reject', _addToHistory: false })
|
||||
const originalCodeReplacement = model.getValue(EndOfLinePreference.LF) // use this as original code
|
||||
this._writeURIText(uri, originalCode, 'wholeFileRange', { shouldRealignDiffAreas: false }) // un-revert
|
||||
originalCode = originalCodeReplacement
|
||||
if (linkedCtrlKZone) {
|
||||
// ctrlkzone should never have any conflicts
|
||||
}
|
||||
else {
|
||||
// keep conflict on whole file - to keep conflict, revert the change and use those contents as original, then un-revert the change
|
||||
const currentFileStr = originalFileStr
|
||||
this.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: 'reject', _addToHistory: false })
|
||||
const oldFileStr = model.getValue(EndOfLinePreference.LF) // use this as original code
|
||||
this._writeURIText(uri, currentFileStr, 'wholeFileRange', { shouldRealignDiffAreas: false }) // un-revert
|
||||
originalCode = oldFileStr
|
||||
}
|
||||
|
||||
}
|
||||
else if (startBehavior === 'accept-conflicts' || startBehavior === 'reject-conflicts') {
|
||||
const behavior = startBehavior === 'accept-conflicts' ? 'accept' : 'reject'
|
||||
|
|
@ -1351,11 +1274,18 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
const diffZone = this._addDiffArea(adding)
|
||||
this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid })
|
||||
this._onDidChangeStreamingInDiffZone.fire({ uri, diffareaid: diffZone.diffareaid })
|
||||
this._onDidAddOrDeleteDiffZones.fire({ uri })
|
||||
|
||||
// a few items related to the ctrlKZone that started streaming this diffZone
|
||||
if (linkedCtrlKZone) {
|
||||
const ctrlKZone = linkedCtrlKZone
|
||||
ctrlKZone._linkedStreamingDiffZone = diffZone.diffareaid
|
||||
this._onDidChangeStreamingInCtrlKZone.fire({ uri, diffareaid: ctrlKZone.diffareaid })
|
||||
}
|
||||
|
||||
console.log('DONE _STREAMING', diffZone)
|
||||
|
||||
console.log('DONE WITH _STARTSTREAMING', diffZone)
|
||||
return { diffZone, onFinishEdit }
|
||||
}
|
||||
|
||||
|
|
@ -1372,6 +1302,8 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
let uri: URI
|
||||
let startRange: 'fullFile' | [number, number]
|
||||
|
||||
let ctrlKZoneIfQuickEdit: CtrlKZone | null = null
|
||||
|
||||
if (from === 'ClickApply') {
|
||||
const uri_ = this._getActiveEditorURI()
|
||||
if (!uri_) return
|
||||
|
|
@ -1381,7 +1313,8 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
else if (from === 'QuickEdit') {
|
||||
const { diffareaid } = opts
|
||||
const ctrlKZone = this.diffAreaOfId[diffareaid]
|
||||
if (ctrlKZone.type !== 'CtrlKZone') return
|
||||
if (ctrlKZone?.type !== 'CtrlKZone') return
|
||||
ctrlKZoneIfQuickEdit = ctrlKZone
|
||||
const { startLine: startLine_, endLine: endLine_, _URI } = ctrlKZone
|
||||
uri = _URI
|
||||
startRange = [startLine_, endLine_]
|
||||
|
|
@ -1390,10 +1323,13 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
throw new Error(`Void: diff.type not recognized on: ${from}`)
|
||||
}
|
||||
|
||||
console.log('q1', this.diffAreaOfId)
|
||||
await this._voidModelService.initializeModel(uri)
|
||||
console.log('q2', this.diffAreaOfId)
|
||||
const { model } = this._voidModelService.getModel(uri)
|
||||
if (!model) return
|
||||
|
||||
console.log('q3', this.diffAreaOfId)
|
||||
|
||||
// promise that resolves when the apply is done
|
||||
let resApplyPromise: () => void
|
||||
|
|
@ -1401,16 +1337,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
const applyPromise = new Promise<void>((res_, rej_) => { resApplyPromise = res_; rejApplyPromise = rej_ })
|
||||
let streamRequestIdRef: { current: string | null } = { current: null } // can use this as a proxy to set the diffArea's stream state requestId
|
||||
|
||||
// start diffzone
|
||||
const res = this._startStreamingDiffZone({
|
||||
uri,
|
||||
streamRequestIdRef,
|
||||
startRange,
|
||||
startBehavior: opts.startBehavior,
|
||||
onUndo: () => { if (diffZone._streamState.isStreaming) rejApplyPromise(new Error('Edit was interrupted by pressing undo.')) },
|
||||
})
|
||||
if (!res) return
|
||||
const { diffZone, onFinishEdit } = res
|
||||
|
||||
// build messages
|
||||
const quickEditFIMTags = defaultQuickEditFimTags // TODO can eventually let users customize modelFimTags
|
||||
|
|
@ -1426,11 +1352,13 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
]
|
||||
}
|
||||
else if (from === 'QuickEdit') {
|
||||
const { diffareaid } = opts
|
||||
const ctrlKZone = this.diffAreaOfId[diffareaid]
|
||||
if (ctrlKZone.type !== 'CtrlKZone') return
|
||||
const { _mountInfo } = ctrlKZone
|
||||
console.log('aaa')
|
||||
if (!ctrlKZoneIfQuickEdit) return
|
||||
console.log('bbb', ctrlKZoneIfQuickEdit)
|
||||
const { _mountInfo } = ctrlKZoneIfQuickEdit
|
||||
console.log('ccc', _mountInfo)
|
||||
const instructions = _mountInfo?.textAreaRef.current?.value ?? ''
|
||||
console.log('ddd', instructions)
|
||||
|
||||
const startLine = startRange === 'fullFile' ? 1 : startRange[0]
|
||||
const endLine = startRange === 'fullFile' ? model.getLineCount() : startRange[1]
|
||||
|
|
@ -1445,27 +1373,30 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
else { throw new Error(`featureName ${from} is invalid`) }
|
||||
|
||||
|
||||
// a few items related to writeover streams and quickEdits
|
||||
if (from === 'QuickEdit') {
|
||||
const { diffareaid } = opts
|
||||
const ctrlKZone = this.diffAreaOfId[diffareaid]
|
||||
if (ctrlKZone.type !== 'CtrlKZone') return
|
||||
// start diffzone
|
||||
const res = this._startStreamingDiffZone({
|
||||
uri,
|
||||
streamRequestIdRef,
|
||||
startBehavior: opts.startBehavior,
|
||||
onUndo: () => { if (diffZone._streamState.isStreaming) rejApplyPromise(new Error('Edit was interrupted by pressing undo.')) },
|
||||
linkedCtrlKZone: ctrlKZoneIfQuickEdit,
|
||||
})
|
||||
if (!res) return
|
||||
const { diffZone, onFinishEdit } = res
|
||||
|
||||
|
||||
ctrlKZone._linkedStreamingDiffZone = diffZone.diffareaid
|
||||
this._onDidChangeCtrlKZoneStreaming.fire({ uri, diffareaid: ctrlKZone.diffareaid })
|
||||
}
|
||||
|
||||
// helpers
|
||||
const onDone = () => {
|
||||
console.log('called onDone')
|
||||
diffZone._streamState = { isStreaming: false, }
|
||||
this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid })
|
||||
this._onDidChangeStreamingInDiffZone.fire({ uri, diffareaid: diffZone.diffareaid })
|
||||
|
||||
if (from === 'QuickEdit') {
|
||||
const ctrlKZone = this.diffAreaOfId[opts.diffareaid] as CtrlKZone
|
||||
if (ctrlKZoneIfQuickEdit) {
|
||||
const ctrlKZone = ctrlKZoneIfQuickEdit
|
||||
|
||||
ctrlKZone._linkedStreamingDiffZone = null
|
||||
this._onDidChangeCtrlKZoneStreaming.fire({ uri, diffareaid: ctrlKZone.diffareaid })
|
||||
this._onDidChangeStreamingInCtrlKZone.fire({ uri, diffareaid: ctrlKZone.diffareaid })
|
||||
this._deleteCtrlKZone(ctrlKZone)
|
||||
}
|
||||
this._refreshStylesAndDiffsInURI(uri)
|
||||
|
|
@ -1593,16 +1524,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
const applyDonePromise = new Promise<void>((res_, rej_) => { resApplyDonePromise = res_; rejApplyDonePromise = rej_ })
|
||||
let streamRequestIdRef: { current: string | null } = { current: null } // can use this as a proxy to set the diffArea's stream state requestId
|
||||
|
||||
// start diffzone
|
||||
const res = this._startStreamingDiffZone({
|
||||
uri,
|
||||
streamRequestIdRef,
|
||||
startRange: 'fullFile',
|
||||
startBehavior: opts.startBehavior,
|
||||
onUndo: () => { if (diffZone._streamState.isStreaming) rejApplyDonePromise(new Error('Edit was interrupted by user pressing undo.')) },
|
||||
})
|
||||
if (!res) return
|
||||
const { diffZone, onFinishEdit } = res
|
||||
|
||||
// build messages - ask LLM to generate search/replace block text
|
||||
const originalFileCode = model.getValue(EndOfLinePreference.LF)
|
||||
|
|
@ -1612,6 +1533,18 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
{ role: 'user', content: userMessageContent },
|
||||
]
|
||||
|
||||
// start diffzone
|
||||
const res = this._startStreamingDiffZone({
|
||||
uri,
|
||||
streamRequestIdRef,
|
||||
startBehavior: opts.startBehavior,
|
||||
linkedCtrlKZone: null,
|
||||
onUndo: () => { if (diffZone._streamState.isStreaming) rejApplyDonePromise(new Error('Edit was interrupted by user pressing undo.')) },
|
||||
})
|
||||
if (!res) return
|
||||
const { diffZone, onFinishEdit } = res
|
||||
|
||||
|
||||
// helpers
|
||||
type SearchReplaceDiffAreaMetadata = {
|
||||
originalBounds: [number, number], // 1-indexed
|
||||
|
|
@ -1656,7 +1589,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
const onDone = () => {
|
||||
diffZone._streamState = { isStreaming: false, }
|
||||
this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid })
|
||||
this._onDidChangeStreamingInDiffZone.fire({ uri, diffareaid: diffZone.diffareaid })
|
||||
this._refreshStylesAndDiffsInURI(uri)
|
||||
|
||||
// delete the tracking zones
|
||||
|
|
@ -1929,7 +1862,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
this._llmMessageService.abort(streamRequestId)
|
||||
|
||||
diffZone._streamState = { isStreaming: false, }
|
||||
this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid })
|
||||
this._onDidChangeStreamingInDiffZone.fire({ uri, diffareaid: diffZone.diffareaid })
|
||||
}
|
||||
|
||||
_undoHistory(uri: URI) {
|
||||
|
|
@ -1972,24 +1905,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
|
||||
private _getDiffZonesOnURI(uri: URI) {
|
||||
const diffZones = [...this.diffAreasOfURI[uri.fsPath]?.values() ?? []]
|
||||
.map(diffareaid => this.diffAreaOfId[diffareaid])
|
||||
.filter(diffArea => !!diffArea && diffArea.type === 'DiffZone')
|
||||
|
||||
return diffZones
|
||||
}
|
||||
|
||||
getURIStreamState = ({ uri }: { uri: URI | null }) => {
|
||||
if (uri === null) return 'idle'
|
||||
const diffZones = this._getDiffZonesOnURI(uri)
|
||||
|
||||
const isStreaming = diffZones.find(diffZone => !!diffZone._streamState.isStreaming)
|
||||
|
||||
const state: URIStreamState = isStreaming ? 'streaming' : (diffZones.length === 0 ? 'idle' : 'acceptRejectAll')
|
||||
return state
|
||||
}
|
||||
|
||||
interruptURIStreaming({ uri }: { uri: URI }) {
|
||||
// brute force for now is OK
|
||||
for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) {
|
||||
|
|
@ -2013,14 +1928,12 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
// onFinishEdit()
|
||||
// }
|
||||
|
||||
private _revertAndDeleteDiffZone(diffZone: DiffZone) {
|
||||
private _revertDiffZone(diffZone: DiffZone) {
|
||||
const uri = diffZone._URI
|
||||
|
||||
const writeText = diffZone.originalCode
|
||||
const toRange: IRange = { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }
|
||||
this._writeURIText(uri, writeText, toRange, { shouldRealignDiffAreas: true })
|
||||
|
||||
this._deleteDiffZone(diffZone)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2037,7 +1950,10 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
if (!diffArea) continue
|
||||
|
||||
if (diffArea.type === 'DiffZone') {
|
||||
if (behavior === 'reject') this._revertAndDeleteDiffZone(diffArea)
|
||||
if (behavior === 'reject') {
|
||||
this._revertDiffZone(diffArea)
|
||||
this._deleteDiffZone(diffArea)
|
||||
}
|
||||
else if (behavior === 'accept') this._deleteDiffZone(diffArea)
|
||||
}
|
||||
else if (diffArea.type === 'CtrlKZone' && removeCtrlKs) {
|
||||
|
|
@ -2209,62 +2125,18 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// testDiffs(): DiffZone | undefined {
|
||||
// const uri = this._getActiveEditorURI()
|
||||
// if (!uri) return
|
||||
|
||||
// const startLine = 1
|
||||
// const endLine = 4
|
||||
|
||||
// const currentFileStr = this._readURI(uri)
|
||||
// if (currentFileStr === null) return
|
||||
// const originalCode = currentFileStr.split('\n').slice((startLine - 1), (endLine - 1) + 1).join('\n')
|
||||
|
||||
// const { onFinishEdit } = this._addToHistory(uri)
|
||||
// const adding: Omit<DiffZone, 'diffareaid'> = {
|
||||
// type: 'DiffZone',
|
||||
// originalCode,
|
||||
// startLine,
|
||||
// endLine,
|
||||
// _URI: uri,
|
||||
// _streamState: { isStreaming: false, },
|
||||
// _diffOfId: {}, // added later
|
||||
// _removeStylesFns: new Set(),
|
||||
// }
|
||||
// const diffZone = this._addDiffArea(adding)
|
||||
// const endResult = `\
|
||||
// const x = 1;
|
||||
// if (x > 0) {
|
||||
// console.log('hi!')
|
||||
// }`
|
||||
// this._writeText(uri, endResult,
|
||||
// { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed
|
||||
// { shouldRealignDiffAreas: true }
|
||||
// )
|
||||
// diffZone._streamState = { isStreaming: false, }
|
||||
// this._refreshStylesAndDiffsInURI(uri)
|
||||
// onFinishEdit()
|
||||
|
||||
// return diffZone
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IEditCodeService, EditCodeService, InstantiationType.Eager);
|
||||
|
||||
const acceptBg = '#1a7431'
|
||||
const acceptAllBg = '#1e8538'
|
||||
const acceptBorder = '1px solid #145626'
|
||||
const rejectBg = '#b42331'
|
||||
const rejectAllBg = '#cf2838'
|
||||
const rejectBorder = '1px solid #8e1c27'
|
||||
const buttonFontSize = '11px'
|
||||
const buttonTextColor = 'white'
|
||||
|
||||
class AcceptRejectWidget extends Widget implements IOverlayWidget {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AcceptRejectInlineWidget extends Widget implements IOverlayWidget {
|
||||
|
||||
public getId() { return this.ID }
|
||||
public getDomNode() { return this._domNode; }
|
||||
|
|
@ -2380,97 +2252,3 @@ class AcceptRejectWidget extends Widget implements IOverlayWidget {
|
|||
|
||||
|
||||
|
||||
|
||||
class AcceptAllRejectAllWidget extends Widget implements IOverlayWidget {
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly editor: ICodeEditor;
|
||||
private readonly ID: string;
|
||||
private readonly _instantiationService: IInstantiationService;
|
||||
|
||||
constructor({ editor, onAcceptAll, onRejectAll, instantiationService }: {
|
||||
editor: ICodeEditor,
|
||||
onAcceptAll: () => void,
|
||||
onRejectAll: () => void,
|
||||
instantiationService: IInstantiationService
|
||||
}) {
|
||||
super();
|
||||
|
||||
this.ID = editor.getModel()?.uri.fsPath + '';
|
||||
this.editor = editor;
|
||||
this._instantiationService = instantiationService;
|
||||
|
||||
// Create container div with buttons
|
||||
const { voidCommandBar, acceptButton, rejectButton, buttons } = dom.h('div@buttons', [
|
||||
dom.h('div@voidCommandBar', []),
|
||||
dom.h('button@acceptButton', []),
|
||||
dom.h('button@rejectButton', [])
|
||||
]);
|
||||
|
||||
// Style the container
|
||||
buttons.style.zIndex = '2';
|
||||
buttons.style.padding = '4px';
|
||||
buttons.style.display = 'flex';
|
||||
buttons.style.gap = '4px';
|
||||
buttons.style.alignItems = 'center';
|
||||
|
||||
// Mount command bar using mountVoidCommandBar
|
||||
this._instantiationService.invokeFunction(accessor => {
|
||||
console.log(voidCommandBar)
|
||||
if (voidCommandBar) { // remove this
|
||||
Math.random()
|
||||
}
|
||||
// mountVoidCommandBar(voidCommandBar, accessor, {})
|
||||
});
|
||||
|
||||
// Style accept button
|
||||
acceptButton.addEventListener('click', onAcceptAll)
|
||||
acceptButton.textContent = 'Accept All';
|
||||
acceptButton.style.backgroundColor = acceptAllBg;
|
||||
acceptButton.style.border = acceptBorder;
|
||||
acceptButton.style.color = buttonTextColor;
|
||||
acceptButton.style.fontSize = buttonFontSize;
|
||||
acceptButton.style.padding = '4px 8px';
|
||||
acceptButton.style.borderRadius = '6px';
|
||||
acceptButton.style.cursor = 'pointer';
|
||||
|
||||
// Style reject button
|
||||
rejectButton.addEventListener('click', onRejectAll)
|
||||
rejectButton.textContent = 'Reject All';
|
||||
rejectButton.style.backgroundColor = rejectAllBg;
|
||||
rejectButton.style.border = rejectBorder;
|
||||
rejectButton.style.color = buttonTextColor;
|
||||
rejectButton.style.fontSize = buttonFontSize;
|
||||
rejectButton.style.color = 'white';
|
||||
rejectButton.style.padding = '4px 8px';
|
||||
rejectButton.style.borderRadius = '6px';
|
||||
rejectButton.style.cursor = 'pointer';
|
||||
|
||||
this._domNode = buttons;
|
||||
|
||||
// Mount the widget
|
||||
editor.addOverlayWidget(this);
|
||||
}
|
||||
|
||||
|
||||
public getId(): string {
|
||||
return this.ID;
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public getPosition() {
|
||||
return {
|
||||
preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER,
|
||||
}
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
this.editor.removeOverlayWidget(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export type AddCtrlKOpts = {
|
|||
editor: ICodeEditor,
|
||||
}
|
||||
|
||||
export type URIStreamState = 'idle' | 'acceptRejectAll' | 'streaming'
|
||||
export type URIAcceptRejectState = 'idle' | 'acceptRejectAll' | 'streaming'
|
||||
|
||||
|
||||
export const IEditCodeService = createDecorator<IEditCodeService>('editCodeService');
|
||||
|
|
@ -40,32 +40,28 @@ export const IEditCodeService = createDecorator<IEditCodeService>('editCodeServi
|
|||
export interface IEditCodeService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
// main entrypoints (initialize things for the functions below to be called):
|
||||
startApplying(opts: StartApplyingOpts): Promise<[URI, Promise<void>] | null>;
|
||||
_sortedUrisWithDiffs: URI[];
|
||||
_sortedDiffsOfFspath: { [fsPath: string]: Diff[] | undefined };
|
||||
addCtrlKZone(opts: AddCtrlKOpts): number | undefined;
|
||||
removeCtrlKZone(opts: { diffareaid: number }): void;
|
||||
|
||||
diffAreaOfId: Record<string, DiffArea>;
|
||||
diffAreasOfURI: Record<string, Set<string> | undefined>;
|
||||
diffOfId: Record<string, Diff>;
|
||||
|
||||
|
||||
addCtrlKZone(opts: AddCtrlKOpts): number | undefined;
|
||||
|
||||
removeCtrlKZone(opts: { diffareaid: number }): void;
|
||||
acceptOrRejectAllDiffAreas(opts: { uri: URI, removeCtrlKs: boolean, behavior: 'reject' | 'accept', _addToHistory?: boolean }): void;
|
||||
|
||||
// events
|
||||
onDidAddOrDeleteDiffZones: Event<{ uri: URI }>;
|
||||
onDidAddOrDeleteDiffInDiffZone: Event<{ uri: URI }>;
|
||||
onDidChangeDiffsInDiffZone: Event<{ uri: URI; diffareaid: number }>; // only fires when not streaming!!! streaming would be too much
|
||||
onDidChangeStreamingInDiffZone: Event<{ uri: URI; diffareaid: number }>;
|
||||
onDidChangeStreamingInCtrlKZone: Event<{ uri: URI; diffareaid: number }>;
|
||||
|
||||
// CtrlKZone streaming state
|
||||
isCtrlKZoneStreaming(opts: { diffareaid: number }): boolean;
|
||||
interruptCtrlKStreaming(opts: { diffareaid: number }): void;
|
||||
onDidChangeCtrlKZoneStreaming: Event<{ uri: URI; diffareaid: number }>;
|
||||
|
||||
// // DiffZone codeBoxId streaming state
|
||||
getURIStreamState(opts: { uri: URI | null }): URIStreamState;
|
||||
interruptURIStreaming(opts: { uri: URI }): void;
|
||||
onDidChangeURIStreamState: Event<{ uri: URI; state: URIStreamState }>;
|
||||
|
||||
// testDiffs(): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export interface IConsistentItemService {
|
|||
|
||||
export const IConsistentItemService = createDecorator<IConsistentItemService>('ConsistentItemService');
|
||||
|
||||
export class ConsistentItemService extends Disposable {
|
||||
export class ConsistentItemService extends Disposable implements IConsistentItemService {
|
||||
|
||||
readonly _serviceBrand: undefined
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,11 @@ class MetricsPollService extends Disposable implements IMetricsPollService {
|
|||
|
||||
// initial state
|
||||
const { window } = dom.getActiveWindow()
|
||||
let i = 1
|
||||
let i = 0
|
||||
|
||||
this.intervalID = window.setInterval(() => {
|
||||
this.metricsService.capture('Alive', { i })
|
||||
this.metricsService.capture('Alive', { iv1: i })
|
||||
i += 1
|
||||
console.log('ping', i)
|
||||
}, PING_EVERY_MS)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useAccessor, useURIStreamState, useSettingsState } from '../util/services.js'
|
||||
import { useAccessor, useCommandBarState, useCommandBarURIListener, useSettingsState } from '../util/services.js'
|
||||
import { usePromise, useRefState } from '../util/helpers.js'
|
||||
import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
|
|
@ -128,28 +128,37 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: stri
|
|||
|
||||
const accessor = useAccessor()
|
||||
const editCodeService = accessor.get('IEditCodeService')
|
||||
const voidCommandBarService = accessor.get('IVoidCommandBarService')
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
|
||||
const [_, rerender] = useState(0)
|
||||
|
||||
const getUriBeingApplied = useCallback(() => applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null, [applyBoxId])
|
||||
const getStreamState = useCallback(() => editCodeService.getURIStreamState({ uri: getUriBeingApplied() }), [editCodeService, getUriBeingApplied])
|
||||
const getUriBeingApplied = useCallback(() => {
|
||||
return applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null
|
||||
}, [applyBoxId])
|
||||
|
||||
const getStreamState = useCallback(() => {
|
||||
const uri = getUriBeingApplied()
|
||||
if (!uri) return 'idle-no-changes'
|
||||
return voidCommandBarService.getStreamState(uri)
|
||||
}, [voidCommandBarService, getUriBeingApplied])
|
||||
|
||||
// listen for stream updates on this box
|
||||
useURIStreamState(
|
||||
useCallback((uri_, newStreamState) => {
|
||||
const shouldUpdate = (
|
||||
getUriBeingApplied()?.fsPath === uri_.fsPath
|
||||
|| (uri === 'current' ? false : uri.fsPath === uri_.fsPath)
|
||||
)
|
||||
if (!shouldUpdate) return
|
||||
rerender(c => c + 1)
|
||||
}, [applyBoxId, editCodeService, getUriBeingApplied, uri])
|
||||
|
||||
|
||||
useCommandBarURIListener(useCallback((uri_) => {
|
||||
const shouldUpdate = (
|
||||
getUriBeingApplied()?.fsPath === uri_.fsPath
|
||||
|| (uri !== 'current' && uri.fsPath === uri_.fsPath)
|
||||
)
|
||||
if (!shouldUpdate) return
|
||||
rerender(c => c + 1)
|
||||
}, [applyBoxId, editCodeService, getUriBeingApplied, uri])
|
||||
)
|
||||
|
||||
const onClickSubmit = useCallback(async () => {
|
||||
if (isDisabled) return
|
||||
if (getStreamState() === 'streaming') return
|
||||
if (getStreamState()) return
|
||||
const [newApplyingUri, _] = await editCodeService.startApplying({
|
||||
from: 'ClickApply',
|
||||
applyStr: codeStr,
|
||||
|
|
@ -167,7 +176,7 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: stri
|
|||
|
||||
|
||||
const onInterrupt = useCallback(() => {
|
||||
if (getStreamState() !== 'streaming') return
|
||||
if (!getStreamState()) return
|
||||
const uri = getUriBeingApplied()
|
||||
if (!uri) return
|
||||
|
||||
|
|
@ -250,7 +259,7 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: stri
|
|||
</>
|
||||
}
|
||||
|
||||
if (currStreamState === 'idle') {
|
||||
if (currStreamState === 'idle-no-changes') {
|
||||
buttonsHTML = <>
|
||||
<JumpToFileButton uri={uri} />
|
||||
{copyButton}
|
||||
|
|
@ -258,7 +267,7 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: stri
|
|||
</>
|
||||
}
|
||||
|
||||
if (currStreamState === 'acceptRejectAll') {
|
||||
if (currStreamState === 'idle-has-changes') {
|
||||
buttonsHTML = <>
|
||||
<JumpToFileButton uri={uri} />
|
||||
{reapplyButton}
|
||||
|
|
@ -270,9 +279,9 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: stri
|
|||
const statusIndicatorHTML = <div className='flex flex-row items-center size-4'>
|
||||
<div
|
||||
className={` size-1.5 rounded-full border
|
||||
${currStreamState === 'idle' ? 'bg-void-bg-3 border-void-border-1' :
|
||||
${currStreamState === 'idle-no-changes' ? 'bg-void-bg-3 border-void-border-1' :
|
||||
currStreamState === 'streaming' ? 'bg-orange-500 border-orange-500 shadow-[0_0_4px_0px_rgba(234,88,12,0.6)]' :
|
||||
currStreamState === 'acceptRejectAll' ? 'bg-green-500 border-green-500 shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]' :
|
||||
currStreamState === 'idle-has-changes' ? 'bg-green-500 border-green-500 shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]' :
|
||||
'bg-void-border-1 border-void-border-1'
|
||||
}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocati
|
|||
}
|
||||
|
||||
function isValidUri(s: string): boolean {
|
||||
return s.length > 5 && isAbsolute(s) && !s.startsWith('//') // common case that is a false positive is comments like //
|
||||
return s.length > 5 && isAbsolute(s) && !s.includes('//') && !s.includes('/*') // common case that is a false positive is comments like //
|
||||
}
|
||||
|
||||
const Codespan = ({ text, className, onClick }: { text: string, className?: string, onClick?: () => void }) => {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export const QuickEditChat = ({
|
|||
editCodeService.startApplying({
|
||||
from: 'QuickEdit',
|
||||
diffareaid,
|
||||
startBehavior: 'keep-conflicts',
|
||||
})
|
||||
}, [isStreamingRef, isDisabled, editCodeService, diffareaid])
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
|
||||
import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useUriState, useSettingsState } from '../util/services.js';
|
||||
import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useSettingsState, useActiveURI } from '../util/services.js';
|
||||
|
||||
import { ChatMarkdownRender, ChatMessageLocation, getApplyBoxId } from '../markdown/ChatMarkdownRender.js';
|
||||
import { URI } from '../../../../../../../base/common/uri.js';
|
||||
|
|
@ -208,7 +208,7 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) =>
|
|||
|
||||
|
||||
const nameOfChatMode = {
|
||||
'normal': 'Normal',
|
||||
'normal': 'Chat',
|
||||
'gather': 'Gather',
|
||||
'agent': 'Agent',
|
||||
}
|
||||
|
|
@ -488,18 +488,18 @@ export const SelectedFiles = (
|
|||
const modelReferenceService = accessor.get('IVoidModelService')
|
||||
|
||||
// state for tracking prospective files
|
||||
const { currentUri } = useUriState()
|
||||
const { uri: currentURI } = useActiveURI()
|
||||
const [recentUris, setRecentUris] = useState<URI[]>([])
|
||||
const maxRecentUris = 10
|
||||
const maxProspectiveFiles = 3
|
||||
useEffect(() => { // handle recent files
|
||||
if (!currentUri) return
|
||||
if (!currentURI) return
|
||||
setRecentUris(prev => {
|
||||
const withoutCurrent = prev.filter(uri => uri.fsPath !== currentUri.fsPath) // remove duplicates
|
||||
const withCurrent = [currentUri, ...withoutCurrent]
|
||||
const withoutCurrent = prev.filter(uri => uri.fsPath !== currentURI.fsPath) // remove duplicates
|
||||
const withCurrent = [currentURI, ...withoutCurrent]
|
||||
return withCurrent.slice(0, maxRecentUris)
|
||||
})
|
||||
}, [currentUri])
|
||||
}, [currentURI])
|
||||
const [prospectiveSelections, setProspectiveSelections] = useState<StagingSelectionItem[]>([])
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,20 @@ export const mountFnGenerator = (Component: (params: any) => React.ReactNode) =>
|
|||
const disposables = _registerServices(accessor)
|
||||
|
||||
const root = ReactDOM.createRoot(rootElement)
|
||||
root.render(<Component {...props} />); // tailwind dark theme indicator
|
||||
|
||||
return disposables
|
||||
const rerender = (props?: any) => {
|
||||
root.render(<Component {...props} />); // tailwind dark theme indicator
|
||||
}
|
||||
const dispose = () => {
|
||||
root.unmount();
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
|
||||
rerender(props)
|
||||
|
||||
const returnVal = {
|
||||
rerender,
|
||||
dispose,
|
||||
}
|
||||
return returnVal
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
|
|||
import { VoidSidebarState } from '../../../sidebarStateService.js'
|
||||
import { VoidSettingsState } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'
|
||||
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'
|
||||
import { VoidUriState } from '../../../voidUriStateService.js';
|
||||
import { RefreshModelStateOfProvider } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js'
|
||||
|
||||
import { ServicesAccessor } from '../../../../../../../editor/browser/editorExtensions.js';
|
||||
|
|
@ -23,9 +22,8 @@ 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, URIStreamState } from '../../../editCodeServiceInterface.js'
|
||||
import { IEditCodeService } from '../../../editCodeServiceInterface.js'
|
||||
|
||||
import { IVoidUriStateService } from '../../../voidUriStateService.js';
|
||||
import { ISidebarStateService } from '../../../sidebarStateService.js';
|
||||
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'
|
||||
import { ICodeEditorService } from '../../../../../../../editor/browser/services/codeEditorService.js'
|
||||
|
|
@ -47,15 +45,13 @@ import { ITerminalToolService } from '../../../terminalToolService.js'
|
|||
import { ILanguageService } from '../../../../../../../editor/common/languages/language.js'
|
||||
import { IVoidModelService } from '../../../../common/voidModelService.js'
|
||||
import { IWorkspaceContextService } from '../../../../../../../platform/workspace/common/workspace.js'
|
||||
|
||||
import { IVoidCommandBarService } from '../../../voidCommandBarService.js'
|
||||
|
||||
|
||||
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
|
||||
|
||||
// even if React hasn't mounted yet, the variables are always updated to the latest state.
|
||||
// React listens by adding a setState function to these listeners.
|
||||
let uriState: VoidUriState
|
||||
const uriStateListeners: Set<(s: VoidUriState) => void> = new Set()
|
||||
|
||||
let sidebarState: VoidSidebarState
|
||||
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
|
||||
|
|
@ -77,8 +73,8 @@ let colorThemeState: ColorScheme
|
|||
const colorThemeStateListeners: Set<(s: ColorScheme) => void> = new Set()
|
||||
|
||||
const ctrlKZoneStreamingStateListeners: Set<(diffareaid: number, s: boolean) => void> = new Set()
|
||||
const uriStreamingStateListeners: Set<(uri: URI, s: URIStreamState) => void> = new Set()
|
||||
|
||||
const commandBarURIStateListeners: Set<(uri: URI) => void> = new Set();
|
||||
const activeURIListeners: Set<(uri: URI | null) => void> = new Set();
|
||||
|
||||
|
||||
// must call this before you can use any of the hooks below
|
||||
|
|
@ -90,24 +86,17 @@ export const _registerServices = (accessor: ServicesAccessor) => {
|
|||
_registerAccessor(accessor)
|
||||
|
||||
const stateServices = {
|
||||
uriStateService: accessor.get(IVoidUriStateService),
|
||||
sidebarStateService: accessor.get(ISidebarStateService),
|
||||
chatThreadsStateService: accessor.get(IChatThreadService),
|
||||
settingsStateService: accessor.get(IVoidSettingsService),
|
||||
refreshModelService: accessor.get(IRefreshModelService),
|
||||
themeService: accessor.get(IThemeService),
|
||||
editCodeService: accessor.get(IEditCodeService),
|
||||
voidCommandBarService: accessor.get(IVoidCommandBarService),
|
||||
modelService: accessor.get(IModelService),
|
||||
}
|
||||
|
||||
const { uriStateService, sidebarStateService, settingsStateService, chatThreadsStateService, refreshModelService, themeService, editCodeService } = stateServices
|
||||
|
||||
uriState = uriStateService.state
|
||||
disposables.push(
|
||||
uriStateService.onDidChangeState(() => {
|
||||
uriState = uriStateService.state
|
||||
uriStateListeners.forEach(l => l(uriState))
|
||||
})
|
||||
)
|
||||
const { sidebarStateService, settingsStateService, chatThreadsStateService, refreshModelService, themeService, editCodeService, voidCommandBarService, modelService } = stateServices
|
||||
|
||||
sidebarState = sidebarStateService.state
|
||||
disposables.push(
|
||||
|
|
@ -161,15 +150,21 @@ export const _registerServices = (accessor: ServicesAccessor) => {
|
|||
|
||||
// no state
|
||||
disposables.push(
|
||||
editCodeService.onDidChangeCtrlKZoneStreaming(({ diffareaid }) => {
|
||||
editCodeService.onDidChangeStreamingInCtrlKZone(({ diffareaid }) => {
|
||||
const isStreaming = editCodeService.isCtrlKZoneStreaming({ diffareaid })
|
||||
ctrlKZoneStreamingStateListeners.forEach(l => l(diffareaid, isStreaming))
|
||||
})
|
||||
)
|
||||
|
||||
disposables.push(
|
||||
editCodeService.onDidChangeURIStreamState(({ uri }) => {
|
||||
const isStreaming = editCodeService.getURIStreamState({ uri })
|
||||
uriStreamingStateListeners.forEach(l => l(uri, isStreaming))
|
||||
voidCommandBarService.onDidChangeState(({ uri }) => {
|
||||
commandBarURIStateListeners.forEach(l => l(uri));
|
||||
})
|
||||
)
|
||||
|
||||
disposables.push(
|
||||
voidCommandBarService.onDidChangeActiveURI(({ uri }) => {
|
||||
activeURIListeners.forEach(l => l(uri));
|
||||
})
|
||||
)
|
||||
|
||||
|
|
@ -193,7 +188,6 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
|
|||
IRefreshModelService: accessor.get(IRefreshModelService),
|
||||
IVoidSettingsService: accessor.get(IVoidSettingsService),
|
||||
IEditCodeService: accessor.get(IEditCodeService),
|
||||
IVoidUriStateService: accessor.get(IVoidUriStateService),
|
||||
ISidebarStateService: accessor.get(ISidebarStateService),
|
||||
IChatThreadService: accessor.get(IChatThreadService),
|
||||
|
||||
|
|
@ -218,6 +212,8 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
|
|||
IVoidModelService: accessor.get(IVoidModelService),
|
||||
IWorkspaceContextService: accessor.get(IWorkspaceContextService),
|
||||
|
||||
IVoidCommandBarService: accessor.get(IVoidCommandBarService),
|
||||
|
||||
} as const
|
||||
return reactAccessor
|
||||
}
|
||||
|
|
@ -244,16 +240,6 @@ export const useAccessor = () => {
|
|||
|
||||
// -- state of services --
|
||||
|
||||
export const useUriState = () => {
|
||||
const [s, ss] = useState(uriState)
|
||||
useEffect(() => {
|
||||
ss(uriState)
|
||||
uriStateListeners.add(ss)
|
||||
return () => { uriStateListeners.delete(ss) }
|
||||
}, [ss])
|
||||
return s
|
||||
}
|
||||
|
||||
export const useSidebarState = () => {
|
||||
const [s, ss] = useState(sidebarState)
|
||||
useEffect(() => {
|
||||
|
|
@ -340,14 +326,6 @@ export const useCtrlKZoneStreamingState = (listener: (diffareaid: number, s: boo
|
|||
}, [listener, ctrlKZoneStreamingStateListeners])
|
||||
}
|
||||
|
||||
export const useURIStreamState = (listener: (uri: URI, s: URIStreamState) => void) => {
|
||||
useEffect(() => {
|
||||
uriStreamingStateListeners.add(listener)
|
||||
return () => { uriStreamingStateListeners.delete(listener) }
|
||||
}, [listener, uriStreamingStateListeners])
|
||||
}
|
||||
|
||||
|
||||
export const useIsDark = () => {
|
||||
const [s, ss] = useState(colorThemeState)
|
||||
useEffect(() => {
|
||||
|
|
@ -359,6 +337,40 @@ export const useIsDark = () => {
|
|||
// s is the theme, return isDark instead of s
|
||||
const isDark = s === ColorScheme.DARK || s === ColorScheme.HIGH_CONTRAST_DARK
|
||||
return isDark
|
||||
|
||||
}
|
||||
|
||||
export const useCommandBarURIListener = (listener: (uri: URI) => void) => {
|
||||
useEffect(() => {
|
||||
commandBarURIStateListeners.add(listener);
|
||||
return () => { commandBarURIStateListeners.delete(listener) };
|
||||
}, [listener]);
|
||||
};
|
||||
export const useCommandBarState = () => {
|
||||
const accessor = useAccessor()
|
||||
const commandBarService = accessor.get('IVoidCommandBarService')
|
||||
const [s, ss] = useState({ state: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs });
|
||||
const listener = useCallback(() => {
|
||||
ss({ state: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs });
|
||||
}, [commandBarService])
|
||||
useCommandBarURIListener(listener)
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// roughly gets the active URI - this is used to get the history of recent URIs
|
||||
export const useActiveURI = () => {
|
||||
const accessor = useAccessor()
|
||||
const commandBarService = accessor.get('IVoidCommandBarService')
|
||||
const [s, ss] = useState(commandBarService.activeURI)
|
||||
useEffect(() => {
|
||||
const listener = () => { ss(commandBarService.activeURI) }
|
||||
activeURIListeners.add(listener);
|
||||
return () => { activeURIListeners.delete(listener) };
|
||||
}, [])
|
||||
return { uri: s }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,186 +4,241 @@
|
|||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { useAccessor, useIsDark, useUriState } from '../util/services.js';
|
||||
import { useAccessor, useCommandBarState, useIsDark } from '../util/services.js';
|
||||
|
||||
import '../styles.css'
|
||||
import { DiffZone } from '../../../editCodeService.js';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { URI } from '../../../../../../../base/common/uri.js';
|
||||
import { ICodeEditor } from '../../../../../../../editor/browser/editorBrowser.js';
|
||||
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js';
|
||||
import { getBasename } from '../sidebar-tsx/SidebarChat.js';
|
||||
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBorder } from '../../../../common/helpers/colors.js';
|
||||
|
||||
export const VoidCommandBarMain = ({ className }: { className: string }) => {
|
||||
export type VoidCommandBarProps = {
|
||||
uri: URI | null;
|
||||
editor: ICodeEditor;
|
||||
}
|
||||
|
||||
export const VoidCommandBarMain = ({ uri, editor }: VoidCommandBarProps) => {
|
||||
const isDark = useIsDark()
|
||||
|
||||
console.log('VoidCommandBarMain', uri?.fsPath)
|
||||
return <div
|
||||
className={`@@void-scope ${isDark ? 'dark' : ''}`}
|
||||
>
|
||||
<VoidCommandBar />
|
||||
<VoidCommandBar uri={uri} editor={editor} />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
const VoidCommandBar = () => {
|
||||
|
||||
const stepIdx = (currIdx: number | null, len: number, step: -1 | 1) => {
|
||||
if (len === 0) return null
|
||||
if (len === 1 && step === -1) return null // don't step backwards if just 1 element
|
||||
return ((currIdx ?? 0) + step) % len
|
||||
}
|
||||
|
||||
|
||||
|
||||
const VoidCommandBar = ({ uri, editor }: { uri: URI | null, editor: ICodeEditor }) => {
|
||||
const accessor = useAccessor()
|
||||
const editCodeService = accessor.get('IEditCodeService')
|
||||
const editorService = accessor.get('ICodeEditorService')
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const commandBarService = accessor.get('IVoidCommandBarService')
|
||||
const voidModelService = accessor.get('IVoidModelService')
|
||||
const { state: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
|
||||
|
||||
const [_, rerender] = useState(0)
|
||||
// Add a state variable to track focus
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
console.log('rerender count: ', _)
|
||||
|
||||
// state for what the user is currently focused on (both URI and diff)
|
||||
const [diffIdxOfFspath, setDiffIdxOfFspath] = useState<Record<string, number | undefined>>({})
|
||||
// const [currentUriIdx, setCurrentUriIdx] = useState(-1) // we are doing O(n) search for this
|
||||
|
||||
const { currentUri } = useUriState()
|
||||
|
||||
// trigger rerender when diffzone is created (TODO need to also update when diff is accepted/rejected)
|
||||
// changes if the user clicks left/right or if the user goes on a uri with changes
|
||||
const [currUriIdx, setUriIdx] = useState<number | null>(null)
|
||||
const [currUriHasChanges, setCurrUriHasChanges] = useState(false)
|
||||
useEffect(() => {
|
||||
const disposable = editCodeService.onDidAddOrDeleteDiffInDiffZone(() => {
|
||||
rerender(c => c + 1) // rerender
|
||||
})
|
||||
return () => disposable.dispose()
|
||||
}, [editCodeService, rerender])
|
||||
|
||||
const getNextDiff = useCallback(({ step }: { step: 1 | -1 }) => {
|
||||
if (!currentUri) {
|
||||
return;
|
||||
const i = sortedCommandBarURIs.findIndex(e => e.fsPath === uri?.fsPath)
|
||||
if (i !== -1) {
|
||||
setUriIdx(i)
|
||||
setCurrUriHasChanges(true)
|
||||
}
|
||||
|
||||
const sortedDiffs = editCodeService._sortedDiffsOfFspath[currentUri.fsPath]
|
||||
|
||||
if (!sortedDiffs || sortedDiffs.length === 0) {
|
||||
return;
|
||||
else {
|
||||
setCurrUriHasChanges(false)
|
||||
}
|
||||
}, [sortedCommandBarURIs, uri])
|
||||
|
||||
const currentDiffIdx = diffIdxOfFspath[currentUri.fsPath] || 0
|
||||
const nextDiffIdx = (currentDiffIdx + step) % sortedDiffs.length
|
||||
// just for style
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
|
||||
const nextDiff = sortedDiffs[nextDiffIdx]
|
||||
|
||||
return { nextDiff, nextDiffIdx }
|
||||
|
||||
}, [currentUri, editCodeService._sortedDiffsOfFspath, diffIdxOfFspath])
|
||||
|
||||
const getNextUri = useCallback(({ step }: { step: 1 | -1 }) => {
|
||||
|
||||
const sortedUris = editCodeService._sortedUrisWithDiffs
|
||||
if (sortedUris.length === 0) {
|
||||
return;
|
||||
const getNextDiffIdx = (step: 1 | -1) => {
|
||||
// check undefined
|
||||
if (!uri) return null
|
||||
const s = commandBarState[uri.fsPath]
|
||||
if (!s) return null
|
||||
const { diffIdx, sortedDiffIds } = s
|
||||
// get next idx
|
||||
const nextDiffIdx = stepIdx(diffIdx, sortedDiffIds.length, step)
|
||||
return nextDiffIdx
|
||||
}
|
||||
const goToDiffIdx = (idx: number | null) => {
|
||||
// check undefined
|
||||
if (!uri) return
|
||||
const s = commandBarState[uri.fsPath]
|
||||
if (!s) return
|
||||
const { sortedDiffIds } = s
|
||||
// reveal
|
||||
if (idx) {
|
||||
const diffid = sortedDiffIds[idx]
|
||||
const diff = editCodeService.diffOfId[diffid]
|
||||
const range = { startLineNumber: diff.startLine, endLineNumber: diff.startLine, startColumn: 1, endColumn: 1 };
|
||||
editor.revealRange(range, ScrollType.Immediate)
|
||||
}
|
||||
|
||||
const defaultUriIdx = step === 1 ? -1 : 0 // defaults: if next, currentIdx = -1; if prev, currentIdx = 0
|
||||
let currentUriIdx = -1
|
||||
if (currentUri) {
|
||||
currentUriIdx = sortedUris.findIndex(u => u.fsPath === currentUri.fsPath)
|
||||
}
|
||||
|
||||
if (currentUriIdx === -1) { // not found
|
||||
currentUriIdx = defaultUriIdx // set to default
|
||||
}
|
||||
|
||||
const nextUriIdx = (currentUriIdx + step) % sortedUris.length
|
||||
const nextUri = sortedUris[nextUriIdx]
|
||||
|
||||
return { nextUri, nextUriIdx }
|
||||
|
||||
}, [currentUri, editCodeService._sortedUrisWithDiffs])
|
||||
|
||||
const gotoNextDiff = ({ step }: { step: 1 | -1 }) => {
|
||||
|
||||
// get the next diff
|
||||
const res = getNextDiff({ step })
|
||||
if (!res) return;
|
||||
|
||||
// scroll to the next diff
|
||||
const { nextDiff, nextDiffIdx } = res;
|
||||
const editor = editorService.getActiveCodeEditor()
|
||||
if (!editor) return;
|
||||
|
||||
const range = { startLineNumber: nextDiff.startLine, endLineNumber: nextDiff.startLine, startColumn: 1, endColumn: 1 };
|
||||
editor.revealRange(range, ScrollType.Immediate)
|
||||
|
||||
// update state
|
||||
const diffArea = editCodeService.diffAreaOfId[nextDiff.diffareaid]
|
||||
setDiffIdxOfFspath(v => ({ ...v, [diffArea._URI.fsPath]: nextDiffIdx }))
|
||||
|
||||
}
|
||||
|
||||
const gotoNextUri = ({ step }: { step: 1 | -1 }) => {
|
||||
|
||||
// get the next uri
|
||||
const res = getNextUri({ step })
|
||||
if (!res) return;
|
||||
|
||||
const { nextUri, nextUriIdx } = res;
|
||||
|
||||
// open the uri and scroll to diff
|
||||
const sortedDiffs = editCodeService._sortedDiffsOfFspath[nextUri.fsPath]
|
||||
if (!sortedDiffs) return;
|
||||
|
||||
const diffIdx = diffIdxOfFspath[nextUri.fsPath] || 0
|
||||
const diff = sortedDiffs[diffIdx]
|
||||
|
||||
const range = { startLineNumber: diff.startLine, endLineNumber: diff.startLine, startColumn: 1, endColumn: 1 };
|
||||
|
||||
commandService.executeCommand('vscode.open', nextUri).then(() => {
|
||||
|
||||
// select the text
|
||||
setTimeout(() => {
|
||||
|
||||
const editor = editorService.getActiveCodeEditor()
|
||||
if (!editor) return;
|
||||
|
||||
editor.revealRange(range, ScrollType.Immediate)
|
||||
|
||||
}, 50)
|
||||
|
||||
})
|
||||
const getNextUriIdx = (step: 1 | -1) => {
|
||||
return stepIdx(currUriIdx, sortedCommandBarURIs.length, step)
|
||||
}
|
||||
const goToURIIdx = async (idx: number | null) => {
|
||||
if (idx === null) return
|
||||
const nextURI = sortedCommandBarURIs[idx]
|
||||
editCodeService.diffAreasOfURI
|
||||
const { model } = await voidModelService.getModelSafe(nextURI)
|
||||
if (model) { editor.setModel(model) } // switch to the URI
|
||||
}
|
||||
|
||||
return <div
|
||||
className={`flex items-center gap-2 p-2 ${isFocused ? 'ring-1 ring-[var(--vscode-focusBorder)]' : ''}`}
|
||||
onFocusCapture={() => setIsFocused(true)}
|
||||
onBlurCapture={() => setIsFocused(false)}
|
||||
|
||||
|
||||
// when change URI, scroll to the proper spot
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
// check undefined
|
||||
if (!uri) return
|
||||
const s = commandBarState[uri.fsPath]
|
||||
if (!s) return
|
||||
const { diffIdx } = s
|
||||
goToDiffIdx(diffIdx)
|
||||
}, 50)
|
||||
|
||||
}, [uri])
|
||||
|
||||
|
||||
const currDiffIdx = uri ? commandBarState[uri.fsPath]?.diffIdx ?? null : null
|
||||
const sortedDiffIds = uri ? commandBarState[uri.fsPath]?.sortedDiffIds ?? null : null
|
||||
|
||||
const nextDiffIdx = getNextDiffIdx(1)
|
||||
const prevDiffIdx = getNextDiffIdx(-1)
|
||||
const nextURIIdx = getNextUriIdx(1)
|
||||
const prevURIIdx = getNextUriIdx(-1)
|
||||
|
||||
|
||||
|
||||
if (sortedCommandBarURIs.length === 0) return null // if there are absolutely no changes
|
||||
|
||||
const navPanel = <div
|
||||
className={`pointer-events-auto flex items-center gap-2 p-2 ${isFocused ? 'ring-1 ring-[var(--vscode-focusBorder)]' : ''}`}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
className={`px-2 py-1 rounded hover:bg-[var(--vscode-button-hoverBackground)] ${!getNextDiff({ step: -1 }) ? 'opacity-50' : ''}`}
|
||||
disabled={!getNextDiff({ step: -1 })}
|
||||
onClick={() => gotoNextDiff({ step: -1 })}
|
||||
className={`
|
||||
px-2 py-1 rounded hover:bg-[var(--vscode-button-hoverBackground)]
|
||||
${prevDiffIdx === null ? 'opacity-50' : ''}
|
||||
`}
|
||||
disabled={prevDiffIdx === null}
|
||||
onClick={() => { goToDiffIdx(prevDiffIdx) }}
|
||||
title="Previous diff"
|
||||
>↑</button>
|
||||
|
||||
<button
|
||||
className={`px-2 py-1 rounded hover:bg-[var(--vscode-button-hoverBackground)] ${!getNextDiff({ step: 1 }) ? 'opacity-50' : ''}`}
|
||||
disabled={!getNextDiff({ step: 1 })}
|
||||
onClick={() => gotoNextDiff({ step: 1 })}
|
||||
className={`
|
||||
px-2 py-1 rounded hover:bg-[var(--vscode-button-hoverBackground)]
|
||||
${nextDiffIdx === null ? 'opacity-50' : ''}
|
||||
`}
|
||||
disabled={nextDiffIdx === null}
|
||||
onClick={() => { goToDiffIdx(nextDiffIdx) }}
|
||||
title="Next diff"
|
||||
>↓</button>
|
||||
|
||||
<button
|
||||
className={`px-2 py-1 rounded hover:bg-[var(--vscode-button-hoverBackground)] ${!getNextUri({ step: -1 }) ? 'opacity-50' : ''}`}
|
||||
disabled={!getNextUri({ step: -1 })}
|
||||
onClick={() => gotoNextUri({ step: -1 })}
|
||||
className={`
|
||||
px-2 py-1 rounded hover:bg-[var(--vscode-button-hoverBackground)]
|
||||
${prevURIIdx === null ? 'opacity-50' : ''}
|
||||
`}
|
||||
disabled={prevURIIdx === null}
|
||||
onClick={() => goToURIIdx(prevURIIdx)}
|
||||
title="Previous file"
|
||||
>←</button>
|
||||
|
||||
<button
|
||||
className={`px-2 py-1 rounded hover:bg-[var(--vscode-button-hoverBackground)] ${!getNextUri({ step: 1 }) ? 'opacity-50' : ''}`}
|
||||
disabled={!getNextUri({ step: 1 })}
|
||||
onClick={() => gotoNextUri({ step: 1 })}
|
||||
className={`
|
||||
px-2 py-1 rounded hover:bg-[var(--vscode-button-hoverBackground)]
|
||||
${nextURIIdx === null ? 'opacity-50' : ''}
|
||||
`}
|
||||
disabled={nextURIIdx === null}
|
||||
onClick={() => goToURIIdx(nextURIIdx)}
|
||||
title="Next file"
|
||||
>→</button>
|
||||
</div>
|
||||
|
||||
<div className="text-[var(--vscode-editor-foreground)] text-xs flex gap-4">
|
||||
<div>File {(editCodeService._sortedUrisWithDiffs.findIndex(u => u.fsPath === currentUri?.fsPath) ?? 0) + 1} of {editCodeService._sortedUrisWithDiffs.length}</div>
|
||||
<div>Diff {(diffIdxOfFspath[currentUri?.fsPath ?? ''] ?? 0) + 1} of {editCodeService._sortedDiffsOfFspath[currentUri?.fsPath ?? '']?.length ?? 0}</div>
|
||||
<div>
|
||||
File {(currUriIdx ?? 0) + 1} of {sortedCommandBarURIs.length}
|
||||
</div>
|
||||
<div>
|
||||
Diff {(currDiffIdx ?? 0) + 1} of {sortedDiffIds?.length ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
const onAcceptAll = () => {
|
||||
if (!uri) return
|
||||
editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false, _addToHistory: true })
|
||||
metricsService.capture('Accept All', {})
|
||||
}
|
||||
const onRejectAll = () => {
|
||||
if (!uri) return
|
||||
editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false, _addToHistory: true })
|
||||
metricsService.capture('Reject All', {})
|
||||
}
|
||||
|
||||
const acceptRejectButtons = currUriHasChanges && <div className="flex gap-2">
|
||||
<button
|
||||
className='pointer-events-auto'
|
||||
onClick={onAcceptAll}
|
||||
style={{
|
||||
backgroundColor: acceptAllBg,
|
||||
border: acceptBorder,
|
||||
color: buttonTextColor,
|
||||
fontSize: buttonFontSize,
|
||||
padding: '4px 8px',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Accept All
|
||||
</button>
|
||||
<button
|
||||
className='pointer-events-auto'
|
||||
onClick={onRejectAll}
|
||||
style={{
|
||||
backgroundColor: rejectAllBg,
|
||||
border: rejectBorder,
|
||||
color: 'white',
|
||||
fontSize: buttonFontSize,
|
||||
padding: '4px 8px',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Reject All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
return <>
|
||||
{navPanel}
|
||||
{acceptRejectButtons}
|
||||
</>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,19 +15,15 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke
|
|||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { IRange } from '../../../../editor/common/core/range.js';
|
||||
import { ITextModel } from '../../../../editor/common/model.js';
|
||||
import { VOID_VIEW_CONTAINER_ID, VOID_VIEW_ID } from './sidebarPane.js';
|
||||
import { VOID_VIEW_ID } from './sidebarPane.js';
|
||||
import { IMetricsService } from '../common/metricsService.js';
|
||||
import { ISidebarStateService } from './sidebarStateService.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { VOID_TOGGLE_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
|
||||
import { VOID_CTRL_L_ACTION_ID } from './actionIDs.js';
|
||||
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { localize2 } from '../../../../nls.js';
|
||||
import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
import { IVoidUriStateService } from './voidUriStateService.js';
|
||||
import { StagingSelectionItem } from '../common/chatThreadServiceTypes.js';
|
||||
import { IChatThreadService } from './chatThreadService.js';
|
||||
|
||||
|
|
@ -286,43 +282,3 @@ export class TabSwitchListener extends Disposable {
|
|||
this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TabSwitchContribution extends Disposable implements IWorkbenchContribution {
|
||||
static readonly ID = 'workbench.contrib.void.tabswitch'
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
@IVoidUriStateService private readonly uriStateService: IVoidUriStateService,
|
||||
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
|
||||
// @ICommandService private readonly commandService: ICommandService,
|
||||
) {
|
||||
super()
|
||||
|
||||
// sidebarIsVisible state
|
||||
let sidebarIsVisible = this.viewsService.isViewContainerVisible(VOID_VIEW_CONTAINER_ID)
|
||||
this._register(this.viewsService.onDidChangeViewVisibility(e => {
|
||||
sidebarIsVisible = e.visible
|
||||
}))
|
||||
|
||||
const onSwitchTab = () => { // update state
|
||||
if (sidebarIsVisible) {
|
||||
const currentUri = this.codeEditorService.getActiveCodeEditor()?.getModel()?.uri
|
||||
if (!currentUri) return;
|
||||
this.uriStateService.setState({ currentUri })
|
||||
// this.commandService.executeCommand(VOID_ADD_SELECTION_TO_SIDEBAR_ACTION_ID)
|
||||
}
|
||||
}
|
||||
|
||||
// when sidebar becomes visible, add current file
|
||||
this._register(this.viewsService.onDidChangeViewVisibility(e => { sidebarIsVisible = e.visible }))
|
||||
|
||||
// run on current tab if it exists, and listen for tab switches and visibility changes
|
||||
onSwitchTab()
|
||||
this._register(this.viewsService.onDidChangeViewVisibility(() => { onSwitchTab() }))
|
||||
this._register(this.instantiationService.createInstance(TabSwitchListener, () => { onSwitchTab() }))
|
||||
}
|
||||
}
|
||||
|
||||
registerWorkbenchContribution2(TabSwitchContribution.ID, TabSwitchContribution, WorkbenchPhase.BlockRestore);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import { mountSidebar } from './react/out/sidebar-tsx/index.js';
|
|||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { Orientation } from '../../../../base/browser/ui/sash/sash.js';
|
||||
// import { IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
|
|
@ -80,8 +80,8 @@ class SidebarViewPane extends ViewPane {
|
|||
// gets set immediately
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
// mount react
|
||||
const disposables: IDisposable[] | undefined = mountSidebar(parent, accessor);
|
||||
disposables?.forEach(d => this._register(d))
|
||||
const disposeFn: (() => void) | undefined = mountSidebar(parent, accessor)?.dispose;
|
||||
this._register(toDisposable(() => disposeFn?.()))
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -331,7 +331,6 @@ export class ToolsService implements IToolsService {
|
|||
if (isFolder)
|
||||
await fileService.createFolder(uri)
|
||||
else {
|
||||
await voidModelService.initializeModel(uri)
|
||||
await fileService.createFile(uri)
|
||||
}
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import './chatThreadService.js'
|
|||
// ping
|
||||
import './metricsPollService.js'
|
||||
|
||||
// helper services
|
||||
import './helperServices/consistentItemService.js'
|
||||
|
||||
// ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ----------
|
||||
|
||||
|
|
|
|||
419
src/vs/workbench/contrib/void/browser/voidCommandBarService.ts
Normal file
419
src/vs/workbench/contrib/void/browser/voidCommandBarService.ts
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import { Widget } from '../../../../base/browser/ui/widget.js';
|
||||
import { IOverlayWidget, ICodeEditor, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { mountVoidCommandBar } from './react/out/void-command-bar-tsx/index.js'
|
||||
import { deepClone } from '../../../../base/common/objects.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { IEditCodeService } from './editCodeServiceInterface.js';
|
||||
import { ITextModel } from '../../../../editor/common/model.js';
|
||||
import { IModelService } from '../../../../editor/common/services/model.js';
|
||||
|
||||
|
||||
|
||||
export interface IVoidCommandBarService {
|
||||
readonly _serviceBrand: undefined;
|
||||
stateOfURI: { [uri: string]: CommandBarStateType };
|
||||
sortedURIs: URI[];
|
||||
activeURI: URI | null;
|
||||
|
||||
onDidChangeState: Event<{ uri: URI }>;
|
||||
onDidChangeActiveURI: Event<{ uri: URI | null }>;
|
||||
|
||||
getStreamState: (uri: URI) => 'streaming' | 'idle-has-changes' | 'idle-no-changes';
|
||||
setDiffIdx(uri: URI, newIdx: number | null): void
|
||||
}
|
||||
|
||||
|
||||
export const IVoidCommandBarService = createDecorator<IVoidCommandBarService>('VoidCommandBarService');
|
||||
|
||||
|
||||
export type CommandBarStateType = undefined | {
|
||||
sortedDiffZoneIds: string[]; // sorted by line number
|
||||
sortedDiffIds: string[]; // sorted by line number (computed)
|
||||
isStreaming: boolean; // is any diffZone streaming in this URI
|
||||
|
||||
diffIdx: number | null; // must refresh whenever sortedDiffIds does so it's valid
|
||||
}
|
||||
|
||||
|
||||
|
||||
const defaultState: NonNullable<CommandBarStateType> = {
|
||||
sortedDiffZoneIds: [],
|
||||
sortedDiffIds: [],
|
||||
isStreaming: false,
|
||||
diffIdx: null,
|
||||
}
|
||||
|
||||
|
||||
export class VoidCommandBarService extends Disposable implements IVoidCommandBarService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
static readonly ID: 'void.VoidCommandBarService'
|
||||
|
||||
// depends on uri -> diffZone -> {streaming, diffs}
|
||||
public stateOfURI: { [uri: string]: CommandBarStateType } = {}
|
||||
public sortedURIs: URI[] = [] // keys of state (depends on diffZones in the uri)
|
||||
private readonly _hooks = new Set<URI>() // uriFsPaths
|
||||
|
||||
// Emits when a URI's stream state changes between idle, streaming, and acceptRejectAll
|
||||
private readonly _onDidChangeState = new Emitter<{ uri: URI }>();
|
||||
readonly onDidChangeState = this._onDidChangeState.event;
|
||||
|
||||
|
||||
// active URI
|
||||
activeURI: URI | null = null;
|
||||
private readonly _onDidChangeActiveURI = new Emitter<{ uri: URI | null }>();
|
||||
readonly onDidChangeActiveURI = this._onDidChangeActiveURI.event;
|
||||
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IEditCodeService private readonly _editCodeService: IEditCodeService,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
||||
const registeredModelURIs = new Set<string>()
|
||||
const initializeModel = async (model: ITextModel) => {
|
||||
// do not add listeners to the same model twice - important, or will see duplicates
|
||||
if (registeredModelURIs.has(model.uri.fsPath)) return
|
||||
registeredModelURIs.add(model.uri.fsPath)
|
||||
this._hooks.add(model.uri)
|
||||
}
|
||||
// initialize all existing models + initialize when a new model mounts
|
||||
this._modelService.getModels().forEach(model => { initializeModel(model) })
|
||||
this._register(this._modelService.onModelAdded(model => { initializeModel(model) }));
|
||||
|
||||
|
||||
|
||||
|
||||
const updateActiveURI = () => {
|
||||
const currentUri = this._codeEditorService.getActiveCodeEditor()?.getModel()?.uri ?? null
|
||||
this.activeURI = currentUri
|
||||
if (!currentUri) return;
|
||||
this._onDidChangeActiveURI.fire({ uri: currentUri })
|
||||
}
|
||||
|
||||
|
||||
// for every new editor, add the floating widget and update active URI
|
||||
const disposablesOfEditorId: { [editorId: string]: IDisposable[] } = {};
|
||||
const onCodeEditorAdd = (editor: ICodeEditor) => {
|
||||
const id = editor.getId();
|
||||
disposablesOfEditorId[id] = [];
|
||||
const d1 = this._instantiationService.createInstance(AcceptRejectAllFloatingWidget, { editor });
|
||||
disposablesOfEditorId[id].push(d1);
|
||||
const d2 = editor.onDidChangeModel((e) => { if (e?.newModelUrl?.scheme === 'file') updateActiveURI() })
|
||||
disposablesOfEditorId[id].push(d2);
|
||||
}
|
||||
const onCodeEditorRemove = (editor: ICodeEditor) => {
|
||||
const id = editor.getId();
|
||||
if (disposablesOfEditorId[id]) {
|
||||
disposablesOfEditorId[id].forEach(d => d.dispose());
|
||||
delete disposablesOfEditorId[id];
|
||||
}
|
||||
}
|
||||
this._register(this._codeEditorService.onCodeEditorAdd((editor) => { onCodeEditorAdd(editor) }))
|
||||
this._register(this._codeEditorService.onCodeEditorRemove((editor) => { onCodeEditorRemove(editor) }))
|
||||
this._codeEditorService.listCodeEditors().forEach(editor => { onCodeEditorAdd(editor) })
|
||||
|
||||
// state updaters
|
||||
this._register(this._editCodeService.onDidAddOrDeleteDiffZones(e => {
|
||||
for (const uri of this._hooks) {
|
||||
if (e.uri.fsPath !== uri.fsPath) return
|
||||
// --- sortedURIs: delete if empty, add if not in state yet
|
||||
const diffZones = this._getDiffZonesOnURI(uri)
|
||||
if (diffZones.length === 0) {
|
||||
this._deleteURIEntryFromState(uri)
|
||||
this._onDidChangeState.fire({ uri })
|
||||
continue // deleted, so done
|
||||
}
|
||||
if (!this.sortedURIs.find(uri2 => uri2.fsPath === uri.fsPath)) {
|
||||
this._addURIEntryToState(uri)
|
||||
}
|
||||
|
||||
const currState = this.stateOfURI[uri.fsPath]
|
||||
if (!currState) continue // should never happen
|
||||
// update state of the diffZones on this URI
|
||||
const oldDiffZones = currState.sortedDiffZoneIds
|
||||
const currentDiffZones = this._editCodeService.diffAreasOfURI[uri.fsPath] || [] // a Set
|
||||
const { addedDiffZones, deletedDiffZones } = this._getDiffZoneChanges(oldDiffZones, currentDiffZones || [])
|
||||
|
||||
const diffZonesWithoutDeleted = oldDiffZones.filter(olddiffareaid => !deletedDiffZones.has(olddiffareaid))
|
||||
|
||||
// --- new state:
|
||||
const newSortedDiffZoneIds = [
|
||||
...diffZonesWithoutDeleted,
|
||||
...addedDiffZones,
|
||||
]
|
||||
const newSortedDiffIds = this._computeSortedDiffs(newSortedDiffZoneIds)
|
||||
const isStreaming = this._isAnyDiffZoneStreaming(currentDiffZones)
|
||||
|
||||
this._setState(uri, {
|
||||
sortedDiffZoneIds: newSortedDiffZoneIds,
|
||||
sortedDiffIds: newSortedDiffIds,
|
||||
isStreaming: isStreaming
|
||||
})
|
||||
this._onDidChangeState.fire({ uri })
|
||||
}
|
||||
|
||||
}))
|
||||
this._register(this._editCodeService.onDidChangeDiffsInDiffZone(e => {
|
||||
for (const uri of this._hooks) {
|
||||
if (e.uri.fsPath !== uri.fsPath) continue
|
||||
// --- sortedURIs: no change
|
||||
// --- state:
|
||||
// sortedDiffIds gets a change to it, so gets recomputed
|
||||
const currState = this.stateOfURI[uri.fsPath]
|
||||
if (!currState) continue // should never happen
|
||||
const { sortedDiffZoneIds } = currState
|
||||
const newSortedDiffIds = this._computeSortedDiffs(sortedDiffZoneIds)
|
||||
this._setState(uri, {
|
||||
sortedDiffIds: newSortedDiffIds,
|
||||
// sortedDiffZoneIds, // no change
|
||||
// isStreaming, // no change
|
||||
})
|
||||
this._onDidChangeState.fire({ uri })
|
||||
}
|
||||
}))
|
||||
this._register(this._editCodeService.onDidChangeStreamingInDiffZone(e => {
|
||||
for (const uri of this._hooks) {
|
||||
if (e.uri.fsPath !== uri.fsPath) continue
|
||||
// --- sortedURIs: no change
|
||||
// --- state:
|
||||
const currState = this.stateOfURI[uri.fsPath]
|
||||
if (!currState) continue // should never happen
|
||||
const { sortedDiffZoneIds } = currState
|
||||
this._setState(uri, {
|
||||
isStreaming: this._isAnyDiffZoneStreaming(sortedDiffZoneIds),
|
||||
// sortedDiffIds, // no change
|
||||
// sortedDiffZoneIds, // no change
|
||||
})
|
||||
this._onDidChangeState.fire({ uri })
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
|
||||
setDiffIdx(uri: URI, newIdx: number | null): void {
|
||||
this._setState(uri, { diffIdx: newIdx });
|
||||
this._onDidChangeState.fire({ uri });
|
||||
}
|
||||
|
||||
|
||||
getStreamState(uri: URI) {
|
||||
const { isStreaming, sortedDiffZoneIds } = this.stateOfURI[uri.fsPath] ?? {}
|
||||
if (isStreaming) {
|
||||
return 'streaming'
|
||||
}
|
||||
if ((sortedDiffZoneIds?.length ?? 0) > 0) {
|
||||
return 'idle-has-changes'
|
||||
}
|
||||
return 'idle-no-changes'
|
||||
}
|
||||
|
||||
|
||||
_computeSortedDiffs(diffareaids: string[]) {
|
||||
const sortedDiffIds = [];
|
||||
for (const diffareaid of diffareaids) {
|
||||
const diffZone = this._editCodeService.diffAreaOfId[diffareaid];
|
||||
if (!diffZone || diffZone.type !== 'DiffZone') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add all diff ids from this diffzone
|
||||
const diffIds = Object.keys(diffZone._diffOfId);
|
||||
sortedDiffIds.push(...diffIds);
|
||||
}
|
||||
|
||||
return sortedDiffIds;
|
||||
}
|
||||
|
||||
_getDiffZoneChanges(currentDiffZones: Iterable<string>, oldDiffZones: Iterable<string>) {
|
||||
// Find the added or deleted diffZones by comparing diffareaids
|
||||
const addedDiffZoneIds = new Set<string>();
|
||||
const deletedDiffZoneIds = new Set<string>();
|
||||
|
||||
// Convert the current diffZones to a set of ids for easy lookup
|
||||
const currentDiffZoneIdSet = new Set(currentDiffZones);
|
||||
|
||||
// Find deleted diffZones (in old but not in current)
|
||||
for (const oldDiffZoneId of oldDiffZones) {
|
||||
if (!currentDiffZoneIdSet.has(oldDiffZoneId)) {
|
||||
const diffZone = this._editCodeService.diffAreaOfId[oldDiffZoneId];
|
||||
if (diffZone && diffZone.type === 'DiffZone') {
|
||||
deletedDiffZoneIds.add(oldDiffZoneId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find added diffZones (in current but not in old)
|
||||
const oldDiffZoneIdSet = new Set(oldDiffZones);
|
||||
for (const currentDiffZoneId of currentDiffZones) {
|
||||
if (!oldDiffZoneIdSet.has(currentDiffZoneId)) {
|
||||
const diffZone = this._editCodeService.diffAreaOfId[currentDiffZoneId];
|
||||
if (diffZone && diffZone.type === 'DiffZone') {
|
||||
addedDiffZoneIds.add(currentDiffZoneId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { addedDiffZones: addedDiffZoneIds, deletedDiffZones: deletedDiffZoneIds }
|
||||
}
|
||||
|
||||
_isAnyDiffZoneStreaming(diffareaids: Iterable<string>) {
|
||||
for (const diffareaid of diffareaids) {
|
||||
const diffZone = this._editCodeService.diffAreaOfId[diffareaid];
|
||||
if (!diffZone || diffZone.type !== 'DiffZone') {
|
||||
continue;
|
||||
}
|
||||
if (diffZone._streamState.isStreaming) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
_setState(uri: URI, opts: Partial<CommandBarStateType>) {
|
||||
const newState = {
|
||||
...this.stateOfURI[uri.fsPath] ?? deepClone(defaultState),
|
||||
...opts
|
||||
}
|
||||
|
||||
// make sure diffIdx is always correct
|
||||
if (newState.diffIdx && newState.diffIdx > newState.sortedDiffIds.length) {
|
||||
newState.diffIdx = newState.sortedDiffIds.length
|
||||
if (newState.diffIdx < 0) newState.diffIdx = null
|
||||
}
|
||||
|
||||
this.stateOfURI = {
|
||||
...this.stateOfURI,
|
||||
[uri.fsPath]: newState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_addURIEntryToState(uri: URI) {
|
||||
// add to sortedURIs
|
||||
this.sortedURIs = [
|
||||
...this.sortedURIs,
|
||||
uri
|
||||
]
|
||||
|
||||
// add to state
|
||||
this.stateOfURI[uri.fsPath] = deepClone(defaultState)
|
||||
}
|
||||
|
||||
_deleteURIEntryFromState(uri: URI) {
|
||||
// delete this from sortedURIs
|
||||
const i = this.sortedURIs.findIndex(uri2 => uri2.fsPath === uri.fsPath)
|
||||
if (i === -1) return
|
||||
this.sortedURIs = [
|
||||
...this.sortedURIs.slice(0, i),
|
||||
...this.sortedURIs.slice(i, Infinity),
|
||||
]
|
||||
// delete from state
|
||||
delete this.stateOfURI[uri.fsPath]
|
||||
}
|
||||
|
||||
|
||||
|
||||
private _getDiffZonesOnURI(uri: URI) {
|
||||
const diffZones = [...this._editCodeService.diffAreasOfURI[uri.fsPath]?.values() ?? []]
|
||||
.map(diffareaid => this._editCodeService.diffAreaOfId[diffareaid])
|
||||
.filter(diffArea => !!diffArea && diffArea.type === 'DiffZone')
|
||||
return diffZones
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IVoidCommandBarService, VoidCommandBarService, InstantiationType.Delayed); // delayed is needed here :(
|
||||
|
||||
// registerWorkbenchContribution2(VoidCommandBarService.ID, VoidCommandBarService, WorkbenchPhase.BlockRestore);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AcceptRejectAllFloatingWidget extends Widget implements IOverlayWidget {
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly editor: ICodeEditor;
|
||||
private readonly ID: string;
|
||||
|
||||
constructor({ editor }: { editor: ICodeEditor, },
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.ID = editor.getId() + '-voidfloatingwidget';
|
||||
this.editor = editor;
|
||||
|
||||
// Create container div
|
||||
const { root } = dom.h('div@root');
|
||||
|
||||
// Style the container
|
||||
root.style.zIndex = '2';
|
||||
root.style.padding = '4px';
|
||||
root.style.alignItems = 'center';
|
||||
root.style.pointerEvents = 'none';
|
||||
|
||||
// Mount command bar using mountVoidCommandBar
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
|
||||
const uri = editor.getModel()?.uri || null
|
||||
const res = mountVoidCommandBar(root, accessor, { uri, editor })
|
||||
if (!res) return
|
||||
|
||||
const dispose = res.dispose
|
||||
const rerender: (o: { uri: URI | null }) => void = res.rerender
|
||||
|
||||
this._register(toDisposable(() => dispose?.()))
|
||||
|
||||
this._register(editor.onDidChangeModel((model) => {
|
||||
const uri = model.newModelUrl
|
||||
rerender({ uri })
|
||||
}))
|
||||
|
||||
});
|
||||
this._domNode = root;
|
||||
|
||||
// Mount the widget
|
||||
editor.addOverlayWidget(this);
|
||||
}
|
||||
|
||||
|
||||
public getId(): string {
|
||||
return this.ID;
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public getPosition() {
|
||||
return {
|
||||
preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER,
|
||||
}
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
this.editor.removeOverlayWidget(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke
|
|||
|
||||
import { mountVoidSettings } from './react/out/void-settings-tsx/index.js'
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
|
||||
|
||||
// refer to preferences.contribution.ts keybindings editor
|
||||
|
|
@ -90,12 +90,12 @@ class VoidSettingsPane extends EditorPane {
|
|||
|
||||
// Mount React into the scrollable content
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
const disposables: IDisposable[] | undefined = mountVoidSettings(settingsElt, accessor);
|
||||
const disposeFn = mountVoidSettings(settingsElt, accessor)?.dispose;
|
||||
this._register(toDisposable(() => disposeFn?.()))
|
||||
|
||||
// setTimeout(() => { // this is a complete hack and I don't really understand how scrollbar works here
|
||||
// this._scrollbar?.scanDomNode();
|
||||
// }, 1000)
|
||||
disposables?.forEach(d => this._register(d));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
|
||||
|
||||
|
||||
// service that manages state
|
||||
export type VoidUriState = {
|
||||
currentUri?: URI
|
||||
}
|
||||
|
||||
export interface IVoidUriStateService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly state: VoidUriState; // readonly to the user
|
||||
setState(newState: Partial<VoidUriState>): void;
|
||||
onDidChangeState: Event<void>;
|
||||
}
|
||||
|
||||
export const IVoidUriStateService = createDecorator<IVoidUriStateService>('voidUriStateService');
|
||||
class VoidUriStateService extends Disposable implements IVoidUriStateService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
static readonly ID = 'voidUriStateService';
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
|
||||
|
||||
|
||||
// state
|
||||
state: VoidUriState
|
||||
|
||||
constructor(
|
||||
) {
|
||||
super()
|
||||
|
||||
// initial state
|
||||
this.state = { currentUri: undefined }
|
||||
}
|
||||
|
||||
setState(newState: Partial<VoidUriState>) {
|
||||
|
||||
this.state = { ...this.state, ...newState }
|
||||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IVoidUriStateService, VoidUriStateService, InstantiationType.Eager);
|
||||
8
src/vs/workbench/contrib/void/common/helpers/colors.ts
Normal file
8
src/vs/workbench/contrib/void/common/helpers/colors.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export const acceptBg = '#1a7431'
|
||||
export const acceptAllBg = '#1e8538'
|
||||
export const acceptBorder = '1px solid #145626'
|
||||
export const rejectBg = '#b42331'
|
||||
export const rejectAllBg = '#cf2838'
|
||||
export const rejectBorder = '1px solid #8e1c27'
|
||||
export const buttonFontSize = '11px'
|
||||
export const buttonTextColor = 'white'
|
||||
Loading…
Reference in a new issue