{/*
{
const tabs = ['chat', 'settings', 'threadSelector']
@@ -27,24 +31,31 @@ const Sidebar = () => {
sidebarStateService.setState({ currentTab: tabs[(index + 1) % tabs.length] as any })
}}>clickme {tab} */}
-
-
+
+
+
+
-
-
+
+
+
+
+
+ {/*
+
+ */}
-
-
-
+ {/*
+
+
+
+
*/}
+
}
-
-const mountFn = mountFnGenerator(Sidebar)
-export default mountFn
-
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 fb289f37..75cc5419 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
@@ -1,25 +1,163 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
- * Void Editor additions licensed under the AGPLv3 License.
+ * 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 { useConfigState, useService, useThreadsState } from '../util/services.js';
-import { generateDiffInstructions } from '../../../prompt/systemPrompts.js';
-import { userInstructionsStr } from '../../../prompt/stringifyFiles.js';
-import { CodeSelection, CodeStagingSelection } from '../../../registerThreads.js';
+import { useSettingsState, useService, useSidebarState, useThreadsState } from '../util/services.js';
+import { ChatMessage, CodeSelection, CodeStagingSelection } from '../../../threadHistoryService.js';
import { BlockCode } from '../markdown/BlockCode.js';
-import { MarkdownRender } from '../markdown/MarkdownRender.js';
+import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
import { IModelService } from '../../../../../../../editor/common/services/model.js';
import { URI } from '../../../../../../../base/common/uri.js';
import { EndOfLinePreference } from '../../../../../../../editor/common/model.js';
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
-import { ErrorDisplay } from '../util/ErrorDisplay.js';
-import { LLMMessageServiceParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
+import { ErrorDisplay } from './ErrorDisplay.js';
+import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
+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 }) => {
+ return (
+
+
+
+ );
+};
+
+
+const IconArrowUp = ({ size, className = '' }: { size: number, className?: string }) => {
+ return (
+
+
+
+
+ );
+};
+
+
+const IconSquare = ({ size, className = '' }: { size: number, className?: string }) => {
+ return (
+
+
+
+ );
+};
+
+type ButtonProps = ButtonHTMLAttributes
+export const ButtonSubmit = ({ className, disabled, ...props }: ButtonProps & Required>) => {
+ return
+
+
+}
+
+export const ButtonStop = ({ className, ...props }: ButtonHTMLAttributes) => {
+
+ return
+
+
+}
+
+
+const ScrollToBottomContainer = ({ children, className, style }: { children: React.ReactNode, className?: string, style?: React.CSSProperties }) => {
+ const [isAtBottom, setIsAtBottom] = useState(true); // Start at bottom
+ const divRef = useRef(null);
+
+ const scrollToBottom = () => {
+ if (divRef.current) {
+ divRef.current.scrollTop = divRef.current.scrollHeight;
+ }
+ };
+
+ const onScroll = () => {
+ const div = divRef.current;
+ if (!div) return;
+
+ const isBottom = Math.abs(
+ div.scrollHeight - div.clientHeight - div.scrollTop
+ ) < 4;
+
+ setIsAtBottom(isBottom);
+ };
+
+ // When children change (new messages added)
+ useEffect(() => {
+ if (isAtBottom) {
+ scrollToBottom();
+ }
+ }, [children, isAtBottom]); // Dependency on children to detect new messages
+
+ // Initial scroll to bottom
+ useEffect(() => {
+ scrollToBottom();
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
-// import { } from '@vscode/webview-ui-toolkit/react';
// read files from VSCode
const VSReadFile = async (modelService: IModelService, uri: URI): Promise => {
@@ -29,27 +167,6 @@ const VSReadFile = async (modelService: IModelService, uri: URI): Promise {
// 'unixify' path
pathStr = pathStr.replace(/[/\\]+/g, '/') // replace any / or \ or \\ with /
@@ -62,63 +179,85 @@ export const SelectedFiles = (
| { type: 'past', selections: CodeSelection[] | null; setStaging?: undefined }
| { type: 'staging', selections: CodeStagingSelection[] | null; setStaging: ((files: CodeStagingSelection[]) => void) }
) => {
+
+ // index -> isOpened
+ const [selectionIsOpened, setSelectionIsOpened] = useState<(boolean)[]>(selections?.map(() => false) ?? [])
+
return (
!!selections && selections.length !== 0 && (
-
- {selections.map((selection, i) => (
-
+
+ {selections.map((selection, i) => {
-
{
- if (type !== 'staging') return
- setStaging([...selections.slice(0, i), ...selections.slice(i + 1, Infinity)])
- }}
+ const showSelectionText = selection.selectionStr && selectionIsOpened[i]
+
+ return (
+
-
{getBasename(selection.fileURI.fsPath)}
-
- {/* X button */}
- {type === 'staging' &&
-
-
-
- }
-
- {/* selection text */}
- {type === 'staging' && selection.selectionStr &&
{
- setStaging([...selections.slice(0, i), { ...selection, selectionStr: null }, ...selections.slice(i + 1, Infinity)])
+ setSelectionIsOpened(s => {
+ const newS = [...s]
+ newS[i] = !newS[i]
+ return newS
+ });
}}
- className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
- >Remove
- )} />}
-
- ))}
+ >
+
+ {/* file name */}
+ {getBasename(selection.fileURI.fsPath)}
+ {/* selection range */}
+ {selection.selectionStr !== null ? ` (${selection.range.startLineNumber}-${selection.range.endLineNumber})` : ''}
+
+
+ {/* type of selection */}
+ {selection.selectionStr !== null ? 'Selection' : 'File'}
+
+ {/* X button */}
+ {type === 'staging' && // hoveredIdx === i
+ {
+ e.stopPropagation();
+ if (type !== 'staging') return;
+ setStaging([...selections.slice(0, i), ...selections.slice(i + 1)])
+ setSelectionIsOpened(o => [...o.slice(0, i), ...o.slice(i + 1)])
+ }}
+ >
+
+
+ }
+
+ {/* selection text */}
+ {showSelectionText &&
+
+
+
+ }
+
+ )
+ })}
)
)
}
-const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
+const ChatBubble = ({ chatMessage }: {
+ chatMessage: ChatMessage
+}) => {
const role = chatMessage.role
- const children = chatMessage.displayContent
- if (!children)
+ if (!chatMessage.displayContent)
return null
let chatbubbleContents: React.ReactNode
@@ -126,15 +265,15 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
if (role === 'user') {
chatbubbleContents = <>
- {children}
+ {chatMessage.displayContent}
>
}
else if (role === 'assistant') {
- chatbubbleContents = // sectionsHTML
+ chatbubbleContents = // sectionsHTML
}
return
-
@@ -144,7 +283,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
export const SidebarChat = () => {
- const chatInputRef = useRef
(null)
+ const inputBoxRef: React.MutableRefObject = useRef(null);
const modelService = useService('modelService')
@@ -154,36 +293,36 @@ export const SidebarChat = () => {
useEffect(() => {
const disposables: IDisposable[] = []
disposables.push(
- sidebarStateService.onDidFocusChat(() => { chatInputRef.current?.focus() }),
- sidebarStateService.onDidBlurChat(() => { chatInputRef.current?.blur() })
+ sidebarStateService.onDidFocusChat(() => { inputBoxRef.current?.focus() }),
+ sidebarStateService.onDidBlurChat(() => { inputBoxRef.current?.blur() })
)
return () => disposables.forEach(d => d.dispose())
- }, [sidebarStateService, chatInputRef])
-
- // config state
- const configState = useConfigState()
- const { voidConfig } = configState
+ }, [sidebarStateService, inputBoxRef])
// threads state
const threadsState = useThreadsState()
const threadsStateService = useService('threadsStateService')
+ const llmMessageService = useService('llmMessageService')
+
// ----- SIDEBAR CHAT state (local) -----
- // state of current message
- const [instructions, setInstructions] = useState('') // the user's instructions
// state of chat
- const [messageStream, setMessageStream] = useState('')
+ const [messageStream, setMessageStream] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const latestRequestIdRef = useRef(null)
- const [latestError, setLatestError] = useState(null)
+ const [latestError, setLatestError] = useState[0] | null>(null)
- const sendLLMMessageService = useService('sendLLMMessageService')
- const isDisabled = !instructions
+ // state of current message
+ const [instructions, setInstructions] = useState('') // the user's instructions
+ const isDisabled = !instructions.trim()
+ const [formHeight, setFormHeight] = useState(0) // TODO should use resize observer instead
+ const [sidebarHeight, setSidebarHeight] = useState(0)
+ const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions])
+
- const formRef = useRef(null)
const onSubmit = async (e: FormEvent) => {
e.preventDefault()
@@ -191,165 +330,226 @@ export const SidebarChat = () => {
if (isLoading) return
- const currSelns = threadsStateService.state._currentStagingSelections
+
+ const currSelns = threadsStateService.state._currentStagingSelections ?? []
const selections = !currSelns ? null : await Promise.all(
currSelns.map(async (sel) => ({ ...sel, content: await VSReadFile(modelService, sel.fileURI) }))
).then(
(files) => files.filter(file => file.content !== null) as CodeSelection[]
)
+
+ // // TODO don't save files to the thread history
+ // const selectedSnippets = currSelns.filter(sel => sel.selectionStr !== null)
+ // const selectedFiles = await Promise.all( // do not add these to the context history
+ // currSelns.filter(sel => sel.selectionStr === null)
+ // .map(async (sel) => ({ ...sel, content: await VSReadFile(modelService, sel.fileURI) }))
+ // ).then(
+ // (files) => files.filter(file => file.content !== null) as CodeSelection[]
+ // )
+ // const contextToSendToLLM = ''
+ // const contextToAddToHistory = ''
+
+
// add system message to chat history
- const systemPromptElt: ChatMessage = { role: 'system', content: generateDiffInstructions }
+ const systemPromptElt: ChatMessage = { role: 'system', content: ctrlLSystem }
threadsStateService.addMessageToCurrentThread(systemPromptElt)
- const userContent = userInstructionsStr(instructions, selections)
- const newHistoryElt: ChatMessage = { role: 'user', content: userContent, displayContent: instructions, selections }
- threadsStateService.addMessageToCurrentThread(newHistoryElt)
+ // add user's message to chat history
+ 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
-
// send message to LLM
+ setIsLoading(true) // must come before message is sent so onError will work
+ setLatestError(null)
+ if (inputBoxRef.current) {
+ inputBoxRef.current.value = ''; // this triggers onDidChangeText
+ inputBoxRef.current.blur();
+ }
- const object: LLMMessageServiceParams = {
+ const object: ServiceSendLLMMessageParams = {
logging: { loggingName: 'Chat' },
- messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })),],
+ messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content || '(null)' })),],
onText: ({ newText, fullText }) => setMessageStream(fullText),
onFinalMessage: ({ fullText: content }) => {
console.log('chat: running final message')
// add assistant's message to chat history, and clear selection
- const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content }
- threadsStateService.addMessageToCurrentThread(newHistoryElt)
- setMessageStream('')
+ const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null }
+ threadsStateService.addMessageToCurrentThread(assistantHistoryElt)
+ setMessageStream(null)
setIsLoading(false)
},
- onError: ({ error }) => {
- console.log('chat: running error', error)
+ onError: ({ message, fullError }) => {
+ console.log('chat: running error', message, fullError)
// add assistant's message to chat history, and clear selection
- let content = messageStream; // just use the current content
- const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content, }
- threadsStateService.addMessageToCurrentThread(newHistoryElt)
+ let content = messageStream ?? ''; // just use the current content
+ const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null, }
+ threadsStateService.addMessageToCurrentThread(assistantHistoryElt)
setMessageStream('')
setIsLoading(false)
- setLatestError(error)
+ setLatestError({ message, fullError })
},
- voidConfig,
+ featureName: 'Ctrl+L',
+
}
- const latestRequestId = sendLLMMessageService.sendLLMMessage(object)
+ const latestRequestId = llmMessageService.sendLLMMessage(object)
latestRequestIdRef.current = latestRequestId
-
- setIsLoading(true)
- setInstructions('');
- formRef.current?.reset(); // reset the form's text when clear instructions or unexpected behavior happens
threadsStateService.setStaging([]) // clear staging
- setLatestError(null)
}
const onAbort = () => {
- // abort the LLM
+ // abort the LLM call
if (latestRequestIdRef.current)
- sendLLMMessageService.abort(latestRequestIdRef.current)
+ llmMessageService.abort(latestRequestIdRef.current)
// if messageStream was not empty, add it to the history
- const llmContent = messageStream || '(null)'
- const newHistoryElt: ChatMessage = { role: 'assistant', content: llmContent, displayContent: messageStream, }
- threadsStateService.addMessageToCurrentThread(newHistoryElt)
+ const llmContent = messageStream ?? ''
+ const assistantHistoryElt: ChatMessage = { role: 'assistant', content: llmContent, displayContent: messageStream || null, }
+ threadsStateService.addMessageToCurrentThread(assistantHistoryElt)
setMessageStream('')
setIsLoading(false)
}
-
const currentThread = threadsStateService.getCurrentThread(threadsState)
const selections = threadsState._currentStagingSelections
- return <>
-
+ const previousMessages = currentThread?.messages ?? []
+
+ // const [_test_messages, _set_test_messages] = useState
([])
+
+ return { if (ref) { setSidebarHeight(ref.clientHeight); } }}
+ className={`w-full h-full`}
+ >
+
{/* previous messages */}
- {currentThread !== null && currentThread?.messages.map((message, i) =>
-
- )}
+ {previousMessages.map((message, i) => )}
+
{/* message stream */}
-
-
- {/* chatbar */}
-
- {/* selection */}
-
-
-
- {/* selections */}
- {(selections && selections.length !== 0) &&
-
-
}
+
-
-
+
+ {/* input box */}
+
0 ? 'absolute bottom-0' : ''}`}
+ >
+
{ if (ref) { setFormHeight(ref.clientHeight); } }}
+ className={`
+ flex flex-col gap-2 p-2 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()
+ }
+ }}
+ >
+ {/* top row */}
+ <>
+ {/* selections */}
+ {(selections && selections.length !== 0) &&
+
+ }
+
+ {/* error message */}
+ {latestError === null ? null :
+ { setLatestError(null) }}
+ showDismiss={true}
+ />
+ }
+ >
+
+ {/* middle row */}
+ `@@[&_textarea]:!void-${style}`) // apply styles to ancestor textarea elements
+ // .join(' ') +
+ // ` outline-none`
+ // .split(' ')
+ // .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-outline-none`
+ }
+ >
+
+ {/* text input */}
+
-
- {/* error message */}
- {latestError === null ? null :
-
{ setLatestError(null) }}
- />}
-
- >
+ {/* bottom row */}
+
+ {/* submit options */}
+
+
+
+
+ {/* submit / stop button */}
+ {isLoading ?
+ // stop button
+
+ :
+ // submit button (up arrow)
+
+ }
+
+
+
+
+
+
}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx
deleted file mode 100644
index d0f91037..00000000
--- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Glass Devtools, Inc. All rights reserved.
- * Void Editor additions licensed under the AGPLv3 License.
- *--------------------------------------------------------------------------------------------*/
-import React, { useEffect, useState } from 'react';
-import { useConfigState, useService } from '../util/services.js';
-import { IVoidConfigStateService, nonDefaultConfigFields, PartialVoidConfig, VoidConfig, VoidConfigField, VoidConfigInfo, SetFieldFnType, ConfigState } from '../../../registerConfig.js';
-
-
-const SettingOfFieldAndParam = ({ field, param, configState, configStateService }:
- { field: VoidConfigField; param: string; configState: ConfigState; configStateService: IVoidConfigStateService }) => {
-
- const { partialVoidConfig } = configState
-
-
- const { enumArr, defaultVal, description } = configStateService.voidConfigInfo[field][param]
- const val = partialVoidConfig[field]?.[param] ?? defaultVal // current value of this item
-
- const updateState = (newValue: string) => { configStateService.setField(field, param, newValue) }
-
- const resetButton = updateState(defaultVal)}
- >
-
-
-
-
- const inputElement = enumArr === undefined ?
- // string
- ( updateState(e.target.value)}
- />)
- :
- // enum
- ( updateState(e.target.value)}
- >
- {enumArr.map((option) => (
-
- {option}
-
- ))}
- )
-
- return
-
{param}
-
{description}
-
- {inputElement}
- {resetButton}
-
-
-}
-
-
-export const SidebarSettings = () => {
-
- const configState = useConfigState()
- const configStateService = useService('configStateService')
-
- const { voidConfig } = configState
- const current_field = voidConfig.default['whichApi'] as VoidConfigField
-
- return (
-
-
- {/* choose the field */}
-
-
-
-
-
-
-
- {/* render all fields, but hide the ones not visible for fast tab switching */}
- {nonDefaultConfigFields.map(field => {
- return
- {Object.keys(configStateService.voidConfigInfo[field]).map((param) => (
-
- ))}
-
- })}
-
- )
-}
-
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx
index 7ae8acef..6a2b1943 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx
@@ -1,7 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
- * Void Editor additions licensed under the AGPLv3 License.
+ * Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
+
import React from "react";
import { useService, useThreadsState } from '../util/services.js';
@@ -23,10 +24,10 @@ export const SidebarThreadSelector = () => {
const { allThreads } = threadsState
// sorted by most recent to least recent
- const sortedThreadIds = Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => allThreads![threadId1].lastModified > allThreads![threadId2].lastModified ? 1 : -1)
+ const sortedThreadIds = Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => allThreads![threadId1].lastModified > allThreads![threadId2].lastModified ? -1 : 1)
return (
-
+
{/* X button at top right */}
@@ -48,7 +49,7 @@ export const SidebarThreadSelector = () => {
{/* a list of all the past threads */}
-
+
{sortedThreadIds.map((threadId) => {
if (!allThreads)
return <>Error: Threads not found.>
@@ -56,21 +57,26 @@ export const SidebarThreadSelector = () => {
let btnStringArr: string[] = []
- let msg1 = truncate(allThreads[threadId].messages[0]?.displayContent ?? '(empty)')
- btnStringArr.push(msg1)
+ const firstMsgIdx = allThreads[threadId].messages.findIndex(msg => msg.role !== 'system' && !!msg.displayContent) ?? ''
+ if (firstMsgIdx !== -1)
+ btnStringArr.push(truncate(allThreads[threadId].messages[firstMsgIdx].displayContent ?? ''))
+ else
+ btnStringArr.push('""')
- let msg2 = truncate(allThreads[threadId].messages[1]?.displayContent ?? '')
- if (msg2)
- btnStringArr.push(msg2)
+ const secondMsgIdx = allThreads[threadId].messages.findIndex((msg, i) => msg.role !== 'system' && !!msg.displayContent && i > firstMsgIdx) ?? ''
+ if (secondMsgIdx !== -1)
+ btnStringArr.push(truncate(allThreads[threadId].messages[secondMsgIdx].displayContent ?? ''))
- btnStringArr.push(allThreads[threadId].messages.length + '')
+ const numMessagesRemaining = allThreads[threadId].messages.filter((msg, i) => msg.role !== 'system' && !!msg.displayContent && i > secondMsgIdx).length
+ if (numMessagesRemaining > 0)
+ btnStringArr.push(numMessagesRemaining + '')
const btnString = btnStringArr.join(' / ')
return (
threadsStateService.switchToThread(pastThread.id)}
title={new Date(pastThread.createdAt).toLocaleString()}
>
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/index.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/index.tsx
new file mode 100644
index 00000000..a174f0ad
--- /dev/null
+++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/index.tsx
@@ -0,0 +1,6 @@
+import { mountFnGenerator } from '../util/mountFnGenerator.js'
+import { Sidebar } from './Sidebar.js'
+
+export const mountSidebar = mountFnGenerator(Sidebar)
+
+
diff --git a/src/vs/workbench/contrib/void/browser/react/src/styles.css b/src/vs/workbench/contrib/void/browser/react/src/styles.css
index 00547a93..31742153 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/styles.css
+++ b/src/vs/workbench/contrib/void/browser/react/src/styles.css
@@ -1,8 +1,26 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Glass Devtools, Inc. All rights reserved.
+ * Void Editor additions licensed under the AGPL 3.0 License.
+ *--------------------------------------------------------------------------------------------*/
+
@tailwind base;
@tailwind components;
@tailwind utilities;
+.select-child-restyle select {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-right: 24px;
+}
+
+* {
+ outline: none !important;
+}
+
+
+
+
/* html {
font-size: var(--vscode-font-size);
}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/ErrorDisplay.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/ErrorDisplay.tsx
deleted file mode 100644
index ab11ff18..00000000
--- a/src/vs/workbench/contrib/void/browser/react/src/util/ErrorDisplay.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import React, { useState } from 'react';
-import { AlertCircle, ChevronDown, ChevronUp, X } from 'lucide-react';
-
-import { getCmdKey } from '../../../getCmdKey.js';
-
-// const opaqueMessage = `\
-// Unfortunately, Void can't see the full error. However, you should be able to find more details by pressing ${getCmdKey()}+Shift+P, typing "Toggle Developer Tools", and looking at the console.\n
-// This error often means you have an incorrect API key. If you're self-hosting your own server, it might mean your CORS headers are off, and you should make sure your server's response has the header "Access-Control-Allow-Origins" set to "*", or at least allows "vscode-file://vscode-app".`
-// if ((error instanceof Error) && (error.cause + '').includes('TypeError: Failed to fetch')) {
-// e = error as any
-// e['Void Team'] = opaqueMessage
-// }
-
-
-type Details = {
- message: string,
- name: string,
- stack: string | null,
- cause: string | null,
- code: string | null,
- additional: Record
-}
-
-// Get detailed error information
-const getErrorDetails = (error: unknown) => {
-
- let details: Details;
-
- let e: Error & { [other: string]: undefined | any }
-
- // If fetch() fails, it gives an opaque message. We add extra details to the error.
- if (error instanceof Error) {
- e = error
- }
- // sometimes error is an object but not an Error
- else if (typeof error === 'object') {
- e = new Error(`The server didn't give a very useful error message. More details below.`, { cause: JSON.stringify(error) })
-
- }
- else {
- e = new Error(String(error))
- }
- // console.log('error display', JSON.stringify(e))
-
- const message = e.message && e.error ?
- (e.message + ':\n' + e.error)
- : e.message || e.error || JSON.stringify(error)
-
- details = {
- name: e.name || 'Error',
- message: message,
- stack: null, // e.stack is ignored because it's ugly and not very useful
- cause: e.cause ? String(e.cause) : null,
- code: e.code || null,
- additional: {}
- }
-
-
- // Collect any additional properties from the e
- for (let prop of Object.getOwnPropertyNames(e).filter((prop) => !Object.keys(details).includes(prop)))
- details.additional[prop] = (e as any)[prop]
-
- return details;
-};
-
-
-
-export const ErrorDisplay = ({
- error,
- onDismiss = null,
- showDismiss = true,
- className = ''
-}: {
- error: Error | object | string,
- onDismiss: (() => void) | null,
- showDismiss?: boolean,
- className?: string
-}) => {
- const [isExpanded, setIsExpanded] = useState(false);
-
- const details = getErrorDetails(error);
- const hasDetails = details.cause || Object.keys(details.additional).length > 0;
-
- return (
-
- {/* Header */}
-
-
-
-
-
- {details.name}
-
-
- {details.message}
-
-
-
-
-
- {hasDetails && (
- setIsExpanded(!isExpanded)}
- className="text-red-600 hover:text-red-800 p-1 rounded"
- >
- {isExpanded ? (
-
- ) : (
-
- )}
-
- )}
- {showDismiss && onDismiss && (
-
-
-
- )}
-
-
-
- {/* Expandable Details */}
- {isExpanded && hasDetails && (
-
- {details.code && (
-
- Error Code:
- {details.code}
-
- )}
-
- {details.cause && (
-
- Cause:
- {details.cause}
-
- )}
-
- {Object.keys(details.additional).length > 0 && (
-
-
Additional Information:
-
- {Object.keys(details.additional).map(key => `${key}:\n${details.additional[key]}`).join('\n')}
-
-
- )}
- {/* {details.stack && (
-
-
Stack Trace:
-
- {details.stack}
-
-
- )} */}
-
- )}
-
- );
-};
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/diffLines.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/diffLines.tsx
deleted file mode 100644
index 753a7ba8..00000000
--- a/src/vs/workbench/contrib/void/browser/react/src/util/diffLines.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import { diffLines, Change } from 'diff';
-
-export { diffLines, Change }
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx
new file mode 100644
index 00000000..6cfcf9a5
--- /dev/null
+++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx
@@ -0,0 +1,366 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Glass Devtools, Inc. All rights reserved.
+ * Void Editor additions licensed under the AGPL 3.0 License.
+ *--------------------------------------------------------------------------------------------*/
+
+import React, { useCallback, useEffect, useRef } from 'react';
+import { useIsDark, useService } from '../util/services.js';
+import { IInputBoxStyles, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
+import { defaultCheckboxStyles, defaultInputBoxStyles, defaultSelectBoxStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
+import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
+import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
+import { Checkbox } from '../../../../../../../base/browser/ui/toggle/toggle.js';
+
+
+
+export const WidgetComponent = ({ ctor, propsFn, dispose, onCreateInstance, children, className }
+ : {
+ ctor: { new(...params: CtorParams): Instance },
+ propsFn: (container: HTMLDivElement) => CtorParams,
+ onCreateInstance: (instance: Instance) => IDisposable[],
+ dispose: (instance: Instance) => void,
+ children?: React.ReactNode,
+ className?: string
+ }
+) => {
+ const containerRef = useRef(null);
+
+ useEffect(() => {
+ const instance = new ctor(...propsFn(containerRef.current!));
+ const disposables = onCreateInstance(instance);
+ return () => {
+ disposables.forEach(d => d.dispose());
+ dispose(instance)
+ }
+ }, [ctor, propsFn, dispose, onCreateInstance, containerRef])
+
+ return {children}
+}
+
+
+
+export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, placeholder, multiline, styles }: {
+ onChangeText: (value: string) => void;
+ styles?: Partial,
+ onCreateInstance?: (instance: InputBox) => void | IDisposable[];
+ inputBoxRef?: { current: InputBox | null };
+ placeholder: string;
+ multiline: boolean;
+}) => {
+
+ const contextViewProvider = useService('contextViewService');
+ return [
+ container,
+ contextViewProvider,
+ {
+ inputBoxStyles: {
+ ...defaultInputBoxStyles,
+ // inputBackground: 'transparent',
+ // inputBorder: 'none',
+ ...styles,
+ },
+ placeholder,
+ tooltip: '',
+ flexibleHeight: multiline,
+ flexibleMaxHeight: 500,
+ flexibleWidth: true,
+ }
+ ] as const, [contextViewProvider, placeholder, multiline])}
+ dispose={useCallback((instance: InputBox) => {
+ instance.dispose()
+ instance.element.remove()
+ }, [])}
+ onCreateInstance={useCallback((instance: InputBox) => {
+ const disposables: IDisposable[] = []
+ disposables.push(
+ instance.onDidChange((newText) => onChangeText(newText))
+ )
+ if (onCreateInstance) {
+ const ds = onCreateInstance(instance) ?? []
+ disposables.push(...ds)
+ }
+ if (inputBoxRef)
+ inputBoxRef.current = instance;
+
+ return disposables
+ }, [onChangeText, onCreateInstance, inputBoxRef])
+ }
+ />
+};
+
+
+
+
+export const VoidSwitch = ({
+ value,
+ onChange,
+ size = 'md',
+ label,
+ disabled = false,
+}: {
+ value: boolean;
+ onChange: (value: boolean) => void;
+ label?: string;
+ disabled?: boolean;
+ size?: 'xs' | 'sm' | 'sm+' | 'md';
+}) => {
+ return (
+
+ !disabled && onChange(!value)}
+ className={`
+ relative inline-flex items-center rounded-full transition-colors duration-200 ease-in-out
+ ${value ? 'bg-gray-900 dark:bg-white' : 'bg-gray-200 dark:bg-gray-700'}
+ ${disabled ? 'opacity-25' : ''}
+ ${size === 'xs' ? 'h-4 w-7' : ''}
+ ${size === 'sm' ? 'h-5 w-9' : ''}
+ ${size === 'sm+' ? 'h-5 w-10' : ''}
+ ${size === 'md' ? 'h-6 w-11' : ''}
+ `}
+ >
+
+
+ {label && (
+
+ {label}
+
+ )}
+
+ );
+};
+
+
+
+
+
+export const VoidCheckBox = ({ label, value, onClick, className }: { label: string, value: boolean, onClick: (checked: boolean) => void, className?: string }) => {
+ const divRef = useRef(null)
+ const instanceRef = useRef(null)
+
+ useEffect(() => {
+ if (!instanceRef.current) return
+ instanceRef.current.checked = value
+ }, [value])
+
+
+ return {
+ divRef.current = container
+ return [label, value, defaultCheckboxStyles] as const
+ }, [label, value])}
+ onCreateInstance={useCallback((instance: Checkbox) => {
+ instanceRef.current = instance;
+ divRef.current?.append(instance.domNode)
+ const d = instance.onChange(() => onClick(instance.checked))
+ return [d]
+ }, [onClick])}
+ dispose={useCallback((instance: Checkbox) => {
+ instance.dispose()
+ instance.domNode.remove()
+ }, [])}
+
+ />
+
+}
+
+
+export const VoidSelectBox = ({ onChangeSelection, onCreateInstance, selectBoxRef, options }: {
+ onChangeSelection: (value: T) => void;
+ onCreateInstance?: ((instance: SelectBox) => void | IDisposable[]);
+ selectBoxRef?: React.MutableRefObject;
+ options: readonly { text: string, value: T }[];
+}) => {
+ const contextViewProvider = useService('contextViewService');
+
+ let containerRef = useRef(null);
+
+ return {
+ containerRef.current = container
+ const defaultIndex = 0;
+ return [
+ options.map(opt => ({ text: opt.text })),
+ defaultIndex,
+ contextViewProvider,
+ defaultSelectBoxStyles
+ ] as const;
+ }, [containerRef, options, contextViewProvider])}
+
+ dispose={useCallback((instance: SelectBox) => {
+ instance.dispose();
+ for (let child of containerRef.current?.childNodes ?? [])
+ containerRef.current?.removeChild(child)
+ }, [containerRef])}
+
+ onCreateInstance={useCallback((instance: SelectBox) => {
+ const disposables: IDisposable[] = []
+
+ if (containerRef.current)
+ instance.render(containerRef.current)
+
+ disposables.push(
+ instance.onDidSelect(e => { onChangeSelection(options[e.index].value); })
+ )
+
+ if (onCreateInstance) {
+ const ds = onCreateInstance(instance) ?? []
+ disposables.push(...ds)
+ }
+ if (selectBoxRef)
+ selectBoxRef.current = instance;
+
+ return disposables;
+ }, [containerRef, onChangeSelection, options, onCreateInstance, selectBoxRef])}
+
+ />;
+};
+
+
+// export const VoidScrollableElt = ({ options, children }: { options: ScrollableElementCreationOptions, children: React.ReactNode }) => {
+// const instanceRef = useRef(null);
+// const [childrenPortal, setChildrenPortal] = useState(null)
+
+// return <>
+// {
+// return [container, options] as const;
+// }, [options])}
+// onCreateInstance={useCallback((instance: DomScrollableElement) => {
+// instanceRef.current = instance;
+// setChildrenPortal(createPortal(children, instance.getDomNode()))
+// return []
+// }, [setChildrenPortal, children])}
+// dispose={useCallback((instance: DomScrollableElement) => {
+// console.log('calling dispose!!!!')
+// // instance.dispose();
+// // instance.getDomNode().remove()
+// }, [])}
+// >{children}
+
+// {childrenPortal}
+
+// >
+// }
+
+// export const VoidSelectBox = ({ onChangeSelection, initVal, selectBoxRef, options }: {
+// initVal: T;
+// selectBoxRef: React.MutableRefObject;
+// options: readonly { text: string, value: T }[];
+// onChangeSelection: (value: T) => void;
+// }) => {
+// const contextViewProvider = useService('contextViewService');
+// const contextMenuProvider = useService('contextMenuService');
+
+
+// return {
+// return [
+// container, {
+// contextMenuProvider,
+// actions: options.map(({ text, value }, i) => ({
+// id: i + '',
+// label: text,
+// tooltip: text,
+// class: undefined,
+// enabled: true,
+// run: () => {
+// onChangeSelection(value);
+// },
+// }))
+
+// }] as const;
+// }, [options, initVal, contextViewProvider])}
+
+// dispose={useCallback((instance: DropdownMenu) => {
+// instance.dispose();
+// // instance.element.remove()
+// }, [])}
+
+// onCreateInstance={useCallback((instance: DropdownMenu) => {
+// return []
+// }, [])}
+
+// />;
+// };
+
+
+
+
+// export const VoidCheckBox = ({ onChangeChecked, initVal, label, checkboxRef, }: {
+// onChangeChecked: (checked: boolean) => void;
+// initVal: boolean;
+// checkboxRef: React.MutableRefObject;
+// label: string;
+// }) => {
+// const containerRef = useRef(null);
+
+// const themeService = useService('themeService');
+// const contextViewService = useService('contextViewService');
+// const hoverService = useService('hoverService');
+
+// useEffect(() => {
+// if (!containerRef.current) return;
+
+// // Create and mount the Checkbox using VSCode's implementation
+
+// checkboxRef.current = new ObjectSettingCheckboxWidget(
+// containerRef.current,
+// themeService,
+// contextViewService,
+// hoverService,
+// );
+
+
+// checkboxRef.current.setValue([{
+// key: { type: 'string', data: label },
+// value: { type: 'boolean', data: initVal },
+// removable: false,
+// resetable: true,
+// }])
+
+// checkboxRef.current.onDidChangeList((list) => {
+// onChangeChecked(!!list);
+// })
+
+
+// // cleanup
+// return () => {
+// if (checkboxRef.current) {
+// checkboxRef.current.dispose();
+// if (containerRef.current) {
+// while (containerRef.current.firstChild) {
+// containerRef.current.removeChild(containerRef.current.firstChild);
+// }
+// }
+// checkboxRef.current = null;
+// }
+// };
+// }, [checkboxRef, label, initVal, onChangeChecked]);
+
+// return
;
+// };
+
+
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/mountFnGenerator.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/mountFnGenerator.tsx
index 6ab7a361..b674e7d5 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/util/mountFnGenerator.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/util/mountFnGenerator.tsx
@@ -1,17 +1,24 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Glass Devtools, Inc. All rights reserved.
+ * Void Editor additions licensed under the AGPL 3.0 License.
+ *--------------------------------------------------------------------------------------------*/
+
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 '../../../helpers/reactServicesHelper.js';
-
-export const mountFnGenerator = (Component: React.FC) => (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
}
- _registerServices(services)
+ const disposables = _registerServices(services)
+
const root = ReactDOM.createRoot(rootElement)
- root.render( );
+ root.render( ); // tailwind dark theme indicator
+
+ return disposables
}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/posthog.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/posthog.tsx
deleted file mode 100644
index 738f6799..00000000
--- a/src/vs/workbench/contrib/void/browser/react/src/util/posthog.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-
-import posthog from 'posthog-js';
-
-export { posthog }
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 9cceb8d7..2cee2419 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
@@ -1,67 +1,135 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Glass Devtools, Inc. All rights reserved.
+ * Void Editor additions licensed under the AGPL 3.0 License.
+ *--------------------------------------------------------------------------------------------*/
+
import { useState, useEffect } from 'react'
-import { ConfigState } from '../../../registerConfig.js'
-import { VoidSidebarState, ReactServicesType } from '../../../registerSidebar.js'
-import { ThreadsState } from '../../../registerThreads.js'
+import { ThreadsState } from '../../../threadHistoryService.js'
+import { RefreshableProviderName, SettingsOfProvider } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
+import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
+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'
+import { RefreshModelStateOfProvider } from '../../../../../../../platform/void/common/refreshModelService.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
let services: ReactServicesType
-// even if React hasn't mounted yet, these variables are always updated to the latest state:
-let sidebarState: VoidSidebarState
-let configState: ConfigState
-let threadsState: ThreadsState
+// 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()
-// React listens by adding a setState function to these:
+let sidebarState: VoidSidebarState
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
-const configStateListeners: Set<(s: ConfigState) => void> = new Set()
+
+let threadsState: ThreadsState
const threadsStateListeners: Set<(s: ThreadsState) => void> = new Set()
+let settingsState: VoidSettingsState
+const settingsStateListeners: Set<(s: VoidSettingsState) => void> = new Set()
+
+let refreshModelState: RefreshModelStateOfProvider
+const refreshModelStateListeners: Set<(s: RefreshModelStateOfProvider) => void> = new Set()
+const refreshModelProviderListeners: Set<(p: RefreshableProviderName, s: RefreshModelStateOfProvider) => void> = new Set()
+
+let colorThemeState: ColorScheme
+const colorThemeStateListeners: Set<(s: ColorScheme) => void> = new Set()
+
// 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!
-
let wasCalled = false
-
export const _registerServices = (services_: ReactServicesType) => {
- if (wasCalled) console.error(`void _registerServices was called again! It should only be called once.`)
+ const disposables: IDisposable[] = []
+
+ // don't register services twice
+ if (wasCalled) {
+ return
+ // console.error(`⚠️ Void _registerServices was called again! It should only be called once.`)
+ }
wasCalled = true
services = services_
- const { sidebarStateService, configStateService, threadsStateService, } = 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
- sidebarStateService.onDidChangeState(() => {
- sidebarState = sidebarStateService.state
- sidebarStateListeners.forEach(l => l(sidebarState))
- })
-
- configState = configStateService.state
- configStateService.onDidChangeState(() => {
- configState = configStateService.state
- configStateListeners.forEach(l => l(configState))
- })
+ disposables.push(
+ sidebarStateService.onDidChangeState(() => {
+ sidebarState = sidebarStateService.state
+ sidebarStateListeners.forEach(l => l(sidebarState))
+ })
+ )
threadsState = threadsStateService.state
- threadsStateService.onDidChangeCurrentThread(() => {
- threadsState = threadsStateService.state
- threadsStateListeners.forEach(l => l(threadsState))
- })
+ disposables.push(
+ threadsStateService.onDidChangeCurrentThread(() => {
+ threadsState = threadsStateService.state
+ threadsStateListeners.forEach(l => l(threadsState))
+ })
+ )
+ settingsState = settingsStateService.state
+ disposables.push(
+ settingsStateService.onDidChangeState(() => {
+ settingsState = settingsStateService.state
+ settingsStateListeners.forEach(l => l(settingsState))
+ })
+ )
+
+ refreshModelState = refreshModelService.state
+ disposables.push(
+ refreshModelService.onDidChangeState((providerName) => {
+ refreshModelState = refreshModelService.state
+ refreshModelStateListeners.forEach(l => l(refreshModelState))
+ refreshModelProviderListeners.forEach(l => l(providerName, refreshModelState))
+ })
+ )
+
+ colorThemeState = themeService.getColorTheme().type
+ disposables.push(
+ themeService.onDidColorThemeChange(theme => {
+ colorThemeState = theme.type
+ colorThemeStateListeners.forEach(l => l(colorThemeState))
+ })
+ )
+
+ return disposables
}
// -- services --
-export const useService = (serviceName: T) => {
+export const useService = (serviceName: T): ReactServicesType[T] => {
if (services === null) {
throw new Error('useAccessor must be used within an AccessorProvider')
}
- return services[serviceName] as ReactServicesType[T]
+ return services[serviceName]
}
// -- 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(() => {
@@ -72,12 +140,12 @@ export const useSidebarState = () => {
return s
}
-export const useConfigState = () => {
- const [s, ss] = useState(configState)
+export const useSettingsState = () => {
+ const [s, ss] = useState(settingsState)
useEffect(() => {
- ss(configState)
- configStateListeners.add(ss)
- return () => { configStateListeners.delete(ss) }
+ ss(settingsState)
+ settingsStateListeners.add(ss)
+ return () => { settingsStateListeners.delete(ss) }
}, [ss])
return s
}
@@ -91,3 +159,37 @@ export const useThreadsState = () => {
}, [ss])
return s
}
+
+
+export const useRefreshModelState = () => {
+ const [s, ss] = useState(refreshModelState)
+ useEffect(() => {
+ ss(refreshModelState)
+ refreshModelStateListeners.add(ss)
+ return () => { refreshModelStateListeners.delete(ss) }
+ }, [ss])
+ return s
+}
+
+
+export const useRefreshModelListener = (listener: (providerName: RefreshableProviderName, s: RefreshModelStateOfProvider) => void) => {
+ useEffect(() => {
+ refreshModelProviderListeners.add(listener)
+ return () => { refreshModelProviderListeners.delete(listener) }
+ }, [listener])
+}
+
+
+export const useIsDark = () => {
+ const [s, ss] = useState(colorThemeState)
+ useEffect(() => {
+ ss(colorThemeState)
+ colorThemeStateListeners.add(ss)
+ return () => { colorThemeStateListeners.delete(ss) }
+ }, [ss])
+
+ // s is the theme, return isDark instead of s
+ const isDark = s === ColorScheme.DARK || s === ColorScheme.HIGH_CONTRAST_DARK
+ return isDark
+
+}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx
new file mode 100644
index 00000000..59abfcde
--- /dev/null
+++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx
@@ -0,0 +1,54 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Glass Devtools, Inc. All rights reserved.
+ * Void Editor additions licensed under the AGPL 3.0 License.
+ *--------------------------------------------------------------------------------------------*/
+
+import { useCallback, useEffect, useRef, useState } from 'react'
+import { FeatureName, featureNames, ModelSelection, modelSelectionsEqual, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
+import { useSettingsState, useRefreshModelState, useService } from '../util/services.js'
+import { VoidSelectBox } from '../util/inputs.js'
+import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'
+
+
+const ModelSelectBox = ({ featureName }: { featureName: FeatureName }) => {
+ const voidSettingsService = useService('settingsStateService')
+ const settingsState = useSettingsState()
+
+ let weChangedText = false
+
+ return {
+ if (weChangedText) return
+ voidSettingsService.setModelSelectionOfFeature(featureName, newVal)
+ }, [voidSettingsService, featureName])}
+ // we are responsible for setting the initial state here. always sync instance when state changes.
+ onCreateInstance={useCallback((instance: SelectBox) => {
+ const syncInstance = () => {
+ const modelsListRef = voidSettingsService.state._modelOptions // as a ref
+ const settingsAtProvider = voidSettingsService.state.modelSelectionOfFeature[featureName]
+ const selectionIdx = settingsAtProvider === null ? -1 : modelsListRef.findIndex(v => modelSelectionsEqual(v.value, settingsAtProvider))
+ weChangedText = true
+ instance.select(selectionIdx === -1 ? 0 : selectionIdx)
+ weChangedText = false
+ }
+ syncInstance()
+ const disposable = voidSettingsService.onDidChangeState(syncInstance)
+ return [disposable]
+ }, [voidSettingsService, featureName])}
+ />
+}
+
+const DummySelectBox = () => {
+ return { }}
+ />
+}
+
+export const ModelDropdown = ({ featureName }: { featureName: FeatureName }) => {
+ const settingsState = useSettingsState()
+ return <>
+ {settingsState._modelOptions.length === 0 ? : }
+ >
+}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx
new file mode 100644
index 00000000..cc00422e
--- /dev/null
+++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx
@@ -0,0 +1,373 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
+import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, featureFlagNames, displayInfoOfFeatureFlag, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
+import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
+import { VoidCheckBox, VoidInputBox, VoidSelectBox, VoidSwitch } from '../util/inputs.js'
+import { useIsDark, useRefreshModelListener, useRefreshModelState, useService, useSettingsState } from '../util/services.js'
+import { X, RefreshCw, Loader2, Check } from 'lucide-react'
+import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
+
+
+
+// models
+const RefreshModelButton = ({ providerName }: { providerName: RefreshableProviderName }) => {
+ const refreshModelState = useRefreshModelState()
+ const refreshModelService = useService('refreshModelService')
+
+ const [justFinished, setJustSucceeded] = useState(false)
+
+ useRefreshModelListener(
+ useCallback((providerName2, refreshModelState) => {
+ if (providerName2 !== providerName) return
+ const { state } = refreshModelState[providerName]
+ if (state !== 'success') return
+ // now we know we just entered 'success' state for this providerName
+ setJustSucceeded(true)
+ const tid = setTimeout(() => { setJustSucceeded(false) }, 2000)
+ return () => clearTimeout(tid)
+ }, [providerName])
+ )
+
+ const { state } = refreshModelState[providerName]
+ const isRefreshing = state === 'refreshing'
+
+ const { title: providerTitle } = displayInfoOfProviderName(providerName)
+ return
+ { refreshModelService.refreshModels(providerName) }}>
+ {isRefreshing ? : (justFinished ? : )}
+
+ {
+ justFinished ? `${providerTitle} Models are up-to-date!` : `Refresh Models List for ${providerTitle}.`
+ }
+
+}
+
+const RefreshableModels = () => {
+ const settingsState = useSettingsState()
+
+
+ const buttons = refreshableProviderNames.map(providerName => {
+ if (!settingsState.settingsOfProvider[providerName].enabled) return null
+ return
+ })
+
+ return <>
+ {buttons}
+ >
+
+}
+
+
+
+const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
+ const settingsStateService = useService('settingsStateService')
+ const settingsState = useSettingsState()
+
+ const providerNameRef = useRef(null)
+ const modelNameRef = useRef(null)
+
+ const [errorString, setErrorString] = useState('')
+
+
+ const providerOptions = useMemo(() => providerNames.map(providerName => ({ text: displayInfoOfProviderName(providerName).title, value: providerName })), [providerNames])
+
+ return <>
+
+ {/* model */}
+
+ { modelNameRef.current = modelName }, [])}
+ multiline={false}
+ />
+
+
+ {/* provider */}
+
+ { providerNameRef.current = providerOptions[0].value }, [providerOptions])} // initialize state
+ onChangeSelection={useCallback((providerName: ProviderName) => { providerNameRef.current = providerName }, [])}
+ options={providerOptions}
+ />
+
+
+ {/* button */}
+
+ {
+ const providerName = providerNameRef.current
+ const modelName = modelNameRef.current
+
+ if (providerName === null) {
+ setErrorString('Please select a provider.')
+ return
+ }
+ if (!modelName) {
+ setErrorString('Please enter a model name.')
+ return
+ }
+ // if model already exists here
+ if (settingsState.settingsOfProvider[providerName].models.find(m => m.modelName === modelName)) {
+ setErrorString(`This model already exists under ${providerName}.`)
+ return
+ }
+
+ settingsStateService.addModel(providerName, modelName)
+ onSubmit()
+
+ }}>Add model
+
+
+ {!errorString ? null :
+ {errorString}
+
}
+
+
+
+
+ >
+
+}
+
+const AddModelMenuFull = () => {
+ const [open, setOpen] = useState(false)
+
+ return
+ {open ?
+
{ setOpen(false) }} />
+ : setOpen(true)}
+ >Add Model
+ }
+
+}
+
+
+export const ModelDump = () => {
+
+ const settingsStateService = useService('settingsStateService')
+ const settingsState = useSettingsState()
+
+ // a dump of all the enabled providers' models
+ const modelDump: (VoidModelInfo & { providerName: ProviderName, providerEnabled: boolean })[] = []
+ for (let providerName of providerNames) {
+ const providerSettings = settingsState.settingsOfProvider[providerName]
+ // if (!providerSettings.enabled) continue
+ modelDump.push(...providerSettings.models.map(model => ({ ...model, providerName, providerEnabled: !!providerSettings.enabled })))
+ }
+
+ // sort by hidden
+ modelDump.sort((a, b) => {
+ return Number(b.providerEnabled) - Number(a.providerEnabled)
+ })
+
+ return
+ {modelDump.map(m => {
+ const { isHidden, isDefault, modelName, providerName, providerEnabled } = m
+
+ const disabled = !providerEnabled
+
+ return
+ {/* left part is width:full */}
+
+ {`${modelName} (${providerName})`}
+
+ {/* right part is anything that fits */}
+
+
{isDefault ? '' : '(custom model)'}
+
+
{ settingsStateService.toggleModelHidden(providerName, modelName) }}
+ disabled={disabled}
+ size='sm'
+ />
+
+
+ {isDefault ? null : { settingsStateService.deleteModel(providerName, modelName) }}> }
+
+
+
+ })}
+
+}
+
+
+
+// providers
+
+const ProviderSetting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => {
+
+
+ const { title: providerTitle, } = displayInfoOfProviderName(providerName)
+
+ const { title: settingTitle, placeholder, subTextMd } = displayInfoOfSettingName(providerName, settingName)
+ const voidSettingsService = useService('settingsStateService')
+
+ let weChangedTextRef = false
+
+ return
+
+
{
+ if (weChangedTextRef) return
+ voidSettingsService.setSettingOfProvider(providerName, settingName, newVal)
+ }, [voidSettingsService, providerName, settingName])}
+
+ // we are responsible for setting the initial value. always sync the instance whenever there's a change to state.
+ onCreateInstance={useCallback((instance: InputBox) => {
+ const syncInstance = () => {
+ const settingsAtProvider = voidSettingsService.state.settingsOfProvider[providerName];
+ const stateVal = settingsAtProvider[settingName as SettingName]
+ // console.log('SYNCING TO', providerName, settingName, stateVal)
+ weChangedTextRef = true
+ instance.value = stateVal as string
+ weChangedTextRef = false
+ }
+ syncInstance()
+ const disposable = voidSettingsService.onDidChangeState(syncInstance)
+ return [disposable]
+ }, [voidSettingsService, providerName, settingName])}
+ multiline={false}
+ />
+ {subTextMd === undefined ? null :
+
+
}
+
+
+
+}
+
+const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => {
+ const voidSettingsState = useSettingsState()
+ const voidSettingsService = useService('settingsStateService')
+
+ const { enabled } = voidSettingsState.settingsOfProvider[providerName]
+ const settingNames = customSettingNamesOfProvider(providerName)
+
+ const { title: providerTitle } = displayInfoOfProviderName(providerName)
+
+ return
+
+
{providerTitle}
+
+ {/* enable provider switch */}
+ {
+ const enabledRef = voidSettingsService.state.settingsOfProvider[providerName].enabled
+ voidSettingsService.setSettingOfProvider(providerName, 'enabled', !enabledRef)
+ }, [voidSettingsService, providerName])}
+ size='sm+'
+ />
+
+
+
+ {/* settings besides models (e.g. api key) */}
+ {settingNames.map((settingName, i) => {
+ return
+ })}
+
+
+}
+
+
+export const VoidProviderSettings = () => {
+ return <>
+ {providerNames.map(providerName =>
+
+ )}
+ >
+}
+
+
+export const VoidFeatureFlagSettings = () => {
+ const voidSettingsService = useService('settingsStateService')
+ const voidSettingsState = useSettingsState()
+
+ return <>
+ {featureFlagNames.map((flagName) => {
+ const value = voidSettingsState.featureFlagSettings[flagName]
+ const { description } = displayInfoOfFeatureFlag(flagName)
+ return
+
+ { voidSettingsService.setFeatureFlag(flagName, !value) }}
+ />
+ {description}
+
+
+ })}
+ >
+}
+
+
+// full settings
+
+export const Settings = () => {
+ const isDark = useIsDark()
+
+ const [tab, setTab] = useState<'models' | 'features'>('models')
+
+ return
+
+
+
+
+
Void Settings
+
+ {/* separator */}
+
+
+
+
+ {/* tabs */}
+
+ { setTab('models') }}
+ >Models
+ { setTab('features') }}
+ >Features
+
+
+ {/* separator */}
+
+
+
+ {/* content */}
+
+
+
+
Providers
+
+
+
+
+
Models
+
+
+
+
+
+
+
+
+
{ setTab('features') }}>Features
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/index.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/index.tsx
new file mode 100644
index 00000000..ff596b24
--- /dev/null
+++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/index.tsx
@@ -0,0 +1,6 @@
+import { mountFnGenerator } from '../util/mountFnGenerator.js'
+import { Settings } from './Settings.js'
+
+export const mountVoidSettings = mountFnGenerator(Settings)
+
+
diff --git a/src/vs/workbench/contrib/void/browser/react/tailwind.config.js b/src/vs/workbench/contrib/void/browser/react/tailwind.config.js
index 52499c40..8a41657b 100644
--- a/src/vs/workbench/contrib/void/browser/react/tailwind.config.js
+++ b/src/vs/workbench/contrib/void/browser/react/tailwind.config.js
@@ -1,32 +1,122 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Glass Devtools, Inc. All rights reserved.
+ * Void Editor additions licensed under the AGPL 3.0 License.
+ *--------------------------------------------------------------------------------------------*/
+
/** @type {import('tailwindcss').Config} */
module.exports = {
+ darkMode: 'selector', // '{prefix-}dark' className is used to identify `dark:`
content: ['./src2/**/*.{jsx,tsx}'], // uses these files to decide how to transform the css file
theme: {
extend: {
- // inject user's vscode theme colors: https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content
colors: {
vscode: {
- "sidebar-bg": "var(--vscode-sideBar-background)",
- "editor-bg": "var(--vscode-editor-background)",
- "editor-fg": "var(--vscode-editor-foreground)",
+ // see: https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content
+
+ // base colors
+ "fg": "var(--vscode-foreground)",
+ "focus-border": "var(--vscode-focusBorder)",
+ "disabled-fg": "var(--vscode-disabledForeground)",
+ "widget-border": "var(--vscode-widget-border)",
+ "widget-shadow": "var(--vscode-widget-shadow)",
+ "selection-bg": "var(--vscode-selection-background)",
+ "description-fg": "var(--vscode-descriptionForeground)",
+ "error-fg": "var(--vscode-errorForeground)",
+ "icon-fg": "var(--vscode-icon-foreground)",
+ "sash-hover-border": "var(--vscode-sash-hoverBorder)",
+
+ // text colors
+ "text-blockquote-bg": "var(--vscode-textBlockQuote-background)",
+ "text-blockquote-border": "var(--vscode-textBlockQuote-border)",
+ "text-codeblock-bg": "var(--vscode-textCodeBlock-background)",
+ "text-link-active-fg": "var(--vscode-textLink-activeForeground)",
+ "text-link-fg": "var(--vscode-textLink-foreground)",
+ "text-preformat-fg": "var(--vscode-textPreformat-foreground)",
+ "text-preformat-bg": "var(--vscode-textPreformat-background)",
+ "text-separator-fg": "var(--vscode-textSeparator-foreground)",
+
+ // input colors
"input-bg": "var(--vscode-input-background)",
- "input-fg": "var(--vscode-input-foreground)",
"input-border": "var(--vscode-input-border)",
- "button-fg": "var(--vscode-button-foreground)",
+ "input-fg": "var(--vscode-input-foreground)",
+ "input-placeholder-fg": "var(--vscode-placeholderForeground)",
+ "input-active-bg": "var(--vscode-activeBackground)",
+ "input-option-active-border": "var(--vscode-activeBorder)",
+ "input-option-active-fg": "var(--vscode-activeForeground)",
+ "input-option-hover-bg": "var(--vscode-hoverBackground)",
+ "input-validation-error-bg": "var(--vscode-errorBackground)",
+ "input-validation-error-fg": "var(--vscode-errorForeground)",
+ "input-validation-error-border": "var(--vscode-errorBorder)",
+ "input-validation-info-bg": "var(--vscode-infoBackground)",
+ "input-validation-info-fg": "var(--vscode-infoForeground)",
+ "input-validation-info-border": "var(--vscode-infoBorder)",
+ "input-validation-warning-bg": "var(--vscode-warningBackground)",
+ "input-validation-warning-fg": "var(--vscode-warningForeground)",
+ "input-validation-warning-border": "var(--vscode-warningBorder)",
+
+ // command center colors (the top bar)
+ "commandcenter-fg": "var(--vscode-commandCenter-foreground)",
+ "commandcenter-active-fg": "var(--vscode-commandCenter-activeForeground)",
+ "commandcenter-bg": "var(--vscode-commandCenter-background)",
+ "commandcenter-active-bg": "var(--vscode-commandCenter-activeBackground)",
+ "commandcenter-border": "var(--vscode-commandCenter-border)",
+ "commandcenter-inactive-fg": "var(--vscode-commandCenter-inactiveForeground)",
+ "commandcenter-inactive-border": "var(--vscode-commandCenter-inactiveBorder)",
+ "commandcenter-active-border": "var(--vscode-commandCenter-activeBorder)",
+ "commandcenter-debugging-bg": "var(--vscode-commandCenter-debuggingBackground)",
+
+ // badge colors
+ "badge-fg": "var(--vscode-badge-foreground)",
+ "badge-bg": "var(--vscode-badge-background)",
+
+ // button colors
"button-bg": "var(--vscode-button-background)",
- "button-hoverBg": "var(--vscode-button-hoverBackground)",
+ "button-fg": "var(--vscode-button-foreground)",
+ "button-border": "var(--vscode-button-border)",
+ "button-separator": "var(--vscode-button-separator)",
+ "button-hover-bg": "var(--vscode-button-hoverBackground)",
"button-secondary-fg": "var(--vscode-button-secondaryForeground)",
"button-secondary-bg": "var(--vscode-button-secondaryBackground)",
- "button-secondary-hoverBg": "var(--vscode-button-secondaryHoverBackground)",
- "dropdown-bg": "var(--vscode-settings-dropdownBackground)",
- "dropdown-foreground": "var(--vscode-settings-dropdownForeground)",
- "dropdown-border": "var(--vscode-settings-dropdownBorder)",
- "focus-border": "var(--vscode-focusBorder)",
+ "button-secondary-hover-bg": "var(--vscode-button-secondaryHoverBackground)",
+
+ // checkbox colors
+ "checkbox-bg": "var(--vscode-checkbox-background)",
+ "checkbox-fg": "var(--vscode-checkbox-foreground)",
+ "checkbox-border": "var(--vscode-checkbox-border)",
+ "checkbox-select-bg": "var(--vscode-checkbox-selectBackground)",
+
+ // sidebar colors
+ "sidebar-bg": "var(--vscode-sideBar-background)",
+ "sidebar-fg": "var(--vscode-sideBar-foreground)",
+ "sidebar-border": "var(--vscode-sideBar-border)",
+ "sidebar-drop-backdrop": "var(--vscode-sideBar-dropBackground)",
+ "sidebar-title-fg": "var(--vscode-sideBarTitle-foreground)",
+ "sidebar-header-bg": "var(--vscode-sideBarSectionHeader-background)",
+ "sidebar-header-fg": "var(--vscode-sideBarSectionHeader-foreground)",
+ "sidebar-header-border": "var(--vscode-sideBarSectionHeader-border)",
+ "sidebar-activitybartop-border": "var(--vscode-sideBarActivityBarTop-border)",
+ "sidebar-title-bg": "var(--vscode-sideBarTitle-background)",
+ "sidebar-title-border": "var(--vscode-sideBarTitle-border)",
+ "sidebar-stickyscroll-bg": "var(--vscode-sideBarStickyScroll-background)",
+ "sidebar-stickyscroll-border": "var(--vscode-sideBarStickyScroll-border)",
+ "sidebar-stickyscroll-shadow": "var(--vscode-sideBarStickyScroll-shadow)",
+
+ // other colors (these are partially complete)
+
+ // editor colors
+ "editor-bg": "var(--vscode-editor-background)",
+ "editor-fg": "var(--vscode-editor-foreground)",
+
+ // editorwidget colors
+ "editorwidget-fg": "var(--vscode-editorWidget-foreground)",
+ "editorwidget-bg": "var(--vscode-editorWidget-background)",
+ "editorwidget-border": "var(--vscode-editorWidget-border)",
+
},
},
},
},
plugins: [],
- prefix: 'prefix-'
+ prefix: 'void-'
}
diff --git a/src/vs/workbench/contrib/void/browser/react/tsconfig.json b/src/vs/workbench/contrib/void/browser/react/tsconfig.json
index 291ee2c1..26ca0a77 100644
--- a/src/vs/workbench/contrib/void/browser/react/tsconfig.json
+++ b/src/vs/workbench/contrib/void/browser/react/tsconfig.json
@@ -1,4 +1,9 @@
-{
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Glass Devtools, Inc. All rights reserved.
+ * Void Editor additions licensed under the AGPL 3.0 License.
+ *--------------------------------------------------------------------------------------------*/
+
+ {
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": false,
diff --git a/src/vs/workbench/contrib/void/browser/react/tsup.config.js b/src/vs/workbench/contrib/void/browser/react/tsup.config.js
index 45039566..2a7547a0 100644
--- a/src/vs/workbench/contrib/void/browser/react/tsup.config.js
+++ b/src/vs/workbench/contrib/void/browser/react/tsup.config.js
@@ -1,11 +1,16 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Glass Devtools, Inc. All rights reserved.
+ * Void Editor additions licensed under the AGPL 3.0 License.
+ *--------------------------------------------------------------------------------------------*/
+
import { defineConfig } from 'tsup'
export default defineConfig({
entry: [
- './src2/sidebar-tsx/Sidebar.tsx',
- './src2/sendLLMMessage/sendLLMMessage.tsx',
- './src2/util/posthog.tsx',
- './src2/util/diffLines.tsx',
+ './src2/sidebar-tsx/index.tsx',
+ './src2/void-settings-tsx/index.tsx',
+ './src2/ctrl-k-tsx/index.tsx',
+ './src2/diff/index.tsx',
],
outDir: './out',
format: ['esm'],
@@ -14,7 +19,7 @@ export default defineConfig({
// dts: true,
// sourcemap: true,
- clean: true,
+ clean: false,
platform: 'browser', // 'node'
target: 'esnext',
injectStyle: true, // bundle css into the output file
diff --git a/src/vs/workbench/contrib/void/browser/registerConfig.ts b/src/vs/workbench/contrib/void/browser/registerConfig.ts
deleted file mode 100644
index 46fc122f..00000000
--- a/src/vs/workbench/contrib/void/browser/registerConfig.ts
+++ /dev/null
@@ -1,335 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Glass Devtools, Inc. All rights reserved.
- * Void Editor additions licensed under the AGPLv3 License.
- *--------------------------------------------------------------------------------------------*/
-
-import { Emitter, Event } from '../../../../base/common/event.js';
-import { Disposable } from '../../../../base/common/lifecycle.js';
-import { IEncryptionService } from '../../../../platform/encryption/common/encryptionService.js';
-import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
-import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
-import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
-
-const configEnum = (description: string, defaultVal: EnumArr[number], enumArr: EnumArr) => {
- return {
- description,
- defaultVal,
- enumArr,
- }
-}
-
-const configString = (description: string, defaultVal: string) => {
- return {
- description,
- defaultVal,
- enumArr: undefined,
- }
-}
-
-export const parseMaxTokensStr = (maxTokensStr: string) => {
- // parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
- const int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
- if (Number.isNaN(int))
- return undefined
- return int
-}
-
-
-// fields you can customize (don't forget 'default' - it isn't included here!)
-export const nonDefaultConfigFields = [
- 'anthropic',
- 'openAI',
- 'gemini',
- 'greptile',
- 'groq',
- 'ollama',
- 'openRouter',
- 'openAICompatible',
- 'azure',
-] as const
-
-
-
-const voidConfigInfo: Record<
- typeof nonDefaultConfigFields[number] | 'default', {
- [prop: string]: {
- description: string;
- enumArr?: readonly string[] | undefined;
- defaultVal: string;
- };
- }
-> = {
- default: {
- whichApi: configEnum(
- 'API Provider.',
- 'anthropic',
- nonDefaultConfigFields,
- ),
-
- maxTokens: configEnum(
- 'Max number of tokens to output.',
- '1024',
- [
- 'default', // this will be parseInt'd into NaN and ignored by the API. Anything that's not a number has this behavior.
- '1024',
- '2048',
- '4096',
- '8192'
- ] as const,
- ),
-
- },
- anthropic: {
- apikey: configString('Anthropic API key.', ''),
- model: configEnum(
- 'Anthropic model to use.',
- 'claude-3-5-sonnet-20240620',
- [
- 'claude-3-5-sonnet-20240620',
- 'claude-3-opus-20240229',
- 'claude-3-sonnet-20240229',
- 'claude-3-haiku-20240307'
- ] as const,
- ),
- },
- openAI: {
- apikey: configString('OpenAI API key.', ''),
- model: configEnum(
- 'OpenAI model to use.',
- 'gpt-4o',
- [
- 'o1-preview',
- 'o1-mini',
- 'gpt-4o',
- 'gpt-4o-2024-05-13',
- 'gpt-4o-2024-08-06',
- 'gpt-4o-mini',
- 'gpt-4o-mini-2024-07-18',
- 'gpt-4-turbo',
- 'gpt-4-turbo-2024-04-09',
- 'gpt-4-turbo-preview',
- 'gpt-4-0125-preview',
- 'gpt-4-1106-preview',
- 'gpt-4',
- 'gpt-4-0613',
- 'gpt-3.5-turbo-0125',
- 'gpt-3.5-turbo',
- 'gpt-3.5-turbo-1106'
- ] as const
- ),
- },
- greptile: {
- apikey: configString('Greptile API key.', ''),
- githubPAT: configString('Github PAT that Greptile uses to access your repository', ''),
- remote: configEnum(
- 'Repo location',
- 'github',
- [
- 'github',
- 'gitlab'
- ] as const
- ),
- repository: configString('Repository identifier in "owner / repository" format.', ''),
- branch: configString('Name of the branch to use.', 'main'),
- },
- groq: {
- apikey: configString('Groq API key.', ''),
- model: configEnum(
- 'Groq model to use.',
- 'mixtral-8x7b-32768',
- [
- "mixtral-8x7b-32768",
- "llama2-70b-4096",
- "gemma-7b-it"
- ] as const
- ),
- },
- ollama: {
- endpoint: configString(
- 'The endpoint of your Ollama instance. Start Ollama by running `OLLAMA_ORIGINS="vscode - webview://*" ollama serve`.',
- 'http://127.0.0.1:11434'
- ),
- model: configEnum(
- 'Ollama model to use.',
- 'codestral',
- ['codestral', 'qwen2.5-coder', 'qwen2.5-coder:0.5b', 'qwen2.5-coder:1.5b', 'qwen2.5-coder:3b', 'qwen2.5-coder:7b', 'qwen2.5-coder:14b', 'qwen2.5-coder:32b', 'codegemma', 'codegemma:2b', 'codegemma:7b', 'codellama', 'codellama:7b', 'codellama:13b', 'codellama:34b', 'codellama:70b', 'codellama:code', 'codellama:python', 'command-r', 'command-r:35b', 'command-r-plus', 'command-r-plus:104b', 'deepseek-coder-v2', 'deepseek-coder-v2:16b', 'deepseek-coder-v2:236b', 'falcon2', 'falcon2:11b', 'firefunction-v2', 'firefunction-v2:70b', 'gemma', 'gemma:2b', 'gemma:7b', 'gemma2', 'gemma2:2b', 'gemma2:9b', 'gemma2:27b', 'llama2', 'llama2:7b', 'llama2:13b', 'llama2:70b', 'llama3', 'llama3:8b', 'llama3:70b', 'llama3-chatqa', 'llama3-chatqa:8b', 'llama3-chatqa:70b', 'llama3-gradient', 'llama3-gradient:8b', 'llama3-gradient:70b', 'llama3.1', 'llama3.1:8b', 'llama3.1:70b', 'llama3.1:405b', 'llava', 'llava:7b', 'llava:13b', 'llava:34b', 'llava-llama3', 'llava-llama3:8b', 'llava-phi3', 'llava-phi3:3.8b', 'mistral', 'mistral:7b', 'mistral-large', 'mistral-large:123b', 'mistral-nemo', 'mistral-nemo:12b', 'mixtral', 'mixtral:8x7b', 'mixtral:8x22b', 'moondream', 'moondream:1.8b', 'openhermes', 'openhermes:v2.5', 'phi3', 'phi3:3.8b', 'phi3:14b', 'phi3.5', 'phi3.5:3.8b', 'qwen', 'qwen:7b', 'qwen:14b', 'qwen:32b', 'qwen:72b', 'qwen:110b', 'qwen2', 'qwen2:0.5b', 'qwen2:1.5b', 'qwen2:7b', 'qwen2:72b', 'smollm', 'smollm:135m', 'smollm:360m', 'smollm:1.7b'] as const
- ),
- },
- openRouter: {
- model: configString(
- 'OpenRouter model to use.',
- 'openai/gpt-4o'
- ),
- apikey: configString('OpenRouter API key.', ''),
- },
- openAICompatible: {
- endpoint: configString('The baseUrl (exluding /chat/completions).', 'http://127.0.0.1:11434/v1'),
- model: configString('The name of the model to use.', 'gpt-4o'),
- apikey: configString('Your API key.', ''),
- },
- azure: {
- // 'void.azure.apiKey': {
- // 'type': 'string',
- // 'description': 'Azure API key.'
- // },
- // 'void.azure.deploymentId': {
- // 'type': 'string',
- // 'description': 'Azure API deployment ID.'
- // },
- // 'void.azure.resourceName': {
- // 'type': 'string',
- // 'description': 'Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`'
- // },
- // 'void.azure.providerSettings': {
- // 'type': 'object',
- // 'properties': {
- // 'baseURL': {
- // 'type': 'string',
- // 'default': 'https://${resourceName}.openai.azure.com/openai/deployments',
- // 'description': 'Azure API base URL.'
- // },
- // 'headers': {
- // 'type': 'object',
- // 'description': 'Custom headers to include in the requests.'
- // }
- // }
- // },
- },
- gemini: {
- apikey: configString('Google API key.', ''),
- model: configEnum(
- 'Gemini model to use.',
- 'gemini-1.5-flash',
- [
- 'gemini-1.5-flash',
- 'gemini-1.5-pro',
- 'gemini-1.5-flash-8b',
- 'gemini-1.0-pro'
- ] as const
- ),
- },
-}
-
-
-// this is the type that comes with metadata like desc, default val, etc
-export type VoidConfigInfo = typeof voidConfigInfo
-export type VoidConfigField = keyof typeof voidConfigInfo // typeof configFields[number]
-
-// this is the type that specifies the user's actual config
-export type PartialVoidConfig = {
- [K in keyof typeof voidConfigInfo]?: {
- [P in keyof typeof voidConfigInfo[K]]?: typeof voidConfigInfo[K][P]['defaultVal']
- }
-}
-
-export type VoidConfig = {
- [K in keyof typeof voidConfigInfo]: {
- [P in keyof typeof voidConfigInfo[K]]: typeof voidConfigInfo[K][P]['defaultVal']
- }
-}
-
-
-const getVoidConfig = (partialVoidConfig: PartialVoidConfig): VoidConfig => {
- const config = {} as PartialVoidConfig
- for (const field of [...nonDefaultConfigFields, 'default'] as const) {
- config[field] = {}
- for (const prop in voidConfigInfo[field]) {
- config[field][prop] = partialVoidConfig[field]?.[prop]?.trim() || voidConfigInfo[field][prop].defaultVal
- }
- }
- return config as VoidConfig
-}
-
-
-const VOID_CONFIG_KEY = 'void.partialVoidConfig'
-
-export type SetFieldFnType = (field: K, param: keyof VoidConfigInfo[K], newVal: string) => Promise;
-
-export type ConfigState = {
- partialVoidConfig: PartialVoidConfig; // free parameter
- voidConfig: VoidConfig; // computed from partialVoidConfig
-}
-
-export interface IVoidConfigStateService {
- readonly _serviceBrand: undefined;
- readonly state: ConfigState;
- readonly voidConfigInfo: VoidConfigInfo;
- onDidChangeState: Event;
- setField: SetFieldFnType;
-}
-
-export const IVoidConfigStateService = createDecorator('VoidConfigStateService');
-class VoidConfigStateService extends Disposable implements IVoidConfigStateService {
- _serviceBrand: undefined;
-
- private readonly _onDidChangeState = new Emitter();
- readonly onDidChangeState: Event = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
-
- state: ConfigState;
- readonly voidConfigInfo: VoidConfigInfo = voidConfigInfo; // just putting this here for simplicity, it's static though
-
- constructor(
- @IStorageService private readonly _storageService: IStorageService,
- @IEncryptionService private readonly _encryptionService: IEncryptionService,
- // could have used this, but it's clearer the way it is (+ slightly different eg StorageTarget.USER)
- // @ISecretStorageService private readonly _secretStorageService: ISecretStorageService,
- ) {
- super()
-
- // at the start, we haven't read the partial config yet, but we need to set state to something, just treat partialVoidConfig like it's empty
- this.state = {
- partialVoidConfig: {},
- voidConfig: getVoidConfig({}),
- }
-
- // read and update the actual state immediately
- this._readPartialVoidConfig().then(partialVoidConfig => {
- this._setState(partialVoidConfig)
- })
-
- }
-
- private async _readPartialVoidConfig(): Promise {
- const encryptedPartialConfig = this._storageService.get(VOID_CONFIG_KEY, StorageScope.APPLICATION)
-
- if (!encryptedPartialConfig)
- return {}
-
- const partialVoidConfigStr = await this._encryptionService.decrypt(encryptedPartialConfig)
- return JSON.parse(partialVoidConfigStr)
- }
-
-
- private async _storePartialVoidConfig(partialVoidConfig: PartialVoidConfig) {
- const encryptedPartialConfigStr = await this._encryptionService.encrypt(JSON.stringify(partialVoidConfig))
- this._storageService.store(VOID_CONFIG_KEY, encryptedPartialConfigStr, StorageScope.APPLICATION, StorageTarget.USER)
- }
-
-
- // Set field on PartialVoidConfig
- setField: SetFieldFnType = async (field: K, param: keyof VoidConfigInfo[K], newVal: string) => {
- const { partialVoidConfig } = this.state
-
- const newPartialConfig: PartialVoidConfig = {
- ...partialVoidConfig,
- [field]: {
- ...partialVoidConfig[field],
- [param]: newVal
- }
- }
- await this._storePartialVoidConfig(newPartialConfig)
- this._setState(newPartialConfig)
- }
-
- // internal function to update state, should be called every time state changes
- private async _setState(partialVoidConfig: PartialVoidConfig) {
- this.state = {
- partialVoidConfig: partialVoidConfig,
- voidConfig: getVoidConfig(partialVoidConfig),
- }
- this._onDidChangeState.fire()
- }
-
-}
-
-registerSingleton(IVoidConfigStateService, VoidConfigStateService, InstantiationType.Eager);
diff --git a/src/vs/workbench/contrib/void/browser/registerMetrics.ts b/src/vs/workbench/contrib/void/browser/registerMetrics.ts
deleted file mode 100644
index 7a6e96ad..00000000
--- a/src/vs/workbench/contrib/void/browser/registerMetrics.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Glass Devtools, Inc. All rights reserved.
- * Void Editor additions licensed under the AGPLv3 License.
- *--------------------------------------------------------------------------------------------*/
-
-import { Disposable } from '../../../../base/common/lifecycle.js';
-import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
-import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
-import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
-
-import { posthog } from './react/out/util/posthog.js'
-
-
-
-// const buildEnv = 'development';
-// const buildNumber = '1.0.0';
-// const isMac = process.platform === 'darwin';
-// // TODO use commandKey
-// const commandKey = isMac ? '⌘' : 'Ctrl';
-// const systemInfo = {
-// buildEnv,
-// buildNumber,
-// isMac,
-// }
-
-
-
-interface IMetricsService {
- readonly _serviceBrand: undefined;
-}
-
-const IMetricsService = createDecorator('metricsService');
-class MetricsService extends Disposable implements IMetricsService {
- _serviceBrand: undefined;
-
- constructor(
- @ITelemetryService private readonly _telemetryService: ITelemetryService
- ) {
- super()
- posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', {
- api_host: 'https://us.i.posthog.com',
- person_profiles: 'identified_only' // we only track events from identified users. We identify them in Sidebar
- })
- const deviceId = this._telemetryService.devDeviceId
- console.debug('deviceId', deviceId)
- posthog.identify(deviceId)
- }
-
-
-}
-
-registerSingleton(IMetricsService, MetricsService, InstantiationType.Eager);
diff --git a/src/vs/workbench/contrib/void/browser/registerSidebar.ts b/src/vs/workbench/contrib/void/browser/registerSidebar.ts
deleted file mode 100644
index cddd9853..00000000
--- a/src/vs/workbench/contrib/void/browser/registerSidebar.ts
+++ /dev/null
@@ -1,246 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Glass Devtools, Inc. All rights reserved.
- * Void Editor additions licensed under the AGPLv3 License.
- *--------------------------------------------------------------------------------------------*/
-
-import { Registry } from '../../../../platform/registry/common/platform.js';
-import {
- Extensions as ViewContainerExtensions, IViewContainersRegistry,
- ViewContainerLocation, IViewsRegistry, Extensions as ViewExtensions,
- IViewDescriptorService,
-} from '../../../common/views.js';
-
-import * as nls from '../../../../nls.js';
-import * as dom from '../../../../base/browser/dom.js';
-
-import { Codicon } from '../../../../base/common/codicons.js';
-import { localize } from '../../../../nls.js';
-import { registerIcon } from '../../../../platform/theme/common/iconRegistry.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 { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPane.js';
-
-import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
-import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
-import { Disposable } from '../../../../base/common/lifecycle.js';
-import { Emitter, Event } from '../../../../base/common/event.js';
-import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
-import { IViewsService } from '../../../services/views/common/viewsService.js';
-import { IThreadHistoryService } from './registerThreads.js';
-import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
-import { IThemeService } from '../../../../platform/theme/common/themeService.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 { IVoidConfigService } from './registerSettings.js';
-// import { IEditorService } from '../../../services/editor/common/editorService.js';
-
-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 { IModelService } from '../../../../editor/common/services/model.js';
-import { ISendLLMMessageService } from '../../../../platform/void/browser/llmMessageService.js';
-
-
-// import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
-
-
-// compare against search.contribution.ts and https://app.greptile.com/chat/w1nsmt3lauwzculipycpn?repo=github%3Amain%3Amicrosoft%2Fvscode
-// 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;
- sendLLMMessageService: ISendLLMMessageService;
-}
-
-// ---------- Define viewpane ----------
-
-class VoidSidebarViewPane extends ViewPane {
-
- constructor(
- options: IViewPaneOptions,
- @IInstantiationService instantiationService: IInstantiationService,
- @IViewDescriptorService viewDescriptorService: IViewDescriptorService,
- @IConfigurationService configurationService: IConfigurationService,
- @IContextKeyService contextKeyService: IContextKeyService,
- @IThemeService themeService: IThemeService,
- @IContextMenuService contextMenuService: IContextMenuService,
- @IKeybindingService keybindingService: IKeybindingService,
- @IOpenerService openerService: IOpenerService,
- @ITelemetryService telemetryService: ITelemetryService,
- @IHoverService hoverService: IHoverService,
- // Void:
- // @IVoidSidebarStateService private readonly _voidSidebarStateService: IVoidSidebarStateService,
- // @IThreadHistoryService private readonly _threadHistoryService: IThreadHistoryService,
- // TODO chat service
- ) {
- super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService)
-
- }
-
-
-
- protected override renderBody(parent: HTMLElement): void {
- super.renderBody(parent);
-
- const { root } = dom.h('div@root')
- dom.append(parent, root);
-
- // 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),
- sendLLMMessageService: accessor.get(ISendLLMMessageService),
- }
- mountFn(root, services);
- });
- }
-
-}
-
-
-
-// ---------- Register viewpane inside the void container ----------
-
-const voidThemeIcon = Codicon.symbolObject;
-const voidViewIcon = registerIcon('void-view-icon', voidThemeIcon, localize('voidViewIcon', 'View icon of the Void chat view.'));
-
-// called VIEWLET_ID in other places for some reason
-export const VOID_VIEW_CONTAINER_ID = 'workbench.view.void'
-export const VOID_VIEW_ID = VOID_VIEW_CONTAINER_ID // not sure if we can change this
-
-// Register view container
-const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry);
-const viewContainer = viewContainerRegistry.registerViewContainer({
- id: VOID_VIEW_CONTAINER_ID,
- title: nls.localize2('void', 'Void'), // 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, });
-
-
-
-// Register search default location to the container (sidebar)
-const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry);
-viewsRegistry.registerViews([{
- id: VOID_VIEW_ID,
- hideByDefault: false, // start open
- containerIcon: voidViewIcon,
- name: nls.localize2('void 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
- },
-}], viewContainer);
-
-
-
-// ---------- Register service that manages sidebar's state ----------
-
-export interface IVoidSidebarStateService {
- readonly _serviceBrand: undefined;
-
- readonly state: VoidSidebarState; // readonly to the user
- setState(newState: Partial): void;
- onDidChangeState: Event;
-
- onDidFocusChat: Event;
- onDidBlurChat: Event;
- fireFocusChat(): void;
- fireBlurChat(): void;
-
- openView(): void;
-}
-
-
-export const IVoidSidebarStateService = createDecorator('voidSidebarStateService');
-class VoidSidebarStateService extends Disposable implements IVoidSidebarStateService {
- _serviceBrand: undefined;
-
- private readonly _onDidChangeState = new Emitter();
- readonly onDidChangeState: Event = this._onDidChangeState.event;
-
- private readonly _onFocusChat = new Emitter();
- readonly onDidFocusChat: Event = this._onFocusChat.event;
-
- private readonly _onBlurChat = new Emitter();
- readonly onDidBlurChat: Event = this._onBlurChat.event;
-
-
- // state
- state: VoidSidebarState
-
-
- setState(newState: Partial) {
- // make sure view is open if the tab changes
- if ('currentTab' in newState) {
- this.openView()
- }
-
- this.state = { ...this.state, ...newState }
- this._onDidChangeState.fire()
- }
-
- fireFocusChat() {
- this._onFocusChat.fire()
- }
-
- fireBlurChat() {
- this._onBlurChat.fire()
- }
-
- openView() {
- this._viewsService.openViewContainer(VOID_VIEW_CONTAINER_ID);
- this._viewsService.openView(VOID_VIEW_ID);
- }
-
- constructor(
- @IViewsService private readonly _viewsService: IViewsService,
- // @IThreadHistoryService private readonly _threadHistoryService: IThreadHistoryService,
- ) {
- super()
- // auto open the view on mount (if it bothers you this is here, this is technically just initializing the state of the view)
- this.openView()
-
- // initial state
- this.state = {
- isHistoryOpen: false,
- currentTab: 'chat',
- }
-
- }
-
-}
-
-registerSingleton(IVoidSidebarStateService, VoidSidebarStateService, InstantiationType.Eager);
diff --git a/src/vs/workbench/contrib/void/browser/registerActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts
similarity index 63%
rename from src/vs/workbench/contrib/void/browser/registerActions.ts
rename to src/vs/workbench/contrib/void/browser/sidebarActions.ts
index 8e5cc561..7f61c729 100644
--- a/src/vs/workbench/contrib/void/browser/registerActions.ts
+++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts
@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
- * Void Editor additions licensed under the AGPLv3 License.
+ * Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
@@ -11,16 +11,18 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
-import { CodeStagingSelection, IThreadHistoryService } from './registerThreads.js';
-// import { IVoidConfigService } from './registerSettings.js';
+import { CodeStagingSelection, IThreadHistoryService } from './threadHistoryService.js';
// import { IEditorService } from '../../../services/editor/common/editorService.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { IRange } from '../../../../editor/common/core/range.js';
import { ITextModel } from '../../../../editor/common/model.js';
-import { IVoidSidebarStateService, VOID_VIEW_ID } from './registerSidebar.js';
-// import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
+import { VOID_VIEW_ID } from './sidebarPane.js';
+import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
+import { ISidebarStateService } from './sidebarStateService.js';
+import { ICommandService } from '../../../../platform/commands/common/commands.js';
+import { OPEN_VOID_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
// ---------- Register commands and keybindings ----------
@@ -51,9 +53,10 @@ const getContentInRange = (model: ITextModel, range: IRange | null) => {
}
// Action: when press ctrl+L, show the sidebar chat and add to the selection
+export const VOID_CTRL_L_ACTION_ID = 'void.ctrlLAction'
registerAction2(class extends Action2 {
constructor() {
- super({ id: 'void.ctrl+l', title: 'Show Sidebar', keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyL, weight: KeybindingWeight.BuiltinExtension } });
+ super({ id: VOID_CTRL_L_ACTION_ID, title: 'Void: Show Sidebar', keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyL, weight: KeybindingWeight.BuiltinExtension } });
}
async run(accessor: ServicesAccessor): Promise {
@@ -61,27 +64,45 @@ registerAction2(class extends Action2 {
if (!model)
return
+ const stateService = accessor.get(ISidebarStateService)
+ const metricsService = accessor.get(IMetricsService)
+
+ metricsService.capture('User Action', { type: 'Ctrl+L' })
- const stateService = accessor.get(IVoidSidebarStateService)
stateService.setState({ isHistoryOpen: false, currentTab: 'chat' })
stateService.fireFocusChat()
- // add selection
- const threadHistoryService = accessor.get(IThreadHistoryService)
- const currentStaging = threadHistoryService.state._currentStagingSelections
- const currentStagingEltIdx = currentStaging?.findIndex(s => s.fileURI.fsPath === model.uri.fsPath)
-
- // if there exists a selection with this URI, replace it
const selectionRange = roundRangeToLines(
accessor.get(IEditorService).activeTextEditorControl?.getSelection()
)
+
if (selectionRange) {
- const selection: CodeStagingSelection = {
- selectionStr: getContentInRange(model, selectionRange),
- fileURI: model.uri
+
+ const selectionStr = getContentInRange(model, selectionRange)
+
+ const selection: CodeStagingSelection = selectionStr === null || selectionRange.startLineNumber > selectionRange.endLineNumber ? {
+ type: 'File',
+ fileURI: model.uri,
+ selectionStr: null,
+ range: null,
+ } : {
+ type: 'Selection',
+ fileURI: model.uri,
+ selectionStr: selectionStr,
+ range: selectionRange,
}
+ // add selection to staging
+ const threadHistoryService = accessor.get(IThreadHistoryService)
+ const currentStaging = threadHistoryService.state._currentStagingSelections
+ const currentStagingEltIdx = currentStaging?.findIndex(s =>
+ s.fileURI.fsPath === model.uri.fsPath
+ && s.range?.startLineNumber === selection.range?.startLineNumber
+ && s.range?.endLineNumber === selection.range?.endLineNumber
+ )
+
+ // if matches with existing selection, overwrite
if (currentStagingEltIdx !== undefined && currentStagingEltIdx !== -1) {
threadHistoryService.setStaging([
...currentStaging!.slice(0, currentStagingEltIdx),
@@ -89,6 +110,7 @@ registerAction2(class extends Action2 {
...currentStaging!.slice(currentStagingEltIdx + 1, Infinity)
])
}
+ // if no match, add
else {
threadHistoryService.setStaging([...(currentStaging ?? []), selection])
}
@@ -103,16 +125,19 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'void.newChatAction',
- title: 'View past chats',
+ title: 'New Chat',
icon: { id: 'add' },
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }]
});
}
async run(accessor: ServicesAccessor): Promise {
- const stateService = accessor.get(IVoidSidebarStateService)
+ const stateService = accessor.get(ISidebarStateService)
+ const metricsService = accessor.get(IMetricsService)
+
+ metricsService.capture('Chat Navigation', { type: 'New Chat' })
+
stateService.setState({ isHistoryOpen: false, currentTab: 'chat' })
stateService.fireFocusChat()
-
const historyService = accessor.get(IThreadHistoryService)
historyService.startNewThread()
}
@@ -123,31 +148,35 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'void.historyAction',
- title: 'View past chats',
+ title: 'View Past Chats',
icon: { id: 'history' },
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }]
});
}
async run(accessor: ServicesAccessor): Promise {
- const stateService = accessor.get(IVoidSidebarStateService)
+ const stateService = accessor.get(ISidebarStateService)
+ const metricsService = accessor.get(IMetricsService)
+
+ metricsService.capture('Chat Navigation', { type: 'History' })
+
stateService.setState({ isHistoryOpen: !stateService.state.isHistoryOpen, currentTab: 'chat' })
stateService.fireBlurChat()
}
})
-// Settings (API config) menu button
+
+// Settings gear
registerAction2(class extends Action2 {
constructor() {
super({
- id: 'void.viewSettings',
- title: 'Void settings',
+ id: 'void.settingsAction',
+ 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 {
- const stateService = accessor.get(IVoidSidebarStateService)
- stateService.setState({ isHistoryOpen: false, currentTab: stateService.state.currentTab === 'settings' ? 'chat' : 'settings' })
- stateService.fireBlurChat()
+ const commandService = accessor.get(ICommandService)
+ commandService.executeCommand(OPEN_VOID_SETTINGS_ACTION_ID)
}
})
diff --git a/src/vs/workbench/contrib/void/browser/sidebarPane.ts b/src/vs/workbench/contrib/void/browser/sidebarPane.ts
new file mode 100644
index 00000000..bbe7cf4b
--- /dev/null
+++ b/src/vs/workbench/contrib/void/browser/sidebarPane.ts
@@ -0,0 +1,150 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Glass Devtools, Inc. All rights reserved.
+ * Void Editor additions licensed under the AGPL 3.0 License.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Registry } from '../../../../platform/registry/common/platform.js';
+import {
+ Extensions as ViewContainerExtensions, IViewContainersRegistry,
+ ViewContainerLocation, IViewsRegistry, Extensions as ViewExtensions,
+ IViewDescriptorService,
+} from '../../../common/views.js';
+
+import * as nls from '../../../../nls.js';
+
+// import { Codicon } from '../../../../base/common/codicons.js';
+// import { localize } from '../../../../nls.js';
+// import { registerIcon } from '../../../../platform/theme/common/iconRegistry.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 { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPane.js';
+
+import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
+import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
+import { IDisposable } from '../../../../base/common/lifecycle.js';
+import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
+import { IThemeService } from '../../../../platform/theme/common/themeService.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 { mountSidebar } from './react/out/sidebar-tsx/index.js';
+
+import { getReactServices } from './helpers/reactServicesHelper.js';
+import { Codicon } from '../../../../base/common/codicons.js';
+import { Orientation } from '../../../../base/browser/ui/sash/sash.js';
+// import { Orientation } from '../../../../base/browser/ui/sash/sash.js';
+// import { Codicon } from '../../../../base/common/codicons.js';
+// import { Codicon } from '../../../../base/common/codicons.js';
+
+
+// compare against search.contribution.ts and debug.contribution.ts, scm.contribution.ts (source control)
+
+// ---------- Define viewpane ----------
+
+class SidebarViewPane extends ViewPane {
+
+ constructor(
+ options: IViewPaneOptions,
+ @IInstantiationService instantiationService: IInstantiationService,
+ @IViewDescriptorService viewDescriptorService: IViewDescriptorService,
+ @IConfigurationService configurationService: IConfigurationService,
+ @IContextKeyService contextKeyService: IContextKeyService,
+ @IThemeService themeService: IThemeService,
+ @IContextMenuService contextMenuService: IContextMenuService,
+ @IKeybindingService keybindingService: IKeybindingService,
+ @IOpenerService openerService: IOpenerService,
+ @ITelemetryService telemetryService: ITelemetryService,
+ @IHoverService hoverService: IHoverService,
+ ) {
+ super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService)
+
+ }
+
+
+
+ protected override renderBody(parent: HTMLElement): void {
+ super.renderBody(parent);
+ // parent.style.overflow = 'auto'
+ parent.style.userSelect = 'text'
+
+ // gets set immediately
+ this.instantiationService.invokeFunction(accessor => {
+ const services = getReactServices(accessor)
+
+ // mount react
+ const disposables: IDisposable[] | undefined = mountSidebar(parent, services);
+ disposables?.forEach(d => this._register(d))
+ });
+ }
+
+ protected override layoutBody(height: number, width: number): void {
+ super.layoutBody(height, width)
+ this.element.style.height = `${height}px`
+ this.element.style.width = `${width}px`
+
+
+ }
+
+}
+
+
+
+// ---------- Register viewpane inside the void container ----------
+
+// const voidThemeIcon = Codicon.symbolObject;
+// const voidViewIcon = registerIcon('void-view-icon', voidThemeIcon, localize('voidViewIcon', 'View icon of the Void chat view.'));
+
+// called VIEWLET_ID in other places for some reason
+export const VOID_VIEW_CONTAINER_ID = 'workbench.view.void'
+export const VOID_VIEW_ID = VOID_VIEW_CONTAINER_ID
+
+// Register view container
+const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry);
+const container = viewContainerRegistry.registerViewContainer({
+ id: VOID_VIEW_CONTAINER_ID,
+ title: nls.localize2('voidContainer', 'Void Chat'), // this is used to say "Void" (Ctrl + L)
+ ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VOID_VIEW_CONTAINER_ID, {
+ mergeViewWithContainerWhenSingleView: true,
+ orientation: Orientation.HORIZONTAL,
+ }]),
+ hideIfEmpty: false,
+ order: 1,
+
+ rejectAddedViews: true,
+ icon: Codicon.symbolMethod,
+
+
+}, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true, isDefault: true });
+
+
+
+// Register search default location to the container (sidebar)
+const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry);
+viewsRegistry.registerViews([{
+ id: VOID_VIEW_ID,
+ hideByDefault: false, // start open
+ // containerIcon: voidViewIcon,
+ name: nls.localize2('voidChat', ''), // this says ... : CHAT
+ ctorDescriptor: new SyncDescriptor(SidebarViewPane),
+ canToggleVisibility: false,
+ canMoveView: false, // can't move this out of its container
+ weight: 80,
+ order: 1,
+ // singleViewPaneContainerTitle: 'hi',
+
+ // openCommandActionDescriptor: {
+ // id: VOID_VIEW_CONTAINER_ID,
+ // keybindings: {
+ // primary: KeyMod.CtrlCmd | KeyCode.KeyL,
+ // },
+ // order: 1
+ // },
+}], container);
+
diff --git a/src/vs/workbench/contrib/void/browser/sidebarStateService.ts b/src/vs/workbench/contrib/void/browser/sidebarStateService.ts
new file mode 100644
index 00000000..683a3ed4
--- /dev/null
+++ b/src/vs/workbench/contrib/void/browser/sidebarStateService.ts
@@ -0,0 +1,84 @@
+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 { IViewsService } from '../../../services/views/common/viewsService.js';
+import { VOID_VIEW_CONTAINER_ID, VOID_VIEW_ID } from './sidebarPane.js';
+
+
+// service that manages sidebar's state
+export type VoidSidebarState = {
+ isHistoryOpen: boolean;
+ currentTab: 'chat';
+}
+
+export interface ISidebarStateService {
+ readonly _serviceBrand: undefined;
+
+ readonly state: VoidSidebarState; // readonly to the user
+ setState(newState: Partial): void;
+ onDidChangeState: Event;
+
+ onDidFocusChat: Event;
+ onDidBlurChat: Event;
+ fireFocusChat(): void;
+ fireBlurChat(): void;
+
+ openSidebarView(): void;
+}
+
+export const ISidebarStateService = createDecorator('voidSidebarStateService');
+class VoidSidebarStateService extends Disposable implements ISidebarStateService {
+ _serviceBrand: undefined;
+
+ static readonly ID = 'voidSidebarStateService';
+
+ private readonly _onDidChangeState = new Emitter();
+ readonly onDidChangeState: Event = this._onDidChangeState.event;
+
+ private readonly _onFocusChat = new Emitter();
+ readonly onDidFocusChat: Event = this._onFocusChat.event;
+
+ private readonly _onBlurChat = new Emitter();
+ readonly onDidBlurChat: Event = this._onBlurChat.event;
+
+
+ // state
+ state: VoidSidebarState
+
+ constructor(
+ @IViewsService private readonly _viewsService: IViewsService,
+ ) {
+ super()
+
+ // initial state
+ this.state = { isHistoryOpen: false, currentTab: 'chat', }
+ }
+
+
+ setState(newState: Partial) {
+ // make sure view is open if the tab changes
+ if ('currentTab' in newState) {
+ this.openSidebarView()
+ }
+
+ this.state = { ...this.state, ...newState }
+ this._onDidChangeState.fire()
+ }
+
+ fireFocusChat() {
+ this._onFocusChat.fire()
+ }
+
+ fireBlurChat() {
+ this._onBlurChat.fire()
+ }
+
+ openSidebarView() {
+ this._viewsService.openViewContainer(VOID_VIEW_CONTAINER_ID);
+ this._viewsService.openView(VOID_VIEW_ID);
+ }
+
+}
+
+registerSingleton(ISidebarStateService, VoidSidebarStateService, InstantiationType.Eager);
diff --git a/src/vs/workbench/contrib/void/browser/registerThreads.ts b/src/vs/workbench/contrib/void/browser/threadHistoryService.ts
similarity index 80%
rename from src/vs/workbench/contrib/void/browser/registerThreads.ts
rename to src/vs/workbench/contrib/void/browser/threadHistoryService.ts
index e942a079..9d704c65 100644
--- a/src/vs/workbench/contrib/void/browser/registerThreads.ts
+++ b/src/vs/workbench/contrib/void/browser/threadHistoryService.ts
@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
- * Void Editor additions licensed under the AGPLv3 License.
+ * Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../base/common/lifecycle.js';
@@ -10,30 +10,42 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
import { URI } from '../../../../base/common/uri.js';
import { Emitter, Event } from '../../../../base/common/event.js';
+import { IAutocompleteService } from './autocompleteService.js';
+import { IRange } from '../../../../editor/common/core/range.js';
-// if selectionStr is null, it means just send the whole file
export type CodeSelection = {
- selectionStr: string | null;
fileURI: URI;
- content: string;
+ selectionStr: string | null;
+ content: string; // TODO remove this (replace `selectionStr` with `content`)
+ range: IRange;
}
+// if selectionStr is null, it means to use the entire file at send time
export type CodeStagingSelection = {
- selectionStr: string | null;
- fileURI: URI;
+ type: 'Selection',
+ fileURI: URI,
+ selectionStr: string,
+ range: IRange
+} | {
+ type: 'File',
+ fileURI: URI,
+ selectionStr: null,
+ range: null
}
+
+// WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors.
export type ChatMessage =
| {
role: 'user';
- content: string; // content sent to the llm
- displayContent: string; // content displayed to user
+ content: string | null; // content sent to the llm - allowed to be '', will be replaced with (empty)
+ displayContent: string | null; // content displayed to user - allowed to be '', will be ignored
selections: CodeSelection[] | null; // the user's selection
}
| {
role: 'assistant';
- content: string; // content received from LLM
- displayContent: string | undefined; // content displayed to user (this is the same as content for now)
+ content: string | null; // content received from LLM - allowed to be '', will be replaced with (empty)
+ displayContent: string | null; // content displayed to user (this is the same as content for now) - allowed to be '', will be ignored
}
| {
role: 'system';
@@ -68,7 +80,7 @@ const newThreadObject = () => {
}
}
-const THREAD_STORAGE_KEY = 'void.threadsHistory'
+const THREAD_STORAGE_KEY = 'void.threadHistory'
export interface IThreadHistoryService {
readonly _serviceBrand: undefined;
@@ -97,8 +109,10 @@ class ThreadHistoryService extends Disposable implements IThreadHistoryService {
constructor(
@IStorageService private readonly _storageService: IStorageService,
+ @IAutocompleteService private readonly _autocomplete: IAutocompleteService,
) {
super()
+ this._autocomplete
this.state = {
allThreads: this._readAllThreads(),
@@ -133,6 +147,8 @@ class ThreadHistoryService extends Disposable implements IThreadHistoryService {
}
switchToThread(threadId: string) {
+ console.log('threadId', threadId)
+ console.log('messages', this.state.allThreads[threadId].messages)
this._setState({ _currentThreadId: threadId }, true)
}
@@ -194,3 +210,4 @@ class ThreadHistoryService extends Disposable implements IThreadHistoryService {
}
registerSingleton(IThreadHistoryService, ThreadHistoryService, InstantiationType.Eager);
+
diff --git a/src/vs/workbench/contrib/void/browser/void.contribution.ts b/src/vs/workbench/contrib/void/browser/void.contribution.ts
index 93466c02..c8dd051a 100644
--- a/src/vs/workbench/contrib/void/browser/void.contribution.ts
+++ b/src/vs/workbench/contrib/void/browser/void.contribution.ts
@@ -1,25 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
- * Void Editor additions licensed under the AGPLv3 License.
+ * Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
-// register keybinds
-import './registerActions.js'
-
-// register Settings
-import './registerConfig.js'
// register inline diffs
-import './registerInlineDiffs.js'
+import './inlineDiffsService.js'
-// register Posthog metrics
-import './registerMetrics.js'
+// register Sidebar pane, state, actions (keybinds, menus) (Ctrl+L)
+import './sidebarActions.js'
+import './sidebarPane.js'
+import './sidebarStateService.js'
-// register Sidebar chat
-import './registerSidebar.js'
+// register quick edit (Ctrl+K)
+import './quickEditActions.js'
// register Thread History
-import './registerThreads.js'
+import './threadHistoryService.js'
+
+// register Autocomplete
+import './autocompleteService.js'
+
+// settings pane
+import './voidSettingsPane.js'
// register css
import './media/void.css'
diff --git a/src/vs/workbench/contrib/void/browser/voidSettingsPane.ts b/src/vs/workbench/contrib/void/browser/voidSettingsPane.ts
new file mode 100644
index 00000000..3b7515b8
--- /dev/null
+++ b/src/vs/workbench/contrib/void/browser/voidSettingsPane.ts
@@ -0,0 +1,162 @@
+/*---------------------------------------------------------------------------------------------
+ * 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, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
+import { Registry } from '../../../../platform/registry/common/platform.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 './helpers/reactServicesHelper.js';
+import { Codicon } from '../../../../base/common/codicons.js';
+import { IDisposable } from '../../../../base/common/lifecycle.js';
+import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js';
+
+
+// refer to preferences.contribution.ts keybindings editor
+
+class VoidSettingsInput 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 VoidSettingsInput.ID;
+ }
+
+ override getName(): string {
+ return nls.localize('voidSettingsInputsName', 'Void Settings');
+ }
+
+ override getIcon() {
+ return Codicon.checklist // symbol for the actual editor pane
+ }
+
+}
+
+
+class VoidSettingsPane extends EditorPane {
+ static readonly ID = 'workbench.test.myCustomPane';
+
+ private _scrollbar: DomScrollableElement | undefined;
+
+ constructor(
+ group: IEditorGroup,
+ @ITelemetryService telemetryService: ITelemetryService,
+ @IThemeService themeService: IThemeService,
+ @IStorageService storageService: IStorageService,
+ @IInstantiationService private readonly instantiationService: IInstantiationService
+ ) {
+ super(VoidSettingsPane.ID, group, telemetryService, themeService, storageService);
+ }
+
+ protected createEditor(parent: HTMLElement): void {
+ parent.style.height = '100%';
+ parent.style.width = '100%';
+
+ const scrollableContent = document.createElement('div');
+ scrollableContent.style.height = '100%';
+ scrollableContent.style.width = '100%';
+
+ this._scrollbar = this._register(new DomScrollableElement(scrollableContent, {}));
+ parent.appendChild(this._scrollbar.getDomNode());
+ this._scrollbar.scanDomNode();
+
+ // Mount React into the scrollable content
+ this.instantiationService.invokeFunction(accessor => {
+ const services = getReactServices(accessor);
+ const disposables: IDisposable[] | undefined = mountVoidSettings(scrollableContent, services);
+
+ setTimeout(() => { // this is a complete hack and I don't really understand how scrollbar works here
+ this._scrollbar?.scanDomNode();
+ }, 1000)
+ disposables?.forEach(d => this._register(d));
+ });
+ }
+
+ layout(dimension: Dimension): void {
+ if (!this._scrollbar) return;
+
+ this._scrollbar.getDomNode().style.height = `${dimension.height}px`;
+ this._scrollbar.getDomNode().style.width = `${dimension.width}px`;
+ this._scrollbar.scanDomNode();
+
+ }
+
+
+ override get minimumWidth() { return 700 }
+
+}
+
+// register Settings pane
+Registry.as(EditorExtensions.EditorPane).registerEditorPane(
+ EditorPaneDescriptor.create(VoidSettingsPane, VoidSettingsPane.ID, nls.localize('VoidSettingsPane', "Void Settings Pane")),
+ [new SyncDescriptor(VoidSettingsInput)]
+);
+
+
+export const OPEN_VOID_SETTINGS_ACTION_ID = 'workbench.action.openVoidSettings'
+// register the gear on the top right
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: OPEN_VOID_SETTINGS_ACTION_ID,
+ title: nls.localize2('voidSettings', "Void: Settings"),
+ f1: true,
+ icon: Codicon.settingsGear,
+ menu: [
+ {
+ id: MenuId.LayoutControlMenuSubmenu,
+ group: 'z_end',
+ },
+ {
+ id: MenuId.LayoutControlMenu,
+ when: ContextKeyExpr.equals('config.workbench.layoutControl.type', 'both'),
+ group: 'z_end'
+ }
+ ]
+ });
+ }
+ async run(accessor: ServicesAccessor): Promise {
+ const editorService = accessor.get(IEditorService);
+ const instantiationService = accessor.get(IInstantiationService);
+ const input = instantiationService.createInstance(VoidSettingsInput);
+ await editorService.openEditor(input);
+ }
+})
+
+
+// add to settings gear on bottom left
+MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
+ group: '0_command',
+ command: {
+ id: OPEN_VOID_SETTINGS_ACTION_ID,
+ title: nls.localize('voidSettings', "Void Settings")
+ },
+ order: 1
+});
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
index 4641275f..fc79846e 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
@@ -817,7 +817,7 @@ export class GettingStartedPage extends EditorPane {
const header = $('.header', {},
$('h1.product-name.caption', {}, this.productService.nameLong),
- $('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))
+ $('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "The open source AI code editor."))
);
const leftColumn = $('.categories-column.categories-column-left', {},);
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts
index 364bee96..974637d1 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts
@@ -248,30 +248,30 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
type: 'svg', altText: 'Language extensions', path: 'languages.svg'
},
},
- {
- id: 'settings',
- title: localize('gettingStarted.settings.title', "Tune your settings"),
- description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')),
- media: {
- type: 'svg', altText: 'VS Code Settings', path: 'settings.svg'
- },
- },
- {
- id: 'settingsSync',
- title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"),
- description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')),
- when: 'syncStatus != uninitialized',
- completionEvents: ['onEvent:sync-enabled'],
- media: {
- type: 'svg', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: 'settingsSync.svg'
- },
- },
- {
- id: 'commandPaletteTask',
- title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "),
- description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')),
- media: { type: 'svg', altText: 'Command Palette overlay for searching and executing commands.', path: 'commandPalette.svg' },
- },
+ // {
+ // id: 'settings',
+ // title: localize('gettingStarted.settings.title', "Tune your settings"),
+ // description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')),
+ // media: {
+ // type: 'svg', altText: 'VS Code Settings', path: 'settings.svg'
+ // },
+ // },
+ // {
+ // id: 'settingsSync',
+ // title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"),
+ // description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')),
+ // when: 'syncStatus != uninitialized',
+ // completionEvents: ['onEvent:sync-enabled'],
+ // media: {
+ // type: 'svg', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: 'settingsSync.svg'
+ // },
+ // },
+ // {
+ // id: 'commandPaletteTask',
+ // title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "),
+ // description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')),
+ // media: { type: 'svg', altText: 'Command Palette overlay for searching and executing commands.', path: 'commandPalette.svg' },
+ // },
{
id: 'pickAFolderTask-Mac',
title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"),
@@ -299,12 +299,12 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
type: 'svg', altText: 'Go to file in quick search.', path: 'search.svg'
}
},
- {
- id: 'videoTutorial',
- title: localize('gettingStarted.videoTutorial.title', "Watch video tutorials"),
- description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')),
- media: { type: 'svg', altText: 'VS Code Settings', path: 'learn.svg' },
- }
+ // {
+ // id: 'videoTutorial',
+ // title: localize('gettingStarted.videoTutorial.title', "Watch video tutorials"),
+ // description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')),
+ // media: { type: 'svg', altText: 'VS Code Settings', path: 'learn.svg' },
+ // }
]
}
},
diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts
index 0f33a1d1..14f3fec1 100644
--- a/src/vs/workbench/workbench.common.main.ts
+++ b/src/vs/workbench/workbench.common.main.ts
@@ -17,7 +17,7 @@ import './browser/workbench.contribution.js';
//#region --- Void
// Void added this:
import './contrib/void/browser/void.contribution.js';
-import '../platform/void/browser/llmMessageService.js';
+import '../platform/void/browser/void.contribution.js';
//#endregion
@@ -334,7 +334,7 @@ import './contrib/surveys/browser/nps.contribution.js';
import './contrib/surveys/browser/languageSurveys.contribution.js';
// Welcome
-import './contrib/welcomeGettingStarted/browser/gettingStarted.contribution.js';
+// import './contrib/welcomeGettingStarted/browser/gettingStarted.contribution.js'; // Void commented this out (removes Welcome page on start)
import './contrib/welcomeWalkthrough/browser/walkThrough.contribution.js';
import './contrib/welcomeViews/common/viewsWelcome.contribution.js';
import './contrib/welcomeViews/common/newFile.contribution.js';
diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts
index 5b0a42b6..57bf9f01 100644
--- a/src/vs/workbench/workbench.desktop.main.ts
+++ b/src/vs/workbench/workbench.desktop.main.ts
@@ -31,12 +31,6 @@ import './electron-sandbox/parts/dialogs/dialog.contribution.js';
//#endregion
-// //#region --- Void
-// // Void added this (modeling off of import '.*clipboardservice.js'):
-// import './services/void/electron-main/sendLLMMessage.js';
-// //#endregion
-
-
//#region --- workbench services