diff --git a/CHANGELOG.md b/CHANGELOG.md
index edd01986089c2..98b10eb01494f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Change Log
+## v1.9.0
+
+- [plugin-ext-vscode] added support for the command `workbench.extensions.installExtension`. [#8745](https://github.com/eclipse-theia/theia/pull/8745)
+
+[Breaking Changes:](#breaking_changes_1.9.0)
+
+- [plugin-ext] `LocalDirectoryPluginDeployerResolver` has moved from `packages/plugin-ext/src/main/node/resolvers/plugin-local-dir-resolver.ts` to `packages/plugin-ext/src/main/node/resolvers/local-file-plugin-deployer-resolver.ts` and now derives from `LocalPluginDeployerResolver`.
+
## v1.8.0 - 26/11/2020
- [api-tests] fixed issue with `saveable` test suite [#8736](https://github.com/eclipse-theia/theia/pull/8736)
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 440730fb20a6b..f818455723ff2 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
@@ -53,6 +53,7 @@ import { DiffService } from '@theia/workspace/lib/browser/diff-service';
import { inject, injectable } from 'inversify';
import { Position } from '@theia/plugin-ext/lib/common/plugin-api-rpc';
import { URI } from 'vscode-uri';
+import { PluginServer } from '@theia/plugin-ext/lib/common/plugin-protocol';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { TerminalFrontendContribution } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
import { QuickOpenWorkspace } from '@theia/workspace/lib/browser/quick-open-workspace';
@@ -63,6 +64,7 @@ import {
} 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 { UriComponents } from '@theia/plugin-ext/lib/common/uri-components';
export namespace VscodeCommands {
export const OPEN: Command = {
@@ -106,6 +108,8 @@ export class PluginVscodeCommandsContribution implements CommandContribution {
protected readonly terminalService: TerminalService;
@inject(CodeEditorWidgetUtil)
protected readonly codeEditorWidgetUtil: CodeEditorWidgetUtil;
+ @inject(PluginServer)
+ protected readonly pluginServer: PluginServer;
registerCommands(commands: CommandRegistry): void {
commands.registerCommand(VscodeCommands.OPEN, {
@@ -208,6 +212,15 @@ export class PluginVscodeCommandsContribution implements CommandContribution {
commands.registerCommand({ id: 'workbench.action.openSettings' }, {
execute: () => commands.executeCommand(CommonCommands.OPEN_PREFERENCES.id)
});
+ commands.registerCommand({ id: 'workbench.extensions.installExtension' }, {
+ execute: async (vsixUriOrExtensionId: UriComponents | string) => {
+ if (typeof vsixUriOrExtensionId === 'string') {
+ this.pluginServer.deploy(`vscode:extension/${vsixUriOrExtensionId}`);
+ } else {
+ this.pluginServer.deploy(`local-file:${URI.revive(vsixUriOrExtensionId).fsPath}`);
+ }
+ }
+ });
commands.registerCommand({ id: 'workbench.action.files.save', }, {
execute: (uri?: monaco.Uri) => {
if (uri) {
diff --git a/packages/plugin-ext/src/main/node/plugin-cli-contribution.ts b/packages/plugin-ext/src/main/node/plugin-cli-contribution.ts
index e989b1527b15d..614c63bc4ce54 100644
--- a/packages/plugin-ext/src/main/node/plugin-cli-contribution.ts
+++ b/packages/plugin-ext/src/main/node/plugin-cli-contribution.ts
@@ -17,7 +17,7 @@
import { injectable } from 'inversify';
import { Argv, Arguments } from 'yargs';
import { CliContribution } from '@theia/core/lib/node/cli';
-import { LocalDirectoryPluginDeployerResolver } from './resolvers/plugin-local-dir-resolver';
+import { LocalDirectoryPluginDeployerResolver } from './resolvers/local-directory-plugin-deployer-resolver';
@injectable()
export class PluginCliContribution implements CliContribution {
diff --git a/packages/plugin-ext/src/main/node/plugin-ext-backend-module.ts b/packages/plugin-ext/src/main/node/plugin-ext-backend-module.ts
index 440e5f138f032..412cb3ddeffd3 100644
--- a/packages/plugin-ext/src/main/node/plugin-ext-backend-module.ts
+++ b/packages/plugin-ext/src/main/node/plugin-ext-backend-module.ts
@@ -24,7 +24,8 @@ import {
PluginDeployerDirectoryHandler, PluginServer, pluginServerJsonRpcPath, PluginDeployerParticipant
} from '../../common/plugin-protocol';
import { PluginDeployerImpl } from './plugin-deployer-impl';
-import { LocalDirectoryPluginDeployerResolver } from './resolvers/plugin-local-dir-resolver';
+import { LocalFilePluginDeployerResolver } from './resolvers/local-file-plugin-deployer-resolver';
+import { LocalDirectoryPluginDeployerResolver } from './resolvers/local-directory-plugin-deployer-resolver';
import { PluginTheiaFileHandler } from './handlers/plugin-theia-file-handler';
import { PluginTheiaDirectoryHandler } from './handlers/plugin-theia-directory-handler';
import { GithubPluginDeployerResolver } from './plugin-github-resolver';
@@ -47,6 +48,7 @@ export function bindMainBackend(bind: interfaces.Bind): void {
bind(BackendApplicationContribution).toService(PluginDeployerContribution);
bind(PluginDeployerResolver).to(LocalDirectoryPluginDeployerResolver).inSingletonScope();
+ bind(PluginDeployerResolver).to(LocalFilePluginDeployerResolver).inSingletonScope();
bind(PluginDeployerResolver).to(GithubPluginDeployerResolver).inSingletonScope();
bind(PluginDeployerResolver).to(HttpPluginDeployerResolver).inSingletonScope();
diff --git a/packages/plugin-ext/src/main/node/resolvers/local-directory-plugin-deployer-resolver.ts b/packages/plugin-ext/src/main/node/resolvers/local-directory-plugin-deployer-resolver.ts
new file mode 100644
index 0000000000000..6e0ea92da99e3
--- /dev/null
+++ b/packages/plugin-ext/src/main/node/resolvers/local-directory-plugin-deployer-resolver.ts
@@ -0,0 +1,37 @@
+/********************************************************************************
+ * Copyright (C) 2018 Red Hat, Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { PluginDeployerResolverContext } from '../../../common/plugin-protocol';
+import { injectable } from 'inversify';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import { LocalPluginDeployerResolver } from './local-plugin-deployer-resolver';
+
+@injectable()
+export class LocalDirectoryPluginDeployerResolver extends LocalPluginDeployerResolver {
+ static LOCAL_DIR = 'local-dir';
+
+ protected get supportedScheme(): string {
+ return LocalDirectoryPluginDeployerResolver.LOCAL_DIR;
+ }
+
+ protected async resolveFromLocalPath(pluginResolverContext: PluginDeployerResolverContext, localPath: string): Promise {
+ const files = await fs.readdir(localPath);
+ files.forEach(file =>
+ pluginResolverContext.addPlugin(file, path.resolve(localPath, file))
+ );
+ }
+}
diff --git a/packages/plugin-ext/src/main/node/resolvers/local-file-plugin-deployer-resolver.ts b/packages/plugin-ext/src/main/node/resolvers/local-file-plugin-deployer-resolver.ts
new file mode 100644
index 0000000000000..9635024ddf0b3
--- /dev/null
+++ b/packages/plugin-ext/src/main/node/resolvers/local-file-plugin-deployer-resolver.ts
@@ -0,0 +1,34 @@
+/********************************************************************************
+ * Copyright (C) 2018 Red Hat, Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { PluginDeployerResolverContext } from '../../../common/plugin-protocol';
+import { injectable } from 'inversify';
+import * as path from 'path';
+import { LocalPluginDeployerResolver } from './local-plugin-deployer-resolver';
+
+@injectable()
+export class LocalFilePluginDeployerResolver extends LocalPluginDeployerResolver {
+ static LOCAL_FILE = 'local-file';
+
+ protected get supportedScheme(): string {
+ return LocalFilePluginDeployerResolver.LOCAL_FILE;
+ }
+
+ async resolveFromLocalPath(pluginResolverContext: PluginDeployerResolverContext, localPath: string): Promise {
+ const fileName = path.basename(localPath);
+ pluginResolverContext.addPlugin(fileName, localPath);
+ }
+}
diff --git a/packages/plugin-ext/src/main/node/resolvers/local-plugin-deployer-resolver.ts b/packages/plugin-ext/src/main/node/resolvers/local-plugin-deployer-resolver.ts
new file mode 100644
index 0000000000000..8eaa16688feb6
--- /dev/null
+++ b/packages/plugin-ext/src/main/node/resolvers/local-plugin-deployer-resolver.ts
@@ -0,0 +1,60 @@
+/********************************************************************************
+ * Copyright (C) 2020 Red Hat, Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { injectable } from 'inversify';
+import { PluginDeployerResolver, PluginDeployerResolverContext } from '../../../common/plugin-protocol';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import { FileUri } from '@theia/core/lib/node';
+import URI from '@theia/core/lib/common/uri';
+
+@injectable()
+export abstract class LocalPluginDeployerResolver implements PluginDeployerResolver {
+ public async resolve(pluginResolverContext: PluginDeployerResolverContext): Promise {
+ const localPath = await this.resolveLocalPluginPath(
+ pluginResolverContext,
+ this.supportedScheme);
+ if (localPath) {
+ await this.resolveFromLocalPath(pluginResolverContext, localPath);
+ }
+ }
+
+ public accept(pluginId: string): boolean {
+ return pluginId.startsWith(this.supportedScheme);
+ }
+
+ protected abstract get supportedScheme(): string;
+
+ protected abstract resolveFromLocalPath(pluginResolverContext: PluginDeployerResolverContext, localPath: string): Promise;
+
+ private async resolveLocalPluginPath(
+ pluginResolverContext: PluginDeployerResolverContext,
+ expectedScheme: string): Promise {
+ const localUri = new URI(pluginResolverContext.getOriginId());
+ if (localUri.scheme !== expectedScheme) {
+ return null;
+ }
+ let fsPath = FileUri.fsPath(localUri);
+ if (!path.isAbsolute(fsPath)) {
+ fsPath = path.resolve(process.cwd(), fsPath);
+ }
+ if (!await fs.pathExists(fsPath)) {
+ console.warn(`The local plugin referenced by ${pluginResolverContext.getOriginId()} does not exist.`);
+ return null;
+ }
+ return fsPath;
+ }
+}
diff --git a/packages/plugin-ext/src/main/node/resolvers/plugin-local-dir-resolver.ts b/packages/plugin-ext/src/main/node/resolvers/plugin-local-dir-resolver.ts
deleted file mode 100644
index 7e9de4a3d11c7..0000000000000
--- a/packages/plugin-ext/src/main/node/resolvers/plugin-local-dir-resolver.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/********************************************************************************
- * Copyright (C) 2018 Red Hat, Inc. and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v. 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0.
- *
- * This Source Code may also be made available under the following Secondary
- * Licenses when the conditions for such availability set forth in the Eclipse
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
- * with the GNU Classpath Exception which is available at
- * https://www.gnu.org/software/classpath/license.html.
- *
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
- ********************************************************************************/
-
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
-import { PluginDeployerResolver, PluginDeployerResolverContext } from '../../../common/plugin-protocol';
-import { injectable } from 'inversify';
-import * as fs from 'fs';
-import * as path from 'path';
-import { FileUri } from '@theia/core/lib/node';
-import URI from '@theia/core/lib/common/uri';
-
-@injectable()
-export class LocalDirectoryPluginDeployerResolver implements PluginDeployerResolver {
-
- static LOCAL_DIR = 'local-dir';
-
- /**
- * Check all files/folder from the local-dir referenced and add them as plugins.
- */
- async resolve(pluginResolverContext: PluginDeployerResolverContext): Promise {
- // get directory
- const localDirUri = new URI(pluginResolverContext.getOriginId());
- if (localDirUri.scheme !== LocalDirectoryPluginDeployerResolver.LOCAL_DIR) {
- return;
- }
- // get fs path
- let dirPath = FileUri.fsPath(localDirUri);
- if (!path.isAbsolute(dirPath)) {
- dirPath = path.resolve(process.cwd(), dirPath);
- }
- // check directory exists
- if (!fs.existsSync(dirPath)) {
- console.warn(`The directory referenced by ${pluginResolverContext.getOriginId()} does not exist.`);
- return;
- }
- // list all stuff from this directory
- await new Promise((resolve: any, reject: any) => {
- fs.readdir(dirPath, (err: any, files: any) => {
- files.forEach((file: any) => {
- pluginResolverContext.addPlugin(file, path.resolve(dirPath, file));
- });
- resolve(true);
- });
- });
- }
- accept(pluginId: string): boolean {
- return pluginId.startsWith(LocalDirectoryPluginDeployerResolver.LOCAL_DIR);
- }
-}