From 390afaf22745236d4eed74c04cced65de613c8b4 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 6 May 2025 12:47:18 -0700 Subject: [PATCH 1/3] ignore extensions actually works --- .../void/browser/react/src/util/services.tsx | 2 + .../react/src/void-settings-tsx/Settings.tsx | 83 ++++++++++++------- .../contrib/void/common/prompt/prompts.ts | 2 +- 3 files changed, 57 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index 8b9ada0e..36fbdd98 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -49,6 +49,7 @@ import { IToolsService } from '../../../toolsService.js' import { IConvertToLLMMessageService } from '../../../convertToLLMMessageService.js' import { ITerminalService } from '../../../../../terminal/browser/terminal.js' import { ISearchService } from '../../../../../../services/search/common/search.js' +import { IExtensionManagementService } from '../../../../../../../platform/extensionManagement/common/extensionManagement.js' // normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes @@ -211,6 +212,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => { IToolsService: accessor.get(IToolsService), IConvertToLLMMessageService: accessor.get(IConvertToLLMMessageService), ITerminalService: accessor.get(ITerminalService), + IExtensionManagementService: accessor.get(IExtensionManagementService), } as const return reactAccessor diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 2771cae1..6df7dd4a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -607,7 +607,7 @@ const ProviderSetting = ({ providerName, settingName, subTextMd }: { providerNam console.log('Error: Provider setting had a non-string value.') return } - + // Create a stable callback reference using useCallback with proper dependencies const handleChangeValue = useCallback((newVal: string) => { voidSettingsService.setSettingOfProvider(providerName, settingName, newVal) @@ -835,7 +835,7 @@ const RedoOnboardingButton = ({ className }: { className?: string }) => { type TransferEditorType = 'VS Code' | 'Cursor' | 'Windsurf' // https://github.com/VSCodium/vscodium/blob/master/docs/index.md#migrating-from-visual-studio-code-to-vscodium // https://code.visualstudio.com/docs/editor/extension-marketplace#_where-are-extensions-installed -type TransferFilesInfo = { from: URI, to: URI }[] +type TransferFilesInfo = { from: URI, to: URI, isExtensions?: boolean }[] const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEditor: TransferEditorType = 'VS Code'): TransferFilesInfo => { if (os === null) throw new Error(`One-click switch is not possible in this environment.`) @@ -853,6 +853,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit }, { from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.vscode', 'extensions'), to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, }] } else if (fromEditor === 'Cursor') { return [{ @@ -864,6 +865,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit }, { from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.cursor', 'extensions'), to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, }] } else if (fromEditor === 'Windsurf') { return [{ @@ -875,6 +877,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit }, { from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.windsurf', 'extensions'), to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, }] } } @@ -893,6 +896,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit }, { from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.vscode', 'extensions'), to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, }] } else if (fromEditor === 'Cursor') { return [{ @@ -904,6 +908,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit }, { from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.cursor', 'extensions'), to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, }] } else if (fromEditor === 'Windsurf') { return [{ @@ -915,6 +920,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit }, { from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.windsurf', 'extensions'), to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, }] } } @@ -935,6 +941,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit }, { from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.vscode', 'extensions'), to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'), + isExtensions: true, }] } else if (fromEditor === 'Cursor') { return [{ @@ -946,6 +953,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit }, { from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.cursor', 'extensions'), to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'), + isExtensions: true, }] } else if (fromEditor === 'Windsurf') { return [{ @@ -957,6 +965,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit }, { from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.windsurf', 'extensions'), to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'), + isExtensions: true, }] } } @@ -1022,43 +1031,59 @@ export const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }: // Define extensions to skip when transferring const extensionBlacklist = [ // ignore extensions - 'ms-vscode-remote.remote-ssh', - 'ms-vscode-remote.remote-wsl', + 'ms-vscode-remote.remote', // ms-vscode-remote.remote-ssh, ms-vscode-remote.remote-wsl // ignore other AI copilots that could conflict with Void keybindings 'sourcegraph.cody-ai', 'continue.continue', 'codeium.codeium', 'saoudrizwan.claude-dev', // cline 'rooveterinaryinc.roo-cline', // roo + // 'github.copilot', ]; - for (const { from, to } of transferTheseFiles) { - console.log('Transferring...', from) - try { - // find a blacklisted item - const isBlacklisted = extensionBlacklist.find(blacklistItem => { - return from.fsPath?.includes(blacklistItem) - }) - if (isBlacklisted) { - console.log(`Skipping conflicting item (${isBlacklisted})`) - continue - } - - } catch { } - - console.log('transferring', from, to) + for (const { from, to, isExtensions } of transferTheseFiles) { // Check if the source file exists before attempting to copy try { - const exists = await fileService.exists(from) - if (exists) { - // Ensure the destination directory exists - const toParent = URI.joinPath(to, '..') - const toParentExists = await fileService.exists(toParent) - if (!toParentExists) { - await fileService.createFolder(toParent) + if (!isExtensions) { + console.log('transferring item', from, to) + + const exists = await fileService.exists(from) + if (exists) { + // Ensure the destination directory exists + const toParent = URI.joinPath(to, '..') + const toParentExists = await fileService.exists(toParent) + if (!toParentExists) { + await fileService.createFolder(toParent) + } + await fileService.copy(from, to, true) + } else { + console.log(`Skipping file that doesn't exist: ${from.toString()}`) } - await fileService.copy(from, to, true) - } else { - console.log(`Skipping file that doesn't exist: ${from.toString()}`) + } + // extensions folder + else { + console.log('transferring extensions...', from, to) + const exists = await fileService.exists(from) + if (exists) { + const stat = await fileService.resolve(from) + const toParent = URI.joinPath(to) // extensions/ + const toParentExists = await fileService.exists(toParent) + if (!toParentExists) { + await fileService.createFolder(toParent) + } + for (const extensionFolder of stat.children ?? []) { + if (extensionBlacklist.find(bItem => extensionFolder.resource.path.includes(bItem))) { + console.log('Skipping...', extensionFolder.resource.path) + continue + } + const from = extensionFolder.resource + const to = URI.joinPath(toParent, extensionFolder.name) + await fileService.copy(from, to, true) + } + // Ensure the destination directory exists + } else { + console.log(`Skipping file that doesn't exist: ${from.toString()}`) + } + console.log('done transferring extensions.') } } catch (e) { diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index bbc4e2c3..858e69c6 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -571,7 +571,7 @@ export const chat_userMessageContent = async (instructions: string, currSelns: S const dirStr: string = await opts.directoryStrService.getDirectoryStrTool(s.uri) const folderStructure = `${s.uri.fsPath} folder structure:${tripleTick[0]}\n${dirStr}\n${tripleTick[1]}` - const uris = await opts.directoryStrService.getAllURIsInDirectory(s.uri, { maxResults: 1_000 }) + const uris = await opts.directoryStrService.getAllURIsInDirectory(s.uri, { maxResults: 100 }) const strOfFiles = await Promise.all(uris.map(async uri => { const { val, truncated } = await readFile(opts.fileService, uri, 100_000) const truncationStr = truncated ? `\n... file truncated ...` : '' From b03cbadb9b2c8b7141de5690e0ad5e245c3ab808 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 6 May 2025 13:16:57 -0700 Subject: [PATCH 2/3] auto delete laggy extensions --- .../void/browser/extensionTransferService.ts | 288 ++++++++++++++++++ .../void/browser/extensionTransferTypes.ts | 11 + .../void/browser/miscWokrbenchContrib.ts | 29 ++ .../void/browser/react/src/util/services.tsx | 2 + .../react/src/void-settings-tsx/Settings.tsx | 220 +------------ .../contrib/void/browser/void.contribution.ts | 3 + 6 files changed, 336 insertions(+), 217 deletions(-) create mode 100644 src/vs/workbench/contrib/void/browser/extensionTransferService.ts create mode 100644 src/vs/workbench/contrib/void/browser/extensionTransferTypes.ts create mode 100644 src/vs/workbench/contrib/void/browser/miscWokrbenchContrib.ts diff --git a/src/vs/workbench/contrib/void/browser/extensionTransferService.ts b/src/vs/workbench/contrib/void/browser/extensionTransferService.ts new file mode 100644 index 00000000..b236dab7 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/extensionTransferService.ts @@ -0,0 +1,288 @@ +/*-------------------------------------------------------------------------------------- + * 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 { env } from '../../../../base/common/process.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { TransferEditorType, TransferFilesInfo } from './extensionTransferTypes.js'; + + +export interface IExtensionTransferService { + readonly _serviceBrand: undefined; // services need this, just leave it undefined + transferExtensions(os: 'mac' | 'windows' | 'linux' | null, fromEditor: TransferEditorType): Promise + deleteBlacklistExtensions(os: 'mac' | 'windows' | 'linux' | null): Promise + +} + +export const IExtensionTransferService = createDecorator('ExtensionTransferService'); + + + + + +// Define extensions to skip when transferring +const extensionBlacklist = [ + // ignore extensions + 'ms-vscode-remote.remote', // ms-vscode-remote.remote-ssh, ms-vscode-remote.remote-wsl + // ignore other AI copilots that could conflict with Void keybindings + 'sourcegraph.cody-ai', + 'continue.continue', + 'codeium.codeium', + 'saoudrizwan.claude-dev', // cline + 'rooveterinaryinc.roo-cline', // roo + // 'github.copilot', +]; + + + +class ExtensionTransferService extends Disposable implements IExtensionTransferService { + _serviceBrand: undefined; + + constructor( + @IFileService private readonly _fileService: IFileService, + ) { + super() + } + + async transferExtensions(os: 'mac' | 'windows' | 'linux' | null, fromEditor: TransferEditorType) { + const transferTheseFiles = transferTheseFilesOfOS(os, fromEditor) + const fileService = this._fileService + + let errAcc = '' + + for (const { from, to, isExtensions } of transferTheseFiles) { + // Check if the source file exists before attempting to copy + try { + if (!isExtensions) { + console.log('transferring item', from, to) + + const exists = await fileService.exists(from) + if (exists) { + // Ensure the destination directory exists + const toParent = URI.joinPath(to, '..') + const toParentExists = await fileService.exists(toParent) + if (!toParentExists) { + await fileService.createFolder(toParent) + } + await fileService.copy(from, to, true) + } else { + console.log(`Skipping file that doesn't exist: ${from.toString()}`) + } + } + // extensions folder + else { + console.log('transferring extensions...', from, to) + const exists = await fileService.exists(from) + if (exists) { + const stat = await fileService.resolve(from) + const toParent = URI.joinPath(to) // extensions/ + const toParentExists = await fileService.exists(toParent) + if (!toParentExists) { + await fileService.createFolder(toParent) + } + for (const extensionFolder of stat.children ?? []) { + if (extensionBlacklist.find(bItem => extensionFolder.resource.path.includes(bItem))) { + console.log('Skipping...', extensionFolder.resource.path) + continue + } + const from = extensionFolder.resource + const to = URI.joinPath(toParent, extensionFolder.name) + await fileService.copy(from, to, true) + } + // Ensure the destination directory exists + } else { + console.log(`Skipping file that doesn't exist: ${from.toString()}`) + } + console.log('done transferring extensions.') + } + } + catch (e) { + console.error('Error copying file:', e) + errAcc += `Error copying ${from.toString()}: ${e}\n` + } + } + + if (errAcc) return errAcc + return undefined + } + + async deleteBlacklistExtensions(os: 'mac' | 'windows' | 'linux' | null) { + const extensionsURI = getExtensionsFolder(os) + if (!extensionsURI) return + const eURI = await this._fileService.resolve(extensionsURI) + for (const child of eURI.children ?? []) { + + // if is not blacklisted, continue + if (!extensionBlacklist.find(bItem => child.resource.path.includes(bItem))) { + continue + } + + try { + console.log('Deleting extension', child.resource.fsPath) + this._fileService.del(child.resource) + } + catch (e) { + console.error('Could not delete extension', child.resource.fsPath, e) + } + } + } +} + + +registerSingleton(IExtensionTransferService, ExtensionTransferService, InstantiationType.Eager); // lazily loaded, even if Eager + + + + + + + + + +const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEditor: TransferEditorType = 'VS Code'): TransferFilesInfo => { + if (os === null) + throw new Error(`One-click switch is not possible in this environment.`) + if (os === 'mac') { + const homeDir = env['HOME'] + if (!homeDir) throw new Error(`$HOME not found`) + + 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'), + }, { + 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'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.vscode', 'extensions'), + to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, + }] + } 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'), + }, { + 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'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.cursor', 'extensions'), + to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, + }] + } 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'), + }, { + 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'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.windsurf', 'extensions'), + to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, + }] + } + } + + if (os === 'linux') { + const homeDir = env['HOME'] + if (!homeDir) throw new Error(`variable for $HOME location not found`) + + 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'), + }, { + 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'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.vscode', 'extensions'), + to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, + }] + } 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'), + }, { + 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'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.cursor', 'extensions'), + to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, + }] + } 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'), + }, { + 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'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.windsurf', 'extensions'), + to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), + isExtensions: true, + }] + } + } + + if (os === 'windows') { + const appdata = env['APPDATA'] + if (!appdata) throw new Error(`variable for %APPDATA% location not found`) + const userprofile = env['USERPROFILE'] + if (!userprofile) throw new Error(`variable for %USERPROFILE% location not found`) + + 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'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'keybindings.json'), + to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'keybindings.json'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.vscode', 'extensions'), + to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'), + isExtensions: true, + }] + } 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'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Cursor', 'User', 'keybindings.json'), + to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'keybindings.json'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.cursor', 'extensions'), + to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'), + isExtensions: true, + }] + } 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'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Windsurf', 'User', 'keybindings.json'), + to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'keybindings.json'), + }, { + from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.windsurf', 'extensions'), + to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'), + isExtensions: true, + }] + } + } + + throw new Error(`os '${os}' not recognized or editor type '${fromEditor}' not supported for this OS`) +} + + +const getExtensionsFolder = (os: 'mac' | 'windows' | 'linux' | null) => { + const t = transferTheseFilesOfOS(os, 'VS Code') // from editor doesnt matter + return t.find(f => f.isExtensions)?.to +} diff --git a/src/vs/workbench/contrib/void/browser/extensionTransferTypes.ts b/src/vs/workbench/contrib/void/browser/extensionTransferTypes.ts new file mode 100644 index 00000000..4e10707c --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/extensionTransferTypes.ts @@ -0,0 +1,11 @@ +/*-------------------------------------------------------------------------------------- + * 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' + +export type TransferEditorType = 'VS Code' | 'Cursor' | 'Windsurf' +// https://github.com/VSCodium/vscodium/blob/master/docs/index.md#migrating-from-visual-studio-code-to-vscodium +// https://code.visualstudio.com/docs/editor/extension-marketplace#_where-are-extensions-installed +export type TransferFilesInfo = { from: URI, to: URI, isExtensions?: boolean }[] diff --git a/src/vs/workbench/contrib/void/browser/miscWokrbenchContrib.ts b/src/vs/workbench/contrib/void/browser/miscWokrbenchContrib.ts new file mode 100644 index 00000000..bc04b733 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/miscWokrbenchContrib.ts @@ -0,0 +1,29 @@ +/*-------------------------------------------------------------------------------------- + * 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 { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { IExtensionTransferService } from './extensionTransferService.js'; +import { os } from '../common/helpers/systemInfo.js'; + +// Onboarding contribution that mounts the component at startup +export class MiscWorkbenchContribs extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.voidMiscWorkbenchContribs'; + + constructor( + @IExtensionTransferService private readonly extensionTransferService: IExtensionTransferService, + ) { + super(); + this.initialize(); + } + + private initialize(): void { + // delete blacklisted extensions + this.extensionTransferService.deleteBlacklistExtensions(os) + + } +} + +registerWorkbenchContribution2(MiscWorkbenchContribs.ID, MiscWorkbenchContribs, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index 36fbdd98..82f47954 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -21,6 +21,7 @@ import { IThemeService } from '../../../../../../../platform/theme/common/themeS import { ILLMMessageService } from '../../../../common/sendLLMMessageService.js'; import { IRefreshModelService } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js'; import { IVoidSettingsService } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'; +import { IExtensionTransferService } from '../../../../../../../workbench/contrib/void/browser/extensionTransferService.js' import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js' import { ICodeEditorService } from '../../../../../../../editor/browser/services/codeEditorService.js' @@ -213,6 +214,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => { IConvertToLLMMessageService: accessor.get(IConvertToLLMMessageService), ITerminalService: accessor.get(ITerminalService), IExtensionManagementService: accessor.get(IExtensionManagementService), + IExtensionTransferService: accessor.get(IExtensionTransferService), } as const return reactAccessor diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 6df7dd4a..328f9fcd 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -10,7 +10,6 @@ import { VoidButtonBgDarken, VoidCustomDropdownBox, VoidInputBox2, VoidSimpleInp import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js' import { X, RefreshCw, Loader2, Check, Asterisk, Plus } from 'lucide-react' import { URI } from '../../../../../../../base/common/uri.js' -import { env } from '../../../../../../../base/common/process.js' import { ModelDropdown } from './ModelDropdown.js' import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js' import { WarningBox } from './WarningBox.js' @@ -19,6 +18,7 @@ import { IconLoading } from '../sidebar-tsx/SidebarChat.js' import { ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js' import Severity from '../../../../../../../base/common/severity.js' import { getModelCapabilities, ModelOverrides } from '../../../../common/modelCapabilities.js'; +import { TransferEditorType, TransferFilesInfo } from '../../../extensionTransferTypes.js'; const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => { @@ -832,146 +832,7 @@ const RedoOnboardingButton = ({ className }: { className?: string }) => { } -type TransferEditorType = 'VS Code' | 'Cursor' | 'Windsurf' -// https://github.com/VSCodium/vscodium/blob/master/docs/index.md#migrating-from-visual-studio-code-to-vscodium -// https://code.visualstudio.com/docs/editor/extension-marketplace#_where-are-extensions-installed -type TransferFilesInfo = { from: URI, to: URI, isExtensions?: boolean }[] -const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEditor: TransferEditorType = 'VS Code'): TransferFilesInfo => { - if (os === null) - throw new Error(`One-click switch is not possible in this environment.`) - if (os === 'mac') { - const homeDir = env['HOME'] - if (!homeDir) throw new Error(`$HOME not found`) - 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'), - }, { - 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'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.vscode', 'extensions'), - to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), - isExtensions: true, - }] - } 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'), - }, { - 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'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.cursor', 'extensions'), - to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), - isExtensions: true, - }] - } 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'), - }, { - 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'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.windsurf', 'extensions'), - to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), - isExtensions: true, - }] - } - } - - if (os === 'linux') { - const homeDir = env['HOME'] - if (!homeDir) throw new Error(`variable for $HOME location not found`) - - 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'), - }, { - 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'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.vscode', 'extensions'), - to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), - isExtensions: true, - }] - } 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'), - }, { - 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'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.cursor', 'extensions'), - to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), - isExtensions: true, - }] - } 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'), - }, { - 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'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.windsurf', 'extensions'), - to: URI.joinPath(URI.from({ scheme: 'file' }), homeDir, '.void-editor', 'extensions'), - isExtensions: true, - }] - } - } - - if (os === 'windows') { - const appdata = env['APPDATA'] - if (!appdata) throw new Error(`variable for %APPDATA% location not found`) - const userprofile = env['USERPROFILE'] - if (!userprofile) throw new Error(`variable for %USERPROFILE% location not found`) - - 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'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Code', 'User', 'keybindings.json'), - to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'keybindings.json'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.vscode', 'extensions'), - to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'), - isExtensions: true, - }] - } 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'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Cursor', 'User', 'keybindings.json'), - to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'keybindings.json'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.cursor', 'extensions'), - to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'), - isExtensions: true, - }] - } 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'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Windsurf', 'User', 'keybindings.json'), - to: URI.joinPath(URI.from({ scheme: 'file' }), appdata, 'Void', 'User', 'keybindings.json'), - }, { - from: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.windsurf', 'extensions'), - to: URI.joinPath(URI.from({ scheme: 'file' }), userprofile, '.void-editor', 'extensions'), - isExtensions: true, - }] - } - } - - throw new Error(`os '${os}' not recognized or editor type '${fromEditor}' not supported for this OS`) -} @@ -1004,93 +865,18 @@ export const ToolApprovalTypeSwitch = ({ approvalType, size, desc }: { approvalT export const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }: { fromEditor?: TransferEditorType, className?: string }) => { const accessor = useAccessor() - const fileService = accessor.get('IFileService') + const extensionTransferService = accessor.get('IExtensionTransferService') const [transferState, setTransferState] = useState<{ type: 'done', error?: string } | { type: | 'loading' | 'justfinished' }>({ type: 'done' }) - let transferTheseFiles: TransferFilesInfo = []; - let editorError: string | null = null; - try { - transferTheseFiles = transferTheseFilesOfOS(os, fromEditor) - } catch (e) { - editorError = e + '' - } - - if (transferTheseFiles.length === 0) - return <> - - const onClick = async () => { if (transferState.type !== 'done') return setTransferState({ type: 'loading' }) - let errAcc = '' - // Define extensions to skip when transferring - const extensionBlacklist = [ - // ignore extensions - 'ms-vscode-remote.remote', // ms-vscode-remote.remote-ssh, ms-vscode-remote.remote-wsl - // ignore other AI copilots that could conflict with Void keybindings - 'sourcegraph.cody-ai', - 'continue.continue', - 'codeium.codeium', - 'saoudrizwan.claude-dev', // cline - 'rooveterinaryinc.roo-cline', // roo - // 'github.copilot', - ]; - for (const { from, to, isExtensions } of transferTheseFiles) { - // Check if the source file exists before attempting to copy - try { - if (!isExtensions) { - console.log('transferring item', from, to) - - const exists = await fileService.exists(from) - if (exists) { - // Ensure the destination directory exists - const toParent = URI.joinPath(to, '..') - const toParentExists = await fileService.exists(toParent) - if (!toParentExists) { - await fileService.createFolder(toParent) - } - await fileService.copy(from, to, true) - } else { - console.log(`Skipping file that doesn't exist: ${from.toString()}`) - } - } - // extensions folder - else { - console.log('transferring extensions...', from, to) - const exists = await fileService.exists(from) - if (exists) { - const stat = await fileService.resolve(from) - const toParent = URI.joinPath(to) // extensions/ - const toParentExists = await fileService.exists(toParent) - if (!toParentExists) { - await fileService.createFolder(toParent) - } - for (const extensionFolder of stat.children ?? []) { - if (extensionBlacklist.find(bItem => extensionFolder.resource.path.includes(bItem))) { - console.log('Skipping...', extensionFolder.resource.path) - continue - } - const from = extensionFolder.resource - const to = URI.joinPath(toParent, extensionFolder.name) - await fileService.copy(from, to, true) - } - // Ensure the destination directory exists - } else { - console.log(`Skipping file that doesn't exist: ${from.toString()}`) - } - console.log('done transferring extensions.') - } - } - catch (e) { - console.error('Error copying file:', e) - errAcc += `Error copying ${from.toString()}: ${e}\n` - } - } + const errAcc = await extensionTransferService.transferExtensions(os, fromEditor) // Even if some files were missing, consider it a success if no actual errors occurred const hadError = !!errAcc diff --git a/src/vs/workbench/contrib/void/browser/void.contribution.ts b/src/vs/workbench/contrib/void/browser/void.contribution.ts index 6d46b6eb..c0e84606 100644 --- a/src/vs/workbench/contrib/void/browser/void.contribution.ts +++ b/src/vs/workbench/contrib/void/browser/void.contribution.ts @@ -55,6 +55,9 @@ import './tooltipService.js' // register onboarding service import './voidOnboardingService.js' +// register misc service +import './miscWokrbenchContrib.js' + // ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ---------- // llmMessage From 262f3aee12f8b7669cdbd163bf310de08b76b020 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 6 May 2025 13:26:36 -0700 Subject: [PATCH 3/3] update gemini models + fix del extension --- .../void/browser/extensionTransferService.ts | 2 +- .../contrib/void/common/modelCapabilities.ts | 23 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/extensionTransferService.ts b/src/vs/workbench/contrib/void/browser/extensionTransferService.ts index b236dab7..08152c2d 100644 --- a/src/vs/workbench/contrib/void/browser/extensionTransferService.ts +++ b/src/vs/workbench/contrib/void/browser/extensionTransferService.ts @@ -124,7 +124,7 @@ class ExtensionTransferService extends Disposable implements IExtensionTransferS try { console.log('Deleting extension', child.resource.fsPath) - this._fileService.del(child.resource) + await this._fileService.del(child.resource, { recursive: true, useTrash: true }) } catch (e) { console.error('Could not delete extension', child.resource.fsPath, e) diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index 0012f9f8..efe721ba 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -88,8 +88,9 @@ export const defaultModelsOfProvider = { 'grok-3-latest', ], gemini: [ // https://ai.google.dev/gemini-api/docs/models/gemini - 'gemini-2.5-pro-exp-03-25', + 'gemini-2.5-pro-preview-05-06', 'gemini-2.5-flash-preview-04-17', + 'gemini-2.0-flash', 'gemini-2.0-flash-lite', ], deepseek: [ // https://api-docs.deepseek.com/quick_start/pricing @@ -713,6 +714,26 @@ const xAISettings: VoidStaticProviderInfo = { // ---------------- GEMINI ---------------- const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing + 'gemini-2.5-pro-preview-05-06': { + contextWindow: 1_048_576, + reservedOutputTokenSpace: 8_192, + cost: { input: 0, output: 0 }, + downloadable: false, + supportsFIM: false, + supportsSystemMessage: 'separated', + specialToolFormat: 'gemini-style', + reasoningCapabilities: false, + }, + 'gemini-2.0-flash-lite': { + contextWindow: 1_048_576, + reservedOutputTokenSpace: 8_192, + cost: { input: 0, output: 0 }, + downloadable: false, + supportsFIM: false, + supportsSystemMessage: 'separated', + specialToolFormat: 'gemini-style', + reasoningCapabilities: false, + }, 'gemini-2.5-flash-preview-04-17': { contextWindow: 1_048_576, reservedOutputTokenSpace: 8_192,