ctrlk draft

This commit is contained in:
mp
2024-12-19 02:14:26 -08:00
parent 5a26aa8746
commit d6074e7ab5
13 changed files with 431 additions and 101 deletions

View File

@ -9,10 +9,12 @@ import { ILLMMessageService } from '../../../../../platform/void/common/llmMessa
import { IRefreshModelService } from '../../../../../platform/void/common/refreshModelService.js';
import { IVoidSettingsService } from '../../../../../platform/void/common/voidSettingsService.js';
import { IInlineDiffsService } from '../inlineDiffsService.js';
import { IQuickEditStateService } from '../quickEditStateService.js';
import { ISidebarStateService } from '../sidebarStateService.js';
import { IThreadHistoryService } from '../threadHistoryService.js';
export type ReactServicesType = {
quickEditStateService: IQuickEditStateService;
sidebarStateService: ISidebarStateService;
settingsStateService: IVoidSettingsService;
threadsStateService: IThreadHistoryService;
@ -33,6 +35,7 @@ export type ReactServicesType = {
export const getReactServices = (accessor: ServicesAccessor): ReactServicesType => {
return {
quickEditStateService: accessor.get(IQuickEditStateService),
settingsStateService: accessor.get(IVoidSettingsService),
sidebarStateService: accessor.get(ISidebarStateService),
threadsStateService: accessor.get(IThreadHistoryService),

View File

@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/brows
// import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
// import { throttle } from '../../../../base/common/decorators.js';
import { writeFileWithDiffInstructions } from './prompt/systemPrompts.js';
import { writeFileWithDiffInstructions } from './prompt/prompts.js';
import { ComputedDiff, findDiffs } from './helpers/findDiffs.js';
import { EndOfLinePreference, ITextModel } from '../../../../editor/common/model.js';
import { IRange } from '../../../../editor/common/core/range.js';

View File

@ -3,38 +3,36 @@
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
// // used for ctrl+l
// const partialGenerationInstructions = ``
import { CodeSelection } from '../threadHistoryService.js';
const stringifySelections = (selections: CodeSelection[]) => {
return selections.map(({ fileURI, content, selectionStr }) =>
`\
File: ${fileURI.fsPath}
\`\`\`
${content // this was the enite file which is foolish
}
\`\`\`${selectionStr === null ? '' : `
Selection: ${selectionStr}`}
`).join('\n')
}
// // used for ctrl+k, autocomplete
// const fimInstructions = ``
export const generateCtrlLPrompt = (instructions: string, selections: CodeSelection[] | null) => {
let str = '';
if (selections && selections.length > 0) {
str += stringifySelections(selections);
str += `Please edit the selected code following these instructions:\n`
}
str += `${instructions}`;
return str;
};
// CTRL+K prompt:
// const promptContent = `Here is the user's original selection:
// \`\`\`
// <MID>${selection}</MID>
// \`\`\`
// The user wants to apply the following instructions to the selection:
// ${instructions}
// Please rewrite the selection following the user's instructions.
// Instructions to follow:
// 1. Follow the user's instructions
// 2. You may ONLY CHANGE the selection, and nothing else in the file
// 3. Make sure all brackets in the new selection are balanced the same was as in the original selection
// 3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake
// Complete the following:
// \`\`\`
// <PRE>${prefix}</PRE>
// <SUF>${suffix}</SUF>
// <MID>`;
export const generateCtrlLInstructions = `\
export const ctrlLSystem = `\
You are a coding assistant. You are given a list of relevant files \`files\`, a selection that the user is making \`selection\`, and instructions to follow \`instructions\`.
Please edit the selected file following the user's instructions (or, if appropriate, answer their question instead).
@ -118,9 +116,33 @@ Memoization Object: A memo object is used to store the results of Fibonacci calc
Check Memo: Before computing fib(n), the function checks if the result is already in memo. If it is, it returns the stored result.
Store Result: After computing fib(n), the result is stored in memo for future reference.
## END EXAMPLES
## END EXAMPLES\
`
export const generateCtrlKPrompt = ({ selection, prefix, suffix, instructions, }: { selection: string, prefix: string, suffix: string, instructions: string, }) => `\
Here is the user's original selection:
\`\`\`
<MID>${selection}</MID>
\`\`\`
The user wants to apply the following instructions to the selection:
${instructions}
Please rewrite the selection following the user's instructions.
Instructions to follow:
1. Follow the user's instructions
2. You may ONLY CHANGE the selection, and nothing else in the file
3. Make sure all brackets in the new selection are balanced the same was as in the original selection
3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake
Complete the following:
\`\`\`
<PRE>${prefix}</PRE>
<SUF>${suffix}</SUF>
<MID>`;
export const generateDiffInstructions = `
You are a coding assistant. You are given a list of relevant files \`files\`, a selection that the user is making \`selection\`, and instructions to follow \`instructions\`.

View File

@ -1,32 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { CodeSelection } from '../threadHistoryService.js';
export const stringifySelections = (selections: CodeSelection[]) => {
return selections.map(({ fileURI, content, selectionStr }) =>
`\
File: ${fileURI.fsPath}
\`\`\`
${content // this was the enite file which is foolish
}
\`\`\`${selectionStr === null ? '' : `
Selection: ${selectionStr}`}
`).join('\n')
}
export const userInstructionsStr = (instructions: string, selections: CodeSelection[] | null) => {
let str = '';
if (selections && selections.length > 0) {
str += stringifySelections(selections);
str += `Please edit the selected code following these instructions:\n`
}
str += `${instructions}`;
return str;
};

View File

@ -1,9 +1,111 @@
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { ICodeEditor, IViewZone } from '../../../../editor/browser/editorBrowser.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { createDecorator, IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
import { Emitter, Event } from '../../../../base/common/event.js';
// import { IInlineDiffService } from '../../../../editor/browser/services/inlineDiffService/inlineDiffService.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { mountCtrlK } from './react/out/ctrl-k-tsx/index.js';
import { getReactServices } from './helpers/reactServicesHelper.js';
import { URI } from '../../../../base/common/uri.js';
type InitialZone = { uri: URI, startLine: number, selectedText: string, }
export type QuickEditPropsType = {
quickEditId: number,
}
export type QuickEdit = {
startLine: number, // 0-indexed
beforeCode: string,
afterCode?: string,
instructions?: string,
responseText?: string, // model can produce a text response too
}
export interface IQuickEditService {
readonly _serviceBrand: undefined;
readonly onDidChangeState: Event<void>;
addZone(zone: InitialZone): void;
}
export const IQuickEditService = createDecorator<IQuickEditService>('voidQuickEditService');
class VoidQuickEditService extends Disposable implements IQuickEditService {
_serviceBrand: undefined;
quickEditId: number = 0
private readonly _onDidChangeState = new Emitter<void>();
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
// state
// state: {}
constructor(
// @IInlineDiffService private readonly _inlineDiffService: IInlineDiffService,
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super();
}
addZone(zone: InitialZone) {
const addZoneToEditor = (editor: ICodeEditor) => {
const model = editor.getModel()
if (!model) return
editor.changeViewZones(accessor => {
const domNode = document.createElement('div');
domNode.style.zIndex = '1'
// domNode.className = 'void-redBG'
const viewZone: IViewZone = {
// afterLineNumber: computedDiff.startLine - 1,
afterLineNumber: 1,
heightInPx: 100,
// heightInLines: 1,
// minWidthInPx: 200,
domNode: domNode,
// marginDomNode: document.createElement('div'), // displayed to left
suppressMouseDown: false,
};
// const zoneId =
accessor.addZone(viewZone)
this._instantiationService.invokeFunction(accessor => {
const services = getReactServices(accessor)
const props: QuickEditPropsType = {
quickEditId: this.quickEditId++,
}
mountCtrlK(domNode, services, props)
})
// disposeInThisEditorFns.push(() => { editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) })
})
}
const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.uri.fsPath === zone.uri.fsPath)
for (const editor of editors) {
addZoneToEditor(editor)
}
}
}
registerSingleton(IQuickEditService, VoidQuickEditService, InstantiationType.Eager);
export const VOID_CTRL_K_ACTION_ID = 'void.ctrlKAction'
@ -12,17 +114,25 @@ registerAction2(class extends Action2 {
super({ id: VOID_CTRL_K_ACTION_ID, title: 'Void: Quick Edit', keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyK, weight: KeybindingWeight.BuiltinExtension } });
}
async run(accessor: ServicesAccessor): Promise<void> {
console.log('hello111!')
const model = accessor.get(ICodeEditorService).getActiveCodeEditor()?.getModel()
if (!model)
return
console.log('hello!')
const quickEditService = accessor.get(IQuickEditService)
const editorService = accessor.get(ICodeEditorService)
const metricsService = accessor.get(IMetricsService)
metricsService.capture('User Action', { type: 'Ctrl+K' })
metricsService.capture('User Action', { type: 'Open Ctrl+K' })
const editor = editorService.getActiveCodeEditor()
if (!editor) return;
const model = editor.getModel()
if (!model) return;
const selection = editor.getSelection()
if (!selection) return;
const uri = model.uri
const startLine = selection.startLineNumber
const selectedText = model.getValueInRange(selection)
quickEditService.addZone({ uri, startLine, selectedText, })
console.log('bye!')
}
});

