fix services in React

This commit is contained in:
Andrew Pareles 2024-11-14 03:22:37 -08:00
parent 50d21828c2
commit dbea46ba32
9 changed files with 94 additions and 38 deletions

View file

@ -1,6 +1,7 @@
import React, { JSX, useCallback, useEffect, useState } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import { BlockCode } from './BlockCode.js'
import { useService } from '../util/contextForServices.js'
enum CopyButtonState {
@ -13,6 +14,7 @@ const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
const inlineDiffService = useService('inlineDiffService')
useEffect(() => {
if (copyButtonState !== CopyButtonState.Copy) {
@ -43,7 +45,8 @@ const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={async () => {
getVSCodeAPI().postMessage({ type: "applyChanges", diffRepr: text })
inlineDiffService.startStreaming('ctrl+l', text)
}}
>
Apply

View file

@ -8,9 +8,10 @@ import { useSidebarState } from '../util/contextForServices.js';
import '../styles.css'
import { SidebarThreadSelector } from './SidebarThreadSelector.js';
import { SidebarChat } from './SidebarChat.js';
const Sidebar = () => {
const [sidebarState, sidebarStateService] = useSidebarState()
const sidebarState = useSidebarState()
const { isHistoryOpen, currentTab: tab } = sidebarState
return <div className='@@void-scope'>
@ -27,7 +28,7 @@ const Sidebar = () => {
</div>
<div className={`${tab === 'chat' ? '' : 'hidden'}`}>
{/* <SidebarChat /> */}
<SidebarChat />
</div>
<div className={`${tab === 'settings' ? '' : 'hidden'}`}>

View file

@ -119,17 +119,16 @@ export const SidebarChat = () => {
// ----- HIGHER STATE -----
// sidebar state
const [sidebarState, sidebarStateService] = useSidebarState()
const { isHistoryOpen, currentTab: tab } = sidebarState
const sidebarStateService = useService('sidebarStateService')
sidebarStateService.onDidFocusChat(() => { chatInputRef.current?.focus() })
sidebarStateService.onDidBlurChat(() => { chatInputRef.current?.blur() })
// config state
const [configState, configStateService] = useConfigState()
const configState = useConfigState()
const { voidConfig } = configState
// threads state
const [threadsState, threadsStateService] = useThreadsState()
const threadsStateService = useService('threadsStateService')
// ----- SIDEBAR CHAT state (local) -----
// state of current message

View file

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { useConfigState } from '../util/contextForServices.js';
import { useConfigState, useService } from '../util/contextForServices.js';
import { IVoidConfigStateService, nonDefaultConfigFields, PartialVoidConfig, VoidConfig, VoidConfigField, VoidConfigInfo, SetFieldFnType, ConfigState } from '../../../registerConfig.js';
@ -61,7 +61,9 @@ const SettingOfFieldAndParam = ({ field, param, configState, configStateService
export const SidebarSettings = () => {
const [configState, configStateService] = useConfigState()
const configState = useConfigState()
const configStateService = useService('configStateService')
const { voidConfig } = configState
const current_field = voidConfig.default['whichApi'] as VoidConfigField

View file

@ -1,5 +1,5 @@
// import React from "react";
import { useSidebarState, useThreadsState } from '../util/contextForServices.js';
import React from "react";
import { useService, useThreadsState } from '../util/contextForServices.js';
const truncate = (s: string) => {
@ -12,9 +12,9 @@ const truncate = (s: string) => {
export const SidebarThreadSelector = () => {
const [threadsState, threadsStateService] = useThreadsState()
const [sidebarState, sidebarStateService] = useSidebarState()
const threadsState = useThreadsState()
const threadsStateService = useService('threadsStateService')
const sidebarStateService = useService('sidebarStateService')
const { allThreads } = threadsState

View file

@ -1,15 +1,20 @@
import React, { createContext, useContext, useEffect, useState } from 'react'
import { ReactServicesType, VoidSidebarState } from '../../../registerSidebar.js';
import { ConfigState } from '../../../registerConfig.js';
import { ThreadsState } from '../../../registerThreads.js';
const AccessorContext = createContext<ReactServicesType | undefined>(undefined)
export const AccessorProvider = ({ children, services }: { children: React.ReactNode; services: ReactServicesType }) => {
registerStateListeners(services)
return <AccessorContext.Provider value={services}>
{children}
</AccessorContext.Provider>
}
// -- services --
const useServices = (): ReactServicesType => {
const context = useContext(AccessorContext)
if (context === undefined) {
@ -18,33 +23,76 @@ const useServices = (): ReactServicesType => {
return context;
}
// -- these use useServices() --
export const useService = <T extends keyof ReactServicesType,>(serviceName: T) => {
const services = useServices()
return services[serviceName] as ReactServicesType[T]
}
// -- state of services --
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but here, useEffect mounts too late and misses initial state changes
let sidebarState: VoidSidebarState | null = null
let configState: ConfigState | null = null
let threadsState: ThreadsState | null = null
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
const configStateListeners: Set<(s: ConfigState) => void> = new Set()
const threadsStateListeners: Set<(s: ThreadsState) => void> = new Set()
let isRegistered = false
const registerStateListeners = (context: ReactServicesType) => {
if (isRegistered) return
isRegistered = true
const { sidebarStateService, configStateService, threadsStateService, } = context
sidebarState = sidebarStateService.state
sidebarStateService.onDidChangeState(() => {
sidebarState = sidebarStateService.state
sidebarStateListeners.forEach(l => l(sidebarState))
})
configState = configStateService.state
configStateService.onDidChangeState(() => {
configState = configStateService.state
configStateListeners.forEach(l => l(configState))
})
threadsState = threadsStateService.state
threadsStateService.onDidChangeCurrentThread(() => {
threadsState = threadsStateService.state
threadsStateListeners.forEach(l => l(threadsState))
})
}
// track the config state using React state so visual updates happen
export const useSidebarState = () => {
const { sidebarStateService } = useServices()
const [sidebarState, setSideBarState] = useState<VoidSidebarState>(sidebarStateService.state)
useEffect(() => { sidebarStateService.onDidChangeState(() => setSideBarState(sidebarStateService.state)) }, [sidebarStateService])
return [sidebarState, sidebarStateService] as const
const [s, ss] = useState(sidebarState)
useEffect(() => {
ss(sidebarState)
sidebarStateListeners.add(ss)
return () => { sidebarStateListeners.delete(ss) }
}, [ss])
return s
}
export const useConfigState = () => {
const { configStateService } = useServices()
const [configState, setConfigState] = useState<ConfigState>(configStateService.state)
useEffect(() => { configStateService.onDidChangeState(() => setConfigState(configStateService.state)) }, [configStateService])
return [configState, configStateService] as const
const [s, ss] = useState(configState)
useEffect(() => {
ss(configState)
configStateListeners.add(ss)
return () => { configStateListeners.delete(ss) }
}, [ss])
return s
}
export const useThreadsState = () => {
const { threadsStateService } = useServices()
const [threadsState, setThreadsState] = useState(threadsStateService.state)
useEffect(() => { threadsStateService.onDidChangeCurrentThread(() => setThreadsState(threadsStateService.state)) }, [threadsStateService])
return [threadsState, threadsStateService] as const
const [s, ss] = useState(threadsState)
useEffect(() => {
ss(threadsState)
threadsStateListeners.add(ss)
return () => { threadsStateListeners.delete(ss) }
}, [ss])
return s
}
// -- other services --
type PublicServiceName = 'fileService'
export const useService = (serviceName: Extract<keyof ReactServicesType, PublicServiceName>) => {
const services = useServices()
return services[serviceName]
}

View file

@ -1,6 +1,4 @@
import posthog from 'posthog-js';
export { posthog }

View file

@ -93,6 +93,8 @@ type StreamingState = {
export interface IInlineDiffsService {
readonly _serviceBrand: undefined;
startStreaming(type: 'ctrl+k' | 'ctrl+l', userMessage: string): void;
}
export const IInlineDiffsService = createDecorator<IInlineDiffsService>('inlineDiffsService');

View file

@ -43,6 +43,7 @@ import mountFn from './react/out/sidebar-tsx/Sidebar.js';
import { IVoidConfigStateService } from './registerConfig.js';
import { IFileService } from '../../../../platform/files/common/files.js';
import { IInlineDiffsService } from './registerInlineDiffs.js';
// import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
@ -59,6 +60,7 @@ export type ReactServicesType = {
configStateService: IVoidConfigStateService;
threadsStateService: IThreadHistoryService;
fileService: IFileService;
inlineDiffService: IInlineDiffsService;
}
// ---------- Define viewpane ----------
@ -101,6 +103,7 @@ class VoidSidebarViewPane extends ViewPane {
sidebarStateService: accessor.get(IVoidSidebarStateService),
threadsStateService: accessor.get(IThreadHistoryService),
fileService: accessor.get(IFileService),
inlineDiffService: accessor.get(IInlineDiffsService),
}
mountFn(root, services);
});
@ -288,7 +291,7 @@ registerAction2(class extends Action2 {
}
async run(accessor: ServicesAccessor): Promise<void> {
const stateService = accessor.get(IVoidSidebarStateService)
stateService.setState({ isHistoryOpen: !stateService.state.isHistoryOpen })
stateService.setState({ isHistoryOpen: !stateService.state.isHistoryOpen, currentTab: 'chat' })
stateService.fireBlurChat()
}
})