From 22773cb8625f3456b19bdfa929e760e444341f2b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 22 Jun 2016 16:59:49 +0200 Subject: [PATCH] replace ActionService with MenuService, merge both contribution points, add user friendly objects --- extensions/markdown/package.json | 49 ++-- src/vs/platform/actions/common/actions.ts | 61 ++++- .../actions/common/commandsExtensionPoint.ts | 232 ----------------- .../actions/common/menusExtensionPoint.ts | 239 ++++++++++++------ .../platform/actions/common/menusService.ts | 98 +++++++ .../workbench/actionBarContributions.ts | 135 ---------- .../actions/workbench/actionsService.ts | 46 ---- src/vs/workbench/browser/workbench.ts | 9 +- .../quickopen/browser/commandsHandler.ts | 6 +- src/vs/workbench/workbench.main.ts | 2 +- 10 files changed, 357 insertions(+), 520 deletions(-) delete mode 100644 src/vs/platform/actions/common/commandsExtensionPoint.ts create mode 100644 src/vs/platform/actions/common/menusService.ts delete mode 100644 src/vs/platform/actions/workbench/actionBarContributions.ts delete mode 100644 src/vs/platform/actions/workbench/actionsService.ts diff --git a/extensions/markdown/package.json b/extensions/markdown/package.json index b4b372414fb..72f87a56764 100644 --- a/extensions/markdown/package.json +++ b/extensions/markdown/package.json @@ -4,7 +4,7 @@ "description": "Markdown for VS Code", "version": "0.2.0", "publisher": "Microsoft", - "aiKey":"AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "engines": { "vscode": "^1.0.0" }, @@ -49,9 +49,15 @@ "icon": { "light": "./media/Preview.svg", "dark": "./media/Preview_inverse.svg" - }, - "when": "resourceLangId == markdown", - "where": "editor/primary" + } + }, + { + "command": "markdown.showPreviewToSide", + "title": "%markdown.previewMarkdownSide.title%", + "icon": { + "light": "./media/Preview.svg", + "dark": "./media/Preview_inverse.svg" + } }, { "command": "markdown.showSource", @@ -60,17 +66,22 @@ "icon": { "light": "./media/ViewSource.svg", "dark": "./media/ViewSource_inverse.svg" - }, - "when": "resourceScheme == markdown", - "where": "editor/primary" - }, - { - "command": "markdown.showPreviewToSide", - "title": "%markdown.previewMarkdownSide.title%", - "when": "resourceLangId == markdown", - "where": "editor/secondary" + } } ], + "menus": { + "editor/primary": [ + { + "when": "resourceLangId == markdown", + "command": "markdown.showPreview", + "alt": "markdown.showPreviewToSide" + }, + { + "when": "resourceLangId == markdown", + "command": "markdown.showSource" + } + ] + }, "keybindings": [ { "command": "markdown.showPreview", @@ -83,17 +94,19 @@ "mac": "cmd+k v" } ], - "snippets": [{ - "language": "markdown", - "path": "./snippets/markdown.json" - }], + "snippets": [ + { + "language": "markdown", + "path": "./snippets/markdown.json" + } + ], "configuration": { "type": "object", "title": "Markdown preview configuration", "properties": { "markdown.styles": { "type": "array", - "default" : null, + "default": null, "description": "A list of URLs or local paths to CSS style sheets to use from the markdown preview." } } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index cd1d1bb4f51..34b287340b7 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -7,18 +7,65 @@ import Actions = require('vs/base/common/actions'); import WinJS = require('vs/base/common/winjs.base'); import Assert = require('vs/base/common/assert'); - import Descriptors = require('vs/platform/instantiation/common/descriptors'); import Instantiation = require('vs/platform/instantiation/common/instantiation'); -import {KbExpr, IKeybindings} from 'vs/platform/keybinding/common/keybindingService'; -import {createDecorator, ServiceIdentifier} from 'vs/platform/instantiation/common/instantiation'; +import {KbExpr, IKeybindings, IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; import {IDisposable} from 'vs/base/common/lifecycle'; +import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; + + +export interface CommandAction { + id: string; + title: string; + category: string; + lightThemeIcon: string; + darkThemeIcon: string; +} + +export interface MenuItem { + command: CommandAction; + alt?: CommandAction; + when?: KbExpr; +} + +export enum MenuLocation { + EditorPrimary = 1, + EditorSecondary = 2 +} + +export namespace MenuLocation { + export function parse(value: string): MenuLocation { + switch (value) { + case 'editor/primary': return MenuLocation.EditorPrimary; + case 'editor/secondary': return MenuLocation.EditorSecondary; + } + } +} + +export const IMenuService = createDecorator('menuService'); + +export interface IMenuService { -export let IActionsService = createDecorator('actionsService'); + serviceId: any; + + getMenuItems(loc: MenuLocation): MenuItem[]; + + getCommandActions(): CommandAction[]; +} -export interface IActionsService { - serviceId: ServiceIdentifier; - getActions(): Actions.IAction[]; +export class ExecuteCommandAction extends Actions.Action { + + constructor( + id: string, + label: string, + @IKeybindingService private _keybindingService: IKeybindingService) { + + super(id, label); + } + + run(...args: any[]): WinJS.TPromise { + return this._keybindingService.executeCommand(this.id, ...args); + } } export class SyncActionDescriptor { diff --git a/src/vs/platform/actions/common/commandsExtensionPoint.ts b/src/vs/platform/actions/common/commandsExtensionPoint.ts deleted file mode 100644 index 714fe301797..00000000000 --- a/src/vs/platform/actions/common/commandsExtensionPoint.ts +++ /dev/null @@ -1,232 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import {localize} from 'vs/nls'; -import {Action} from 'vs/base/common/actions'; -import {join} from 'vs/base/common/paths'; -import {values} from 'vs/base/common/collections'; -import {IJSONSchema} from 'vs/base/common/jsonSchema'; -import {IExtensionService, IExtensionDescription} from 'vs/platform/extensions/common/extensions'; -import {IKeybindingService, KbExpr} from 'vs/platform/keybinding/common/keybindingService'; -import {IExtensionPointUser, ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; - -export type Locations = 'editor/primary' | 'editor/secondary' | 'explorer/context'; - -interface ThemableIcon { - dark: string; - light: string; -} - -interface Command { - command: string; - title: string; - category?: string; - icon?: string | ThemableIcon; - when?: string; - where?: Locations; -} - -function isThemableIcon(thing: any): thing is ThemableIcon { - return typeof thing === 'object' && thing && typeof (thing).dark === 'string' && typeof (thing).light === 'string'; -} - -export class ParsedCommand { - - id: string; - title: string; - category: string; - lightThemeIcon: string; - darkThemeIcon: string; - when: KbExpr; - where: Locations; - - constructor(extension: IExtensionDescription, id: string, title: string, category: string, icon: string | { dark: string, light: string }, when: string, where: Locations) { - - this.id = id; - this.title = title; - this.category = category; - this.when = KbExpr.deserialize(when); - this.where = where; - - if (!icon) { - // nothing - } else if (isThemableIcon(icon)) { - this.lightThemeIcon = join(extension.extensionFolderPath, icon.light); - this.darkThemeIcon = join(extension.extensionFolderPath, icon.dark); - } else { - this.lightThemeIcon = this.darkThemeIcon = join(extension.extensionFolderPath, icon); - } - } - -} - -namespace validation { - - function isValidWhere(where: Locations, user: IExtensionPointUser): boolean { - if (where && ['editor/primary', 'editor/secondary', 'explorer/context'].indexOf(where) < 0) { - user.collector.error(localize('optwhere', "property `where` can be omitted or must be a valid enum value")); - return false; - } - return true; - } - - function isValidIcon(icon: string | ThemableIcon, user: IExtensionPointUser): boolean { - if (typeof icon === 'undefined') { - return true; - } - if (typeof icon === 'string') { - return true; - } - if (typeof icon === 'object' && typeof (icon).dark === 'string' && typeof (icon).light === 'string') { - return true; - } - user.collector.error(localize('opticon', "property `icon` can be omitted or must be either a string or a literal like `{dark, light}`")); - return false; - } - - export function isValidCommand(candidate: Command, user: IExtensionPointUser): boolean { - if (!candidate) { - user.collector.error(localize('nonempty', "expected non-empty value.")); - return false; - } - if (typeof candidate.command !== 'string') { - user.collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command')); - return false; - } - if (typeof candidate.title !== 'string') { - user.collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'title')); - return false; - } - if (candidate.category && typeof candidate.category !== 'string') { - user.collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'category')); - return false; - } - if (candidate.when && typeof candidate.when !== 'string') { - user.collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when')); - return false; - } - if (!isValidIcon(candidate.icon, user)) { - return false; - } - if (!isValidWhere(candidate.where, user)) { - return false; - } - return true; - } -} - - -namespace schema { - - 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: [ - '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' - } - } - } - ] - }, - when: { - description: localize('vscode.extension.contributes.commandType.context.when', "Condition that must be met in order to show the command."), - type: 'string' - }, - where: { - description: localize('vscode.extension.contributes.commandType.context.where', "Menus and tool bars to which commands can be added, e.g. `editor title actions` or `explorer context menu`"), - enum: [ - 'editor/primary', - 'editor/secondary' - ] - } - } - }; - - export const commandContribution: IJSONSchema = { - description: localize('vscode.extension.contributes.commands', "Contributes commands to the command palette."), - oneOf: [ - commandType, - { - type: 'array', - items: commandType - } - ] - }; -} - -export const commands: ParsedCommand[] = []; - -const _commandsById: { [id: string]: ParsedCommand } = Object.create(null); - -function handleCommand(command: Command, user: IExtensionPointUser): void { - if (validation.isValidCommand(command, user)) { - // store command globally - const parsedCommand = new ParsedCommand(user.description, command.command, command.title, command.category, command.icon, command.when, command.where); - if (_commandsById[parsedCommand.id]) { - user.collector.info(localize('command.overwrite', "Command `{0}` appears multiple times in the `commands` section.", parsedCommand.id)); - } - _commandsById[parsedCommand.id] = parsedCommand; - } -} - -ExtensionsRegistry.registerExtensionPoint('commands', schema.commandContribution).setHandler(extensions => { - for (let extension of extensions) { - const {value} = extension; - if (Array.isArray(value)) { - for (let command of value) { - handleCommand(command, extension); - } - } else { - handleCommand(value, extension); - } - } - - commands.push(...values(_commandsById)); - Object.freeze(commands); -}); - -export class CommandAction extends Action { - - constructor( - public command: ParsedCommand, - @IExtensionService extensionService: IExtensionService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(command.id, command.title); - this.order = Number.MAX_VALUE; - - const activationEvent = `onCommand:${command.id}`; - this._actionCallback = (...args: any[]) => { - return extensionService.activateByEvent(activationEvent).then(() => { - return keybindingService.executeCommand(command.id, ...args); - }); - }; - } -} \ No newline at end of file diff --git a/src/vs/platform/actions/common/menusExtensionPoint.ts b/src/vs/platform/actions/common/menusExtensionPoint.ts index 59c845702b9..c1ae1d6f493 100644 --- a/src/vs/platform/actions/common/menusExtensionPoint.ts +++ b/src/vs/platform/actions/common/menusExtensionPoint.ts @@ -5,114 +5,205 @@ 'use strict'; import {localize} from 'vs/nls'; +import {join} from 'vs/base/common/paths'; import {IJSONSchema} from 'vs/base/common/jsonSchema'; -import {IExtensionMessageCollector, ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; +import {forEach} from 'vs/base/common/collections'; +import {IExtensionPointUser, IExtensionMessageCollector, ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; +import {IUserFriendlyCommand, IUserFriendlyMenuItem, IUserFriendlyMenuLocation, MenuRegistry} from './menusService'; namespace schema { - export function isValidLocation(location: string, collector: IExtensionMessageCollector): boolean { - switch (location) { - case 'editor/primary': - case 'editor/secondary': - return true; + // --- menus contribution point + + export function isValidMenuItems(menu: IUserFriendlyMenuItem[], collector: IExtensionMessageCollector): boolean { + if (!Array.isArray(menu)) { + collector.error(localize('requirearry', "menu items must be an arry")); + return false; } - return false; - } - export function isValidMenuItem(item: MenuItem, collector: IExtensionMessageCollector): boolean { + 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; + } + } return true; } - export function isValidMenus(menu: Menus, collector: IExtensionMessageCollector): boolean { - - for (let key in menu) { - if (menu.hasOwnProperty(key)) { - let value = menu[key]; - if (!isValidLocation(key, collector)) { - collector.warn(localize('invalid', "`{0}` is not a valid menu location", key)); - continue; - } - - if (!Array.isArray(value)) { - collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `Array`", key)); - return false; - } + const menuItem: IJSONSchema = { + type: 'object', + properties: { + command: { + description: localize('vscode.extension.contributes.menuItem.command', 'Identifier of the command to execute'), + type: 'string' + }, + alt: { + description: localize('vscode.extension.contributes.menuItem.alt', 'Identifier of an alternative command to execute'), + type: 'string' + }, + when: { + description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'), + type: 'string' + } + } + }; - for (let item of value) { - if (!isValidMenuItem(item, collector)) { - return false; - } - } + export const menusContribtion: IJSONSchema = { + description: localize('vscode.extension.contributes.menus', "Contributes menu items to predefined locations"), + type: 'object', + properties: { + 'editor/primary': { + type: 'array', + items: menuItem } } + }; + + // --- commands contribution point + export function isValidCommand(command: IUserFriendlyCommand, collector: IExtensionMessageCollector): boolean { + if (!command) { + collector.error(localize('nonempty', "expected non-empty value.")); + return false; + } + if (typeof command.command !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command')); + return false; + } + if (typeof command.title !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'title')); + return false; + } + if (command.category && typeof command.category !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'category')); + return false; + } + if (!isValidIcon(command.icon, collector)) { + return false; + } return true; } - export const menus = { + function isValidIcon(icon: string | { light: string; dark: string;}, collector: IExtensionMessageCollector): 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; + } + 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: [ + '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 interface MenuItem { - command: string; - alt: string; -} -export interface Menus { - 'editor/primary': MenuItem[]; - 'editor/secondary': MenuItem[]; + export const commandsContribution: IJSONSchema = { + description: localize('vscode.extension.contributes.commands', "Contributes commands to the command palette."), + oneOf: [ + commandType, + { + type: 'array', + items: commandType + } + ] + }; } -ExtensionsRegistry.registerExtensionPoint('menus', schema.menus).setHandler(extensions => { +ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: IUserFriendlyMenuItem[] }>('menus', schema.menusContribtion).setHandler(extensions => { for (let extension of extensions) { - const {value} = extension; + const {value, collector} = extension; - if (schema.isValidMenus(value, extension.collector)) { - for (var key in value) { - if (value.hasOwnProperty(key)) { - MenusRegistry.registerMenuItems(MenuLocations.fromString(key), value[key]); - } + forEach(value, entry => { + if (!schema.isValidMenuItems(entry.value, collector)) { + return; } - } - } -}); -export enum MenuLocations { - EditorPrimary = 0, - EditorSecondary = 1 -} - -export namespace MenuLocations { - export function fromString(value: string): MenuLocations { - switch (value) { - case 'editor/primary': return MenuLocations.EditorPrimary; - case 'editor/secondary': return MenuLocations.EditorSecondary; - } + if (!MenuRegistry.registerMenuItems(entry.key, entry.value)) { + // ignored + } + }); } -} +}); -export interface IMenuRegistry { - registerMenuItems(location: MenuLocations, items: MenuItem[]): void; - getMenuItems(location: MenuLocations): MenuItem[]; -} +ExtensionsRegistry.registerExtensionPoint('commands', schema.commandsContribution).setHandler(extensions => { -export const MenusRegistry: IMenuRegistry = new class { + function handleCommand(command: IUserFriendlyCommand, extension: IExtensionPointUser) { - private _values: { [location: number]: MenuItem[] } = Object.create(null); + if (!schema.isValidCommand(command, extension.collector)) { + return; + } - registerMenuItems(location: MenuLocations, items: MenuItem[]): void { - let array = this._values[location]; - if (array) { - array.push(...items); + let {icon} = command; + if (!icon) { + // ignore + } else if (typeof icon === 'string') { + command.icon = join(extension.description.extensionFolderPath, icon); } else { - this._values[location] = items; + const light = join(extension.description.extensionFolderPath, icon.light); + const dark = join(extension.description.extensionFolderPath, icon.dark); + command.icon = { light, dark }; + } + + if (MenuRegistry.registerCommand(command)) { + extension.collector.info(localize('dup', "Command `{0}` appears multiple times in the `commands` section.", command.command)); } } - getMenuItems(location: MenuLocations): MenuItem[] { - return this._values[location]; + for (let extension of extensions) { + const {value} = extension; + if (Array.isArray(value)) { + for (let command of value) { + handleCommand(command, extension); + } + } else { + handleCommand(value, extension); + } } -}; +}); diff --git a/src/vs/platform/actions/common/menusService.ts b/src/vs/platform/actions/common/menusService.ts new file mode 100644 index 00000000000..0126a54b3e8 --- /dev/null +++ b/src/vs/platform/actions/common/menusService.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import {values} from 'vs/base/common/collections'; +import {KbExpr} from 'vs/platform/keybinding/common/keybindingService'; +import {MenuLocation, CommandAction, MenuItem, IMenuService} from './actions'; + +export type IUserFriendlyMenuLocation = 'editor/primary' | 'editor/secondary'; + +export interface IUserFriendlyMenuItem { + command: string; + alt?: string; + when?: string; +} + +export interface IUserFriendlyCommand { + command: string; + title: string; + category?: string; + icon?: string | { light: string; dark: string; }; +} + +export interface IMenuRegistry { + registerCommand(userCommand: IUserFriendlyCommand): boolean; + registerMenuItems(location: IUserFriendlyMenuLocation, items: IUserFriendlyMenuItem[]): boolean; +} + +const _registry = new class { + + private _commands: { [id: string]: CommandAction } = Object.create(null); + + private _menuItems: { [loc: number]: IUserFriendlyMenuItem[] } = Object.create(null); + + registerCommand(userCommand: IUserFriendlyCommand): boolean { + let {command, category, icon, title} = userCommand; + if (!icon) { + icon = ''; + } + const old = this._commands[command]; + this._commands[command] = { + id: command, + title, + category, + lightThemeIcon: typeof icon === 'string' ? icon : icon.light, + darkThemeIcon: typeof icon === 'string' ? icon : icon.dark + }; + + return old !== void 0; + } + + registerMenuItems(location: IUserFriendlyMenuLocation, items: IUserFriendlyMenuItem[]): boolean { + const loc = MenuLocation.parse(location); + if (loc) { + let array = this._menuItems[loc]; + if (!array) { + this._menuItems[loc] = items; + } else { + array.push(...items); + } + return true; + } + } + + getMenuItems(loc: MenuLocation): MenuItem[] { + const menuItems = this._menuItems[loc]; + if (menuItems) { + return menuItems.map(item => { + const when = KbExpr.deserialize(item.when); + const command = this._commands[item.command]; + const alt = this._commands[item.alt]; + return { when, command, alt }; + }); + } + } + + getCommandActions(): CommandAction[] { + return values(this._commands); + } +}; + +export const MenuRegistry: IMenuRegistry = _registry; + +export class MenuService implements IMenuService { + + serviceId; + + getMenuItems(loc: MenuLocation): MenuItem[] { + return _registry.getMenuItems(loc); + } + + getCommandActions(): CommandAction[] { + return _registry.getCommandActions(); + } +} diff --git a/src/vs/platform/actions/workbench/actionBarContributions.ts b/src/vs/platform/actions/workbench/actionBarContributions.ts deleted file mode 100644 index aa34ede1a02..00000000000 --- a/src/vs/platform/actions/workbench/actionBarContributions.ts +++ /dev/null @@ -1,135 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import {Registry} from 'vs/platform/platform'; -import URI from 'vs/base/common/uri'; -import {IAction, Action} from 'vs/base/common/actions'; -import {BaseActionItem, ActionItem} from 'vs/base/browser/ui/actionbar/actionbar'; -import {Scope, IActionBarRegistry, Extensions, ActionBarContributor} from 'vs/workbench/browser/actionBarRegistry'; -import {IExtensionService} from 'vs/platform/extensions/common/extensions'; -import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; -import {isLightTheme} from 'vs/platform/theme/common/themes'; -import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; -import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; -import {commands, CommandAction, Locations} from '../common/commandsExtensionPoint'; -import {EditorInput} from 'vs/workbench/common/editor'; - - -abstract class BaseActionBarContributor extends ActionBarContributor { - - private _isReady: boolean = false; - - constructor( - @IExtensionService private _extensionService: IExtensionService, - @IInstantiationService private _instantationService: IInstantiationService, - @IKeybindingService private _keybindingService: IKeybindingService - ) { - super(); - this._extensionService.onReady().then(() => this._isReady = true); - } - - protected abstract _wheres(): { primary: Locations; secondary: Locations }; - - protected abstract _getResource(context: any): URI; - - public hasActions(context: any): boolean { - return this._isReady && this._wheres().primary && this.getActions(context).length > 0; - } - - public hasSecondaryActions(context: any): boolean { - return this._isReady && this._wheres().secondary && this.getSecondaryActions(context).length > 0; - } - - public getActions(context: any): IAction[] { - return this._getActions(context, this._wheres().primary); - } - - public getSecondaryActions(context: any): IAction[] { - return this._getActions(context, this._wheres().secondary); - } - - private _getActions(context: any, where: Locations): IAction[] { - const result: IAction[] = []; - - for (let command of commands) { - // console.log(command.id, command.when, - // this._keybindingService.contextMatchesRules(command.when), - // this._keybindingService.getContextValue('resourceLangId'), - // this._keybindingService.getContextValue('resourceScheme') - // ); - if (command.where === where && this._keybindingService.contextMatchesRules(command.when)) { - let resource = this._keybindingService.getContextValue('resource'); - - result.push(this._instantationService.createInstance(class extends CommandAction { - run() { - return super.run(resource); - } - }, command)); - } - } - - return result; - } - - public getActionItem(context: any, action: Action): BaseActionItem { - if (action instanceof CommandAction) { - return this._instantationService.createInstance(CommandActionItem, action); - } - } -} - -class EditorContributor extends BaseActionBarContributor { - - protected _wheres(): { primary: Locations; secondary: Locations } { - return { primary: 'editor/primary', secondary: 'editor/secondary' }; - } - protected _getResource(context: any): URI { - const {input} = context; - if (input instanceof EditorInput) { - if (typeof input.getResource === 'function') { - const candidate = input.getResource(); - if (candidate instanceof URI) { - return candidate; - } - } - } - } -} - -class CommandActionItem extends ActionItem { - - constructor( - action: CommandAction, - @IThemeService private _themeService: IThemeService - ) { - super(undefined, action, { - icon: !!(action.command.lightThemeIcon || action.command.darkThemeIcon), - label: !(action.command.lightThemeIcon || action.command.darkThemeIcon) - }); - - this._themeService.onDidThemeChange(this._updateClass, this, this.callOnDispose); - } - - _updateClass(): void { - super._updateClass(); - const element = this.$e.getHTMLElement(); - const {lightThemeIcon, darkThemeIcon} = (this._action).command; - if (element.classList.contains('icon')) { - if (isLightTheme(this._themeService.getTheme())) { - element.style.backgroundImage = `url("${lightThemeIcon}")`; - } else { - element.style.backgroundImage = `url("${darkThemeIcon}")`; - } - } - } - - onClick(event: Event): void { - super.onClick(event); - } -} - -Registry.as(Extensions.Actionbar).registerActionBarContributor(Scope.EDITOR, EditorContributor); \ No newline at end of file diff --git a/src/vs/platform/actions/workbench/actionsService.ts b/src/vs/platform/actions/workbench/actionsService.ts deleted file mode 100644 index 70bf8e563db..00000000000 --- a/src/vs/platform/actions/workbench/actionsService.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import {localize} from 'vs/nls'; -import {IAction} from 'vs/base/common/actions'; -import {IExtensionService} from 'vs/platform/extensions/common/extensions'; -import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; -import {IActionsService} from '../common/actions'; -import {commands, CommandAction} from '../common/commandsExtensionPoint'; -import 'vs/platform/actions/workbench/actionBarContributions'; - -export default class ActionsService implements IActionsService { - - serviceId: any; - - private _extensionsActions: IAction[]; - - constructor( - @IExtensionService private _extensionService: IExtensionService, - @IKeybindingService private _keybindingsService: IKeybindingService - ) { - this._extensionService.onReady().then(() => this._extensionsActions = null); - } - - getActions(): IAction[] { - - if (!this._extensionsActions) { - this._extensionsActions = []; - for (let command of commands) { - - const action = new CommandAction(command, this._extensionService, this._keybindingsService); - action.order = Number.MAX_VALUE; - action.label = command.category - ? localize('category.label', "{0}: {1}", command.category, command.title) - : command.title; - - this._extensionsActions.push(action); - } - } - - return this._extensionsActions.slice(0); - } -} diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 07d83498e13..6291e863804 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -57,14 +57,15 @@ import {IEditorGroupService} from 'vs/workbench/services/group/common/groupServi import {IHistoryService} from 'vs/workbench/services/history/common/history'; import {IEventService} from 'vs/platform/event/common/event'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {SyncDescriptor} from 'vs/platform/instantiation/common/descriptors'; import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; import {IMessageService} from 'vs/platform/message/common/message'; import {IThreadService} from 'vs/platform/thread/common/thread'; import {MainThreadService} from 'vs/platform/thread/common/mainThreadService'; import {IStatusbarService} from 'vs/platform/statusbar/common/statusbar'; -import {IActionsService} from 'vs/platform/actions/common/actions'; -import ActionsService from 'vs/platform/actions/workbench/actionsService'; +import {IMenuService} from 'vs/platform/actions/common/actions'; +import {MenuService} from 'vs/platform/actions/common/menusService'; import {IContextMenuService} from 'vs/platform/contextview/browser/contextView'; interface WorkbenchParams { @@ -346,8 +347,8 @@ export class Workbench implements IPartService { // Context Menu serviceCollection.set(IContextMenuService, this.instantiationService.createInstance(ContextMenuService)); - // Actions - serviceCollection.set(IActionsService, this.instantiationService.createInstance(ActionsService)); + // Menus/Actions + serviceCollection.set(IMenuService, new SyncDescriptor(MenuService)); // Viewlet service (sidebar part) this.sidebarPart = this.instantiationService.createInstance(SidebarPart, Identifiers.SIDEBAR_PART); diff --git a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts index e242f1a020c..15333bc1ad3 100644 --- a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts @@ -16,7 +16,7 @@ import {IAction, Action} from 'vs/base/common/actions'; import {toErrorMessage} from 'vs/base/common/errors'; import {Mode, IEntryRunContext, IAutoFocus} from 'vs/base/parts/quickopen/common/quickOpen'; import {QuickOpenEntryGroup, IHighlight, QuickOpenModel} from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import {SyncActionDescriptor, IActionsService} from 'vs/platform/actions/common/actions'; +import {SyncActionDescriptor, ExecuteCommandAction, IMenuService} from 'vs/platform/actions/common/actions'; import {IWorkbenchActionRegistry, Extensions as ActionExtensions} from 'vs/workbench/common/actionRegistry'; import {Registry} from 'vs/platform/platform'; import {QuickOpenHandler, QuickOpenAction} from 'vs/workbench/browser/quickopen'; @@ -229,7 +229,7 @@ export class CommandsHandler extends QuickOpenHandler { @IInstantiationService private instantiationService: IInstantiationService, @IMessageService private messageService: IMessageService, @IKeybindingService private keybindingService: IKeybindingService, - @IActionsService private actionsService: IActionsService + @IMenuService private menuService: IMenuService ) { super(); } @@ -260,7 +260,7 @@ export class CommandsHandler extends QuickOpenHandler { let editorEntries = this.editorActionsToEntries(editorActions, searchValue); // Other Actions - let otherActions = this.actionsService.getActions(); + let otherActions = this.menuService.getCommandActions().map(command => new ExecuteCommandAction(command.id, command.category ? nls.localize('', "{0}: {1}", command.category, command.title) : command.title, this.keybindingService)); let otherEntries = this.otherActionsToEntries(otherActions, searchValue); // Concat diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 484d26fff73..2b8686b4ceb 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -16,7 +16,7 @@ import 'vs/editor/browser/editor.all'; // Languages import 'vs/languages/languages.main'; -// Extension Points +// Menus/Actions import 'vs/platform/actions/common/menusExtensionPoint'; // Workbench -- GitLab