View File

@ -0,0 +1,82 @@
import { Emitter, Event } from '../../../../base/common/event.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { QuickEdit } from './quickEditActions.js';
// service that manages state
export type VoidQuickEditState = {
quickEditsOfDocument: { [uri: string]: QuickEdit }
}
export interface IQuickEditStateService {
readonly _serviceBrand: undefined;
readonly state: VoidQuickEditState; // readonly to the user
setState(newState: Partial<VoidQuickEditState>): void;
onDidChangeState: Event<void>;
onDidFocusChat: Event<void>;
onDidBlurChat: Event<void>;
fireFocusChat(): void;
fireBlurChat(): void;
}
export const IQuickEditStateService = createDecorator<IQuickEditStateService>('voidQuickEditStateService');
class VoidQuickEditStateService extends Disposable implements IQuickEditStateService {
_serviceBrand: undefined;
static readonly ID = 'voidQuickEditStateService';
private readonly _onDidChangeState = new Emitter<void>();
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
private readonly _onFocusChat = new Emitter<void>();
readonly onDidFocusChat: Event<void> = this._onFocusChat.event;
private readonly _onBlurChat = new Emitter<void>();
readonly onDidBlurChat: Event<void> = this._onBlurChat.event;
// state
state: VoidQuickEditState
constructor(
// @IViewsService private readonly _viewsService: IViewsService,
) {
super()
// initial state
this.state = { quickEditsOfDocument: {} }
}
setState(newState: Partial<VoidQuickEditState>) {
// make sure view is open if the tab changes
// if ('currentTab' in newState) {
// this.addQuickEdit()
// }
this.state = { ...this.state, ...newState }
this._onDidChangeState.fire()
}
fireFocusChat() {
this._onFocusChat.fire()
}
fireBlurChat() {
this._onBlurChat.fire()
}
// addQuickEdit() {
// this._viewsService.openViewContainer(VOID_VIEW_CONTAINER_ID);
// this._viewsService.openView(VOID_VIEW_ID);
// }
}
registerSingleton(IQuickEditStateService, VoidQuickEditStateService, InstantiationType.Eager);

