mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
178
src/vs/platform/actions/browser/menuItemActionItem.ts
Normal file
178
src/vs/platform/actions/browser/menuItemActionItem.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IMenu, MenuItemAction, IMenuActionOptions } from 'vs/platform/actions/common/actions';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
|
||||
export function fillInActions(menu: IMenu, options: IMenuActionOptions, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
|
||||
const groups = menu.getActions(options);
|
||||
if (groups.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let tuple of groups) {
|
||||
let [group, actions] = tuple;
|
||||
if (isPrimaryGroup(group)) {
|
||||
|
||||
const head = Array.isArray<IAction>(target) ? target : target.primary;
|
||||
|
||||
// split contributed actions at the point where order
|
||||
// changes form lt zero to gte
|
||||
let pivot = 0;
|
||||
for (; pivot < actions.length; pivot++) {
|
||||
if ((<MenuItemAction>actions[pivot]).order >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// prepend contributed actions with order lte zero
|
||||
head.unshift(...actions.slice(0, pivot));
|
||||
|
||||
// find the first separator which marks the end of the
|
||||
// navigation group - might be the whole array length
|
||||
let sep = 0;
|
||||
while (sep < head.length) {
|
||||
if (head[sep] instanceof Separator) {
|
||||
break;
|
||||
}
|
||||
sep++;
|
||||
}
|
||||
// append contributed actions with order gt zero
|
||||
head.splice(sep, 0, ...actions.slice(pivot));
|
||||
|
||||
} else {
|
||||
const to = Array.isArray<IAction>(target) ? target : target.secondary;
|
||||
|
||||
if (to.length > 0) {
|
||||
to.push(new Separator());
|
||||
}
|
||||
|
||||
to.push(...actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function createActionItem(action: IAction, keybindingService: IKeybindingService, messageService: IMessageService): ActionItem {
|
||||
if (action instanceof MenuItemAction) {
|
||||
return new MenuItemActionItem(action, keybindingService, messageService);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
const _altKey = new class extends Emitter<boolean> {
|
||||
|
||||
private _subscriptions: IDisposable[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._subscriptions.push(domEvent(document.body, 'keydown')(e => this.fire(e.altKey)));
|
||||
this._subscriptions.push(domEvent(document.body, 'keyup')(e => this.fire(false)));
|
||||
this._subscriptions.push(domEvent(document.body, 'mouseleave')(e => this.fire(false)));
|
||||
this._subscriptions.push(domEvent(document.body, 'blur')(e => this.fire(false)));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
this._subscriptions = dispose(this._subscriptions);
|
||||
}
|
||||
};
|
||||
|
||||
export class MenuItemActionItem extends ActionItem {
|
||||
|
||||
private _wantsAltCommand: boolean = false;
|
||||
|
||||
constructor(
|
||||
action: MenuItemAction,
|
||||
@IKeybindingService private _keybindingService: IKeybindingService,
|
||||
@IMessageService protected _messageService: IMessageService
|
||||
) {
|
||||
super(undefined, action, { icon: !!action.class, label: !action.class });
|
||||
}
|
||||
|
||||
protected get _commandAction(): IAction {
|
||||
return this._wantsAltCommand && (<MenuItemAction>this._action).alt || this._action;
|
||||
}
|
||||
|
||||
onClick(event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.actionRunner.run(this._commandAction)
|
||||
.done(undefined, err => this._messageService.show(Severity.Error, err));
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
let mouseOver = false;
|
||||
let altDown = false;
|
||||
|
||||
const updateAltState = () => {
|
||||
const wantsAltCommand = mouseOver && altDown;
|
||||
if (wantsAltCommand !== this._wantsAltCommand) {
|
||||
this._wantsAltCommand = wantsAltCommand;
|
||||
this._updateLabel();
|
||||
this._updateTooltip();
|
||||
this._updateClass();
|
||||
}
|
||||
};
|
||||
|
||||
this._callOnDispose.push(_altKey.event(value => {
|
||||
altDown = value;
|
||||
updateAltState();
|
||||
}));
|
||||
|
||||
this._callOnDispose.push(domEvent(container, 'mouseleave')(_ => {
|
||||
mouseOver = false;
|
||||
updateAltState();
|
||||
}));
|
||||
|
||||
this._callOnDispose.push(domEvent(container, 'mouseenter')(e => {
|
||||
mouseOver = true;
|
||||
updateAltState();
|
||||
}));
|
||||
}
|
||||
|
||||
_updateLabel(): void {
|
||||
if (this.options.label) {
|
||||
this.$e.text(this._commandAction.label);
|
||||
}
|
||||
}
|
||||
|
||||
_updateTooltip(): void {
|
||||
const element = this.$e.getHTMLElement();
|
||||
const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id);
|
||||
const keybindingLabel = keybinding && keybinding.getLabel();
|
||||
|
||||
element.title = keybindingLabel
|
||||
? localize('titleAndKb', "{0} ({1})", this._commandAction.label, keybindingLabel)
|
||||
: this._commandAction.label;
|
||||
}
|
||||
|
||||
_updateClass(): void {
|
||||
if (this.options.icon) {
|
||||
const element = this.$e.getHTMLElement();
|
||||
if (this._commandAction !== this._action) {
|
||||
element.classList.remove(this._action.class);
|
||||
} else if ((<MenuItemAction>this._action).alt) {
|
||||
element.classList.remove((<MenuItemAction>this._action).alt.class);
|
||||
}
|
||||
element.classList.add('icon', this._commandAction.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
247
src/vs/platform/actions/common/actions.ts
Normal file
247
src/vs/platform/actions/common/actions.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IConstructorSignature2, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import Event from 'vs/base/common/event';
|
||||
|
||||
export interface ILocalizedString {
|
||||
value: string;
|
||||
original: string;
|
||||
}
|
||||
|
||||
export interface ICommandAction {
|
||||
id: string;
|
||||
title: string | ILocalizedString;
|
||||
category?: string | ILocalizedString;
|
||||
iconClass?: string;
|
||||
}
|
||||
|
||||
export interface IMenuItem {
|
||||
command: ICommandAction;
|
||||
alt?: ICommandAction;
|
||||
when?: ContextKeyExpr;
|
||||
group?: 'navigation' | string;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export class MenuId {
|
||||
|
||||
static readonly EditorTitle = new MenuId('1');
|
||||
static readonly EditorTitleContext = new MenuId('2');
|
||||
static readonly EditorContext = new MenuId('3');
|
||||
static readonly ExplorerContext = new MenuId('4');
|
||||
static readonly ProblemsPanelContext = new MenuId('5');
|
||||
static readonly DebugVariablesContext = new MenuId('6');
|
||||
static readonly DebugWatchContext = new MenuId('7');
|
||||
static readonly DebugCallStackContext = new MenuId('8');
|
||||
static readonly DebugBreakpointsContext = new MenuId('9');
|
||||
static readonly DebugConsoleContext = new MenuId('10');
|
||||
static readonly SCMTitle = new MenuId('11');
|
||||
static readonly SCMResourceGroupContext = new MenuId('12');
|
||||
static readonly SCMResourceContext = new MenuId('13');
|
||||
static readonly CommandPalette = new MenuId('14');
|
||||
static readonly ViewTitle = new MenuId('15');
|
||||
static readonly ViewItemContext = new MenuId('16');
|
||||
|
||||
constructor(private _id: string) {
|
||||
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IMenuActionOptions {
|
||||
arg?: any;
|
||||
shouldForwardArgs?: boolean;
|
||||
}
|
||||
|
||||
export interface IMenu extends IDisposable {
|
||||
onDidChange: Event<IMenu>;
|
||||
getActions(options?: IMenuActionOptions): [string, MenuItemAction[]][];
|
||||
}
|
||||
|
||||
export const IMenuService = createDecorator<IMenuService>('menuService');
|
||||
|
||||
export interface IMenuService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
createMenu(id: MenuId, scopedKeybindingService: IContextKeyService): IMenu;
|
||||
}
|
||||
|
||||
export interface IMenuRegistry {
|
||||
addCommand(userCommand: ICommandAction): boolean;
|
||||
getCommand(id: string): ICommandAction;
|
||||
appendMenuItem(menu: MenuId, item: IMenuItem): IDisposable;
|
||||
getMenuItems(loc: MenuId): IMenuItem[];
|
||||
}
|
||||
|
||||
export const MenuRegistry: IMenuRegistry = new class {
|
||||
|
||||
private _commands: { [id: string]: ICommandAction } = Object.create(null);
|
||||
|
||||
private _menuItems: { [loc: string]: IMenuItem[] } = Object.create(null);
|
||||
|
||||
addCommand(command: ICommandAction): boolean {
|
||||
const old = this._commands[command.id];
|
||||
this._commands[command.id] = command;
|
||||
return old !== void 0;
|
||||
}
|
||||
|
||||
getCommand(id: string): ICommandAction {
|
||||
return this._commands[id];
|
||||
}
|
||||
|
||||
appendMenuItem({ id }: MenuId, item: IMenuItem): IDisposable {
|
||||
let array = this._menuItems[id];
|
||||
if (!array) {
|
||||
this._menuItems[id] = array = [item];
|
||||
} else {
|
||||
array.push(item);
|
||||
}
|
||||
return {
|
||||
dispose() {
|
||||
const idx = array.indexOf(item);
|
||||
if (idx >= 0) {
|
||||
array.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getMenuItems({ id }: MenuId): IMenuItem[] {
|
||||
const result = this._menuItems[id] || [];
|
||||
|
||||
if (id === MenuId.CommandPalette.id) {
|
||||
// CommandPalette is special because it shows
|
||||
// all commands by default
|
||||
this._appendImplicitItems(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _appendImplicitItems(result: IMenuItem[]) {
|
||||
const set = new Set<string>();
|
||||
for (const { command, alt } of result) {
|
||||
set.add(command.id);
|
||||
if (alt) {
|
||||
set.add(alt.id);
|
||||
}
|
||||
}
|
||||
for (let id in this._commands) {
|
||||
if (!set.has(id)) {
|
||||
result.push({ command: this._commands[id] });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class ExecuteCommandAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@ICommandService private _commandService: ICommandService) {
|
||||
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(...args: any[]): TPromise<any> {
|
||||
return this._commandService.executeCommand(this.id, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
export class MenuItemAction extends ExecuteCommandAction {
|
||||
|
||||
private _options: IMenuActionOptions;
|
||||
|
||||
readonly item: ICommandAction;
|
||||
readonly alt: MenuItemAction;
|
||||
|
||||
constructor(
|
||||
item: ICommandAction,
|
||||
alt: ICommandAction,
|
||||
options: IMenuActionOptions,
|
||||
@ICommandService commandService: ICommandService
|
||||
) {
|
||||
typeof item.title === 'string' ? super(item.id, item.title, commandService) : super(item.id, item.title.value, commandService);
|
||||
this._cssClass = item.iconClass;
|
||||
this._enabled = true;
|
||||
this._options = options || {};
|
||||
|
||||
this.item = item;
|
||||
this.alt = alt ? new MenuItemAction(alt, undefined, this._options, commandService) : undefined;
|
||||
}
|
||||
|
||||
run(...args: any[]): TPromise<any> {
|
||||
let runArgs = [];
|
||||
|
||||
if (this._options.arg) {
|
||||
runArgs = [...runArgs, this._options.arg];
|
||||
}
|
||||
|
||||
if (this._options.shouldForwardArgs) {
|
||||
runArgs = [...runArgs, ...args];
|
||||
}
|
||||
|
||||
return super.run(...runArgs);
|
||||
}
|
||||
}
|
||||
|
||||
export class SyncActionDescriptor {
|
||||
|
||||
private _descriptor: SyncDescriptor0<Action>;
|
||||
|
||||
private _id: string;
|
||||
private _label: string;
|
||||
private _keybindings: IKeybindings;
|
||||
private _keybindingContext: ContextKeyExpr;
|
||||
private _keybindingWeight: number;
|
||||
|
||||
constructor(ctor: IConstructorSignature2<string, string, Action>,
|
||||
id: string, label: string, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number
|
||||
) {
|
||||
this._id = id;
|
||||
this._label = label;
|
||||
this._keybindings = keybindings;
|
||||
this._keybindingContext = keybindingContext;
|
||||
this._keybindingWeight = keybindingWeight;
|
||||
this._descriptor = createSyncDescriptor(ctor, this._id, this._label);
|
||||
}
|
||||
|
||||
public get syncDescriptor(): SyncDescriptor0<Action> {
|
||||
return this._descriptor;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
public get keybindings(): IKeybindings {
|
||||
return this._keybindings;
|
||||
}
|
||||
|
||||
public get keybindingContext(): ContextKeyExpr {
|
||||
return this._keybindingContext;
|
||||
}
|
||||
|
||||
public get keybindingWeight(): number {
|
||||
return this._keybindingWeight;
|
||||
}
|
||||
}
|
||||
144
src/vs/platform/actions/common/menu.ts
Normal file
144
src/vs/platform/actions/common/menu.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MenuId, MenuRegistry, MenuItemAction, IMenu, IMenuItem, IMenuActionOptions } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
||||
type MenuItemGroup = [string, IMenuItem[]];
|
||||
|
||||
export class Menu implements IMenu {
|
||||
|
||||
private _menuGroups: MenuItemGroup[] = [];
|
||||
private _disposables: IDisposable[] = [];
|
||||
private _onDidChange = new Emitter<IMenu>();
|
||||
|
||||
constructor(
|
||||
private _id: MenuId,
|
||||
startupSignal: TPromise<boolean>,
|
||||
@ICommandService private _commandService: ICommandService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService
|
||||
) {
|
||||
startupSignal.then(_ => {
|
||||
const menuItems = MenuRegistry.getMenuItems(_id);
|
||||
const keysFilter = new Set<string>();
|
||||
|
||||
let group: MenuItemGroup;
|
||||
menuItems.sort(Menu._compareMenuItems);
|
||||
|
||||
for (let item of menuItems) {
|
||||
// group by groupId
|
||||
const groupName = item.group;
|
||||
if (!group || group[0] !== groupName) {
|
||||
group = [groupName, []];
|
||||
this._menuGroups.push(group);
|
||||
}
|
||||
group[1].push(item);
|
||||
|
||||
// keep keys for eventing
|
||||
Menu._fillInKbExprKeys(item.when, keysFilter);
|
||||
}
|
||||
|
||||
// subscribe to context changes
|
||||
this._disposables.push(this._contextKeyService.onDidChangeContext(keys => {
|
||||
if (!keys) {
|
||||
return;
|
||||
}
|
||||
for (let k of keys) {
|
||||
if (keysFilter.has(k)) {
|
||||
this._onDidChange.fire();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._onDidChange.fire(this);
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposables = dispose(this._disposables);
|
||||
this._onDidChange.dispose();
|
||||
}
|
||||
|
||||
get onDidChange(): Event<IMenu> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
getActions(options: IMenuActionOptions): [string, MenuItemAction[]][] {
|
||||
const result: [string, MenuItemAction[]][] = [];
|
||||
for (let group of this._menuGroups) {
|
||||
const [id, items] = group;
|
||||
const activeActions: MenuItemAction[] = [];
|
||||
for (const item of items) {
|
||||
if (this._contextKeyService.contextMatchesRules(item.when)) {
|
||||
const action = new MenuItemAction(item.command, item.alt, options, this._commandService);
|
||||
action.order = item.order; //TODO@Ben order is menu item property, not an action property
|
||||
activeActions.push(action);
|
||||
}
|
||||
}
|
||||
if (activeActions.length > 0) {
|
||||
result.push([id, activeActions]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _fillInKbExprKeys(exp: ContextKeyExpr, set: Set<string>): void {
|
||||
if (exp) {
|
||||
for (let key of exp.keys()) {
|
||||
set.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static _compareMenuItems(a: IMenuItem, b: IMenuItem): number {
|
||||
|
||||
let aGroup = a.group;
|
||||
let bGroup = b.group;
|
||||
|
||||
if (aGroup !== bGroup) {
|
||||
|
||||
// Falsy groups come last
|
||||
if (!aGroup) {
|
||||
return 1;
|
||||
} else if (!bGroup) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 'navigation' group comes first
|
||||
if (aGroup === 'navigation') {
|
||||
return -1;
|
||||
} else if (bGroup === 'navigation') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// lexical sort for groups
|
||||
let value = aGroup.localeCompare(bGroup);
|
||||
if (value !== 0) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// sort on priority - default is 0
|
||||
let aPrio = a.order || 0;
|
||||
let bPrio = b.order || 0;
|
||||
if (aPrio < bPrio) {
|
||||
return -1;
|
||||
} else if (aPrio > bPrio) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// sort on titles
|
||||
const aTitle = typeof a.command.title === 'string' ? a.command.title : a.command.title.value;
|
||||
const bTitle = typeof b.command.title === 'string' ? b.command.title : b.command.title.value;
|
||||
return aTitle.localeCompare(bTitle);
|
||||
}
|
||||
}
|
||||
28
src/vs/platform/actions/common/menuService.ts
Normal file
28
src/vs/platform/actions/common/menuService.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { Menu } from 'vs/platform/actions/common/menu';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
||||
export class MenuService implements IMenuService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(
|
||||
@IExtensionService private _extensionService: IExtensionService,
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu {
|
||||
return new Menu(id, this._extensionService.onReady(), this._commandService, contextKeyService);
|
||||
}
|
||||
}
|
||||
368
src/vs/platform/actions/electron-browser/menusExtensionPoint.ts
Normal file
368
src/vs/platform/actions/electron-browser/menusExtensionPoint.ts
Normal file
@@ -0,0 +1,368 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { createCSSRule } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
|
||||
import { join } from 'path';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions';
|
||||
|
||||
namespace schema {
|
||||
|
||||
// --- menus contribution point
|
||||
|
||||
export interface IUserFriendlyMenuItem {
|
||||
command: string;
|
||||
alt?: string;
|
||||
when?: string;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
export function parseMenuId(value: string): MenuId {
|
||||
switch (value) {
|
||||
case 'commandPalette': return MenuId.CommandPalette;
|
||||
case 'editor/title': return MenuId.EditorTitle;
|
||||
case 'editor/context': return MenuId.EditorContext;
|
||||
case 'explorer/context': return MenuId.ExplorerContext;
|
||||
case 'editor/title/context': return MenuId.EditorTitleContext;
|
||||
case 'debug/callstack/context': return MenuId.DebugCallStackContext;
|
||||
case 'scm/title': return MenuId.SCMTitle;
|
||||
case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext;
|
||||
case 'scm/resourceState/context': return MenuId.SCMResourceContext;
|
||||
case 'view/title': return MenuId.ViewTitle;
|
||||
case 'view/item/context': return MenuId.ViewItemContext;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
export function isValidMenuItems(menu: IUserFriendlyMenuItem[], collector: ExtensionMessageCollector): boolean {
|
||||
if (!Array.isArray(menu)) {
|
||||
collector.error(localize('requirearray', "menu items must be an array"));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let item of menu) {
|
||||
if (typeof item.command !== 'string') {
|
||||
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
|
||||
return false;
|
||||
}
|
||||
if (item.alt && typeof item.alt !== 'string') {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt'));
|
||||
return false;
|
||||
}
|
||||
if (item.when && typeof item.when !== 'string') {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
|
||||
return false;
|
||||
}
|
||||
if (item.group && typeof item.group !== 'string') {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'group'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const menuItem: IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
command: {
|
||||
description: localize('vscode.extension.contributes.menuItem.command', 'Identifier of the command to execute. The command must be declared in the \'commands\'-section'),
|
||||
type: 'string'
|
||||
},
|
||||
alt: {
|
||||
description: localize('vscode.extension.contributes.menuItem.alt', 'Identifier of an alternative command to execute. The command must be declared in the \'commands\'-section'),
|
||||
type: 'string'
|
||||
},
|
||||
when: {
|
||||
description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'),
|
||||
type: 'string'
|
||||
},
|
||||
group: {
|
||||
description: localize('vscode.extension.contributes.menuItem.group', 'Group into which this command belongs'),
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const menusContribtion: IJSONSchema = {
|
||||
description: localize('vscode.extension.contributes.menus', "Contributes menu items to the editor"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
'commandPalette': {
|
||||
description: localize('menus.commandPalette', "The Command Palette"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'editor/title': {
|
||||
description: localize('menus.editorTitle', "The editor title menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'editor/context': {
|
||||
description: localize('menus.editorContext', "The editor context menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'explorer/context': {
|
||||
description: localize('menus.explorerContext', "The file explorer context menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'editor/title/context': {
|
||||
description: localize('menus.editorTabContext', "The editor tabs context menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'debug/callstack/context': {
|
||||
description: localize('menus.debugCallstackContext', "The debug callstack context menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'scm/title': {
|
||||
description: localize('menus.scmTitle', "The Source Control title menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'scm/resourceGroup/context': {
|
||||
description: localize('menus.resourceGroupContext', "The Source Control resource group context menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'scm/resourceState/context': {
|
||||
description: localize('menus.resourceStateContext', "The Source Control resource state context menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'view/title': {
|
||||
description: localize('view.viewTitle', "The contributed view title menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'view/item/context': {
|
||||
description: localize('view.itemContext', "The contributed view item context menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --- commands contribution point
|
||||
|
||||
export interface IUserFriendlyCommand {
|
||||
command: string;
|
||||
title: string | ILocalizedString;
|
||||
category?: string | ILocalizedString;
|
||||
icon?: IUserFriendlyIcon;
|
||||
}
|
||||
|
||||
export type IUserFriendlyIcon = string | { light: string; dark: string; };
|
||||
|
||||
export function isValidCommand(command: IUserFriendlyCommand, collector: ExtensionMessageCollector): boolean {
|
||||
if (!command) {
|
||||
collector.error(localize('nonempty', "expected non-empty value."));
|
||||
return false;
|
||||
}
|
||||
if (isFalsyOrWhitespace(command.command)) {
|
||||
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
|
||||
return false;
|
||||
}
|
||||
if (!isValidLocalizedString(command.title, collector, 'title')) {
|
||||
return false;
|
||||
}
|
||||
if (command.category && !isValidLocalizedString(command.category, collector, 'category')) {
|
||||
return false;
|
||||
}
|
||||
if (!isValidIcon(command.icon, collector)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isValidIcon(icon: IUserFriendlyIcon, collector: ExtensionMessageCollector): boolean {
|
||||
if (typeof icon === 'undefined') {
|
||||
return true;
|
||||
}
|
||||
if (typeof icon === 'string') {
|
||||
return true;
|
||||
} else if (typeof icon.dark === 'string' && typeof icon.light === 'string') {
|
||||
return true;
|
||||
}
|
||||
collector.error(localize('opticon', "property `icon` can be omitted or must be either a string or a literal like `{dark, light}`"));
|
||||
return false;
|
||||
}
|
||||
|
||||
function isValidLocalizedString(localized: string | ILocalizedString, collector: ExtensionMessageCollector, propertyName: string): boolean {
|
||||
if (typeof localized === 'undefined') {
|
||||
collector.error(localize('requireStringOrObject', "property `{0}` is mandatory and must be of type `string` or `object`", propertyName));
|
||||
return false;
|
||||
} else if (typeof localized === 'string' && isFalsyOrWhitespace(localized)) {
|
||||
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", propertyName));
|
||||
return false;
|
||||
} else if (typeof localized !== 'string' && (isFalsyOrWhitespace(localized.original) || isFalsyOrWhitespace(localized.value))) {
|
||||
collector.error(localize('requirestrings', "properties `{0}` and `{1}` are mandatory and must be of type `string`", `${propertyName}.value`, `${propertyName}.original`));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const commandType: IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
command: {
|
||||
description: localize('vscode.extension.contributes.commandType.command', 'Identifier of the command to execute'),
|
||||
type: 'string'
|
||||
},
|
||||
title: {
|
||||
description: localize('vscode.extension.contributes.commandType.title', 'Title by which the command is represented in the UI'),
|
||||
type: 'string'
|
||||
},
|
||||
category: {
|
||||
description: localize('vscode.extension.contributes.commandType.category', '(Optional) Category string by the command is grouped in the UI'),
|
||||
type: 'string'
|
||||
},
|
||||
icon: {
|
||||
description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path or a themable configuration'),
|
||||
anyOf: [{
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
light: {
|
||||
description: localize('vscode.extension.contributes.commandType.icon.light', 'Icon path when a light theme is used'),
|
||||
type: 'string'
|
||||
},
|
||||
dark: {
|
||||
description: localize('vscode.extension.contributes.commandType.icon.dark', 'Icon path when a dark theme is used'),
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const commandsContribution: IJSONSchema = {
|
||||
description: localize('vscode.extension.contributes.commands', "Contributes commands to the command palette."),
|
||||
oneOf: [
|
||||
commandType,
|
||||
{
|
||||
type: 'array',
|
||||
items: commandType
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
ExtensionsRegistry.registerExtensionPoint<schema.IUserFriendlyCommand | schema.IUserFriendlyCommand[]>('commands', [], schema.commandsContribution).setHandler(extensions => {
|
||||
|
||||
const ids = new IdGenerator('contrib-cmd-icon-');
|
||||
|
||||
function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser<any>) {
|
||||
|
||||
if (!schema.isValidCommand(userFriendlyCommand, extension.collector)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { icon, category, title, command } = userFriendlyCommand;
|
||||
let iconClass: string;
|
||||
if (icon) {
|
||||
iconClass = ids.nextId();
|
||||
if (typeof icon === 'string') {
|
||||
const path = join(extension.description.extensionFolderPath, icon);
|
||||
createCSSRule(`.icon.${iconClass}`, `background-image: url("${URI.file(path).toString()}")`);
|
||||
} else {
|
||||
const light = join(extension.description.extensionFolderPath, icon.light);
|
||||
const dark = join(extension.description.extensionFolderPath, icon.dark);
|
||||
createCSSRule(`.icon.${iconClass}`, `background-image: url("${URI.file(light).toString()}")`);
|
||||
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${URI.file(dark).toString()}")`);
|
||||
}
|
||||
}
|
||||
|
||||
if (MenuRegistry.addCommand({ id: command, title, category, iconClass })) {
|
||||
extension.collector.info(localize('dup', "Command `{0}` appears multiple times in the `commands` section.", userFriendlyCommand.command));
|
||||
}
|
||||
}
|
||||
|
||||
for (let extension of extensions) {
|
||||
const { value } = extension;
|
||||
if (Array.isArray<schema.IUserFriendlyCommand>(value)) {
|
||||
for (let command of value) {
|
||||
handleCommand(command, extension);
|
||||
}
|
||||
} else {
|
||||
handleCommand(value, extension);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyMenuItem[] }>('menus', [], schema.menusContribtion).setHandler(extensions => {
|
||||
for (let extension of extensions) {
|
||||
const { value, collector } = extension;
|
||||
|
||||
forEach(value, entry => {
|
||||
if (!schema.isValidMenuItems(entry.value, collector)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menu = schema.parseMenuId(entry.key);
|
||||
if (!menu) {
|
||||
collector.warn(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry.key));
|
||||
return;
|
||||
}
|
||||
|
||||
for (let item of entry.value) {
|
||||
let command = MenuRegistry.getCommand(item.command);
|
||||
let alt = item.alt && MenuRegistry.getCommand(item.alt);
|
||||
|
||||
if (!command) {
|
||||
collector.error(localize('missing.command', "Menu item references a command `{0}` which is not defined in the 'commands' section.", item.command));
|
||||
continue;
|
||||
}
|
||||
if (item.alt && !alt) {
|
||||
collector.warn(localize('missing.altCommand', "Menu item references an alt-command `{0}` which is not defined in the 'commands' section.", item.alt));
|
||||
}
|
||||
if (item.command === item.alt) {
|
||||
collector.info(localize('dupe.command', "Menu item references the same command as default and alt-command"));
|
||||
}
|
||||
|
||||
if (item.alt && menu !== MenuId.EditorTitle && item.group !== 'navigation') {
|
||||
collector.info(localize('nosupport.altCommand', "Sorry, but currently only the 'navigation' group of the 'editor/title' menu supports alt-commands"));
|
||||
}
|
||||
|
||||
let group: string;
|
||||
let order: number;
|
||||
if (item.group) {
|
||||
const idx = item.group.lastIndexOf('@');
|
||||
if (idx > 0) {
|
||||
group = item.group.substr(0, idx);
|
||||
order = Number(item.group.substr(idx + 1)) || undefined;
|
||||
} else {
|
||||
group = item.group;
|
||||
}
|
||||
}
|
||||
|
||||
MenuRegistry.appendMenuItem(menu, {
|
||||
command,
|
||||
alt,
|
||||
group,
|
||||
order,
|
||||
when: ContextKeyExpr.deserialize(item.when)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
229
src/vs/platform/actions/test/common/menuService.test.ts
Normal file
229
src/vs/platform/actions/test/common/menuService.test.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { MenuService } from 'vs/platform/actions/common/menuService';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { NullCommandService } from 'vs/platform/commands/common/commands';
|
||||
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||
import { IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtensionPointContribution, IExtensionDescription, IExtensionsStatus, IExtensionService, ActivationTimes } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
// --- service instances
|
||||
|
||||
class MockExtensionService implements IExtensionService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
public activateByEvent(activationEvent: string): TPromise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public onReady(): TPromise<boolean> {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
public getExtensions(): TPromise<IExtensionDescription[]> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): TPromise<ExtensionPointContribution<T>[]> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public getExtensionsActivationTimes(): { [id: string]: ActivationTimes; } {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public restartExtensionHost(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
const extensionService = new MockExtensionService();
|
||||
|
||||
const contextKeyService = new class extends MockContextKeyService {
|
||||
contextMatchesRules() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// --- tests
|
||||
|
||||
suite('MenuService', function () {
|
||||
|
||||
let menuService: MenuService;
|
||||
let disposables: IDisposable[];
|
||||
|
||||
setup(function () {
|
||||
menuService = new MenuService(extensionService, NullCommandService);
|
||||
disposables = [];
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
dispose(disposables);
|
||||
});
|
||||
|
||||
test('group sorting', function () {
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'one', title: 'FOO' },
|
||||
group: '0_hello'
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'two', title: 'FOO' },
|
||||
group: 'hello'
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'three', title: 'FOO' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'four', title: 'FOO' },
|
||||
group: ''
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'five', title: 'FOO' },
|
||||
group: 'navigation'
|
||||
}));
|
||||
|
||||
const groups = menuService.createMenu(MenuId.ExplorerContext, contextKeyService).getActions();
|
||||
|
||||
assert.equal(groups.length, 5);
|
||||
const [one, two, three, four, five] = groups;
|
||||
|
||||
assert.equal(one[0], 'navigation');
|
||||
assert.equal(two[0], '0_hello');
|
||||
assert.equal(three[0], 'hello');
|
||||
assert.equal(four[0], 'Hello');
|
||||
assert.equal(five[0], '');
|
||||
});
|
||||
|
||||
test('in group sorting, by title', function () {
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'a', title: 'aaa' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'b', title: 'fff' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'c', title: 'zzz' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
const groups = menuService.createMenu(MenuId.ExplorerContext, contextKeyService).getActions();
|
||||
|
||||
assert.equal(groups.length, 1);
|
||||
const [[, actions]] = groups;
|
||||
|
||||
assert.equal(actions.length, 3);
|
||||
const [one, two, three] = actions;
|
||||
assert.equal(one.id, 'a');
|
||||
assert.equal(two.id, 'b');
|
||||
assert.equal(three.id, 'c');
|
||||
});
|
||||
|
||||
test('in group sorting, by title and order', function () {
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'a', title: 'aaa' },
|
||||
group: 'Hello',
|
||||
order: 10
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'b', title: 'fff' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'c', title: 'zzz' },
|
||||
group: 'Hello',
|
||||
order: -1
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'd', title: 'yyy' },
|
||||
group: 'Hello',
|
||||
order: -1
|
||||
}));
|
||||
|
||||
const groups = menuService.createMenu(MenuId.ExplorerContext, contextKeyService).getActions();
|
||||
|
||||
assert.equal(groups.length, 1);
|
||||
const [[, actions]] = groups;
|
||||
|
||||
assert.equal(actions.length, 4);
|
||||
const [one, two, three, four] = actions;
|
||||
assert.equal(one.id, 'd');
|
||||
assert.equal(two.id, 'c');
|
||||
assert.equal(three.id, 'b');
|
||||
assert.equal(four.id, 'a');
|
||||
});
|
||||
|
||||
|
||||
test('in group sorting, special: navigation', function () {
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'a', title: 'aaa' },
|
||||
group: 'navigation',
|
||||
order: 1.3
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'b', title: 'fff' },
|
||||
group: 'navigation',
|
||||
order: 1.2
|
||||
}));
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: { id: 'c', title: 'zzz' },
|
||||
group: 'navigation',
|
||||
order: 1.1
|
||||
}));
|
||||
|
||||
const groups = menuService.createMenu(MenuId.ExplorerContext, contextKeyService).getActions();
|
||||
|
||||
assert.equal(groups.length, 1);
|
||||
const [[, actions]] = groups;
|
||||
|
||||
assert.equal(actions.length, 3);
|
||||
const [one, two, three] = actions;
|
||||
assert.equal(one.id, 'c');
|
||||
assert.equal(two.id, 'b');
|
||||
assert.equal(three.id, 'a');
|
||||
});
|
||||
|
||||
test('special MenuId palette', function () {
|
||||
|
||||
disposables.push(MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: { id: 'a', title: 'Explicit' }
|
||||
}));
|
||||
|
||||
MenuRegistry.addCommand({ id: 'b', title: 'Implicit' });
|
||||
|
||||
const [first, second] = MenuRegistry.getMenuItems(MenuId.CommandPalette);
|
||||
assert.equal(first.command.id, 'a');
|
||||
assert.equal(first.command.title, 'Explicit');
|
||||
|
||||
assert.equal(second.command.id, 'b');
|
||||
assert.equal(second.command.title, 'Implicit');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user