From b03cbadb9b2c8b7141de5690e0ad5e245c3ab808 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 6 May 2025 13:16:57 -0700 Subject: [PATCH] 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