View File

@ -0,0 +1,18 @@
import { useEffect, useState } from 'react'
import { useIsDark, useSidebarState } from '../util/services.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
import { CtrlKChat } from './CtrlKChat.js'
import { QuickEditPropsType } from '../../../quickEditActions.js'
export const CtrlK = (props: QuickEditPropsType) => {
const isDark = useIsDark()
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ width: '100%', height: '100%' }}>
<ErrorBoundary>
<CtrlKChat {...props} />
</ErrorBoundary>
</div>
}

View File

@ -0,0 +1,83 @@
import React, { FormEvent, useCallback, useRef, useState } from 'react';
import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState, useService } from '../util/services.js';
import { OnError } from '../../../../../../../platform/void/common/llmMessageTypes.js';
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { getCmdKey } from '../../../helpers/getCmdKey.js';
import { VoidInputBox } from '../util/inputs.js';
import { QuickEditPropsType } from '../../../quickEditActions.js';
export const CtrlKChat = (props: QuickEditPropsType) => {
const inputBoxRef: React.MutableRefObject<InputBox | null> = useRef(null);
// -- imported state --
// const threadsStateService = useService('service')
// const sidebarState = useSidebarState()
const quickEditState = useQuickEditState()
// -- local state --
// state of chat
const [messageStream, setMessageStream] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
const latestRequestIdRef = useRef<string | null>(null)
const [latestError, setLatestError] = useState<Parameters<OnError>[0] | null>(null)
// state of current message
const [instructions, setInstructions] = useState('') // the user's instructions
const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions])
const isDisabled = !instructions.trim()
const onSubmit = useCallback((e: FormEvent) => {
// TODO
}, [])
return <form
className={
// copied from SidebarChat.tsx
`flex flex-col gap-2 p-1 relative input text-left shrink-0
transition-all duration-200
rounded-md
bg-vscode-input-bg
border border-vscode-commandcenter-inactive-border focus-within:border-vscode-commandcenter-active-border hover:border-vscode-commandcenter-active-border`
}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
onSubmit(e)
}
}}
onSubmit={(e) => {
console.log('submit!')
onSubmit(e)
}}
onClick={(e) => {
if (e.currentTarget === e.target) {
inputBoxRef.current?.focus()
}
}}
>
<div
className={
// copied from SidebarChat.tsx
`@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_textarea]:!void-max-h-[100px] @@[&_div.monaco-inputbox]:!void- @@[&_div.monaco-inputbox]:!void-outline-none`
}
>
{/* text input */}
<VoidInputBox
placeholder={`${getCmdKey()}+K to select`}
onChangeText={onChangeText}
inputBoxRef={inputBoxRef}
multiline={true}
/>
</div>
</form>
}

