diff --git a/cli/src/bin/code/legacy_args.rs b/cli/src/bin/code/legacy_args.rs index 5a8e0f01071..0bd92c92fd3 100644 --- a/cli/src/bin/code/legacy_args.rs +++ b/cli/src/bin/code/legacy_args.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use cli::commands::args::{ CliCore, Commands, DesktopCodeOptions, ExtensionArgs, ExtensionSubcommand, - InstallExtensionArgs, ListExtensionArgs, UninstallExtensionArgs, DownloadExtensionArgs, + InstallExtensionArgs, ListExtensionArgs, UninstallExtensionArgs, }; /// Tries to parse the argv using the legacy CLI interface, looking for its @@ -64,7 +64,6 @@ pub fn try_parse_legacy( // Now translate them to subcommands. // --list-extensions -> ext list // --update-extensions -> update - // --download-extension -> ext download // --install-extension=id -> ext install // --uninstall-extension=id -> ext uninstall // --status -> status @@ -80,17 +79,6 @@ pub fn try_parse_legacy( })), ..Default::default() }) - } else if let Some(exts) = args.get("download-extension") { - Some(CliCore { - subcommand: Some(Commands::Extension(ExtensionArgs { - subcommand: ExtensionSubcommand::Download(DownloadExtensionArgs { - id: exts.to_vec(), - location: get_first_arg_value("location"), - }), - desktop_code_options, - })), - ..Default::default() - }) } else if let Some(exts) = args.remove("install-extension") { Some(CliCore { subcommand: Some(Commands::Extension(ExtensionArgs { diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index 3f4dfd9b7a4..21efc1c6667 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -272,8 +272,6 @@ pub enum ExtensionSubcommand { Uninstall(UninstallExtensionArgs), /// Update the installed extensions. Update, - /// Download an extension. - Download(DownloadExtensionArgs), } impl ExtensionSubcommand { @@ -307,16 +305,6 @@ impl ExtensionSubcommand { ExtensionSubcommand::Update => { target.push("--update-extensions".to_string()); } - ExtensionSubcommand::Download(args) => { - for id in args.id.iter() { - target.push(format!("--download-extension={id}")); - } - if let Some(location) = &args.location { - if !location.is_empty() { - target.push(format!("--location={location}")); - } - } - } } } } @@ -359,21 +347,6 @@ pub struct UninstallExtensionArgs { pub id: Vec, } -#[derive(Args, Debug, Clone)] -pub struct DownloadExtensionArgs { - /// Id of the extension to download. The identifier of an - /// extension is '${publisher}.${name}'. Should provide '--location' to specify the location to download the VSIX. - /// To download a specific version provide '@${version}'. - /// For example: 'vscode.csharp@1.2.3'. - #[clap(name = "ext-id")] - pub id: Vec, - - /// Specify the location to download the VSIX. - #[clap(long, value_name = "location")] - pub location: Option, - -} - #[derive(Args, Debug, Clone)] pub struct VersionArgs { #[clap(subcommand)] diff --git a/resources/completions/bash/code b/resources/completions/bash/code index 92b1b4cfcfb..d141c297b96 100644 --- a/resources/completions/bash/code +++ b/resources/completions/bash/code @@ -49,7 +49,7 @@ _@@APPNAME@@() --list-extensions --show-versions --install-extension --uninstall-extension --enable-proposed-api --verbose --log -s --status -p --performance --prof-startup --disable-extensions - --disable-extension --inspect-extensions --update-extensions --download-extension + --disable-extension --inspect-extensions --update-extensions --inspect-brk-extensions --disable-gpu' -- "$cur") ) [[ $COMPREPLY == *= ]] && compopt -o nospace return diff --git a/resources/completions/zsh/_code b/resources/completions/zsh/_code index f49211447ba..eafa37c81c7 100644 --- a/resources/completions/zsh/_code +++ b/resources/completions/zsh/_code @@ -20,9 +20,8 @@ arguments=( '--category[filters installed extension list by category, when using --list-extensions]' '--show-versions[show versions of installed extensions, when using --list-extensions]' '--install-extension[install an extension]:id or path:_files -g "*.vsix(-.)"' - '--uninstall-extension[uninstall an extension]:id' + '--uninstall-extension[uninstall an extension]:id or path:_files -g "*.vsix(-.)"' '--update-extensions[update the installed extensions]' - '--download-extension[download an extension]:id' '--enable-proposed-api[enables proposed API features for extensions]::extension id: ' '--verbose[print verbose output (implies --wait)]' '--log[log level to use]:level [info]:(critical error warn info debug trace off)' diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 39033f27227..e91bea07d6a 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -33,7 +33,6 @@ function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { return !!argv['install-source'] || !!argv['list-extensions'] || !!argv['install-extension'] - || !!argv['download-extension'] || !!argv['uninstall-extension'] || !!argv['update-extensions'] || !!argv['locate-extension'] diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index bbca94ab07c..d3b20ecea47 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -282,14 +282,6 @@ class CliMain extends Disposable { return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).listExtensions(!!this.argv['show-versions'], this.argv['category'], profileLocation); } - // Download Extensions - else if (this.argv['download-extension']) { - if (!this.argv['location']) { - throw new Error('The location argument is required to download an extension.'); - } - return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).downloadExtensions(this.argv['download-extension'], URI.parse(this.argv['location'])); - } - // Install Extension else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) { const installOptions: InstallOptions = { isMachineScoped: !!this.argv['do-not-sync'], installPreReleaseVersion: !!this.argv['pre-release'], profileLocation }; diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index f2ad98bdd17..818021ff0c1 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -75,8 +75,6 @@ export interface NativeParsedArgs { 'disable-extensions'?: boolean; 'disable-extension'?: string[]; // undefined or array of 1 or more 'list-extensions'?: boolean; - 'download-extension'?: string[]; - 'location'?: string; 'show-versions'?: boolean; 'category'?: string; 'install-extension'?: string[]; // undefined or array of 1 or more diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 48809d5e894..379e327c646 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -97,7 +97,6 @@ export const OPTIONS: OptionDescriptions> = { 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extensions.") }, 'category': { type: 'string', allowEmptyValue: true, cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extensions."), args: 'category' }, - 'download-extension': { type: 'string[]', cat: 'e', args: 'ext-id', description: localize('downloadExtension', "Downloads the extension VSIX that can be installable. The argument is an identifier of an extension that is '${publisher}.${name}'. To download a specific version provide '@${version}'. For example: 'vscode.csharp@1.2.3'. Should provide '--location' to specify the location to download the VSIX.") }, 'install-extension': { type: 'string[]', cat: 'e', args: 'ext-id | path', description: localize('installExtension', "Installs or updates an extension. The argument is either an extension id or a path to a VSIX. The identifier of an extension is '${publisher}.${name}'. Use '--force' argument to update to latest version. To install a specific version provide '@${version}'. For example: 'vscode.csharp@1.2.3'.") }, 'pre-release': { type: 'boolean', cat: 'e', description: localize('install prerelease', "Installs the pre-release version of the extension, when using --install-extension") }, 'uninstall-extension': { type: 'string[]', cat: 'e', args: 'ext-id', description: localize('uninstallExtension', "Uninstalls an extension.") }, @@ -164,7 +163,6 @@ export const OPTIONS: OptionDescriptions> = { 'file-chmod': { type: 'boolean' }, 'install-builtin-extension': { type: 'string[]' }, 'force': { type: 'boolean' }, - 'location': { type: 'string' }, 'do-not-sync': { type: 'boolean' }, 'trace': { type: 'boolean' }, 'trace-category-filter': { type: 'string' }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 36a9901341a..69ba77c8c84 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -14,7 +14,6 @@ import { EXTENSION_IDENTIFIER_REGEX, IExtensionGalleryService, IExtensionInfo, I import { areSameExtensions, getExtensionId, getGalleryExtensionId, getIdAndVersion } from './extensionManagementUtil.js'; import { ExtensionType, EXTENSION_CATEGORIES, IExtensionManifest } from '../../extensions/common/extensions.js'; import { ILogger } from '../../log/common/log.js'; -import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); @@ -29,7 +28,6 @@ export class ExtensionManagementCLI { protected readonly logger: ILogger, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, ) { } protected get location(): string | undefined { @@ -72,42 +70,6 @@ export class ExtensionManagementCLI { } } - public async downloadExtensions(extensions: string[], target: URI): Promise { - if (!extensions.length) { - return; - } - - this.logger.info(localize('downloadingExtensions', "Downloading extensions...")); - - const extensionsInfo: IExtensionInfo[] = []; - for (const extension of extensions) { - const [id, version] = getIdAndVersion(extension); - extensionsInfo.push({ id, version: version !== 'prerelease' ? version : undefined, preRelease: version === 'prerelease' }); - } - - try { - const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsInfo, CancellationToken.None); - const targetPlatform = await this.extensionManagementService.getTargetPlatform(); - await Promise.allSettled(extensionsInfo.map(async extensionInfo => { - const galleryExtension = galleryExtensions.find(e => areSameExtensions(e.identifier, { id: extensionInfo.id })); - if (!galleryExtension) { - this.logger.error(`${notFound(extensionInfo.id)}\n${useId}`); - return; - } - const compatible = await this.extensionGalleryService.getCompatibleExtension(galleryExtension, !!extensionInfo.hasPreRelease, targetPlatform); - try { - await this.extensionGalleryService.download(compatible ?? galleryExtension, this.uriIdentityService.extUri.joinPath(target, `${galleryExtension.identifier.id}-${galleryExtension.version}.vsix`), InstallOperation.None); - this.logger.info(localize('successDownload', "Extension '{0}' was successfully downloaded.", extensionInfo.id)); - } catch (error) { - this.logger.error(localize('error while downloading extension', "Error while downloading extension '{0}': {1}", extensionInfo.id, getErrorMessage(error))); - } - })); - } catch (error) { - this.logger.error(localize('error while downloading extensions', "Error while downloading extensions: {0}", getErrorMessage(error))); - throw error; - } - } - public async installExtensions(extensions: (string | URI)[], builtinExtensions: (string | URI)[], installOptions: InstallOptions, force: boolean): Promise { const failed: string[] = []; diff --git a/src/vs/workbench/api/browser/mainThreadCLICommands.ts b/src/vs/workbench/api/browser/mainThreadCLICommands.ts index eaf95ea867c..be73afd9fd4 100644 --- a/src/vs/workbench/api/browser/mainThreadCLICommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCLICommands.ts @@ -18,7 +18,6 @@ import { ServiceCollection } from '../../../platform/instantiation/common/servic import { ILabelService } from '../../../platform/label/common/label.js'; import { AbstractMessageLogger, ILogger, LogLevel } from '../../../platform/log/common/log.js'; import { IOpenerService } from '../../../platform/opener/common/opener.js'; -import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js'; import { IOpenWindowOptions, IWindowOpenable } from '../../../platform/window/common/window.js'; import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js'; import { IExtensionManagementServerService } from '../../services/extensionManagement/common/extensionManagement.js'; @@ -104,12 +103,11 @@ class RemoteExtensionManagementCLI extends ExtensionManagementCLI { logger: ILogger, @IExtensionManagementService extensionManagementService: IExtensionManagementService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, - @IUriIdentityService uriIdentityService: IUriIdentityService, @ILabelService labelService: ILabelService, @IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService, @IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super(logger, extensionManagementService, extensionGalleryService, uriIdentityService); + super(logger, extensionManagementService, extensionGalleryService); const remoteAuthority = envService.remoteAuthority; this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 3addd18766f..4db7fda24f2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -8,7 +8,7 @@ import { KeyMod, KeyCode } from '../../../../base/common/keyCodes.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { MenuRegistry, MenuId, registerAction2, Action2, IMenuItem, IAction2Options } from '../../../../platform/actions/common/actions.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { ExtensionsLocalizedLabel, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, EXTENSION_INSTALL_SOURCE_CONTEXT, ExtensionInstallSource, UseUnpkgResourceApi } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { ExtensionsLocalizedLabel, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, EXTENSION_INSTALL_SOURCE_CONTEXT, ExtensionInstallSource, UseUnpkgResourceApi, InstallOperation } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService, extensionsConfigurationNodeBase } from '../../../services/extensionManagement/common/extensionManagement.js'; import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js'; @@ -77,6 +77,7 @@ import { CONTEXT_KEYBINDINGS_EDITOR } from '../../preferences/common/preferences import { ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { IConfigurationMigrationRegistry, Extensions as ConfigurationMigrationExtensions } from '../../../common/configuration.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */); @@ -491,7 +492,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IContextKeyService contextKeyService: IContextKeyService, @IViewsService private readonly viewsService: IViewsService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -499,6 +500,10 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IProductService private readonly productService: IProductService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @INotificationService private readonly notificationService: INotificationService, ) { super(); const hasGalleryContext = CONTEXT_HAS_GALLERY.bindTo(contextKeyService); @@ -1582,6 +1587,53 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi run: async (accessor: ServicesAccessor, id: string) => accessor.get(IPreferencesService).openSettings({ jsonEditor: false, query: `@ext:${id}` }) }); + const downloadVSIX = async (extensionId: string, preRelease: boolean) => { + const result = await this.fileDialogService.showOpenDialog({ + title: localize('download title', "Select folder to download the VSIX"), + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + openLabel: localize('download', "Download"), + }); + + if (!result?.[0]) { + return; + } + + const [galleryExtension] = await this.extensionGalleryService.getExtensions([{ id: extensionId, preRelease: true }], { compatible: true }, CancellationToken.None); + if (!galleryExtension) { + throw new Error(localize('not found', "Extension '{0}' not found.", extensionId)); + } + await this.extensionGalleryService.download(galleryExtension, this.uriIdentityService.extUri.joinPath(result[0], `${galleryExtension.identifier.id}-${galleryExtension.version}.vsix`), InstallOperation.None); + this.notificationService.info(localize('download.completed', "Successfully downloaded the VSIX")); + }; + + this.registerExtensionAction({ + id: 'workbench.extensions.action.download', + title: localize('download VSIX', "Download VSIX"), + menu: { + id: MenuId.ExtensionContext, + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension')), + order: this.productService.quality === 'stable' ? 0 : 1 + }, + run: async (accessor: ServicesAccessor, extensionId: string) => { + downloadVSIX(extensionId, false); + } + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.downloadPreRelease', + title: localize('download pre-release', "Download Pre-Release VSIX"), + menu: { + id: MenuId.ExtensionContext, + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('extensionHasPreReleaseVersion')), + order: this.productService.quality === 'stable' ? 1 : 0 + }, + run: async (accessor: ServicesAccessor, extensionId: string) => { + downloadVSIX(extensionId, true); + } + }); + this.registerExtensionAction({ id: 'workbench.extensions.action.manageAccountPreferences', title: localize2('workbench.extensions.action.changeAccountPreference', "Account Preferences"),