Merge VS Code 1.21 source code (#1067)

* Initial VS Code 1.21 file copy with patches

* A few more merges

* Post npm install

* Fix batch of build breaks

* Fix more build breaks

* Fix more build errors

* Fix more build breaks

* Runtime fixes 1

* Get connection dialog working with some todos

* Fix a few packaging issues

* Copy several node_modules to package build to fix loader issues

* Fix breaks from master

* A few more fixes

* Make tests pass

* First pass of license header updates

* Second pass of license header updates

* Fix restore dialog issues

* Remove add additional themes menu items

* fix select box issues where the list doesn't show up

* formatting

* Fix editor dispose issue

* Copy over node modules to correct location on all platforms
This commit is contained in:
Karl Burtram
2018-04-04 15:27:51 -07:00
committed by GitHub
parent 5fba3e31b4
commit dafb780987
9412 changed files with 141255 additions and 98813 deletions

View File

@@ -7,24 +7,72 @@
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 { IMenu, MenuItemAction, IMenuActionOptions, ICommandAction } from 'vs/platform/actions/common/actions';
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';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { memoize } from 'vs/base/common/decorators';
import { IdGenerator } from 'vs/base/common/idGenerator';
import { createCSSRule } from 'vs/base/browser/dom';
import URI from 'vs/base/common/uri';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { isWindows } from 'vs/base/common/platform';
// The alternative key on all platforms is alt. On windows we also support shift as an alternative key #44136
class AlternativeKeyEmitter extends Emitter<boolean> {
export function fillInActions(menu: IMenu, options: IMenuActionOptions, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
private _subscriptions: IDisposable[] = [];
private _isPressed: boolean;
private constructor(contextMenuService: IContextMenuService) {
super();
this._subscriptions.push(domEvent(document.body, 'keydown')(e => {
this.isPressed = e.altKey || (isWindows && e.shiftKey);
}));
this._subscriptions.push(domEvent(document.body, 'keyup')(e => this.isPressed = false));
this._subscriptions.push(domEvent(document.body, 'mouseleave')(e => this.isPressed = false));
this._subscriptions.push(domEvent(document.body, 'blur')(e => this.isPressed = false));
// Workaround since we do not get any events while a context menu is shown
this._subscriptions.push(contextMenuService.onDidContextMenu(() => this.isPressed = false));
}
get isPressed(): boolean {
return this._isPressed;
}
set isPressed(value: boolean) {
this._isPressed = value;
this.fire(this._isPressed);
}
@memoize
static getInstance(contextMenuService: IContextMenuService) {
return new AlternativeKeyEmitter(contextMenuService);
}
dispose() {
super.dispose();
this._subscriptions = dispose(this._subscriptions);
}
}
export function fillInActions(menu: IMenu, options: IMenuActionOptions, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, contextMenuService: IContextMenuService, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
const groups = menu.getActions(options);
if (groups.length === 0) {
return;
}
const getAlternativeActions = AlternativeKeyEmitter.getInstance(contextMenuService).isPressed;
for (let tuple of groups) {
let [group, actions] = tuple;
if (getAlternativeActions) {
actions = actions.map(a => !!a.alt ? a.alt : a);
}
if (isPrimaryGroup(group)) {
const head = Array.isArray<IAction>(target) ? target : target.primary;
@@ -65,43 +113,29 @@ export function fillInActions(menu: IMenu, options: IMenuActionOptions, target:
}
export function createActionItem(action: IAction, keybindingService: IKeybindingService, messageService: IMessageService): ActionItem {
export function createActionItem(action: IAction, keybindingService: IKeybindingService, notificationService: INotificationService, contextMenuService: IContextMenuService): ActionItem {
if (action instanceof MenuItemAction) {
return new MenuItemActionItem(action, keybindingService, messageService);
return new MenuItemActionItem(action, keybindingService, notificationService, contextMenuService);
}
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);
}
};
const ids = new IdGenerator('menu-item-action-item-icon-');
export class MenuItemActionItem extends ActionItem {
static readonly ICON_PATH_TO_CSS_RULES: Map<string /* path*/, string /* CSS rule */> = new Map<string, string>();
private _wantsAltCommand: boolean = false;
private _itemClassDispose: IDisposable;
constructor(
action: MenuItemAction,
@IKeybindingService private _keybindingService: IKeybindingService,
@IMessageService protected _messageService: IMessageService
public _action: MenuItemAction,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@INotificationService protected _notificationService: INotificationService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService
) {
super(undefined, action, { icon: !!action.class, label: !action.class });
super(undefined, _action, { icon: !!(_action.class || _action.item.iconPath), label: !_action.class && !_action.item.iconPath });
}
protected get _commandAction(): IAction {
@@ -113,17 +147,19 @@ export class MenuItemActionItem extends ActionItem {
event.stopPropagation();
this.actionRunner.run(this._commandAction)
.done(undefined, err => this._messageService.show(Severity.Error, err));
.done(undefined, err => this._notificationService.error(err));
}
render(container: HTMLElement): void {
super.render(container);
this._updateItemClass(this._action.item);
let mouseOver = false;
let altDown = false;
let alternativeKeyDown = false;
const updateAltState = () => {
const wantsAltCommand = mouseOver && altDown;
const wantsAltCommand = mouseOver && alternativeKeyDown;
if (wantsAltCommand !== this._wantsAltCommand) {
this._wantsAltCommand = wantsAltCommand;
this._updateLabel();
@@ -132,8 +168,8 @@ export class MenuItemActionItem extends ActionItem {
}
};
this._callOnDispose.push(_altKey.event(value => {
altDown = value;
this._callOnDispose.push(AlternativeKeyEmitter.getInstance(this._contextMenuService).event(value => {
alternativeKeyDown = value;
updateAltState();
}));
@@ -166,13 +202,55 @@ export class MenuItemActionItem extends ActionItem {
_updateClass(): void {
if (this.options.icon) {
const element = this.$e.getHTMLElement();
if (this._commandAction !== this._action) {
element.classList.remove(this._action.class);
this._updateItemClass(this._action.alt.item);
} else if ((<MenuItemAction>this._action).alt) {
element.classList.remove((<MenuItemAction>this._action).alt.class);
this._updateItemClass(this._action.item);
}
element.classList.add('icon', this._commandAction.class);
}
}
_updateItemClass(item: ICommandAction): void {
dispose(this._itemClassDispose);
this._itemClassDispose = undefined;
if (item.iconPath) {
let iconClass: string;
if (MenuItemActionItem.ICON_PATH_TO_CSS_RULES.has(item.iconPath.dark)) {
iconClass = MenuItemActionItem.ICON_PATH_TO_CSS_RULES.get(item.iconPath.dark);
} else {
iconClass = ids.nextId();
createCSSRule(`.icon.${iconClass}`, `background-image: url("${URI.file(item.iconPath.light || item.iconPath.dark).toString()}")`);
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${URI.file(item.iconPath.dark).toString()}")`);
MenuItemActionItem.ICON_PATH_TO_CSS_RULES.set(item.iconPath.dark, iconClass);
}
this.$e.getHTMLElement().classList.add('icon', iconClass);
this._itemClassDispose = { dispose: () => this.$e.getHTMLElement().classList.remove('icon', iconClass) };
}
}
dispose(): void {
if (this._itemClassDispose) {
dispose(this._itemClassDispose);
this._itemClassDispose = undefined;
}
super.dispose();
}
}
// Need to subclass MenuItemActionItem in order to respect
// the action context coming from any action bar, without breaking
// existing users
export class ContextAwareMenuItemActionItem extends MenuItemActionItem {
onClick(event: MouseEvent): void {
event.preventDefault();
event.stopPropagation();
this.actionRunner.run(this._commandAction, this._context)
.done(undefined, err => this._notificationService.error(err));
}
}

View File

@@ -23,8 +23,8 @@ export interface ICommandAction {
id: string;
title: string | ILocalizedString;
category?: string | ILocalizedString;
iconClass?: string;
iconPath?: string;
iconPath?: { dark: string; light?: string; };
precondition?: ContextKeyExpr;
}
export interface IMenuItem {
@@ -43,6 +43,7 @@ export class MenuId {
static readonly EditorTitleContext = new MenuId();
static readonly EditorContext = new MenuId();
static readonly ExplorerContext = new MenuId();
static readonly OpenEditorsContext = new MenuId();
static readonly ProblemsPanelContext = new MenuId();
static readonly DebugVariablesContext = new MenuId();
static readonly DebugWatchContext = new MenuId();
@@ -153,7 +154,7 @@ export class ExecuteCommandAction extends Action {
constructor(
id: string,
label: string,
@ICommandService private _commandService: ICommandService) {
@ICommandService private readonly _commandService: ICommandService) {
super(id, label);
}
@@ -174,15 +175,16 @@ export class MenuItemAction extends ExecuteCommandAction {
item: ICommandAction,
alt: ICommandAction,
options: IMenuActionOptions,
@IContextKeyService contextKeyService: IContextKeyService,
@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._cssClass = undefined;
this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition);
this._options = options || {};
this.item = item;
this.alt = alt ? new MenuItemAction(alt, undefined, this._options, commandService) : undefined;
this.alt = alt ? new MenuItemAction(alt, undefined, this._options, contextKeyService, commandService) : undefined;
}
run(...args: any[]): TPromise<any> {

View File

@@ -23,8 +23,8 @@ export class Menu implements IMenu {
constructor(
id: MenuId,
startupSignal: TPromise<boolean>,
@ICommandService private _commandService: ICommandService,
@IContextKeyService private _contextKeyService: IContextKeyService
@ICommandService private readonly _commandService: ICommandService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService
) {
startupSignal.then(_ => {
const menuItems = MenuRegistry.getMenuItems(id);
@@ -73,7 +73,7 @@ export class Menu implements IMenu {
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);
const action = new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService);
action.order = item.order; //TODO@Ben order is menu item property, not an action property
activeActions.push(action);
}

View File

@@ -1,28 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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.whenInstalledExtensionsRegistered(), this._commandService, contextKeyService);
}
}

View File

@@ -1,384 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 'touchBar': return MenuId.TouchBarContext;
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/sourceControl': return MenuId.SCMSourceControl;
case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext;
case 'scm/resourceState/context': return MenuId.SCMResourceContext;
case 'scm/change/title': return MenuId.SCMChangeContext;
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
},
'touchBar': {
description: localize('menus.touchBar', "The touch bar (macOS only)"),
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/sourceControl': {
description: localize('menus.scmSourceControl', "The Source Control 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;
let iconPath: string;
if (icon) {
iconClass = ids.nextId();
if (typeof icon === 'string') {
iconPath = join(extension.description.extensionFolderPath, icon);
createCSSRule(`.icon.${iconClass}`, `background-image: url("${URI.file(iconPath).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()}")`);
iconPath = join(extension.description.extensionFolderPath, icon.dark);
}
}
if (MenuRegistry.addCommand({ id: command, title, category, iconClass, iconPath })) {
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)
});
}
});
}
});

View File

@@ -1,258 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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, ProfileSession } from 'vs/platform/extensions/common/extensions';
import Event, { Emitter } from 'vs/base/common/event';
// --- service instances
class MockExtensionService implements IExtensionService {
public _serviceBrand: any;
private _onDidRegisterExtensions = new Emitter<IExtensionDescription[]>();
public get onDidRegisterExtensions(): Event<IExtensionDescription[]> {
return this._onDidRegisterExtensions.event;
}
onDidChangeExtensionsStatus = null;
public activateByEvent(activationEvent: string): TPromise<void> {
throw new Error('Not implemented');
}
public whenInstalledExtensionsRegistered(): 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 startExtensionHostProfile(): TPromise<ProfileSession> {
throw new Error('Not implemented');
}
public restartExtensionHost(): void {
throw new Error('Method not implemented.');
}
public startExtensionHost(): void {
throw new Error('Method not implemented.');
}
public stopExtensionHost(): void {
throw new Error('Method not implemented.');
}
public getExtensionHostInformation(): any {
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' });
let foundA = false;
let foundB = false;
for (const item of MenuRegistry.getMenuItems(MenuId.CommandPalette)) {
if (item.command.id === 'a') {
assert.equal(item.command.title, 'Explicit');
foundA = true;
}
if (item.command.id === 'b') {
assert.equal(item.command.title, 'Implicit');
foundB = true;
}
}
assert.equal(foundA, true);
assert.equal(foundB, true);
});
});