Compare commits

...

3 Commits

Author SHA1 Message Date
mp d6074e7ab5 ctrlk draft 2024-12-19 02:14:26 -08:00
mp 5a26aa8746 Merge branch 'model-selection' into polishing-changes 2024-12-18 15:45:18 -08:00
Andrew Pareles 8661fe1417 fix selection styles and add void watermark (buttons look bad) 2024-12-18 13:35:34 -08:00
23 changed files with 752 additions and 217 deletions
@@ -170,6 +170,9 @@ export const titleOfProviderName = (providerName: ProviderName) => {
type DisplayInfo = {
title: string,
placeholder: string,
helpfulUrl?: string,
urlPurpose?: string,
}
export const displayInfoOfSettingName = (providerName: ProviderName, settingName: SettingName): DisplayInfo => {
if (settingName === 'apiKey') {
@@ -182,6 +185,16 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
providerName === 'groq' ? 'gsk_key...' :
providerName === 'openAICompatible' ? 'sk-key...' :
'(never)',
helpfulUrl: providerName === 'anthropic' ? 'https://console.anthropic.com/settings/keys' :
providerName === 'openAI' ? 'https://platform.openai.com/api-keys' :
providerName === 'openRouter' ? 'https://openrouter.ai/settings/keys' :
providerName === 'gemini' ? 'https://aistudio.google.com/apikey' :
providerName === 'groq' ? 'https://console.groq.com/keys' :
providerName === 'openAICompatible' ? undefined :
undefined,
urlPurpose: 'to get your API key.',
}
}
else if (settingName === 'endpoint') {
@@ -189,9 +202,16 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
title: providerName === 'ollama' ? 'Your Ollama endpoint' :
providerName === 'openAICompatible' ? 'baseURL' // (do not include /chat/completions)
: '(never)',
placeholder: providerName === 'ollama' ? customProviderSettingsDefaults.ollama.endpoint
: providerName === 'openAICompatible' ? 'https://my-website.com/v1'
: '(never)',
helpfulUrl: providerName === 'ollama' ? 'https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network'
: providerName === 'openAICompatible' ? undefined
: undefined,
urlPurpose: 'for more information.',
}
}
else if (settingName === 'enabled') {
@@ -4,82 +4,112 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from '../../../../nls.js';
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
import { isMacintosh, isWeb, OS } from '../../../../base/common/platform.js';
import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { append, clearNode, $, h } from '../../../../base/browser/dom.js';
import { KeybindingLabel } from '../../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { defaultKeybindingLabelStyles } from '../../../../platform/theme/browser/defaultStyles.js';
import { editorForeground, registerColor, transparent } from '../../../../platform/theme/common/colorRegistry.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
import { ColorScheme } from '../../../../platform/theme/common/theme.js';
import { isRecentFolder, IWorkspacesService } from '../../../../platform/workspaces/common/workspaces.js';
// import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { OpenFileFolderAction, OpenFolderAction } from '../../actions/workspaceActions.js';
import { isMacintosh, isNative, OS } from '../../../../base/common/platform.js';
import { VOID_CTRL_L_ACTION_ID } from '../../../contrib/void/browser/sidebarActions.js';
import { VOID_CTRL_K_ACTION_ID } from '../../../contrib/void/browser/quickEditActions.js';
import { defaultKeybindingLabelStyles } from '../../../../platform/theme/browser/defaultStyles.js';
import { IWindowOpenable } from '../../../../platform/window/common/window.js';
import { ILabelService, Verbosity } from '../../../../platform/label/common/label.js';
import { splitRecentLabel } from '../../../../base/common/labels.js';
import { IHostService } from '../../../services/host/browser/host.js';
// import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
registerColor('editorWatermark.foreground', { dark: transparent(editorForeground, 0.6), light: transparent(editorForeground, 0.68), hcDark: editorForeground, hcLight: editorForeground }, localize('editorLineHighlight', 'Foreground color for the labels in the editor watermark.'));
interface WatermarkEntry {
readonly text: string;
readonly id: string;
readonly mac?: boolean;
readonly when?: ContextKeyExpression;
}
// interface WatermarkEntry {
// readonly text: string;
// readonly id: string;
// readonly mac?: boolean;
// readonly when?: ContextKeyExpression;
// }
const showCommands: WatermarkEntry = { text: localize('watermark.showCommands', "Show All Commands"), id: 'workbench.action.showCommands' };
const quickAccess: WatermarkEntry = { text: localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' };
const openFileNonMacOnly: WatermarkEntry = { text: localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile', mac: false };
const openFolderNonMacOnly: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder', mac: false };
const openFileOrFolderMacOnly: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder', mac: true };
const openRecent: WatermarkEntry = { text: localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' };
const newUntitledFileMacOnly: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile', mac: true };
const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' };
const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: ContextKeyExpr.equals('terminalProcessSupported', true) };
const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: ContextKeyExpr.equals('terminalProcessSupported', true) };
const toggleFullscreen: WatermarkEntry = { text: localize({ key: 'watermark.toggleFullscreen', comment: ['toggle is a verb here'] }, "Toggle Full Screen"), id: 'workbench.action.toggleFullScreen' };
const showSettings: WatermarkEntry = { text: localize('watermark.showSettings', "Show Settings"), id: 'workbench.action.openSettings' };
// const showCommands: WatermarkEntry = { text: localize('watermark.showCommands', "Show All Commands"), id: 'workbench.action.showCommands' };
// const quickAccess: WatermarkEntry = { text: localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' };
// const openFileNonMacOnly: WatermarkEntry = { text: localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile', mac: false };
// const openFolderNonMacOnly: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder', mac: false };
// const openFileOrFolderMacOnly: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder', mac: true };
// const openRecent: WatermarkEntry = { text: localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' };
// const newUntitledFileMacOnly: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile', mac: true };
// const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' };
// const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: ContextKeyExpr.equals('terminalProcessSupported', true) };
// const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: ContextKeyExpr.equals('terminalProcessSupported', true) };
// const toggleFullscreen: WatermarkEntry = { text: localize({ key: 'watermark.toggleFullscreen', comment: ['toggle is a verb here'] }, "Toggle Full Screen"), id: 'workbench.action.toggleFullScreen' };
// const showSettings: WatermarkEntry = { text: localize('watermark.showSettings', "Show Settings"), id: 'workbench.action.openSettings' };
const noFolderEntries = [
showCommands,
openFileNonMacOnly,
openFolderNonMacOnly,
openFileOrFolderMacOnly,
openRecent,
newUntitledFileMacOnly
];
// // shown when Void is emtpty
// const noFolderEntries = [
// // showCommands,
// openFileNonMacOnly,
// openFolderNonMacOnly,
// openFileOrFolderMacOnly,
// openRecent,
// // newUntitledFileMacOnly
// ];
const folderEntries = [
showCommands,
quickAccess,
findInFiles,
startDebugging,
toggleTerminal,
toggleFullscreen,
showSettings
];
// const folderEntries = [
// showCommands,
// // quickAccess,
// // findInFiles,
// // startDebugging,
// // toggleTerminal,
// // toggleFullscreen,
// // showSettings
// ];
export class EditorGroupWatermark extends Disposable {
private readonly shortcuts: HTMLElement;
private readonly transientDisposables = this._register(new DisposableStore());
private enabled: boolean = false;
// private enabled: boolean = false;
private workbenchState: WorkbenchState;
private keybindingLabels = new Set<KeybindingLabel>();
private currentDisposables = new Set<IDisposable>();
constructor(
container: HTMLElement,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IConfigurationService private readonly configurationService: IConfigurationService
// @IContextKeyService private readonly contextKeyService: IContextKeyService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IThemeService private readonly themeService: IThemeService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@ICommandService private readonly commandService: ICommandService,
@IHostService private readonly hostService: IHostService,
@ILabelService private readonly labelService: ILabelService,
) {
super();
const elements = h('.editor-group-watermark', [
h('.letterpress'),
h('.letterpress@icon'),
h('.shortcuts@shortcuts'),
]);
append(container, elements.root);
this.shortcuts = elements.shortcuts;
this.shortcuts = elements.shortcuts; // shortcuts div is modified on render()
// void icon style
const updateTheme = () => {
const theme = this.themeService.getColorTheme().type
const isDark = theme === ColorScheme.DARK || theme === ColorScheme.HIGH_CONTRAST_DARK
elements.icon.style.maxWidth = '220px'
elements.icon.style.opacity = '50%'
elements.icon.style.filter = isDark ? 'brightness(.5)' : 'invert(1)'
}
updateTheme()
this._register(
this.themeService.onDidColorThemeChange(updateTheme)
)
this.registerListeners();
@@ -103,56 +133,164 @@ export class EditorGroupWatermark extends Disposable {
this.render();
}));
const allEntriesWhenClauses = [...noFolderEntries, ...folderEntries].filter(entry => entry.when !== undefined).map(entry => entry.when!);
const allKeys = new Set<string>();
allEntriesWhenClauses.forEach(when => when.keys().forEach(key => allKeys.add(key)));
this._register(this.contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(allKeys)) {
this.render();
}
}));
// const allEntriesWhenClauses = [...noFolderEntries, ...folderEntries].filter(entry => entry.when !== undefined).map(entry => entry.when!);
// const allKeys = new Set<string>();
// allEntriesWhenClauses.forEach(when => when.keys().forEach(key => allKeys.add(key)));
// this._register(this.contextKeyService.onDidChangeContext(e => {
// if (e.affectsSome(allKeys)) {
// this.render();
// }
// }));
}
private render(): void {
const enabled = this.configurationService.getValue<boolean>('workbench.tips.enabled');
// const enabled = this.configurationService.getValue<boolean>('workbench.tips.enabled');
if (enabled === this.enabled) {
return;
}
// if (enabled === this.enabled) {
// return;
// }
// this.enabled = enabled;
// if (!enabled) {
// return;
// }
// const hasFolder = this.workbenchState !== WorkbenchState.EMPTY;
// const selected = (hasFolder ? folderEntries : noFolderEntries)
// .filter(entry => !('when' in entry) || this.contextKeyService.contextMatchesRules(entry.when))
// .filter(entry => !('mac' in entry) || entry.mac === (isMacintosh && !isWeb))
// .filter(entry => !!CommandsRegistry.getCommand(entry.id))
// .filter(entry => !!this.keybindingService.lookupKeybinding(entry.id));
this.enabled = enabled;
this.clear();
if (!enabled) {
return;
}
const box = append(this.shortcuts, $('.watermark-box'));
const folder = this.workbenchState !== WorkbenchState.EMPTY;
const selected = (folder ? folderEntries : noFolderEntries)
.filter(entry => !('when' in entry) || this.contextKeyService.contextMatchesRules(entry.when))
.filter(entry => !('mac' in entry) || entry.mac === (isMacintosh && !isWeb))
.filter(entry => !!CommandsRegistry.getCommand(entry.id))
.filter(entry => !!this.keybindingService.lookupKeybinding(entry.id));
const boxBelow = append(this.shortcuts, $(''))
const update = async () => {
const update = () => {
clearNode(box);
this.keybindingLabels.forEach(label => label.dispose());
this.keybindingLabels.clear();
clearNode(boxBelow);
for (const entry of selected) {
const keys = this.keybindingService.lookupKeybinding(entry.id);
if (!keys) {
continue;
this.currentDisposables.forEach(label => label.dispose());
this.currentDisposables.clear();
// Void - if the workbench is empty, show open
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
// Open Folder
const button = h('button')
button.root.textContent = 'Open Folder'
button.root.onclick = () => {
this.commandService.executeCommand(isMacintosh && isNative ? OpenFileFolderAction.ID : OpenFolderAction.ID)
// if (this.contextKeyService.contextMatchesRules(ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace')))) {
// this.commandService.executeCommand(OpenFolderViaWorkspaceAction.ID);
// } else {
// this.commandService.executeCommand(isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder');
// }
}
box.appendChild(button.root);
// Recents
const recentlyOpened = await this.workspacesService.getRecentlyOpened()
.catch(() => ({ files: [], workspaces: [] })).then(w => w.workspaces);
box.append(
...recentlyOpened.map(w => {
let fullPath: string;
let windowOpenable: IWindowOpenable;
if (isRecentFolder(w)) {
windowOpenable = { folderUri: w.folderUri };
fullPath = w.label || this.labelService.getWorkspaceLabel(w.folderUri, { verbose: Verbosity.LONG });
}
else {
return null
// fullPath = w.label || this.labelService.getWorkspaceLabel(w.workspace, { verbose: Verbosity.LONG });
// windowOpenable = { workspaceUri: w.workspace.configPath };
}
const { name, parentPath } = splitRecentLabel(fullPath);
const li = $('li');
const link = $('button.button-link');
link.innerText = name;
link.title = fullPath;
link.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentPath));
link.addEventListener('click', e => {
this.hostService.openWindow([windowOpenable], {
forceNewWindow: e.ctrlKey || e.metaKey,
remoteAuthority: w.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable
});
e.preventDefault();
e.stopPropagation();
});
li.appendChild(link);
const span = $('span');
span.classList.add('path');
span.classList.add('detail');
span.innerText = parentPath;
span.title = fullPath;
li.appendChild(span);
return li
}).filter(v => !!v)
)
}
else {
// show them Void keybindings
const keys = this.keybindingService.lookupKeybinding(VOID_CTRL_L_ACTION_ID);
const dl = append(box, $('dl'));
const dt = append(dl, $('dt'));
dt.textContent = entry.text;
dt.textContent = 'Chat'
const dd = append(dl, $('dd'));
const label = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
label.set(keys);
this.keybindingLabels.add(label);
if (keys)
label.set(keys);
this.currentDisposables.add(label);
const keys2 = this.keybindingService.lookupKeybinding(VOID_CTRL_K_ACTION_ID);
const dl2 = append(box, $('dl'));
const dt2 = append(dl2, $('dt'));
dt2.textContent = 'Quick Edit'
const dd2 = append(dl2, $('dd'));
const label2 = new KeybindingLabel(dd2, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
if (keys2)
label2.set(keys2);
this.currentDisposables.add(label2);
const keys3 = this.keybindingService.lookupKeybinding('workbench.action.openGlobalKeybindings');
const button3 = append(boxBelow, $('button'));
button3.textContent = 'Change Keybindings'
const label3 = new KeybindingLabel(button3, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
if (keys3)
label3.set(keys3);
button3.onclick = () => {
this.commandService.executeCommand('workbench.action.openGlobalKeybindings')
}
this.currentDisposables.add(label3);
}
};
update();
@@ -167,6 +305,6 @@ export class EditorGroupWatermark extends Disposable {
override dispose(): void {
super.dispose();
this.clear();
this.keybindingLabels.forEach(label => label.dispose());
this.currentDisposables.forEach(label => label.dispose());
}
}
@@ -9,13 +9,15 @@
height: 100%;
}
.monaco-workbench .part.editor > .content .editor-group-container.empty {
opacity: 0.5; /* dimmed to indicate inactive state */
.monaco-workbench .part.editor > .content .editor-group-container.empty {
opacity: 0.5;
/* dimmed to indicate inactive state */
}
.monaco-workbench .part.editor > .content .editor-group-container.empty.active,
.monaco-workbench .part.editor > .content .editor-group-container.empty.dragged-over {
opacity: 1; /* indicate active/dragged-over group through undimmed state */
opacity: 1;
/* indicate active/dragged-over group through undimmed state */
}
.monaco-workbench .part.editor > .content:not(.empty) .editor-group-container.empty.active:focus {
@@ -24,12 +26,13 @@
}
.monaco-workbench .part.editor > .content.empty .editor-group-container.empty.active:focus {
outline: none; /* never show outline for empty group if it is the last */
outline: none;
/* never show outline for empty group if it is the last */
}
/* Watermark & shortcuts */
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark {
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark {
display: flex;
height: 100%;
max-width: 290px;
@@ -49,26 +52,27 @@
height: calc(100% - 70px);
}
/* light */
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark > .letterpress {
width: 100%;
max-height: 100%;
aspect-ratio: 1/1;
background-image: url('./letterpress-light.svg');
background-image: url('./void_cube_noshadow.png');
background-size: contain;
background-position-x: center;
background-repeat: no-repeat;
}
.monaco-workbench.vs-dark .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./letterpress-dark.svg');
background-image: url('./void_cube_noshadow.png');
}
.monaco-workbench.hc-light .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./letterpress-hcLight.svg');
background-image: url('./void_cube_noshadow.png');
}
.monaco-workbench.hc-black .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./letterpress-hcDark.svg');
background-image: url('./void_cube_noshadow.png');
}
.monaco-workbench .part.editor > .content:not(.empty) .editor-group-container > .editor-group-watermark > .shortcuts,
@@ -109,12 +113,13 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title {
position: relative;
box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title:not(.tabs) {
display: flex; /* when tabs are not shown, use flex layout */
display: flex;
/* when tabs are not shown, use flex layout */
flex-wrap: nowrap;
}
@@ -144,7 +149,8 @@
.monaco-workbench .part.editor > .content .editor-group-container.empty.locked > .editor-group-container-toolbar,
.monaco-workbench .part.editor > .content:not(.empty) .editor-group-container.empty > .editor-group-container-toolbar,
.monaco-workbench .part.editor > .content.auxiliary .editor-group-container.empty > .editor-group-container-toolbar {
display: block; /* show toolbar when more than one editor group or always when auxiliary or locked */
display: block;
/* show toolbar when more than one editor group or always when auxiliary or locked */
}
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .actions-container {
@@ -157,7 +163,7 @@
/* Editor */
.monaco-workbench .part.editor > .content .editor-group-container.empty > .editor-container {
.monaco-workbench .part.editor > .content .editor-group-container.empty > .editor-container {
display: none;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

@@ -9,10 +9,12 @@ import { ILLMMessageService } from '../../../../../platform/void/common/llmMessa
import { IRefreshModelService } from '../../../../../platform/void/common/refreshModelService.js';
import { IVoidSettingsService } from '../../../../../platform/void/common/voidSettingsService.js';
import { IInlineDiffsService } from '../inlineDiffsService.js';
import { IQuickEditStateService } from '../quickEditStateService.js';
import { ISidebarStateService } from '../sidebarStateService.js';
import { IThreadHistoryService } from '../threadHistoryService.js';
export type ReactServicesType = {
quickEditStateService: IQuickEditStateService;
sidebarStateService: ISidebarStateService;
settingsStateService: IVoidSettingsService;
threadsStateService: IThreadHistoryService;
@@ -33,6 +35,7 @@ export type ReactServicesType = {
export const getReactServices = (accessor: ServicesAccessor): ReactServicesType => {
return {
quickEditStateService: accessor.get(IQuickEditStateService),
settingsStateService: accessor.get(IVoidSettingsService),
sidebarStateService: accessor.get(ISidebarStateService),
threadsStateService: accessor.get(IThreadHistoryService),
@@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/brows
// import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
// import { throttle } from '../../../../base/common/decorators.js';
import { writeFileWithDiffInstructions } from './prompt/systemPrompts.js';
import { writeFileWithDiffInstructions } from './prompt/prompts.js';
import { ComputedDiff, findDiffs } from './helpers/findDiffs.js';
import { EndOfLinePreference, ITextModel } from '../../../../editor/common/model.js';
import { IRange } from '../../../../editor/common/core/range.js';
@@ -3,38 +3,36 @@
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
// // used for ctrl+l
// const partialGenerationInstructions = ``
import { CodeSelection } from '../threadHistoryService.js';
const stringifySelections = (selections: CodeSelection[]) => {
return selections.map(({ fileURI, content, selectionStr }) =>
`\
File: ${fileURI.fsPath}
\`\`\`
${content // this was the enite file which is foolish
}
\`\`\`${selectionStr === null ? '' : `
Selection: ${selectionStr}`}
`).join('\n')
}
// // used for ctrl+k, autocomplete
// const fimInstructions = ``
export const generateCtrlLPrompt = (instructions: string, selections: CodeSelection[] | null) => {
let str = '';
if (selections && selections.length > 0) {
str += stringifySelections(selections);
str += `Please edit the selected code following these instructions:\n`
}
str += `${instructions}`;
return str;
};
// CTRL+K prompt:
// const promptContent = `Here is the user's original selection:
// \`\`\`
// <MID>${selection}</MID>
// \`\`\`
// The user wants to apply the following instructions to the selection:
// ${instructions}
// Please rewrite the selection following the user's instructions.
// Instructions to follow:
// 1. Follow the user's instructions
// 2. You may ONLY CHANGE the selection, and nothing else in the file
// 3. Make sure all brackets in the new selection are balanced the same was as in the original selection
// 3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake
// Complete the following:
// \`\`\`
// <PRE>${prefix}</PRE>
// <SUF>${suffix}</SUF>
// <MID>`;
export const generateCtrlLInstructions = `\
export const ctrlLSystem = `\
You are a coding assistant. You are given a list of relevant files \`files\`, a selection that the user is making \`selection\`, and instructions to follow \`instructions\`.
Please edit the selected file following the user's instructions (or, if appropriate, answer their question instead).
@@ -118,9 +116,33 @@ Memoization Object: A memo object is used to store the results of Fibonacci calc
Check Memo: Before computing fib(n), the function checks if the result is already in memo. If it is, it returns the stored result.
Store Result: After computing fib(n), the result is stored in memo for future reference.
## END EXAMPLES
## END EXAMPLES\
`
export const generateCtrlKPrompt = ({ selection, prefix, suffix, instructions, }: { selection: string, prefix: string, suffix: string, instructions: string, }) => `\
Here is the user's original selection:
\`\`\`
<MID>${selection}</MID>
\`\`\`
The user wants to apply the following instructions to the selection:
${instructions}
Please rewrite the selection following the user's instructions.
Instructions to follow:
1. Follow the user's instructions
2. You may ONLY CHANGE the selection, and nothing else in the file
3. Make sure all brackets in the new selection are balanced the same was as in the original selection
3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake
Complete the following:
\`\`\`
<PRE>${prefix}</PRE>
<SUF>${suffix}</SUF>
<MID>`;
export const generateDiffInstructions = `
You are a coding assistant. You are given a list of relevant files \`files\`, a selection that the user is making \`selection\`, and instructions to follow \`instructions\`.
@@ -1,32 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { CodeSelection } from '../threadHistoryService.js';
export const stringifySelections = (selections: CodeSelection[]) => {
return selections.map(({ fileURI, content, selectionStr }) =>
`\
File: ${fileURI.fsPath}
\`\`\`
${content // this was the enite file which is foolish
}
\`\`\`${selectionStr === null ? '' : `
Selection: ${selectionStr}`}
`).join('\n')
}
export const userInstructionsStr = (instructions: string, selections: CodeSelection[] | null) => {
let str = '';
if (selections && selections.length > 0) {
str += stringifySelections(selections);
str += `Please edit the selected code following these instructions:\n`
}
str += `${instructions}`;
return str;
};
@@ -0,0 +1,138 @@
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { ICodeEditor, IViewZone } from '../../../../editor/browser/editorBrowser.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { createDecorator, IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
import { Emitter, Event } from '../../../../base/common/event.js';
// import { IInlineDiffService } from '../../../../editor/browser/services/inlineDiffService/inlineDiffService.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { mountCtrlK } from './react/out/ctrl-k-tsx/index.js';
import { getReactServices } from './helpers/reactServicesHelper.js';
import { URI } from '../../../../base/common/uri.js';
type InitialZone = { uri: URI, startLine: number, selectedText: string, }
export type QuickEditPropsType = {
quickEditId: number,
}
export type QuickEdit = {
startLine: number, // 0-indexed
beforeCode: string,
afterCode?: string,
instructions?: string,
responseText?: string, // model can produce a text response too
}
export interface IQuickEditService {
readonly _serviceBrand: undefined;
readonly onDidChangeState: Event<void>;
addZone(zone: InitialZone): void;
}
export const IQuickEditService = createDecorator<IQuickEditService>('voidQuickEditService');
class VoidQuickEditService extends Disposable implements IQuickEditService {
_serviceBrand: undefined;
quickEditId: number = 0
private readonly _onDidChangeState = new Emitter<void>();
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
// state
// state: {}
constructor(
// @IInlineDiffService private readonly _inlineDiffService: IInlineDiffService,
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super();
}
addZone(zone: InitialZone) {
const addZoneToEditor = (editor: ICodeEditor) => {
const model = editor.getModel()
if (!model) return
editor.changeViewZones(accessor => {
const domNode = document.createElement('div');
domNode.style.zIndex = '1'
// domNode.className = 'void-redBG'
const viewZone: IViewZone = {
// afterLineNumber: computedDiff.startLine - 1,
afterLineNumber: 1,
heightInPx: 100,
// heightInLines: 1,
// minWidthInPx: 200,
domNode: domNode,
// marginDomNode: document.createElement('div'), // displayed to left
suppressMouseDown: false,
};
// const zoneId =
accessor.addZone(viewZone)
this._instantiationService.invokeFunction(accessor => {
const services = getReactServices(accessor)
const props: QuickEditPropsType = {
quickEditId: this.quickEditId++,
}
mountCtrlK(domNode, services, props)
})
// disposeInThisEditorFns.push(() => { editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) })
})
}
const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.uri.fsPath === zone.uri.fsPath)
for (const editor of editors) {
addZoneToEditor(editor)
}
}
}
registerSingleton(IQuickEditService, VoidQuickEditService, InstantiationType.Eager);
export const VOID_CTRL_K_ACTION_ID = 'void.ctrlKAction'
registerAction2(class extends Action2 {
constructor() {
super({ id: VOID_CTRL_K_ACTION_ID, title: 'Void: Quick Edit', keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyK, weight: KeybindingWeight.BuiltinExtension } });
}
async run(accessor: ServicesAccessor): Promise<void> {
const quickEditService = accessor.get(IQuickEditService)
const editorService = accessor.get(ICodeEditorService)
const metricsService = accessor.get(IMetricsService)
metricsService.capture('User Action', { type: 'Open Ctrl+K' })
const editor = editorService.getActiveCodeEditor()
if (!editor) return;
const model = editor.getModel()
if (!model) return;
const selection = editor.getSelection()
if (!selection) return;
const uri = model.uri
const startLine = selection.startLineNumber
const selectedText = model.getValueInRange(selection)
quickEditService.addZone({ uri, startLine, selectedText, })
}
});
@@ -0,0 +1,82 @@
import { Emitter, Event } from '../../../../base/common/event.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { QuickEdit } from './quickEditActions.js';
// service that manages state
export type VoidQuickEditState = {
quickEditsOfDocument: { [uri: string]: QuickEdit }
}
export interface IQuickEditStateService {
readonly _serviceBrand: undefined;
readonly state: VoidQuickEditState; // readonly to the user
setState(newState: Partial<VoidQuickEditState>): void;
onDidChangeState: Event<void>;
onDidFocusChat: Event<void>;
onDidBlurChat: Event<void>;
fireFocusChat(): void;
fireBlurChat(): void;
}
export const IQuickEditStateService = createDecorator<IQuickEditStateService>('voidQuickEditStateService');
class VoidQuickEditStateService extends Disposable implements IQuickEditStateService {
_serviceBrand: undefined;
static readonly ID = 'voidQuickEditStateService';
private readonly _onDidChangeState = new Emitter<void>();
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
private readonly _onFocusChat = new Emitter<void>();
readonly onDidFocusChat: Event<void> = this._onFocusChat.event;
private readonly _onBlurChat = new Emitter<void>();
readonly onDidBlurChat: Event<void> = this._onBlurChat.event;
// state
state: VoidQuickEditState
constructor(
// @IViewsService private readonly _viewsService: IViewsService,
) {
super()
// initial state
this.state = { quickEditsOfDocument: {} }
}
setState(newState: Partial<VoidQuickEditState>) {
// make sure view is open if the tab changes
// if ('currentTab' in newState) {
// this.addQuickEdit()
// }
this.state = { ...this.state, ...newState }
this._onDidChangeState.fire()
}
fireFocusChat() {
this._onFocusChat.fire()
}
fireBlurChat() {
this._onBlurChat.fire()
}
// addQuickEdit() {
// this._viewsService.openViewContainer(VOID_VIEW_CONTAINER_ID);
// this._viewsService.openView(VOID_VIEW_ID);
// }
}
registerSingleton(IQuickEditStateService, VoidQuickEditStateService, InstantiationType.Eager);
@@ -0,0 +1,18 @@
import { useEffect, useState } from 'react'
import { useIsDark, useSidebarState } from '../util/services.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
import { CtrlKChat } from './CtrlKChat.js'
import { QuickEditPropsType } from '../../../quickEditActions.js'
export const CtrlK = (props: QuickEditPropsType) => {
const isDark = useIsDark()
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ width: '100%', height: '100%' }}>
<ErrorBoundary>
<CtrlKChat {...props} />
</ErrorBoundary>
</div>
}
@@ -0,0 +1,83 @@
import React, { FormEvent, useCallback, useRef, useState } from 'react';
import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState, useService } from '../util/services.js';
import { OnError } from '../../../../../../../platform/void/common/llmMessageTypes.js';
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { getCmdKey } from '../../../helpers/getCmdKey.js';
import { VoidInputBox } from '../util/inputs.js';
import { QuickEditPropsType } from '../../../quickEditActions.js';
export const CtrlKChat = (props: QuickEditPropsType) => {
const inputBoxRef: React.MutableRefObject<InputBox | null> = useRef(null);
// -- imported state --
// const threadsStateService = useService('service')
// const sidebarState = useSidebarState()
const quickEditState = useQuickEditState()
// -- local state --
// state of chat
const [messageStream, setMessageStream] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
const latestRequestIdRef = useRef<string | null>(null)
const [latestError, setLatestError] = useState<Parameters<OnError>[0] | null>(null)
// state of current message
const [instructions, setInstructions] = useState('') // the user's instructions
const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions])
const isDisabled = !instructions.trim()
const onSubmit = useCallback((e: FormEvent) => {
// TODO
}, [])
return <form
className={
// copied from SidebarChat.tsx
`flex flex-col gap-2 p-1 relative input text-left shrink-0
transition-all duration-200
rounded-md
bg-vscode-input-bg
border border-vscode-commandcenter-inactive-border focus-within:border-vscode-commandcenter-active-border hover:border-vscode-commandcenter-active-border`
}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
onSubmit(e)
}
}}
onSubmit={(e) => {
console.log('submit!')
onSubmit(e)
}}
onClick={(e) => {
if (e.currentTarget === e.target) {
inputBoxRef.current?.focus()
}
}}
>
<div
className={
// copied from SidebarChat.tsx
`@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_textarea]:!void-max-h-[100px] @@[&_div.monaco-inputbox]:!void- @@[&_div.monaco-inputbox]:!void-outline-none`
}
>
{/* text input */}
<VoidInputBox
placeholder={`${getCmdKey()}+K to select`}
onChangeText={onChangeText}
inputBoxRef={inputBoxRef}
multiline={true}
/>
</div>
</form>
}
@@ -0,0 +1,8 @@
import { mountFnGenerator } from '../util/mountFnGenerator.js'
import { CtrlK } from './CtrlK.js'
export const mountCtrlK = mountFnGenerator(CtrlK)
@@ -3,12 +3,10 @@
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { FormEvent, Fragment, useCallback, useEffect, useRef, useState } from 'react';
import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, useCallback, useEffect, useRef, useState } from 'react';
import { useSettingsState, useService, useSidebarState, useThreadsState } from '../util/services.js';
import { generateCtrlLInstructions, generateDiffInstructions } from '../../../prompt/systemPrompts.js';
import { userInstructionsStr } from '../../../prompt/stringifySelections.js';
import { ChatMessage, CodeSelection, CodeStagingSelection } from '../../../threadHistoryService.js';
import { BlockCode } from '../markdown/BlockCode.js';
@@ -23,6 +21,7 @@ import { getCmdKey } from '../../../helpers/getCmdKey.js'
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { VoidInputBox } from '../util/inputs.js';
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
import { ctrlLSystem, generateCtrlLPrompt } from '../../../prompt/prompts.js';
const IconX = ({ size, className = '' }: { size: number, className?: string }) => {
@@ -85,6 +84,33 @@ const IconSquare = ({ size, className = '' }: { size: number, className?: string
);
};
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement>
export const ButtonSubmit = ({ className, disabled, ...props }: ButtonProps & Required<Pick<ButtonProps, 'disabled'>>) => {
return <button
className={`size-[20px] rounded-full shrink-0 grow-0 cursor-pointer
${disabled ? 'bg-vscode-disabled-fg' : 'bg-white'}
${className}
`}
type='submit'
{...props}
>
<IconArrowUp size={20} className="stroke-[2]" />
</button>
}
export const ButtonStop = ({ className, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) => {
return <button
className={`size-[20px] rounded-full bg-white cursor-pointer flex items-center justify-center
${className}
`}
type='button'
{...props}
>
<IconSquare size={16} className="stroke-[2]" />
</button>
}
const ScrollToBottomContainer = ({ children, className, style }: { children: React.ReactNode, className?: string, style?: React.CSSProperties }) => {
const [isAtBottom, setIsAtBottom] = useState(true); // Start at bottom
@@ -277,6 +303,8 @@ export const SidebarChat = () => {
const threadsState = useThreadsState()
const threadsStateService = useService('threadsStateService')
const llmMessageService = useService('llmMessageService')
// ----- SIDEBAR CHAT state (local) -----
// state of chat
@@ -286,7 +314,6 @@ export const SidebarChat = () => {
const [latestError, setLatestError] = useState<Parameters<OnError>[0] | null>(null)
const llmMessageService = useService('llmMessageService')
// state of current message
const [instructions, setInstructions] = useState('') // the user's instructions
@@ -325,11 +352,11 @@ export const SidebarChat = () => {
// add system message to chat history
const systemPromptElt: ChatMessage = { role: 'system', content: generateCtrlLInstructions }
const systemPromptElt: ChatMessage = { role: 'system', content: ctrlLSystem }
threadsStateService.addMessageToCurrentThread(systemPromptElt)
// add user's message to chat history
const userHistoryElt: ChatMessage = { role: 'user', content: userInstructionsStr(instructions, selections), displayContent: instructions, selections: selections }
const userHistoryElt: ChatMessage = { role: 'user', content: generateCtrlLPrompt(instructions, selections), displayContent: instructions, selections: selections }
threadsStateService.addMessageToCurrentThread(userHistoryElt)
const currentThread = threadsStateService.getCurrentThread(threadsStateService.state) // the the instant state right now, don't wait for the React state
@@ -474,14 +501,14 @@ export const SidebarChat = () => {
{/* middle row */}
<div
className={
// // overwrite vscode styles (generated with this code):
// // hack to overwrite vscode styles (generated with this code):
// `bg-transparent outline-none text-vscode-input-fg min-h-[81px] max-h-[500px]`
// .split(' ')
// .map(style => `@@[&_textarea]:!void-${style}`) // apply styles to ancestor input and textarea elements
// .map(style => `@@[&_textarea]:!void-${style}`) // apply styles to ancestor textarea elements
// .join(' ') +
// ` outline-none`
// .split(' ')
// .map(style => `@@[&_div.monaco-inputbox]:!void-${style}`) // apply styles to ancestor input and textarea elements
// .map(style => `@@[&_div.monaco-inputbox]:!void-${style}`)
// .join(' ');
`@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_textarea]:!void-min-h-[81px] @@[&_textarea]:!void-max-h-[500px]@@[&_div.monaco-inputbox]:!void- @@[&_div.monaco-inputbox]:!void-outline-none`
}
@@ -508,27 +535,14 @@ export const SidebarChat = () => {
{/* submit / stop button */}
{isLoading ?
// stop button
<button
className={`size-[20px] rounded-full bg-white cursor-pointer flex items-center justify-center`}
<ButtonStop
onClick={onAbort}
type='button'
>
<IconSquare size={16} className="stroke-[2]" />
</button>
/>
:
// submit button (up arrow)
<button
className={`size-[20px] rounded-full shrink-0 grow-0 cursor-pointer
${isDisabled ?
'bg-vscode-disabled-fg' // cursor-not-allowed
: 'bg-white' // cursor-pointer
}
`}
<ButtonSubmit
disabled={isDisabled}
type='submit'
>
<IconArrowUp size={20} className="stroke-[2]" />
</button>
/>
}
</div>
@@ -8,6 +8,15 @@
@tailwind utilities;
@layer components {
.select-ellipsis select {
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 24px;
}
}
/* html {
font-size: var(--vscode-font-size);
}
@@ -9,8 +9,6 @@ import { IInputBoxStyles, InputBox } from '../../../../../../../base/browser/ui/
import { defaultInputBoxStyles, defaultSelectBoxStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
import { DomScrollableElement } from '../../../../../../../base/browser/ui/scrollbar/scrollableElement.js';
import { ScrollableElementCreationOptions } from '../../../../../../../base/browser/ui/scrollbar/scrollableElementOptions.js';
@@ -106,7 +104,7 @@ export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectB
let containerRef = useRef<HTMLDivElement | null>(null);
return <WidgetComponent
className='text-ellipsis whitespace-nowrap pr-6'
className='@@select-ellipsis'
ctor={SelectBox}
propsFn={useCallback((container) => {
containerRef.current = container
@@ -8,8 +8,7 @@ import * as ReactDOM from 'react-dom/client'
import { _registerServices } from './services.js';
import { ReactServicesType } from '../../../helpers/reactServicesHelper.js';
export const mountFnGenerator = (Component: (params: any) => React.ReactNode) => (rootElement: HTMLElement, services: ReactServicesType) => {
export const mountFnGenerator = (Component: (params: any) => React.ReactNode) => (rootElement: HTMLElement, services: ReactServicesType, props?: any) => {
if (typeof document === 'undefined') {
console.error('index.tsx error: document was undefined')
return
@@ -19,7 +18,7 @@ export const mountFnGenerator = (Component: (params: any) => React.ReactNode) =>
const root = ReactDOM.createRoot(rootElement)
root.render(<Component />); // tailwind dark theme indicator
root.render(<Component {...props} />); // tailwind dark theme indicator
return disposables
}
@@ -12,6 +12,7 @@ import { ReactServicesType } from '../../../helpers/reactServicesHelper.js'
import { VoidSidebarState } from '../../../sidebarStateService.js'
import { VoidSettingsState } from '../../../../../../../platform/void/common/voidSettingsService.js'
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'
import { VoidQuickEditState } from '../../../quickEditStateService.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
@@ -20,6 +21,9 @@ let services: ReactServicesType
// even if React hasn't mounted yet, the variables are always updated to the latest state.
// React listens by adding a setState function to these listeners.
let quickEditState: VoidQuickEditState
const quickEditStateListeners: Set<(s: VoidQuickEditState) => void> = new Set()
let sidebarState: VoidSidebarState
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
@@ -50,7 +54,15 @@ export const _registerServices = (services_: ReactServicesType) => {
wasCalled = true
services = services_
const { sidebarStateService, settingsStateService, threadsStateService, refreshModelService, themeService } = services
const { sidebarStateService, quickEditStateService, settingsStateService, threadsStateService, refreshModelService, themeService, } = services
quickEditState = quickEditStateService.state
disposables.push(
quickEditStateService.onDidChangeState(() => {
quickEditState = quickEditStateService.state
quickEditStateListeners.forEach(l => l(quickEditState))
})
)
sidebarState = sidebarStateService.state
disposables.push(
@@ -106,6 +118,16 @@ export const useService = <T extends keyof ReactServicesType,>(serviceName: T):
// -- state of services --
export const useQuickEditState = () => {
const [s, ss] = useState(quickEditState)
useEffect(() => {
ss(quickEditState)
quickEditStateListeners.add(ss)
return () => { quickEditStateListeners.delete(ss) }
}, [ss])
return s
}
export const useSidebarState = () => {
const [s, ss] = useState(sidebarState)
useEffect(() => {
@@ -146,38 +146,39 @@ export const ModelDump = () => {
const ProviderSetting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => {
const { title, placeholder } = displayInfoOfSettingName(providerName, settingName)
const { title, placeholder, } = displayInfoOfSettingName(providerName, settingName)
const voidSettingsService = useService('settingsStateService')
let weChangedTextRef = false
return <><ErrorBoundary>
<label>{title}</label>
<VoidInputBox
placeholder={placeholder}
onChangeText={useCallback((newVal) => {
if (weChangedTextRef) return
voidSettingsService.setSettingOfProvider(providerName, settingName, newVal)
}, [voidSettingsService, providerName, settingName])}
return <ErrorBoundary>
<div className='my-1'>
<VoidInputBox
placeholder={`Enter your ${title} here (${placeholder}).`}
onChangeText={useCallback((newVal) => {
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}
/>
</ErrorBoundary></>
// 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}
/>
</div>
</ErrorBoundary>
}
@@ -255,6 +256,7 @@ export const Settings = () => {
<AddModelButton />
<RefreshableModels />
</ErrorBoundary>
<h2 className={`text-3xl mt-4 mb-2`}>Providers</h2>
<ErrorBoundary>
<VoidProviderSettings />
</ErrorBoundary>
@@ -9,6 +9,7 @@ export default defineConfig({
entry: [
'./src2/sidebar-tsx/index.tsx',
'./src2/void-settings-tsx/index.tsx',
'./src2/ctrl-k-tsx/index.tsx',
'./src2/diff/index.tsx',
],
outDir: './out',
@@ -53,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<void> {
@@ -66,7 +67,7 @@ registerAction2(class extends Action2 {
const stateService = accessor.get(ISidebarStateService)
const metricsService = accessor.get(IMetricsService)
metricsService.capture('Chat Navigation', { type: 'Ctrl+L' })
metricsService.capture('User Action', { type: 'Ctrl+L' })
stateService.setState({ isHistoryOpen: false, currentTab: 'chat' })
stateService.fireFocusChat()
@@ -7,11 +7,14 @@
// register inline diffs
import './inlineDiffsService.js'
// register Sidebar pane, state, actions (keybinds, menus)
// register Sidebar pane, state, actions (keybinds, menus) (Ctrl+L)
import './sidebarActions.js'
import './sidebarPane.js'
import './sidebarStateService.js'
// register quick edit (Ctrl+K)
import './quickEditActions.js'
// register Thread History
import './threadHistoryService.js'