one contribution, one directory — finish the upstream merge

The rebrand renamed platform/void and contrib/void/browser to code; upstream then
moved platform/void into contrib/void/{common,electron-main}. Git could not see
those as the same move, so the merge kept both sides and the contribution came out
split across three directories — browser/ under contrib/code, common/ and
electron-main/ under contrib/void, a pre-move copy under platform/code. Every
../common/x.js import out of browser/ dangled. Shared-file conflicts were settled
by replacing upstream's lines instead of keeping both, so extHost.protocol lost
MainThreadChatStatus and ExtHostMcp and telemetryService lost the body of _log.

The fork's only change to src/ is that rename, so the tree it should have is
upstream's with the rename applied. This is that tree. What upstream deleted goes
with it: inlineDiffsService and the InlineDiff extension host API.

buildreact joins the gate because react/out is generated rather than committed and
the workbench imports its mount functions from there — the order HOW_TO_CONTRIBUTE
already gives. compile alone reported 281 errors; ci, buildreact and compile
together finish with 0.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-10 00:07:08 -07:00
co-authored by Hanzo Dev
parent 7e3545c695
commit d5f28e6ed9
161 changed files with 2175 additions and 7779 deletions
+3 -3
View File
@@ -22,9 +22,9 @@ product.overrides.json
*.snap.actual
.vscode-test
# Void added these:
# Code added these:
.tmp/
.tmp2/
.tool-versions
src/vs/workbench/contrib/void/browser/react/out/**
src/vs/workbench/contrib/void/browser/react/src2/**
src/vs/workbench/contrib/code/browser/react/out/**
src/vs/workbench/contrib/code/browser/react/src2/**
+2 -2
View File
@@ -149,8 +149,8 @@ Edit: far too many changes to track... this is old
- src/vs/workbench/api/common/{extHost.api.impl.ts | extHostApiCommands.ts}
- src/vs/workbench/workbench.common.main.ts
- src/vs/workbench/contrib/void/\*
- extensions/void/\*
- src/vs/workbench/contrib/code/\*
- extensions/code/\*
- .github/\*
- .vscode/settings/\*
+1 -1
View File
@@ -1,3 +1,3 @@
This folder is no longer relevant, since Code is no longer just an extension.
See the new Code code in `src/vs/workbench/contrib/void` (and a few other locations - you can just search "Code").
See the new Code code in `src/vs/workbench/contrib/code` (and a few other locations - you can just search "Code").
+8 -3
View File
@@ -1,6 +1,11 @@
# This fork has had no build gate at all — .github/workflows holds only
# triage.yml. `npm ci && npm run compile` is the repo's real build (gulp over
# the whole workbench plus every bundled extension), so it is what runs.
# triage.yml. The repo's real build is `npm ci && npm run buildreact && npm run
# compile`, the order HOW_TO_CONTRIBUTE.md gives, so it is what runs.
#
# buildreact is load-bearing, not a convenience: it bundles the sidebar, settings
# and quick-edit React roots into src/vs/workbench/contrib/code/browser/react/out,
# which is generated rather than committed. The workbench imports its mount
# functions from there, so without it compile cannot resolve them.
#
# The apt line is not scaffolding: on Linux the postinstall step compiles
# native-keymap, node-pty and keytar, which need the X11/kerberos/libsecret
@@ -9,4 +14,4 @@
# reusable runs gates under `bash -c`, with no set -e of its own.
test:
- name: build
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends build-essential g++ python3 libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev && npm ci && npm run compile
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends build-essential g++ python3 libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev && npm ci && npm run buildreact && npm run compile
+2 -7
View File
@@ -1,7 +1,6 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
// "jsx": "react", // <-- Code added this
"esModuleInterop": true,
"removeComments": false,
"preserveConstEnums": true,
@@ -30,11 +29,7 @@
"./*.ts",
"./typings",
"./vs/**/*.ts",
"vscode-dts/vscode.proposed.*.d.ts",
"vscode-dts/vscode.d.ts",
// Code added these:
// "./vs/workbench/contrib/void/browser/**.tsx",
// "./vs/**/*.d.mts",
"./vscode-dts/vscode.proposed.*.d.ts",
"./vscode-dts/vscode.d.ts"
]
}
@@ -26,7 +26,7 @@
}
/* custom speed & easing for loading icon */
.codicon-loading:not(.codicon-no-default-spin), /* Void changed this as it is literally broken to the !important */
.codicon-loading:not(.codicon-no-default-spin), /* Code changed this as it is literally broken to the !important */
.codicon-tree-item-loading::before {
animation-duration: 1s !important;
animation-timing-function: cubic-bezier(0.53, 0.21, 0.29, 0.67) !important;
+2 -2
View File
@@ -57,8 +57,8 @@ export type ExtensionVirtualWorkspaceSupport = {
export interface IProductConfiguration {
readonly version: string;
readonly voidVersion?: string;
readonly release?: string; // Void - VSCodium added this, we add it for TS
readonly codeVersion?: string;
readonly release?: string; // Code - VSCodium added this, we add it for TS
readonly date?: string;
readonly quality?: string;
readonly commit?: string;
File diff suppressed because one or more lines are too long
+14 -14
View File
@@ -25959,7 +25959,7 @@
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/lines-around-comment.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/prefer-find.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-confusing-non-null-assertion.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-invalid-code-type.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-invalid-void-type.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/class-methods-use-this.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/space-infix-ops.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/ban-tslint-comment.js
@@ -25980,7 +25980,7 @@
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/prefer-return-this-type.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-misused-promises.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-unused-expressions.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-invalid-code-type.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-invalid-void-type.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-type-alias.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/strict-boolean-expressions.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/type-annotation-spacing.js
@@ -26057,7 +26057,7 @@
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-unsafe-unary-minus.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/prefer-for-of.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-empty-interface.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-meaningless-code-operator.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-meaningless-void-operator.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/explicit-function-return-type.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/comma-dangle.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/index.js
@@ -26068,7 +26068,7 @@
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-unsafe-argument.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-extra-parens.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-import-type-side-effects.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-meaningless-code-operator.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-meaningless-void-operator.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/method-signature-style.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-unused-vars.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-array-delete.js
@@ -26167,7 +26167,7 @@
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-inferrable-types.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-useless-template-literals.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-unused-expressions.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-confusing-code-expression.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-confusing-void-expression.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/prefer-ts-expect-error.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-unsafe-member-access.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/prefer-namespace-keyword.js
@@ -26242,7 +26242,7 @@
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/ban-ts-comment.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/prefer-regexp-exec.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-non-null-asserted-optional-chain.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-confusing-code-expression.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-confusing-void-expression.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-for-in-array.js.map
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/require-await.js
/Users/example/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-dupe-class-members.js
@@ -26277,7 +26277,7 @@
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-unnecessary-type-arguments.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/ban-ts-comment.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-confusing-code-expression.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-confusing-void-expression.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/require-await.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-dupe-class-members.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/ban-types.md
@@ -26341,7 +26341,7 @@
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-unused-vars.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-array-delete.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/README.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-meaningless-code-operator.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-meaningless-void-operator.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/prefer-string-starts-ends-with.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-non-null-asserted-nullish-coalescing.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/prefer-for-of.md
@@ -26376,7 +26376,7 @@
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/strict-boolean-expressions.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/type-annotation-spacing.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/typedef.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-invalid-code-type.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-invalid-void-type.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-type-alias.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-unnecessary-type-constraint.md
/Users/example/node_modules/@typescript-eslint/eslint-plugin/docs/rules/no-implied-eval.md
@@ -64786,7 +64786,7 @@
/Users/example/extensions/notebook-renderers/node_modules/whatwg-url/lib/utils.js
/Users/example/extensions/notebook-renderers/node_modules/whatwg-url/lib/url-state-machine.js
/Users/example/extensions/notebook-renderers/node_modules/whatwg-url/lib/URL.js
/Users/example/extensions/notebook-renderers/node_modules/whatwg-url/lib/CodeFunction.js
/Users/example/extensions/notebook-renderers/node_modules/whatwg-url/lib/VoidFunction.js
/Users/example/extensions/notebook-renderers/node_modules/whatwg-url/LICENSE.txt
/Users/example/extensions/notebook-renderers/node_modules/domexception/index.js
/Users/example/extensions/notebook-renderers/node_modules/domexception/webidl2js-wrapper.js
@@ -64797,7 +64797,7 @@
/Users/example/extensions/notebook-renderers/node_modules/domexception/lib/utils.js
/Users/example/extensions/notebook-renderers/node_modules/domexception/lib/DOMException-impl.js
/Users/example/extensions/notebook-renderers/node_modules/domexception/lib/DOMException.js
/Users/example/extensions/notebook-renderers/node_modules/domexception/lib/CodeFunction.js
/Users/example/extensions/notebook-renderers/node_modules/domexception/lib/VoidFunction.js
/Users/example/extensions/notebook-renderers/node_modules/domexception/LICENSE.txt
/Users/example/extensions/notebook-renderers/node_modules/combined-stream/License
/Users/example/extensions/notebook-renderers/node_modules/combined-stream/Readme.md
@@ -65079,7 +65079,7 @@
/Users/example/extensions/notebook-renderers/node_modules/jsdom/lib/jsdom/living/generated/Text.js
/Users/example/extensions/notebook-renderers/node_modules/jsdom/lib/jsdom/living/generated/HTMLTableElement.js
/Users/example/extensions/notebook-renderers/node_modules/jsdom/lib/jsdom/living/generated/HTMLMenuElement.js
/Users/example/extensions/notebook-renderers/node_modules/jsdom/lib/jsdom/living/generated/CodeFunction.js
/Users/example/extensions/notebook-renderers/node_modules/jsdom/lib/jsdom/living/generated/VoidFunction.js
/Users/example/extensions/notebook-renderers/node_modules/jsdom/lib/jsdom/living/generated/PopStateEvent.js
/Users/example/extensions/notebook-renderers/node_modules/jsdom/lib/jsdom/living/generated/Document.js
/Users/example/extensions/notebook-renderers/node_modules/jsdom/lib/jsdom/living/generated/FormData.js
@@ -73339,7 +73339,7 @@
/Users/example/extensions/markdown-language-features/server/node_modules/vscode-languageserver/node.cmd
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/LICENSE
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/CHANGELOG.md
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/code-tag.js
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/void-tag.js
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/parse.d.ts
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/nodes/html.js
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/nodes/type.js
@@ -73351,7 +73351,7 @@
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/nodes/text.d.ts
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/nodes/node.d.ts
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/nodes/text.js
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/code-tag.d.ts
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/void-tag.d.ts
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/index.js
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/valid.d.ts
/Users/example/extensions/markdown-language-features/server/node_modules/node-html-parser/dist/valid.js
+21 -36
View File
@@ -123,11 +123,16 @@ import { IWebContentExtractorService } from '../../platform/webContentExtractor/
import { NativeWebContentExtractorService } from '../../platform/webContentExtractor/electron-main/webContentExtractorService.js';
import ErrorTelemetry from '../../platform/telemetry/electron-main/errorTelemetry.js';
import { LLMMessageChannel } from '../../platform/void/electron-main/llmMessageChannel.js';
import { IMetricsService } from '../../platform/void/common/metricsService.js';
import { MetricsMainService } from '../../platform/void/electron-main/metricsMainService.js';
import { CodeMainUpdateService } from '../../platform/void/electron-main/codeUpdateMainService.js';
import { ICodeUpdateService } from '../../platform/void/common/codeUpdateService.js';
// in theory this is not allowed
// ignore the eslint errors below
import { IMetricsService } from '../../workbench/contrib/code/common/metricsService.js';
import { ICodeUpdateService } from '../../workbench/contrib/code/common/codeUpdateService.js';
import { MetricsMainService } from '../../workbench/contrib/code/electron-main/metricsMainService.js';
import { CodeMainUpdateService } from '../../workbench/contrib/code/electron-main/codeUpdateMainService.js';
import { LLMMessageChannel } from '../../workbench/contrib/code/electron-main/sendLLMMessageChannel.js';
import { CodeSCMService } from '../../workbench/contrib/code/electron-main/codeSCMMainService.js';
import { ICodeSCMService } from '../../workbench/contrib/code/common/codeSCMTypes.js';
import { MCPChannel } from '../../workbench/contrib/code/electron-main/mcpChannel.js';
/**
* The main VS Code application. There will only ever be one instance,
* even if the user starts many instances (e.g. from the command line).
@@ -522,35 +527,6 @@ export class CodeApplication extends Disposable {
});
//#endregion
// //#region Code IPC
// validatedIpcMain.handle('vscode:sendLLMMessage', async (event, data) => {
// try {
// await this.sendLLMMessage(data);
// } catch (error) {
// console.error('Error sending LLM message:', error);
// }
// });
// //#endregion
}
private onUnexpectedError(error: Error): void {
if (error) {
// take only the message and stack property
const friendlyError = {
message: `[uncaught exception in main]: ${error.message}`,
stack: error.stack
};
// handle on client side
this.windowsMainService?.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
}
this.logService.error(`[uncaught exception in main]: ${error}`);
if (error.stack) {
this.logService.error(error.stack);
}
}
async startup(): Promise<void> {
@@ -1128,6 +1104,7 @@ export class CodeApplication extends Disposable {
// Code main process services (required for services with a channel for comm between browser and electron-main (node))
services.set(IMetricsService, new SyncDescriptor(MetricsMainService, undefined, false));
services.set(ICodeUpdateService, new SyncDescriptor(CodeMainUpdateService, undefined, false));
services.set(ICodeSCMService, new SyncDescriptor(CodeSCMService, undefined, false));
// Default Extensions Profile Init
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true));
@@ -1266,8 +1243,16 @@ export class CodeApplication extends Disposable {
const codeUpdatesChannel = ProxyChannel.fromService(accessor.get(ICodeUpdateService), disposables);
mainProcessElectronServer.registerChannel('code-channel-update', codeUpdatesChannel);
const llmMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
mainProcessElectronServer.registerChannel('code-channel-llmMessageService', llmMessageChannel);
const sendLLMMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
mainProcessElectronServer.registerChannel('code-channel-llmMessage', sendLLMMessageChannel);
// Code added this
const codeSCMChannel = ProxyChannel.fromService(accessor.get(ICodeSCMService), disposables);
mainProcessElectronServer.registerChannel('code-channel-scm', codeSCMChannel);
// Code added this
const mcpChannel = new MCPChannel();
mainProcessElectronServer.registerChannel('code-channel-mcp', mcpChannel);
// Extension Host Debug Broadcasting
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
+1 -1
View File
@@ -3570,7 +3570,7 @@ class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggesti
const defaults: InternalQuickSuggestionsOptions = {
other: 'on',
comments: 'off',
strings: 'on' // Void changed this setting
strings: 'on' // Code changed this setting
};
const types: IJSONSchema[] = [
{ type: 'boolean' },
@@ -209,11 +209,6 @@ export class EditorWorker implements IDisposable, IWorkerTextModelSyncChannelSer
private static readonly _diffLimit = 100000;
public async $computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise<TextEdit[]> {
return this.$Code_computeMoreMinimalEdits(modelUrl, edits, pretty)
}
// Code added this as non async
public $Code_computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): TextEdit[] {
const model = this._getModel(modelUrl);
if (!model) {
return edits;
@@ -122,7 +122,7 @@ export class SmartSelectController implements IEditorContribution {
this._state = this._state.map(state => state.mov(forward));
const newSelections = this._state.map(state => Selection.fromPositions(state.ranges[state.index].getStartPosition(), state.ranges[state.index].getEndPosition()));
// Void changed this to skip over added whitespace when using smartSelect
// Code changed this to skip over added whitespace when using smartSelect
// // Store the original selections for comparison
// const originalSelections = selections;
@@ -418,7 +418,7 @@ suite('SnippetController2', function () {
model.setValue('');
editor.setSelection(new Selection(1, 1, 1, 1));
ctrl.insert('.REGION ${2:FUNCTION_NAME}\nCREATE.FUNCTION ${1:CODE} ${2:FUNCTION_NAME}(${3:})\n\t${4:}\nEND\n.ENDREGION$0');
ctrl.insert('.REGION ${2:FUNCTION_NAME}\nCREATE.FUNCTION ${1:VOID} ${2:FUNCTION_NAME}(${3:})\n\t${4:}\nEND\n.ENDREGION$0');
assertSelections(editor, new Selection(2, 17, 2, 21));
ctrl.next();
@@ -438,7 +438,7 @@ suite('SnippetController2', function () {
model.setValue('');
editor.setSelection(new Selection(1, 1, 1, 1));
ctrl.insert('.REGION ${2:FUNCTION_NAME}\nCREATE.FUNCTION ${1:CODE} ${2:FUNCTION_NAME}(${3:})\n\t${4:}\nEND\n.ENDREGION$0');
ctrl.insert('.REGION ${2:FUNCTION_NAME}\nCREATE.FUNCTION ${1:VOID} ${2:FUNCTION_NAME}(${3:})\n\t${4:}\nEND\n.ENDREGION$0');
assertSelections(editor, new Selection(2, 17, 2, 21));
ctrl.next();
@@ -1,326 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Emitter, Event } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { deepClone } from '../../../base/common/objects.js';
import { IEncryptionService } from '../../encryption/common/encryptionService.js';
import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
import { IMetricsService } from './metricsService.js';
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultNames, CodeModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings } from './codeSettingsTypes.js';
const STORAGE_KEY = 'void.settingsServiceStorage'
type SetSettingOfProviderFn = <S extends SettingName>(
providerName: ProviderName,
settingName: S,
newVal: SettingsOfProvider[ProviderName][S extends keyof SettingsOfProvider[ProviderName] ? S : never],
) => Promise<void>;
type SetModelSelectionOfFeatureFn = <K extends FeatureName>(
featureName: K,
newVal: ModelSelectionOfFeature[K],
options?: { doNotApplyEffects?: true }
) => Promise<void>;
type SetGlobalSettingFn = <T extends GlobalSettingName, >(settingName: T, newVal: GlobalSettings[T]) => void;
export type ModelOption = { name: string, selection: ModelSelection }
export type CodeSettingsState = {
readonly settingsOfProvider: SettingsOfProvider; // optionsOfProvider
readonly modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature
readonly globalSettings: GlobalSettings;
readonly _modelOptions: ModelOption[] // computed based on the two above items
}
type RealCodeSettings = Exclude<keyof CodeSettingsState, '_modelOptions'>
type EventProp<T extends RealCodeSettings = RealCodeSettings> = T extends 'globalSettings' ? [T, keyof CodeSettingsState[T]] : T | 'all'
export interface ICodeSettingsService {
readonly _serviceBrand: undefined;
readonly state: CodeSettingsState; // in order to play nicely with react, you should immutably change state
readonly waitForInitState: Promise<void>;
onDidChangeState: Event<EventProp>;
setSettingOfProvider: SetSettingOfProviderFn;
setModelSelectionOfFeature: SetModelSelectionOfFeatureFn;
setGlobalSetting: SetGlobalSettingFn;
setAutodetectedModels(providerName: ProviderName, modelNames: string[], logging: object): void;
toggleModelHidden(providerName: ProviderName, modelName: string): void;
addModel(providerName: ProviderName, modelName: string): void;
deleteModel(providerName: ProviderName, modelName: string): boolean;
}
let _computeModelOptions = (settingsOfProvider: SettingsOfProvider) => {
let modelOptions: ModelOption[] = []
for (const providerName of providerNames) {
const providerConfig = settingsOfProvider[providerName]
if (!providerConfig._enabled) continue // if disabled, don't display model options
for (const { modelName, isHidden } of providerConfig.models) {
if (isHidden) continue
modelOptions.push({ name: `${modelName} (${providerName})`, selection: { providerName, modelName } })
}
}
return modelOptions
}
const defaultState = () => {
const d: CodeSettingsState = {
settingsOfProvider: deepClone(defaultSettingsOfProvider),
modelSelectionOfFeature: { 'Ctrl+L': null, 'Ctrl+K': null, 'Autocomplete': null, 'FastApply': null },
globalSettings: deepClone(defaultGlobalSettings),
_modelOptions: _computeModelOptions(defaultSettingsOfProvider), // computed
}
return d
}
export const ICodeSettingsService = createDecorator<ICodeSettingsService>('CodeSettingsService');
class CodeSettingsService extends Disposable implements ICodeSettingsService {
_serviceBrand: undefined;
private readonly _onDidChangeState = new Emitter<EventProp>();
readonly onDidChangeState: Event<EventProp> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
state: CodeSettingsState;
waitForInitState: Promise<void> // await this if you need a valid state initially
constructor(
@IStorageService private readonly _storageService: IStorageService,
@IEncryptionService private readonly _encryptionService: IEncryptionService,
@IMetricsService private readonly _metricsService: IMetricsService,
// could have used this, but it's clearer the way it is (+ slightly different eg StorageTarget.USER)
// @ISecretStorageService private readonly _secretStorageService: ISecretStorageService,
) {
super()
// at the start, we haven't read the partial config yet, but we need to set state to something
this.state = defaultState()
let resolver: () => void = () => { }
this.waitForInitState = new Promise((res, rej) => resolver = res)
// read and update the actual state immediately
this._readState().then(readS => {
// the stored data structure might be outdated, so we need to update it here (can do a more general solution later when we need to)
readS = {
...readS,
settingsOfProvider: {
// A HACK BECAUSE WE ADDED DEEPSEEK (did not exist before, comes before readS)
...{ deepseek: defaultSettingsOfProvider.deepseek },
// A HACK BECAUSE WE ADDED MISTRAL (did not exist before, comes before readS)
...{ mistral: defaultSettingsOfProvider.mistral },
...readS.settingsOfProvider,
// A HACK BECAUSE WE ADDED NEW GEMINI MODELS (existed before, comes after readS)
gemini: {
...readS.settingsOfProvider.gemini,
models: [
...readS.settingsOfProvider.gemini.models,
...defaultSettingsOfProvider.gemini.models.filter(m => /* if cant find the model in readS (yes this is O(n^2), very small) */ !readS.settingsOfProvider.gemini.models.find(m2 => m2.modelName === m.modelName))
]
}
},
modelSelectionOfFeature: {
// A HACK BECAUSE WE ADDED FastApply
...{ 'FastApply': null },
...readS.modelSelectionOfFeature,
}
}
this.state = readS
resolver()
this._onDidChangeState.fire('all')
})
}
private async _readState(): Promise<CodeSettingsState> {
const encryptedState = this._storageService.get(STORAGE_KEY, StorageScope.APPLICATION)
if (!encryptedState)
return defaultState()
const stateStr = await this._encryptionService.decrypt(encryptedState)
return JSON.parse(stateStr)
}
private async _storeState() {
const state = this.state
const encryptedState = await this._encryptionService.encrypt(JSON.stringify(state))
this._storageService.store(STORAGE_KEY, encryptedState, StorageScope.APPLICATION, StorageTarget.USER);
}
setSettingOfProvider: SetSettingOfProviderFn = async (providerName, settingName, newVal) => {
const newModelSelectionOfFeature = this.state.modelSelectionOfFeature
const newSettingsOfProvider = {
...this.state.settingsOfProvider,
[providerName]: {
...this.state.settingsOfProvider[providerName],
[settingName]: newVal,
}
}
const newGlobalSettings = this.state.globalSettings
// if changed models or enabled a provider, recompute models list
const modelsListChanged = settingName === 'models' || settingName === '_enabled'
const newModelsList = modelsListChanged ? _computeModelOptions(newSettingsOfProvider) : this.state._modelOptions
const newState: CodeSettingsState = {
modelSelectionOfFeature: newModelSelectionOfFeature,
settingsOfProvider: newSettingsOfProvider,
globalSettings: newGlobalSettings,
_modelOptions: newModelsList,
}
// this must go above this.setanythingelse()
this.state = newState
// if the user-selected model is no longer in the list, update the selection for each feature that needs it to something relevant (the 0th model available, or null)
if (modelsListChanged) {
for (const featureName of featureNames) {
const currentSelection = newModelSelectionOfFeature[featureName]
const selnIdx = currentSelection === null ? -1 : newModelsList.findIndex(m => modelSelectionsEqual(m.selection, currentSelection))
if (selnIdx === -1) {
if (newModelsList.length !== 0)
this.setModelSelectionOfFeature(featureName, newModelsList[0].selection, { doNotApplyEffects: true })
else
this.setModelSelectionOfFeature(featureName, null, { doNotApplyEffects: true })
}
}
}
await this._storeState()
this._onDidChangeState.fire('settingsOfProvider')
}
setGlobalSetting: SetGlobalSettingFn = async (settingName, newVal) => {
const newState: CodeSettingsState = {
...this.state,
globalSettings: {
...this.state.globalSettings,
[settingName]: newVal
}
}
this.state = newState
await this._storeState()
this._onDidChangeState.fire(['globalSettings', settingName])
}
setModelSelectionOfFeature: SetModelSelectionOfFeatureFn = async (featureName, newVal, options) => {
const newState: CodeSettingsState = {
...this.state,
modelSelectionOfFeature: {
...this.state.modelSelectionOfFeature,
[featureName]: newVal
}
}
this.state = newState
if (options?.doNotApplyEffects)
return
await this._storeState()
this._onDidChangeState.fire('modelSelectionOfFeature')
}
setAutodetectedModels(providerName: ProviderName, newDefaultModelNames: string[], logging: object) {
const { models } = this.state.settingsOfProvider[providerName]
const old_names = models.map(m => m.modelName)
const newDefaultModels = modelInfoOfDefaultNames(newDefaultModelNames, { isAutodetected: true, existingModels: models })
const newModels = [
...newDefaultModels,
...models.filter(m => !m.isDefault), // keep any non-default models
]
this.setSettingOfProvider(providerName, 'models', newModels)
// if the models changed, log it
const new_names = newModels.map(m => m.modelName)
if (!(old_names.length === new_names.length
&& old_names.every((_, i) => old_names[i] === new_names[i])
)) {
this._metricsService.capture('Autodetect Models', { providerName, newModels, ...logging })
}
}
toggleModelHidden(providerName: ProviderName, modelName: string) {
const { models } = this.state.settingsOfProvider[providerName]
const modelIdx = models.findIndex(m => m.modelName === modelName)
if (modelIdx === -1) return
const newIsHidden = !models[modelIdx].isHidden
const newModels: CodeModelInfo[] = [
...models.slice(0, modelIdx),
{ ...models[modelIdx], isHidden: newIsHidden },
...models.slice(modelIdx + 1, Infinity)
]
this.setSettingOfProvider(providerName, 'models', newModels)
this._metricsService.capture('Toggle Model Hidden', { providerName, modelName, newIsHidden })
}
addModel(providerName: ProviderName, modelName: string) {
const { models } = this.state.settingsOfProvider[providerName]
const existingIdx = models.findIndex(m => m.modelName === modelName)
if (existingIdx !== -1) return // if exists, do nothing
const newModels = [
...models,
{ modelName, isDefault: false, isHidden: false }
]
this.setSettingOfProvider(providerName, 'models', newModels)
this._metricsService.capture('Add Model', { providerName, modelName })
}
deleteModel(providerName: ProviderName, modelName: string): boolean {
const { models } = this.state.settingsOfProvider[providerName]
const delIdx = models.findIndex(m => m.modelName === modelName)
if (delIdx === -1) return false
const newModels = [
...models.slice(0, delIdx), // delete the idx
...models.slice(delIdx + 1, Infinity)
]
this.setSettingOfProvider(providerName, 'models', newModels)
this._metricsService.capture('Delete Model', { providerName, modelName })
return true
}
}
registerSingleton(ICodeSettingsService, CodeSettingsService, InstantiationType.Eager);
@@ -1,481 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
export type CodeModelInfo = {
modelName: string,
isDefault: boolean, // whether or not it's a default for its provider
isHidden: boolean, // whether or not the user is hiding it
isAutodetected?: boolean, // whether the model was autodetected by polling
}
// creates `modelInfo` from `modelNames`
export const modelInfoOfDefaultNames = (modelNames: string[], options?: { isAutodetected: true, existingModels: CodeModelInfo[] }): CodeModelInfo[] => {
const { isAutodetected, existingModels } = options ?? {}
if (!existingModels) { // default settings
return modelNames.map((modelName, i) => ({
modelName,
isDefault: true,
isAutodetected: isAutodetected,
isHidden: modelNames.length >= 10 // hide all models if there are a ton of them, and make user enable them individually
}))
} else { // settings if there are existing models (keep existing `isHidden` property)
const existingModelsMap: Record<string, CodeModelInfo> = {}
for (const existingModel of existingModels) {
existingModelsMap[existingModel.modelName] = existingModel
}
return modelNames.map((modelName, i) => ({
modelName,
isDefault: true,
isAutodetected: isAutodetected,
isHidden: !!existingModelsMap[modelName]?.isHidden,
}))
}
}
// https://docs.anthropic.com/en/docs/about-claude/models
export const defaultAnthropicModels = modelInfoOfDefaultNames([
'claude-3-5-sonnet-20241022',
'claude-3-5-haiku-20241022',
'claude-3-opus-20240229',
'claude-3-sonnet-20240229',
// 'claude-3-haiku-20240307',
])
// https://platform.openai.com/docs/models/gp
export const defaultOpenAIModels = modelInfoOfDefaultNames([
'o1-preview',
'o1-mini',
'gpt-4o',
'gpt-4o-mini',
// 'gpt-4o-2024-05-13',
// 'gpt-4o-2024-08-06',
// 'gpt-4o-mini-2024-07-18',
// 'gpt-4-turbo',
// 'gpt-4-turbo-2024-04-09',
// 'gpt-4-turbo-preview',
// 'gpt-4-0125-preview',
// 'gpt-4-1106-preview',
// 'gpt-4',
// 'gpt-4-0613',
// 'gpt-3.5-turbo-0125',
// 'gpt-3.5-turbo',
// 'gpt-3.5-turbo-1106',
])
// https://platform.openai.com/docs/models/gp
export const defaultDeepseekModels = modelInfoOfDefaultNames([
'deepseek-chat',
'deepseek-reasoner',
])
// https://console.groq.com/docs/models
export const defaultGroqModels = modelInfoOfDefaultNames([
"llama3-70b-8192",
"llama-3.3-70b-versatile",
"llama-3.1-8b-instant",
"gemma2-9b-it",
"mixtral-8x7b-32768"
])
export const defaultGeminiModels = modelInfoOfDefaultNames([
'gemini-1.5-flash',
'gemini-1.5-pro',
'gemini-1.5-flash-8b',
'gemini-2.0-flash-exp',
'gemini-2.0-flash-thinking-exp-1219',
'learnlm-1.5-pro-experimental'
])
export const defaultMistralModels = modelInfoOfDefaultNames([
"codestral-latest",
"open-codestral-mamba",
"open-mistral-nemo",
"mistral-large-latest",
"pixtral-large-latest",
"ministral-3b-latest",
"ministral-8b-latest",
"mistral-small-latest",
])
// export const parseMaxTokensStr = (maxTokensStr: string) => {
// // parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
// const int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
// if (Number.isNaN(int))
// return undefined
// return int
// }
export const anthropicMaxPossibleTokens = (modelName: string) => {
if (modelName === 'claude-3-5-sonnet-20241022'
|| modelName === 'claude-3-5-haiku-20241022')
return 8192
if (modelName === 'claude-3-opus-20240229'
|| modelName === 'claude-3-sonnet-20240229'
|| modelName === 'claude-3-haiku-20240307')
return 4096
return 1024 // return a reasonably small number if they're using a different model
}
type UnionOfKeys<T> = T extends T ? keyof T : never;
export const defaultProviderSettings = {
anthropic: {
apiKey: '',
},
openAI: {
apiKey: '',
},
deepseek: {
apiKey: '',
},
ollama: {
endpoint: 'http://127.0.0.1:11434',
},
openRouter: {
apiKey: '',
},
openAICompatible: {
endpoint: '',
apiKey: '',
},
gemini: {
apiKey: '',
},
groq: {
apiKey: '',
},
mistral: {
apiKey: ''
}
} as const
export type ProviderName = keyof typeof defaultProviderSettings
export const providerNames = Object.keys(defaultProviderSettings) as ProviderName[]
export const localProviderNames = ['ollama'] satisfies ProviderName[] // all local names
export const nonlocalProviderNames = providerNames.filter((name) => !(localProviderNames as string[]).includes(name)) // all non-local names
type CustomSettingName = UnionOfKeys<typeof defaultProviderSettings[ProviderName]>
type CustomProviderSettings<providerName extends ProviderName> = {
[k in CustomSettingName]: k extends keyof typeof defaultProviderSettings[providerName] ? string : undefined
}
export const customSettingNamesOfProvider = (providerName: ProviderName) => {
return Object.keys(defaultProviderSettings[providerName]) as CustomSettingName[]
}
type CommonProviderSettings = {
_enabled: boolean | undefined, // undefined initially, computed when user types in all fields
models: CodeModelInfo[],
}
export type SettingsForProvider<providerName extends ProviderName> = CustomProviderSettings<providerName> & CommonProviderSettings
// part of state
export type SettingsOfProvider = {
[providerName in ProviderName]: SettingsForProvider<providerName>
}
export type SettingName = keyof SettingsForProvider<ProviderName>
type DisplayInfoForProviderName = {
title: string,
desc?: string,
}
export const displayInfoOfProviderName = (providerName: ProviderName): DisplayInfoForProviderName => {
if (providerName === 'anthropic') {
return {
title: 'Anthropic',
}
}
else if (providerName === 'openAI') {
return {
title: 'OpenAI',
}
}
else if (providerName === 'deepseek') {
return {
title: 'DeepSeek',
}
}
else if (providerName === 'openRouter') {
return {
title: 'OpenRouter',
}
}
else if (providerName === 'ollama') {
return {
title: 'Ollama',
}
}
else if (providerName === 'openAICompatible') {
return {
title: 'OpenAI-Compatible',
}
}
else if (providerName === 'gemini') {
return {
title: 'Gemini',
}
}
else if (providerName === 'groq') {
return {
title: 'Groq',
}
}
else if (providerName === 'mistral') {
return {
title: 'Mistral',
}
}
throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`)
}
type DisplayInfo = {
title: string,
placeholder: string,
subTextMd?: string,
}
export const displayInfoOfSettingName = (providerName: ProviderName, settingName: SettingName): DisplayInfo => {
if (settingName === 'apiKey') {
return {
title: 'API Key',
// **Please follow this convention**:
// The word "key..." here is a placeholder for the hash. For example, sk-ant-key... means the key will look like sk-ant-abcdefg123...
placeholder: providerName === 'anthropic' ? 'sk-ant-key...' : // sk-ant-api03-key
providerName === 'openAI' ? 'sk-proj-key...' :
providerName === 'deepseek' ? 'sk-key...' :
providerName === 'openRouter' ? 'sk-or-key...' : // sk-or-v1-key
providerName === 'gemini' ? 'key...' :
providerName === 'groq' ? 'gsk_key...' :
providerName === 'mistral' ? 'key...' :
providerName === 'openAICompatible' ? 'sk-key...' :
'',
subTextMd: providerName === 'anthropic' ? 'Get your [API Key here](https://console.anthropic.com/settings/keys).' :
providerName === 'openAI' ? 'Get your [API Key here](https://platform.openai.com/api-keys).' :
providerName === 'deepseek' ? 'Get your [API Key here](https://platform.deepseek.com/api_keys).' :
providerName === 'openRouter' ? 'Get your [API Key here](https://openrouter.ai/settings/keys).' :
providerName === 'gemini' ? 'Get your [API Key here](https://aistudio.google.com/apikey).' :
providerName === 'groq' ? 'Get your [API Key here](https://console.groq.com/keys).' :
providerName === 'mistral' ? 'Get your [API Key here](https://console.mistral.ai/api-keys/).' :
providerName === 'openAICompatible' ? undefined :
'',
}
}
else if (settingName === 'endpoint') {
return {
title: providerName === 'ollama' ? 'Endpoint' :
providerName === 'openAICompatible' ? 'baseURL' // (do not include /chat/completions)
: '(never)',
placeholder: providerName === 'ollama' ? defaultProviderSettings.ollama.endpoint
: providerName === 'openAICompatible' ? 'https://my-website.com/v1'
: '(never)',
subTextMd: providerName === 'ollama' ? 'If you would like to change this endpoint, please read more about [Endpoints here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network).' :
undefined,
}
}
else if (settingName === '_enabled') {
return {
title: '(never)',
placeholder: '(never)',
}
}
else if (settingName === 'models') {
return {
title: '(never)',
placeholder: '(never)',
}
}
throw new Error(`displayInfo: Unknown setting name: "${settingName}"`)
}
const defaultCustomSettings: Record<CustomSettingName, undefined> = {
apiKey: undefined,
endpoint: undefined,
}
export const voidInitModelOptions = {
anthropic: {
models: defaultAnthropicModels,
},
openAI: {
models: defaultOpenAIModels,
},
deepseek: {
models: defaultDeepseekModels,
},
ollama: {
models: [],
},
openRouter: {
models: [], // any string
},
openAICompatible: {
models: [],
},
gemini: {
models: defaultGeminiModels,
},
groq: {
models: defaultGroqModels,
},
mistral: {
models: defaultMistralModels,
}
}
// used when waiting and for a type reference
export const defaultSettingsOfProvider: SettingsOfProvider = {
anthropic: {
...defaultCustomSettings,
...defaultProviderSettings.anthropic,
...voidInitModelOptions.anthropic,
_enabled: undefined,
},
openAI: {
...defaultCustomSettings,
...defaultProviderSettings.openAI,
...voidInitModelOptions.openAI,
_enabled: undefined,
},
deepseek: {
...defaultCustomSettings,
...defaultProviderSettings.deepseek,
...voidInitModelOptions.deepseek,
_enabled: undefined,
},
gemini: {
...defaultCustomSettings,
...defaultProviderSettings.gemini,
...voidInitModelOptions.gemini,
_enabled: undefined,
},
groq: {
...defaultCustomSettings,
...defaultProviderSettings.groq,
...voidInitModelOptions.groq,
_enabled: undefined,
},
ollama: {
...defaultCustomSettings,
...defaultProviderSettings.ollama,
...voidInitModelOptions.ollama,
_enabled: undefined,
},
openRouter: {
...defaultCustomSettings,
...defaultProviderSettings.openRouter,
...voidInitModelOptions.openRouter,
_enabled: undefined,
},
openAICompatible: {
...defaultCustomSettings,
...defaultProviderSettings.openAICompatible,
...voidInitModelOptions.openAICompatible,
_enabled: undefined,
},
mistral: {
...defaultCustomSettings,
...defaultProviderSettings.mistral,
...voidInitModelOptions.mistral,
_enabled: undefined,
}
}
export type ModelSelection = { providerName: ProviderName, modelName: string }
export const modelSelectionsEqual = (m1: ModelSelection, m2: ModelSelection) => {
return m1.modelName === m2.modelName && m1.providerName === m2.providerName
}
// this is a state
export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete', 'FastApply'] as const
export type ModelSelectionOfFeature = Record<(typeof featureNames)[number], ModelSelection | null>
export type FeatureName = keyof ModelSelectionOfFeature
export const displayInfoOfFeatureName = (featureName: FeatureName) => {
if (featureName === 'Autocomplete')
return 'Autocomplete'
else if (featureName === 'Ctrl+K')
return 'Quick Edit'
else if (featureName === 'Ctrl+L')
return 'Sidebar Chat'
else if (featureName === 'FastApply')
return 'Fast Apply'
else
throw new Error(`Feature Name ${featureName} not allowed`)
}
// the models of these can be refreshed (in theory all can, but not all should)
export const refreshableProviderNames = localProviderNames
export type RefreshableProviderName = typeof refreshableProviderNames[number]
export type GlobalSettings = {
autoRefreshModels: boolean;
aiInstructions: string;
}
export const defaultGlobalSettings: GlobalSettings = {
autoRefreshModels: true,
aiInstructions: '',
}
export type GlobalSettingName = keyof GlobalSettings
export const globalSettingNames = Object.keys(defaultGlobalSettings) as GlobalSettingName[]
@@ -1,179 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainSendLLMMessageParams, MainLLMMessageAbortParams, ServiceModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, MainModelListParams, OllamaModelResponse, OpenaiCompatibleModelResponse, } from './llmMessageTypes.js';
import { IChannel } from '../../../base/parts/ipc/common/ipc.js';
import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
import { generateUuid } from '../../../base/common/uuid.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { Event } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { ICodeSettingsService } from './codeSettingsService.js';
// import { INotificationService } from '../../notification/common/notification.js';
// calls channel to implement features
export const ILLMMessageService = createDecorator<ILLMMessageService>('llmMessageService');
export interface ILLMMessageService {
readonly _serviceBrand: undefined;
sendLLMMessage: (params: ServiceSendLLMMessageParams) => string | null;
abort: (requestId: string) => void;
ollamaList: (params: ServiceModelListParams<OllamaModelResponse>) => void;
openAICompatibleList: (params: ServiceModelListParams<OpenaiCompatibleModelResponse>) => void;
}
export class LLMMessageService extends Disposable implements ILLMMessageService {
readonly _serviceBrand: undefined;
private readonly channel: IChannel // LLMMessageChannel
// llmMessage
private readonly onTextHooks_llm: { [eventId: string]: ((params: EventLLMMessageOnTextParams) => void) } = {}
private readonly onFinalMessageHooks_llm: { [eventId: string]: ((params: EventLLMMessageOnFinalMessageParams) => void) } = {}
private readonly onErrorHooks_llm: { [eventId: string]: ((params: EventLLMMessageOnErrorParams) => void) } = {}
// ollamaList
private readonly onSuccess_ollama: { [eventId: string]: ((params: EventModelListOnSuccessParams<OllamaModelResponse>) => void) } = {}
private readonly onError_ollama: { [eventId: string]: ((params: EventModelListOnErrorParams<OllamaModelResponse>) => void) } = {}
// openAICompatibleList
private readonly onSuccess_openAICompatible: { [eventId: string]: ((params: EventModelListOnSuccessParams<OpenaiCompatibleModelResponse>) => void) } = {}
private readonly onError_openAICompatible: { [eventId: string]: ((params: EventModelListOnErrorParams<OpenaiCompatibleModelResponse>) => void) } = {}
constructor(
@IMainProcessService private readonly mainProcessService: IMainProcessService, // used as a renderer (only usable on client side)
@ICodeSettingsService private readonly codeSettingsService: ICodeSettingsService,
// @INotificationService private readonly notificationService: INotificationService,
) {
super()
// const service = ProxyChannel.toService<LLMMessageChannel>(mainProcessService.getChannel('code-channel-sendLLMMessage')); // lets you call it like a service
// see llmMessageChannel.ts
this.channel = this.mainProcessService.getChannel('code-channel-llmMessageService')
// .listen sets up an IPC channel and takes a few ms, so we set up listeners immediately and add hooks to them instead
// llm
this._register((this.channel.listen('onText_llm') satisfies Event<EventLLMMessageOnTextParams>)(e => {
this.onTextHooks_llm[e.requestId]?.(e)
}))
this._register((this.channel.listen('onFinalMessage_llm') satisfies Event<EventLLMMessageOnFinalMessageParams>)(e => {
this.onFinalMessageHooks_llm[e.requestId]?.(e)
this._onRequestIdDone(e.requestId)
}))
this._register((this.channel.listen('onError_llm') satisfies Event<EventLLMMessageOnErrorParams>)(e => {
console.error('Error in LLMMessageService:', JSON.stringify(e))
this.onErrorHooks_llm[e.requestId]?.(e)
this._onRequestIdDone(e.requestId)
}))
// ollama .list()
this._register((this.channel.listen('onSuccess_ollama') satisfies Event<EventModelListOnSuccessParams<OllamaModelResponse>>)(e => {
this.onSuccess_ollama[e.requestId]?.(e)
}))
this._register((this.channel.listen('onError_ollama') satisfies Event<EventModelListOnErrorParams<OllamaModelResponse>>)(e => {
this.onError_ollama[e.requestId]?.(e)
}))
// openaiCompatible .list()
this._register((this.channel.listen('onSuccess_openAICompatible') satisfies Event<EventModelListOnSuccessParams<OpenaiCompatibleModelResponse>>)(e => {
this.onSuccess_openAICompatible[e.requestId]?.(e)
}))
this._register((this.channel.listen('onError_openAICompatible') satisfies Event<EventModelListOnErrorParams<OpenaiCompatibleModelResponse>>)(e => {
this.onError_openAICompatible[e.requestId]?.(e)
}))
}
sendLLMMessage(params: ServiceSendLLMMessageParams) {
const { onText, onFinalMessage, onError, ...proxyParams } = params;
const { useProviderFor: featureName } = proxyParams
// end early if no provider
const modelSelection = this.codeSettingsService.state.modelSelectionOfFeature[featureName]
if (modelSelection === null) {
onError({ message: 'Please add a Provider in Settings!', fullError: null })
return null
}
const { providerName, modelName } = modelSelection
// add state for request id
const requestId = generateUuid();
this.onTextHooks_llm[requestId] = onText
this.onFinalMessageHooks_llm[requestId] = onFinalMessage
this.onErrorHooks_llm[requestId] = onError
const { aiInstructions } = this.codeSettingsService.state.globalSettings
const { settingsOfProvider } = this.codeSettingsService.state
// params will be stripped of all its functions over the IPC channel
this.channel.call('sendLLMMessage', {
...proxyParams,
aiInstructions,
requestId,
providerName,
modelName,
settingsOfProvider,
} satisfies MainSendLLMMessageParams);
return requestId
}
abort(requestId: string) {
this.channel.call('abort', { requestId } satisfies MainLLMMessageAbortParams);
this._onRequestIdDone(requestId)
}
ollamaList = (params: ServiceModelListParams<OllamaModelResponse>) => {
const { onSuccess, onError, ...proxyParams } = params
const { settingsOfProvider } = this.codeSettingsService.state
// add state for request id
const requestId_ = generateUuid();
this.onSuccess_ollama[requestId_] = onSuccess
this.onError_ollama[requestId_] = onError
this.channel.call('ollamaList', {
...proxyParams,
settingsOfProvider,
requestId: requestId_,
} satisfies MainModelListParams<OllamaModelResponse>)
}
openAICompatibleList = (params: ServiceModelListParams<OpenaiCompatibleModelResponse>) => {
const { onSuccess, onError, ...proxyParams } = params
const { settingsOfProvider } = this.codeSettingsService.state
// add state for request id
const requestId_ = generateUuid();
this.onSuccess_openAICompatible[requestId_] = onSuccess
this.onError_openAICompatible[requestId_] = onError
this.channel.call('openAICompatibleList', {
...proxyParams,
settingsOfProvider,
requestId: requestId_,
} satisfies MainModelListParams<OpenaiCompatibleModelResponse>)
}
_onRequestIdDone(requestId: string) {
delete this.onTextHooks_llm[requestId]
delete this.onFinalMessageHooks_llm[requestId]
delete this.onErrorHooks_llm[requestId]
delete this.onSuccess_ollama[requestId]
delete this.onError_ollama[requestId]
}
}
registerSingleton(ILLMMessageService, LLMMessageService, InstantiationType.Eager);
@@ -1,177 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { ProviderName, SettingsOfProvider } from './codeSettingsTypes.js'
export const errorDetails = (fullError: Error | null): string | null => {
if (fullError === null) {
return null
}
else if (typeof fullError === 'object') {
return JSON.stringify(fullError, null, 2)
}
else if (typeof fullError === 'string') {
return null
}
return null
}
export type OnText = (p: { newText: string, fullText: string }) => void
export type OnFinalMessage = (p: { fullText: string }) => void
export type OnError = (p: { message: string, fullError: Error | null }) => void
export type AbortRef = { current: (() => void) | null }
export type LLMMessage = {
role: 'system' | 'user' | 'assistant';
content: string;
}
export type _InternalLLMMessage = {
role: 'user' | 'assistant';
content: string;
}
type _InternalOllamaFIMMessages = {
prefix: string;
suffix: string;
stopTokens: string[];
}
type SendLLMType = {
type: 'sendLLMMessage';
messages: LLMMessage[];
} | {
type: 'ollamaFIM';
messages: _InternalOllamaFIMMessages;
}
// service types
export type ServiceSendLLMMessageParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
logging: { loggingName: string, };
useProviderFor: 'Ctrl+K' | 'Ctrl+L' | 'Autocomplete';
} & SendLLMType
// params to the true sendLLMMessage function
export type SendLLMMessageParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
logging: { loggingName: string, };
abortRef: AbortRef;
aiInstructions: string;
providerName: ProviderName;
modelName: string;
settingsOfProvider: SettingsOfProvider;
} & SendLLMType
// can't send functions across a proxy, use listeners instead
export type BlockedMainLLMMessageParams = 'onText' | 'onFinalMessage' | 'onError' | 'abortRef'
export type MainSendLLMMessageParams = Omit<SendLLMMessageParams, BlockedMainLLMMessageParams> & { requestId: string } & SendLLMType
export type MainLLMMessageAbortParams = { requestId: string }
export type EventLLMMessageOnTextParams = Parameters<OnText>[0] & { requestId: string }
export type EventLLMMessageOnFinalMessageParams = Parameters<OnFinalMessage>[0] & { requestId: string }
export type EventLLMMessageOnErrorParams = Parameters<OnError>[0] & { requestId: string }
export type _InternalSendLLMMessageFnType = (
params: {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
providerName: ProviderName;
settingsOfProvider: SettingsOfProvider;
modelName: string;
_setAborter: (aborter: () => void) => void;
messages: _InternalLLMMessage[];
}
) => void
export type _InternalOllamaFIMMessageFnType = (
params: {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
providerName: ProviderName;
settingsOfProvider: SettingsOfProvider;
modelName: string;
_setAborter: (aborter: () => void) => void;
messages: _InternalOllamaFIMMessages;
}
) => void
// service -> main -> internal -> event (back to main)
// (browser)
// These are from 'ollama' SDK
interface OllamaModelDetails {
parent_model: string;
format: string;
family: string;
families: string[];
parameter_size: string;
quantization_level: string;
}
export type OllamaModelResponse = {
name: string;
modified_at: Date;
size: number;
digest: string;
details: OllamaModelDetails;
expires_at: Date;
size_vram: number;
}
export type OpenaiCompatibleModelResponse = {
id: string;
created: number;
object: 'model';
owned_by: string;
}
// params to the true list fn
export type ModelListParams<modelResponse> = {
settingsOfProvider: SettingsOfProvider;
onSuccess: (param: { models: modelResponse[] }) => void;
onError: (param: { error: string }) => void;
}
// params to the service
export type ServiceModelListParams<modelResponse> = {
onSuccess: (param: { models: modelResponse[] }) => void;
onError: (param: { error: any }) => void;
}
type BlockedMainModelListParams = 'onSuccess' | 'onError'
export type MainModelListParams<modelResponse> = Omit<ModelListParams<modelResponse>, BlockedMainModelListParams> & { requestId: string }
export type EventModelListOnSuccessParams<modelResponse> = Parameters<ModelListParams<modelResponse>['onSuccess']>[0] & { requestId: string }
export type EventModelListOnErrorParams<modelResponse> = Parameters<ModelListParams<modelResponse>['onError']>[0] & { requestId: string }
export type _InternalModelListFnType<modelResponse> = (params: ModelListParams<modelResponse>) => void
@@ -1,73 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { createDecorator, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js';
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
import { localize2 } from '../../../../nls.js';
import { registerAction2, Action2 } from '../../../../platform/actions/common/actions.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
export interface IMetricsService {
readonly _serviceBrand: undefined;
capture(event: string, params: Record<string, any>): void;
setOptOut(val: boolean): void;
getDebuggingProperties(): Promise<object>;
}
export const IMetricsService = createDecorator<IMetricsService>('metricsService');
// implemented by calling channel
export class MetricsService implements IMetricsService {
readonly _serviceBrand: undefined;
private readonly metricsService: IMetricsService;
constructor(
@IMainProcessService mainProcessService: IMainProcessService // (only usable on client side)
) {
// creates an IPC proxy to use metricsMainService.ts
this.metricsService = ProxyChannel.toService<IMetricsService>(mainProcessService.getChannel('code-channel-metrics'));
}
// call capture on the channel
capture(...params: Parameters<IMetricsService['capture']>) {
this.metricsService.capture(...params);
}
setOptOut(...params: Parameters<IMetricsService['setOptOut']>) {
this.metricsService.setOptOut(...params);
}
// anything transmitted over a channel must be async even if it looks like it doesn't have to be
async getDebuggingProperties(): Promise<object> {
return this.metricsService.getDebuggingProperties()
}
}
registerSingleton(IMetricsService, MetricsService, InstantiationType.Eager);
// debugging action
registerAction2(class extends Action2 {
constructor() {
super({
id: 'codeDebugInfo',
f1: true,
title: localize2('voidMetricsDebug', 'Code: Log Debug Info'),
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const metricsService = accessor.get(IMetricsService)
const notifService = accessor.get(INotificationService)
const debugProperties = await metricsService.getDebuggingProperties()
console.log('Metrics:', debugProperties)
notifService.info(`Code Debug info:\n${JSON.stringify(debugProperties, null, 2)}`)
}
})
@@ -1,222 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
import { ICodeSettingsService } from './codeSettingsService.js';
import { ILLMMessageService } from './llmMessageService.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
import { RefreshableProviderName, refreshableProviderNames, SettingsOfProvider } from './codeSettingsTypes.js';
import { OllamaModelResponse, OpenaiCompatibleModelResponse } from './llmMessageTypes.js';
type RefreshableState = ({
state: 'init',
timeoutId: null,
} | {
state: 'refreshing',
timeoutId: NodeJS.Timeout | null, // the timeoutId of the most recent call to refreshModels
} | {
state: 'finished',
timeoutId: null,
} | {
state: 'error',
timeoutId: null,
})
/*
user click -> error -> fire(error)
\> success -> fire(success)
finally: keep polling
poll -> do not fire
*/
export type RefreshModelStateOfProvider = Record<RefreshableProviderName, RefreshableState>
const refreshBasedOn: { [k in RefreshableProviderName]: (keyof SettingsOfProvider[k])[] } = {
ollama: ['_didFillInProviderSettings', 'endpoint'],
vLLM: ['_didFillInProviderSettings', 'endpoint'],
lmStudio: ['_didFillInProviderSettings', 'endpoint'],
// openAICompatible: ['_didFillInProviderSettings', 'endpoint', 'apiKey'],
}
const REFRESH_INTERVAL = 5_000
// const COOLDOWN_TIMEOUT = 300
const autoOptions = { enableProviderOnSuccess: true, doNotFire: true }
// element-wise equals
function eq<T>(a: T[], b: T[]): boolean {
if (a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false
}
return true
}
export interface IRefreshModelService {
readonly _serviceBrand: undefined;
startRefreshingModels: (providerName: RefreshableProviderName, options: { enableProviderOnSuccess: boolean, doNotFire: boolean }) => void;
onDidChangeState: Event<RefreshableProviderName>;
state: RefreshModelStateOfProvider;
}
export const IRefreshModelService = createDecorator<IRefreshModelService>('RefreshModelService');
export class RefreshModelService extends Disposable implements IRefreshModelService {
readonly _serviceBrand: undefined;
private readonly _onDidChangeState = new Emitter<RefreshableProviderName>();
readonly onDidChangeState: Event<RefreshableProviderName> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
constructor(
@ICodeSettingsService private readonly codeSettingsService: ICodeSettingsService,
@ILLMMessageService private readonly llmMessageService: ILLMMessageService,
) {
super()
const disposables: Set<IDisposable> = new Set()
const initializeAutoPollingAndOnChange = () => {
this._clearAllTimeouts()
disposables.forEach(d => d.dispose())
disposables.clear()
if (!codeSettingsService.state.globalSettings.autoRefreshModels) return
for (const providerName of refreshableProviderNames) {
// const { _enabled: enabled } = this.codeSettingsService.state.settingsOfProvider[providerName]
this.startRefreshingModels(providerName, autoOptions)
// every time providerName.enabled changes, refresh models too, like a useEffect
let relevantVals = () => refreshBasedOn[providerName].map(settingName => codeSettingsService.state.settingsOfProvider[providerName][settingName])
let prevVals = relevantVals() // each iteration of a for loop has its own context and vars, so this is ok
disposables.add(
codeSettingsService.onDidChangeState(() => { // we might want to debounce this
const newVals = relevantVals()
if (!eq(prevVals, newVals)) {
const prevEnabled = prevVals[0] as boolean
const enabled = newVals[0] as boolean
// if it was just enabled, or there was a change and it wasn't to the enabled state, refresh
if ((enabled && !prevEnabled) || (!enabled && !prevEnabled)) {
// if user just clicked enable, refresh
this.startRefreshingModels(providerName, autoOptions)
}
else {
// else if user just clicked disable, don't refresh
// //give cooldown before re-enabling (or at least re-fetching)
// const timeoutId = setTimeout(() => this.refreshModels(providerName, !enabled), COOLDOWN_TIMEOUT)
// this._setTimeoutId(providerName, timeoutId)
}
prevVals = newVals
}
})
)
}
}
// on mount (when get init settings state), and if a relevant feature flag changes, start refreshing models
codeSettingsService.waitForInitState.then(() => {
initializeAutoPollingAndOnChange()
this._register(
codeSettingsService.onDidChangeState((type) => { if (typeof type === 'object' && type[1] === 'autoRefreshModels') initializeAutoPollingAndOnChange() })
)
})
}
state: RefreshModelStateOfProvider = {
ollama: { state: 'init', timeoutId: null },
vLLM: { state: 'init', timeoutId: null },
lmStudio: { state: 'init', timeoutId: null },
}
// start listening for models (and don't stop)
startRefreshingModels: IRefreshModelService['startRefreshingModels'] = (providerName, options) => {
this._clearProviderTimeout(providerName)
this._setRefreshState(providerName, 'refreshing', options)
const autoPoll = () => {
if (this.codeSettingsService.state.globalSettings.autoRefreshModels) {
// resume auto-polling
const timeoutId = setTimeout(() => this.startRefreshingModels(providerName, autoOptions), REFRESH_INTERVAL)
this._setTimeoutId(providerName, timeoutId)
}
}
const listFn = providerName === 'ollama' ? this.llmMessageService.ollamaList
: this.llmMessageService.openAICompatibleList
listFn({
providerName,
onSuccess: ({ models }) => {
// set the models to the detected models
this.codeSettingsService.setAutodetectedModels(
providerName,
models.map(model => {
if (providerName === 'ollama') return (model as OllamaModelResponse).name;
else if (providerName === 'vLLM') return (model as OpenaiCompatibleModelResponse).id;
else if (providerName === 'lmStudio') return (model as OpenaiCompatibleModelResponse).id;
else throw new Error('refreshMode fn: unknown provider', providerName);
}),
{ enableProviderOnSuccess: options.enableProviderOnSuccess, hideRefresh: options.doNotFire }
)
if (options.enableProviderOnSuccess) this.codeSettingsService.setSettingOfProvider(providerName, '_enabled', true)
this._setRefreshState(providerName, 'finished', options)
autoPoll()
},
onError: ({ error }) => {
this._setRefreshState(providerName, 'error', options)
autoPoll()
}
})
}
_clearAllTimeouts() {
for (const providerName of refreshableProviderNames) {
this._clearProviderTimeout(providerName)
}
}
_clearProviderTimeout(providerName: RefreshableProviderName) {
// cancel any existing poll
if (this.state[providerName].timeoutId) {
clearTimeout(this.state[providerName].timeoutId)
this._setTimeoutId(providerName, null)
}
}
private _setTimeoutId(providerName: RefreshableProviderName, timeoutId: NodeJS.Timeout | null) {
this.state[providerName].timeoutId = timeoutId
}
private _setRefreshState(providerName: RefreshableProviderName, state: RefreshableState['state'], options?: { doNotFire: boolean }) {
if (options?.doNotFire) return
this.state[providerName].state = state
this._onDidChangeState.fire(providerName)
}
}
registerSingleton(IRefreshModelService, RefreshModelService, InstantiationType.Eager);
@@ -1,50 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Disposable } from '../../../base/common/lifecycle.js';
import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
import { IProductService } from '../../product/common/productService.js';
import { ICodeUpdateService } from '../common/codeUpdateService.js';
export class CodeMainUpdateService extends Disposable implements ICodeUpdateService {
_serviceBrand: undefined;
constructor(
@IProductService private readonly _productService: IProductService,
@IEnvironmentMainService private readonly _envMainService: IEnvironmentMainService,
) {
super()
}
async check() {
const isDevMode = !this._envMainService.isBuilt // found in abstractUpdateService.ts
if (isDevMode) {
return { hasUpdate: false } as const
}
try {
const res = await fetch(`https://updates.code.hanzo.ai/api/v0/${this._productService.commit}`)
const resJSON = await res.json()
if (!resJSON) return null
const { hasUpdate, downloadMessage } = resJSON ?? {}
if (hasUpdate === undefined)
return null
const after = (downloadMessage || '') + ''
return { hasUpdate: !!hasUpdate, message: after }
}
catch (e) {
return null
}
}
}
@@ -1,55 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import Anthropic from '@anthropic-ai/sdk';
import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js';
import { anthropicMaxPossibleTokens } from '../../common/codeSettingsTypes.js';
export const sendAnthropicMsg: _InternalSendLLMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
const thisConfig = settingsOfProvider.anthropic
const maxTokens = anthropicMaxPossibleTokens(modelName)
if (maxTokens === undefined) {
onError({ message: `Please set a value for Max Tokens.`, fullError: null })
return
}
const anthropic = new Anthropic({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true });
const stream = anthropic.messages.stream({
// system: systemMessage,
messages: messages,
model: modelName,
max_tokens: maxTokens,
});
// when receive text
stream.on('text', (newText, fullText) => {
onText({ newText, fullText })
})
// when we get the final message on this stream (or when error/fail)
stream.on('finalMessage', (claude_response) => {
// stringify the response's content
const content = claude_response.content.map(c => c.type === 'text' ? c.text : c.type).join('\n');
onFinalMessage({ fullText: content })
})
stream.on('error', (error) => {
// the most common error will be invalid API key (401), so we handle this with a nice message
if (error instanceof Anthropic.APIError && error.status === 401) {
onError({ message: 'Invalid API key.', fullError: error })
}
else {
onError({ message: error + '', fullError: error }) // anthropic errors can be stringified nicely like this
}
})
// TODO need to test this to make sure it works, it might throw an error
_setAborter(() => stream.controller.abort())
};
@@ -1,43 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Content, GoogleGenerativeAI } from '@google/generative-ai';
import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js';
// Gemini
export const sendGeminiMsg: _InternalSendLLMMessageFnType = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
let fullText = ''
const thisConfig = settingsOfProvider.gemini
const genAI = new GoogleGenerativeAI(thisConfig.apiKey);
const model = genAI.getGenerativeModel({ model: modelName });
// Convert messages to Gemini format
const geminiMessages: Content[] = messages
.map((msg, i) => ({
parts: [{ text: msg.content }],
role: msg.role === 'assistant' ? 'model' : 'user'
}))
model.generateContentStream({
// systemInstruction: systemMessage,
contents: geminiMessages,
})
.then(async response => {
_setAborter(() => response.stream.return(fullText))
for await (const chunk of response.stream) {
const newText = chunk.text();
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });
})
.catch((error) => {
onError({ message: error + '', fullError: error })
})
}
@@ -1,68 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
// // Greptile
// // https://docs.greptile.com/api-reference/query
// // https://docs.greptile.com/quickstart#sample-response-streamed
// import { SendLLMMessageFnTypeInternal } from '../../common/llmMessageTypes.js';
// export const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, _setAborter }) => {
// let fullText = ''
// const thisConfig = settingsOfProvider.greptile
// fetch('https://api.greptile.com/v2/query', {
// method: 'POST',
// headers: {
// 'Authorization': `Bearer ${thisConfig.apikey}`,
// 'X-Github-Token': `${thisConfig.githubPAT}`,
// 'Content-Type': `application/json`,
// },
// body: JSON.stringify({
// messages,
// stream: true,
// repositories: [thisConfig.repoinfo],
// }),
// })
// // this is {message}\n{message}\n{message}...\n
// .then(async response => {
// const text = await response.text()
// console.log('got greptile', text)
// return JSON.parse(`[${text.trim().split('\n').join(',')}]`)
// })
// // TODO make this actually stream, right now it just sends one message at the end
// // TODO add _setAborter() when add streaming
// .then(async responseArr => {
// for (const response of responseArr) {
// const type: string = response['type']
// const message = response['message']
// // when receive text
// if (type === 'message') {
// fullText += message
// onText({ newText: message, fullText })
// }
// else if (type === 'sources') {
// const { filepath, linestart: _, lineend: _2 } = message as { filepath: string; linestart: number | null; lineend: number | null }
// fullText += filepath
// onText({ newText: filepath, fullText })
// }
// // type: 'status' with an empty 'message' means last message
// else if (type === 'status') {
// if (!message) {
// onFinalMessage({ fullText })
// }
// }
// }
// })
// .catch(error => {
// onError({ error })
// });
// }
@@ -1,44 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import Groq from 'groq-sdk';
import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js';
// Groq
export const sendGroqMsg: _InternalSendLLMMessageFnType = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
let fullText = '';
const thisConfig = settingsOfProvider.groq
const groq = new Groq({
apiKey: thisConfig.apiKey,
dangerouslyAllowBrowser: true
});
await groq.chat.completions
.create({
messages: messages,
model: modelName,
stream: true,
// temperature: 0.7,
// max_tokens: parseMaxTokensStr(thisConfig.maxTokens),
})
.then(async response => {
_setAborter(() => response.controller.abort())
// when receive text
for await (const chunk of response) {
const newText = chunk.choices[0]?.delta?.content || '';
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });
})
.catch(error => {
onError({ message: error + '', fullError: error });
})
};
@@ -1,46 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Mistral } from '@mistralai/mistralai';
import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js';
// Mistral
export const sendMistralMsg: _InternalSendLLMMessageFnType = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
let fullText = '';
const thisConfig = settingsOfProvider.mistral;
const mistral = new Mistral({
apiKey: thisConfig.apiKey,
})
await mistral.chat
.stream({
messages: messages,
model: modelName,
stream: true,
// temperature: 0.7,
// maxTokens: 2048,
})
.then(async response => {
// Mistral has a really nonstandard API - no interrupt and weird stream types
_setAborter(() => { console.log('Mistral does not support interrupts! Further messages will just be ignored.') });
// when receive text
for await (const chunk of response) {
const c = chunk.data.choices[0].delta.content || ''
const newText = (
typeof c === 'string' ? c
: c?.map(c => c.type === 'text' ? c.text : c.type).join('\n')
)
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });
})
.catch(error => {
onError({ message: error + '', fullError: error });
})
}
@@ -1,116 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Ollama } from 'ollama';
import { _InternalModelListFnType, _InternalOllamaFIMMessageFnType, _InternalSendLLMMessageFnType, OllamaModelResponse } from '../../common/llmMessageTypes.js';
import { defaultProviderSettings } from '../../common/codeSettingsTypes.js';
export const ollamaList: _InternalModelListFnType<OllamaModelResponse> = async ({ onSuccess: onSuccess_, onError: onError_, settingsOfProvider }) => {
const onSuccess = ({ models }: { models: OllamaModelResponse[] }) => {
onSuccess_({ models })
}
const onError = ({ error }: { error: string }) => {
onError_({ error })
}
try {
const thisConfig = settingsOfProvider.ollama
// if endpoint is empty, normally ollama will send to 11434, but we want it to fail - the user should type it in
if (!thisConfig.endpoint) throw new Error(`Ollama Endpoint was empty (please enter ${defaultProviderSettings.ollama.endpoint} in Code if you want the default url).`)
const ollama = new Ollama({ host: thisConfig.endpoint })
ollama.list()
.then((response) => {
const { models } = response
onSuccess({ models })
})
.catch((error) => {
onError({ error: error + '' })
})
}
catch (error) {
onError({ error: error + '' })
}
}
export const sendOllamaFIM: _InternalOllamaFIMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
const thisConfig = settingsOfProvider.ollama
// if endpoint is empty, normally ollama will send to 11434, but we want it to fail - the user should type it in
if (!thisConfig.endpoint) throw new Error(`Ollama Endpoint was empty (please enter ${defaultProviderSettings.ollama.endpoint} if you want the default).`)
let fullText = ''
const ollama = new Ollama({ host: thisConfig.endpoint })
ollama.generate({
model: modelName,
prompt: messages.prefix,
suffix: messages.suffix,
options: {
stop: messages.stopTokens,
},
raw: true,
stream: true,
// options: { num_predict: parseMaxTokensStr(thisConfig.maxTokens) } // this is max_tokens
})
.then(async stream => {
_setAborter(() => stream.abort())
// iterate through the stream
for await (const chunk of stream) {
const newText = chunk.response;
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });
})
// when error/fail
.catch((error) => {
onError({ message: error + '', fullError: error })
})
};
// Ollama
export const sendOllamaMsg: _InternalSendLLMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
const thisConfig = settingsOfProvider.ollama
// if endpoint is empty, normally ollama will send to 11434, but we want it to fail - the user should type it in
if (!thisConfig.endpoint) throw new Error(`Ollama Endpoint was empty (please enter ${defaultProviderSettings.ollama.endpoint} if you want the default).`)
let fullText = ''
const ollama = new Ollama({ host: thisConfig.endpoint })
ollama.chat({
model: modelName,
messages: messages,
stream: true,
// options: { num_predict: parseMaxTokensStr(thisConfig.maxTokens) } // this is max_tokens
})
.then(async stream => {
_setAborter(() => stream.abort())
// iterate through the stream
for await (const chunk of stream) {
const newText = chunk.message.content;
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });
})
// when error/fail
.catch((error) => {
onError({ message: error + '', fullError: error })
})
};
// ['codestral', 'qwen2.5-coder', 'qwen2.5-coder:0.5b', 'qwen2.5-coder:1.5b', 'qwen2.5-coder:3b', 'qwen2.5-coder:7b', 'qwen2.5-coder:14b', 'qwen2.5-coder:32b', 'codegemma', 'codegemma:2b', 'codegemma:7b', 'codellama', 'codellama:7b', 'codellama:13b', 'codellama:34b', 'codellama:70b', 'codellama:code', 'codellama:python', 'command-r', 'command-r:35b', 'command-r-plus', 'command-r-plus:104b', 'deepseek-coder-v2', 'deepseek-coder-v2:16b', 'deepseek-coder-v2:236b', 'falcon2', 'falcon2:11b', 'firefunction-v2', 'firefunction-v2:70b', 'gemma', 'gemma:2b', 'gemma:7b', 'gemma2', 'gemma2:2b', 'gemma2:9b', 'gemma2:27b', 'llama2', 'llama2:7b', 'llama2:13b', 'llama2:70b', 'llama3', 'llama3:8b', 'llama3:70b', 'llama3-chatqa', 'llama3-chatqa:8b', 'llama3-chatqa:70b', 'llama3-gradient', 'llama3-gradient:8b', 'llama3-gradient:70b', 'llama3.1', 'llama3.1:8b', 'llama3.1:70b', 'llama3.1:405b', 'llava', 'llava:7b', 'llava:13b', 'llava:34b', 'llava-llama3', 'llava-llama3:8b', 'llava-phi3', 'llava-phi3:3.8b', 'mistral', 'mistral:7b', 'mistral-large', 'mistral-large:123b', 'mistral-nemo', 'mistral-nemo:12b', 'mixtral', 'mixtral:8x7b', 'mixtral:8x22b', 'moondream', 'moondream:1.8b', 'openhermes', 'openhermes:v2.5', 'phi3', 'phi3:3.8b', 'phi3:14b', 'phi3.5', 'phi3.5:3.8b', 'qwen', 'qwen:7b', 'qwen:14b', 'qwen:32b', 'qwen:72b', 'qwen:110b', 'qwen2', 'qwen2:0.5b', 'qwen2:1.5b', 'qwen2:7b', 'qwen2:72b', 'smollm', 'smollm:135m', 'smollm:360m', 'smollm:1.7b',]
@@ -1,112 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import OpenAI from 'openai';
import { _InternalModelListFnType, _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js';
import { Model } from 'openai/resources/models.js';
// import { parseMaxTokensStr } from './util.js';
export const openaiCompatibleList: _InternalModelListFnType<Model> = async ({ onSuccess: onSuccess_, onError: onError_, settingsOfProvider }) => {
const onSuccess = ({ models }: { models: Model[] }) => {
onSuccess_({ models })
}
const onError = ({ error }: { error: string }) => {
onError_({ error })
}
try {
const thisConfig = settingsOfProvider.openAICompatible
const openai = new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true })
openai.models.list()
.then(async (response) => {
const models: Model[] = []
models.push(...response.data)
while (response.hasNextPage()) {
models.push(...(await response.getNextPage()).data)
}
onSuccess({ models })
})
.catch((error) => {
onError({ error: error + '' })
})
}
catch (error) {
onError({ error: error + '' })
}
}
// OpenAI, OpenRouter, OpenAICompatible
export const sendOpenAIMsg: _InternalSendLLMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }) => {
let fullText = ''
let openai: OpenAI
let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
if (providerName === 'openAI') {
const thisConfig = settingsOfProvider.openAI
openai = new OpenAI({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true });
options = { model: modelName, messages: messages, stream: true, /*max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens)*/ }
}
else if (providerName === 'openRouter') {
const thisConfig = settingsOfProvider.openRouter
openai = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1', apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true,
defaultHeaders: {
'HTTP-Referer': 'https://code.hanzo.ai', // Optional, for including your app on openrouter.ai rankings.
'X-Title': 'Code Editor', // Optional. Shows in rankings on openrouter.ai.
},
});
options = { model: modelName, messages: messages, stream: true, /*max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens)*/ }
}
else if (providerName === 'deepseek') {
const thisConfig = settingsOfProvider.deepseek
openai = new OpenAI({
baseURL: 'https://api.deepseek.com/v1', apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true,
});
options = { model: modelName, messages: messages, stream: true, /*max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens)*/ }
}
else if (providerName === 'openAICompatible') {
const thisConfig = settingsOfProvider.openAICompatible
openai = new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true })
options = { model: modelName, messages: messages, stream: true, /*max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens)*/ }
}
else {
console.error(`sendOpenAIMsg: invalid providerName: ${providerName}`)
throw new Error(`providerName was invalid: ${providerName}`)
}
openai.chat.completions
.create(options)
.then(async response => {
_setAborter(() => response.controller.abort())
// when receive text
for await (const chunk of response) {
const newText = chunk.choices[0]?.delta?.content || '';
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });
})
// when error/fail - this catches errors of both .create() and .then(for await)
.catch(error => {
if (error instanceof OpenAI.APIError && error.status === 401) {
onError({ message: 'Invalid API key.', fullError: error });
}
else {
onError({ message: error + '', fullError: error });
}
})
};
@@ -1,167 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { SendLLMMessageParams, OnText, OnFinalMessage, OnError, LLMMessage, _InternalLLMMessage } from '../../common/llmMessageTypes.js';
import { IMetricsService } from '../../common/metricsService.js';
import { sendAnthropicMsg } from './anthropic.js';
import { sendOllamaFIM, sendOllamaMsg } from './ollama.js';
import { sendOpenAIMsg } from './openai.js';
import { sendGeminiMsg } from './gemini.js';
import { sendGroqMsg } from './groq.js';
import { sendMistralMsg } from './mistral.js';
const cleanMessages = (messages: LLMMessage[]): _InternalLLMMessage[] => {
// trim message content (Anthropic and other providers give an error if there is trailing whitespace)
messages = messages.map(m => ({ ...m, content: m.content.trim() }))
// find system messages and concatenate them
const systemMessage = messages
.filter(msg => msg.role === 'system')
.map(msg => msg.content)
.join('\n') || undefined;
// remove all system messages
const noSystemMessages = messages
.filter(msg => msg.role !== 'system') as _InternalLLMMessage[]
// add system mesasges to first message (should be a user message)
if (systemMessage && (noSystemMessages.length !== 0)) {
const newFirstMessage = {
role: noSystemMessages[0].role,
content: (''
+ '<SYSTEM_MESSAGE>\n'
+ systemMessage
+ '\n'
+ '</SYSTEM_MESSAGE>\n'
+ noSystemMessages[0].content
)
}
noSystemMessages.splice(0, 1) // delete first message
noSystemMessages.unshift(newFirstMessage) // add new first message
}
return noSystemMessages
}
export const sendLLMMessage = ({
type,
aiInstructions,
messages: messages_,
onText: onText_,
onFinalMessage: onFinalMessage_,
onError: onError_,
abortRef: abortRef_,
logging: { loggingName },
settingsOfProvider,
providerName,
modelName,
}: SendLLMMessageParams,
metricsService: IMetricsService
) => {
// messages.unshift({ role: 'system', content: aiInstructions })
const messagesArr = type === 'sendLLMMessage' ? cleanMessages(messages_) : []
// only captures number of messages and message "shape", no actual code, instructions, prompts, etc
const captureLLMEvent = (eventId: string, extras?: object) => {
metricsService.capture(eventId, {
providerName,
modelName,
...type === 'sendLLMMessage' ? {
numMessages: messagesArr?.length,
messagesShape: messagesArr?.map(msg => ({ role: msg.role, length: msg.content.length })),
origNumMessages: messages_?.length,
origMessagesShape: messages_?.map(msg => ({ role: msg.role, length: msg.content.length })),
} : type === 'ollamaFIM' ? {
} : {},
...extras,
})
}
const submit_time = new Date()
let _fullTextSoFar = ''
let _aborter: (() => void) | null = null
let _setAborter = (fn: () => void) => { _aborter = fn }
let _didAbort = false
const onText: OnText = ({ newText, fullText }) => {
if (_didAbort) return
onText_({ newText, fullText })
_fullTextSoFar = fullText
}
const onFinalMessage: OnFinalMessage = ({ fullText }) => {
if (_didAbort) return
captureLLMEvent(`${loggingName} - Received Full Message`, { messageLength: fullText.length, duration: new Date().getMilliseconds() - submit_time.getMilliseconds() })
onFinalMessage_({ fullText })
}
const onError: OnError = ({ message: error, fullError }) => {
if (_didAbort) return
console.error('sendLLMMessage onError:', error)
captureLLMEvent(`${loggingName} - Error`, { error })
onError_({ message: error, fullError })
}
const onAbort = () => {
captureLLMEvent(`${loggingName} - Abort`, { messageLengthSoFar: _fullTextSoFar.length })
try { _aborter?.() } // aborter sometimes automatically throws an error
catch (e) { }
_didAbort = true
}
abortRef_.current = onAbort
captureLLMEvent(`${loggingName} - Sending Message`, { messageLength: messagesArr[messagesArr.length - 1]?.content.length })
try {
switch (providerName) {
case 'anthropic':
sendAnthropicMsg({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'openAI':
case 'openRouter':
case 'deepseek':
case 'openAICompatible':
sendOpenAIMsg({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'gemini':
sendGeminiMsg({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'ollama':
if (type === 'ollamaFIM')
sendOllamaFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName })
else
sendOllamaMsg({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'groq':
sendGroqMsg({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'mistral':
sendMistralMsg({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
default:
onError({ message: `Error: Code provider was "${providerName}", which is not recognized.`, fullError: null })
break;
}
}
catch (error) {
if (error instanceof Error) { onError({ message: error + '', fullError: error }) }
else { onError({ message: `Unexpected Error in sendLLMMessage: ${error}`, fullError: error }); }
// ; (_aborter as any)?.()
// _didAbort = true
}
}
@@ -1,140 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
// registered in app.ts
// code convention is to make a service responsible for this stuff, and not a channel, but having fewer files is simpler...
import { IServerChannel } from '../../../base/parts/ipc/common/ipc.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, MainSendLLMMessageParams, AbortRef, SendLLMMessageParams, MainLLMMessageAbortParams, MainModelListParams, ModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, OllamaModelResponse, OpenaiCompatibleModelResponse, } from '../common/llmMessageTypes.js';
import { sendLLMMessage } from './llmMessage/sendLLMMessage.js'
import { IMetricsService } from '../common/metricsService.js';
import { ollamaList } from './llmMessage/ollama.js';
import { openaiCompatibleList } from './llmMessage/openai.js';
// NODE IMPLEMENTATION - calls actual sendLLMMessage() and returns listeners to it
export class LLMMessageChannel implements IServerChannel {
// sendLLMMessage
private readonly _onText_llm = new Emitter<EventLLMMessageOnTextParams>();
private readonly _onFinalMessage_llm = new Emitter<EventLLMMessageOnFinalMessageParams>();
private readonly _onError_llm = new Emitter<EventLLMMessageOnErrorParams>();
// abort
private readonly _abortRefOfRequestId_llm: Record<string, AbortRef> = {}
// ollamaList
private readonly _onSuccess_ollama = new Emitter<EventModelListOnSuccessParams<OllamaModelResponse>>();
private readonly _onError_ollama = new Emitter<EventModelListOnErrorParams<OllamaModelResponse>>();
// openaiCompatibleList
private readonly _onSuccess_openAICompatible = new Emitter<EventModelListOnSuccessParams<OpenaiCompatibleModelResponse>>();
private readonly _onError_openAICompatible = new Emitter<EventModelListOnErrorParams<OpenaiCompatibleModelResponse>>();
// stupidly, channels can't take in @IService
constructor(
private readonly metricsService: IMetricsService,
) { }
// browser uses this to listen for changes
listen(_: unknown, event: string): Event<any> {
if (event === 'onText_llm') {
return this._onText_llm.event;
}
else if (event === 'onFinalMessage_llm') {
return this._onFinalMessage_llm.event;
}
else if (event === 'onError_llm') {
return this._onError_llm.event;
}
else if (event === 'onSuccess_ollama') {
return this._onSuccess_ollama.event;
}
else if (event === 'onError_ollama') {
return this._onError_ollama.event;
}
else if (event === 'onSuccess_openAICompatible') {
return this._onSuccess_openAICompatible.event;
}
else if (event === 'onError_openAICompatible') {
return this._onError_openAICompatible.event;
}
else {
throw new Error(`Event not found: ${event}`);
}
}
// browser uses this to call
async call(_: unknown, command: string, params: any): Promise<any> {
try {
if (command === 'sendLLMMessage') {
this._callSendLLMMessage(params)
}
else if (command === 'abort') {
this._callAbort(params)
}
else if (command === 'ollamaList') {
this._callOllamaList(params)
}
else if (command === 'openAICompatibleList') {
this._callOpenAICompatibleList(params)
}
else {
throw new Error(`Code sendLLM: command "${command}" not recognized.`)
}
}
catch (e) {
console.log('llmMessageChannel: Call Error:', e)
}
}
// the only place sendLLMMessage is actually called
private async _callSendLLMMessage(params: MainSendLLMMessageParams) {
const { requestId } = params;
if (!(requestId in this._abortRefOfRequestId_llm))
this._abortRefOfRequestId_llm[requestId] = { current: null }
const mainThreadParams: SendLLMMessageParams = {
...params,
onText: ({ newText, fullText }) => { this._onText_llm.fire({ requestId, newText, fullText }); },
onFinalMessage: ({ fullText }) => { this._onFinalMessage_llm.fire({ requestId, fullText }); },
onError: ({ message: error, fullError }) => { console.log('sendLLM: firing err'); this._onError_llm.fire({ requestId, message: error, fullError }); },
abortRef: this._abortRefOfRequestId_llm[requestId],
}
sendLLMMessage(mainThreadParams, this.metricsService);
}
private _callAbort(params: MainLLMMessageAbortParams) {
const { requestId } = params;
if (!(requestId in this._abortRefOfRequestId_llm)) return
this._abortRefOfRequestId_llm[requestId].current?.()
delete this._abortRefOfRequestId_llm[requestId]
}
private _callOllamaList(params: MainModelListParams<OllamaModelResponse>) {
const { requestId } = params;
const mainThreadParams: ModelListParams<OllamaModelResponse> = {
...params,
onSuccess: ({ models }) => { this._onSuccess_ollama.fire({ requestId, models }); },
onError: ({ error }) => { this._onError_ollama.fire({ requestId, error }); },
}
ollamaList(mainThreadParams)
}
private _callOpenAICompatibleList(params: MainModelListParams<OpenaiCompatibleModelResponse>) {
const { requestId } = params;
const mainThreadParams: ModelListParams<OpenaiCompatibleModelResponse> = {
...params,
onSuccess: ({ models }) => { this._onSuccess_openAICompatible.fire({ requestId, models }); },
onError: ({ error }) => { this._onError_openAICompatible.fire({ requestId, error }); },
}
openaiCompatibleList(mainThreadParams)
}
}
@@ -24,7 +24,7 @@ export class EncryptionMainService implements IEncryptionMainService {
@ILogService private readonly logService: ILogService
) {
// Void added this as a nice default for linux so you don't need to specify encryption provider
// Code added this as a nice default for linux so you don't need to specify encryption provider
if (isLinux && !app.commandLine.getSwitchValue('password-store')) {
this.logService.trace('[EncryptionMainService] No password-store switch, defaulting to basic...');
app.commandLine.appendSwitch('password-store', PasswordStoreCLIOption.basic);
@@ -410,7 +410,6 @@ function isVersionValid(currentVersion: string, date: ProductDate, requestedVers
}
if (!isValidVersion(currentVersion, date, desiredVersion)) {
// Code - ignore not compatible
notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion));
return false;
}
@@ -31,29 +31,25 @@ export class ServerTelemetryService extends TelemetryService implements IServerT
}
override publicLog(eventName: string, data?: ITelemetryData) {
// Code commented this out
// if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) {
// return;
// }
// return super.publicLog(eventName, data);
if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) {
return;
}
return super.publicLog(eventName, data);
}
override publicLog2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
// Code commented this out
// return this.publicLog(eventName, data as ITelemetryData | undefined);
return this.publicLog(eventName, data as ITelemetryData | undefined);
}
override publicLogError(errorEventName: string, data?: ITelemetryData) {
// Code commented this out
// if (this._injectedTelemetryLevel < TelemetryLevel.ERROR) {
// return Promise.resolve(undefined);
// }
// return super.publicLogError(errorEventName, data);
if (this._injectedTelemetryLevel < TelemetryLevel.ERROR) {
return Promise.resolve(undefined);
}
return super.publicLogError(errorEventName, data);
}
override publicLogError2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
// Code commented this out
// return this.publicLogError(eventName, data as ITelemetryData | undefined);
return this.publicLogError(eventName, data as ITelemetryData | undefined);
}
async updateInjectedTelemetryLevel(telemetryLevel: TelemetryLevel): Promise<void> {
@@ -121,12 +121,11 @@ export class TelemetryService implements ITelemetryService {
this._disposables.dispose();
}
// Code commented this out
private _log(eventName: string, eventLevel: TelemetryLevel, data?: ITelemetryData) {
// // don't send events when the user is optout
// if (this._telemetryLevel < eventLevel) {
// return;
// }
// don't send events when the user is optout
if (this._telemetryLevel < eventLevel) {
return;
}
// add experiment properties
data = mixin(data, this._experimentProperties);
@@ -150,18 +149,16 @@ export class TelemetryService implements ITelemetryService {
}
publicLogError(errorEventName: string, data?: ITelemetryData) {
// Code commented this out
// if (!this._sendErrorTelemetry) {
// return;
// }
if (!this._sendErrorTelemetry) {
return;
}
// Send error event and anonymize paths
this._log(errorEventName, TelemetryLevel.ERROR, data);
}
publicLogError2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
// Code commented this out
// this.publicLogError(eventName, data as ITelemetryData);
this.publicLogError(eventName, data as ITelemetryData);
}
}
@@ -173,7 +170,7 @@ function getTelemetryLevelSettingDescription(): string {
const restartString = !isWeb ? localize('telemetry.restart', 'Microsoft says \"Some third party extensions might not respect this setting. Consult the specific extension\'s documentation to be sure. A full restart of the application is necessary for crash reporting changes to take effect.\"') : '';
// Void removed these
// Code removed these
// const crashReportsHeader = localize('telemetry.crashReports', "Crash Reports");
// const errorsHeader = localize('telemetry.errors', "Error Telemetry");
// const usageHeader = localize('telemetry.usage', "Usage Data");
@@ -194,7 +191,7 @@ ${telemetryText}
${restartString}
Void separately records basic usage like the number of messages people are sending. If you'd like to disable Void metrics, you may do so in Void's Settings.
Code separately records basic usage like the number of messages people are sending. If you'd like to disable Code metrics, you may do so in Code's Settings.
`;
return telemetryDescription;
@@ -251,4 +248,3 @@ configurationRegistry.registerConfiguration({
}
},
});
@@ -15,10 +15,7 @@ import { IRequestService } from '../../request/common/request.js';
import { AvailableForDownload, DisablementReason, IUpdateService, State, StateType, UpdateType } from '../common/update.js';
export function createUpdateURL(platform: string, quality: string, productService: IProductService): string {
// return `https://code.hanzo.ai/api/update/${platform}/stable`;
// return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`;
// https://github.com/VSCodium/update-api
return `https://updates.code.hanzo.ai/api/update/${platform}/${quality}/${productService.commit}`;
return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`;
}
export type UpdateErrorClassification = {
@@ -108,7 +105,10 @@ export abstract class AbstractUpdateService implements IUpdateService {
this.setState(State.Idle(this.getUpdateType()));
// Code - temporarily disabled while we figure out how to do this the right way
if (updateMode === 'manual') {
this.logService.info('update#ctor - manual checks only; automatic updates are disabled by user preference');
return;
}
if (updateMode === 'start') {
this.logService.info('update#ctor - startup checks only; automatic updates are disabled by user preference');
@@ -88,9 +88,8 @@ import './mainThreadShare.js';
import './mainThreadProfileContentHandlers.js';
import './mainThreadAiRelatedInformation.js';
import './mainThreadAiEmbeddingVector.js';
// Code added this:
import './mainThreadInlineDiff.js';
import './mainThreadMcp.js';
import './mainThreadChatStatus.js';
export class ExtensionPoints implements IWorkbenchContribution {
@@ -1,131 +0,0 @@
// Code created this file
// it comes from mainThreadCodeInsets.ts
import { Disposable } from '../../../base/common/lifecycle.js';
import { ICodeEditorService } from '../../../editor/browser/services/codeEditorService.js';
import { MainContext, MainThreadInlineDiffShape } from '../common/extHost.protocol.js';
import { IInlineDiffService } from '../../../editor/browser/services/inlineDiffService/inlineDiffService.js';
import { ICodeEditor } from '../../../editor/browser/editorBrowser.js';
import { IRange } from '../../../editor/common/core/range.js';
import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';
import { IUndoRedoElement, IUndoRedoService, UndoRedoElementType, UndoRedoGroup } from '../../../platform/undoRedo/common/undoRedo.js';
import { IBulkEditService } from '../../../editor/browser/services/bulkEditService.js';
import { WorkspaceEdit } from '../../../editor/common/languages.js';
// import { IHistoryService } from '../../services/history/common/history.js';
@extHostNamedCustomer(MainContext.MainThreadInlineDiff)
export class MainThreadInlineDiff extends Disposable implements MainThreadInlineDiffShape {
// private readonly _proxy: ExtHostEditorInsetsShape;
// private readonly _disposables = new DisposableStore();
constructor(
context: IExtHostContext,
@IInlineDiffService private readonly _inlineDiff: IInlineDiffService,
@ICodeEditorService private readonly _editorService: ICodeEditorService,
// @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
) {
super();
// this._proxy = context.getProxy(ExtHostContext.ExtHostEditorInsets);
// this._wcHistoryService.addEntry()
}
_streamingState: { type: 'streaming'; editGroup: UndoRedoGroup } | { type: 'idle' } = { type: 'idle' }
startStreaming(editorId: string) {
const editor = this._getEditor(editorId)
if (!editor) return
const model = editor.getModel()
if (!model) return
// all changes made when streaming should be a part of the group so we can undo them all together
this._streamingState = {
type: 'streaming',
editGroup: new UndoRedoGroup()
}
// TODO probably need to convert this to a stack
const diffsSnapshotBefore = { placeholder: '' }
const diffsSnapshotAfter = { placeholder: '' }
const elt: IUndoRedoElement = {
type: UndoRedoElementType.Resource,
resource: model.uri,
label: 'Add Diffs',
code: 'undoredo.inlineDiff',
undo: () => {
// reapply diffareas and diffs here
console.log('reverting diffareas...', diffsSnapshotBefore.placeholder)
},
redo: () => {
// reapply diffareas and diffs here
// when done, need to record diffSnapshotAfter
console.log('re-applying diffareas...', diffsSnapshotAfter.placeholder)
}
}
this._undoRedoService.pushElement(elt, this._streamingState.editGroup)
// ---------- START ----------
editor.updateOptions({ readOnly: true })
// ---------- WHEN DONE ----------
editor.updateOptions({ readOnly: false })
}
streamChange(editorId: string, edit: WorkspaceEdit) {
const editor = this._getEditor(editorId)
if (!editor) return
if (this._streamingState.type !== 'streaming') {
console.error('Expected streamChange to be in state \'streaming\'.')
return
}
// count all changes towards the group
this._bulkEditService.apply(edit, { undoRedoGroupId: this._streamingState.editGroup.id, })
}
_getEditor = (editorId: string): ICodeEditor | undefined => {
let editor: ICodeEditor | undefined;
editorId = editorId.substr(0, editorId.indexOf(',')); //todo@jrieken HACK
for (const candidate of this._editorService.listCodeEditors()) {
if (candidate.getId() === editorId
// && candidate.hasModel() && isEqual(candidate.getModel().uri, URI.revive(uri))
) {
editor = candidate;
break;
}
}
return editor
}
$addDiff(editorId: string, originalText: string, range: IRange): void {
const editor = this._getEditor(editorId);
if (!editor) return
this._inlineDiff.addDiff(editor, originalText, range)
}
}
@@ -221,9 +221,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostStatusBar = rpcProtocol.set(ExtHostContext.ExtHostStatusBar, new ExtHostStatusBar(rpcProtocol, extHostCommands.converter));
const extHostSpeech = rpcProtocol.set(ExtHostContext.ExtHostSpeech, new ExtHostSpeech(rpcProtocol));
const extHostEmbeddings = rpcProtocol.set(ExtHostContext.ExtHostEmbeddings, new ExtHostEmbeddings(rpcProtocol));
// Code added this:
const extHostInlineDiff = rpcProtocol.set(ExtHostContext.ExtHostInlineDiff, new ExtHostInlineDiff(rpcProtocol.getProxy(MainContext.MainThreadInlineDiff), extHostEditors));
rpcProtocol.set(ExtHostContext.ExtHostMcp, accessor.get(IExtHostMpcService));
// Check that no named customers are missing
const expected = Object.values<ProxyIdentifier<any>>(ExtHostContext);
@@ -558,17 +556,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider);
},
// Code added addInlineDiff here:
addInlineDiff(editor: vscode.TextEditor, originalText: string, modifiedRange: vscode.Range): void {
extHostInlineDiff.addDiff(editor, originalText, modifiedRange)
},
// Code added this (I think will need to add this back when add ctrl+K)
// registerCodeCtrlKProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
// return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider);
// },
registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable {
return extHostLanguageFeatures.registerDefinitionProvider(extension, checkSelector(selector), provider);
},
@@ -838,7 +825,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostWebviewPanels.createWebviewPanel(extension, viewType, title, showOptions, options);
},
createWebviewTextEditorInset(editor: vscode.TextEditor, line: number, height: number, options?: vscode.WebviewOptions): vscode.WebviewEditorInset {
// checkProposedApiEnabled(extension, 'editorInsets'); // Code commented this out
checkProposedApiEnabled(extension, 'editorInsets');
return extHostEditorInsets.createWebviewEditorInset(editor, line, height, options, extension);
},
createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.ExtensionTerminalOptions | string, shellPath?: string, shellArgs?: readonly string[] | string): vscode.Terminal {
@@ -3145,9 +3145,7 @@ export const MainContext = {
MainThreadMcp: createProxyIdentifier<MainThreadMcpShape>('MainThreadMcpShape'),
MainThreadAiRelatedInformation: createProxyIdentifier<MainThreadAiRelatedInformationShape>('MainThreadAiRelatedInformation'),
MainThreadAiEmbeddingVector: createProxyIdentifier<MainThreadAiEmbeddingVectorShape>('MainThreadAiEmbeddingVector'),
// Code added this:
MainThreadInlineDiff: createProxyIdentifier<MainThreadInlineDiffShape>('MainThreadInlineDiff'),
MainThreadChatStatus: createProxyIdentifier<MainThreadChatStatusShape>('MainThreadChatStatus'),
};
export const ExtHostContext = {
@@ -3218,16 +3216,5 @@ export const ExtHostContext = {
ExtHostTesting: createProxyIdentifier<ExtHostTestingShape>('ExtHostTesting'),
ExtHostTelemetry: createProxyIdentifier<ExtHostTelemetryShape>('ExtHostTelemetry'),
ExtHostLocalization: createProxyIdentifier<ExtHostLocalizationShape>('ExtHostLocalization'),
// Code added this:
ExtHostInlineDiff: createProxyIdentifier<ExtHostInlineDiffShape>('ExtHostInlineDiff'), // Code added this
ExtHostMcp: createProxyIdentifier<ExtHostMcpShape>('ExtHostMcp'),
};
// Code added these:
export interface ExtHostInlineDiffShape {
$onDidDispose(handle: number): void;
}
export interface MainThreadInlineDiffShape {
$addDiff(editorId: string, originalText: string, range: IRange): void;
}
@@ -304,16 +304,6 @@ const newCommands: ApiCommand[] = [
})(value);
})
),
// // --- Code code lens
// new ApiCommand(
// 'vscode.executeCodeCodeLensProvider', '_executeCodeCodeLensProvider', 'Execute Code code lens provider.',
// [ApiCommandArgument.Uri, ApiCommandArgument.Number.with('itemResolveCount', 'Number of lenses that should be resolved and returned. Will only return resolved lenses, will impact performance)').optional()],
// new ApiCommandResult<languages.CodeLens[], vscode.CodeLens[] | undefined>('A promise that resolves to an array of CodeCodeLens-instances.', (value, _args, converter) => {
// return tryMapWith<languages.CodeLens, vscode.CodeLens>(item => {
// return new types.CodeLens(typeConverters.Range.to(item.range), item.command && converter.fromInternal(item.command));
// })(value);
// })
// ),
// --- code actions
new ApiCommand(
'vscode.executeCodeActionProvider', '_executeCodeActionProvider', 'Execute code action provider.',
@@ -445,7 +435,7 @@ const newCommands: ApiCommand[] = [
).optional(),
ApiCommandArgument.String.with('label', '').optional()
],
ApiCommandResult.Code
ApiCommandResult.Void
),
new ApiCommand(
'vscode.openWith', '_workbench.openWith', 'Opens the provided resource with a specific editor.',
@@ -457,7 +447,7 @@ const newCommands: ApiCommand[] = [
v => !v ? v : typeof v === 'number' ? [typeConverters.ViewColumn.from(v), undefined] : [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)],
).optional()
],
ApiCommandResult.Code
ApiCommandResult.Void
),
new ApiCommand(
'vscode.diff', '_workbench.diff', 'Opens the provided resources in the diff editor to compare their contents.',
@@ -470,7 +460,7 @@ const newCommands: ApiCommand[] = [
v => v && [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)]
).optional(),
],
ApiCommandResult.Code
ApiCommandResult.Void
),
new ApiCommand(
'vscode.changes', '_workbench.changes', 'Opens a list of resources in the changes editor to compare their contents.',
@@ -495,7 +485,7 @@ const newCommands: ApiCommand[] = [
},
v => v)
],
ApiCommandResult.Code
ApiCommandResult.Void
),
// --- type hierarchy
new ApiCommand(
@@ -517,7 +507,7 @@ const newCommands: ApiCommand[] = [
new ApiCommand(
'vscode.revealTestInExplorer', '_revealTestInExplorer', 'Reveals a test instance in the explorer',
[ApiCommandArgument.TestItem],
ApiCommandResult.Code
ApiCommandResult.Void
),
new ApiCommand(
'vscode.startContinuousTestRun', 'testing.startContinuousRunFromExtension', 'Starts running the given tests with continuous run mode.',
@@ -533,7 +523,7 @@ const newCommands: ApiCommand[] = [
new ApiCommand(
'vscode.experimental.editSession.continue', '_workbench.editSessions.actions.continueEditSession', 'Continue the current edit session in a different workspace',
[ApiCommandArgument.Uri.with('workspaceUri', 'The target workspace to continue the current edit session in')],
ApiCommandResult.Code
ApiCommandResult.Void
),
// --- context keys
new ApiCommand(
@@ -542,7 +532,7 @@ const newCommands: ApiCommand[] = [
ApiCommandArgument.String.with('name', 'The context key name'),
new ApiCommandArgument('value', 'The context key value', () => true, v => v),
],
ApiCommandResult.Code
ApiCommandResult.Void
),
// --- inline chat
new ApiCommand(
@@ -561,7 +551,7 @@ const newCommands: ApiCommand[] = [
position: v.position ? typeConverters.Position.from(v.position) : undefined,
};
})],
ApiCommandResult.Code
ApiCommandResult.Void
)
];
@@ -76,7 +76,7 @@ export class ExtHostCommands implements ExtHostCommandsShape {
// converted to their internal command and don't need
// any indirection commands
const candidate = this._apiCommands.get(id);
return candidate?.result === ApiCommandResult.Code
return candidate?.result === ApiCommandResult.Void
? candidate : undefined;
},
logService
@@ -483,7 +483,7 @@ export class ApiCommandArgument<V, O = V> {
export class ApiCommandResult<V, O = V> {
static readonly Code = new ApiCommandResult<void, void>('no result', v => v);
static readonly Void = new ApiCommandResult<void, void>('no result', v => v);
constructor(
readonly description: string,
@@ -1,63 +0,0 @@
// This file was created by Code
// reference extHostCodeInsets.ts
import { Emitter } from '../../../base/common/event.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { ExtHostInlineDiffShape, MainThreadInlineDiffShape } from './extHost.protocol.js';
import * as vscode from 'vscode'
import { ExtHostTextEditor } from './extHostTextEditor.js';
import { ExtHostEditors } from './extHostTextEditors.js';
import { Range } from '../../../workbench/api/common/extHostTypeConverters.js'
export class ExtHostInlineDiff implements ExtHostInlineDiffShape {
private readonly _disposables = new DisposableStore();
private _insets = new Map<number, { editor: vscode.TextEditor; inset: vscode.WebviewEditorInset; onDidReceiveMessage: Emitter<any> }>();
constructor(
private readonly _proxy: MainThreadInlineDiffShape,
private readonly _editors: ExtHostEditors,
) { }
dispose(): void {
this._insets.forEach(value => value.inset.dispose());
this._disposables.dispose();
}
addDiff(editor: vscode.TextEditor, originalText: string, modifiedRange: vscode.Range) {
let apiEditor: ExtHostTextEditor | undefined;
for (const candidate of this._editors.getVisibleTextEditors(true)) {
if (candidate.value === editor) {
apiEditor = <ExtHostTextEditor>candidate;
break;
}
}
if (!apiEditor) {
throw new Error('not a visible editor');
}
// can't send over the editor, so just send over its id and reconstruct it. This is stupid but it's what VSCode's editorinset does - Andrew
const id = apiEditor.id;
// let uri = apiEditor.value.document.uri;
// convert to IRange
const range = Range.from(modifiedRange)
this._proxy.$addDiff(id, originalText, range)
}
// main thread calls this when disposes diff with this particular handle
$onDidDispose(handle: number): void {
const value = this._insets.get(handle);
if (value) {
value.inset.dispose();
}
}
}
@@ -89,7 +89,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
return v;
})
],
ApiCommandResult.Code);
ApiCommandResult.Void);
const requestKernelVariablesApiCommand = new ApiCommand(
'vscode.executeNotebookVariableProvider',
@@ -35,13 +35,14 @@ export class ToggleAuxiliaryBarAction extends Action2 {
title: ToggleAuxiliaryBarAction.LABEL,
toggled: {
condition: AuxiliaryBarVisibleContext,
title: localize('secondary sidebar', "Code Side Bar"),
title: localize('closeSecondarySideBar', 'Hide Code Side Bar'),
icon: closeIcon,
mnemonicTitle: localize({ key: 'secondary sidebar mnemonic', comment: ['&& denotes a mnemonic'] }, "Secondary Si&&de Bar"),
},
icon: closeIcon, // Ensures no flickering when using toggled.icon
category: Categories.View,
metadata: {
description: localize('openAndCloseAuxiliaryBar', 'Open/Show and Close/Hide Void Side Bar'),
description: localize('openAndCloseAuxiliaryBar', 'Open/Show and Close/Hide Code Side Bar'),
},
f1: true,
keybinding: {
@@ -80,7 +81,7 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.closeAuxiliaryBar',
title: localize2('closeSecondarySideBar', 'Hide Void Side Bar'),
title: localize2('closeSecondarySideBar', 'Hide Code Side Bar'),
category: Categories.View,
precondition: AuxiliaryBarVisibleContext,
f1: true,
@@ -165,7 +166,7 @@ registerAction2(class extends SwitchCompositeViewAction {
constructor() {
super({
id: 'workbench.action.previousAuxiliaryBarView',
title: localize2('previousAuxiliaryBarView', 'Previous Void Side Bar View'),
title: localize2('previousAuxiliaryBarView', 'Previous Code Side Bar View'),
category: Categories.View,
f1: true
}, ViewContainerLocation.AuxiliaryBar, -1);
@@ -176,7 +177,7 @@ registerAction2(class extends SwitchCompositeViewAction {
constructor() {
super({
id: 'workbench.action.nextAuxiliaryBarView',
title: localize2('nextAuxiliaryBarView', 'Next Void Side Bar View'),
title: localize2('nextAuxiliaryBarView', 'Next Code Side Bar View'),
category: Categories.View,
f1: true
}, ViewContainerLocation.AuxiliaryBar, 1);
@@ -52,7 +52,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart {
static readonly viewContainersWorkspaceStateKey = 'workbench.auxiliarybar.viewContainersWorkspaceState';
// Use the side bar dimensions
override readonly minimumWidth: number = 230; // Code changed this (was 170)
override readonly minimumWidth: number = 280; // Code changed this (was 170)
override readonly maximumWidth: number = Number.POSITIVE_INFINITY;
override readonly minimumHeight: number = 0;
override readonly maximumHeight: number = Number.POSITIVE_INFINITY;
@@ -233,6 +233,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart {
new Separator(),
new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions),
toAction({ id: ToggleSidebarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Code Side Bar Left") : localize('move second side bar right', "Move Code Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSidebarPositionAction.ID) }),
toggleShowLabelsAction,
toAction({ id: ToggleAuxiliaryBarAction.ID, label: localize('hide second side bar', "Hide Code Side Bar"), run: () => this.commandService.executeCommand(ToggleAuxiliaryBarAction.ID) })
]);
}
@@ -30,7 +30,7 @@
background-repeat: no-repeat;
background-position: center center;
background-size: 16px;
background-image: url('../../../../browser/media/code-icon-sm.png');
background-image: url('../../../../browser/media/code-icon-sm.png'); /* // Code */
width: 16px;
padding: 0;
margin: 0 6px 0 10px;
@@ -17,12 +17,15 @@ import { editorForeground, registerColor, transparent } from '../../../../platfo
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
import { isRecentFolder, IWorkspacesService } from '../../../../platform/workspaces/common/workspaces.js';
import { IHostService } from '../../../services/host/browser/host.js';
import { CODE_OPEN_SETTINGS_ACTION_ID } from '../../../contrib/void/browser/codeSettingsPane.js';
import { CODE_CTRL_K_ACTION_ID, CODE_CTRL_L_ACTION_ID } from '../../../contrib/void/browser/actionIDs.js';
// import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { ILabelService, Verbosity } from '../../../../platform/label/common/label.js';
import { ColorScheme } from '../../web.api.js';
import { OpenFileFolderAction, OpenFolderAction } from '../../actions/workspaceActions.js';
import { IWindowOpenable } from '../../../../platform/window/common/window.js';
import { splitRecentLabel } from '../../../../base/common/labels.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
/* eslint-disable */ // Void
import { VOID_CTRL_K_ACTION_ID, VOID_CTRL_L_ACTION_ID } from '../../../contrib/void/browser/actionIDs.js';
/* eslint-disable */ // Code
import { CODE_CTRL_K_ACTION_ID, CODE_CTRL_L_ACTION_ID } from '../../../contrib/code/browser/actionIDs.js';
import { VIEWLET_ID as REMOTE_EXPLORER_VIEWLET_ID } from '../../../contrib/remote/browser/remoteExplorer.js';
/* eslint-enable */
@@ -47,15 +50,9 @@ import { VIEWLET_ID as REMOTE_EXPLORER_VIEWLET_ID } from '../../../contrib/remot
// const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: { web: ContextKeyExpr.equals('terminalProcessSupported', true) } };
// const openSettings: WatermarkEntry = { text: localize('watermark.openSettings', "Open Settings"), id: 'workbench.action.openSettings' };
// // shown when Code is emtpty
// const noFolderEntries = [
// // showCommands,
// openFileNonMacOnly,
// openFolderNonMacOnly,
// openFileOrFolderMacOnly,
// openRecent,
// // newUntitledFileMacOnly
// ];
// const showCopilot = ContextKeyExpr.or(ContextKeyExpr.equals('chatSetupHidden', false), ContextKeyExpr.equals('chatSetupInstalled', true));
// const openChat: WatermarkEntry = { text: localize('watermark.openChat', "Open Chat"), id: 'workbench.action.chat.open', when: { native: showCopilot, web: showCopilot } };
// const openCopilotEdits: WatermarkEntry = { text: localize('watermark.openCopilotEdits', "Open Copilot Edits"), id: 'workbench.action.chat.openEditSession', when: { native: showCopilot, web: showCopilot } };
// const emptyWindowEntries: WatermarkEntry[] = coalesce([
// showCommands,
@@ -194,11 +191,11 @@ export class EditorGroupWatermark extends Disposable {
buttonContainer.style.alignItems = 'center'; // Center the buttons horizontally
buttonContainer.style.gap = '8px'; // Reduce gap between buttons from 16px to 8px
buttonContainer.style.marginBottom = '16px';
voidIconBox.appendChild(buttonContainer);
codeIconBox.appendChild(buttonContainer);
// Open a folder
const openFolderButton = h('button')
openFolderButton.root.classList.add('code-watermark-button')
openFolderButton.root.classList.add('code-openfolder-button')
openFolderButton.root.style.display = 'block'
openFolderButton.root.style.width = '124px' // Set width to 124px as requested
openFolderButton.root.textContent = 'Open Folder'
@@ -210,7 +207,19 @@ export class EditorGroupWatermark extends Disposable {
// this.commandService.executeCommand(isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder');
// }
}
codeIconBox.appendChild(openFolderButton.root);
buttonContainer.appendChild(openFolderButton.root);
// Open SSH button
const openSSHButton = h('button')
openSSHButton.root.classList.add('code-openssh-button')
openSSHButton.root.style.display = 'block'
openSSHButton.root.style.backgroundColor = '#5a5a5a' // Made darker than the default gray
openSSHButton.root.style.width = '124px' // Set width to 124px as requested
openSSHButton.root.textContent = 'Open SSH'
openSSHButton.root.onclick = () => {
this.viewsService.openViewContainer(REMOTE_EXPLORER_VIEWLET_ID);
}
buttonContainer.appendChild(openSSHButton.root);
// Recents
@@ -296,21 +305,21 @@ export class EditorGroupWatermark extends Disposable {
label2.set(keys2);
this.currentDisposables.add(label2);
const keys3 = this.keybindingService.lookupKeybinding('workbench.action.openGlobalKeybindings');
const button3 = append(recentsBox, $('button'));
button3.textContent = 'Code Settings'
button3.style.display = 'block'
button3.style.marginLeft = 'auto'
button3.style.marginRight = 'auto'
button3.classList.add('code-settings-watermark-button')
// const keys3 = this.keybindingService.lookupKeybinding('workbench.action.openGlobalKeybindings');
// const button3 = append(recentsBox, $('button'));
// button3.textContent = `Code Settings`
// button3.style.display = 'block'
// button3.style.marginLeft = 'auto'
// button3.style.marginRight = 'auto'
// button3.classList.add('code-settings-watermark-button')
const label3 = new KeybindingLabel(button3, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
if (keys3)
label3.set(keys3);
button3.onclick = () => {
this.commandService.executeCommand(CODE_OPEN_SETTINGS_ACTION_ID)
}
this.currentDisposables.add(label3);
// const label3 = new KeybindingLabel(button3, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
// if (keys3)
// label3.set(keys3);
// button3.onclick = () => {
// this.commandService.executeCommand(CODE_OPEN_SETTINGS_ACTION_ID)
// }
// this.currentDisposables.add(label3);
}
@@ -55,7 +55,7 @@
width: 100%;
max-height: 100%;
aspect-ratio: 1/1;
background-image: url('./void_cube_noshadow.png'); /* // Void */
background-image: url('./code_cube_noshadow.png'); /* // Code */
background-size: contain;
background-position-x: center;
background-repeat: no-repeat;
@@ -63,17 +63,17 @@
.void-void-icon,
.monaco-workbench.vs-dark .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./void_cube_noshadow.png'); /* // Void */
background-image: url('./code_cube_noshadow.png'); /* // Code */
}
.void-void-icon,
.monaco-workbench.hc-light .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./void_cube_noshadow.png'); /* // Void */
background-image: url('./code_cube_noshadow.png'); /* // Code */
}
.void-void-icon,
.monaco-workbench.hc-black .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./void_cube_noshadow.png'); /* // Void */
background-image: url('./code_cube_noshadow.png'); /* // Code */
}
.monaco-workbench .part.editor > .content:not(.empty) .editor-group-container > .editor-group-watermark > .shortcuts,
@@ -204,7 +204,7 @@ export class PaneCompositeBar extends Disposable {
private createMoveAction(viewContainer: ViewContainer, newLocation: ViewContainerLocation, defaultLocation: ViewContainerLocation): IAction {
return toAction({
id: `moveViewContainerTo${newLocation}`,
label: newLocation === ViewContainerLocation.Panel ? localize('panel', "Panel") : newLocation === ViewContainerLocation.Sidebar ? localize('sidebar', "Primary Side Bar") : localize('auxiliarybar', "Void Side Bar"),
label: newLocation === ViewContainerLocation.Panel ? localize('panel', "Panel") : newLocation === ViewContainerLocation.Sidebar ? localize('sidebar', "Primary Side Bar") : localize('auxiliarybar', "Code Side Bar"),
run: () => {
let index: number | undefined;
if (newLocation !== defaultLocation) {
@@ -312,51 +312,6 @@ registerAction2(class extends Action2 {
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.closePanel',
title: localize2('closePanel', 'Hide Panel'),
category: Categories.View,
icon: closeIcon,
menu: [{
id: MenuId.CommandPalette,
when: PanelVisibleContext,
}, {
id: MenuId.PanelTitle,
group: 'navigation',
order: 2
}]
});
}
run(accessor: ServicesAccessor) {
accessor.get(IWorkbenchLayoutService).setPartHidden(true, Parts.PANEL_PART);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.closeAuxiliaryBar',
title: localize2('closeSecondarySideBar', 'Hide Code Side Bar'),
category: Categories.View,
icon: closeIcon,
menu: [{
id: MenuId.CommandPalette,
when: AuxiliaryBarVisibleContext,
}, {
id: MenuId.AuxiliaryBarTitle,
group: 'navigation',
order: 2,
when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP)
}]
});
}
run(accessor: ServicesAccessor) {
accessor.get(IWorkbenchLayoutService).setPartHidden(true, Parts.AUXILIARYBAR_PART);
}
});
MenuRegistry.appendMenuItems([
{
id: MenuId.LayoutControlMenu,
@@ -429,7 +384,7 @@ export class MovePanelToSecondarySideBarAction extends MoveViewsBetweenPanelsAct
registerAction2(MovePanelToSidePanelAction);
registerAction2(MovePanelToSecondarySideBarAction);
// --- Move Void Side Bar Views To Panel
// --- Move Code Side Bar Views To Panel
class MoveSidePanelToPanelAction extends MoveViewsBetweenPanelsAction {
static readonly ID = 'workbench.action.moveSidePanelToPanel';
@@ -437,7 +392,7 @@ class MoveSidePanelToPanelAction extends MoveViewsBetweenPanelsAction {
constructor() {
super(ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel, {
id: MoveSidePanelToPanelAction.ID,
title: localize2('moveSidePanelToPanel', "Move Side Bar Views To Panel"), // Code - this seemed to have a typo before
title: localize2('moveSidePanelToPanel', "Move Code Side Bar Views To Panel"),
category: Categories.View,
f1: false
});
@@ -257,7 +257,7 @@
}
.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-left > .window-appicon:not(.codicon) {
background-image: url('../../../media/code-icon-sm.png');
background-image: url('../../../media/code-icon.svg');
background-repeat: no-repeat;
background-position: center center;
background-size: 16px;
@@ -271,23 +271,6 @@
display: none;
}
.monaco-workbench .part.titlebar > .titlebar-container .window-appicon > .home-bar-icon-badge {
position: absolute;
right: 9px;
bottom: 6px;
width: 8px;
height: 8px;
z-index: 1;
/* on top of home indicator */
background-image: url('../../../media/code-icon-sm.png');
background-repeat: no-repeat;
background-position: center center;
background-size: 8px;
pointer-events: none;
border-top: 1px solid transparent;
border-left: 1px solid transparent;
}
/* Window Controls Container */
.monaco-workbench .part.titlebar .window-controls-container {
display: flex;
@@ -547,12 +547,12 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'type': 'string',
'enum': ['default', 'top', 'bottom', 'hidden'],
'default': 'default',
'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar relative to the Primary and Code Side Bars."),
'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar relative to the Primary and Code Side Bars."), // Code side bar
'enumDescriptions': [
localize('workbench.activityBar.location.default', "Show the Activity Bar on the side of the Primary Side Bar and on top of the Code Side Bar."),
localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Code Side Bars."),
localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Code Side Bars."),
localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Code Side Bars.")
localize('workbench.activityBar.location.default', "Show the Activity Bar on the side of the Primary Side Bar and on top of the Code Side Bar."), // Code side bar
localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Code Side Bars."), // Code side bar
localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Code Side Bars."), // Code side bar
localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Code Side Bars.") // Code side bar
],
},
'workbench.activityBar.iconClickBehavior': {
@@ -612,7 +612,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'description': localize('workbench.hover.delay', "Controls the delay in milliseconds after which the hover is shown for workbench items (ex. some extension provided tree view items). Already visible items may require a refresh before reflecting this setting change."),
// Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely.
// On Mac, the delay is 1500.
'default': isMacintosh ? 300 : 300, // <-- Code edited this to 300 : 300 (was 1500 : 500)
'default': 300, // Code changed this from isMacintosh ? 1500 : 500,
'minimum': 0
},
'workbench.reduceMotion': {
@@ -678,7 +678,7 @@ const defaultChat = {
};
// // Add next to the command center if command center is disabled
// Void commented this out with /* */ - copilot head
// Code commented this out with /* */ - copilot head
/* MenuRegistry.appendMenuItem(MenuId.CommandCenter, {
submenu: MenuId.ChatTitleBarMenu,
title: localize('title4', "Copilot"),
@@ -44,7 +44,7 @@ import { ChatViewId } from './chat.js';
// --- Chat Container & View Registration
// Void commented this out
// Code commented this out
// const chatViewContainer: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
// id: CHAT_SIDEBAR_PANEL_ID,
// title: localize2('chat.viewContainer.label', "Chat"),
@@ -86,7 +86,7 @@ import { ChatViewId } from './chat.js';
// Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(chatViewDescriptor, chatViewContainer);
// --- Edits Container & View Registration
// Void commented this out
// Code commented this out
// const editsViewContainer: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
// id: CHAT_EDITING_SIDEBAR_PANEL_ID,
@@ -435,7 +435,7 @@ Registry.as<IExtensionFeaturesRegistry>(Extensions.ExtensionFeaturesRegistry).re
});
// Void commented this out
// Code commented this out
// // TODO@roblourens remove after a few months
// export class MovedChatEditsViewPane extends ViewPane {
@@ -33,7 +33,7 @@ registerAction2(class extends Action2 {
title: localize2('dummy', 'dummy: Init'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyCode.Digit0,
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
@@ -4,3 +4,23 @@
export const CODE_CTRL_L_ACTION_ID = 'void.ctrlLAction'
export const CODE_CTRL_K_ACTION_ID = 'void.ctrlKAction'
export const CODE_ACCEPT_DIFF_ACTION_ID = 'void.acceptDiff'
export const CODE_REJECT_DIFF_ACTION_ID = 'void.rejectDiff'
export const CODE_GOTO_NEXT_DIFF_ACTION_ID = 'void.goToNextDiff'
export const CODE_GOTO_PREV_DIFF_ACTION_ID = 'void.goToPrevDiff'
export const CODE_GOTO_NEXT_URI_ACTION_ID = 'void.goToNextUri'
export const CODE_GOTO_PREV_URI_ACTION_ID = 'void.goToPrevUri'
export const CODE_ACCEPT_FILE_ACTION_ID = 'void.acceptFile'
export const CODE_REJECT_FILE_ACTION_ID = 'void.rejectFile'
export const CODE_ACCEPT_ALL_DIFFS_ACTION_ID = 'void.acceptAllDiffs'
export const CODE_REJECT_ALL_DIFFS_ACTION_ID = 'void.rejectAllDiffs'
@@ -18,8 +18,8 @@ import { extractCodeFromRegular } from '../common/helpers/extractCodeFromResult.
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
import { isWindows } from '../../../../base/common/platform.js';
import { IVoidSettingsService } from '../common/voidSettingsService.js';
import { FeatureName } from '../common/voidSettingsTypes.js';
import { ICodeSettingsService } from '../common/codeSettingsService.js';
import { FeatureName } from '../common/codeSettingsTypes.js';
import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
// import { IContextGatheringService } from './contextGatheringService.js';
@@ -28,7 +28,7 @@ import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
const allLinebreakSymbols = ['\r\n', '\n']
const _ln = isWindows ? allLinebreakSymbols[0] : allLinebreakSymbols[1]
// The extension this was called from is here - https://github.com/hanzoai/code/blob/autocomplete/extensions/void/src/extension/extension.ts
// The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/code/src/extension/extension.ts
/*
@@ -893,7 +893,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
@IEditorService private readonly _editorService: IEditorService,
@IModelService private readonly _modelService: IModelService,
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
@ICodeSettingsService private readonly _settingsService: ICodeSettingsService,
@IConvertToLLMMessageService private readonly _convertToLLMMessageService: IConvertToLLMMessageService
// @IContextGatheringService private readonly _contextGatheringService: IContextGatheringService,
) {
@@ -14,8 +14,8 @@ import { ILLMMessageService } from '../common/sendLLMMessageService.js';
import { chat_userMessageContent, isABuiltinToolName } from '../common/prompt/prompts.js';
import { AnthropicReasoning, getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js';
import { IVoidSettingsService } from '../common/voidSettingsService.js';
import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/codeSettingsTypes.js';
import { ICodeSettingsService } from '../common/codeSettingsService.js';
import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, ToolCallParams, ToolName, ToolResult } from '../common/toolsServiceTypes.js';
import { IToolsService } from './toolsService.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
@@ -24,10 +24,10 @@ import { ChatMessage, CheckpointEntry, CodespanLocationLink, StagingSelectionIte
import { Position } from '../../../../editor/common/core/position.js';
import { IMetricsService } from '../common/metricsService.js';
import { shorten } from '../../../../base/common/labels.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { ICodeModelService } from '../common/codeModelService.js';
import { findLast, findLastIdx } from '../../../../base/common/arraysFind.js';
import { IEditCodeService } from './editCodeServiceInterface.js';
import { VoidFileSnapshot } from '../common/editCodeServiceTypes.js';
import { CodeFileSnapshot } from '../common/editCodeServiceTypes.js';
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
import { truncate } from '../../../../base/common/strings.js';
import { THREAD_STORAGE_KEY } from '../common/storageKeys.js';
@@ -314,10 +314,10 @@ class ChatThreadService extends Disposable implements IChatThreadService {
constructor(
@IStorageService private readonly _storageService: IStorageService,
@IVoidModelService private readonly _voidModelService: IVoidModelService,
@ICodeModelService private readonly _codeModelService: ICodeModelService,
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
@IToolsService private readonly _toolsService: IToolsService,
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
@ICodeSettingsService private readonly _settingsService: ICodeSettingsService,
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@IMetricsService private readonly _metricsService: IMetricsService,
@IEditCodeService private readonly _editCodeService: IEditCodeService,
@@ -443,11 +443,11 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// set streamState
const messages = newState.allThreads[threadId]?.messages
const lastMessage = messages && messages[messages.length - 1]
// if awaiting user but stream state doesn't indicate it (happens if restart Void)
// if awaiting user but stream state doesn't indicate it (happens if restart Code)
if (lastMessage && lastMessage.role === 'tool' && lastMessage.type === 'tool_request')
this._setStreamState(threadId, { isRunning: 'awaiting_user', })
// if running now but stream state doesn't indicate it (happens if restart Void), cancel that last tool
// if running now but stream state doesn't indicate it (happens if restart Code), cancel that last tool
if (lastMessage && lastMessage.role === 'tool' && lastMessage.type === 'running_now') {
this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', content: lastMessage.content, id: lastMessage.id, rawParams: lastMessage.rawParams, result: null, name: lastMessage.name, params: lastMessage.params, mcpServerName: lastMessage.mcpServerName })
@@ -945,11 +945,11 @@ class ChatThreadService extends Disposable implements IChatThreadService {
private _getCheckpointInfo = (checkpointMessage: ChatMessage & { role: 'checkpoint' }, fsPath: string, opts: { includeUserModifiedChanges: boolean }) => {
const voidFileSnapshot = checkpointMessage.voidFileSnapshotOfURI ? checkpointMessage.voidFileSnapshotOfURI[fsPath] ?? null : null
if (!opts.includeUserModifiedChanges) { return { voidFileSnapshot, } }
const codeFileSnapshot = checkpointMessage.codeFileSnapshotOfURI ? checkpointMessage.codeFileSnapshotOfURI[fsPath] ?? null : null
if (!opts.includeUserModifiedChanges) { return { codeFileSnapshot, } }
const userModifiedVoidFileSnapshot = fsPath in checkpointMessage.userModifications.voidFileSnapshotOfURI ? checkpointMessage.userModifications.voidFileSnapshotOfURI[fsPath] ?? null : null
return { voidFileSnapshot: userModifiedVoidFileSnapshot ?? voidFileSnapshot, }
const userModifiedCodeFileSnapshot = fsPath in checkpointMessage.userModifications.codeFileSnapshotOfURI ? checkpointMessage.userModifications.codeFileSnapshotOfURI[fsPath] ?? null : null
return { codeFileSnapshot: userModifiedCodeFileSnapshot ?? codeFileSnapshot, }
}
private _computeNewCheckpointInfo({ threadId }: { threadId: string }) {
@@ -959,59 +959,59 @@ class ChatThreadService extends Disposable implements IChatThreadService {
const lastCheckpointIdx = findLastIdx(thread.messages, (m) => m.role === 'checkpoint') ?? -1
if (lastCheckpointIdx === -1) return
const voidFileSnapshotOfURI: { [fsPath: string]: VoidFileSnapshot | undefined } = {}
const codeFileSnapshotOfURI: { [fsPath: string]: CodeFileSnapshot | undefined } = {}
// add a change for all the URIs in the checkpoint history
const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: 0, hiIdx: lastCheckpointIdx, }) ?? {}
for (const fsPath in lastIdxOfURI ?? {}) {
const { model } = this._voidModelService.getModelFromFsPath(fsPath)
const { model } = this._codeModelService.getModelFromFsPath(fsPath)
if (!model) continue
const checkpoint2 = thread.messages[lastIdxOfURI[fsPath]] || null
if (!checkpoint2) continue
if (checkpoint2.role !== 'checkpoint') continue
const res = this._getCheckpointInfo(checkpoint2, fsPath, { includeUserModifiedChanges: false })
if (!res) continue
const { voidFileSnapshot: oldVoidFileSnapshot } = res
const { codeFileSnapshot: oldCodeFileSnapshot } = res
// if there was any change to the str or diffAreaSnapshot, update. rough approximation of equality, oldDiffAreasSnapshot === diffAreasSnapshot is not perfect
const voidFileSnapshot = this._editCodeService.getVoidFileSnapshot(URI.file(fsPath))
if (oldVoidFileSnapshot === voidFileSnapshot) continue
voidFileSnapshotOfURI[fsPath] = voidFileSnapshot
const codeFileSnapshot = this._editCodeService.getCodeFileSnapshot(URI.file(fsPath))
if (oldCodeFileSnapshot === codeFileSnapshot) continue
codeFileSnapshotOfURI[fsPath] = codeFileSnapshot
}
// // add a change for all user-edited files (that aren't in the history)
// for (const fsPath of this._userModifiedFilesToCheckInCheckpoints.keys()) {
// if (fsPath in lastIdxOfURI) continue // if already visisted, don't visit again
// const { model } = this._voidModelService.getModelFromFsPath(fsPath)
// const { model } = this._codeModelService.getModelFromFsPath(fsPath)
// if (!model) continue
// currStrOfFsPath[fsPath] = model.getValue(EndOfLinePreference.LF)
// }
return { voidFileSnapshotOfURI }
return { codeFileSnapshotOfURI }
}
private _addUserCheckpoint({ threadId }: { threadId: string }) {
const { voidFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {}
const { codeFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {}
this._addCheckpoint(threadId, {
role: 'checkpoint',
type: 'user_edit',
voidFileSnapshotOfURI: voidFileSnapshotOfURI ?? {},
userModifications: { voidFileSnapshotOfURI: {}, },
codeFileSnapshotOfURI: codeFileSnapshotOfURI ?? {},
userModifications: { codeFileSnapshotOfURI: {}, },
})
}
// call this right after LLM edits a file
private _addToolEditCheckpoint({ threadId, uri, }: { threadId: string, uri: URI }) {
const thread = this.state.allThreads[threadId]
if (!thread) return
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
if (!model) return // should never happen
const diffAreasSnapshot = this._editCodeService.getVoidFileSnapshot(uri)
const diffAreasSnapshot = this._editCodeService.getCodeFileSnapshot(uri)
this._addCheckpoint(threadId, {
role: 'checkpoint',
type: 'tool_edit',
voidFileSnapshotOfURI: { [uri.fsPath]: diffAreasSnapshot },
userModifications: { voidFileSnapshotOfURI: {} },
codeFileSnapshotOfURI: { [uri.fsPath]: diffAreasSnapshot },
userModifications: { codeFileSnapshotOfURI: {} },
})
}
@@ -1035,7 +1035,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
for (let i = loIdx; i <= hiIdx; i += 1) {
const message = thread.messages[i]
if (message?.role !== 'checkpoint') continue
for (const fsPath in message.voidFileSnapshotOfURI) { // do not include userModified.beforeStrOfURI here, jumping should not include those changes
for (const fsPath in message.codeFileSnapshotOfURI) { // do not include userModified.beforeStrOfURI here, jumping should not include those changes
lastIdxOfURI[fsPath] = i
}
}
@@ -1055,13 +1055,13 @@ class ChatThreadService extends Disposable implements IChatThreadService {
return [checkpoint, currCheckpointIdx]
}
private _addUserModificationsToCurrCheckpoint({ threadId }: { threadId: string }) {
const { voidFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {}
const { codeFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {}
const res = this._readCurrentCheckpoint(threadId)
if (!res) return
const [checkpoint, checkpointIdx] = res
this._editMessageInThread(threadId, checkpointIdx, {
...checkpoint,
userModifications: { voidFileSnapshotOfURI: voidFileSnapshotOfURI ?? {}, },
userModifications: { codeFileSnapshotOfURI: codeFileSnapshotOfURI ?? {}, },
})
}
@@ -1139,9 +1139,9 @@ We only need to do it for files that were edited since `to`, ie files between to
if (message.role !== 'checkpoint') continue
const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified })
if (!res) continue
const { voidFileSnapshot } = res
if (!voidFileSnapshot) continue
this._editCodeService.restoreVoidFileSnapshot(URI.file(fsPath), voidFileSnapshot)
const { codeFileSnapshot } = res
if (!codeFileSnapshot) continue
this._editCodeService.restoreCodeFileSnapshot(URI.file(fsPath), codeFileSnapshot)
break
}
}
@@ -1173,9 +1173,9 @@ We only need to do it for files that were edited since `from`, ie files between
if (message.role !== 'checkpoint') continue
const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified })
if (!res) continue
const { voidFileSnapshot } = res
if (!voidFileSnapshot) continue
this._editCodeService.restoreVoidFileSnapshot(URI.file(fsPath), voidFileSnapshot)
const { codeFileSnapshot } = res
if (!codeFileSnapshot) continue
this._editCodeService.restoreCodeFileSnapshot(URI.file(fsPath), codeFileSnapshot)
break
}
}
@@ -1463,7 +1463,7 @@ We only need to do it for files that were edited since `from`, ie files between
// check all prevUris for the target
for (const uri of prevUris) {
const modelRef = await this._voidModelService.getModelSafe(uri)
const modelRef = await this._codeModelService.getModelSafe(uri)
const { model } = modelRef
if (!model) continue
@@ -26,7 +26,60 @@ import './autocompleteService.js'
import './codeSettingsPane.js'
// register css
import './media/void.css'
import './media/code.css'
// update (frontend part, also see platform/)
import './codeUpdateActions.js'
import './convertToLLMMessageWorkbenchContrib.js'
// tools
import './toolsService.js'
import './terminalToolService.js'
// register Thread History
import './chatThreadService.js'
// ping
import './metricsPollService.js'
// helper services
import './helperServices/consistentItemService.js'
// register selection helper
import './codeSelectionHelperWidget.js'
// register tooltip service
import './tooltipService.js'
// register onboarding service
import './codeOnboardingService.js'
// register misc service
import './miscWokrbenchContrib.js'
// register file service (for explorer context menu)
import './fileService.js'
// register source control management
import './codeSCMService.js'
// ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ----------
// llmMessage
import '../common/sendLLMMessageService.js'
// codeSettings
import '../common/codeSettingsService.js'
// refreshModel
import '../common/refreshModelService.js'
// metrics
import '../common/metricsService.js'
// updates
import '../common/codeUpdateService.js'
// model service
import '../common/codeModelService.js'
@@ -11,7 +11,7 @@ import { Widget } from '../../../../base/browser/ui/widget.js';
import { IOverlayWidget, ICodeEditor, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { mountVoidCommandBar } from './react/out/void-editor-widgets-tsx/index.js'
import { mountCodeCommandBar } from './react/out/code-editor-widgets-tsx/index.js'
import { deepClone } from '../../../../base/common/objects.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { IEditCodeService } from './editCodeServiceInterface.js';
@@ -19,7 +19,7 @@ import { ITextModel } from '../../../../editor/common/model.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { VOID_ACCEPT_DIFF_ACTION_ID, VOID_REJECT_DIFF_ACTION_ID, VOID_GOTO_NEXT_DIFF_ACTION_ID, VOID_GOTO_PREV_DIFF_ACTION_ID, VOID_GOTO_NEXT_URI_ACTION_ID, VOID_GOTO_PREV_URI_ACTION_ID, VOID_ACCEPT_FILE_ACTION_ID, VOID_REJECT_FILE_ACTION_ID, VOID_ACCEPT_ALL_DIFFS_ACTION_ID, VOID_REJECT_ALL_DIFFS_ACTION_ID } from './actionIDs.js';
import { CODE_ACCEPT_DIFF_ACTION_ID, CODE_REJECT_DIFF_ACTION_ID, CODE_GOTO_NEXT_DIFF_ACTION_ID, CODE_GOTO_PREV_DIFF_ACTION_ID, CODE_GOTO_NEXT_URI_ACTION_ID, CODE_GOTO_PREV_URI_ACTION_ID, CODE_ACCEPT_FILE_ACTION_ID, CODE_REJECT_FILE_ACTION_ID, CODE_ACCEPT_ALL_DIFFS_ACTION_ID, CODE_REJECT_ALL_DIFFS_ACTION_ID } from './actionIDs.js';
import { localize2 } from '../../../../nls.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
@@ -27,11 +27,11 @@ import { IMetricsService } from '../common/metricsService.js';
import { KeyMod } from '../../../../editor/common/services/editorBaseApi.js';
import { KeyCode } from '../../../../base/common/keyCodes.js';
import { ScrollType } from '../../../../editor/common/editorCommon.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { ICodeModelService } from '../common/codeModelService.js';
export interface IVoidCommandBarService {
export interface ICodeCommandBarService {
readonly _serviceBrand: undefined;
stateOfURI: { [uri: string]: CommandBarStateType };
sortedURIs: URI[];
@@ -54,7 +54,7 @@ export interface IVoidCommandBarService {
}
export const IVoidCommandBarService = createDecorator<IVoidCommandBarService>('VoidCommandBarService');
export const ICodeCommandBarService = createDecorator<ICodeCommandBarService>('CodeCommandBarService');
export type CommandBarStateType = undefined | {
@@ -75,10 +75,10 @@ const defaultState: NonNullable<CommandBarStateType> = {
}
export class VoidCommandBarService extends Disposable implements IVoidCommandBarService {
export class CodeCommandBarService extends Disposable implements ICodeCommandBarService {
_serviceBrand: undefined;
static readonly ID: 'void.VoidCommandBarService'
static readonly ID: 'void.CodeCommandBarService'
// depends on uri -> diffZone -> {streaming, diffs}
public stateOfURI: { [uri: string]: CommandBarStateType } = {}
@@ -100,7 +100,7 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IModelService private readonly _modelService: IModelService,
@IEditCodeService private readonly _editCodeService: IEditCodeService,
@IVoidModelService private readonly _voidModelService: IVoidModelService,
@ICodeModelService private readonly _codeModelService: ICodeModelService,
) {
super();
@@ -460,7 +460,7 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar
if (!nextURI) return;
// Get the model for this URI
const { model } = await this._voidModelService.getModelSafe(nextURI);
const { model } = await this._codeModelService.getModelSafe(nextURI);
if (!model) return;
// Find an editor to use
@@ -488,10 +488,10 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar
}
registerSingleton(IVoidCommandBarService, VoidCommandBarService, InstantiationType.Delayed); // delayed is needed here :(
registerSingleton(ICodeCommandBarService, CodeCommandBarService, InstantiationType.Delayed); // delayed is needed here :(
export type VoidCommandBarProps = {
export type CodeCommandBarProps = {
uri: URI | null;
editor: ICodeEditor;
}
@@ -535,12 +535,12 @@ class AcceptRejectAllFloatingWidget extends Widget implements IOverlayWidget {
this.instantiationService.invokeFunction(accessor => {
const uri = editor.getModel()?.uri || null
const res = mountVoidCommandBar(root, accessor, { uri, editor } satisfies VoidCommandBarProps)
const res = mountCodeCommandBar(root, accessor, { uri, editor } satisfies CodeCommandBarProps)
if (!res) return
this._register(toDisposable(() => res.dispose?.()))
this._register(editor.onWillChangeModel((model) => {
const uri = model.newModelUrl
res.rerender({ uri, editor } satisfies VoidCommandBarProps)
res.rerender({ uri, editor } satisfies CodeCommandBarProps)
}))
})
}
@@ -570,20 +570,20 @@ class AcceptRejectAllFloatingWidget extends Widget implements IOverlayWidget {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_ACCEPT_DIFF_ACTION_ID,
id: CODE_ACCEPT_DIFF_ACTION_ID,
f1: true,
title: localize2('voidAcceptDiffAction', 'Void: Accept Diff'),
title: localize2('codeAcceptDiffAction', 'Code: Accept Diff'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.Enter,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter },
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const editCodeService = accessor.get(IEditCodeService);
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const metricsService = accessor.get(IMetricsService);
@@ -613,20 +613,20 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_REJECT_DIFF_ACTION_ID,
id: CODE_REJECT_DIFF_ACTION_ID,
f1: true,
title: localize2('voidRejectDiffAction', 'Void: Reject Diff'),
title: localize2('codeRejectDiffAction', 'Code: Reject Diff'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.Backspace,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Backspace },
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const editCodeService = accessor.get(IEditCodeService);
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const metricsService = accessor.get(IMetricsService);
const activeURI = commandBarService.activeURI;
@@ -654,19 +654,19 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_GOTO_NEXT_DIFF_ACTION_ID,
id: CODE_GOTO_NEXT_DIFF_ACTION_ID,
f1: true,
title: localize2('voidGoToNextDiffAction', 'Void: Go to Next Diff'),
title: localize2('codeGoToNextDiffAction', 'Code: Go to Next Diff'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.DownArrow },
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const metricsService = accessor.get(IMetricsService);
const nextDiffIdx = commandBarService.getNextDiffIdx(1);
@@ -681,19 +681,19 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_GOTO_PREV_DIFF_ACTION_ID,
id: CODE_GOTO_PREV_DIFF_ACTION_ID,
f1: true,
title: localize2('voidGoToPrevDiffAction', 'Void: Go to Previous Diff'),
title: localize2('codeGoToPrevDiffAction', 'Code: Go to Previous Diff'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.UpArrow },
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const metricsService = accessor.get(IMetricsService);
const prevDiffIdx = commandBarService.getNextDiffIdx(-1);
@@ -708,19 +708,19 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_GOTO_NEXT_URI_ACTION_ID,
id: CODE_GOTO_NEXT_URI_ACTION_ID,
f1: true,
title: localize2('voidGoToNextUriAction', 'Void: Go to Next File with Diffs'),
title: localize2('codeGoToNextUriAction', 'Code: Go to Next File with Diffs'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.RightArrow },
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const metricsService = accessor.get(IMetricsService);
const nextUriIdx = commandBarService.getNextUriIdx(1);
@@ -735,19 +735,19 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_GOTO_PREV_URI_ACTION_ID,
id: CODE_GOTO_PREV_URI_ACTION_ID,
f1: true,
title: localize2('voidGoToPrevUriAction', 'Void: Go to Previous File with Diffs'),
title: localize2('codeGoToPrevUriAction', 'Code: Go to Previous File with Diffs'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.LeftArrow },
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const metricsService = accessor.get(IMetricsService);
const prevUriIdx = commandBarService.getNextUriIdx(-1);
@@ -762,18 +762,18 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_ACCEPT_FILE_ACTION_ID,
id: CODE_ACCEPT_FILE_ACTION_ID,
f1: true,
title: localize2('voidAcceptFileAction', 'Void: Accept All Diffs in Current File'),
title: localize2('codeAcceptFileAction', 'Code: Accept All Diffs in Current File'),
keybinding: {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Enter,
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const editCodeService = accessor.get(IEditCodeService);
const metricsService = accessor.get(IMetricsService);
@@ -793,18 +793,18 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_REJECT_FILE_ACTION_ID,
id: CODE_REJECT_FILE_ACTION_ID,
f1: true,
title: localize2('voidRejectFileAction', 'Void: Reject All Diffs in Current File'),
title: localize2('codeRejectFileAction', 'Code: Reject All Diffs in Current File'),
keybinding: {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Backspace,
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const editCodeService = accessor.get(IEditCodeService);
const metricsService = accessor.get(IMetricsService);
@@ -824,18 +824,18 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_ACCEPT_ALL_DIFFS_ACTION_ID,
id: CODE_ACCEPT_ALL_DIFFS_ACTION_ID,
f1: true,
title: localize2('voidAcceptAllDiffsAction', 'Void: Accept All Diffs in All Files'),
title: localize2('codeAcceptAllDiffsAction', 'Code: Accept All Diffs in All Files'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const metricsService = accessor.get(IMetricsService);
if (commandBarService.anyFileIsStreaming()) return;
@@ -849,18 +849,18 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: VOID_REJECT_ALL_DIFFS_ACTION_ID,
id: CODE_REJECT_ALL_DIFFS_ACTION_ID,
f1: true,
title: localize2('voidRejectAllDiffsAction', 'Void: Reject All Diffs in All Files'),
title: localize2('codeRejectAllDiffsAction', 'Code: Reject All Diffs in All Files'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace,
weight: KeybindingWeight.VoidExtension,
weight: KeybindingWeight.CodeExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const commandBarService = accessor.get(IVoidCommandBarService);
const commandBarService = accessor.get(ICodeCommandBarService);
const metricsService = accessor.get(IMetricsService);
if (commandBarService.anyFileIsStreaming()) return;
@@ -7,12 +7,12 @@ import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
import { mountVoidOnboarding } from './react/out/void-onboarding/index.js'
import { mountCodeOnboarding } from './react/out/code-onboarding/index.js'
import { h, getActiveWindow } from '../../../../base/browser/dom.js';
// Onboarding contribution that mounts the component at startup
export class OnboardingContribution extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.voidOnboarding';
static readonly ID = 'workbench.contrib.codeOnboarding';
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@@ -33,7 +33,7 @@ export class OnboardingContribution extends Disposable implements IWorkbenchCont
const onboardingContainer = h('div.void-onboarding-container').root;
workbench.appendChild(onboardingContainer);
this.instantiationService.invokeFunction((accessor: ServicesAccessor) => {
const result = mountVoidOnboarding(onboardingContainer, accessor);
const result = mountCodeOnboarding(onboardingContainer, accessor);
if (result && typeof result.dispose === 'function') {
this._register(toDisposable(result.dispose));
}
@@ -9,12 +9,12 @@ import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/c
import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'
import { ISCMService } from '../../scm/common/scm.js'
import { ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js'
import { IVoidSCMService } from '../common/voidSCMTypes.js'
import { ICodeSCMService } from '../common/codeSCMTypes.js'
import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js'
import { IVoidSettingsService } from '../common/voidSettingsService.js'
import { ICodeSettingsService } from '../common/codeSettingsService.js'
import { IConvertToLLMMessageService } from './convertToLLMMessageService.js'
import { ILLMMessageService } from '../common/sendLLMMessageService.js'
import { ModelSelection, OverridesOfModel, ModelSelectionOptions } from '../common/voidSettingsTypes.js'
import { ModelSelection, OverridesOfModel, ModelSelectionOptions } from '../common/codeSettingsTypes.js'
import { gitCommitMessage_systemMessage, gitCommitMessage_userMessage } from '../common/prompt/prompts.js'
import { LLMChatMessage } from '../common/sendLLMMessageTypes.js'
import { generateUuid } from '../../../../base/common/uuid.js'
@@ -37,22 +37,22 @@ export interface IGenerateCommitMessageService {
abort(): void
}
export const IGenerateCommitMessageService = createDecorator<IGenerateCommitMessageService>('voidGenerateCommitMessageService');
export const IGenerateCommitMessageService = createDecorator<IGenerateCommitMessageService>('codeGenerateCommitMessageService');
const loadingContextKey = 'voidSCMGenerateCommitMessageLoading'
const loadingContextKey = 'codeSCMGenerateCommitMessageLoading'
class GenerateCommitMessageService extends Disposable implements IGenerateCommitMessageService {
readonly _serviceBrand: undefined;
private readonly execute = new ThrottledDelayer(300)
private llmRequestId: string | null = null
private currentRequestId: string | null = null
private voidSCM: IVoidSCMService
private codeSCM: ICodeSCMService
private loadingContextKey: IContextKey<boolean>
constructor(
@ISCMService private readonly scmService: ISCMService,
@IMainProcessService mainProcessService: IMainProcessService,
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
@ICodeSettingsService private readonly codeSettingsService: ICodeSettingsService,
@IConvertToLLMMessageService private readonly convertToLLMMessageService: IConvertToLLMMessageService,
@ILLMMessageService private readonly llmMessageService: ILLMMessageService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@@ -60,7 +60,7 @@ class GenerateCommitMessageService extends Disposable implements IGenerateCommit
) {
super()
this.loadingContextKey = this.contextKeyService.createKey(loadingContextKey, false)
this.voidSCM = ProxyChannel.toService<IVoidSCMService>(mainProcessService.getChannel('void-channel-scm'))
this.codeSCM = ProxyChannel.toService<ICodeSCMService>(mainProcessService.getChannel('code-channel-scm'))
}
override dispose() {
@@ -78,17 +78,17 @@ class GenerateCommitMessageService extends Disposable implements IGenerateCommit
try {
const { path, repo } = this.gitRepoInfo()
const [stat, sampledDiffs, branch, log] = await Promise.all([
this.voidSCM.gitStat(path),
this.voidSCM.gitSampledDiffs(path),
this.voidSCM.gitBranch(path),
this.voidSCM.gitLog(path)
this.codeSCM.gitStat(path),
this.codeSCM.gitSampledDiffs(path),
this.codeSCM.gitBranch(path),
this.codeSCM.gitLog(path)
])
if (!this.isCurrentRequest(requestId)) { throw new CancellationError() }
const modelSelection = this.voidSettingsService.state.modelSelectionOfFeature['SCM'] ?? null
const modelSelectionOptions = modelSelection ? this.voidSettingsService.state.optionsOfModelSelection['SCM'][modelSelection?.providerName]?.[modelSelection.modelName] : undefined
const overridesOfModel = this.voidSettingsService.state.overridesOfModel
const modelSelection = this.codeSettingsService.state.modelSelectionOfFeature['SCM'] ?? null
const modelSelectionOptions = modelSelection ? this.codeSettingsService.state.optionsOfModelSelection['SCM'][modelSelection?.providerName]?.[modelSelection.modelName] : undefined
const overridesOfModel = this.codeSettingsService.state.overridesOfModel
const modelOptions: ModelOptions = { modelSelection, modelSelectionOptions, overridesOfModel }
@@ -159,7 +159,7 @@ class GenerateCommitMessageService extends Disposable implements IGenerateCommit
onAbort: () => {
reject(new CancellationError())
},
logging: { loggingName: 'VoidSCM - Commit Message' },
logging: { loggingName: 'CodeSCM - Commit Message' },
})
})
}
@@ -177,7 +177,7 @@ class GenerateCommitMessageService extends Disposable implements IGenerateCommit
private onError(error: any) {
if (!isCancellationError(error)) {
console.error(error)
this.notificationService.error(localize2('voidFailedToGenerateCommitMessage', 'Failed to generate commit message.').value)
this.notificationService.error(localize2('codeFailedToGenerateCommitMessage', 'Failed to generate commit message.').value)
}
}
}
@@ -186,9 +186,9 @@ class GenerateCommitMessageAction extends Action2 {
constructor() {
super({
id: 'void.generateCommitMessageAction',
title: localize2('voidCommitMessagePrompt', 'Void: Generate Commit Message'),
title: localize2('codeCommitMessagePrompt', 'Code: Generate Commit Message'),
icon: ThemeIcon.fromId('sparkle'),
tooltip: localize2('voidCommitMessagePromptTooltip', 'Void: Generate Commit Message'),
tooltip: localize2('codeCommitMessagePromptTooltip', 'Code: Generate Commit Message'),
f1: true,
menu: [{
id: MenuId.SCMInputBox,
@@ -208,9 +208,9 @@ class LoadingGenerateCommitMessageAction extends Action2 {
constructor() {
super({
id: 'void.loadingGenerateCommitMessageAction',
title: localize2('voidCommitMessagePromptCancel', 'Void: Cancel Commit Message Generation'),
title: localize2('codeCommitMessagePromptCancel', 'Code: Cancel Commit Message Generation'),
icon: ThemeIcon.fromId('stop-circle'),
tooltip: localize2('voidCommitMessagePromptCancelTooltip', 'Void: Cancel Commit Message Generation'),
tooltip: localize2('codeCommitMessagePromptCancelTooltip', 'Code: Cancel Commit Message Generation'),
f1: false, //Having a cancel command in the command palette is more confusing than useful.
menu: [{
id: MenuId.SCMInputBox,
@@ -11,9 +11,9 @@ import { IEditorContribution } from '../../../../editor/common/editorCommon.js';
import { Selection } from '../../../../editor/common/core/selection.js';
import { RunOnceScheduler } from '../../../../base/common/async.js';
import * as dom from '../../../../base/browser/dom.js';
import { mountVoidSelectionHelper } from './react/out/void-editor-widgets-tsx/index.js';
import { mountCodeSelectionHelper } from './react/out/code-editor-widgets-tsx/index.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IVoidSettingsService } from '../common/voidSettingsService.js';
import { ICodeSettingsService } from '../common/codeSettingsService.js';
import { EditorOption } from '../../../../editor/common/config/editorOptions.js';
import { getLengthOfTextPx } from './editCodeService.js';
@@ -22,13 +22,13 @@ const minDistanceFromRightPx = 400;
const minLeftPx = 60;
export type VoidSelectionHelperProps = {
export type CodeSelectionHelperProps = {
rerenderKey: number // alternates between 0 and 1
}
export class SelectionHelperContribution extends Disposable implements IEditorContribution, IOverlayWidget {
public static readonly ID = 'editor.contrib.voidSelectionHelper';
public static readonly ID = 'editor.contrib.codeSelectionHelper';
// react
private _rootHTML: HTMLElement;
private _rerender: (props?: any) => void = () => { };
@@ -43,7 +43,7 @@ export class SelectionHelperContribution extends Disposable implements IEditorCo
constructor(
private readonly _editor: ICodeEditor,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IVoidSettingsService private readonly _voidSettingsService: IVoidSettingsService
@ICodeSettingsService private readonly _codeSettingsService: ICodeSettingsService
) {
super();
@@ -63,7 +63,7 @@ export class SelectionHelperContribution extends Disposable implements IEditorCo
if (this._reactComponentDisposable) {
this._reactComponentDisposable.dispose();
}
const res = mountVoidSelectionHelper(content, accessor);
const res = mountCodeSelectionHelper(content, accessor);
if (!res) return;
this._reactComponentDisposable = res;
@@ -242,11 +242,11 @@ export class SelectionHelperContribution extends Disposable implements IEditorCo
this._isVisible = true;
// rerender
const enabled = this._voidSettingsService.state.globalSettings.showInlineSuggestions
const enabled = this._codeSettingsService.state.globalSettings.showInlineSuggestions
&& this._editor.hasTextFocus() // needed since VS Code counts unfocused selections as selections, which causes this to rerender when it shouldnt (bad ux)
if (enabled) {
this._rerender({ rerenderKey: this._rerenderKey } satisfies VoidSelectionHelperProps)
this._rerender({ rerenderKey: this._rerenderKey } satisfies CodeSelectionHelperProps)
this._rerenderKey = (this._rerenderKey + 1) % 2;
// this._reactComponentRerender();
}
@@ -49,7 +49,7 @@ class CodeSettingsInput extends EditorInput {
}
override getName(): string {
return nls.localize('codeSettingsInputsName', 'Code Settings');
return nls.localize('codeSettingsInputsName', 'Code\'s Settings');
}
override getIcon() {
@@ -90,7 +90,8 @@ class CodeSettingsPane extends EditorPane {
// Mount React into the scrollable content
this.instantiationService.invokeFunction(accessor => {
const disposables: IDisposable[] | undefined = mountCodeSettings(settingsElt, accessor);
const disposeFn = mountCodeSettings(settingsElt, accessor)?.dispose;
this._register(toDisposable(() => disposeFn?.()))
// setTimeout(() => { // this is a complete hack and I don't really understand how scrollbar works here
// this._scrollbar?.scanDomNode();
@@ -111,7 +112,7 @@ class CodeSettingsPane extends EditorPane {
// register Settings pane
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
EditorPaneDescriptor.create(CodeSettingsPane, CodeSettingsPane.ID, nls.localize('CodeSettingsPane', "Code Settings Pane")),
EditorPaneDescriptor.create(CodeSettingsPane, CodeSettingsPane.ID, nls.localize('CodeSettingsPane', "Code\'s Settings Pane")),
[new SyncDescriptor(CodeSettingsInput)]
);
@@ -144,17 +145,23 @@ registerAction2(class extends Action2 {
const instantiationService = accessor.get(IInstantiationService);
// close all instances if found
const openEditors = editorService.findEditors(CodeSettingsInput.RESOURCE);
if (openEditors.length > 0) {
await editorService.closeEditors(openEditors);
// if is open, close it
const openEditors = editorService.findEditors(CodeSettingsInput.RESOURCE); // should only have 0 or 1 elements...
if (openEditors.length !== 0) {
const openEditor = openEditors[0].editor
const isCurrentlyOpen = editorService.activeEditor?.resource?.fsPath === openEditor.resource?.fsPath
if (isCurrentlyOpen)
await editorService.closeEditors(openEditors)
else
await editorGroupService.activeGroup.openEditor(openEditor)
return;
}
// else open it
const input = instantiationService.createInstance(CodeSettingsInput);
await editorService.openEditor(input);
await editorGroupService.activeGroup.openEditor(input);
}
})
@@ -165,7 +172,7 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: CODE_OPEN_SETTINGS_ACTION_ID,
title: nls.localize2('codeSettings', "Code: Open Settings"),
title: nls.localize2('codeSettingsAction2', "Code: Open Settings"),
f1: true,
icon: Codicon.settingsGear,
});
@@ -195,7 +202,7 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '0_command',
command: {
id: CODE_TOGGLE_SETTINGS_ACTION_ID,
title: nls.localize('codeSettings', "Code Settings")
title: nls.localize('codeSettingsActionGear', "Code\'s Settings")
},
order: 1
});
@@ -8,21 +8,111 @@ import Severity from '../../../../base/common/severity.js';
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
import { localize2 } from '../../../../nls.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
import { ICodeUpdateService } from '../../../../platform/void/common/codeUpdateService.js';
import { INotificationActions, INotificationHandle, INotificationService } from '../../../../platform/notification/common/notification.js';
import { IMetricsService } from '../common/metricsService.js';
import { ICodeUpdateService } from '../common/codeUpdateService.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import * as dom from '../../../../base/browser/dom.js';
import { IUpdateService } from '../../../../platform/update/common/update.js';
import { VoidCheckUpdateRespose } from '../common/voidUpdateServiceTypes.js';
import { CodeCheckUpdateRespose } from '../common/codeUpdateServiceTypes.js';
import { IAction } from '../../../../base/common/actions.js';
const notifyYesUpdate = (notifService: INotificationService, msg?: string) => {
const message = msg || 'This is a very old version of void, please download the latest version! [Code Editor](https://code.hanzo.ai/download-beta)!'
notifService.notify({
const notifyUpdate = (res: CodeCheckUpdateRespose & { message: string }, notifService: INotificationService, updateService: IUpdateService): INotificationHandle => {
const message = res?.message || 'This is a very old version of Code, please download the latest version! [Code Editor](https://voideditor.com/download-beta)!'
let actions: INotificationActions | undefined
if (res?.action) {
const primary: IAction[] = []
if (res.action === 'reinstall') {
primary.push({
label: `Reinstall`,
id: 'void.updater.reinstall',
enabled: true,
tooltip: '',
class: undefined,
run: () => {
const { window } = dom.getActiveWindow()
window.open('https://voideditor.com/download-beta')
}
})
}
if (res.action === 'download') {
primary.push({
label: `Download`,
id: 'void.updater.download',
enabled: true,
tooltip: '',
class: undefined,
run: () => {
updateService.downloadUpdate()
}
})
}
if (res.action === 'apply') {
primary.push({
label: `Apply`,
id: 'void.updater.apply',
enabled: true,
tooltip: '',
class: undefined,
run: () => {
updateService.applyUpdate()
}
})
}
if (res.action === 'restart') {
primary.push({
label: `Restart`,
id: 'void.updater.restart',
enabled: true,
tooltip: '',
class: undefined,
run: () => {
updateService.quitAndInstall()
}
})
}
primary.push({
id: 'void.updater.site',
enabled: true,
label: `Code Site`,
tooltip: '',
class: undefined,
run: () => {
const { window } = dom.getActiveWindow()
window.open('https://voideditor.com/')
}
})
actions = {
primary: primary,
secondary: [{
id: 'void.updater.close',
enabled: true,
label: `Keep current version`,
tooltip: '',
class: undefined,
run: () => {
notifController.close()
}
}]
}
}
else {
actions = undefined
}
const notifController = notifService.notify({
severity: Severity.Info,
message: message,
sticky: true,
@@ -36,15 +126,9 @@ const notifyYesUpdate = (notifService: INotificationService, msg?: string) => {
// d.dispose()
// })
}
const notifyNoUpdate = (notifService: INotificationService) => {
notifService.notify({
severity: Severity.Info,
message: 'Code is up-to-date!',
})
}
const notifyErrChecking = (notifService: INotificationService) => {
const message = `Code Error: There was an error checking for updates. If this persists, please get in touch or reinstall Code [here](https://code.hanzo.ai/download-beta)!`
notifService.notify({
const notifyErrChecking = (notifService: INotificationService): INotificationHandle => {
const message = `Code Error: There was an error checking for updates. If this persists, please get in touch or reinstall Code [here](https://voideditor.com/download-beta)!`
const notifController = notifService.notify({
severity: Severity.Info,
message: message,
sticky: true,
@@ -53,31 +137,31 @@ const notifyErrChecking = (notifService: INotificationService) => {
}
const performVoidCheck = async (
const performCodeCheck = async (
explicit: boolean,
notifService: INotificationService,
voidUpdateService: IVoidUpdateService,
codeUpdateService: ICodeUpdateService,
metricsService: IMetricsService,
updateService: IUpdateService,
): Promise<INotificationHandle | null> => {
const metricsTag = explicit ? 'Manual' : 'Auto'
metricsService.capture(`Void Update ${metricsTag}: Checking...`, {})
const res = await voidUpdateService.check(explicit)
metricsService.capture(`Code Update ${metricsTag}: Checking...`, {})
const res = await codeUpdateService.check(explicit)
if (!res) {
const notifController = notifyErrChecking(notifService);
metricsService.capture(`Void Update ${metricsTag}: Error`, { res })
metricsService.capture(`Code Update ${metricsTag}: Error`, { res })
return notifController
}
else {
if (res.message) {
const notifController = notifyUpdate(res, notifService, updateService)
metricsService.capture(`Void Update ${metricsTag}: Yes`, { res })
metricsService.capture(`Code Update ${metricsTag}: Yes`, { res })
return notifController
}
else {
metricsService.capture(`Void Update ${metricsTag}: No`, { res })
metricsService.capture(`Code Update ${metricsTag}: No`, { res })
return null
}
}
@@ -102,11 +186,14 @@ registerAction2(class extends Action2 {
const metricsService = accessor.get(IMetricsService)
const updateService = accessor.get(IUpdateService)
metricsService.capture('Code Update Manual: Checking...', {})
const res = await codeUpdateService.check()
if (!res) { notifyErrChecking(notifService); metricsService.capture('Code Update Manual: Error', { res }) }
else if (res.hasUpdate) { notifyYesUpdate(notifService, res.message); metricsService.capture('Code Update Manual: Yes', { res }) }
else if (!res.hasUpdate) { notifyNoUpdate(notifService); metricsService.capture('Code Update Manual: No', { res }) }
const currNotifController = lastNotifController
const newController = await performCodeCheck(true, notifService, codeUpdateService, metricsService, updateService)
if (newController) {
currNotifController?.close()
lastNotifController = newController
}
}
})
@@ -114,17 +201,15 @@ registerAction2(class extends Action2 {
class CodeUpdateWorkbenchContribution extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.void.codeUpdate'
constructor(
@ICodeUpdateService private readonly codeUpdateService: ICodeUpdateService,
@IMetricsService private readonly metricsService: IMetricsService,
@INotificationService private readonly notifService: INotificationService,
@ICodeUpdateService codeUpdateService: ICodeUpdateService,
@IMetricsService metricsService: IMetricsService,
@INotificationService notifService: INotificationService,
@IUpdateService updateService: IUpdateService,
) {
super()
const autoCheck = async () => {
this.metricsService.capture('Code Update Startup: Checking...', {})
const res = await this.codeUpdateService.check()
if (!res) { notifyErrChecking(this.notifService); this.metricsService.capture('Code Update Startup: Error', { res }) }
else if (res.hasUpdate) { notifyYesUpdate(this.notifService, res.message); this.metricsService.capture('Code Update Startup: Yes', { res }) }
else if (!res.hasUpdate) { this.metricsService.capture('Code Update Startup: No', { res }) } // display nothing if up to date
const autoCheck = () => {
performCodeCheck(false, notifService, codeUpdateService, metricsService, updateService)
}
// check once 5 seconds after mount
@@ -1,57 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Emitter, Event } from '../../../../base/common/event.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { URI } from '../../../../base/common/uri.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
// service that manages state
export type CodeUriState = {
currentUri?: URI
}
export interface ICodeUriStateService {
readonly _serviceBrand: undefined;
readonly state: CodeUriState; // readonly to the user
setState(newState: Partial<CodeUriState>): void;
onDidChangeState: Event<void>;
}
export const ICodeUriStateService = createDecorator<ICodeUriStateService>('codeUriStateService');
class CodeUriStateService extends Disposable implements ICodeUriStateService {
_serviceBrand: undefined;
static readonly ID = 'codeUriStateService';
private readonly _onDidChangeState = new Emitter<void>();
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
// state
state: CodeUriState
constructor(
) {
super()
// initial state
this.state = { currentUri: undefined }
}
setState(newState: Partial<CodeUriState>) {
this.state = { ...this.state, ...newState }
this._onDidChangeState.fire()
}
}
registerSingleton(ICodeUriStateService, CodeUriStateService, InstantiationType.Eager);
@@ -9,11 +9,11 @@ import { ChatMessage } from '../common/chatThreadServiceTypes.js';
import { getIsReasoningEnabledState, getReservedOutputTokenSpace, getModelCapabilities } from '../common/modelCapabilities.js';
import { reParsedToolXMLString, chat_systemMessage } from '../common/prompt/prompts.js';
import { AnthropicLLMChatMessage, AnthropicReasoning, GeminiLLMChatMessage, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
import { IVoidSettingsService } from '../common/voidSettingsService.js';
import { ChatMode, FeatureName, ModelSelection, ProviderName } from '../common/voidSettingsTypes.js';
import { ICodeSettingsService } from '../common/codeSettingsService.js';
import { ChatMode, FeatureName, ModelSelection, ProviderName } from '../common/codeSettingsTypes.js';
import { IDirectoryStrService } from '../common/directoryStrService.js';
import { ITerminalToolService } from './terminalToolService.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { ICodeModelService } from '../common/codeModelService.js';
import { URI } from '../../../../base/common/uri.js';
import { EndOfLinePreference } from '../../../../editor/common/model.js';
import { ToolName } from '../common/toolsServiceTypes.js';
@@ -538,25 +538,25 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
@IEditorService private readonly editorService: IEditorService,
@IDirectoryStrService private readonly directoryStrService: IDirectoryStrService,
@ITerminalToolService private readonly terminalToolService: ITerminalToolService,
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
@IVoidModelService private readonly voidModelService: IVoidModelService,
@ICodeSettingsService private readonly codeSettingsService: ICodeSettingsService,
@ICodeModelService private readonly codeModelService: ICodeModelService,
@IMCPService private readonly mcpService: IMCPService,
) {
super()
}
// Read .voidrules files from workspace folders
private _getVoidRulesFileContents(): string {
private _getCodeRulesFileContents(): string {
try {
const workspaceFolders = this.workspaceContextService.getWorkspace().folders;
let voidRules = '';
let codeRules = '';
for (const folder of workspaceFolders) {
const uri = URI.joinPath(folder.uri, '.voidrules')
const { model } = this.voidModelService.getModel(uri)
const { model } = this.codeModelService.getModel(uri)
if (!model) continue
voidRules += model.getValue(EndOfLinePreference.LF) + '\n\n';
codeRules += model.getValue(EndOfLinePreference.LF) + '\n\n';
}
return voidRules.trim();
return codeRules.trim();
}
catch (e) {
return ''
@@ -565,12 +565,12 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
// Get combined AI instructions from settings and .voidrules files
private _getCombinedAIInstructions(): string {
const globalAIInstructions = this.voidSettingsService.state.globalSettings.aiInstructions;
const voidRulesFileContent = this._getVoidRulesFileContents();
const globalAIInstructions = this.codeSettingsService.state.globalSettings.aiInstructions;
const codeRulesFileContent = this._getCodeRulesFileContents();
const ans: string[] = []
if (globalAIInstructions) ans.push(globalAIInstructions)
if (voidRulesFileContent) ans.push(voidRulesFileContent)
if (codeRulesFileContent) ans.push(codeRulesFileContent)
return ans.join('\n\n')
}
@@ -637,7 +637,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
prepareLLMSimpleMessages: IConvertToLLMMessageService['prepareLLMSimpleMessages'] = ({ simpleMessages, systemMessage, modelSelection, featureName }) => {
if (modelSelection === null) return { messages: [], separateSystemMessage: undefined }
const { overridesOfModel } = this.voidSettingsService.state
const { overridesOfModel } = this.codeSettingsService.state
const { providerName, modelName } = modelSelection
const {
@@ -646,7 +646,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
supportsSystemMessage,
} = getModelCapabilities(providerName, modelName, overridesOfModel)
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]
const modelSelectionOptions = this.codeSettingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]
// Get combined AI instructions
const aiInstructions = this._getCombinedAIInstructions();
@@ -670,7 +670,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
prepareLLMChatMessages: IConvertToLLMMessageService['prepareLLMChatMessages'] = async ({ chatMessages, chatMode, modelSelection }) => {
if (modelSelection === null) return { messages: [], separateSystemMessage: undefined }
const { overridesOfModel } = this.voidSettingsService.state
const { overridesOfModel } = this.codeSettingsService.state
const { providerName, modelName } = modelSelection
const {
@@ -679,11 +679,11 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
supportsSystemMessage,
} = getModelCapabilities(providerName, modelName, overridesOfModel)
const { disableSystemMessage } = this.voidSettingsService.state.globalSettings;
const { disableSystemMessage } = this.codeSettingsService.state.globalSettings;
const fullSystemMessage = await this._generateChatMessagesSystemMessage(chatMode, specialToolFormat)
const systemMessage = disableSystemMessage ? '' : fullSystemMessage;
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection['Chat'][modelSelection.providerName]?.[modelSelection.modelName]
const modelSelectionOptions = this.codeSettingsService.state.optionsOfModelSelection['Chat'][modelSelection.providerName]?.[modelSelection.modelName]
// Get combined AI instructions
const aiInstructions = this._getCombinedAIInstructions();
@@ -7,22 +7,22 @@ import { Disposable } from '../../../../base/common/lifecycle.js';
import { URI } from '../../../../base/common/uri.js';
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { ICodeModelService } from '../common/codeModelService.js';
class ConvertContribWorkbenchContribution extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.void.convertcontrib'
_serviceBrand: undefined;
constructor(
@IVoidModelService private readonly voidModelService: IVoidModelService,
@ICodeModelService private readonly codeModelService: ICodeModelService,
@IWorkspaceContextService private readonly workspaceContext: IWorkspaceContextService,
) {
super()
const initializeURI = (uri: URI) => {
this.workspaceContext.getWorkspace()
const voidRulesURI = URI.joinPath(uri, '.voidrules')
this.voidModelService.initializeModel(voidRulesURI)
const codeRulesURI = URI.joinPath(uri, '.voidrules')
this.codeModelService.initializeModel(codeRulesURI)
}
// call
@@ -23,10 +23,10 @@ import * as dom from '../../../../base/browser/dom.js';
import { Widget } from '../../../../base/browser/ui/widget.js';
import { URI } from '../../../../base/common/uri.js';
import { IConsistentEditorItemService, IConsistentItemService } from './helperServices/consistentItemService.js';
import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplaceGivenDescription_systemMessage, searchReplaceGivenDescription_userMessage, tripleTick, } from '../common/prompt/prompts.js';
import { IVoidCommandBarService } from './voidCommandBarService.js';
import { codePrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplaceGivenDescription_systemMessage, searchReplaceGivenDescription_userMessage, tripleTick, } from '../common/prompt/prompts.js';
import { ICodeCommandBarService } from './codeCommandBarService.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { VOID_ACCEPT_DIFF_ACTION_ID, VOID_REJECT_DIFF_ACTION_ID } from './actionIDs.js';
import { CODE_ACCEPT_DIFF_ACTION_ID, CODE_REJECT_DIFF_ACTION_ID } from './actionIDs.js';
import { mountCtrlK } from './react/out/quick-edit-tsx/index.js'
import { QuickEditPropsType } from './quickEditActions.js';
@@ -39,15 +39,15 @@ import { ILLMMessageService } from '../common/sendLLMMessageService.js';
import { LLMChatMessage } from '../common/sendLLMMessageTypes.js';
import { IMetricsService } from '../common/metricsService.js';
import { IEditCodeService, AddCtrlKOpts, StartApplyingOpts, CallBeforeStartApplyingOpts, } from './editCodeServiceInterface.js';
import { IVoidSettingsService } from '../common/voidSettingsService.js';
import { FeatureName } from '../common/voidSettingsTypes.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { ICodeSettingsService } from '../common/codeSettingsService.js';
import { FeatureName } from '../common/codeSettingsTypes.js';
import { ICodeModelService } from '../common/codeModelService.js';
import { deepClone } from '../../../../base/common/objects.js';
import { acceptBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../common/helpers/colors.js';
import { DiffArea, Diff, CtrlKZone, VoidFileSnapshot, DiffAreaSnapshotEntry, diffAreaSnapshotKeys, DiffZone, TrackingZone, ComputedDiff } from '../common/editCodeServiceTypes.js';
import { DiffArea, Diff, CtrlKZone, CodeFileSnapshot, DiffAreaSnapshotEntry, diffAreaSnapshotKeys, DiffZone, TrackingZone, ComputedDiff } from '../common/editCodeServiceTypes.js';
import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
// import { isMacintosh } from '../../../../base/common/platform.js';
// import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
// import { CODE_OPEN_SETTINGS_ACTION_ID } from './codeSettingsPane.js';
const numLinesOfStr = (str: string) => str.split('\n').length
@@ -192,9 +192,9 @@ class EditCodeService extends Disposable implements IEditCodeService {
@IMetricsService private readonly _metricsService: IMetricsService,
@INotificationService private readonly _notificationService: INotificationService,
// @ICommandService private readonly _commandService: ICommandService,
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
@ICodeSettingsService private readonly _settingsService: ICodeSettingsService,
// @IFileService private readonly _fileService: IFileService,
@IVoidModelService private readonly _voidModelService: IVoidModelService,
@ICodeModelService private readonly _codeModelService: ICodeModelService,
@IConvertToLLMMessageService private readonly _convertToLLMMessageService: IConvertToLLMMessageService,
) {
super();
@@ -203,7 +203,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
const registeredModelURIs = new Set<string>()
const initializeModel = async (model: ITextModel) => {
await this._voidModelService.initializeModel(model.uri)
await this._codeModelService.initializeModel(model.uri)
// do not add listeners to the same model twice - important, or will see duplicates
if (registeredModelURIs.has(model.uri.fsPath)) return
@@ -279,15 +279,15 @@ class EditCodeService extends Disposable implements IEditCodeService {
// const details = errorDetails(e.fullError)
// this._notificationService.notify({
// severity: Severity.Warning,
// message: `Void Error: ${e.message}`,
// message: `Code Error: ${e.message}`,
// actions: {
// secondary: [{
// id: 'void.onerror.opensettings',
// enabled: true,
// label: `Open Void's settings`,
// label: `Open Code's settings`,
// tooltip: '',
// class: undefined,
// run: () => { this._commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) }
// run: () => { this._commandService.executeCommand(CODE_OPEN_SETTINGS_ACTION_ID) }
// }]
// },
// source: details ? `(Hold ${isMacintosh ? 'Option' : 'Alt'} to hover) - ${details}\n\nIf this persists, feel free to [report](https://github.com/voideditor/void/issues/new) it.` : undefined
@@ -315,7 +315,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
private _addDiffAreaStylesToURI = (uri: URI) => {
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) {
const diffArea = this.diffAreaOfId[diffareaid]
@@ -324,10 +324,10 @@ class EditCodeService extends Disposable implements IEditCodeService {
// add sweep styles to the diffZone
if (diffArea._streamState.isStreaming) {
// sweepLine ... sweepLine
const fn1 = this._addLineDecoration(model, diffArea._streamState.line, diffArea._streamState.line, 'void-sweepIdxBG')
const fn1 = this._addLineDecoration(model, diffArea._streamState.line, diffArea._streamState.line, 'code-sweepIdxBG')
// sweepLine+1 ... endLine
const fn2 = diffArea._streamState.line + 1 <= diffArea.endLine ?
this._addLineDecoration(model, diffArea._streamState.line + 1, diffArea.endLine, 'void-sweepBG')
this._addLineDecoration(model, diffArea._streamState.line + 1, diffArea.endLine, 'code-sweepBG')
: null
diffArea._removeStylesFns.add(() => { fn1?.(); fn2?.(); })
@@ -336,7 +336,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
else if (diffArea.type === 'CtrlKZone' && diffArea._linkedStreamingDiffZone === null) {
// highlight zone's text
const fn = this._addLineDecoration(model, diffArea.startLine, diffArea.endLine, 'void-highlightBG')
const fn = this._addLineDecoration(model, diffArea.startLine, diffArea.endLine, 'code-highlightBG')
diffArea._removeStylesFns.add(() => fn?.());
}
}
@@ -344,7 +344,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
private _computeDiffsAndAddStylesToURI = (uri: URI) => {
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
if (model === null) return
const fullFileText = model.getValue(EndOfLinePreference.LF)
@@ -477,11 +477,11 @@ class EditCodeService extends Disposable implements IEditCodeService {
const disposeInThisEditorFns: (() => void)[] = []
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
// green decoration and minimap decoration
if (type !== 'deletion') {
const fn = this._addLineDecoration(model, diff.startLine, diff.endLine, 'void-greenBG', {
const fn = this._addLineDecoration(model, diff.startLine, diff.endLine, 'code-greenBG', {
minimap: { color: { id: 'minimapGutter.addedBackground' }, position: 2 },
overviewRuler: { color: { id: 'editorOverviewRuler.addedForeground' }, position: 7 }
})
@@ -496,7 +496,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
fn: (editor) => {
const domNode = document.createElement('div');
domNode.className = 'void-redBG'
domNode.className = 'code-redBG'
const renderOptions = RenderOptions.fromEditor(editor)
@@ -585,7 +585,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
offsetLines = 1
}
}
else { throw new Error('Void 1') }
else { throw new Error('Code 1') }
const buttonsWidget = this._instantiationService.createInstance(AcceptRejectInlineWidget, {
editor,
@@ -625,7 +625,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
weAreWriting = false
private _writeURIText(uri: URI, text: string, range_: IRange | 'wholeFileRange', { shouldRealignDiffAreas, }: { shouldRealignDiffAreas: boolean, }) {
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
if (!model) {
this._refreshStylesAndDiffsInURI(uri) // at the end of a write, we still expect to refresh all styles. e.g. sometimes we expect to restore all the decorations even if no edits were made when _writeText is used
return
@@ -663,8 +663,8 @@ class EditCodeService extends Disposable implements IEditCodeService {
private _getCurrentVoidFileSnapshot = (uri: URI): VoidFileSnapshot => {
const { model } = this._voidModelService.getModel(uri)
private _getCurrentCodeFileSnapshot = (uri: URI): CodeFileSnapshot => {
const { model } = this._codeModelService.getModel(uri)
const snapshottedDiffAreaOfId: Record<string, DiffAreaSnapshotEntry> = {}
for (const diffareaid in this.diffAreaOfId) {
@@ -687,7 +687,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
}
private _restoreVoidFileSnapshot = async (uri: URI, snapshot: VoidFileSnapshot) => {
private _restoreCodeFileSnapshot = async (uri: URI, snapshot: CodeFileSnapshot) => {
// for each diffarea in this uri, stop streaming if currently streaming
for (const diffareaid in this.diffAreaOfId) {
const diffArea = this.diffAreaOfId[diffareaid]
@@ -737,34 +737,34 @@ class EditCodeService extends Disposable implements IEditCodeService {
}
private _addToHistory(uri: URI, opts?: { onWillUndo?: () => void }) {
const beforeSnapshot: VoidFileSnapshot = this._getCurrentVoidFileSnapshot(uri)
let afterSnapshot: VoidFileSnapshot | null = null
const beforeSnapshot: CodeFileSnapshot = this._getCurrentCodeFileSnapshot(uri)
let afterSnapshot: CodeFileSnapshot | null = null
const elt: IUndoRedoElement = {
type: UndoRedoElementType.Resource,
resource: uri,
label: 'Void Agent',
label: 'Code Agent',
code: 'undoredo.editCode',
undo: async () => { opts?.onWillUndo?.(); await this._restoreVoidFileSnapshot(uri, beforeSnapshot) },
redo: async () => { if (afterSnapshot) await this._restoreVoidFileSnapshot(uri, afterSnapshot) }
undo: async () => { opts?.onWillUndo?.(); await this._restoreCodeFileSnapshot(uri, beforeSnapshot) },
redo: async () => { if (afterSnapshot) await this._restoreCodeFileSnapshot(uri, afterSnapshot) }
}
this._undoRedoService.pushElement(elt)
const onFinishEdit = async () => {
afterSnapshot = this._getCurrentVoidFileSnapshot(uri)
await this._voidModelService.saveModel(uri)
afterSnapshot = this._getCurrentCodeFileSnapshot(uri)
await this._codeModelService.saveModel(uri)
}
return { onFinishEdit }
}
public getVoidFileSnapshot(uri: URI) {
return this._getCurrentVoidFileSnapshot(uri)
public getCodeFileSnapshot(uri: URI) {
return this._getCurrentCodeFileSnapshot(uri)
}
public restoreVoidFileSnapshot(uri: URI, snapshot: VoidFileSnapshot): void {
this._restoreVoidFileSnapshot(uri, snapshot)
public restoreCodeFileSnapshot(uri: URI, snapshot: CodeFileSnapshot): void {
this._restoreCodeFileSnapshot(uri, snapshot)
}
@@ -997,7 +997,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
else if (lastDiff.type === 'deletion')
endLineInLlmTextSoFar = lastDiff.startLine
else
throw new Error(`Void: diff.type not recognized on: ${lastDiff}`)
throw new Error(`Code: diff.type not recognized on: ${lastDiff}`)
}
// at the start, add a newline between the stream and originalCode to make reasoning easier
@@ -1127,8 +1127,8 @@ class EditCodeService extends Disposable implements IEditCodeService {
public async callBeforeApplyOrEdit(givenURI: URI | 'current') {
const uri = this._uriOfGivenURI(givenURI)
if (!uri) return
await this._voidModelService.initializeModel(uri)
await this._voidModelService.saveModel(uri) // save the URI
await this._codeModelService.initializeModel(uri)
await this._codeModelService.saveModel(uri) // save the URI
}
@@ -1269,7 +1269,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
linkedCtrlKZone: CtrlKZone | null,
onWillUndo: () => void,
}) {
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
if (!model) return
// treat like full file, unless linkedCtrlKZone was provided in which case use its diff's range
@@ -1376,10 +1376,10 @@ class EditCodeService extends Disposable implements IEditCodeService {
startRange = [startLine_, endLine_]
}
else {
throw new Error(`Void: diff.type not recognized on: ${from}`)
throw new Error(`Code: diff.type not recognized on: ${from}`)
}
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
if (!model) return
let streamRequestIdRef: { current: string | null } = { current: null } // can use this as a proxy to set the diffArea's stream state requestId
@@ -1408,7 +1408,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
const startLine = startRange === 'fullFile' ? 1 : startRange[0]
const endLine = startRange === 'fullFile' ? model.getLineCount() : startRange[1]
const { prefix, suffix } = voidPrefixAndSuffix({ fullFileStr: originalFileCode, startLine, endLine })
const { prefix, suffix } = codePrefixAndSuffix({ fullFileStr: originalFileCode, startLine, endLine })
const userContent = ctrlKStream_userMessage({ selection: originalCode, instructions: instructions, prefix, suffix, fimTags: quickEditFIMTags, language })
const { messages: a, separateSystemMessage: b } = this._convertToLLMMessageService.prepareLLMSimpleMessages({
@@ -1480,7 +1480,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
else if (from === 'ClickApply') {
return extractCodeFromRegular({ text: fullText, recentlyAddedTextLen })
}
throw new Error('Void 1')
throw new Error('Code 1')
}
// refresh now in case onText takes a while to get 1st message
@@ -1578,7 +1578,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
_fileLengthOfGivenURI(givenURI: URI | 'current') {
const uri = this._uriOfGivenURI(givenURI)
if (!uri) return null
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
if (!model) return null
const numCharsInFile = model.getValueLength(EndOfLinePreference.LF)
return numCharsInFile
@@ -1617,7 +1617,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
const blocks = extractSearchReplaceBlocks(blocksStr)
if (blocks.length === 0) throw new Error(`No Search/Replace blocks were received!`)
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
if (!model) throw new Error(`Error applying Search/Replace blocks: File does not exist.`)
const modelStr = model.getValue(EndOfLinePreference.LF)
// .split('\n').map(l => '\t' + l).join('\n') // for testing purposes only, remember to remove this
@@ -1678,7 +1678,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
const uri = this._getURIBeforeStartApplying(opts)
if (!uri) return
const { model } = this._voidModelService.getModel(uri)
const { model } = this._codeModelService.getModel(uri)
if (!model) return
let streamRequestIdRef: { current: string | null } = { current: null } // can use this as a proxy to set the diffArea's stream state requestId
@@ -1968,7 +1968,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
const blocks = extractSearchReplaceBlocks(fullText)
if (blocks.length === 0) {
this._notificationService.info(`Void: We ran Fast Apply, but the LLM didn't output any changes.`)
this._notificationService.info(`Code: We ran Fast Apply, but the LLM didn't output any changes.`)
}
this._writeURIText(uri, originalFileCode, 'wholeFileRange', { shouldRealignDiffAreas: true })
@@ -2158,7 +2158,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
].join('\n')
}
else {
throw new Error(`Void error: ${diff}.type not recognized`)
throw new Error(`Code error: ${diff}.type not recognized`)
}
// console.log('DIFF', diff)
@@ -2250,7 +2250,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.endLine, endColumn: Number.MAX_SAFE_INTEGER } // 1-indexed
}
else {
throw new Error(`Void error: ${diff}.type not recognized`)
throw new Error(`Code error: ${diff}.type not recognized`)
}
// update the file
@@ -2305,7 +2305,7 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget {
startLine: number,
offsetLines: number
},
@IVoidCommandBarService private readonly _voidCommandBarService: IVoidCommandBarService,
@ICodeCommandBarService private readonly _codeCommandBarService: ICodeCommandBarService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IEditCodeService private readonly _editCodeService: IEditCodeService,
) {
@@ -2328,15 +2328,15 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget {
const lineHeight = editor.getOption(EditorOption.lineHeight);
const getAcceptRejectText = () => {
const acceptKeybinding = this._keybindingService.lookupKeybinding(VOID_ACCEPT_DIFF_ACTION_ID);
const rejectKeybinding = this._keybindingService.lookupKeybinding(VOID_REJECT_DIFF_ACTION_ID);
const acceptKeybinding = this._keybindingService.lookupKeybinding(CODE_ACCEPT_DIFF_ACTION_ID);
const rejectKeybinding = this._keybindingService.lookupKeybinding(CODE_REJECT_DIFF_ACTION_ID);
// Use the standalone function directly since we're in a nested class that
// can't access EditCodeService's methods
const acceptKeybindLabel = this._editCodeService.processRawKeybindingText(acceptKeybinding && acceptKeybinding.getLabel() || '');
const rejectKeybindLabel = this._editCodeService.processRawKeybindingText(rejectKeybinding && rejectKeybinding.getLabel() || '');
const commandBarStateAtUri = this._voidCommandBarService.stateOfURI[uri.fsPath];
const commandBarStateAtUri = this._codeCommandBarService.stateOfURI[uri.fsPath];
const selectedDiffIdx = commandBarStateAtUri?.diffIdx ?? 0; // 0th item is selected by default
const thisDiffIdx = commandBarStateAtUri?.sortedDiffIds.indexOf(diffid) ?? null;
@@ -2435,7 +2435,7 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget {
// Listen for state changes in the command bar service
this._register(this._voidCommandBarService.onDidChangeState(e => {
this._register(this._codeCommandBarService.onDidChangeState(e => {
if (uri && e.uri.fsPath === uri.fsPath) {
const { acceptText, rejectText } = getAcceptRejectText()
@@ -7,7 +7,7 @@ import { Event } from '../../../../base/common/event.js';
import { URI } from '../../../../base/common/uri.js';
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { Diff, DiffArea, VoidFileSnapshot } from '../common/editCodeServiceTypes.js';
import { Diff, DiffArea, CodeFileSnapshot } from '../common/editCodeServiceTypes.js';
export type StartBehavior = 'accept-conflicts' | 'reject-conflicts' | 'keep-conflicts'
@@ -73,6 +73,6 @@ export interface IEditCodeService {
interruptURIStreaming(opts: { uri: URI }): void;
// testDiffs(): void;
getVoidFileSnapshot(uri: URI): VoidFileSnapshot;
restoreVoidFileSnapshot(uri: URI, snapshot: VoidFileSnapshot): void;
getCodeFileSnapshot(uri: URI): CodeFileSnapshot;
restoreCodeFileSnapshot(uri: URI, snapshot: CodeFileSnapshot): void;
}
@@ -31,7 +31,7 @@ const extensionBlacklist = [
// ignore extensions
'ms-vscode-remote.remote', // ms-vscode-remote.remote-ssh, ms-vscode-remote.remote-wsl
'ms-vscode.remote', // ms-vscode.remote-explorer
// ignore other AI copilots that could conflict with Void keybindings
// ignore other AI copilots that could conflict with Code keybindings
'sourcegraph.cody-ai',
'continue.continue',
'codeium.codeium',
@@ -195,10 +195,10 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
if (fromEditor === 'VS Code') {
return [{
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Code', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Void', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Code', 'User', 'settings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Code', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Void', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Code', 'User', 'keybindings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.vscode', 'extensions'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'),
@@ -207,10 +207,10 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
} else if (fromEditor === 'Cursor') {
return [{
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Cursor', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Void', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Code', 'User', 'settings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Cursor', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Void', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Code', 'User', 'keybindings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.cursor', 'extensions'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'),
@@ -219,10 +219,10 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
} else if (fromEditor === 'Windsurf') {
return [{
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Windsurf', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Void', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Code', 'User', 'settings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Windsurf', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Void', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, 'Library', 'Application Support', 'Code', 'User', 'keybindings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.windsurf', 'extensions'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'),
@@ -238,10 +238,10 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
if (fromEditor === 'VS Code') {
return [{
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Code', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Void', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Code', 'User', 'settings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Code', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Void', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Code', 'User', 'keybindings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.vscode', 'extensions'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'),
@@ -250,10 +250,10 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
} else if (fromEditor === 'Cursor') {
return [{
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Cursor', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Void', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Code', 'User', 'settings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Cursor', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Void', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Code', 'User', 'keybindings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.cursor', 'extensions'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'),
@@ -262,10 +262,10 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
} else if (fromEditor === 'Windsurf') {
return [{
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Windsurf', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Void', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Code', 'User', 'settings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Windsurf', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Void', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.config', 'Code', 'User', 'keybindings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.windsurf', 'extensions'),
to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'),
@@ -283,10 +283,10 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
if (fromEditor === 'VS Code') {
return [{
from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'settings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'keybindings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.vscode', 'extensions'),
to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'),
@@ -295,10 +295,10 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
} else if (fromEditor === 'Cursor') {
return [{
from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Cursor', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'settings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Cursor', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'keybindings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.cursor', 'extensions'),
to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'),
@@ -307,10 +307,10 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
} else if (fromEditor === 'Windsurf') {
return [{
from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Windsurf', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'settings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'settings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Windsurf', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'keybindings.json'),
to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'keybindings.json'),
}, {
from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.windsurf', 'extensions'),
to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'),
@@ -7,17 +7,17 @@ import { IFileService } from '../../../../platform/files/common/files.js';
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
import { IDirectoryStrService } from '../common/directoryStrService.js';
import { messageOfSelection } from '../common/prompt/prompts.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { ICodeModelService } from '../common/codeModelService.js';
class FilePromptActionService extends Action2 {
private static readonly VOID_COPY_FILE_PROMPT_ID = 'void.copyfileprompt'
private static readonly CODE_COPY_FILE_PROMPT_ID = 'void.copyfileprompt'
constructor() {
super({
id: FilePromptActionService.VOID_COPY_FILE_PROMPT_ID,
title: localize2('voidCopyPrompt', 'Void: Copy Prompt'),
id: FilePromptActionService.CODE_COPY_FILE_PROMPT_ID,
title: localize2('codeCopyPrompt', 'Code: Copy Prompt'),
menu: [{
id: MenuId.ExplorerContext,
group: '8_void',
@@ -31,7 +31,7 @@ class FilePromptActionService extends Action2 {
const fileService = accessor.get(IFileService);
const clipboardService = accessor.get(IClipboardService)
const directoryStrService = accessor.get(IDirectoryStrService)
const voidModelService = accessor.get(IVoidModelService)
const codeModelService = accessor.get(ICodeModelService)
const stat = await fileService.stat(uri)
@@ -45,7 +45,7 @@ class FilePromptActionService extends Action2 {
m = await messageOfSelection({
type: 'File',
uri,
language: (await voidModelService.getModelSafe(uri)).model?.getLanguageId() || '',
language: (await codeModelService.getModelSafe(uri)).model?.getLanguageId() || '',
state: { wasAddedAsCurrentFile: false, },
}, {
folderOpts,
@@ -1,170 +0,0 @@
// eg "bash" -> "shell"
export const nameToVscodeLanguage: { [key: string]: string } = {
// Web Technologies
'html': 'html',
'css': 'css',
'scss': 'scss',
'sass': 'scss',
'less': 'less',
'javascript': 'typescript',
'js': 'typescript', // use more general renderer
'jsx': 'typescript',
'typescript': 'typescript',
'ts': 'typescript',
'tsx': 'typescript',
'json': 'json',
'jsonc': 'json',
// Programming Languages
'python': 'python',
'py': 'python',
'java': 'java',
'cpp': 'cpp',
'c++': 'cpp',
'c': 'c',
'csharp': 'csharp',
'cs': 'csharp',
'c#': 'csharp',
'go': 'go',
'golang': 'go',
'rust': 'rust',
'rs': 'rust',
'ruby': 'ruby',
'rb': 'ruby',
'php': 'php',
'shell': 'shell',
'bash': 'shell',
'sh': 'shell',
'zsh': 'shell',
// Markup and Config
'markdown': 'markdown',
'md': 'markdown',
'xml': 'xml',
'svg': 'xml',
'yaml': 'yaml',
'yml': 'yaml',
'ini': 'ini',
'toml': 'ini',
// Database and Query Languages
'sql': 'sql',
'mysql': 'sql',
'postgresql': 'sql',
'graphql': 'graphql',
'gql': 'graphql',
// Others
'dockerfile': 'dockerfile',
'docker': 'dockerfile',
'makefile': 'makefile',
'plaintext': 'plaintext',
'text': 'plaintext'
};
// eg ".ts" -> "typescript"
const fileExtensionToVscodeLanguage: { [key: string]: string } = {
// Web
'html': 'html',
'htm': 'html',
'css': 'css',
'scss': 'scss',
'less': 'less',
'js': 'javascript',
'jsx': 'javascript',
'ts': 'typescript',
'tsx': 'typescript',
'json': 'json',
'jsonc': 'json',
// Programming Languages
'py': 'python',
'java': 'java',
'cpp': 'cpp',
'cc': 'cpp',
'c': 'c',
'h': 'cpp',
'hpp': 'cpp',
'cs': 'csharp',
'go': 'go',
'rs': 'rust',
'rb': 'ruby',
'php': 'php',
'sh': 'shell',
'bash': 'shell',
'zsh': 'shell',
// Markup/Config
'md': 'markdown',
'markdown': 'markdown',
'xml': 'xml',
'svg': 'xml',
'yaml': 'yaml',
'yml': 'yaml',
'ini': 'ini',
'toml': 'ini',
// Other
'sql': 'sql',
'graphql': 'graphql',
'gql': 'graphql',
'dockerfile': 'dockerfile',
'docker': 'dockerfile',
'mk': 'makefile',
// Config Files and Dot Files
'npmrc': 'ini',
'env': 'ini',
'gitignore': 'ignore',
'dockerignore': 'ignore',
'eslintrc': 'json',
'babelrc': 'json',
'prettierrc': 'json',
'stylelintrc': 'json',
'editorconfig': 'ini',
'htaccess': 'apacheconf',
'conf': 'ini',
'config': 'ini',
// Package Files
'package': 'json',
'package-lock': 'json',
'gemfile': 'ruby',
'podfile': 'ruby',
'rakefile': 'ruby',
// Build Systems
'cmake': 'cmake',
'makefile': 'makefile',
'gradle': 'groovy',
// Shell Scripts
'bashrc': 'shell',
'zshrc': 'shell',
'fish': 'shell',
// Version Control
'gitconfig': 'ini',
'hgrc': 'ini',
'svnconfig': 'ini',
// Web Server
'nginx': 'nginx',
// Misc Config
'properties': 'properties',
'cfg': 'ini',
'reg': 'ini'
};
export function filenameToVscodeLanguage(filename: string): string | undefined {
const ext = filename.toLowerCase().split('.').pop();
if (!ext) return undefined;
return fileExtensionToVscodeLanguage[ext];
}
@@ -1,177 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
class SurroundingsRemover {
readonly originalS: string
i: number
j: number
// string is s[i...j]
constructor(s: string) {
this.originalS = s
this.i = 0
this.j = s.length - 1
}
value() {
return this.originalS.substring(this.i, this.j + 1)
}
// returns whether it removed the whole prefix
removePrefix = (prefix: string): boolean => {
let offset = 0
// console.log('prefix', prefix, Math.min(this.j, prefix.length - 1))
while (this.i <= this.j && offset <= prefix.length - 1) {
if (this.originalS.charAt(this.i) !== prefix.charAt(offset))
break
offset += 1
this.i += 1
}
return offset === prefix.length
}
// // removes suffix from right to left
removeSuffix = (suffix: string): boolean => {
// e.g. suffix = <PRE/>, the string is <PRE>hi<P
const s = this.value()
// for every possible prefix of `suffix`, check if string ends with it
for (let len = Math.min(s.length, suffix.length); len >= 1; len -= 1) {
if (s.endsWith(suffix.substring(0, len))) { // the end of the string equals a prefix
this.j -= len
return len === suffix.length
}
}
return false
}
// removeSuffix = (suffix: string): boolean => {
// let offset = 0
// while (this.j >= Math.max(this.i, 0)) {
// if (this.originalS.charAt(this.j) !== suffix.charAt(suffix.length - 1 - offset))
// break
// offset += 1
// this.j -= 1
// }
// return offset === suffix.length
// }
removeFromStartUntil = (until: string, alsoRemoveUntilStr: boolean) => {
const index = this.originalS.indexOf(until, this.i)
if (index === -1) {
this.i = this.j + 1
return false
}
// console.log('index', index, until.length)
if (alsoRemoveUntilStr)
this.i = index + until.length
else
this.i = index
return true
}
removeCodeBlock = () => {
// Match either:
// 1. ```language\n<code>\n```\n?
// 2. ```<code>\n```\n?
const pm = this
const foundCodeBlock = pm.removePrefix('```')
if (!foundCodeBlock) return false
pm.removeFromStartUntil('\n', true) // language
const j = pm.j
let foundCodeBlockEnd = pm.removeSuffix('```')
if (pm.j === j) foundCodeBlockEnd = pm.removeSuffix('```\n') // if no change, try again with \n after ```
if (!foundCodeBlockEnd) return false
pm.removeSuffix('\n') // remove the newline before ```
return true
}
deltaInfo = (recentlyAddedTextLen: number) => {
// aaaaaatextaaaaaa{recentlyAdded}
// ^ i j len
// |
// recentyAddedIdx
const recentlyAddedIdx = this.originalS.length - recentlyAddedTextLen
const actualDelta = this.originalS.substring(Math.max(this.i, recentlyAddedIdx), this.j + 1)
const ignoredSuffix = this.originalS.substring(Math.max(this.j + 1, recentlyAddedIdx), Infinity)
return [actualDelta, ignoredSuffix] as const
}
}
export const extractCodeFromRegular = ({ text, recentlyAddedTextLen }: { text: string, recentlyAddedTextLen: number }): [string, string, string] => {
const pm = new SurroundingsRemover(text)
pm.removeCodeBlock()
const s = pm.value()
const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen)
return [s, delta, ignoredSuffix]
}
// Ollama has its own FIM, we should not use this if we use that
export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { text: string, recentlyAddedTextLen: number, midTag: string }): [string, string, string] => {
/* ------------- summary of the regex -------------
[optional ` | `` | ```]
(match optional_language_name)
[optional strings here]
[required <MID> tag]
(match the stuff between mid tags)
[optional <MID/> tag]
[optional ` | `` | ```]
*/
const pm = new SurroundingsRemover(text)
pm.removeCodeBlock()
const foundMid = pm.removePrefix(`<${midTag}>`)
if (foundMid) {
pm.removeSuffix(`</${midTag}>`)
}
const s = pm.value()
const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen)
return [s, delta, ignoredSuffix]
// // const regex = /[\s\S]*?(?:`{1,3}\s*([a-zA-Z_]+[\w]*)?[\s\S]*?)?<MID>([\s\S]*?)(?:<\/MID>|`{1,3}|$)/;
// const regex = new RegExp(
// `[\\s\\S]*?(?:\`{1,3}\\s*([a-zA-Z_]+[\\w]*)?[\\s\\S]*?)?<${midTag}>([\\s\\S]*?)(?:</${midTag}>|\`{1,3}|$)`,
// ''
// );
// const match = text.match(regex);
// if (match) {
// const [_, languageName, codeBetweenMidTags] = match;
// return [languageName, codeBetweenMidTags] as const
// } else {
// return [undefined, extractCodeFromRegular(text)] as const
// }
}
@@ -1,10 +0,0 @@
import { URI } from '../../../../../base/common/uri'
import { EndOfLinePreference } from '../../../../../editor/common/model'
import { IModelService } from '../../../../../editor/common/services/model.js'
// read files from VSCode
export const VSReadFile = async (modelService: IModelService, uri: URI): Promise<string | null> => {
const model = modelService.getModel(uri)
if (!model) return null
return model.getValue(EndOfLinePreference.LF)
}
File diff suppressed because it is too large Load Diff
@@ -3,28 +3,29 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
.monaco-editor .code-sweepIdxBG {
background-color: var(--vscode-code-sweepIdxBG);
.monaco-editor .void-sweepIdxBG {
background-color: var(--vscode-void-sweepIdxBG);
}
.code-sweepBG {
background-color: var(--vscode-code-sweepBG);
.void-sweepBG {
background-color: var(--vscode-void-sweepBG);
}
.code-highlightBG {
background-color: var(--vscode-code-highlightBG);
.void-highlightBG {
background-color: var(--vscode-void-highlightBG);
}
.code-greenBG {
background-color: var(--vscode-code-greenBG);
.void-greenBG {
background-color: var(--vscode-void-greenBG);
}
.code-redBG {
background-color: var(--vscode-code-redBG);
.void-redBG {
background-color: var(--vscode-void-redBG);
}
.code-watermark-button {
margin: 8px 0;
/* Renamed from void-watermark-button to void-openfolder-button */
.void-openfolder-button {
padding: 8px 20px;
background-color: #306dce;
color: white;
@@ -54,20 +55,18 @@
cursor: pointer;
transition: background-color 0.2s ease;
}
.code-watermark-button:hover {
background-color: #2563eb;
.void-openssh-button:hover {
background-color: #474747; /* Darker on hover */
}
.code-watermark-button:active {
background-color: #2563eb;
.void-openssh-button:active {
background-color: #474747;
}
.code-settings-watermark-button {
margin: 8px 0;
padding: 8px 20px;
background-color: var(--vscode-input-background);
.void-settings-watermark-button {
margin: 8px 0;
padding: 8px 20px;
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: none;
border-radius: 4px;
@@ -76,22 +75,22 @@
cursor: pointer;
transition: all 0.2s ease;
}
.code-settings-watermark-button:hover {
filter: brightness(1.1);
}
.code-settings-watermark-button:active {
.void-settings-watermark-button:hover {
filter: brightness(1.1);
}
.void-settings-watermark-button:active {
filter: brightness(1.1);
}
.code-link {
.void-link {
color: #3b82f6;
cursor: pointer;
transition: all 0.2s ease;
}
.code-link:hover {
.void-link:hover {
opacity: 80%;
}
@@ -165,89 +164,41 @@
/* For Firefox */
}
.code-scrollable-element::-webkit-scrollbar,
.code-scrollable-element *::-webkit-scrollbar {
width: 14px !important;
height: 14px !important;
.void-scrollable-element::-webkit-scrollbar,
.void-scrollable-element *::-webkit-scrollbar {
width: var(--scrollbar-vertical-width) !important;
height: var(--scrollbar-horizontal-height) !important;
background-color: var(--void-bg-3) !important;
}
.code-scrollable-element::-webkit-scrollbar-track,
.code-scrollable-element *::-webkit-scrollbar-track {
background: transparent !important;
.void-scrollable-element::-webkit-scrollbar-thumb,
.void-scrollable-element *::-webkit-scrollbar-thumb {
background-color: var(--void-bg-1) !important;
border-radius: 4px !important;
border: none !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
.code-scrollable-element::-webkit-scrollbar-thumb,
.code-scrollable-element *::-webkit-scrollbar-thumb {
background-color: transparent !important;
border-radius: 0px !important;
.void-scrollable-element::-webkit-scrollbar-thumb:hover,
.void-scrollable-element *::-webkit-scrollbar-thumb:hover {
background-color: var(--void-bg-1) !important;
filter: brightness(1.1) !important;
}
.code-scrollable-element::-webkit-scrollbar-thumb:hover,
.code-scrollable-element *::-webkit-scrollbar-thumb:hover {
background-color: var(--vscode-scrollbarSlider-hoverBackground) !important;
.void-scrollable-element::-webkit-scrollbar-thumb:active,
.void-scrollable-element *::-webkit-scrollbar-thumb:active {
background-color: var(--void-bg-1) !important;
filter: brightness(1.2) !important;
}
.code-scrollable-element::-webkit-scrollbar-thumb:active,
.code-scrollable-element *::-webkit-scrollbar-thumb:active {
background-color: var(--vscode-scrollbarSlider-activeBackground) !important;
.void-scrollable-element::-webkit-scrollbar-track,
.void-scrollable-element *::-webkit-scrollbar-track {
background-color: var(--void-bg-3) !important;
border: none !important;
}
.code-scrollable-element::-webkit-scrollbar-corner,
.code-scrollable-element *::-webkit-scrollbar-corner {
background-color: transparent !important;
}
.code-scrollable-element.show-scrollbar-0::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-0 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 0%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-1::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-1 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 10%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-2::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-2 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 20%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-3::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-3 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 30%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-4::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-4 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 40%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-5::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-5 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 50%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-6::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-6 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 60%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-7::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-7 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 70%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-8::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-8 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 80%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-9::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-9 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 90%, transparent) !important;
}
.code-scrollable-element.show-scrollbar-10::-webkit-scrollbar-thumb,
.code-scrollable-element.show-scrollbar-10 *::-webkit-scrollbar-thumb {
background-color: var(--vscode-scrollbarSlider-background) !important;
.void-scrollable-element::-webkit-scrollbar-corner,
.void-scrollable-element *::-webkit-scrollbar-corner {
background-color: var(--void-bg-3) !important;
}
@@ -19,11 +19,11 @@ export interface IMetricsPollService {
const PING_EVERY_MS = 15 * 1000 * 60 // 15 minutes
export const IMetricsPollService = createDecorator<IMetricsPollService>('voidMetricsPollService');
export const IMetricsPollService = createDecorator<IMetricsPollService>('codeMetricsPollService');
class MetricsPollService extends Disposable implements IMetricsPollService {
_serviceBrand: undefined;
static readonly ID = 'voidMetricsPollService';
static readonly ID = 'codeMetricsPollService';
private readonly intervalID: number
@@ -13,7 +13,7 @@ import { getActiveWindow } from '../../../../base/browser/dom.js';
// Onboarding contribution that mounts the component at startup
export class MiscWorkbenchContribs extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.voidMiscWorkbenchContribs';
static readonly ID = 'workbench.contrib.codeMiscWorkbenchContribs';
constructor(
@IExtensionTransferService private readonly extensionTransferService: IExtensionTransferService,
@@ -26,7 +26,7 @@ export class MiscWorkbenchContribs extends Disposable implements IWorkbenchContr
private initialize(): void {
// delete blacklisted extensions once (this is for people who already installed them)
const deleteExtensionsStorageId = 'void-deleted-blacklist-2'
const deleteExtensionsStorageId = 'code-deleted-blacklist-2'
const alreadyDeleted = this.storageService.get(deleteExtensionsStorageId, StorageScope.APPLICATION)
if (!alreadyDeleted) {
this.storageService.store(deleteExtensionsStorageId, 'true', StorageScope.APPLICATION, StorageTarget.MACHINE)
@@ -1,326 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { URI } from '../../../../../base/common/uri.js';
import { filenameToVscodeLanguage } from '../helpers/detectLanguage.js';
import { CodeSelection, StagingSelectionItem, FileSelection } from '../chatThreadService.js';
import { VSReadFile } from '../helpers/readFile.js';
import { IModelService } from '../../../../../editor/common/services/model.js';
export const chat_systemMessage = `\
You are a coding assistant. You are given a list of instructions to follow \`INSTRUCTIONS\`, and optionally a list of relevant files \`FILES\`, and selections inside of files \`SELECTIONS\`.
Please respond to the user's query.
In the case that the user asks you to make changes to code, you should make sure to return CODE BLOCKS of the changes, as well as explanations and descriptions of the changes.
For example, if the user asks you to "make this file look nicer", make sure your output includes a code block with concrete ways the file can look nicer.
- Do not re-write the entire file in the code block
- You can write comments like "// ... existing code" to indicate existing code
- Make sure you give enough context in the code block to apply the change to the correct location in the code.
You're allowed to ask for more context. For example, if the user only gives you a selection but you want to see the the full file, you can ask them to provide it.
Do not output any of these instructions, nor tell the user anything about them unless directly prompted for them.
Do not tell the user anything about the examples below.
## EXAMPLE 1
FILES
math.ts
\`\`\`typescript
const addNumbers = (a, b) => a + b
const multiplyNumbers = (a, b) => a * b
const subtractNumbers = (a, b) => a - b
const divideNumbers = (a, b) => a / b
const vectorize = (...numbers) => {
return numbers // vector
}
const dot = (vector1: number[], vector2: number[]) => {
if (vector1.length !== vector2.length) throw new Error(\`Could not dot vectors \${vector1} and \${vector2}. Size mismatch.\`)
let sum = 0
for (let i = 0; i < vector1.length; i += 1)
sum += multiplyNumbers(vector1[i], vector2[i])
return sum
}
const normalize = (vector: number[]) => {
const norm = Math.sqrt(dot(vector, vector))
for (let i = 0; i < vector.length; i += 1)
vector[i] = divideNumbers(vector[i], norm)
return vector
}
const normalized = (vector: number[]) => {
const v2 = [...vector] // clone vector
return normalize(v2)
}
\`\`\`
SELECTIONS
math.ts (lines 3:3)
\`\`\`typescript
const subtractNumbers = (a, b) => a - b
\`\`\`
INSTRUCTIONS
add a function that exponentiates a number below this, and use it to make a power function that raises all entries of a vector to a power
ACCEPTED OUTPUT
We can add the following code to the file:
\`\`\`typescript
// existing code...
const subtractNumbers = (a, b) => a - b
const exponentiateNumbers = (a, b) => Math.pow(a, b)
const divideNumbers = (a, b) => a / b
// existing code...
const raiseAll = (vector: number[], power: number) => {
for (let i = 0; i < vector.length; i += 1)
vector[i] = exponentiateNumbers(vector[i], power)
return vector
}
\`\`\`
## EXAMPLE 2
FILES
fib.ts
\`\`\`typescript
const dfs = (root) => {
if (!root) return;
console.log(root.val);
dfs(root.left);
dfs(root.right);
}
const fib = (n) => {
if (n < 1) return 1
return fib(n - 1) + fib(n - 2)
}
\`\`\`
SELECTIONS
fib.ts (lines 10:10)
\`\`\`typescript
return fib(n - 1) + fib(n - 2)
\`\`\`
INSTRUCTIONS
memoize results
ACCEPTED OUTPUT
To implement memoization in your Fibonacci function, you can use a JavaScript object to store previously computed results. This will help avoid redundant calculations and improve performance. Here's how you can modify your function:
\`\`\`typescript
// existing code...
const fib = (n, memo = {}) => {
if (n < 1) return 1;
if (memo[n]) return memo[n]; // Check if result is already computed
memo[n] = fib(n - 1, memo) + fib(n - 2, memo); // Store result in memo
return memo[n];
}
\`\`\`
Explanation:
Memoization Object: A memo object is used to store the results of Fibonacci calculations for each n.
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\
`
type FileSelnLocal = FileSelection & { content: string }
const stringifyFileSelection = ({ fileURI, selectionStr, range, content }: FileSelnLocal) => {
return `\
${fileURI.fsPath}
\`\`\`${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}
${content}
\`\`\`
`
}
const stringifyCodeSelection = ({ fileURI, selectionStr, range }: CodeSelection) => {
return `\
${fileURI.fsPath} (lines ${range.startLineNumber}:${range.endLineNumber})
\`\`\`${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}
${selectionStr}
\`\`\`
`
}
const failToReadStr = 'Could not read content. This file may have been deleted. If you expected content here, you can tell the user about this as they might not know.'
const stringifyFileSelections = async (fileSelections: FileSelection[], modelService: IModelService) => {
if (fileSelections.length === 0) return null
const fileSlns: FileSelnLocal[] = await Promise.all(fileSelections.map(async (sel) => {
const content = await VSReadFile(modelService, sel.fileURI) ?? failToReadStr
return { ...sel, content }
}))
return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n')
}
const stringifyCodeSelections = (codeSelections: CodeSelection[]) => {
return codeSelections.map(sel => stringifyCodeSelection(sel)).join('\n')
}
export const chat_userMessage = async (instructions: string, selections: StagingSelectionItem[] | null, modelService: IModelService) => {
const fileSelections = selections?.filter(s => s.type === 'File') as FileSelection[]
const codeSelections = selections?.filter(s => s.type === 'Selection') as CodeSelection[]
const filesStr = await stringifyFileSelections(fileSelections, modelService)
const codeStr = stringifyCodeSelections(codeSelections)
let str = ''
if (filesStr) str += `FILES\n${filesStr}\n`
if (codeStr) str += `SELECTIONS\n${codeStr}\n`
str += `INSTRUCTIONS\n${instructions}`
return str;
};
export const fastApply_systemMessage = `\
You are a coding assistant that re-writes an entire file to make a change. You are given the original file \`ORIGINAL_FILE\` and a change \`CHANGE\`.
Directions:
1. Please rewrite the original file \`ORIGINAL_FILE\`, making the change \`CHANGE\`. You must completely re-write the whole file.
2. Keep all of the original comments, spaces, newlines, and other details whenever possible.
3. ONLY output the full new file. Do not add any other explanations or text.
`
export const fastApply_userMessage = ({ originalCode, applyStr, uri }: { originalCode: string, applyStr: string, uri: URI }) => {
const language = filenameToVscodeLanguage(uri.fsPath) ?? ''
return `\
ORIGINAL_FILE
\`\`\`${language}
${originalCode}
\`\`\`
CHANGE
\`\`\`
${applyStr}
\`\`\`
INSTRUCTIONS
Please finish writing the new file by applying the change to the original file. Return ONLY the completion of the file, without any explanation.
`
}
export const voidPrefixAndSuffix = ({ fullFileStr, startLine, endLine }: { fullFileStr: string, startLine: number, endLine: number }) => {
const fullFileLines = fullFileStr.split('\n')
// we can optimize this later
const MAX_PREFIX_SUFFIX_CHARS = 20_000
/*
a
a
a <-- final i (prefix = a\na\n)
a
|b <-- startLine-1 (middle = b\nc\nd\n) <-- initial i (moves up)
c
d| <-- endLine-1 <-- initial j (moves down)
e
e <-- final j (suffix = e\ne\n)
e
e
*/
let prefix = ''
let i = startLine - 1 // 0-indexed exclusive
// we'll include fullFileLines[i...(startLine-1)-1].join('\n') in the prefix.
while (i !== 0) {
const newLine = fullFileLines[i - 1]
if (newLine.length + 1 + prefix.length <= MAX_PREFIX_SUFFIX_CHARS) { // +1 to include the \n
prefix = `${newLine}\n${prefix}`
i -= 1
}
else break
}
let suffix = ''
let j = endLine - 1
while (j !== fullFileLines.length - 1) {
const newLine = fullFileLines[j + 1]
if (newLine.length + 1 + suffix.length <= MAX_PREFIX_SUFFIX_CHARS) { // +1 to include the \n
suffix = `${suffix}\n${newLine}`
j += 1
}
else break
}
return { prefix, suffix }
}
export type FimTagsType = {
preTag: string,
sufTag: string,
midTag: string
}
export const defaultFimTags: FimTagsType = {
preTag: 'ABOVE',
sufTag: 'BELOW',
midTag: 'SELECTION',
}
// this should probably be longer
export const ctrlKStream_systemMessage = ({ fimTags: { preTag, midTag, sufTag } }: { fimTags: FimTagsType }) => {
return `\
You are a FIM (fill-in-the-middle) coding assistant. Your task is to fill in the middle SELECTION marked by <${midTag}> tags.
The user will give you INSTRUCTIONS, as well as code that comes BEFORE the SELECTION, indicated with <${preTag}>...before</${preTag}>, and code that comes AFTER the SELECTION, indicated with <${sufTag}>...after</${sufTag}>.
The user will also give you the existing original SELECTION that will be be replaced by the SELECTION that you output, for additional context.
Instructions:
1. Your OUTPUT should be a SINGLE PIECE OF CODE of the form <${midTag}>...new_code</${midTag}>. Do NOT output any text or explanations before or after this.
2. You may ONLY CHANGE the original SELECTION, and NOT the content in the <${preTag}>...</${preTag}> or <${sufTag}>...</${sufTag}> tags.
3. Make sure all brackets in the new selection are balanced the same as in the original selection.
4. Be careful not to duplicate or remove variables, comments, or other syntax by mistake.
`
}
export const ctrlKStream_userMessage = ({ selection, prefix, suffix, instructions, fimTags, isOllamaFIM, language }: {
selection: string, prefix: string, suffix: string, instructions: string, fimTags: FimTagsType, language: string,
isOllamaFIM: false, // we require this be false for clarity
}) => {
const { preTag, sufTag, midTag } = fimTags
// prompt the model artifically on how to do FIM
// const preTag = 'BEFORE'
// const sufTag = 'AFTER'
// const midTag = 'SELECTION'
return `\
CURRENT SELECTION
\`\`\`${language}
<${midTag}>${selection}</${midTag}>
\`\`\`
INSTRUCTIONS
${instructions}
<${preTag}>${prefix}</${preTag}>
<${sufTag}>${suffix}</${sufTag}>
Return only the completion block of code (of the form \`\`\`${language}
<${midTag}>...new code</${midTag}>
\`\`\`).`
};
@@ -42,6 +42,7 @@ registerAction2(class extends Action2 {
keybinding: {
primary: KeyMod.CtrlCmd | KeyCode.KeyK,
weight: KeybindingWeight.CodeExtension,
when: ContextKeyExpr.deserialize('editorFocus && !terminalFocus'),
}
});
}
@@ -1,77 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
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 CodeQuickEditState = {
quickEditsOfDocument: { [uri: string]: QuickEdit }
}
export interface IQuickEditStateService {
readonly _serviceBrand: undefined;
readonly state: CodeQuickEditState; // readonly to the user
setState(newState: Partial<CodeQuickEditState>): void;
onDidChangeState: Event<void>;
onDidFocusChat: Event<void>;
onDidBlurChat: Event<void>;
fireFocusChat(): void;
fireBlurChat(): void;
}
export const IQuickEditStateService = createDecorator<IQuickEditStateService>('codeQuickEditStateService');
class CodeQuickEditStateService extends Disposable implements IQuickEditStateService {
_serviceBrand: undefined;
static readonly ID = 'codeQuickEditStateService';
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: CodeQuickEditState
constructor(
) {
super()
// initial state
this.state = { quickEditsOfDocument: {} }
}
setState(newState: Partial<CodeQuickEditState>) {
this.state = { ...this.state, ...newState }
this._onDidChangeState.fire()
}
fireFocusChat() {
this._onFocusChat.fire()
}
fireBlurChat() {
this._onBlurChat.fire()
}
}
registerSingleton(IQuickEditStateService, CodeQuickEditStateService, InstantiationType.Eager);
@@ -60,7 +60,7 @@ function findDesiredPathFromLocalPath(localDesiredPath, currentPath) {
function saveStylesFile() {
setTimeout(() => {
try {
const pathToCssFile = findDesiredPathFromLocalPath('./src/vs/workbench/contrib/void/browser/react/src2/styles.css', __dirname);
const pathToCssFile = findDesiredPathFromLocalPath('./src/vs/workbench/contrib/code/browser/react/src2/styles.css', __dirname);
if (pathToCssFile === undefined) {
console.error('[scope-tailwind] Error finding styles.css');
@@ -87,7 +87,7 @@ if (isWatch) {
try {
console.log('🔨 Running initial scope-tailwind build to create src2 folder...');
execSync(
'npx scope-tailwind ./src -o src2/ -s code-scope -c styles.css -p "code-"',
'npx scope-tailwind ./src -o src2/ -s void-scope -c styles.css -p "code-"',
{ stdio: 'inherit' }
);
console.log('✅ src2/ created successfully.');
@@ -103,7 +103,7 @@ if (isWatch) {
'--watch', 'src',
'--ext', 'ts,tsx,css',
'--exec',
'npx scope-tailwind ./src -o src2/ -s code-scope -c styles.css -p "code-"'
'npx scope-tailwind ./src -o src2/ -s void-scope -c styles.css -p "code-"'
]);
const tsupWatcher = spawn('npx', [
@@ -145,7 +145,7 @@ if (isWatch) {
console.log('📦 Building...');
// Run scope-tailwind once
execSync('npx scope-tailwind ./src -o src2/ -s code-scope -c styles.css -p "code-"', { stdio: 'inherit' });
execSync('npx scope-tailwind ./src -o src2/ -s void-scope -c styles.css -p "code-"', { stdio: 'inherit' });
// Run tsup once
execSync('npx tsup', { stdio: 'inherit' });
@@ -10,26 +10,26 @@ import '../styles.css'
import { useCallback, useEffect, useState, useRef } from 'react';
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js';
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
import { VoidCommandBarProps } from '../../../voidCommandBarService.js';
import { CodeCommandBarProps } from '../../../codeCommandBarService.js';
import { Check, EllipsisVertical, Menu, MoveDown, MoveLeft, MoveRight, MoveUp, X } from 'lucide-react';
import {
VOID_GOTO_NEXT_DIFF_ACTION_ID,
VOID_GOTO_PREV_DIFF_ACTION_ID,
VOID_GOTO_NEXT_URI_ACTION_ID,
VOID_GOTO_PREV_URI_ACTION_ID,
VOID_ACCEPT_FILE_ACTION_ID,
VOID_REJECT_FILE_ACTION_ID,
VOID_ACCEPT_ALL_DIFFS_ACTION_ID,
VOID_REJECT_ALL_DIFFS_ACTION_ID
CODE_GOTO_NEXT_DIFF_ACTION_ID,
CODE_GOTO_PREV_DIFF_ACTION_ID,
CODE_GOTO_NEXT_URI_ACTION_ID,
CODE_GOTO_PREV_URI_ACTION_ID,
CODE_ACCEPT_FILE_ACTION_ID,
CODE_REJECT_FILE_ACTION_ID,
CODE_ACCEPT_ALL_DIFFS_ACTION_ID,
CODE_REJECT_ALL_DIFFS_ACTION_ID
} from '../../../actionIDs.js';
export const VoidCommandBarMain = ({ uri, editor }: VoidCommandBarProps) => {
export const CodeCommandBarMain = ({ uri, editor }: CodeCommandBarProps) => {
const isDark = useIsDark()
return <div
className={`@@void-scope ${isDark ? 'dark' : ''}`}
>
<VoidCommandBar uri={uri} editor={editor} />
<CodeCommandBar uri={uri} editor={editor} />
</div>
}
@@ -83,14 +83,14 @@ export const RejectAllButtonWrapper = ({ text, onClick, className, ...props }: {
export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
export const CodeCommandBar = ({ uri, editor }: CodeCommandBarProps) => {
const accessor = useAccessor()
const editCodeService = accessor.get('IEditCodeService')
const editorService = accessor.get('ICodeEditorService')
const metricsService = accessor.get('IMetricsService')
const commandService = accessor.get('ICommandService')
const commandBarService = accessor.get('IVoidCommandBarService')
const voidModelService = accessor.get('IVoidModelService')
const commandBarService = accessor.get('ICodeCommandBarService')
const codeModelService = accessor.get('ICodeModelService')
const keybindingService = accessor.get('IKeybindingService')
const { stateOfURI: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
const [showAcceptRejectAllButtons, setShowAcceptRejectAllButtons] = useState(false)
@@ -171,14 +171,14 @@ export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
const _upKeybinding = keybindingService.lookupKeybinding(VOID_GOTO_PREV_DIFF_ACTION_ID);
const _downKeybinding = keybindingService.lookupKeybinding(VOID_GOTO_NEXT_DIFF_ACTION_ID);
const _leftKeybinding = keybindingService.lookupKeybinding(VOID_GOTO_PREV_URI_ACTION_ID);
const _rightKeybinding = keybindingService.lookupKeybinding(VOID_GOTO_NEXT_URI_ACTION_ID);
const _acceptFileKeybinding = keybindingService.lookupKeybinding(VOID_ACCEPT_FILE_ACTION_ID);
const _rejectFileKeybinding = keybindingService.lookupKeybinding(VOID_REJECT_FILE_ACTION_ID);
const _acceptAllKeybinding = keybindingService.lookupKeybinding(VOID_ACCEPT_ALL_DIFFS_ACTION_ID);
const _rejectAllKeybinding = keybindingService.lookupKeybinding(VOID_REJECT_ALL_DIFFS_ACTION_ID);
const _upKeybinding = keybindingService.lookupKeybinding(CODE_GOTO_PREV_DIFF_ACTION_ID);
const _downKeybinding = keybindingService.lookupKeybinding(CODE_GOTO_NEXT_DIFF_ACTION_ID);
const _leftKeybinding = keybindingService.lookupKeybinding(CODE_GOTO_PREV_URI_ACTION_ID);
const _rightKeybinding = keybindingService.lookupKeybinding(CODE_GOTO_NEXT_URI_ACTION_ID);
const _acceptFileKeybinding = keybindingService.lookupKeybinding(CODE_ACCEPT_FILE_ACTION_ID);
const _rejectFileKeybinding = keybindingService.lookupKeybinding(CODE_REJECT_FILE_ACTION_ID);
const _acceptAllKeybinding = keybindingService.lookupKeybinding(CODE_ACCEPT_ALL_DIFFS_ACTION_ID);
const _rejectAllKeybinding = keybindingService.lookupKeybinding(CODE_REJECT_ALL_DIFFS_ACTION_ID);
const upKeybindLabel = editCodeService.processRawKeybindingText(_upKeybinding?.getLabel() || '');
const downKeybindLabel = editCodeService.processRawKeybindingText(_downKeybinding?.getLabel() || '');
@@ -231,7 +231,7 @@ export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
<AcceptAllButtonWrapper
// text={`Accept All${acceptAllKeybindLabel ? ` ${acceptAllKeybindLabel}` : ''}`}
text={`Accept All`}
data-tooltip-id='void-tooltip'
data-tooltip-id='code-tooltip'
data-tooltip-content={acceptAllKeybindLabel}
data-tooltip-delay-show={500}
onClick={onAcceptAll}
@@ -239,7 +239,7 @@ export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
<RejectAllButtonWrapper
// text={`Reject All${rejectAllKeybindLabel ? ` ${rejectAllKeybindLabel}` : ''}`}
text={`Reject All`}
data-tooltip-id='void-tooltip'
data-tooltip-id='code-tooltip'
data-tooltip-content={rejectAllKeybindLabel}
data-tooltip-delay-show={500}
onClick={onRejectAll}
@@ -263,7 +263,7 @@ export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
commandBarService.goToDiffIdx(prevDiffIdx);
}
}}
data-tooltip-id="void-tooltip"
data-tooltip-id="code-tooltip"
data-tooltip-content={`${upKeybindLabel ? `${upKeybindLabel}` : ''}`}
data-tooltip-delay-show={500}
>
@@ -288,7 +288,7 @@ export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
commandBarService.goToDiffIdx(nextDiffIdx);
}
}}
data-tooltip-id="void-tooltip"
data-tooltip-id="code-tooltip"
data-tooltip-content={`${downKeybindLabel ? `${downKeybindLabel}` : ''}`}
data-tooltip-delay-show={500}
>
@@ -310,7 +310,7 @@ export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
commandBarService.goToURIIdx(prevURIIdx);
}
}}
data-tooltip-id="void-tooltip"
data-tooltip-id="code-tooltip"
data-tooltip-content={`${leftKeybindLabel ? `${leftKeybindLabel}` : ''}`}
data-tooltip-delay-show={500}
>
@@ -332,7 +332,7 @@ export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
commandBarService.goToURIIdx(nextURIIdx);
}
}}
data-tooltip-id="void-tooltip"
data-tooltip-id="code-tooltip"
data-tooltip-content={`${rightKeybindLabel ? `${rightKeybindLabel}` : ''}`}
data-tooltip-delay-show={500}
>
@@ -347,7 +347,7 @@ export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
<AcceptAllButtonWrapper
// text={`Accept File${acceptFileKeybindLabel ? ` ${acceptFileKeybindLabel}` : ''}`}
text={`Accept File`}
data-tooltip-id='void-tooltip'
data-tooltip-id='code-tooltip'
data-tooltip-content={acceptFileKeybindLabel}
data-tooltip-delay-show={500}
onClick={onAcceptFile}
@@ -355,7 +355,7 @@ export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
<RejectAllButtonWrapper
// text={`Reject File${rejectFileKeybindLabel ? ` ${rejectFileKeybindLabel}` : ''}`}
text={`Reject File`}
data-tooltip-id='void-tooltip'
data-tooltip-id='code-tooltip'
data-tooltip-content={rejectFileKeybindLabel}
data-tooltip-delay-show={500}
onClick={onRejectFile}
@@ -7,36 +7,36 @@
import { useAccessor, useActiveURI, useIsDark, useSettingsState } from '../util/services.js';
import '../styles.css'
import { VOID_CTRL_K_ACTION_ID, VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
import { CODE_CTRL_K_ACTION_ID, CODE_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
import { Circle, MoreVertical } from 'lucide-react';
import { useEffect, useState } from 'react';
import { VoidSelectionHelperProps } from '../../../../../../contrib/void/browser/voidSelectionHelperWidget.js';
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
import { CodeSelectionHelperProps } from '../../../../../../contrib/code/browser/codeSelectionHelperWidget.js';
import { CODE_OPEN_SETTINGS_ACTION_ID } from '../../../codeSettingsPane.js';
export const VoidSelectionHelperMain = (props: VoidSelectionHelperProps) => {
export const CodeSelectionHelperMain = (props: CodeSelectionHelperProps) => {
const isDark = useIsDark()
return <div
className={`@@void-scope ${isDark ? 'dark' : ''}`}
>
<VoidSelectionHelper {...props} />
<CodeSelectionHelper {...props} />
</div>
}
const VoidSelectionHelper = ({ rerenderKey }: VoidSelectionHelperProps) => {
const CodeSelectionHelper = ({ rerenderKey }: CodeSelectionHelperProps) => {
const accessor = useAccessor()
const keybindingService = accessor.get('IKeybindingService')
const commandService = accessor.get('ICommandService')
const ctrlLKeybind = keybindingService.lookupKeybinding(VOID_CTRL_L_ACTION_ID)
const ctrlKKeybind = keybindingService.lookupKeybinding(VOID_CTRL_K_ACTION_ID)
const ctrlLKeybind = keybindingService.lookupKeybinding(CODE_CTRL_L_ACTION_ID)
const ctrlKKeybind = keybindingService.lookupKeybinding(CODE_CTRL_K_ACTION_ID)
const dividerHTML = <div className='w-[0.5px] bg-void-border-3'></div>
@@ -45,7 +45,7 @@ const VoidSelectionHelper = ({ rerenderKey }: VoidSelectionHelperProps) => {
useEffect(() => {
const disposable = commandService.onWillExecuteCommand(e => {
if (e.commandId === VOID_CTRL_L_ACTION_ID || e.commandId === VOID_CTRL_K_ACTION_ID) {
if (e.commandId === CODE_CTRL_L_ACTION_ID || e.commandId === CODE_CTRL_K_ACTION_ID) {
setClickState('clickedOption')
}
});
@@ -79,7 +79,7 @@ const VoidSelectionHelper = ({ rerenderKey }: VoidSelectionHelperProps) => {
cursor-pointer
'
onClick={() => {
commandService.executeCommand(VOID_CTRL_L_ACTION_ID)
commandService.executeCommand(CODE_CTRL_L_ACTION_ID)
setClickState('clickedOption');
}}
>
@@ -99,7 +99,7 @@ const VoidSelectionHelper = ({ rerenderKey }: VoidSelectionHelperProps) => {
cursor-pointer
'
onClick={() => {
commandService.executeCommand(VOID_CTRL_K_ACTION_ID)
commandService.executeCommand(CODE_CTRL_K_ACTION_ID)
setClickState('clickedOption');
}}
>
@@ -133,7 +133,7 @@ const VoidSelectionHelper = ({ rerenderKey }: VoidSelectionHelperProps) => {
cursor-pointer
'
onClick={() => {
commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID);
commandService.executeCommand(CODE_OPEN_SETTINGS_ACTION_ID);
setClickState('clickedOption');
}}
>
@@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------*/
import { mountFnGenerator } from '../util/mountFnGenerator.js'
import { VoidCommandBarMain } from './VoidCommandBar.js'
import { VoidSelectionHelperMain } from './VoidSelectionHelper.js'
import { CodeCommandBarMain } from './CodeCommandBar.js'
import { CodeSelectionHelperMain } from './CodeSelectionHelper.js'
export const mountVoidCommandBar = mountFnGenerator(VoidCommandBarMain)
export const mountCodeCommandBar = mountFnGenerator(CodeCommandBarMain)
export const mountVoidSelectionHelper = mountFnGenerator(VoidSelectionHelperMain)
export const mountCodeSelectionHelper = mountFnGenerator(CodeSelectionHelperMain)
@@ -6,19 +6,19 @@
import { useEffect, useRef, useState } from 'react';
import { useAccessor, useIsDark, useSettingsState } from '../util/services.js';
import { Brain, Check, ChevronRight, DollarSign, ExternalLink, Lock, X } from 'lucide-react';
import { displayInfoOfProviderName, ProviderName, providerNames, localProviderNames, featureNames, FeatureName, isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js';
import { displayInfoOfProviderName, ProviderName, providerNames, localProviderNames, featureNames, FeatureName, isFeatureNameDisabled } from '../../../../common/codeSettingsTypes.js';
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
import { OllamaSetupInstructions, OneClickSwitchButton, SettingsForProvider, ModelDump } from '../void-settings-tsx/Settings.js';
import { OllamaSetupInstructions, OneClickSwitchButton, SettingsForProvider, ModelDump } from '../code-settings-tsx/Settings.js';
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js';
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js';
import { isLinux } from '../../../../../../../base/common/platform.js';
const OVERRIDE_VALUE = false
export const VoidOnboarding = () => {
export const CodeOnboarding = () => {
const voidSettingsState = useSettingsState()
const isOnboardingComplete = voidSettingsState.globalSettings.isOnboardingComplete || OVERRIDE_VALUE
const codeSettingsState = useSettingsState()
const isOnboardingComplete = codeSettingsState.globalSettings.isOnboardingComplete || OVERRIDE_VALUE
const isDark = useIsDark()
@@ -32,14 +32,14 @@ export const VoidOnboarding = () => {
style={{ height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
<ErrorBoundary>
<VoidOnboardingContent />
<CodeOnboardingContent />
</ErrorBoundary>
</div>
</div>
)
}
const VoidIcon = () => {
const CodeIcon = () => {
const accessor = useAccessor()
const themeService = accessor.get('IThemeService')
@@ -206,7 +206,7 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
Add {displayInfoOfProviderName(providerName).title}
{providerName === 'gemini' && (
<span
data-tooltip-id="void-tooltip-provider-info"
data-tooltip-id="code-tooltip-provider-info"
data-tooltip-content="Gemini 2.5 Pro offers 25 free messages a day, and Gemini 2.5 Flash offers 500. We recommend using models down the line as you run out of free credits."
data-tooltip-place="right"
className="ml-1 text-xs align-top text-blue-400"
@@ -214,7 +214,7 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
)}
{providerName === 'openRouter' && (
<span
data-tooltip-id="void-tooltip-provider-info"
data-tooltip-id="code-tooltip-provider-info"
data-tooltip-content="OpenRouter offers 50 free messages a day, and 1000 if you deposit $10. Only applies to models labeled ':free'."
data-tooltip-place="right"
className="ml-1 text-xs align-top text-blue-400"
@@ -275,7 +275,7 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
// OnboardingPage
// title:
// div
// "Welcome to Void"
// "Welcome to Code"
// image
// content:<></>
// title
@@ -285,7 +285,7 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
// OnboardingPage
// title:
// div
// "How would you like to use Void?"
// "How would you like to use Code?"
// content:
// ModelQuestionContent
// |
@@ -328,7 +328,7 @@ const NextButton = ({ onClick, ...props }: { onClick: () => void } & React.Butto
} rounded text-black duration-600 transition-all
`}
{...disabled && {
'data-tooltip-id': 'void-tooltip',
'data-tooltip-id': 'code-tooltip',
"data-tooltip-content": 'Please enter all required fields or choose another provider', // (double-click to proceed anyway, can come back in Settings)
"data-tooltip-place": 'top',
}}
@@ -467,14 +467,14 @@ const PrimaryActionButton = ({ children, className, ringSize, ...props }: { chil
type WantToUseOption = 'smart' | 'private' | 'cheap' | 'all'
const VoidOnboardingContent = () => {
const CodeOnboardingContent = () => {
const accessor = useAccessor()
const voidSettingsService = accessor.get('IVoidSettingsService')
const voidMetricsService = accessor.get('IMetricsService')
const codeSettingsService = accessor.get('ICodeSettingsService')
const codeMetricsService = accessor.get('IMetricsService')
const voidSettingsState = useSettingsState()
const codeSettingsState = useSettingsState()
const [pageIndex, setPageIndex] = useState(0)
@@ -518,9 +518,9 @@ const VoidOnboardingContent = () => {
const selectedProviderName = getSelectedProvider();
const didFillInProviderSettings = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName]._didFillInProviderSettings
const isApiKeyLongEnoughIfApiKeyExists = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].apiKey ? voidSettingsState.settingsOfProvider[selectedProviderName].apiKey.length > 15 : true
const isAtLeastOneModel = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].models.length >= 1
const didFillInProviderSettings = selectedProviderName && codeSettingsState.settingsOfProvider[selectedProviderName]._didFillInProviderSettings
const isApiKeyLongEnoughIfApiKeyExists = selectedProviderName && codeSettingsState.settingsOfProvider[selectedProviderName].apiKey ? codeSettingsState.settingsOfProvider[selectedProviderName].apiKey.length > 15 : true
const isAtLeastOneModel = selectedProviderName && codeSettingsState.settingsOfProvider[selectedProviderName].models.length >= 1
const didFillInSelectedProviderSettings = !!(didFillInProviderSettings && isApiKeyLongEnoughIfApiKeyExists && isAtLeastOneModel)
@@ -543,11 +543,11 @@ const VoidOnboardingContent = () => {
/>
<PrimaryActionButton
onClick={() => {
voidSettingsService.setGlobalSetting('isOnboardingComplete', true);
voidMetricsService.capture('Completed Onboarding', { selectedProviderName, wantToUseOption })
codeSettingsService.setGlobalSetting('isOnboardingComplete', true);
codeMetricsService.capture('Completed Onboarding', { selectedProviderName, wantToUseOption })
}}
ringSize={voidSettingsState.globalSettings.isOnboardingComplete ? 'screen' : undefined}
>Enter the Void</PrimaryActionButton>
ringSize={codeSettingsState.globalSettings.isOnboardingComplete ? 'screen' : undefined}
>Enter the Code</PrimaryActionButton>
</div>
</div>
@@ -586,21 +586,21 @@ const VoidOnboardingContent = () => {
// reset the page to page 0 if the user redos onboarding
useEffect(() => {
if (!voidSettingsState.globalSettings.isOnboardingComplete) {
if (!codeSettingsState.globalSettings.isOnboardingComplete) {
setPageIndex(0)
}
}, [setPageIndex, voidSettingsState.globalSettings.isOnboardingComplete])
}, [setPageIndex, codeSettingsState.globalSettings.isOnboardingComplete])
const contentOfIdx: { [pageIndex: number]: React.ReactNode } = {
0: <OnboardingPageShell
content={
<div className='flex flex-col items-center gap-8'>
<div className="text-5xl font-light text-center">Welcome to Void</div>
<div className="text-5xl font-light text-center">Welcome to Code</div>
{/* Slice of Void image */}
{/* Slice of Code image */}
<div className='max-w-md w-full h-[30vh] mx-auto flex items-center justify-center'>
{!isLinux && <VoidIcon />}
{!isLinux && <CodeIcon />}
</div>
@@ -4,6 +4,6 @@
*--------------------------------------------------------------------------------------*/
import { mountFnGenerator } from '../util/mountFnGenerator.js'
import { VoidOnboarding } from './VoidOnboarding.js'
import { CodeOnboarding } from './CodeOnboarding.js'
export const mountVoidOnboarding = mountFnGenerator(VoidOnboarding)
export const mountCodeOnboarding = mountFnGenerator(CodeOnboarding)
@@ -4,15 +4,15 @@
*--------------------------------------------------------------------------------------*/
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { FeatureName, featureNames, ModelSelection, modelSelectionsEqual, ProviderName, providerNames } from '../../../../../../../platform/void/common/codeSettingsTypes.js'
import { FeatureName, featureNames, isFeatureNameDisabled, ModelSelection, modelSelectionsEqual, ProviderName, providerNames, SettingsOfProvider } from '../../../../../../../workbench/contrib/code/common/codeSettingsTypes.js'
import { useSettingsState, useRefreshModelState, useAccessor } from '../util/services.js'
import { _CodeSelectBox, CodeCustomSelectBox } from '../util/inputs.js'
import { _CodeSelectBox, CodeCustomDropdownBox } from '../util/inputs.js'
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'
import { IconWarning } from '../sidebar-tsx/SidebarChat.js'
import { CODE_OPEN_SETTINGS_ACTION_ID, CODE_TOGGLE_SETTINGS_ACTION_ID } from '../../../codeSettingsPane.js'
import { ModelOption } from '../../../../../../../platform/void/common/codeSettingsService.js'
import { modelFilterOfFeatureName, ModelOption } from '../../../../../../../workbench/contrib/code/common/codeSettingsService.js'
import { WarningBox } from './WarningBox.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
const optionsEqual = (m1: ModelOption[], m2: ModelOption[]) => {
if (m1.length !== m2.length) return false
@@ -27,13 +27,13 @@ const ModelSelectBox = ({ options, featureName, className }: { options: ModelOpt
const codeSettingsService = accessor.get('ICodeSettingsService')
const selection = codeSettingsService.state.modelSelectionOfFeature[featureName]
const selectedOption = selection ? codeSettingsService.state._modelOptions.find(v => modelSelectionsEqual(v.selection, selection)) : options[0]
const selectedOption = selection ? codeSettingsService.state._modelOptions.find(v => modelSelectionsEqual(v.selection, selection))! : options[0]
const onChangeOption = useCallback((newOption: ModelOption) => {
codeSettingsService.setModelSelectionOfFeature(featureName, newOption.selection)
}, [codeSettingsService, featureName])
return <CodeCustomSelectBox
return <CodeCustomDropdownBox
options={options}
selectedOption={selectedOption}
onChangeOption={onChangeOption}
@@ -41,40 +41,13 @@ const ModelSelectBox = ({ options, featureName, className }: { options: ModelOpt
getOptionDropdownName={(option) => option.selection.modelName}
getOptionDropdownDetail={(option) => option.selection.providerName}
getOptionsEqual={(a, b) => optionsEqual([a], [b])}
className={`text-xs text-code-fg-3 px-1`}
className={className}
matchInputWidth={false}
/>
}
// const codeSettingsService = accessor.get('ICodeSettingsService')
// let weChangedText = false
// return <CodeSelectBox
// className='@@[&_select]:!code-text-xs text-code-fg-3'
// options={options}
// onChangeSelection={useCallback((newVal: ModelSelection) => {
// if (weChangedText) return
// codeSettingsService.setModelSelectionOfFeature(featureName, newVal)
// }, [codeSettingsService, featureName])}
// // we are responsible for setting the initial state here. always sync instance when state changes.
// onCreateInstance={useCallback((instance: SelectBox) => {
// const syncInstance = () => {
// const modelsListRef = codeSettingsService.state._modelOptions // as a ref
// const settingsAtProvider = codeSettingsService.state.modelSelectionOfFeature[featureName]
// const selectionIdx = settingsAtProvider === null ? -1 : modelsListRef.findIndex(v => modelSelectionsEqual(v.value, settingsAtProvider))
// weChangedText = true
// instance.select(selectionIdx === -1 ? 0 : selectionIdx)
// weChangedText = false
// }
// syncInstance()
// const disposable = codeSettingsService.onDidChangeState(syncInstance)
// return [disposable]
// }, [codeSettingsService, featureName])}
// />
// }
const MemoizedModelSelectBox = ({ featureName }: { featureName: FeatureName }) => {
const MemoizedModelDropdown = ({ featureName, className }: { featureName: FeatureName, className: string }) => {
const settingsState = useSettingsState()
const oldOptionsRef = useRef<ModelOption[]>([])
const [memoizedOptions, setMemoizedOptions] = useState(oldOptionsRef.current)
@@ -99,31 +72,7 @@ const MemoizedModelSelectBox = ({ featureName }: { featureName: FeatureName }) =
}
export const WarningBox = ({ text, onClick, className }: { text: string; onClick?: () => void; className?: string }) => {
return <div
className={`
text-code-warning brightness-90 opacity-90
text-xs text-ellipsis
${onClick ? `hover:brightness-75 transition-all duration-200 cursor-pointer` : ''}
flex items-center flex-nowrap
${className}
`}
onClick={onClick}
>
<IconWarning
size={14}
className='mr-1'
/>
<span>{text}</span>
</div>
// return <CodeSelectBox
// options={[{ text: 'Please add a model!', value: null }]}
// onChangeSelection={() => { }}
// />
}
export const ModelDropdown = ({ featureName }: { featureName: FeatureName }) => {
export const ModelDropdown = ({ featureName, className }: { featureName: FeatureName, className: string }) => {
const settingsState = useSettingsState()
const accessor = useAccessor()
File diff suppressed because it is too large Load Diff
@@ -19,7 +19,7 @@ export const WarningBox = ({ text, onClick, className }: { text: string; onClick
/>
<span>{text}</span>
</div>
// return <VoidSelectBox
// return <CodeSelectBox
// options={[{ text: 'Please add a model!', value: null }]}
// onChangeSelection={() => { }}
// />
@@ -11,10 +11,10 @@ import { useIsDark } from '../util/services.js';
/**
* Creates a configured global tooltip component with consistent styling
* To use:
* 1. Mount a Tooltip with some id eg id='void-tooltip'
* 2. Add data-tooltip-id="void-tooltip" and data-tooltip-content="Your tooltip text" to any element
* 1. Mount a Tooltip with some id eg id='code-tooltip'
* 2. Add data-tooltip-id="code-tooltip" and data-tooltip-content="Your tooltip text" to any element
*/
export const VoidTooltip = () => {
export const CodeTooltip = () => {
const isDark = useIsDark()
@@ -82,26 +82,26 @@ export const VoidTooltip = () => {
<Tooltip
id="void-tooltip"
id="code-tooltip"
// border='1px solid var(--vscode-editorGroup-border)'
border='1px solid rgba(100,100,100,.2)'
opacity={1}
delayShow={50}
/>
<Tooltip
id="void-tooltip-orange"
id="code-tooltip-orange"
border='1px solid rgba(200,200,200,.3)'
opacity={1}
delayShow={50}
/>
<Tooltip
id="void-tooltip-green"
id="code-tooltip-green"
border='1px solid rgba(200,200,200,.3)'
opacity={1}
delayShow={50}
/>
<Tooltip
id="void-tooltip-ollama-settings"
id="code-tooltip-ollama-settings"
border='1px solid rgba(100,100,100,.2)'
opacity={1}
openEvents={{ mouseover: true, click: true, focus: true }}
@@ -127,7 +127,7 @@ export const VoidTooltip = () => {
</Tooltip>
<Tooltip
id="void-tooltip-provider-info"
id="code-tooltip-provider-info"
border='1px solid rgba(100,100,100,.2)'
opacity={1}
delayShow={50}
@@ -4,6 +4,6 @@
*--------------------------------------------------------------------------------------*/
import { mountFnGenerator } from '../util/mountFnGenerator.js'
import { VoidTooltip } from './VoidTooltip.js'
import { CodeTooltip } from './CodeTooltip.js'
export const mountVoidTooltip = mountFnGenerator(VoidTooltip)
export const mountCodeTooltip = mountFnGenerator(CodeTooltip)
@@ -6,11 +6,11 @@
import { useState, useEffect, useCallback, useRef, Fragment } from 'react'
import { useAccessor, useChatThreadsState, useChatThreadsStreamState, useCommandBarState, useCommandBarURIListener, useSettingsState } from '../util/services.js'
import { usePromise, useRefState } from '../util/helpers.js'
import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js'
import { isFeatureNameDisabled } from '../../../../common/codeSettingsTypes.js'
import { URI } from '../../../../../../../base/common/uri.js'
import { FileSymlink, LucideIcon, RotateCw, Terminal } from 'lucide-react'
import { Check, X, Square, Copy, Play, } from 'lucide-react'
import { getBasename, ListableToolItem, voidOpenFileFn, ToolChildrenWrapper } from '../sidebar-tsx/SidebarChat.js'
import { getBasename, ListableToolItem, codeOpenFileFn, ToolChildrenWrapper } from '../sidebar-tsx/SidebarChat.js'
import { PlacesType, VariantType } from 'react-tooltip'
enum CopyButtonText {
@@ -109,7 +109,7 @@ export const JumpToFileButton = ({ uri, ...props }: { uri: URI | 'current' } & R
<IconShell1
Icon={FileSymlink}
onClick={() => {
voidOpenFileFn(uri, accessor)
codeOpenFileFn(uri, accessor)
}}
{...tooltipPropsForApplyBlock({ tooltipName: 'Go to file' })}
{...props}
@@ -141,13 +141,13 @@ const getUriBeingApplied = (applyBoxId: string) => {
export const useApplyStreamState = ({ applyBoxId }: { applyBoxId: string }) => {
const accessor = useAccessor()
const voidCommandBarService = accessor.get('IVoidCommandBarService')
const codeCommandBarService = accessor.get('ICodeCommandBarService')
const getStreamState = useCallback(() => {
const uri = getUriBeingApplied(applyBoxId)
if (!uri) return 'idle-no-changes'
return voidCommandBarService.getStreamState(uri)
}, [voidCommandBarService, applyBoxId])
return codeCommandBarService.getStreamState(uri)
}, [codeCommandBarService, applyBoxId])
const [currStreamStateRef, setStreamState] = useRefState(getStreamState())
@@ -190,7 +190,7 @@ export const StatusIndicator = ({ indicatorColor, title, className, ...props }:
};
const tooltipPropsForApplyBlock = ({ tooltipName, color = undefined, position = 'top', offset = undefined }: { tooltipName: string, color?: IndicatorColor, position?: PlacesType, offset?: number }) => ({
'data-tooltip-id': color === 'orange' ? `void-tooltip-orange` : color === 'green' ? 'void-tooltip-green' : 'void-tooltip',
'data-tooltip-id': color === 'orange' ? `code-tooltip-orange` : color === 'green' ? 'code-tooltip-green' : 'code-tooltip',
'data-tooltip-place': position as PlacesType,
'data-tooltip-content': `${tooltipName}`,
'data-tooltip-offset': offset,
@@ -198,13 +198,13 @@ const tooltipPropsForApplyBlock = ({ tooltipName, color = undefined, position =
export const useEditToolStreamState = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI }) => {
const accessor = useAccessor()
const voidCommandBarService = accessor.get('IVoidCommandBarService')
const [streamState, setStreamState] = useState(voidCommandBarService.getStreamState(uri))
const codeCommandBarService = accessor.get('ICodeCommandBarService')
const [streamState, setStreamState] = useState(codeCommandBarService.getStreamState(uri))
// listen for stream updates on this box
useCommandBarURIListener(useCallback((uri_) => {
const shouldUpdate = uri.fsPath === uri_.fsPath
if (shouldUpdate) { setStreamState(voidCommandBarService.getStreamState(uri)) }
}, [voidCommandBarService, applyBoxId, uri]))
if (shouldUpdate) { setStreamState(codeCommandBarService.getStreamState(uri)) }
}, [codeCommandBarService, applyBoxId, uri]))
return { streamState, }
}
@@ -351,14 +351,14 @@ const ApplyButtonsForEdit = ({
setApplying(newApplyingUri)
if (!applyDonePromise) {
notificationService.info(`Void Error: We couldn't run Apply here. ${uri === 'current' ? 'This Apply block wants to run on the current file, but you might not have a file open.' : `This Apply block wants to run on ${uri.fsPath}, but it might not exist.`}`)
notificationService.info(`Code Error: We couldn't run Apply here. ${uri === 'current' ? 'This Apply block wants to run on the current file, but you might not have a file open.' : `This Apply block wants to run on ${uri.fsPath}, but it might not exist.`}`)
}
// catch any errors by interrupting the stream
applyDonePromise?.catch(e => {
const uri = getUriBeingApplied(applyBoxId)
if (uri) editCodeService.interruptURIStreaming({ uri: uri })
notificationService.info(`Void Error: There was a problem running Apply: ${e}.`)
notificationService.info(`Code Error: There was a problem running Apply: ${e}.`)
})
metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only
@@ -530,7 +530,7 @@ export const BlockCodeApplyWrapper = ({
name={<span className='not-italic'>{getBasename(uri.fsPath)}</span>}
isSmall={true}
showDot={false}
onClick={() => { voidOpenFileFn(uri, accessor) }}
onClick={() => { codeOpenFileFn(uri, accessor) }}
/>
: <span>{language}</span>
@@ -1,29 +0,0 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import React from 'react';
import { CodeCodeEditor, CodeCodeEditorProps } from '../util/inputs.js';
export const BlockCode = ({ buttonsOnHover, ...codeEditorProps }: { buttonsOnHover?: React.ReactNode } & CodeCodeEditorProps) => {
const isSingleLine = !codeEditorProps.initValue.includes('\n')
return (
<>
<div className="relative group w-full overflow-hidden my-4">
{buttonsOnHover === null ? null : (
<div className={`z-[1] absolute top-0 right-0 opacity-0 group-hover:opacity-100 duration-200 ${isSingleLine ? 'h-full flex items-center' : ''}`}>
<div className={`flex space-x-1 ${isSingleLine ? 'pr-2' : 'p-2'}`}>
{buttonsOnHover}
</div>
</div>
)}
<CodeCodeEditor {...codeEditorProps} />
</div>
</>
)
}
@@ -14,7 +14,7 @@ import { isAbsolute } from '../../../../../../../base/common/path.js'
import { separateOutFirstLine } from '../../../../common/helpers/util.js'
import { BlockCode } from '../util/inputs.js'
import { CodespanLocationLink } from '../../../../common/chatThreadServiceTypes.js'
import { getBasename, getRelative, voidOpenFileFn } from '../sidebar-tsx/SidebarChat.js'
import { getBasename, getRelative, codeOpenFileFn } from '../sidebar-tsx/SidebarChat.js'
export type ChatMessageLocation = {
@@ -97,7 +97,7 @@ const Codespan = ({ text, className, onClick, tooltip }: { text: string, classNa
className={`font-mono font-medium rounded-sm bg-void-bg-1 px-1 ${className}`}
onClick={onClick}
{...tooltip ? {
'data-tooltip-id': 'void-tooltip',
'data-tooltip-id': 'code-tooltip',
'data-tooltip-content': tooltip,
'data-tooltip-place': 'top',
} : {}}
@@ -150,42 +150,118 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
const onClick = () => {
if (!link) return;
// Use the updated voidOpenFileFn to open the file and handle selection
// Use the updated codeOpenFileFn to open the file and handle selection
if (link.selection)
voidOpenFileFn(link.uri, accessor, [link.selection.startLineNumber, link.selection.endLineNumber]);
codeOpenFileFn(link.uri, accessor, [link.selection.startLineNumber, link.selection.endLineNumber]);
else
voidOpenFileFn(link.uri, accessor);
codeOpenFileFn(link.uri, accessor);
}
return <>
<button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-code-bg-1 text-code-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
onClick={onCopy}
>
{copyButtonState}
</button>
<button
// btn btn-secondary btn-sm border text-sm border-vscode-input-border rounded
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-code-bg-1 text-code-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
onClick={onApply}
>
Apply
</button>
</>
return <Codespan
text={displayText}
onClick={onClick}
className={link ? 'underline hover:brightness-90 transition-all duration-200 cursor-pointer' : ''}
tooltip={tooltip || undefined}
/>
}
export const CodeSpan = ({ children, className }: { children: React.ReactNode, className?: string }) => {
return <code className={`
bg-code-bg-1
px-1
rounded-sm
font-mono font-medium
break-all
${className}
`}
>
{children}
</code>
const paragraphToLatexSegments = (paragraphText: string) => {
const segments: React.ReactNode[] = [];
if (paragraphText
&& !(paragraphText.includes('#') || paragraphText.includes('`')) // don't process latex if a codespan or header tag
&& !/^[\w\s.()[\]{}]+$/.test(paragraphText) // don't process latex if string only contains alphanumeric chars, whitespace, periods, and brackets
) {
const rawText = paragraphText;
// Regular expressions to match LaTeX delimiters
const displayMathRegex = /\$\$(.*?)\$\$/g; // Display math: $$...$$
const inlineMathRegex = /\$((?!\$).*?)\$/g; // Inline math: $...$ (but not $$)
// Check if the paragraph contains any LaTeX expressions
if (displayMathRegex.test(rawText) || inlineMathRegex.test(rawText)) {
// Reset the regex state (since we used .test earlier)
displayMathRegex.lastIndex = 0;
inlineMathRegex.lastIndex = 0;
// Parse the text into segments of regular text and LaTeX
let lastIndex = 0;
let segmentId = 0;
// First replace display math ($$...$$)
let match;
while ((match = displayMathRegex.exec(rawText)) !== null) {
const [fullMatch, formula] = match;
const matchIndex = match.index;
// Add text before the LaTeX expression
if (matchIndex > lastIndex) {
const textBefore = rawText.substring(lastIndex, matchIndex);
segments.push(
<span key={`text-${segmentId++}`}>
{textBefore}
</span>
);
}
// Add the LaTeX expression
segments.push(
<LatexRender key={`latex-${segmentId++}`} latex={fullMatch} />
);
lastIndex = matchIndex + fullMatch.length;
}
// Add any remaining text (which might contain inline math)
if (lastIndex < rawText.length) {
const remainingText = rawText.substring(lastIndex);
// Process inline math in the remaining text
lastIndex = 0;
inlineMathRegex.lastIndex = 0;
const inlineSegments: React.ReactNode[] = [];
while ((match = inlineMathRegex.exec(remainingText)) !== null) {
const [fullMatch] = match;
const matchIndex = match.index;
// Add text before the inline LaTeX
if (matchIndex > lastIndex) {
const textBefore = remainingText.substring(lastIndex, matchIndex);
inlineSegments.push(
<span key={`inline-text-${segmentId++}`}>
{textBefore}
</span>
);
}
// Add the inline LaTeX
inlineSegments.push(
<LatexRender key={`inline-latex-${segmentId++}`} latex={fullMatch} />
);
lastIndex = matchIndex + fullMatch.length;
}
// Add any remaining text after all inline math
if (lastIndex < remainingText.length) {
inlineSegments.push(
<span key={`inline-final-${segmentId++}`}>
{remainingText.substring(lastIndex)}
</span>
);
}
segments.push(...inlineSegments);
}
}
}
return segments
}
@@ -263,45 +339,32 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
if (t.type === 'heading') {
const HeadingTag = `h${t.depth}` as keyof JSX.IntrinsicElements
const headingClasses: { [h: string]: string } = {
h1: "text-4xl font-semibold mt-6 mb-4 pb-2 border-b border-code-bg-2",
h2: "text-3xl font-semibold mt-6 mb-4 pb-2 border-b border-code-bg-2",
h3: "text-2xl font-semibold mt-6 mb-4",
h4: "text-xl font-semibold mt-6 mb-4",
h5: "text-lg font-semibold mt-6 mb-4",
h6: "text-base font-semibold mt-6 mb-4 text-gray-600"
}
return <HeadingTag className={headingClasses[HeadingTag]}>{t.text}</HeadingTag>
return <HeadingTag>
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} inPTag={true} codeURI={codeURI} {...options} />
</HeadingTag>
}
if (t.type === 'table') {
return (
<div className={`${noSpace ? '' : 'my-4'} overflow-x-auto`}>
<table className="min-w-full border border-code-bg-2">
<div>
<table>
<thead>
<tr className="bg-code-bg-1">
{t.header.map((cell: any, index: number) => (
<th
key={index}
className="px-4 py-2 border border-code-bg-2 font-semibold"
style={{ textAlign: t.align[index] || "left" }}
>
{cell.raw}
<tr>
{t.header.map((h, hIdx: number) => (
<th key={hIdx}>
{h.text}
</th>
))}
</tr>
</thead>
<tbody>
{t.rows.map((row: any[], rowIndex: number) => (
<tr key={rowIndex} className={rowIndex % 2 === 0 ? 'bg-white' : 'bg-code-bg-1'}>
{row.map((cell: any, cellIndex: number) => (
<td
key={cellIndex}
className="px-4 py-2 border border-code-bg-2"
style={{ textAlign: t.align[cellIndex] || "left" }}
>
{cell.raw}
{t.rows.map((row, rowIdx: number) => (
<tr key={rowIdx}>
{row.map((r, rIdx: number) => (
<td key={rIdx} >
{r.text}
</td>
))}
</tr>
@@ -346,12 +409,12 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
// )
}
if (t.type === "hr") {
return <hr className="my-6 border-t border-code-bg-2" />
if (t.type === 'hr') {
return <hr />
}
if (t.type === "blockquote") {
return <blockquote className={`pl-4 border-l-4 border-code-bg-2 italic ${noSpace ? '' : 'my-4'}`}>{t.text}</blockquote>
if (t.type === 'blockquote') {
return <blockquote>{t.text}</blockquote>
}
if (t.type === 'list_item') {
@@ -13,7 +13,7 @@ export const QuickEdit = (props: QuickEditPropsType) => {
const isDark = useIsDark()
return <div className={`@@code-scope ${isDark ? 'dark' : ''}`}>
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`}>
<ErrorBoundary>
<QuickEditChat {...props} />
</ErrorBoundary>
@@ -3,15 +3,14 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react';
import { useSettingsState, useSidebarState, useChatThreadsState, useQuickEditState, useAccessor } from '../util/services.js';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useSettingsState, useAccessor, useCtrlKZoneStreamingState } from '../util/services.js';
import { TextAreaFns, CodeInputBox2 } from '../util/inputs.js';
import { QuickEditPropsType } from '../../../quickEditActions.js';
import { ButtonStop, ButtonSubmit, IconX } from '../sidebar-tsx/SidebarChat.js';
import { ModelDropdown } from '../code-settings-tsx/ModelDropdown.js';
import { ButtonStop, ButtonSubmit, IconX, CodeChatArea } from '../sidebar-tsx/SidebarChat.js';
import { CODE_CTRL_K_ACTION_ID } from '../../../actionIDs.js';
import { useRefState } from '../util/helpers.js';
import { isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
import { isFeatureNameDisabled } from '../../../../../../../workbench/contrib/code/common/codeSettingsTypes.js';
@@ -96,108 +95,43 @@ export const QuickEditChat = ({
const chatAreaRef = useRef<HTMLDivElement | null>(null)
return <div ref={sizerRef} style={{ maxWidth: 450 }} className={`py-2 w-full`}>
<form
// copied from SidebarChat.tsx
className={`
flex flex-col gap-2 p-2 relative input text-left shrink-0
transition-all duration-200
rounded-md
bg-vscode-input-bg
border border-code-border-3 focus-within:border-code-border-1 hover:border-code-border-1
`}
onClick={(e) => {
textAreaRef.current?.focus()
}}
<CodeChatArea
featureName='Ctrl+K'
divRef={chatAreaRef}
onSubmit={onSubmit}
onAbort={onInterrupt}
onClose={onX}
isStreaming={isStreamingRef.current}
loadingIcon={loadingIcon}
isDisabled={isDisabled}
onClickAnywhere={() => { textAreaRef.current?.focus() }}
>
{/* // this div is used to position the input box properly */}
<div
className={`w-full z-[999] relative`}
>
<div className='flex flex-row items-center justify-between items-end gap-1'>
{/* input */}
<div // copied from SidebarChat.tsx
className={`w-full`}
>
{/* text input */}
<CodeInputBox2
className='px-1'
initValue={initText}
ref={useCallback((r: HTMLTextAreaElement | null) => {
textAreaRef.current = r
textAreaRef_(r)
// if presses the esc key, X
r?.addEventListener('keydown', (e) => {
if (e.key === 'Escape')
onX()
})
}, [textAreaRef_, onX])}
fnsRef={textAreaFnsRef}
placeholder={`Enter instructions...`}
// ${keybindingString} to select.
onChangeText={useCallback((newStr: string) => {
setInstructionsAreEmpty(!newStr)
onChangeText_(newStr)
}, [onChangeText_])}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
onSubmit(e)
return
}
}}
multiline={true}
/>
</div>
{/* X button */}
<div className='absolute -top-1 -right-1 cursor-pointer z-1'>
<IconX
size={12}
className="stroke-[2] opacity-80 text-code-fg-3 hover:brightness-95"
onClick={onX}
/>
</div>
</div>
{/* bottom row */}
<div
className='flex flex-row justify-between items-end gap-1'
>
{/* submit options */}
<div className='max-w-[150px]
@@[&_select]:!code-border-none
@@[&_select]:!code-outline-none'
>
<ModelDropdown featureName='Ctrl+K' />
</div>
{/* submit / stop button */}
{isStreaming ?
// stop button
<ButtonStop
onClick={onInterrupt}
/>
:
// submit button (up arrow)
<ButtonSubmit
onClick={onSubmit}
disabled={isDisabled}
/>
<CodeInputBox2
className='px-1'
initValue={initText}
ref={useCallback((r: HTMLTextAreaElement | null) => {
textAreaRef.current = r
textAreaRef_(r)
r?.addEventListener('keydown', (e) => {
if (e.key === 'Escape')
onX()
})
}, [textAreaRef_, onX])}
fnsRef={textAreaFnsRef}
placeholder="Enter instructions..."
onChangeText={useCallback((newStr: string) => {
setInstructionsAreEmpty(!newStr)
onChangeText_(newStr)
}, [onChangeText_])}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
onSubmit()
return
}
}}
multiline={true}
/>
</VoidChatArea>
</CodeChatArea>
</div>
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------*/
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
import { WarningBox } from '../code-settings-tsx/WarningBox.js';
interface Props {
children: ReactNode;
@@ -25,8 +25,7 @@ export const ErrorDisplay = ({
const details = errorDetails(fullError)
const isExpandable = !!details
const message = message_ === 'TypeError: fetch failed' ? 'TypeError: fetch failed. This likely means you specified the wrong endpoint in Code Settings.' : message_ + ''
const message = message_ + ''
return (
<div className={`rounded-lg border border-red-200 bg-red-50 p-4 overflow-auto`}>

Some files were not shown because too many files have changed in this diff Show More