Skip to content

Commit

Permalink
VSX: Add Toolbar Menu and Support 'Install from VSIX...' Command
Browse files Browse the repository at this point in the history
What it does
- Adds an `Install from VSIX...` command that supports installation of extensions from a locally available `.vsix` file.
- Adds a '_more_' toolbar menu to the `extensions view` to which additional commands may be registered

How to test
1. Visit open-vsx.org and download an extension as a `.vsix` file.
2. Open the `extensions` view.
3. Select the `_more_` menu item in the toolbar and execute the `Install from VSIX...` command.
4. Select a local `.vsix` file in the file dialog and click `Install`.
5. Confirm that the selected extension has been installed by checking the `extensions` view.

Signed-off-by: seantan22 <sean.a.tan@ericsson.com>
  • Loading branch information
seantan22 committed Mar 23, 2021
1 parent 3426e8d commit 10381a1
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
} from '@theia/plugin-ext/lib/common/plugin-api-rpc-model';
import { DocumentsMainImpl } from '@theia/plugin-ext/lib/main/browser/documents-main';
import { createUntitledURI } from '@theia/plugin-ext/lib/main/browser/editor/untitled-resource';
import { toDocumentSymbol } from '@theia/plugin-ext/lib/plugin/type-converters';
import { isUriComponents, toDocumentSymbol } 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';
Expand All @@ -65,6 +65,7 @@ import {
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';
import { FileService } from '@theia/filesystem/lib/browser/file-service';

export namespace VscodeCommands {
export const OPEN: Command = {
Expand All @@ -78,6 +79,10 @@ export namespace VscodeCommands {
export const DIFF: Command = {
id: 'vscode.diff'
};

export const INSTALL_FROM_VSIX: Command = {
id: 'workbench.extensions.installExtension'
};
}

@injectable()
Expand Down Expand Up @@ -110,6 +115,8 @@ export class PluginVscodeCommandsContribution implements CommandContribution {
protected readonly codeEditorWidgetUtil: CodeEditorWidgetUtil;
@inject(PluginServer)
protected readonly pluginServer: PluginServer;
@inject(FileService)
protected readonly fileService: FileService;

registerCommands(commands: CommandRegistry): void {
commands.registerCommand(VscodeCommands.OPEN, {
Expand Down Expand Up @@ -212,12 +219,13 @@ 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) => {
commands.registerCommand({ id: VscodeCommands.INSTALL_FROM_VSIX.id }, {
execute: async (vsixUriOrExtensionId: TheiaURI | UriComponents | string) => {
if (typeof vsixUriOrExtensionId === 'string') {
this.pluginServer.deploy(`vscode:extension/${vsixUriOrExtensionId}`);
await this.pluginServer.deploy(`vscode:extension/${vsixUriOrExtensionId}`);
} else {
this.pluginServer.deploy(`local-file:${URI.revive(vsixUriOrExtensionId).fsPath}`);
const uriPath = isUriComponents(vsixUriOrExtensionId) ? URI.revive(vsixUriOrExtensionId).fsPath : await this.fileService.fsPath(vsixUriOrExtensionId);
await this.pluginServer.deploy(`local-file:${uriPath}`);
}
}
});
Expand Down
79 changes: 76 additions & 3 deletions packages/vsx-registry/src/browser/vsx-extensions-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ import { Widget } from '@theia/core/lib/browser/widgets/widget';
import { VSXExtensionsModel } from './vsx-extensions-model';
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
import { ColorRegistry, Color } from '@theia/core/lib/browser/color-registry';
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { TabBarToolbarContribution, TabBarToolbarItem, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { MessageService, Mutable } from '@theia/core/lib/common';
import { FileDialogService, OpenFileDialogProps } from '@theia/filesystem/lib/browser';
import { LabelProvider } from '@theia/core/lib/browser';
import { VscodeCommands } from '@theia/plugin-ext-vscode/lib/browser/plugin-vscode-commands-contribution';

export namespace VSXExtensionsCommands {
export const CLEAR_ALL: Command = {
Expand All @@ -32,14 +36,24 @@ export namespace VSXExtensionsCommands {
label: 'Clear Search Results',
iconClass: 'clear-all'
};
export const INSTALL_FROM_VSIX: Command & { dialogLabel: string } = {
id: 'vsxExtensions.installFromVSIX',
category: 'Extensions',
label: 'Install from VSIX...',
dialogLabel: 'Install from VSIX'
};
}

@injectable()
export class VSXExtensionsContribution extends AbstractViewContribution<VSXExtensionsViewContainer>
implements ColorContribution, FrontendApplicationContribution, TabBarToolbarContribution {

@inject(VSXExtensionsModel)
protected readonly model: VSXExtensionsModel;
@inject(VSXExtensionsModel) protected readonly model: VSXExtensionsModel;
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;
@inject(TabBarToolbarRegistry) protected readonly tabbarToolbarRegistry: TabBarToolbarRegistry;
@inject(FileDialogService) protected readonly fileDialogService: FileDialogService;
@inject(MessageService) protected readonly messageService: MessageService;
@inject(LabelProvider) protected readonly labelProvider: LabelProvider;

constructor() {
super({
Expand All @@ -65,6 +79,10 @@ export class VSXExtensionsContribution extends AbstractViewContribution<VSXExten
isEnabled: w => this.withWidget(w, () => !!this.model.search.query),
isVisible: w => this.withWidget(w, () => true)
});

commands.registerCommand(VSXExtensionsCommands.INSTALL_FROM_VSIX, {
execute: () => this.installFromVSIX()
});
}

registerToolbarItems(registry: TabBarToolbarRegistry): void {
Expand All @@ -75,8 +93,36 @@ export class VSXExtensionsContribution extends AbstractViewContribution<VSXExten
priority: 1,
onDidChange: this.model.onDidChange
});

this.registerMoreToolbarItem({
id: VSXExtensionsCommands.INSTALL_FROM_VSIX.id,
command: VSXExtensionsCommands.INSTALL_FROM_VSIX.id,
tooltip: VSXExtensionsCommands.INSTALL_FROM_VSIX.label,
group: 'other_1'
});
}

/**
* Register commands to the `More Actions...` extensions toolbar item.
*/
registerMoreToolbarItem = (item: Mutable<TabBarToolbarItem>) => {
const commandId = item.command;
const id = 'vsxExtensions.tabbar.toolbar.' + commandId;
const command = this.commandRegistry.getCommand(commandId);
this.commandRegistry.registerCommand({ id, iconClass: command && command.iconClass }, {
execute: (w, ...args) => w instanceof VSXExtensionsViewContainer
&& this.commandRegistry.executeCommand(commandId, ...args),
isEnabled: (w, ...args) => w instanceof VSXExtensionsViewContainer
&& this.commandRegistry.isEnabled(commandId, ...args),
isVisible: (w, ...args) => w instanceof VSXExtensionsViewContainer
&& this.commandRegistry.isVisible(commandId, ...args),
isToggled: (w, ...args) => w instanceof VSXExtensionsViewContainer
&& this.commandRegistry.isToggled(commandId, ...args),
});
item.command = id;
this.tabbarToolbarRegistry.registerItem(item);
};

registerColors(colors: ColorRegistry): void {
// VS Code colors should be aligned with https://code.visualstudio.com/api/references/theme-color#extensions
colors.register(
Expand Down Expand Up @@ -107,4 +153,31 @@ export class VSXExtensionsContribution extends AbstractViewContribution<VSXExten
}
return false;
}

/**
* Installs a local .vsix file after prompting the `Open File` dialog. Resolves to the URI of the file.
*/
protected async installFromVSIX(): Promise<void> {
const props: OpenFileDialogProps = {
title: VSXExtensionsCommands.INSTALL_FROM_VSIX.dialogLabel,
openLabel: 'Install',
filters: { 'VSIX Extensions (*.vsix)': ['vsix'] },
canSelectMany: false
};
const extensionUri = await this.fileDialogService.showOpenDialog(props);
if (extensionUri) {
if (extensionUri.path.ext === '.vsix') {
const extensionName = this.labelProvider.getName(extensionUri);
try {
await this.commandRegistry.executeCommand(VscodeCommands.INSTALL_FROM_VSIX.id, extensionUri);
this.messageService.info(`Completed installing ${extensionName} from VSIX.`);
} catch (e) {
this.messageService.error(`Failed to install ${extensionName} from VSIX.`);
console.warn(e);
}
} else {
this.messageService.error('The selected file is not a valid "*.vsix" plugin.');
}
}
}
}

0 comments on commit 10381a1

Please sign in to comment.