add editor pane

This commit is contained in:
Andrew Pareles 2024-12-15 00:30:21 -08:00
parent f21ef61677
commit 7dda4bdc08
21 changed files with 265 additions and 117 deletions

View file

@ -0,0 +1,15 @@
// ---------- common ----------
// llmMessage
import '../common/llmMessageService.js'
// voidConfig
import '../common/voidConfigService.js'
// refreshModel
import '../common/refreshModelService.js'
// metrics
import '../common/metricsService.js'

View file

@ -1,11 +0,0 @@
// llmMessage
import './llmMessageService.js'
// voidConfig
import './voidConfigService.js'
// refreshModel
import './refreshModelService.js'
// metrics
import './metricsService.js'

View file

@ -3,7 +3,7 @@
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { diffLines } from './react/out/util/diffLines.js'
import { diffLines } from './react/out/diff/index.js'
export type ComputedDiff = {
type: 'edit';

View file

@ -15,11 +15,11 @@ import { useSidebarState } from '../util/services.js';
import '../styles.css'
import { SidebarThreadSelector } from './SidebarThreadSelector.js';
import { SidebarChat } from './SidebarChat.js';
import { ModelSelectionSettings } from './ModelSelectionSettings.js';
import { VoidProviderSettings } from './VoidProviderSettings.js';
import { ModelSelectionSettings } from '../void-settings-tsx/ModelSelectionSettings.js';
import { VoidProviderSettings } from '../void-settings-tsx/VoidProviderSettings.js';
import ErrorBoundary from './ErrorBoundary.js';
const Sidebar = () => {
export const Sidebar = () => {
const sidebarState = useSidebarState()
const { isHistoryOpen, currentTab: tab } = sidebarState
@ -43,16 +43,6 @@ const Sidebar = () => {
<ErrorBoundary>
<SidebarChat />
</ErrorBoundary>
<ErrorBoundary>
<ModelSelectionSettings />
</ErrorBoundary>
</div>
<div className={`${tab === 'settings' ? '' : 'hidden'}`}>
<ErrorBoundary>
<VoidProviderSettings />
</ErrorBoundary>
</div>
</div>
@ -61,7 +51,3 @@ const Sidebar = () => {
}
const mountFn = mountFnGenerator(Sidebar)
export default mountFn

View file

@ -22,7 +22,7 @@ import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platf
import { getCmdKey } from '../../../getCmdKey.js'
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { VoidInputBox } from './inputs.js';
import { ModelSelectionOfFeature } from './ModelSelectionSettings.js';
import { ModelSelectionOfFeature } from '../void-settings-tsx/ModelSelectionSettings.js';
const IconX = ({ size, className = '' }: { size: number, className?: string }) => {
@ -202,7 +202,7 @@ const ChatBubble = ({ chatMessage }: {
}
return <div className={`${role === 'user' ? 'text-right' : 'text-left'}`}>
<div className={`inline-block p-2 rounded-lg space-y-2 ${role === 'user' ? 'bg-vscode-input-bg text-vscode-input-fg' : ''} max-w-full`}>
<div className={`inline-block p-2 rounded-lg space-y-2 ${role === 'user' ? 'bg-vscode-input-bg text-vscode-input-fg' : ''} max-w-full overflow-auto`}>
{chatbubbleContents}
</div>
</div>

View file

@ -0,0 +1,6 @@
import { mountFnGenerator } from '../util/mountFnGenerator.js'
import { Sidebar } from './Sidebar.js'
export const mountSidebar = mountFnGenerator(Sidebar)

View file

@ -5,9 +5,9 @@
import React, { useCallback, useEffect, useRef } from 'react';
import { useService } from '../util/services.js';
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { defaultCheckboxStyles, defaultInputBoxStyles, defaultSelectBoxStyles, defaultToggleStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
import { SelectBox, unthemedSelectBoxStyles } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { defaultInputBoxStyles, defaultSelectBoxStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
@ -125,7 +125,7 @@ export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectB
instance.render(containerRef.current)
disposables.push(
instance.onDidSelect(e => { onChangeSelection(options[e.index].value ); })
instance.onDidSelect(e => { onChangeSelection(options[e.index].value); })
)
if (onCreateInstance) {

View file

@ -5,8 +5,8 @@
import React, { useEffect, useState } from 'react';
import * as ReactDOM from 'react-dom/client'
import { ReactServicesType, VoidSidebarState } from '../../../registerSidebar.js';
import { _registerServices } from './services.js';
import { ReactServicesType } from '../../../reactServices.js';
export const mountFnGenerator = (Component: React.FC) => (rootElement: HTMLElement, services: ReactServicesType) => {

View file

@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { useState, useEffect } from 'react'
import { VoidSidebarState, ReactServicesType } from '../../../registerSidebar.js'
import { VoidSidebarState } from '../../../registerSidebar.js'
import { ThreadsState } from '../../../registerThreads.js'
import { SettingsOfProvider } from '../../../../../../../platform/void/common/voidConfigTypes.js'
import { RefreshModelState } from '../../../../../../../platform/void/common/refreshModelService.js'
import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
import { ReactServicesType } from '../../../reactServices.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes

View file

@ -7,7 +7,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { FeatureName, featureNames, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidConfigTypes.js'
import { dummyModelData } from '../../../../../../../platform/void/common/voidConfigModelDefaults.js'
import { useConfigState, useRefreshModelState, useService } from '../util/services.js'
import { VoidSelectBox } from './inputs.js'
import { VoidSelectBox } from '../sidebar-tsx/inputs.js'
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'

View file

@ -5,10 +5,10 @@
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
import { titleOfProviderName, displayInfoOfSettingName, ProviderName, providerNames, featureNames, SettingsOfProvider, SettingName, defaultVoidProviderState } from '../../../../../../../platform/void/common/voidConfigTypes.js'
import { VoidInputBox } from './inputs.js'
import { VoidInputBox } from '../sidebar-tsx/inputs.js'
import { useConfigState, useService } from '../util/services.js'
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
import ErrorBoundary from './ErrorBoundary.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
const Setting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => {

View file

@ -0,0 +1,13 @@
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
import { VoidProviderSettings } from './VoidProviderSettings.js'
export const VoidSettings = () => {
return <>
<ErrorBoundary>
<VoidProviderSettings />
</ErrorBoundary>
</>
}

View file

@ -0,0 +1,6 @@
import { mountFnGenerator } from '../util/mountFnGenerator.js'
import { VoidSettings } from './VoidSettings.js'
export const mountVoidSettings = mountFnGenerator(VoidSettings)

View file

@ -7,8 +7,9 @@ import { defineConfig } from 'tsup'
export default defineConfig({
entry: [
'./src2/sidebar-tsx/Sidebar.tsx',
'./src2/util/diffLines.tsx',
'./src2/sidebar-tsx/index.tsx',
'./src2/void-settings-tsx/index.tsx',
'./src2/diff/index.tsx',
],
outDir: './out',
format: ['esm'],

View file

@ -0,0 +1,51 @@
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
import { IContextViewService, IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
import { IFileService } from '../../../../platform/files/common/files.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js';
import { IRefreshModelService } from '../../../../platform/void/common/refreshModelService.js';
import { IVoidConfigStateService } from '../../../../platform/void/common/voidConfigService.js';
import { IInlineDiffsService } from './registerInlineDiffs.js';
import { IVoidSidebarStateService } from './registerSidebar.js';
import { IThreadHistoryService } from './registerThreads.js';
export type ReactServicesType = {
sidebarStateService: IVoidSidebarStateService;
configStateService: IVoidConfigStateService;
threadsStateService: IThreadHistoryService;
fileService: IFileService;
modelService: IModelService;
inlineDiffService: IInlineDiffsService;
llmMessageService: ILLMMessageService;
clipboardService: IClipboardService;
refreshModelService: IRefreshModelService;
themeService: IThemeService,
hoverService: IHoverService,
contextViewService: IContextViewService;
contextMenuService: IContextMenuService;
}
export const getReactServices = (accessor: ServicesAccessor): ReactServicesType => {
return {
configStateService: accessor.get(IVoidConfigStateService),
sidebarStateService: accessor.get(IVoidSidebarStateService),
threadsStateService: accessor.get(IThreadHistoryService),
fileService: accessor.get(IFileService),
modelService: accessor.get(IModelService),
inlineDiffService: accessor.get(IInlineDiffsService),
llmMessageService: accessor.get(ILLMMessageService),
clipboardService: accessor.get(IClipboardService),
themeService: accessor.get(IThemeService),
hoverService: accessor.get(IHoverService),
refreshModelService: accessor.get(IRefreshModelService),
contextViewService: accessor.get(IContextViewService),
contextMenuService: accessor.get(IContextMenuService),
}
}

View file

@ -150,23 +150,23 @@ registerAction2(class extends Action2 {
}
})
// Settings (API config) menu button
registerAction2(class extends Action2 {
constructor() {
super({
id: 'void.viewSettings',
title: 'Void Settings',
icon: { id: 'settings-gear' },
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }]
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const stateService = accessor.get(IVoidSidebarStateService)
const metricsService = accessor.get(IMetricsService)
// // Settings (API config) menu button
// registerAction2(class extends Action2 {
// constructor() {
// super({
// id: 'void.viewSettings',
// title: 'Void Settings',
// icon: { id: 'settings-gear' },
// menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }]
// });
// }
// async run(accessor: ServicesAccessor): Promise<void> {
// const stateService = accessor.get(IVoidSidebarStateService)
// const metricsService = accessor.get(IMetricsService)
metricsService.capture('Chat Navigation', { type: 'Settings' })
// metricsService.capture('Chat Navigation', { type: 'Settings' })
stateService.setState({ isHistoryOpen: false, currentTab: stateService.state.currentTab === 'settings' ? 'chat' : 'settings' })
stateService.fireBlurChat()
}
})
// stateService.setState({ isHistoryOpen: false, currentTab: stateService.state.currentTab === 'settings' ? 'chat' : 'settings' })
// stateService.fireBlurChat()
// }
// })

View file

@ -18,7 +18,7 @@ import * as nls from '../../../../nls.js';
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
// import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPane.js';
@ -27,51 +27,26 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte
import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { IThreadHistoryService } from './registerThreads.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import mountFn from './react/out/sidebar-tsx/Sidebar.js';
import { mountSidebar } from './react/out/sidebar-tsx/index.js';
import { IVoidConfigStateService } from '../../../../platform/void/common/voidConfigService.js';
import { IFileService } from '../../../../platform/files/common/files.js';
import { IInlineDiffsService } from './registerInlineDiffs.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js';
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { IRefreshModelService } from '../../../../platform/void/common/refreshModelService.js';
import { getReactServices } from './reactServices.js';
// compare against search.contribution.ts and debug.contribution.ts, scm.contribution.ts (source control)
export type VoidSidebarState = {
isHistoryOpen: boolean;
currentTab: 'chat' | 'settings';
}
export type ReactServicesType = {
sidebarStateService: IVoidSidebarStateService;
configStateService: IVoidConfigStateService;
threadsStateService: IThreadHistoryService;
fileService: IFileService;
modelService: IModelService;
inlineDiffService: IInlineDiffsService;
llmMessageService: ILLMMessageService;
clipboardService: IClipboardService;
refreshModelService: IRefreshModelService;
themeService: IThemeService,
hoverService: IHoverService,
contextViewService: IContextViewService;
contextMenuService: IContextMenuService;
currentTab: 'chat';
}
// ---------- Define viewpane ----------
@ -104,24 +79,10 @@ class VoidSidebarViewPane extends ViewPane {
// gets set immediately
this.instantiationService.invokeFunction(accessor => {
const services: ReactServicesType = {
configStateService: accessor.get(IVoidConfigStateService),
sidebarStateService: accessor.get(IVoidSidebarStateService),
threadsStateService: accessor.get(IThreadHistoryService),
fileService: accessor.get(IFileService),
modelService: accessor.get(IModelService),
inlineDiffService: accessor.get(IInlineDiffsService),
llmMessageService: accessor.get(ILLMMessageService),
clipboardService: accessor.get(IClipboardService),
themeService: accessor.get(IThemeService),
hoverService: accessor.get(IHoverService),
refreshModelService: accessor.get(IRefreshModelService),
contextViewService: accessor.get(IContextViewService),
contextMenuService: accessor.get(IContextMenuService),
}
const services = getReactServices(accessor)
// mount react
const disposables: IDisposable[] | undefined = mountFn(parent, services);
const disposables: IDisposable[] | undefined = mountSidebar(parent, services);
disposables?.forEach(d => this._register(d))
});
}
@ -143,12 +104,12 @@ export const VOID_VIEW_ID = VOID_VIEW_CONTAINER_ID // simplicity
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
const viewContainer = viewContainerRegistry.registerViewContainer({
id: VOID_VIEW_CONTAINER_ID,
title: nls.localize2('void', 'Void Chat'), // this is used to say "Void" (Ctrl + L)
title: nls.localize2('void chat', 'Void Chat'), // this is used to say "Void" (Ctrl + L)
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VOID_VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }]),
hideIfEmpty: false,
// icon: voidViewIcon,
order: 1,
}, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true, });
}, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true, isDefault: true });
@ -158,17 +119,17 @@ viewsRegistry.registerViews([{
id: VOID_VIEW_ID,
hideByDefault: false, // start open
// containerIcon: voidViewIcon,
name: nls.localize2('void chat', "Chat"), // this says ... : CHAT
name: nls.localize2('chat', 'Chat'), // this says ... : CHAT
ctorDescriptor: new SyncDescriptor(VoidSidebarViewPane),
canToggleVisibility: false,
canMoveView: true,
openCommandActionDescriptor: {
id: viewContainer.id,
keybindings: {
primary: KeyMod.CtrlCmd | KeyCode.KeyL,
},
order: 1
},
// openCommandActionDescriptor: {
// id: viewContainer.id,
// keybindings: {
// primary: KeyMod.CtrlCmd | KeyCode.KeyL,
// },
// order: 1
// },
}], viewContainer);

View file

@ -20,3 +20,6 @@ import './registerAutocomplete.js'
// register css
import './media/void.css'
// settings pane
import './voidSettingsEditorPane.js'

View file

@ -0,0 +1,116 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { EditorInput } from '../../../common/editor/editorInput.js';
import * as nls from '../../../../nls.js';
import { EditorExtensions } from '../../../common/editor.js';
import { EditorPane } from '../../../browser/parts/editor/editorPane.js';
import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
import { IStorageService } from '../../../../platform/storage/common/storage.js';
import { Dimension } from '../../../../base/browser/dom.js';
import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js';
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { URI } from '../../../../base/common/uri.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { mountVoidSettings } from './react/out/void-settings-tsx/index.js'
import { getReactServices } from './reactServices.js';
// refer to preferences.contribution.ts keybindings editor
export class VoidEditorInput extends EditorInput {
static readonly ID: string = 'workbench.input.void.settings';
readonly resource = URI.from({
scheme: 'void-editor-settings',
path: 'void-settings' // Give it a unique path
});
constructor() {
super();
}
override get typeId(): string {
return VoidEditorInput.ID;
}
override getName(): string {
return nls.localize('voidSettingsInputsName', "Void Settings");
}
}
class MyCustomPane extends EditorPane {
static readonly ID = 'workbench.test.myCustomPane';
constructor(
group: IEditorGroup,
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super(MyCustomPane.ID, group, telemetryService, themeService, storageService);
}
protected createEditor(container: HTMLElement): void {
this.instantiationService.invokeFunction(accessor => {
const services = getReactServices(accessor)
mountVoidSettings(container, services);
})
}
layout(dimension: Dimension): void {
const container = this.getContainer();
if (!container) return;
container.style.width = `${dimension.width}px`;
container.style.height = `${dimension.height}px`;
}
}
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
EditorPaneDescriptor.create(MyCustomPane, MyCustomPane.ID, nls.localize('MyCustomPane', "CustomPane")),
[new SyncDescriptor(VoidEditorInput)]
);
// Register the action
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.openVoidEditor',
title: 'Open Void Settings',
keybinding: {
when: ContextKeyExpr.true(),
primary: KeyMod.CtrlCmd | KeyCode.KeyE,
weight: KeybindingWeight.WorkbenchContrib
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const editorService = accessor.get(IEditorService);
const instantiationService = accessor.get(IInstantiationService);
const input = instantiationService.createInstance(VoidEditorInput);
await editorService.openEditor(input);
}
});

View file

@ -17,7 +17,7 @@ import './browser/workbench.contribution.js';
//#region --- Void
// Void added this:
import './contrib/void/browser/void.contribution.js';
import '../platform/void/common/void.contribution.js';
import '../platform/void/browser/void.contribution.js';
//#endregion