mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
fix ctrlK state
This commit is contained in:
parent
07f4a59ce6
commit
64e43fa53e
5 changed files with 93 additions and 22 deletions
|
|
@ -38,6 +38,7 @@ import { filenameToVscodeLanguage } from './helpers/detectLanguage.js';
|
||||||
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
|
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
|
||||||
import { isMacintosh } from '../../../../base/common/platform.js';
|
import { isMacintosh } from '../../../../base/common/platform.js';
|
||||||
import { EditorOption } from '../../../../editor/common/config/editorOptions.js';
|
import { EditorOption } from '../../../../editor/common/config/editorOptions.js';
|
||||||
|
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||||
// import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
// import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||||
// import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
// import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||||
// import { localize2 } from '../../../../nls.js';
|
// import { localize2 } from '../../../../nls.js';
|
||||||
|
|
@ -146,6 +147,7 @@ type DiffZone = {
|
||||||
line?: undefined;
|
line?: undefined;
|
||||||
};
|
};
|
||||||
editorId?: undefined;
|
editorId?: undefined;
|
||||||
|
linkedStreamingDiffZone?: undefined;
|
||||||
} & CommonZoneProps
|
} & CommonZoneProps
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -160,6 +162,7 @@ const diffAreaSnapshotKeys = [
|
||||||
'startLine',
|
'startLine',
|
||||||
'endLine',
|
'endLine',
|
||||||
'editorId',
|
'editorId',
|
||||||
|
|
||||||
] as const satisfies (keyof DiffArea)[]
|
] as const satisfies (keyof DiffArea)[]
|
||||||
|
|
||||||
type DiffAreaSnapshot<DiffAreaType extends DiffArea = DiffArea> = Pick<DiffAreaType, typeof diffAreaSnapshotKeys[number]>
|
type DiffAreaSnapshot<DiffAreaType extends DiffArea = DiffArea> = Pick<DiffAreaType, typeof diffAreaSnapshotKeys[number]>
|
||||||
|
|
@ -172,6 +175,7 @@ type HistorySnapshot = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type StreamState = Set<number> // set of streaming diffareaids, only applies to diffzones
|
||||||
|
|
||||||
export interface IInlineDiffsService {
|
export interface IInlineDiffsService {
|
||||||
readonly _serviceBrand: undefined;
|
readonly _serviceBrand: undefined;
|
||||||
|
|
@ -179,6 +183,9 @@ export interface IInlineDiffsService {
|
||||||
interruptStreaming(diffareaid: number): void;
|
interruptStreaming(diffareaid: number): void;
|
||||||
addCtrlKZone(opts: AddCtrlKOpts): number | undefined;
|
addCtrlKZone(opts: AddCtrlKOpts): number | undefined;
|
||||||
removeCtrlKZone(opts: { diffareaid: number }): void;
|
removeCtrlKZone(opts: { diffareaid: number }): void;
|
||||||
|
|
||||||
|
onDidChangeStreamState: Event<number>
|
||||||
|
streamingDiffZonesState: StreamState
|
||||||
// testDiffs(): void;
|
// testDiffs(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,6 +202,13 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
diffOfId: Record<string, Diff> = {}; // redundant with diffArea._diffs
|
diffOfId: Record<string, Diff> = {}; // redundant with diffArea._diffs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// streaming state for React, reflection of the streaming value in the diffarea, only applies to diffZone
|
||||||
|
readonly streamingDiffZonesState: StreamState = new Set()
|
||||||
|
private readonly _onDidChangeStreamState = new Emitter<number>();
|
||||||
|
readonly onDidChangeStreamState: Event<number> = this._onDidChangeStreamState.event;
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
// @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right
|
// @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right
|
||||||
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
||||||
|
|
@ -366,6 +380,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
mountCtrlK(domNode, accessor, {
|
mountCtrlK(domNode, accessor, {
|
||||||
|
|
||||||
diffareaid: ctrlKZone.diffareaid,
|
diffareaid: ctrlKZone.diffareaid,
|
||||||
|
initStreamingDiffZoneId: ctrlKZone._linkedStreamingDiffZone,
|
||||||
|
|
||||||
textAreaRef: (r) => {
|
textAreaRef: (r) => {
|
||||||
textAreaRef.current = r
|
textAreaRef.current = r
|
||||||
|
|
@ -1045,7 +1060,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
// check if there's overlap with any other ctrlKZone and if so, focus it
|
// check if there's overlap with any other ctrlKZone and if so, focus it
|
||||||
const overlappingCtrlKZone = this._findOverlappingDiffArea({ startLine, endLine, uri, filter: (diffArea) => diffArea.type === 'CtrlKZone' })
|
const overlappingCtrlKZone = this._findOverlappingDiffArea({ startLine, endLine, uri, filter: (diffArea) => diffArea.type === 'CtrlKZone' })
|
||||||
if (overlappingCtrlKZone) {
|
if (overlappingCtrlKZone) {
|
||||||
(overlappingCtrlKZone as CtrlKZone)._mountInfo?.textAreaRef.current?.focus()
|
editor.revealLine(overlappingCtrlKZone.startLine) // important
|
||||||
|
setTimeout(() => (overlappingCtrlKZone as CtrlKZone)._mountInfo?.textAreaRef.current?.focus(), 100)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1053,6 +1069,9 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
if (overlappingDiffZone)
|
if (overlappingDiffZone)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
editor.revealLine(startLine)
|
||||||
|
editor.setSelection({ startLineNumber: startLine, endLineNumber: startLine, startColumn: 1, endColumn: 1 })
|
||||||
|
|
||||||
const { onFinishEdit } = this._addToHistory(uri)
|
const { onFinishEdit } = this._addToHistory(uri)
|
||||||
|
|
||||||
const adding: Omit<CtrlKZone, 'diffareaid'> = {
|
const adding: Omit<CtrlKZone, 'diffareaid'> = {
|
||||||
|
|
@ -1184,10 +1203,14 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
_removeStylesFns: new Set(),
|
_removeStylesFns: new Set(),
|
||||||
}
|
}
|
||||||
const diffZone = this._addDiffArea(adding)
|
const diffZone = this._addDiffArea(adding)
|
||||||
|
this.streamingDiffZonesState.add(diffZone.diffareaid)
|
||||||
|
this._onDidChangeStreamState.fire(diffZone.diffareaid)
|
||||||
|
|
||||||
if (featureName === 'Ctrl+K') {
|
if (featureName === 'Ctrl+K') {
|
||||||
const { diffareaid } = opts
|
const { diffareaid } = opts
|
||||||
const ctrlKZone = this.diffAreaOfId[diffareaid]
|
const ctrlKZone = this.diffAreaOfId[diffareaid]
|
||||||
if (ctrlKZone.type !== 'CtrlKZone') return
|
if (ctrlKZone.type !== 'CtrlKZone') return
|
||||||
|
|
||||||
ctrlKZone._linkedStreamingDiffZone = diffZone.diffareaid
|
ctrlKZone._linkedStreamingDiffZone = diffZone.diffareaid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1220,9 +1243,13 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
|
|
||||||
const onDone = (hadError: boolean) => {
|
const onDone = (hadError: boolean) => {
|
||||||
diffZone._streamState = { isStreaming: false, }
|
diffZone._streamState = { isStreaming: false, }
|
||||||
|
this.streamingDiffZonesState.delete(diffZone.diffareaid)
|
||||||
|
this._onDidChangeStreamState.fire(diffZone.diffareaid)
|
||||||
|
|
||||||
if (featureName === 'Ctrl+K') {
|
if (featureName === 'Ctrl+K') {
|
||||||
const ctrlKZone = this.diffAreaOfId[opts.diffareaid] as CtrlKZone
|
const ctrlKZone = this.diffAreaOfId[opts.diffareaid] as CtrlKZone
|
||||||
ctrlKZone._linkedStreamingDiffZone = null // gets deleted next so not really needed...
|
|
||||||
|
ctrlKZone._linkedStreamingDiffZone = null
|
||||||
this._deleteCtrlKZone(ctrlKZone)
|
this._deleteCtrlKZone(ctrlKZone)
|
||||||
}
|
}
|
||||||
this._refreshStylesAndDiffsInURI(uri)
|
this._refreshStylesAndDiffsInURI(uri)
|
||||||
|
|
@ -1300,7 +1327,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||||
this._llmMessageService.abort(streamRequestId)
|
this._llmMessageService.abort(streamRequestId)
|
||||||
|
|
||||||
diffZone._streamState = { isStreaming: false, }
|
diffZone._streamState = { isStreaming: false, }
|
||||||
|
this.streamingDiffZonesState.delete(diffZone.diffareaid)
|
||||||
|
this._onDidChangeStreamState.fire(diffZone.diffareaid)
|
||||||
}
|
}
|
||||||
|
|
||||||
_undoHistory(uri: URI) {
|
_undoHistory(uri: URI) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { VOID_CTRL_K_ACTION_ID } from './actionIDs.js';
|
||||||
|
|
||||||
export type QuickEditPropsType = {
|
export type QuickEditPropsType = {
|
||||||
diffareaid: number,
|
diffareaid: number,
|
||||||
|
initStreamingDiffZoneId: number | null,
|
||||||
textAreaRef: (ref: HTMLTextAreaElement | null) => void;
|
textAreaRef: (ref: HTMLTextAreaElement | null) => void;
|
||||||
onChangeHeight: (height: number) => void;
|
onChangeHeight: (height: number) => void;
|
||||||
onChangeText: (text: string) => void;
|
onChangeText: (text: string) => void;
|
||||||
|
|
@ -60,12 +61,6 @@ registerAction2(class extends Action2 {
|
||||||
|
|
||||||
const { startLineNumber: startLine, endLineNumber: endLine } = selection
|
const { startLineNumber: startLine, endLineNumber: endLine } = selection
|
||||||
|
|
||||||
|
|
||||||
// deselect - clear selection
|
|
||||||
editor.setSelection({ startLineNumber: startLine, endLineNumber: startLine, startColumn: 1, endColumn: 1 })
|
|
||||||
|
|
||||||
editor.revealLine(startLine) // important
|
|
||||||
|
|
||||||
const inlineDiffsService = accessor.get(IInlineDiffsService)
|
const inlineDiffsService = accessor.get(IInlineDiffsService)
|
||||||
inlineDiffsService.addCtrlKZone({ startLine, endLine, editor })
|
inlineDiffsService.addCtrlKZone({ startLine, endLine, editor })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,17 @@
|
||||||
*--------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react';
|
import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState, useAccessor } from '../util/services.js';
|
import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState, useAccessor, useIsStreaming } from '../util/services.js';
|
||||||
import { TextAreaFns, VoidInputBox2 } from '../util/inputs.js';
|
import { TextAreaFns, VoidInputBox2 } from '../util/inputs.js';
|
||||||
import { QuickEditPropsType } from '../../../quickEditActions.js';
|
import { QuickEditPropsType } from '../../../quickEditActions.js';
|
||||||
import { ButtonStop, ButtonSubmit, IconX } from '../sidebar-tsx/SidebarChat.js';
|
import { ButtonStop, ButtonSubmit, IconX } from '../sidebar-tsx/SidebarChat.js';
|
||||||
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
|
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
|
||||||
import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js';
|
import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js';
|
||||||
|
import { useRefState } from '../util/helpers.js';
|
||||||
|
|
||||||
export const QuickEditChat = ({
|
export const QuickEditChat = ({
|
||||||
diffareaid,
|
diffareaid,
|
||||||
|
initStreamingDiffZoneId,
|
||||||
onChangeHeight,
|
onChangeHeight,
|
||||||
onChangeText: onChangeText_,
|
onChangeText: onChangeText_,
|
||||||
textAreaRef: textAreaRef_,
|
textAreaRef: textAreaRef_,
|
||||||
|
|
@ -43,32 +45,34 @@ export const QuickEditChat = ({
|
||||||
const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!(initText ?? '')) // the user's instructions
|
const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!(initText ?? '')) // the user's instructions
|
||||||
const isDisabled = instructionsAreEmpty
|
const isDisabled = instructionsAreEmpty
|
||||||
|
|
||||||
const currentlyStreamingIdRef = useRef<number | undefined>(undefined)
|
const [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone] = useRefState<number | null>(initStreamingDiffZoneId)
|
||||||
const [isStreaming, setIsStreaming] = useState(false)
|
|
||||||
|
const isStreaming = useIsStreaming({ diffareaid: currStreamingDiffZoneRef.current })
|
||||||
|
|
||||||
const onSubmit = useCallback((e: FormEvent) => {
|
const onSubmit = useCallback((e: FormEvent) => {
|
||||||
if (isDisabled) return
|
if (isDisabled) return
|
||||||
if (currentlyStreamingIdRef.current !== undefined) return
|
if (currStreamingDiffZoneRef.current !== null) return
|
||||||
textAreaFnsRef.current?.disable()
|
textAreaFnsRef.current?.disable()
|
||||||
|
|
||||||
const instructions = textAreaRef.current?.value ?? ''
|
const instructions = textAreaRef.current?.value ?? ''
|
||||||
currentlyStreamingIdRef.current = inlineDiffsService.startApplying({
|
const id = inlineDiffsService.startApplying({
|
||||||
featureName: 'Ctrl+K',
|
featureName: 'Ctrl+K',
|
||||||
diffareaid: diffareaid,
|
diffareaid: diffareaid,
|
||||||
userMessage: instructions,
|
userMessage: instructions,
|
||||||
})
|
})
|
||||||
setIsStreaming(true)
|
setCurrentlyStreamingDiffZone(id ?? null)
|
||||||
}, [isDisabled, inlineDiffsService, diffareaid])
|
}, [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone, isDisabled, inlineDiffsService, diffareaid])
|
||||||
|
|
||||||
const onInterrupt = useCallback(() => {
|
const onInterrupt = useCallback(() => {
|
||||||
if (currentlyStreamingIdRef.current !== undefined)
|
if (currStreamingDiffZoneRef.current === null) return
|
||||||
inlineDiffsService.interruptStreaming(currentlyStreamingIdRef.current)
|
inlineDiffsService.interruptStreaming(currStreamingDiffZoneRef.current)
|
||||||
|
setCurrentlyStreamingDiffZone(null)
|
||||||
textAreaFnsRef.current?.enable()
|
textAreaFnsRef.current?.enable()
|
||||||
setIsStreaming(false)
|
}, [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone, inlineDiffsService])
|
||||||
}, [inlineDiffsService])
|
|
||||||
|
|
||||||
|
|
||||||
const onX = useCallback(() => {
|
const onX = useCallback(() => {
|
||||||
|
onInterrupt()
|
||||||
inlineDiffsService.removeCtrlKZone({ diffareaid })
|
inlineDiffsService.removeCtrlKZone({ diffareaid })
|
||||||
}, [inlineDiffsService, diffareaid])
|
}, [inlineDiffsService, diffareaid])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useCallback, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type ReturnType<T> = [
|
||||||
|
{ readonly current: T },
|
||||||
|
(t: T) => void
|
||||||
|
]
|
||||||
|
|
||||||
|
// use this if state might be too slow to catch
|
||||||
|
export const useRefState = <T,>(initVal: T): ReturnType<T> => {
|
||||||
|
const [_, _setState] = useState(false)
|
||||||
|
const ref = useRef<T>(initVal)
|
||||||
|
const setState = useCallback((newVal: T) => {
|
||||||
|
_setState(n => !n) // call rerender
|
||||||
|
ref.current = newVal
|
||||||
|
}, [])
|
||||||
|
return [ref, setState]
|
||||||
|
}
|
||||||
|
|
@ -27,7 +27,7 @@ import { IThemeService } from '../../../../../../../platform/theme/common/themeS
|
||||||
import { ILLMMessageService } from '../../../../../../../platform/void/common/llmMessageService.js';
|
import { ILLMMessageService } from '../../../../../../../platform/void/common/llmMessageService.js';
|
||||||
import { IRefreshModelService } from '../../../../../../../platform/void/common/refreshModelService.js';
|
import { IRefreshModelService } from '../../../../../../../platform/void/common/refreshModelService.js';
|
||||||
import { IVoidSettingsService } from '../../../../../../../platform/void/common/voidSettingsService.js';
|
import { IVoidSettingsService } from '../../../../../../../platform/void/common/voidSettingsService.js';
|
||||||
import { IInlineDiffsService } from '../../../inlineDiffsService.js';
|
import { IInlineDiffsService, StreamState } from '../../../inlineDiffsService.js';
|
||||||
import { IQuickEditStateService } from '../../../quickEditStateService.js';
|
import { IQuickEditStateService } from '../../../quickEditStateService.js';
|
||||||
import { ISidebarStateService } from '../../../sidebarStateService.js';
|
import { ISidebarStateService } from '../../../sidebarStateService.js';
|
||||||
import { IThreadHistoryService } from '../../../threadHistoryService.js';
|
import { IThreadHistoryService } from '../../../threadHistoryService.js';
|
||||||
|
|
@ -70,6 +70,9 @@ const refreshModelProviderListeners: Set<(p: RefreshableProviderName, s: Refresh
|
||||||
let colorThemeState: ColorScheme
|
let colorThemeState: ColorScheme
|
||||||
const colorThemeStateListeners: Set<(s: ColorScheme) => void> = new Set()
|
const colorThemeStateListeners: Set<(s: ColorScheme) => void> = new Set()
|
||||||
|
|
||||||
|
let streamState: StreamState
|
||||||
|
const diffareaStreamStateListeners: Set<(diffareaid: number) => void> = new Set()
|
||||||
|
|
||||||
// must call this before you can use any of the hooks below
|
// must call this before you can use any of the hooks below
|
||||||
// this should only be called ONCE! this is the only place you don't need to dispose onDidChange. If you use state.onDidChange anywhere else, make sure to dispose it!
|
// this should only be called ONCE! this is the only place you don't need to dispose onDidChange. If you use state.onDidChange anywhere else, make sure to dispose it!
|
||||||
let wasCalled = false
|
let wasCalled = false
|
||||||
|
|
@ -93,9 +96,10 @@ export const _registerServices = (accessor: ServicesAccessor) => {
|
||||||
settingsStateService: accessor.get(IVoidSettingsService),
|
settingsStateService: accessor.get(IVoidSettingsService),
|
||||||
refreshModelService: accessor.get(IRefreshModelService),
|
refreshModelService: accessor.get(IRefreshModelService),
|
||||||
themeService: accessor.get(IThemeService),
|
themeService: accessor.get(IThemeService),
|
||||||
|
inlineDiffsService: accessor.get(IInlineDiffsService),
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sidebarStateService, quickEditStateService, settingsStateService, threadsStateService, refreshModelService, themeService, } = stateServices
|
const { sidebarStateService, quickEditStateService, settingsStateService, threadsStateService, refreshModelService, themeService, inlineDiffsService } = stateServices
|
||||||
|
|
||||||
quickEditState = quickEditStateService.state
|
quickEditState = quickEditStateService.state
|
||||||
disposables.push(
|
disposables.push(
|
||||||
|
|
@ -146,6 +150,13 @@ export const _registerServices = (accessor: ServicesAccessor) => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
disposables.push(
|
||||||
|
inlineDiffsService.onDidChangeStreamState((diffareaid) => {
|
||||||
|
streamState = inlineDiffsService.streamingDiffZonesState
|
||||||
|
diffareaStreamStateListeners.forEach(l => l(diffareaid))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
return disposables
|
return disposables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,3 +294,17 @@ export const useIsDark = () => {
|
||||||
return isDark
|
return isDark
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const useIsStreaming = ({ diffareaid }: { diffareaid: number | null }) => {
|
||||||
|
console.log('difareaid', diffareaid)
|
||||||
|
const [s, ss] = useState(diffareaid === null ? false : streamState.has(diffareaid))
|
||||||
|
useEffect(() => { ss(diffareaid === null ? false : streamState.has(diffareaid)) }, [diffareaid])
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = (diffareaid_: number) => { if (diffareaid === diffareaid_) ss(streamState.has(diffareaid)) }
|
||||||
|
diffareaStreamStateListeners.add(listener)
|
||||||
|
return () => { diffareaStreamStateListeners.delete(listener) }
|
||||||
|
}, [ss, diffareaid])
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue