diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 1526406d..c325fc8e 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -162,9 +162,12 @@ export interface IChatThreadService { isFocusingMessage(): boolean; setFocusedMessageIdx(messageIdx: number | undefined): void; - // _useFocusedStagingState(messageIdx?: number | undefined): readonly [StagingInfo, (stagingInfo: StagingInfo) => void]; - _useCurrentThreadState(): readonly [ThreadType['state'], (newState: Partial) => void]; - _useCurrentMessageState(messageIdx: number): readonly [UserMessageState, (newState: Partial) => void]; + // exposed getters/setters + getCurrentMessageState: (messageIdx: number) => UserMessageState + setCurrentMessageState: (messageIdx: number, newState: Partial) => void + getCurrentThreadStagingSelections: () => StagingSelectionItem[] + setCurrentThreadStagingSelections: (stagingSelections: StagingSelectionItem[]) => void + // call to edit a message editUserMessageAndStreamResponse({ userMessage, chatMode, messageIdx }: { userMessage: string, chatMode: ChatMode, messageIdx: number }): Promise; @@ -622,33 +625,27 @@ class ChatThreadService extends Disposable implements IChatThreadService { } + getCurrentThreadStagingSelections = () => { + return this.getCurrentThread().state.stagingSelections + } + + setCurrentThreadStagingSelections = (stagingSelections: StagingSelectionItem[]) => { + this._setCurrentThreadState({ stagingSelections }) + } + // gets `staging` and `setStaging` of the currently focused element, given the index of the currently selected message (or undefined if no message is selected) - _useCurrentMessageState(messageIdx: number) { - - const thread = this.getCurrentThread() - const messages = thread.messages - const currMessage = messages[messageIdx] - - if (currMessage.role !== 'user') { - return [defaultMessageState, (s: any) => { }] as const - } - - const state = currMessage.state - const setState = (newState: Partial) => this._setCurrentMessageState(newState, messageIdx) - - return [state, setState] as const - + getCurrentMessageState(messageIdx: number): UserMessageState { + const currMessage = this.getCurrentThread()?.messages?.[messageIdx] + if (!currMessage || currMessage.role !== 'user') return defaultMessageState + return currMessage.state + } + setCurrentMessageState(messageIdx: number, newState: Partial) { + const currMessage = this.getCurrentThread()?.messages?.[messageIdx] + if (!currMessage || currMessage.role !== 'user') return + this._setCurrentMessageState(newState, messageIdx) } - _useCurrentThreadState() { - const thread = this.getCurrentThread() - - const state = thread.state - const setState = this._setCurrentThreadState.bind(this) - - return [state, setState] as const - } } diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 228d7a2f..657584ed 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -248,10 +248,17 @@ type StreamLocationMutable = { line: number, col: number, addedSplitYet: boolean export interface IEditCodeService { readonly _serviceBrand: undefined; - startApplying(opts: StartApplyingOpts): number | void; - interruptStreaming(diffareaid: number): void; + startApplying(opts: StartApplyingOpts): URI | null; + addCtrlKZone(opts: AddCtrlKOpts): number | undefined; removeCtrlKZone(opts: { diffareaid: number }): void; + + isDiffZoneStreaming(opts: { diffareaid: number }): boolean; + isCtrlKZoneStreaming(opts: { diffareaid: number }): boolean; + + interruptDiffZoneStreaming(opts: { diffareaid: number }): void; + interruptCtrlKStreaming(opts: { diffareaid: number }): void; + // testDiffs(): void; } @@ -274,6 +281,7 @@ class EditCodeService extends Disposable implements IEditCodeService { private readonly _onDidAddOrDeleteDiffZones = new Emitter<{ uri: URI }>(); + constructor( // @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right @ICodeEditorService private readonly _editorService: ICodeEditorService, @@ -309,7 +317,7 @@ class EditCodeService extends Disposable implements IEditCodeService { // when a stream starts or ends let removeAcceptRejectAllUI: (() => void) | null = null - const onChangeUriState = () => { + const changeUriState = () => { const uri = model.uri const diffZones = [...this.diffAreasOfURI[uri.fsPath].values()] .map(diffareaid => this.diffAreaOfId[diffareaid]) @@ -322,8 +330,8 @@ class EditCodeService extends Disposable implements IEditCodeService { removeAcceptRejectAllUI = null } } - this._register(this._onDidAddOrDeleteDiffZones.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) onChangeUriState() })) - this._register(this._onDidChangeStreaming.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) onChangeUriState() })) + this._register(this._onDidAddOrDeleteDiffZones.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) changeUriState() })) + this._register(this._onDidChangeStreaming.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) changeUriState() })) } // initialize all existing models + initialize when a new model mounts for (let model of this._modelService.getModels()) { initializeModel(model) } @@ -526,7 +534,6 @@ class EditCodeService extends Disposable implements IEditCodeService { mountCtrlK(domNode, accessor, { diffareaid: ctrlKZone.diffareaid, - initStreamingDiffZoneId: ctrlKZone._linkedStreamingDiffZone, textAreaRef: (r) => { textAreaRef.current = r @@ -1198,19 +1205,15 @@ class EditCodeService extends Disposable implements IEditCodeService { public startApplying(opts: StartApplyingOpts) { - if (opts.type === 'rewrite') { - const addedDiffZone = this._initializeWriteoverStream(opts) - return addedDiffZone?.diffareaid + const addedDiffArea = this._initializeWriteoverStream(opts) + return addedDiffArea?._URI ?? null } - else if (opts.type === 'searchReplace') { - const addedDiffZone = this._initializeSearchAndReplaceStream(opts) - return addedDiffZone?.diffareaid + const addedDiffArea = this._initializeSearchAndReplaceStream(opts) + return addedDiffArea?._URI ?? null } - - return undefined - + return null } @@ -1708,18 +1711,46 @@ class EditCodeService extends Disposable implements IEditCodeService { this._undoRedoService.undo(uri) } - // call this outside undo/redo (it calls undo). this is only for aborting a diffzone stream - interruptStreaming(diffareaid: number) { - const diffArea = this.diffAreaOfId[diffareaid] - if (!diffArea) return - if (diffArea.type !== 'DiffZone') return - if (!diffArea._streamState.isStreaming) return - this._stopIfStreaming(diffArea) - this._undoHistory(diffArea._URI) + + + isCtrlKZoneStreaming({ diffareaid }: { diffareaid: number }) { + const ctrlKZone = this.diffAreaOfId[diffareaid] + if (!ctrlKZone) return false + if (ctrlKZone.type !== 'CtrlKZone') return false + return !!ctrlKZone._linkedStreamingDiffZone } + isDiffZoneStreaming({ diffareaid }: { diffareaid: number }) { + const diffZone = this.diffAreaOfId[diffareaid] + if (diffZone?.type !== 'DiffZone') return false + return diffZone._streamState.isStreaming + } + + + // call this outside undo/redo (it calls undo). this is only for aborting a diffzone stream + 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) + } + + interruptCtrlKStreaming({ diffareaid }: { diffareaid: number }) { + const ctrlKZone = this.diffAreaOfId[diffareaid] + if (ctrlKZone?.type !== 'CtrlKZone') return + if (!ctrlKZone._linkedStreamingDiffZone) return + + const linkedStreamingDiffZone = this.diffAreaOfId[ctrlKZone._linkedStreamingDiffZone] + if (!linkedStreamingDiffZone) return + if (linkedStreamingDiffZone.type !== 'DiffZone') return + + this.interruptDiffZoneStreaming({ diffareaid: linkedStreamingDiffZone.diffareaid }) + + } diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts index 1099e74c..da8f5c55 100644 --- a/src/vs/workbench/contrib/void/browser/quickEditActions.ts +++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts @@ -17,7 +17,6 @@ import { IMetricsService } from '../common/metricsService.js'; export type QuickEditPropsType = { diffareaid: number, - initStreamingDiffZoneId: number | null, textAreaRef: (ref: HTMLTextAreaElement | null) => void; onChangeHeight: (height: number) => void; onChangeText: (text: string) => void; 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 d279c631..0c6176ff 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,7 @@ import { useState, useEffect, useCallback } from 'react' -import { useAccessor } from '../util/services.js' +import { useAccessor, useIsURIStreaming } from '../util/services.js' +import { useRefState } from '../util/helpers.js' +import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js' enum CopyButtonText { Idle = 'Copy', @@ -50,7 +52,15 @@ const ApplyButton = ({ codeStr, codeBoxId }: { codeStr: string, codeBoxId: strin const metricsService = accessor.get('IMetricsService') - const onApply = useCallback(() => { + const [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone] = useRefState(initStreamingDiffZoneId) + const isStreaming = currStreamingDiffZoneRef.current !== null + const isDisabled = !!isFeatureNameDisabled('Ctrl+K', settingsState) + + useIsDiffZoneStreaming(isDiffAreaStreaming) + + + const onSubmit = useCallback(() => { + const diffareaid = editCodeService.startApplying({ from: 'ClickApply', type: 'searchReplace', @@ -60,7 +70,31 @@ const ApplyButton = ({ codeStr, codeBoxId }: { codeStr: string, codeBoxId: strin metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only - }, [metricsService, editCodeService, codeStr]) + + if (isDisabled) return + if (currStreamingDiffZoneRef.current !== null) return + textAreaFnsRef.current?.disable() + + const id = editCodeService.startApplying({ + from: 'QuickEdit', + type: 'rewrite', + diffareaid: diffareaid, + }) + setCurrentlyStreamingDiffZone(id ?? null) + }, [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone, isDisabled, editCodeService, diffareaid]) + + const onInterrupt = useCallback(() => { + if (currStreamingDiffZoneRef.current === null) return + editCodeService.interruptStreaming(currStreamingDiffZoneRef.current) + setCurrentlyStreamingDiffZone(null) + textAreaFnsRef.current?.enable() + }, [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone, editCodeService]) + + + + + + const isSingleLine = !codeStr.includes('\n') 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 f79c9af9..f00bc251 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 @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------*/ import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react'; -import { useSettingsState, useSidebarState, useChatThreadsState, useQuickEditState, useAccessor } from '../util/services.js'; +import { useSettingsState, useSidebarState, useChatThreadsState, useQuickEditState, useAccessor, useIsCtrlKZoneStreaming } from '../util/services.js'; import { TextAreaFns, VoidInputBox2 } from '../util/inputs.js'; import { QuickEditPropsType } from '../../../quickEditActions.js'; import { ButtonStop, ButtonSubmit, IconX, VoidChatArea } from '../sidebar-tsx/SidebarChat.js'; @@ -16,7 +16,6 @@ import { isFeatureNameDisabled } from '../../../../../../../workbench/contrib/vo export const QuickEditChat = ({ diffareaid, - initStreamingDiffZoneId, onChangeHeight, onChangeText: onChangeText_, textAreaRef: textAreaRef_, @@ -49,28 +48,25 @@ export const QuickEditChat = ({ const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!(initText ?? '')) // the user's instructions const isDisabled = instructionsAreEmpty || !!isFeatureNameDisabled('Ctrl+K', settingsState) - const [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone] = useRefState(initStreamingDiffZoneId) - const isStreaming = currStreamingDiffZoneRef.current !== null + const isStreamingRefState = useIsCtrlKZoneStreaming(diffareaid) const onSubmit = useCallback(() => { if (isDisabled) return - if (currStreamingDiffZoneRef.current !== null) return + if (isStreamingRefState.current) return textAreaFnsRef.current?.disable() - const id = editCodeService.startApplying({ + editCodeService.startApplying({ from: 'QuickEdit', - type:'rewrite', - diffareaid: diffareaid, + type: 'rewrite', + diffareaid, }) - setCurrentlyStreamingDiffZone(id ?? null) - }, [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone, isDisabled, editCodeService, diffareaid]) + }, [isStreamingRefState, isDisabled, editCodeService, diffareaid]) const onInterrupt = useCallback(() => { - if (currStreamingDiffZoneRef.current === null) return - editCodeService.interruptStreaming(currStreamingDiffZoneRef.current) - setCurrentlyStreamingDiffZone(null) + if (!isStreamingRefState.current ) return + editCodeService.interruptCtrlKStreaming({ diffareaid }) textAreaFnsRef.current?.enable() - }, [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone, editCodeService]) + }, [isStreamingRefState, editCodeService]) const onX = useCallback(() => { @@ -89,7 +85,7 @@ export const QuickEditChat = ({ onSubmit={onSubmit} onAbort={onInterrupt} onClose={onX} - isStreaming={isStreaming} + isStreaming={isStreamingRefState.current} isDisabled={isDisabled} featureName="Ctrl+K" className="py-2 w-full" diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 30351697..fd2d8c9e 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -557,11 +557,11 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM let setStagingSelections = (s: StagingSelectionItem[]) => { } if (messageIdx !== undefined) { - const [_state, _setState] = chatThreadsService._useCurrentMessageState(messageIdx) + const _state = chatThreadsService.getCurrentMessageState(messageIdx) isBeingEdited = _state.isBeingEdited - setIsBeingEdited = (v) => _setState({ isBeingEdited: v }) stagingSelections = _state.stagingSelections - setStagingSelections = (s) => { _setState({ stagingSelections: s }) } + setIsBeingEdited = (v) => chatThreadsService.setCurrentMessageState(messageIdx, { isBeingEdited: v }) + setStagingSelections = (s) => chatThreadsService.setCurrentMessageState(messageIdx, { stagingSelections: s }) } @@ -780,9 +780,8 @@ export const SidebarChat = () => { const currentThread = chatThreadsService.getCurrentThread() const previousMessages = currentThread?.messages ?? [] - const [_state, _setState] = chatThreadsService._useCurrentThreadState() - const selections = _state.stagingSelections - const setSelections = (s: StagingSelectionItem[]) => { _setState({ stagingSelections: s }) } + const selections = chatThreadsService.getCurrentThread().state.stagingSelections + const setSelections = (s: StagingSelectionItem[]) => { chatThreadsService.setCurrentThreadStagingSelections(s) } // stream state const currThreadStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId) @@ -818,7 +817,7 @@ export const SidebarChat = () => { textAreaFnsRef.current?.setValue('') textAreaRef.current?.focus() // focus input after submit - }, [chatThreadsService, isDisabled, isStreaming, textAreaRef, textAreaFnsRef, selections, setSelections]) + }, [chatThreadsService, isDisabled, isStreaming, textAreaRef, textAreaFnsRef, setSelections]) const onAbort = () => { const threadId = currentThread.id 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 2d516ace..7ca177ce 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 @@ -47,6 +47,7 @@ import { IEnvironmentService } from '../../../../../../../platform/environment/c import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js' import { IPathService } from '../../../../../../../workbench/services/path/common/pathService.js' import { IMetricsService } from '../../../../../../../workbench/contrib/void/common/metricsService.js' +import { URI } from '../../../../../../../base/common/uri.js' @@ -353,3 +354,17 @@ export const useIsDark = () => { return isDark } + + + + +export const useIsCtrlKZoneStreaming = (diffareaid: number) => { + + return { current: true } + +} + + +export const useIsDiffZoneStreaming = (uri: URI) => { + +} diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts index 39eb8381..722eaa57 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts @@ -141,13 +141,11 @@ registerAction2(class extends Action2 { let setSelections = (s: StagingSelectionItem[]) => { } if (focusedMessageIdx === undefined) { - const [state, setState] = chatThreadService._useCurrentThreadState() - selections = state.stagingSelections - setSelections = (s) => setState({ stagingSelections: s }) + selections = chatThreadService.getCurrentThreadStagingSelections() + setSelections = (s: StagingSelectionItem[]) => chatThreadService.setCurrentThreadStagingSelections(s) } else { - const [state, setState] = chatThreadService._useCurrentMessageState(focusedMessageIdx) - selections = state.stagingSelections - setSelections = (s) => setState({ stagingSelections: s }) + selections = chatThreadService.getCurrentMessageState(focusedMessageIdx).stagingSelections + setSelections = (s) => chatThreadService.setCurrentMessageState(focusedMessageIdx, { stagingSelections: s }) } // if matches with existing selection, overwrite (since text may change)