View File

@ -0,0 +1,8 @@
import { mountFnGenerator } from '../util/mountFnGenerator.js'
import { CtrlK } from './CtrlK.js'
export const mountCtrlK = mountFnGenerator(CtrlK)

View File

@ -3,12 +3,10 @@
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { FormEvent, Fragment, useCallback, useEffect, useRef, useState } from 'react';
import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, useCallback, useEffect, useRef, useState } from 'react';
import { useSettingsState, useService, useSidebarState, useThreadsState } from '../util/services.js';
import { generateCtrlLInstructions, generateDiffInstructions } from '../../../prompt/systemPrompts.js';
import { userInstructionsStr } from '../../../prompt/stringifySelections.js';
import { ChatMessage, CodeSelection, CodeStagingSelection } from '../../../threadHistoryService.js';
import { BlockCode } from '../markdown/BlockCode.js';
@ -23,6 +21,7 @@ import { getCmdKey } from '../../../helpers/getCmdKey.js'
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { VoidInputBox } from '../util/inputs.js';
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
import { ctrlLSystem, generateCtrlLPrompt } from '../../../prompt/prompts.js';
const IconX = ({ size, className = '' }: { size: number, className?: string }) => {
@ -85,6 +84,33 @@ const IconSquare = ({ size, className = '' }: { size: number, className?: string
);
};
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement>
export const ButtonSubmit = ({ className, disabled, ...props }: ButtonProps & Required<Pick<ButtonProps, 'disabled'>>) => {
return <button
className={`size-[20px] rounded-full shrink-0 grow-0 cursor-pointer
${disabled ? 'bg-vscode-disabled-fg' : 'bg-white'}
${className}
`}
type='submit'
{...props}
>
<IconArrowUp size={20} className="stroke-[2]" />
</button>
}
export const ButtonStop = ({ className, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) => {
return <button
className={`size-[20px] rounded-full bg-white cursor-pointer flex items-center justify-center
${className}
`}
type='button'
{...props}
>
<IconSquare size={16} className="stroke-[2]" />
</button>
}
const ScrollToBottomContainer = ({ children, className, style }: { children: React.ReactNode, className?: string, style?: React.CSSProperties }) => {
const [isAtBottom, setIsAtBottom] = useState(true); // Start at bottom
@ -277,6 +303,8 @@ export const SidebarChat = () => {
const threadsState = useThreadsState()
const threadsStateService = useService('threadsStateService')
const llmMessageService = useService('llmMessageService')
// ----- SIDEBAR CHAT state (local) -----
// state of chat
@ -286,7 +314,6 @@ export const SidebarChat = () => {
const [latestError, setLatestError] = useState<Parameters<OnError>[0] | null>(null)
const llmMessageService = useService('llmMessageService')
// state of current message
const [instructions, setInstructions] = useState('') // the user's instructions
@ -325,11 +352,11 @@ export const SidebarChat = () => {
// add system message to chat history
const systemPromptElt: ChatMessage = { role: 'system', content: generateCtrlLInstructions }
const systemPromptElt: ChatMessage = { role: 'system', content: ctrlLSystem }
threadsStateService.addMessageToCurrentThread(systemPromptElt)
// add user's message to chat history
const userHistoryElt: ChatMessage = { role: 'user', content: userInstructionsStr(instructions, selections), displayContent: instructions, selections: selections }
const userHistoryElt: ChatMessage = { role: 'user', content: generateCtrlLPrompt(instructions, selections), displayContent: instructions, selections: selections }
threadsStateService.addMessageToCurrentThread(userHistoryElt)
const currentThread = threadsStateService.getCurrentThread(threadsStateService.state) // the the instant state right now, don't wait for the React state
@ -474,14 +501,14 @@ export const SidebarChat = () => {
{/* middle row */}
<div
className={
// // overwrite vscode styles (generated with this code):
// // hack to overwrite vscode styles (generated with this code):
// `bg-transparent outline-none text-vscode-input-fg min-h-[81px] max-h-[500px]`
// .split(' ')
// .map(style => `@@[&_textarea]:!void-${style}`) // apply styles to ancestor input and textarea elements
// .map(style => `@@[&_textarea]:!void-${style}`) // apply styles to ancestor textarea elements
// .join(' ') +
// ` outline-none`
// .split(' ')
// .map(style => `@@[&_div.monaco-inputbox]:!void-${style}`) // apply styles to ancestor input and textarea elements
// .map(style => `@@[&_div.monaco-inputbox]:!void-${style}`)
// .join(' ');
`@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_textarea]:!void-min-h-[81px] @@[&_textarea]:!void-max-h-[500px]@@[&_div.monaco-inputbox]:!void- @@[&_div.monaco-inputbox]:!void-outline-none`
}
@ -508,27 +535,14 @@ export const SidebarChat = () => {
{/* submit / stop button */}
{isLoading ?
// stop button
<button
className={`size-[20px] rounded-full bg-white cursor-pointer flex items-center justify-center`}
<ButtonStop
onClick={onAbort}
type='button'
>
<IconSquare size={16} className="stroke-[2]" />
</button>
/>
:
// submit button (up arrow)
<button
className={`size-[20px] rounded-full shrink-0 grow-0 cursor-pointer
${isDisabled ?
'bg-vscode-disabled-fg' // cursor-not-allowed
: 'bg-white' // cursor-pointer
}
`}
<ButtonSubmit
disabled={isDisabled}
type='submit'
>
<IconArrowUp size={20} className="stroke-[2]" />
</button>
/>
}
</div>

View File

@ -8,8 +8,7 @@ import * as ReactDOM from 'react-dom/client'
import { _registerServices } from './services.js';
import { ReactServicesType } from '../../../helpers/reactServicesHelper.js';
export const mountFnGenerator = (Component: (params: any) => React.ReactNode) => (rootElement: HTMLElement, services: ReactServicesType) => {
export const mountFnGenerator = (Component: (params: any) => React.ReactNode) => (rootElement: HTMLElement, services: ReactServicesType, props?: any) => {
if (typeof document === 'undefined') {
console.error('index.tsx error: document was undefined')
return
@ -19,7 +18,7 @@ export const mountFnGenerator = (Component: (params: any) => React.ReactNode) =>
const root = ReactDOM.createRoot(rootElement)
root.render(<Component />); // tailwind dark theme indicator
root.render(<Component {...props} />); // tailwind dark theme indicator
return disposables
}

View File

@ -12,6 +12,7 @@ import { ReactServicesType } from '../../../helpers/reactServicesHelper.js'
import { VoidSidebarState } from '../../../sidebarStateService.js'
import { VoidSettingsState } from '../../../../../../../platform/void/common/voidSettingsService.js'
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'
import { VoidQuickEditState } from '../../../quickEditStateService.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
@ -20,6 +21,9 @@ let services: ReactServicesType
// 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 quickEditState: VoidQuickEditState
const quickEditStateListeners: Set<(s: VoidQuickEditState) => void> = new Set()
let sidebarState: VoidSidebarState
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
@ -50,7 +54,15 @@ export const _registerServices = (services_: ReactServicesType) => {
wasCalled = true
services = services_
const { sidebarStateService, settingsStateService, threadsStateService, refreshModelService, themeService } = services
const { sidebarStateService, quickEditStateService, settingsStateService, threadsStateService, refreshModelService, themeService, } = services
quickEditState = quickEditStateService.state
disposables.push(
quickEditStateService.onDidChangeState(() => {
quickEditState = quickEditStateService.state
quickEditStateListeners.forEach(l => l(quickEditState))
})
)
sidebarState = sidebarStateService.state
disposables.push(
@ -106,6 +118,16 @@ export const useService = <T extends keyof ReactServicesType,>(serviceName: T):
// -- state of services --
export const useQuickEditState = () => {
const [s, ss] = useState(quickEditState)
useEffect(() => {
ss(quickEditState)
quickEditStateListeners.add(ss)
return () => { quickEditStateListeners.delete(ss) }
}, [ss])
return s
}
export const useSidebarState = () => {
const [s, ss] = useState(sidebarState)
useEffect(() => {

View File

@ -9,6 +9,7 @@ export default defineConfig({
entry: [
'./src2/sidebar-tsx/index.tsx',
'./src2/void-settings-tsx/index.tsx',
'./src2/ctrl-k-tsx/index.tsx',
'./src2/diff/index.tsx',
],
outDir: './out',