diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index b14c085a..9c37821f 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -128,13 +128,16 @@ export type StartApplyingOpts = { from: 'QuickEdit'; type: 'rewrite'; diffareaid: number; // id of the CtrlK area (contains text selection) + chatCodeBoxId: string | null; } | { from: 'ClickApply'; type: 'searchReplace' | 'rewrite'; applyStr: string; + chatCodeBoxId: string | null; } + export type AddCtrlKOpts = { startLine: number, endLine: number, @@ -197,12 +200,11 @@ type DiffZone = { isStreaming: true; streamRequestIdRef: { current: string | null }; line: number; - applyBoxId?: string; + codeBoxId: string | null; } | { isStreaming: false; streamRequestIdRef?: undefined; line?: undefined; - applyBoxId?: undefined; }; editorId?: undefined; linkedStreamingDiffZone?: undefined; @@ -260,10 +262,10 @@ export interface IEditCodeService { interruptCtrlKStreaming(opts: { diffareaid: number }): void; onDidChangeCtrlKZoneStreaming: Event<{ uri: URI; diffareaid: number }>; - // // DiffZone streaming state - // isApplyBoxIdStreaming(opts: { applyBoxId: string }): boolean; - // interruptApplyBoxId(opts: { applyBoxId: string }): void; - // onDidChangeApplyBoxIdStreaming: Event<{ applyBoxId: string }>; + // // DiffZone codeBoxId streaming state + isCodeBoxIdStreaming(opts: { codeBoxId: string }): boolean; + interruptCodeBoxId(opts: { codeBoxId: string }): void; + onDidChangeCodeBoxIdStreaming: Event<{ codeBoxId: string }>; // testDiffs(): void; } @@ -284,12 +286,14 @@ class EditCodeService extends Disposable implements IEditCodeService { // only applies to diffZones // streamingDiffZones: Set = new Set() private readonly _onDidChangeDiffZoneStreaming = new Emitter<{ uri: URI; diffareaid: number }>(); - private readonly _onDidChangeCtrlKZoneStreaming = new Emitter<{ uri: URI; diffareaid: number }>(); - private readonly _onDidAddOrDeleteDiffZone = new Emitter<{ uri: URI }>(); + private readonly _onDidAddOrDeleteDiffZones = new Emitter<{ uri: URI }>(); - onDidChangeDiffZoneStreaming = this._onDidChangeDiffZoneStreaming.event + private readonly _onDidChangeCtrlKZoneStreaming = new Emitter<{ uri: URI; diffareaid: number }>(); onDidChangeCtrlKZoneStreaming = this._onDidChangeCtrlKZoneStreaming.event + private readonly _onDidChangeCodeBoxIdStreaming = new Emitter<{ uri: URI; diffareaid: number; codeBoxId: string }>(); + onDidChangeCodeBoxIdStreaming = this._onDidChangeCodeBoxIdStreaming.event + constructor( // @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right @ICodeEditorService private readonly _editorService: ICodeEditorService, @@ -342,7 +346,17 @@ class EditCodeService extends Disposable implements IEditCodeService { } this._register(this._onDidChangeDiffZoneStreaming.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) updateAcceptRejectAllUI() })) - this._register(this._onDidAddOrDeleteDiffZone.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) updateAcceptRejectAllUI() })) + this._register(this._onDidAddOrDeleteDiffZones.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) updateAcceptRejectAllUI() })) + + // codeBoxId + this._register(this._onDidChangeDiffZoneStreaming.event(({ diffareaid }) => { + const diffZone = this.diffAreaOfId[diffareaid] + if (diffZone?.type !== 'DiffZone') return + if (!diffZone._streamState.isStreaming) return + const { codeBoxId } = diffZone._streamState + if (codeBoxId === null) return + this._onDidChangeCodeBoxIdStreaming.fire({ uri: model.uri, codeBoxId, diffareaid }) + })) } // initialize all existing models + initialize when a new model mounts @@ -864,7 +878,7 @@ class EditCodeService extends Disposable implements IEditCodeService { } this.diffAreasOfURI[uri.fsPath].add(diffareaid) } - this._onDidAddOrDeleteDiffZone.fire({ uri }) + this._onDidAddOrDeleteDiffZones.fire({ uri }) // restore file content const numLines = this._getNumLines(uri) @@ -934,7 +948,7 @@ class EditCodeService extends Disposable implements IEditCodeService { this._clearAllDiffAreaEffects(diffZone) delete this.diffAreaOfId[diffZone.diffareaid] this.diffAreasOfURI[diffZone._URI.fsPath].delete(diffZone.diffareaid.toString()) - this._onDidAddOrDeleteDiffZone.fire({ uri: diffZone._URI }) + this._onDidAddOrDeleteDiffZones.fire({ uri: diffZone._URI }) } private _deleteTrackingZone(trackingZone: TrackingZone) { @@ -1252,7 +1266,7 @@ class EditCodeService extends Disposable implements IEditCodeService { private _initializeWriteoverStream(opts: StartApplyingOpts): DiffZone | undefined { - const { from } = opts + const { from, chatCodeBoxId } = opts let startLine: number let endLine: number @@ -1312,13 +1326,14 @@ class EditCodeService extends Disposable implements IEditCodeService { isStreaming: true, streamRequestIdRef, line: startLine, + codeBoxId: chatCodeBoxId, }, _diffOfId: {}, // added later _removeStylesFns: new Set(), } const diffZone = this._addDiffArea(adding) this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) - this._onDidAddOrDeleteDiffZone.fire({ uri }) + this._onDidAddOrDeleteDiffZones.fire({ uri }) if (from === 'QuickEdit') { const { diffareaid } = opts @@ -1436,7 +1451,8 @@ class EditCodeService extends Disposable implements IEditCodeService { - private _initializeSearchAndReplaceStream({ applyStr }: { applyStr: string }) { + private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }) { + const { applyStr, chatCodeBoxId } = opts const uri_ = this._getActiveEditorURI() if (!uri_) return @@ -1485,13 +1501,14 @@ class EditCodeService extends Disposable implements IEditCodeService { isStreaming: true, streamRequestIdRef, line: startLine, + codeBoxId: chatCodeBoxId, }, _diffOfId: {}, // added later _removeStylesFns: new Set(), } const diffZone = this._addDiffArea(adding) this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) - this._onDidAddOrDeleteDiffZone.fire({ uri }) + this._onDidAddOrDeleteDiffZones.fire({ uri }) const revertAndContinueHistory = () => { @@ -1729,12 +1746,16 @@ class EditCodeService extends Disposable implements IEditCodeService { - isDiffZoneStreaming({ diffareaid }: { diffareaid: number }) { + _interruptDiffZoneStreaming({ diffareaid }: { diffareaid: number }) { const diffZone = this.diffAreaOfId[diffareaid] - if (diffZone?.type !== 'DiffZone') return false - return diffZone._streamState.isStreaming + if (diffZone?.type !== 'DiffZone') return + if (!diffZone._streamState.isStreaming) return + + this._stopIfStreaming(diffZone) + this._undoHistory(diffZone._URI) } + isCtrlKZoneStreaming({ diffareaid }: { diffareaid: number }) { const ctrlKZone = this.diffAreaOfId[diffareaid] if (!ctrlKZone) return false @@ -1743,15 +1764,6 @@ class EditCodeService extends Disposable implements IEditCodeService { } - interruptDiffZoneStreaming({ diffareaid }: { diffareaid: number }) { - const diffZone = this.diffAreaOfId[diffareaid] - if (diffZone?.type !== 'DiffZone') return - if (!diffZone._streamState.isStreaming) return - - this._stopIfStreaming(diffZone) - this._undoHistory(diffZone._URI) - } - // diffareaid of the ctrlKZone (even though the stream state is dictated by the linked diffZone) interruptCtrlKStreaming({ diffareaid }: { diffareaid: number }) { const ctrlKZone = this.diffAreaOfId[diffareaid] @@ -1762,11 +1774,37 @@ class EditCodeService extends Disposable implements IEditCodeService { if (!linkedStreamingDiffZone) return if (linkedStreamingDiffZone.type !== 'DiffZone') return - this.interruptDiffZoneStreaming({ diffareaid: linkedStreamingDiffZone.diffareaid }) + this._interruptDiffZoneStreaming({ diffareaid: linkedStreamingDiffZone.diffareaid }) } + isCodeBoxIdStreaming({ codeBoxId }: { codeBoxId: string }) { + // brute force is OK for now + for (const diffareaid in this.diffAreaOfId) { + const diffArea = this.diffAreaOfId[diffareaid] + if (!diffArea) continue + if (diffArea.type !== 'DiffZone') continue + if (!diffArea._streamState.isStreaming) continue + if (diffArea._streamState.codeBoxId === codeBoxId) return true + } + return false + } + + interruptCodeBoxId({ codeBoxId }: { codeBoxId: string }) { + // brute force for now is OK + for (const diffareaid in this.diffAreaOfId) { + const diffArea = this.diffAreaOfId[diffareaid] + if (!diffArea) continue + if (diffArea.type !== 'DiffZone') continue + if (!diffArea._streamState.isStreaming) continue + if (diffArea._streamState.codeBoxId === codeBoxId) { + this._interruptDiffZoneStreaming({ diffareaid: diffArea.diffareaid }) + return + } + } + } + // public removeDiffZone(diffZone: DiffZone, behavior: 'reject' | 'accept') { // const uri = diffZone._URI diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 6224110b..ecd5d7d9 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react' -import { useAccessor, useIsDiffZoneStreaming } from '../util/services.js' +import { useAccessor, useCodeBoxIdStreamingState, useSettingsState } from '../util/services.js' import { useRefState } from '../util/helpers.js' import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js' @@ -44,54 +44,35 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => { } +const useStreamStateRef = ({ codeBoxId }: { codeBoxId: string | null }) => { + const accessor = useAccessor() + const editCodeService = accessor.get('IEditCodeService') + const [isStreamingRef, setIsStreamingRef] = useRefState(editCodeService.isCodeBoxIdStreaming({ codeBoxId })) + useCodeBoxIdStreamingState(useCallback((codeBoxId2, isStreaming) => { + if (codeBoxId !== codeBoxId2) return + setIsStreamingRef(isStreaming) + }, [codeBoxId, setIsStreamingRef])) + return [isStreamingRef, setIsStreamingRef] as const +} -const ApplyButton = ({ codeStr, codeBoxId }: { codeStr: string, codeBoxId: string }) => { + + +const StopButton = ({ codeBoxId }: { codeBoxId: string }) => { const accessor = useAccessor() const editCodeService = accessor.get('IEditCodeService') const metricsService = accessor.get('IMetricsService') + const settingsState = useSettingsState() - // const isStreaming = useIsDiffZoneStreaming(isDiffAreaStreaming) + const [isStreamingRef, _] = useStreamStateRef({ codeBoxId }) - // const onSubmit = useCallback(() => { - - // const uri = editCodeService.startApplying({ - // from: 'ClickApply', - // type: 'searchReplace', - // applyStr: codeStr, - // }) - - // metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only - - - - // if (isStreaming) return - - // setCurrentlyStreamingDiffZone(id ?? null) - // }, [isStreaming, editCodeService]) - - // const onInterrupt = useCallback(() => { - // if (currStreamingDiffZoneRef.current === null) return - // editCodeService.interruptStreaming(currStreamingDiffZoneRef.current) - // setCurrentlyStreamingDiffZone(null) - // textAreaFnsRef.current?.enable() - // }, [isStreaming, editCodeService]) - - - - - - - - const isSingleLine = !codeStr.includes('\n') - return @@ -103,8 +84,57 @@ const ApplyButton = ({ codeStr, codeBoxId }: { codeStr: string, codeBoxId: strin export const ApplyBlockHoverButtons = ({ codeStr, codeBoxId }: { codeStr: string, codeBoxId: string | null }) => { + + + + const accessor = useAccessor() + + const editCodeService = accessor.get('IEditCodeService') + const metricsService = accessor.get('IMetricsService') + + const settingsState = useSettingsState() + + const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) + + const [isStreamingRef, _] = useStreamStateRef({ codeBoxId }) + + const onSubmit = useCallback(() => { + if (isDisabled) return + if (isStreamingRef.current) return + editCodeService.startApplying({ + from: 'ClickApply', + type: 'searchReplace', + applyStr: codeStr, + chatCodeBoxId: codeBoxId, + }) + metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only + }, [isStreamingRef, editCodeService, codeBoxId, codeStr, metricsService]) + + + const onInterrupt = useCallback(() => { + if (isStreamingRef.current) return + if (codeBoxId === null) return + editCodeService.interruptCodeBoxId({ codeBoxId, }) + metricsService.capture('Stop Apply', {}) + }, [isStreamingRef, editCodeService, codeBoxId, metricsService]) + + + + const isSingleLine = !codeStr.includes('\n') + + const applyButton = + + + return <> - - {codeBoxId !== null && } + {!isStreamingRef.current && } + {!isStreamingRef.current && codeBoxId !== null && } + {!isStreamingRef.current && } } diff --git a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx index fe70caa3..9e8f674f 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx @@ -65,6 +65,7 @@ export const QuickEditChat = ({ from: 'QuickEdit', type: 'rewrite', diffareaid, + chatCodeBoxId: null, }) }, [isStreamingRef, isDisabled, editCodeService, diffareaid]) diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index 9ea92f0c..6e1f5102 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -76,9 +76,7 @@ let colorThemeState: ColorScheme const colorThemeStateListeners: Set<(s: ColorScheme) => void> = new Set() const ctrlKZoneStreamingStateListeners: Set<(diffareaid: number, s: boolean) => void> = new Set() - -let diffZoneStreamingState: Record -const diffZoneStreamingStateListeners: Set<(diffareaid: number, state: boolean) => void> = new Set() +const codeBoxIdStreamingStateListeners: Set<(codeBoxId: string, s: boolean) => void> = new Set() @@ -184,6 +182,12 @@ export const _registerServices = (accessor: ServicesAccessor) => { ctrlKZoneStreamingStateListeners.forEach(l => l(diffareaid, isStreaming)) }) ) + disposables.push( + editCodeService.onDidChangeCodeBoxIdStreaming(({ codeBoxId }) => { + const isStreaming = editCodeService.isCodeBoxIdStreaming({ codeBoxId }) + codeBoxIdStreamingStateListeners.forEach(l => l(codeBoxId, isStreaming)) + }) + ) @@ -351,7 +355,6 @@ export const useRefreshModelListener = (listener: (providerName: RefreshableProv }, [listener, refreshModelProviderListeners]) } - export const useCtrlKZoneStreamingState = (listener: (diffareaid: number, s: boolean) => void) => { useEffect(() => { ctrlKZoneStreamingStateListeners.add(listener) @@ -359,16 +362,18 @@ export const useCtrlKZoneStreamingState = (listener: (diffareaid: number, s: boo }, [listener, ctrlKZoneStreamingStateListeners]) } - -export const useIsDiffZoneStreaming = (diffareaid: number) => { - return { current: true } - +export const useCodeBoxIdStreamingState = (listener: (codeBoxId: string, s: boolean) => void) => { + useEffect(() => { + codeBoxIdStreamingStateListeners.add(listener) + return () => { codeBoxIdStreamingStateListeners.delete(listener) } + }, [listener, codeBoxIdStreamingStateListeners]) } + export const useIsDark = () => { const [s, ss] = useState(colorThemeState) useEffect(() => {