From 5dfc23e69790348b7ada3ae47d6c0bc1c81f7683 Mon Sep 17 00:00:00 2001 From: Dennis Huebner Date: Fri, 20 Sep 2024 15:31:53 +0200 Subject: [PATCH] Support version for installExtension, support uninstall cmd (#13795, #13796) --- .../plugin-vscode-commands-contribution.ts | 109 +++++++++++------- .../src/common/plugin-vscode-uri.ts | 3 +- .../src/common/plugin-identifiers.ts | 27 +++++ .../src/browser/vsx-extension.tsx | 7 +- 4 files changed, 101 insertions(+), 45 deletions(-) diff --git a/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts b/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts index ce02e28545981..c7bcc09ad82cc 100755 --- a/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts +++ b/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts @@ -14,12 +14,14 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { Command, CommandContribution, CommandRegistry, environment, isOSX, CancellationTokenSource, MessageService } from '@theia/core'; +import { CallHierarchyService, CallHierarchyServiceProvider } from '@theia/callhierarchy/lib/browser'; +import { CancellationTokenSource, Command, CommandContribution, CommandRegistry, MessageService, environment, isOSX } from '@theia/core'; import { ApplicationShell, CommonCommands, NavigatableWidget, - OpenerService, OpenHandler, + OpenHandler, + OpenerService, QuickInputService, Saveable, TabBar, @@ -28,60 +30,59 @@ import { } from '@theia/core/lib/browser'; import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; import { ApplicationShellMouseTracker } from '@theia/core/lib/browser/shell/application-shell-mouse-tracker'; +import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection'; +import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { CommandService } from '@theia/core/lib/common/command'; +import { nls } from '@theia/core/lib/common/nls'; import TheiaURI from '@theia/core/lib/common/uri'; -import { EditorManager, EditorCommands } from '@theia/editor/lib/browser'; +import { inject, injectable, optional } from '@theia/core/shared/inversify'; +import { URI } from '@theia/core/shared/vscode-uri'; +import { EditorCommands, EditorManager } from '@theia/editor/lib/browser'; +import { FileService } from '@theia/filesystem/lib/browser/file-service'; +import * as monaco from '@theia/monaco-editor-core'; +import { MonacoLanguages } from '@theia/monaco/lib/browser/monaco-languages'; +import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; +import { FILE_NAVIGATOR_ID, FileNavigatorWidget } from '@theia/navigator/lib/browser'; +import { + FILE_NAVIGATOR_TOGGLE_COMMAND_ID, + FileNavigatorCommands +} from '@theia/navigator/lib/browser/navigator-contribution'; +import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution'; +import { Range } from '@theia/plugin'; +import { Position } from '@theia/plugin-ext/lib/common/plugin-api-rpc'; import { - TextDocumentShowOptions, - Location, - CallHierarchyItem, CallHierarchyIncomingCall, + CallHierarchyItem, CallHierarchyOutgoingCall, - TypeHierarchyItem, + DocumentHighlight, + FormattingOptions, Hover, + Location, + TextDocumentShowOptions, TextEdit, - FormattingOptions, - DocumentHighlight + TypeHierarchyItem } from '@theia/plugin-ext/lib/common/plugin-api-rpc-model'; -import { DocumentsMainImpl } from '@theia/plugin-ext/lib/main/browser/documents-main'; -import { isUriComponents, toMergedSymbol, toPosition } from '@theia/plugin-ext/lib/plugin/type-converters'; -import { ViewColumn } from '@theia/plugin-ext/lib/plugin/types-impl'; -import { WorkspaceCommands } from '@theia/workspace/lib/browser'; -import { WorkspaceService, WorkspaceInput } from '@theia/workspace/lib/browser/workspace-service'; -import { DiffService } from '@theia/workspace/lib/browser/diff-service'; -import { inject, injectable, optional } from '@theia/core/shared/inversify'; -import { Position } from '@theia/plugin-ext/lib/common/plugin-api-rpc'; -import { URI } from '@theia/core/shared/vscode-uri'; -import { PluginServer } from '@theia/plugin-ext/lib/common/plugin-protocol'; -import { TerminalFrontendContribution } from '@theia/terminal/lib/browser/terminal-frontend-contribution'; -import { QuickOpenWorkspace } from '@theia/workspace/lib/browser/quick-open-workspace'; -import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; -import { - FileNavigatorCommands, - FILE_NAVIGATOR_TOGGLE_COMMAND_ID -} from '@theia/navigator/lib/browser/navigator-contribution'; -import { FILE_NAVIGATOR_ID, FileNavigatorWidget } from '@theia/navigator/lib/browser'; -import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection'; +import { PluginDeployOptions, PluginIdentifiers, PluginServer } from '@theia/plugin-ext/lib/common/plugin-protocol'; import { UriComponents } from '@theia/plugin-ext/lib/common/uri-components'; -import { FileService } from '@theia/filesystem/lib/browser/file-service'; -import { CallHierarchyServiceProvider, CallHierarchyService } from '@theia/callhierarchy/lib/browser'; -import { TypeHierarchyServiceProvider, TypeHierarchyService } from '@theia/typehierarchy/lib/browser'; -import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; +import { CustomEditorOpener } from '@theia/plugin-ext/lib/main/browser/custom-editors/custom-editor-opener'; +import { DocumentsMainImpl } from '@theia/plugin-ext/lib/main/browser/documents-main'; import { fromCallHierarchyCalleeToModelCallHierarchyOutgoingCall, fromCallHierarchyCallerToModelCallHierarchyIncomingCall, fromItemHierarchyDefinition, toItemHierarchyDefinition } from '@theia/plugin-ext/lib/main/browser/hierarchy/hierarchy-types-converters'; -import { CustomEditorOpener } from '@theia/plugin-ext/lib/main/browser/custom-editors/custom-editor-opener'; -import { nls } from '@theia/core/lib/common/nls'; -import { WindowService } from '@theia/core/lib/browser/window/window-service'; -import * as monaco from '@theia/monaco-editor-core'; -import { VSCodeExtensionUri } from '../common/plugin-vscode-uri'; import { CodeEditorWidgetUtil } from '@theia/plugin-ext/lib/main/browser/menus/vscode-theia-menu-mappings'; -import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution'; -import { Range } from '@theia/plugin'; -import { MonacoLanguages } from '@theia/monaco/lib/browser/monaco-languages'; +import { isUriComponents, toMergedSymbol, toPosition } from '@theia/plugin-ext/lib/plugin/type-converters'; +import { ViewColumn } from '@theia/plugin-ext/lib/plugin/types-impl'; +import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; +import { TerminalFrontendContribution } from '@theia/terminal/lib/browser/terminal-frontend-contribution'; +import { TypeHierarchyService, TypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser'; +import { WorkspaceCommands } from '@theia/workspace/lib/browser'; +import { DiffService } from '@theia/workspace/lib/browser/diff-service'; +import { QuickOpenWorkspace } from '@theia/workspace/lib/browser/quick-open-workspace'; +import { WorkspaceInput, WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; +import { VSCodeExtensionUri } from '../common/plugin-vscode-uri'; export namespace VscodeCommands { @@ -109,6 +110,10 @@ export namespace VscodeCommands { export const INSTALL_FROM_VSIX: Command = { id: 'workbench.extensions.installExtension' }; + + export const UNINSTALL_EXTENSION: Command = { + id: 'workbench.extensions.uninstallExtension' + }; } // https://wicg.github.io/webusb/ @@ -370,16 +375,36 @@ export class PluginVscodeCommandsContribution implements CommandContribution { commands.registerCommand({ id: 'workbench.files.action.refreshFilesExplorer' }, { execute: () => commands.executeCommand(FileNavigatorCommands.REFRESH_NAVIGATOR.id) }); - commands.registerCommand({ id: VscodeCommands.INSTALL_FROM_VSIX.id }, { + commands.registerCommand(VscodeCommands.INSTALL_FROM_VSIX, { execute: async (vsixUriOrExtensionId: TheiaURI | UriComponents | string) => { if (typeof vsixUriOrExtensionId === 'string') { - await this.pluginServer.deploy(VSCodeExtensionUri.fromId(vsixUriOrExtensionId).toString()); + let extensionId = vsixUriOrExtensionId; + let opts: PluginDeployOptions | undefined; + if (PluginIdentifiers.isVersionedId(vsixUriOrExtensionId)) { + const idAndVersion = PluginIdentifiers.getIdAndVersion(vsixUriOrExtensionId); + extensionId = idAndVersion[0]; + opts = { version: idAndVersion[1]!, ignoreOtherVersions: true }; + } + await this.pluginServer.deploy(VSCodeExtensionUri.fromId(extensionId).toString(), undefined, opts); } else { const uriPath = isUriComponents(vsixUriOrExtensionId) ? URI.revive(vsixUriOrExtensionId).fsPath : await this.fileService.fsPath(vsixUriOrExtensionId); await this.pluginServer.deploy(`local-file:${uriPath}`); } } }); + commands.registerCommand(VscodeCommands.UNINSTALL_EXTENSION, { + execute: async (id: string) => { + if (!id) { + throw new Error(nls.localizeByDefault('Extension id required.')); + } + if (!PluginIdentifiers.isVersionedId(id)) { + throw new Error(`Invalid extension id: ${id}\nExpected format: .@.`); + } + const idAndVersion = PluginIdentifiers.identifiersFromVersionedId(id); + const pluginId = PluginIdentifiers.componentsToVersionedId(idAndVersion!); + await this.pluginServer.uninstall(pluginId); + } + }); commands.registerCommand({ id: 'workbench.action.files.save', }, { execute: (uri?: monaco.Uri) => { if (uri) { diff --git a/packages/plugin-ext-vscode/src/common/plugin-vscode-uri.ts b/packages/plugin-ext-vscode/src/common/plugin-vscode-uri.ts index b0c0acec9390d..1d2423ab8ad05 100644 --- a/packages/plugin-ext-vscode/src/common/plugin-vscode-uri.ts +++ b/packages/plugin-ext-vscode/src/common/plugin-vscode-uri.ts @@ -15,6 +15,7 @@ // ***************************************************************************** import URI from '@theia/core/lib/common/uri'; +import { PluginIdentifiers } from '@theia/plugin-ext'; /** * Static methods for identifying a plugin as the target of the VSCode deployment system. @@ -32,7 +33,7 @@ export namespace VSCodeExtensionUri { } export function fromVersionedId(versionedId: string): URI { - const versionAndId = versionedId.split('@'); + const versionAndId = PluginIdentifiers.getIdAndVersion(versionedId); return fromId(versionAndId[0], versionAndId[1]); } diff --git a/packages/plugin-ext/src/common/plugin-identifiers.ts b/packages/plugin-ext/src/common/plugin-identifiers.ts index 748d5afd31111..026ba12283cf3 100644 --- a/packages/plugin-ext/src/common/plugin-identifiers.ts +++ b/packages/plugin-ext/src/common/plugin-identifiers.ts @@ -81,4 +81,31 @@ export namespace PluginIdentifiers { } return { id: probablyId.slice(0, endOfName) as UnversionedId, version: probablyId.slice(endOfName + 1) }; } + + const EXTENSION_IDENTIFIER_WITH_VERSION_REGEX = /^([^.]+\..+)@((prerelease)|(\d+\.\d+\.\d+(-.*)?))$/; + + /** + * Extracts the extension identifier and version from a string. + * @param id The extension identifier + * @returns A tuple of the extension identifier and the version, if present. + */ + export function getIdAndVersion(id: string): [string, string | undefined] { + const matches = EXTENSION_IDENTIFIER_WITH_VERSION_REGEX.exec(id); + if (matches && matches[1]) { + return [matches[1], matches[2]]; + } + return [id, undefined]; + } + + /** + * Checks if the extension identifier is in the format `.@`. + * @param id The extension identifier + * @returns `true` if the extension identifier is in the format `.@`. + */ + export function isVersionedId(id: string): boolean { + const matches = EXTENSION_IDENTIFIER_WITH_VERSION_REGEX.exec(id); + // eslint-disable-next-line no-null/no-null + return matches !== null && matches.length > 2; + } + } diff --git a/packages/vsx-registry/src/browser/vsx-extension.tsx b/packages/vsx-registry/src/browser/vsx-extension.tsx index 4a0784ddb7d72..e9c0eafd23833 100644 --- a/packages/vsx-registry/src/browser/vsx-extension.tsx +++ b/packages/vsx-registry/src/browser/vsx-extension.tsx @@ -22,6 +22,7 @@ import { TreeElement, TreeElementNode } from '@theia/core/lib/browser/source-tre import { OpenerService, open, OpenerOptions } from '@theia/core/lib/browser/opener-service'; import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; import { PluginServer, DeployedPlugin, PluginType, PluginIdentifiers, PluginDeployOptions } from '@theia/plugin-ext/lib/common/plugin-protocol'; +import { VscodeCommands } from '@theia/plugin-ext-vscode/lib/browser/plugin-vscode-commands-contribution'; import { VSCodeExtensionUri } from '@theia/plugin-ext-vscode/lib/common/plugin-vscode-uri'; import { ProgressService } from '@theia/core/lib/common/progress-service'; import { Endpoint } from '@theia/core/lib/browser/endpoint'; @@ -324,8 +325,10 @@ export class VSXExtension implements VSXExtensionData, TreeElement { if (plugin) { await this.progressService.withProgress( nls.localizeByDefault('Uninstalling {0}...', this.id), 'extensions', - () => this.pluginServer.uninstall(PluginIdentifiers.componentsToVersionedId(plugin.metadata.model)) - ); + async () => { + const versionedId = PluginIdentifiers.componentsToVersionedId(plugin.metadata.model); + await this.commandRegistry.executeCommand(VscodeCommands.UNINSTALL_EXTENSION.id, versionedId); + }); } } finally { this._busy--;