Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix context-keys ScmProvider update, add submenu plugin contribution #8996

Merged
merged 1 commit into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion packages/core/src/browser/shell/tab-bar-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,16 @@ export class TabBarToolbar extends ReactWidget {
const menuPath = ['TAB_BAR_TOOLBAR_CONTEXT_MENU'];
const toDisposeOnHide = new DisposableCollection();
for (const [, item] of this.more) {
toDisposeOnHide.push(this.menus.registerMenuAction([...menuPath, item.group!], {
// Register a submenu for the item, if the group is in format `<submenu group>/<submenu name>/.../<item group>`
if (item.group!.indexOf('/') !== -1) {
const split = item.group!.split('/');
const paths: string[] = [];
for (let i = 0; i < split.length - 1; i += 2) {
paths.push(split[i], split[i + 1]);
toDisposeOnHide.push(this.menus.registerSubmenu([...menuPath, ...paths], split[i + 1]));
}
}
toDisposeOnHide.push(this.menus.registerMenuAction([...menuPath, ...item.group!.split('/')], {
label: item.tooltip,
commandId: item.id,
when: item.when
Expand Down Expand Up @@ -291,6 +300,8 @@ export interface TabBarToolbarItem {
/**
* Optional group for the item. Default `navigation`.
* `navigation` group will be inlined, while all the others will be within the `...` dropdown.
* A group in format `submenu_group_1/submenu 1/.../submenu_group_n/ submenu n/item_group` means that the item will be located in a submenu(s) of the `...` dropdown.
* The submenu's title is named by the submenu section name, e.g. `group/<submenu name>/subgroup`.
*/
readonly group?: string;

Expand Down
18 changes: 16 additions & 2 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface PluginPackageContribution {
viewsWelcome?: PluginPackageViewWelcome[];
commands?: PluginPackageCommand | PluginPackageCommand[];
menus?: { [location: string]: PluginPackageMenu[] };
submenus?: PluginPackageSubmenu[];
keybindings?: PluginPackageKeybinding | PluginPackageKeybinding[];
debuggers?: PluginPackageDebuggersContribution[];
snippets?: PluginPackageSnippetsContribution[];
Expand Down Expand Up @@ -116,12 +117,18 @@ export interface PluginPackageCommand {
}

export interface PluginPackageMenu {
command: string;
command?: string;
submenu?: string;
alt?: string;
group?: string;
when?: string;
}

export interface PluginPackageSubmenu {
id: string;
label: string;
}

export interface PluginPackageKeybinding {
key?: string;
command: string;
Expand Down Expand Up @@ -487,6 +494,7 @@ export interface PluginContribution {
viewsWelcome?: ViewWelcome[];
commands?: PluginCommand[]
menus?: { [location: string]: Menu[] };
submenus?: Submenu[];
keybindings?: Keybinding[];
debuggers?: DebuggerContribution[];
snippets?: SnippetContribution[];
Expand Down Expand Up @@ -647,12 +655,18 @@ export type IconUrl = string | { light: string; dark: string; };
* Menu contribution
*/
export interface Menu {
command: string;
command?: string;
submenu?: string
alt?: string;
group?: string;
when?: string;
}

export interface Submenu {
id: string;
label: string;
}

/**
* Keybinding contribution
*/
Expand Down
77 changes: 52 additions & 25 deletions packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,42 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject } from 'inversify';
import { inject, injectable } from 'inversify';
import {
AutoClosingPair,
AutoClosingPairConditional,
buildFrontendModuleName,
DebuggerContribution,
IconThemeContribution,
IconUrl,
Keybinding,
LanguageConfiguration,
LanguageContribution,
Menu,
PluginCommand,
PluginContribution,
PluginEngine,
PluginLifecycle,
PluginModel,
PluginPackage,
PluginScanner,
PluginLifecycle,
buildFrontendModuleName,
PluginContribution,
PluginPackageCommand,
PluginPackageDebuggersContribution,
PluginPackageKeybinding,
PluginPackageLanguageContribution,
LanguageContribution,
PluginPackageLanguageContributionConfiguration,
LanguageConfiguration,
PluginTaskDefinitionContribution,
AutoClosingPairConditional,
AutoClosingPair,
ViewContainer,
Keybinding,
PluginPackageKeybinding,
PluginPackageViewContainer,
View,
PluginPackageMenu,
PluginPackageSubmenu,
PluginPackageView,
ViewWelcome,
PluginPackageViewContainer,
PluginPackageViewWelcome,
Menu,
PluginPackageMenu,
PluginPackageDebuggersContribution,
DebuggerContribution,
PluginScanner,
PluginTaskDefinitionContribution,
SnippetContribution,
PluginPackageCommand,
PluginCommand,
IconUrl,
Submenu,
ThemeContribution,
IconThemeContribution
View,
ViewContainer,
ViewWelcome
} from '../../../common/plugin-protocol';
import * as fs from 'fs';
import * as path from 'path';
Expand All @@ -60,7 +62,11 @@ import { deepClone } from '@theia/core/lib/common/objects';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { PreferenceSchema, PreferenceSchemaProperties } from '@theia/core/lib/common/preferences/preference-schema';
import { RecursivePartial } from '@theia/core/lib/common/types';
import { ProblemMatcherContribution, ProblemPatternContribution, TaskDefinition } from '@theia/task/lib/common/task-protocol';
import {
ProblemMatcherContribution,
ProblemPatternContribution,
TaskDefinition
} from '@theia/task/lib/common/task-protocol';
import { ColorDefinition } from '@theia/core/lib/browser/color-registry';
import { ResourceLabelFormatter } from '@theia/core/lib/common/label-protocol';

Expand Down Expand Up @@ -168,6 +174,14 @@ export class TheiaPluginScanner implements PluginScanner {
console.error(`Could not read '${rawPlugin.name}' contribution 'languages'.`, rawPlugin.contributes!.languages, err);
}

try {
if (rawPlugin.contributes!.submenus) {
contributions.submenus = this.readSubmenus(rawPlugin.contributes.submenus!);
}
} catch (err) {
console.error(`Could not read '${rawPlugin.name}' contribution 'submenus'.`, rawPlugin.contributes!.submenus, err);
}

try {
if (rawPlugin.contributes!.grammars) {
const grammars = this.grammarsReader.readGrammars(rawPlugin.contributes.grammars!, rawPlugin.packagePath);
Expand Down Expand Up @@ -527,6 +541,7 @@ export class TheiaPluginScanner implements PluginScanner {
private readMenu(rawMenu: PluginPackageMenu): Menu {
const result: Menu = {
command: rawMenu.command,
submenu: rawMenu.submenu,
alt: rawMenu.alt,
group: rawMenu.group,
when: rawMenu.when
Expand All @@ -538,6 +553,18 @@ export class TheiaPluginScanner implements PluginScanner {
return rawLanguages.map(language => this.readLanguage(language, pluginPath));
}

private readSubmenus(rawSubmenus: PluginPackageSubmenu[]): Submenu[] {
return rawSubmenus.map(submenu => this.readSubmenu(submenu));
}

private readSubmenu(rawSubmenu: PluginPackageSubmenu): Submenu {
return {
id: rawSubmenu.id,
label: rawSubmenu.label
};

}

private readLanguage(rawLang: PluginPackageLanguageContribution, pluginPath: string): LanguageContribution {
// TODO: add validation to all parameters
const result: LanguageContribution = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { TabBarToolbarRegistry, TabBarToolbarItem } from '@theia/core/lib/browse
import { NAVIGATOR_CONTEXT_MENU } from '@theia/navigator/lib/browser/navigator-contribution';
import { QuickCommandService } from '@theia/core/lib/browser/quick-open/quick-command-service';
import { VIEW_ITEM_CONTEXT_MENU, TreeViewWidget, VIEW_ITEM_INLINE_MENU } from '../view/tree-view-widget';
import { DeployedPlugin, Menu, ScmCommandArg, TimelineCommandArg, TreeViewSelection } from '../../../common';
import { DeployedPlugin, Menu, ScmCommandArg, Submenu, TimelineCommandArg, TreeViewSelection } from '../../../common';
import { DebugStackFramesWidget } from '@theia/debug/lib/browser/view/debug-stack-frames-widget';
import { DebugThreadsWidget } from '@theia/debug/lib/browser/view/debug-threads-widget';
import { TreeWidgetSelection } from '@theia/core/lib/browser/tree/tree-widget-selection';
Expand Down Expand Up @@ -98,32 +98,39 @@ export class MenusContributionPointHandler {
if (!allMenus) {
return Disposable.NULL;
}
const allSubmenus = plugin.contributes && plugin.contributes.submenus;
const toDispose = new DisposableCollection();
for (const location in allMenus) {
if (location === 'commandPalette') {
for (const menu of allMenus[location]) {
if (menu.when) {
if (menu.command && menu.when) {
toDispose.push(this.quickCommandService.pushCommandContext(menu.command, menu.when));
}
}
} else if (location === 'editor/title') {
for (const action of allMenus[location]) {
if (!action.command) {
continue;
}
toDispose.push(this.registerTitleAction(location, action, {
execute: widget => this.codeEditorWidgetUtil.is(widget) && this.commands.executeCommand(action.command, this.codeEditorWidgetUtil.getResourceUri(widget)),
isEnabled: widget => this.codeEditorWidgetUtil.is(widget) && this.commands.isEnabled(action.command, this.codeEditorWidgetUtil.getResourceUri(widget)),
isVisible: widget => this.codeEditorWidgetUtil.is(widget) && this.commands.isVisible(action.command, this.codeEditorWidgetUtil.getResourceUri(widget))
execute: widget => this.codeEditorWidgetUtil.is(widget) && this.commands.executeCommand(action.command!, this.codeEditorWidgetUtil.getResourceUri(widget)),
isEnabled: widget => this.codeEditorWidgetUtil.is(widget) && this.commands.isEnabled(action.command!, this.codeEditorWidgetUtil.getResourceUri(widget)),
isVisible: widget => this.codeEditorWidgetUtil.is(widget) && this.commands.isVisible(action.command!, this.codeEditorWidgetUtil.getResourceUri(widget))
}));
}
} else if (location === 'view/title') {
for (const action of allMenus[location]) {
if (!action.command) {
continue;
}
toDispose.push(this.registerTitleAction(location, { ...action, when: undefined }, {
execute: widget => widget instanceof PluginViewWidget && this.commands.executeCommand(action.command),
execute: widget => widget instanceof PluginViewWidget && this.commands.executeCommand(action.command!),
isEnabled: widget => widget instanceof PluginViewWidget &&
this.viewContextKeys.with({ view: widget.options.viewId }, () =>
this.commands.isEnabled(action.command) && this.viewContextKeys.match(action.when)),
this.commands.isEnabled(action.command!) && this.viewContextKeys.match(action.when)),
isVisible: widget => widget instanceof PluginViewWidget &&
this.viewContextKeys.with({ view: widget.options.viewId }, () =>
this.commands.isVisible(action.command) && this.viewContextKeys.match(action.when))
this.commands.isVisible(action.command!) && this.viewContextKeys.match(action.when))
}));
}
} else if (location === 'view/item/context') {
Expand All @@ -133,9 +140,20 @@ export class MenusContributionPointHandler {
toDispose.push(this.registerTreeMenuAction(menuPath, menu));
}
} else if (location === 'scm/title') {
for (const action of allMenus[location]) {
toDispose.push(this.registerScmTitleAction(location, action));
}
const registerActions = (menus: Menu[], group: string | undefined) => {
for (const action of menus) {
if (group) {
action.group = group + (action.group ? '/' + action.group.split('@')[0] : '/_');
}
if (action.submenu) {
const submenu: Submenu = allSubmenus!.find(s => s.id === action.submenu)!;
registerActions(allMenus[action.submenu], action.group!.split('@')[0] + '/' + submenu.label);
} else {
toDispose.push(this.registerScmTitleAction(location, action));
}
}
};
registerActions(allMenus[location], undefined);
} else if (location === 'scm/resourceGroup/context') {
for (const menu of allMenus[location]) {
const inline = menu.group && /^inline/.test(menu.group) || false;
Expand Down Expand Up @@ -256,6 +274,9 @@ export class MenusContributionPointHandler {
}

protected registerTitleAction(location: string, action: Menu, handler: CommandHandler): Disposable {
if (!action.command) {
return Disposable.NULL;
}
const toDispose = new DisposableCollection();
const id = this.createSyntheticCommandId(action.command, { prefix: `__plugin.${location.replace('/', '.')}.action.` });
const command: Command = { id };
Expand Down Expand Up @@ -301,11 +322,14 @@ export class MenusContributionPointHandler {
}

protected registerScmTitleAction(location: string, action: Menu): Disposable {
if (!action.command) {
return Disposable.NULL;
}
const selectedRepository = () => this.toScmArgs(this.scmService.selectedRepository);
return this.registerTitleAction(location, action, {
execute: widget => widget instanceof ScmWidget && this.commands.executeCommand(action.command, selectedRepository()),
isEnabled: widget => widget instanceof ScmWidget && this.commands.isEnabled(action.command, selectedRepository()),
isVisible: widget => widget instanceof ScmWidget && this.commands.isVisible(action.command, selectedRepository())
execute: widget => widget instanceof ScmWidget && this.commands.executeCommand(action.command!, selectedRepository()),
isEnabled: widget => widget instanceof ScmWidget && this.commands.isEnabled(action.command!, selectedRepository()),
isVisible: widget => widget instanceof ScmWidget && this.commands.isVisible(action.command!, selectedRepository())
});
}
protected registerScmMenuAction(menuPath: MenuPath, menu: Menu): Disposable {
Expand Down Expand Up @@ -403,6 +427,9 @@ export class MenusContributionPointHandler {
}

protected registerMenuAction(menuPath: MenuPath, menu: Menu, handler: (command: string) => CommandHandler): Disposable {
if (!menu.command) {
return Disposable.NULL;
}
const toDispose = new DisposableCollection();
const commandId = this.createSyntheticCommandId(menu.command, { prefix: '__plugin.menu.action.' });
const command: Command = { id: commandId };
Expand Down
1 change: 1 addition & 0 deletions packages/scm/src/browser/scm-groups-tree-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class ScmGroupsTreeModel extends ScmTreeModel {

protected changeRepository(provider: ScmProvider | undefined): void {
this.toDisposeOnRepositoryChange.dispose();
this.contextKeys.scmProvider.set(provider ? provider.id : undefined);
this.provider = provider;
if (provider) {
this.toDisposeOnRepositoryChange.push(provider.onDidChange(() => {
Expand Down
4 changes: 0 additions & 4 deletions packages/scm/src/browser/scm-tree-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,15 +367,11 @@ export abstract class ScmTreeModel extends TreeModelImpl {
return;
}

const currentScmProviderId = this.contextKeys.scmProvider.get();
const currentScmResourceGroup = this.contextKeys.scmResourceGroup.get();
this.contextKeys.scmProvider.set(this.provider.id);
this.contextKeys.scmResourceGroup.set(groupId);
try {
callback();
} finally {
this.contextKeys.scmProvider.set(currentScmProviderId);
this.contextKeys.scmResourceGroup.set(currentScmResourceGroup);
}
}

Expand Down