diff --git a/src/vs/platform/actions/workbench/actionBarContributor.ts b/src/vs/platform/actions/browser/actionBarContributor.ts similarity index 86% rename from src/vs/platform/actions/workbench/actionBarContributor.ts rename to src/vs/platform/actions/browser/actionBarContributor.ts index ea170f0a468..135aaa8c69e 100644 --- a/src/vs/platform/actions/workbench/actionBarContributor.ts +++ b/src/vs/platform/actions/browser/actionBarContributor.ts @@ -11,12 +11,10 @@ import Event, {Emitter} from 'vs/base/common/event'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; import {IExtensionService} from 'vs/platform/extensions/common/extensions'; -import {Menus, MenuItem, IMenuService} from 'vs/platform/actions/common/actions'; +import {MenuId, MenuItem, IMenuService} from 'vs/platform/actions/common/actions'; import {ResourceContextKey} from 'vs/platform/actions/common/resourceContextKey'; import {Action, IAction} from 'vs/base/common/actions'; import {BaseActionItem, ActionItem} from 'vs/base/browser/ui/actionbar/actionbar'; -import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; -import {isLightTheme} from 'vs/platform/theme/common/themes'; import {domEvent} from 'vs/base/browser/event'; export class ActionBarContributor { @@ -28,11 +26,10 @@ export class ActionBarContributor { constructor( scope: HTMLElement, - location: Menus, + location: MenuId, @IMenuService private _menuService: IMenuService, @IKeybindingService private _keybindingService: IKeybindingService, - @IExtensionService private _extensionService: IExtensionService, - @IThemeService private _themeService: IThemeService + @IExtensionService private _extensionService: IExtensionService ) { this._scope = scope; this._extensionService.onReady().then(() => { @@ -82,7 +79,7 @@ export class ActionBarContributor { getActionItem(action: IAction): BaseActionItem { if (action instanceof MenuItemAction) { - return new MenuItemActionItem(action, this._themeService, this._keybindingService); + return new MenuItemActionItem(action, this._keybindingService); } } } @@ -129,12 +126,9 @@ class MenuItemActionItem extends ActionItem { constructor( action: MenuItemAction, - @IThemeService private _themeService: IThemeService, @IKeybindingService private _keybindingService: IKeybindingService ) { super(undefined, action, { icon: true, label: false }); - - this._callOnDispose.push(this._themeService.onDidThemeChange(_ => this._updateClass())); } private get command() { @@ -182,10 +176,8 @@ class MenuItemActionItem extends ActionItem { _updateClass(): void { if (this.options.icon) { const element = this.$e.getHTMLElement(); - const {darkThemeIcon, lightThemeIcon} = this.command; - const themeId = this._themeService.getTheme(); - element.classList.add('icon'); - element.style.backgroundImage = `url("${isLightTheme(themeId) ? lightThemeIcon : darkThemeIcon}")`; + const {iconClass} = this.command; + element.classList.add('icon', iconClass); } } } diff --git a/src/vs/platform/actions/common/menusExtensionPoint.ts b/src/vs/platform/actions/browser/menusExtensionPoint.ts similarity index 69% rename from src/vs/platform/actions/common/menusExtensionPoint.ts rename to src/vs/platform/actions/browser/menusExtensionPoint.ts index d5815be14c9..22865e40184 100644 --- a/src/vs/platform/actions/common/menusExtensionPoint.ts +++ b/src/vs/platform/actions/browser/menusExtensionPoint.ts @@ -4,18 +4,31 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import {createCSSRule} from 'vs/base/browser/dom'; import {localize} from 'vs/nls'; import {join} from 'vs/base/common/paths'; +import {IdGenerator} from 'vs/base/common/idGenerator'; import {IJSONSchema} from 'vs/base/common/jsonSchema'; import {forEach} from 'vs/base/common/collections'; import {IExtensionPointUser, IExtensionMessageCollector, ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; -import {IUserFriendlyCommand, IUserFriendlyMenuItem, IUserFriendlyMenuLocation, MenuRegistry} from './menusService'; +import {IDeclaredMenuItem, MenuRegistry} from './menusService'; +import {MenuId} from 'vs/platform/actions/common/actions'; + namespace schema { // --- menus contribution point - export function isValidMenuItems(menu: IUserFriendlyMenuItem[], collector: IExtensionMessageCollector): boolean { + + export function parseMenus(value: string): MenuId { + switch (value) { + case 'editor/title': return MenuId.EditorTitle; + case 'explorer/context': return MenuId.ExplorerContext; + } + } + + + export function isValidMenuItems(menu: IDeclaredMenuItem[], collector: IExtensionMessageCollector): boolean { if (!Array.isArray(menu)) { collector.error(localize('requirearry', "menu items must be an arry")); return false; @@ -74,6 +87,15 @@ namespace schema { // --- commands contribution point + export interface IUserFriendlyCommand { + command: string; + title: string; + category?: string; + icon?: IUserFriendlyIcon; + } + + export type IUserFriendlyIcon = string | { light: string; dark: string; }; + export function isValidCommand(command: IUserFriendlyCommand, collector: IExtensionMessageCollector): boolean { if (!command) { collector.error(localize('nonempty', "expected non-empty value.")); @@ -97,7 +119,7 @@ namespace schema { return true; } - function isValidIcon(icon: string | { light: string; dark: string;}, collector: IExtensionMessageCollector): boolean { + function isValidIcon(icon: IUserFriendlyIcon, collector: IExtensionMessageCollector): boolean { if (typeof icon === 'undefined') { return true; } @@ -159,7 +181,7 @@ namespace schema { }; } -ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: IUserFriendlyMenuItem[] }>('menus', schema.menusContribtion).setHandler(extensions => { +ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: IDeclaredMenuItem[] }>('menus', schema.menusContribtion).setHandler(extensions => { for (let extension of extensions) { const {value, collector} = extension; @@ -168,40 +190,50 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: IUserFriendlyMenuItem return; } - if (!MenuRegistry.registerMenuItems(entry.key, entry.value)) { - // ignored + const menu = schema.parseMenus(entry.key); + if (!menu) { + collector.warn(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry.key)); + return; } + + MenuRegistry.registerMenuItems(menu, entry.value); }); } }); -ExtensionsRegistry.registerExtensionPoint('commands', schema.commandsContribution).setHandler(extensions => { +ExtensionsRegistry.registerExtensionPoint('commands', schema.commandsContribution).setHandler(extensions => { - function handleCommand(command: IUserFriendlyCommand, extension: IExtensionPointUser) { + const ids = new IdGenerator('contrib-cmd-icon-'); - if (!schema.isValidCommand(command, extension.collector)) { + function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand , extension: IExtensionPointUser) { + + if (!schema.isValidCommand(userFriendlyCommand, extension.collector)) { return; } - let {icon} = command; - if (!icon) { - // ignore - } else if (typeof icon === 'string') { - command.icon = join(extension.description.extensionFolderPath, icon); - } else { - const light = join(extension.description.extensionFolderPath, icon.light); - const dark = join(extension.description.extensionFolderPath, icon.dark); - command.icon = { light, dark }; + 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("${path}")`); + } else { + const light = join(extension.description.extensionFolderPath, icon.light); + const dark = join(extension.description.extensionFolderPath, icon.dark); + createCSSRule(`.icon.${iconClass}`, `background-image: url("${light}")`); + createCSSRule(`.vs-dark .icon.${iconClass}, hc-black .icon.${iconClass}`, `background-image: url("${dark}")`); + } } - if (MenuRegistry.registerCommand(command)) { - extension.collector.info(localize('dup', "Command `{0}` appears multiple times in the `commands` section.", command.command)); + if (MenuRegistry.registerCommand({ 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(value)) { + if (Array.isArray(value)) { for (let command of value) { handleCommand(command, extension); } diff --git a/src/vs/platform/actions/common/menusService.ts b/src/vs/platform/actions/browser/menusService.ts similarity index 50% rename from src/vs/platform/actions/common/menusService.ts rename to src/vs/platform/actions/browser/menusService.ts index 6ee114280e7..7b315d06e48 100644 --- a/src/vs/platform/actions/common/menusService.ts +++ b/src/vs/platform/actions/browser/menusService.ts @@ -7,65 +7,41 @@ import {values} from 'vs/base/common/collections'; import {KbExpr} from 'vs/platform/keybinding/common/keybindingService'; -import {Menus, CommandAction, MenuItem, IMenuService} from './actions'; +import {MenuId, CommandAction, MenuItem, IMenuService} from 'vs/platform/actions/common/actions'; -export type IUserFriendlyMenuLocation = 'editor/title'; - -export interface IUserFriendlyMenuItem { +export interface IDeclaredMenuItem { 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; + registerCommand(userCommand: CommandAction): boolean; + registerMenuItems(location: MenuId, items: IDeclaredMenuItem[]): void; } 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 - }; + private _menuItems: { [loc: number]: IDeclaredMenuItem[] } = Object.create(null); + registerCommand(command: CommandAction): boolean { + const old = this._commands[command.id]; + this._commands[command.id] = command; return old !== void 0; } - registerMenuItems(location: IUserFriendlyMenuLocation, items: IUserFriendlyMenuItem[]): boolean { - const loc = Menus.parse(location); - if (loc) { - let array = this._menuItems[loc]; - if (!array) { - this._menuItems[loc] = items; - } else { - array.push(...items); - } - return true; + registerMenuItems(loc: MenuId, items: IDeclaredMenuItem[]): void { + let array = this._menuItems[loc]; + if (!array) { + this._menuItems[loc] = items; + } else { + array.push(...items); } } - getMenuItems(loc: Menus): MenuItem[] { + getMenuItems(loc: MenuId): MenuItem[] { const menuItems = this._menuItems[loc]; if (menuItems) { return menuItems.map(item => { @@ -88,7 +64,7 @@ export class MenuService implements IMenuService { serviceId; - getMenuItems(loc: Menus): MenuItem[] { + getMenuItems(loc: MenuId): MenuItem[] { return _registry.getMenuItems(loc); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index dd3d60893ee..e7c1e54b4d1 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -13,13 +13,11 @@ import {KbExpr, IKeybindings, IKeybindingService} from 'vs/platform/keybinding/c 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; + category?: string; + iconClass?: string; } export interface MenuItem { @@ -28,27 +26,18 @@ export interface MenuItem { when?: KbExpr; } -export enum Menus { +export enum MenuId { EditorTitle = 1, ExplorerContext = 2 } -export namespace Menus { - export function parse(value: string): Menus { - switch (value) { - case 'editor/title': return Menus.EditorTitle; - case 'explorer/context': return Menus.ExplorerContext; - } - } -} - export const IMenuService = createDecorator('menuService'); export interface IMenuService { serviceId: any; - getMenuItems(loc: Menus): MenuItem[]; + getMenuItems(loc: MenuId): MenuItem[]; getCommandActions(): CommandAction[]; } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index ee5d423dd17..45e00437110 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -31,8 +31,8 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; import {CloseEditorsInGroupAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, CloseEditorAction, KeepEditorAction, CloseOtherEditorsInGroupAction, CloseRightEditorsInGroupAction, ShowEditorsInGroupAction} from 'vs/workbench/browser/parts/editor/editorActions'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; -import {ActionBarContributor} from 'vs/platform/actions/workbench/actionBarContributor'; -import {Menus} from 'vs/platform/actions/common/actions'; +import {ActionBarContributor} from 'vs/platform/actions/browser/actionBarContributor'; +import {MenuId} from 'vs/platform/actions/common/actions'; import {ResourceContextKey} from 'vs/platform/actions/common/resourceContextKey'; export interface IToolbarActions { @@ -177,7 +177,7 @@ export abstract class TitleControl implements ITitleAreaControl { } public create(parent: HTMLElement): void { - this.titleActionBarContributor = this.instantiationService.createInstance(ActionBarContributor, parent, Menus.EditorTitle); + this.titleActionBarContributor = this.instantiationService.createInstance(ActionBarContributor, parent, MenuId.EditorTitle); this.toDispose.push(this.titleActionBarContributor.onDidUpdate(e => this.refresh())); this.toDispose.push(this.titleActionBarContributor); } diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 6291e863804..4713b30c236 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -65,7 +65,7 @@ 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 {IMenuService} from 'vs/platform/actions/common/actions'; -import {MenuService} from 'vs/platform/actions/common/menusService'; +import {MenuService} from 'vs/platform/actions/browser/menusService'; import {IContextMenuService} from 'vs/platform/contextview/browser/contextView'; interface WorkbenchParams { diff --git a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts index 4c7451b6b43..65b1c120504 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts @@ -46,8 +46,8 @@ import {IProgressService} from 'vs/platform/progress/common/progress'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {Keybinding, CommonKeybindings} from 'vs/base/common/keyCodes'; import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent'; -import {ActionBarContributor} from 'vs/platform/actions/workbench/actionBarContributor'; -import {Menus} from 'vs/platform/actions/common/actions'; +import {ActionBarContributor} from 'vs/platform/actions/browser/actionBarContributor'; +import {MenuId} from 'vs/platform/actions/common/actions'; export class FileDataSource implements IDataSource { private workspace: IWorkspace; @@ -468,7 +468,7 @@ export class FileController extends DefaultController { if (!this.contextMenuActions) { this.contextMenuActions = this.instantiationService.createInstance(ActionBarContributor, - tree.getHTMLElement(), Menus.ExplorerContext); + tree.getHTMLElement(), MenuId.ExplorerContext); } event.preventDefault(); diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 2b8686b4ceb..69924242294 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -17,7 +17,7 @@ import 'vs/editor/browser/editor.all'; import 'vs/languages/languages.main'; // Menus/Actions -import 'vs/platform/actions/common/menusExtensionPoint'; +import 'vs/platform/actions/browser/menusExtensionPoint'; // Workbench import 'vs/workbench/browser/actions/toggleStatusbarVisibility';