Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d6074e7ab5 | |||
| 5a26aa8746 | |||
| 8661fe1417 |
@@ -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';
|
||||
|
||||
+50
-28
@@ -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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user