未验证 提交 65c3b9e9 编写于 作者: S SteVen Batten 提交者: GitHub

Submenus for Menu Widget (#52780)

* submenu registration, ignore && mnemonic rendering for now

* clean up ActionItems for Menu Widget and submenu

* clean up the update actions logic in menubar

* fix keyboard navigation for submenus

* add terminal menu and update submenu labels

* fix missing separator issue

* updating some aria labels

* bring back action runner to hide menu before running

* address feedback from @bpasero
上级 c9b7b4d9
......@@ -4,10 +4,11 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IAction, IActionRunner, Action } from 'vs/base/common/actions';
import { IAction, IActionRunner } from 'vs/base/common/actions';
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { TPromise } from 'vs/base/common/winjs.base';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { SubmenuAction } from 'vs/base/browser/ui/menu/menu';
export interface IEvent {
shiftKey?: boolean;
......@@ -16,9 +17,9 @@ export interface IEvent {
metaKey?: boolean;
}
export class ContextSubMenu extends Action {
export class ContextSubMenu extends SubmenuAction {
constructor(label: string, public entries: (ContextSubMenu | IAction)[]) {
super('contextsubmenu', label, '', true);
super(label, entries, 'contextsubmenu');
}
}
......
......@@ -819,6 +819,7 @@ export const EventType = {
MOUSE_OVER: 'mouseover',
MOUSE_MOVE: 'mousemove',
MOUSE_OUT: 'mouseout',
MOUSE_LEAVE: 'mouseleave',
CONTEXT_MENU: 'contextmenu',
WHEEL: 'wheel',
// Keyboard
......
......@@ -54,7 +54,7 @@
.monaco-action-bar .action-label {
font-size: 11px;
margin-right: 4px;
margin-right: 4px;
}
.monaco-action-bar .action-label.octicon {
......
......@@ -43,8 +43,6 @@ export class BaseActionItem implements IActionItem {
public _context: any;
public _action: IAction;
static MNEMONIC_REGEX: RegExp = /&&(.)/g;
private _actionRunner: IActionRunner;
constructor(context: any, action: IAction, protected options?: IBaseActionItemOptions) {
......@@ -277,11 +275,7 @@ export class ActionItem extends BaseActionItem {
public _updateLabel(): void {
if (this.options.label) {
let label = this.getAction().label;
if (label && this.options.isMenu) {
label = label.replace(BaseActionItem.MNEMONIC_REGEX, '$1\u0332');
}
this.$e.text(label);
this.$e.text(this.getAction().label);
}
}
......@@ -565,15 +559,6 @@ export class ActionBar implements IActionRunner {
return this.domNode;
}
private _addMnemonic(action: IAction, actionItemElement: HTMLElement): void {
let matches = BaseActionItem.MNEMONIC_REGEX.exec(action.label);
if (matches && matches.length === 2) {
let mnemonic = matches[1];
actionItemElement.accessKey = mnemonic.toLocaleLowerCase();
}
}
public push(arg: IAction | IAction[], options: IActionOptions = {}): void {
const actions: IAction[] = !Array.isArray(arg) ? [arg] : arg;
......@@ -591,10 +576,6 @@ export class ActionBar implements IActionRunner {
e.stopPropagation();
});
if (options.isMenu) {
this._addMnemonic(action, actionItemElement);
}
let item: IActionItem = null;
if (this.options.actionItemProvider) {
......
......@@ -5,6 +5,7 @@
.monaco-menu .monaco-action-bar.vertical {
margin-left: 0;
overflow: visible;
}
.monaco-menu .monaco-action-bar.vertical .actions-container {
......@@ -47,7 +48,8 @@
background: none;
}
.monaco-menu .monaco-action-bar.vertical .keybinding {
.monaco-menu .monaco-action-bar.vertical .keybinding,
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
display: inline-block;
-ms-flex: 2 1 auto;
flex: 2 1 auto;
......@@ -57,7 +59,12 @@
text-align: right;
}
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding {
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
padding: 0.8em .5em;
}
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding,
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator {
opacity: 0.4;
}
......
......@@ -7,11 +7,13 @@
import 'vs/css!./menu';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IActionRunner, IAction } from 'vs/base/common/actions';
import { ActionBar, IActionItemProvider, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { IActionRunner, IAction, Action } from 'vs/base/common/actions';
import { ActionBar, IActionItemProvider, ActionsOrientation, Separator, ActionItem, IActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar';
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import { Event } from 'vs/base/common/event';
import { addClass } from 'vs/base/browser/dom';
import { addClass, EventType, EventHelper, EventLike } from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { $ } from 'vs/base/browser/builder';
export interface IMenuOptions {
context?: any;
......@@ -21,6 +23,18 @@ export interface IMenuOptions {
ariaLabel?: string;
}
export class SubmenuAction extends Action {
constructor(label: string, public entries: (SubmenuAction | IAction)[], cssClass?: string) {
super(!!cssClass ? cssClass : 'submenu', label, '', true);
}
}
interface ISubMenuData {
parent: Menu;
submenu?: Menu;
}
export class Menu {
private actionBar: ActionBar;
......@@ -35,9 +49,31 @@ export class Menu {
menuContainer.setAttribute('role', 'presentation');
container.appendChild(menuContainer);
let parentData: ISubMenuData = {
parent: this
};
const getActionItem = (action: IAction) => {
if (action instanceof Separator) {
return new ActionItem(options.context, action, { icon: true });
} else if (action instanceof SubmenuAction) {
return new SubmenuActionItem(action, action.entries, parentData, options);
} else {
const menuItemOptions: IActionItemOptions = {};
if (options.getKeyBinding) {
const keybinding = options.getKeyBinding(action);
if (keybinding) {
menuItemOptions.keybinding = keybinding.getLabel();
}
}
return new MenuActionItem(options.context, action, menuItemOptions);
}
};
this.actionBar = new ActionBar(menuContainer, {
orientation: ActionsOrientation.VERTICAL,
actionItemProvider: options.actionItemProvider,
actionItemProvider: options.actionItemProvider ? options.actionItemProvider : getActionItem,
context: options.context,
actionRunner: options.actionRunner,
isMenu: true,
......@@ -70,4 +106,139 @@ export class Menu {
this.listener = null;
}
}
}
class MenuActionItem extends ActionItem {
static MNEMONIC_REGEX: RegExp = /&&(.)/g;
constructor(ctx: any, action: IAction, options: IActionItemOptions = {}) {
options.isMenu = true;
super(action, action, options);
}
private _addMnemonic(action: IAction, actionItemElement: HTMLElement): void {
let matches = MenuActionItem.MNEMONIC_REGEX.exec(action.label);
if (matches && matches.length === 2) {
let mnemonic = matches[1];
let ariaLabel = action.label.replace(MenuActionItem.MNEMONIC_REGEX, mnemonic);
actionItemElement.accessKey = mnemonic.toLocaleLowerCase();
this.$e.attr('aria-label', ariaLabel);
} else {
this.$e.attr('aria-label', action.label);
}
}
public render(container: HTMLElement): void {
super.render(container);
this._addMnemonic(this.getAction(), container);
this.$e.attr('role', 'menuitem');
}
public _updateLabel(): void {
if (this.options.label) {
let label = this.getAction().label;
if (label && this.options.isMenu) {
label = label.replace(MenuActionItem.MNEMONIC_REGEX, '$1\u0332');
}
this.$e.text(label);
}
}
}
class SubmenuActionItem extends MenuActionItem {
private mysubmenu: Menu;
constructor(
action: IAction,
private submenuActions: IAction[],
private parentData: ISubMenuData,
private submenuOptions?: IMenuOptions
) {
super(action, action, { label: true, isMenu: true });
}
public render(container: HTMLElement): void {
super.render(container);
this.builder = $(container);
$(this.builder).addClass('monaco-submenu-item');
$('span.submenu-indicator').text('\u25B6').appendTo(this.builder);
this.$e.attr('role', 'menu');
$(this.builder).on(EventType.KEY_UP, (e) => {
let event = new StandardKeyboardEvent(e as KeyboardEvent);
if (event.equals(KeyCode.RightArrow)) {
EventHelper.stop(e, true);
this.createSubmenu();
}
});
$(this.builder).on(EventType.KEY_DOWN, (e) => {
let event = new StandardKeyboardEvent(e as KeyboardEvent);
if (event.equals(KeyCode.RightArrow)) {
EventHelper.stop(e, true);
}
});
$(this.builder).on(EventType.MOUSE_OVER, (e) => {
this.cleanupExistingSubmenu(false);
this.createSubmenu();
});
$(this.builder).on(EventType.MOUSE_LEAVE, (e) => {
this.parentData.parent.focus();
this.cleanupExistingSubmenu(true);
});
}
public onClick(e: EventLike) {
// stop clicking from trying to run an action
EventHelper.stop(e, true);
}
private cleanupExistingSubmenu(force: boolean) {
if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) {
this.parentData.submenu.dispose();
this.parentData.submenu = null;
}
}
private createSubmenu() {
if (!this.parentData.submenu) {
const submenuContainer = $(this.builder).div({ class: 'monaco-submenu menubar-menu-items-holder context-view' });
$(submenuContainer).style({
'left': `${$(this.builder).getClientArea().width}px`
});
$(submenuContainer).on(EventType.KEY_UP, (e) => {
let event = new StandardKeyboardEvent(e as KeyboardEvent);
if (event.equals(KeyCode.LeftArrow)) {
EventHelper.stop(e, true);
this.parentData.parent.focus();
this.parentData.submenu.dispose();
this.parentData.submenu = null;
}
});
$(submenuContainer).on(EventType.KEY_DOWN, (e) => {
let event = new StandardKeyboardEvent(e as KeyboardEvent);
if (event.equals(KeyCode.LeftArrow)) {
EventHelper.stop(e, true);
}
});
this.parentData.submenu = new Menu(submenuContainer.getHTMLElement(), this.submenuActions, this.submenuOptions);
this.parentData.submenu.focus();
this.mysubmenu = this.parentData.submenu;
}
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@
import { localize } from 'vs/nls';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMenu, MenuItemAction, IMenuActionOptions, ICommandAction } from 'vs/platform/actions/common/actions';
import { IMenu, MenuItemAction, IMenuActionOptions, ICommandAction, SubmenuItemAction } 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';
......@@ -91,11 +91,11 @@ export function fillInActionBarActions(menu: IMenu, options: IMenuActionOptions,
fillInActions(groups, target, false, isPrimaryGroup);
}
function fillInActions(groups: [string, MenuItemAction[]][], target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, getAlternativeActions, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
function fillInActions(groups: [string, (MenuItemAction | SubmenuItemAction)[]][], target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, getAlternativeActions, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
for (let tuple of groups) {
let [group, actions] = tuple;
if (getAlternativeActions) {
actions = actions.map(a => !!a.alt ? a.alt : a);
actions = actions.map(a => (a instanceof MenuItemAction) && !!a.alt ? a.alt : a);
}
if (isPrimaryGroup(group)) {
......
......@@ -43,6 +43,22 @@ export interface IMenuItem {
order?: number;
}
export interface ISubmenuItem {
title: string | ILocalizedString;
submenu: MenuId;
when?: ContextKeyExpr;
group?: 'navigation' | string;
order?: number;
}
export function isIMenuItem(item: IMenuItem | ISubmenuItem): item is IMenuItem {
return (item as IMenuItem).command !== undefined;
}
export function isISubmenuItem(item: IMenuItem | ISubmenuItem): item is ISubmenuItem {
return (item as ISubmenuItem).submenu !== undefined;
}
export class MenuId {
private static ID = 1;
......@@ -81,6 +97,7 @@ export class MenuId {
static readonly MenubarWindowMenu = new MenuId();
static readonly MenubarPreferencesMenu = new MenuId();
static readonly MenubarHelpMenu = new MenuId();
static readonly MenubarTerminalMenu = new MenuId();
readonly id: string = String(MenuId.ID++);
}
......@@ -92,7 +109,7 @@ export interface IMenuActionOptions {
export interface IMenu extends IDisposable {
onDidChange: Event<IMenu>;
getActions(options?: IMenuActionOptions): [string, MenuItemAction[]][];
getActions(options?: IMenuActionOptions): [string, (MenuItemAction | SubmenuItemAction)[]][];
}
export const IMenuService = createDecorator<IMenuService>('menuService');
......@@ -107,15 +124,15 @@ export interface IMenuService {
export interface IMenuRegistry {
addCommand(userCommand: ICommandAction): boolean;
getCommand(id: string): ICommandAction;
appendMenuItem(menu: MenuId, item: IMenuItem): IDisposable;
getMenuItems(loc: MenuId): IMenuItem[];
appendMenuItem(menu: MenuId, item: IMenuItem | ISubmenuItem): IDisposable;
getMenuItems(loc: MenuId): (IMenuItem | ISubmenuItem)[];
}
export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {
private _commands: { [id: string]: ICommandAction } = Object.create(null);
private _menuItems: { [loc: string]: IMenuItem[] } = Object.create(null);
private _menuItems: { [loc: string]: (IMenuItem | ISubmenuItem)[] } = Object.create(null);
addCommand(command: ICommandAction): boolean {
const old = this._commands[command.id];
......@@ -127,7 +144,7 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {
return this._commands[id];
}
appendMenuItem({ id }: MenuId, item: IMenuItem): IDisposable {
appendMenuItem({ id }: MenuId, item: IMenuItem | ISubmenuItem): IDisposable {
let array = this._menuItems[id];
if (!array) {
this._menuItems[id] = array = [item];
......@@ -144,7 +161,7 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {
};
}
getMenuItems({ id }: MenuId): IMenuItem[] {
getMenuItems({ id }: MenuId): (IMenuItem | ISubmenuItem)[] {
const result = this._menuItems[id] || [];
if (id === MenuId.CommandPalette.id) {
......@@ -155,9 +172,12 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {
return result;
}
private _appendImplicitItems(result: IMenuItem[]) {
private _appendImplicitItems(result: (IMenuItem | ISubmenuItem)[]) {
const set = new Set<string>();
for (const { command, alt } of result) {
const temp = result.filter(item => { return isIMenuItem(item); }) as IMenuItem[];
for (const { command, alt } of temp) {
set.add(command.id);
if (alt) {
set.add(alt.id);
......@@ -186,6 +206,16 @@ export class ExecuteCommandAction extends Action {
}
}
export class SubmenuItemAction extends Action {
// private _options: IMenuActionOptions;
readonly item: ISubmenuItem;
constructor(item: ISubmenuItem) {
typeof item.title === 'string' ? super('', item.title, 'submenu') : super('', item.title.value, 'submenu');
this.item = item;
}
}
export class MenuItemAction extends ExecuteCommandAction {
private _options: IMenuActionOptions;
......
......@@ -9,10 +9,10 @@ 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 { MenuId, MenuRegistry, MenuItemAction, IMenu, IMenuItem, IMenuActionOptions, ISubmenuItem, SubmenuItemAction, isIMenuItem } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
type MenuItemGroup = [string, IMenuItem[]];
type MenuItemGroup = [string, (IMenuItem | ISubmenuItem)[]];
export class Menu implements IMenu {
......@@ -66,14 +66,14 @@ export class Menu implements IMenu {
return this._onDidChange.event;
}
getActions(options: IMenuActionOptions): [string, MenuItemAction[]][] {
const result: [string, MenuItemAction[]][] = [];
getActions(options: IMenuActionOptions): [string, (MenuItemAction | SubmenuItemAction)[]][] {
const result: [string, (MenuItemAction | SubmenuItemAction)[]][] = [];
for (let group of this._menuGroups) {
const [id, items] = group;
const activeActions: MenuItemAction[] = [];
const activeActions: (MenuItemAction | SubmenuItemAction)[] = [];
for (const item of items) {
if (this._contextKeyService.contextMatchesRules(item.when)) {
const action = new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService);
const action = isIMenuItem(item) ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) : new SubmenuItemAction(item);
action.order = item.order; //TODO@Ben order is menu item property, not an action property
activeActions.push(action);
}
......
......@@ -10,15 +10,16 @@ import { isMacintosh } from 'vs/base/common/platform';
// TODO: Add submenu support to remove layout, preferences, and recent top level
menubarCommands.setup();
recentMenuRegistration();
fileMenuRegistration();
editMenuRegistration();
recentMenuRegistration();
selectionMenuRegistration();
viewMenuRegistration();
layoutMenuRegistration();
goMenuRegistration();
debugMenuRegistration();
tasksMenuRegistration();
terminalMenuRegistration();
if (isMacintosh) {
windowMenuRegistration();
......@@ -74,6 +75,13 @@ function fileMenuRegistration() {
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"),
submenu: MenuId.MenubarRecentMenu,
group: '2_open',
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: '3_workspace',
command: {
......@@ -128,6 +136,13 @@ function fileMenuRegistration() {
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
title: nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences"),
submenu: MenuId.MenubarPreferencesMenu,
group: '5_autosave',
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: '6_close',
command: {
......@@ -603,6 +618,13 @@ function viewMenuRegistration() {
});
// TODO: Editor Layout Submenu
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
title: nls.localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout"),
submenu: MenuId.MenubarLayoutMenu,
group: '5_layout',
order: 1
});
// Workbench Layout
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
......@@ -1475,3 +1497,102 @@ function helpMenuRegistration() {
order: 1
});
}
function terminalMenuRegistration() {
// Manage
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '1_manage',
command: {
id: 'workbench.action.terminal.new',
title: nls.localize({ key: 'miNewTerminal', comment: ['&& denotes a mnemonic'] }, "&&New Terminal")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '1_manage',
command: {
id: 'workbench.action.terminal.split',
title: nls.localize({ key: 'miSplitTerminal', comment: ['&& denotes a mnemonic'] }, "&&Split Terminal")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '1_manage',
command: {
id: 'workbench.action.terminal.kill',
title: nls.localize({ key: 'miKillTerminal', comment: ['&& denotes a mnemonic'] }, "&&Kill Terminal")
},
order: 3
});
// Run
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '2_run',
command: {
id: 'workbench.action.terminal.clear',
title: nls.localize({ key: 'miClear', comment: ['&& denotes a mnemonic'] }, "&&Clear")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '2_run',
command: {
id: 'workbench.action.terminal.runActiveFile',
title: nls.localize({ key: 'miRunActiveFile', comment: ['&& denotes a mnemonic'] }, "Run &&Active File")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '2_run',
command: {
id: 'workbench.action.terminal.runSelectedFile',
title: nls.localize({ key: 'miRunSelectedText', comment: ['&& denotes a mnemonic'] }, "Run &&Selected Text")
},
order: 3
});
// Selection
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '3_selection',
command: {
id: 'workbench.action.terminal.scrollToPreviousCommand',
title: nls.localize({ key: 'miScrollToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Previous Command")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '3_selection',
command: {
id: 'workbench.action.terminal.scrollToNextCommand',
title: nls.localize({ key: 'miScrollToNextCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Next Command")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '3_selection',
command: {
id: 'workbench.action.terminal.selectToPreviousCommand',
title: nls.localize({ key: 'miSelectToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Select To Previous Command")
},
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, {
group: '3_selection',
command: {
id: 'workbench.action.terminal.selectToNextCommand',
title: nls.localize({ key: 'miSelectToNextCommand', comment: ['&& denotes a mnemonic'] }, "Select To Next Command")
},
order: 4
});
}
\ No newline at end of file
......@@ -11,18 +11,18 @@ import * as nls from 'vs/nls';
import * as browser from 'vs/base/browser/browser';
import { Part } from 'vs/workbench/browser/part';
import { IMenubarService, IMenubarMenu, IMenubarMenuItemAction, IMenubarData } from 'vs/platform/menubar/common/menubar';
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWindowService, MenuBarVisibility } from 'vs/platform/windows/common/windows';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ActionRunner, IActionRunner, IAction } from 'vs/base/common/actions';
import { Builder, $ } from 'vs/base/browser/builder';
import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { EventType, Dimension, toggleClass } from 'vs/base/browser/dom';
import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { Menu, IMenuOptions } from 'vs/base/browser/ui/menu/menu';
import { Menu, IMenuOptions, SubmenuAction } from 'vs/base/browser/ui/menu/menu';
import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
......@@ -53,15 +53,13 @@ export class MenubarPart extends Part {
private topLevelMenus: {
'File': IMenu;
'Edit': IMenu;
'Recent': IMenu;
'Selection': IMenu;
'View': IMenu;
'Layout': IMenu;
'Go': IMenu;
'Terminal': IMenu;
'Debug': IMenu;
'Tasks': IMenu;
'Window'?: IMenu;
'Preferences': IMenu;
'Help': IMenu;
[index: string]: IMenu;
};
......@@ -69,14 +67,12 @@ export class MenubarPart extends Part {
private topLevelTitles = {
'File': nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File"),
'Edit': nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit"),
'Recent': nls.localize({ key: 'mRecent', comment: ['&& denotes a mnemonic'] }, "&&Recent"),
'Selection': nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection"),
'View': nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View"),
'Layout': nls.localize({ key: 'mLayout', comment: ['&& denotes a mnemonic'] }, "&&Layout"),
'Go': nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"),
'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"),
'Debug': nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"),
'Tasks': nls.localize({ key: 'mTasks', comment: ['&& denotes a mnemonic'] }, "&&Tasks"),
'Preferences': nls.localize({ key: 'mPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences"),
'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")
};
......@@ -120,14 +116,12 @@ export class MenubarPart extends Part {
this.topLevelMenus = {
'File': this.menuService.createMenu(MenuId.MenubarFileMenu, this.contextKeyService),
'Edit': this.menuService.createMenu(MenuId.MenubarEditMenu, this.contextKeyService),
'Recent': this.menuService.createMenu(MenuId.MenubarRecentMenu, this.contextKeyService),
'Selection': this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService),
'View': this.menuService.createMenu(MenuId.MenubarViewMenu, this.contextKeyService),
'Layout': this.menuService.createMenu(MenuId.MenubarLayoutMenu, this.contextKeyService),
'Go': this.menuService.createMenu(MenuId.MenubarGoMenu, this.contextKeyService),
'Terminal': this.menuService.createMenu(MenuId.MenubarTerminalMenu, this.contextKeyService),
'Debug': this.menuService.createMenu(MenuId.MenubarDebugMenu, this.contextKeyService),
'Tasks': this.menuService.createMenu(MenuId.MenubarTasksMenu, this.contextKeyService),
'Preferences': this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService),
'Help': this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService)
};
......@@ -378,27 +372,34 @@ export class MenubarPart extends Part {
titleElement: titleElement
});
// Update cached actions array for CustomMenus
const updateActions = () => {
this.customMenus[menuIndex].actions = [];
const updateActions = (menu: IMenu, target: IAction[]) => {
target.splice(0);
let groups = menu.getActions();
for (let group of groups) {
const [, actions] = group;
actions.map((action: IAction) => {
action.label = this.calculateActionLabel(action);
this.setCheckedStatus(action);
});
for (let action of actions) {
if (action instanceof SubmenuItemAction) {
const submenu = this.menuService.createMenu(action.item.submenu, this.contextKeyService);
const submenuActions = [];
updateActions(submenu, submenuActions);
target.push(new SubmenuAction(action.label, submenuActions));
} else {
action.label = this.calculateActionLabel(action);
this.setCheckedStatus(action);
target.push(action);
}
}
this.customMenus[menuIndex].actions.push(...actions);
this.customMenus[menuIndex].actions.push(new Separator());
target.push(new Separator());
}
this.customMenus[menuIndex].actions.pop();
target.pop();
};
menu.onDidChange(updateActions);
updateActions();
this.customMenus[menuIndex].actions = [];
menu.onDidChange(() => updateActions(menu, this.customMenus[menuIndex].actions));
updateActions(menu, this.customMenus[menuIndex].actions);
this.customMenus[menuIndex].titleElement.on(EventType.CLICK, (event) => {
this.toggleCustomMenu(menuIndex);
......@@ -537,14 +538,6 @@ export class MenubarPart extends Part {
this.toggleCustomMenu(0);
}
private _getActionItem(action: IAction): ActionItem {
const keybinding = this.keybindingService.lookupKeybinding(action.id);
if (keybinding) {
return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true });
}
return null;
}
private toggleCustomMenu(menuIndex: number): void {
const customMenu = this.customMenus[menuIndex];
......@@ -573,9 +566,10 @@ export class MenubarPart extends Part {
});
let menuOptions: IMenuOptions = {
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id),
actionRunner: this.actionRunner,
ariaLabel: 'File',
actionItemProvider: (action) => { return this._getActionItem(action); }
// ariaLabel: 'File'
// actionItemProvider: (action) => { return this._getActionItem(action); }
};
let menuWidget = new Menu(menuHolder.getHTMLElement(), customMenu.actions, menuOptions);
......
......@@ -74,6 +74,10 @@
padding: 0.5em 2em;
}
.monaco-shell .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
padding: 0.5em 1em;
}
/* START Keyboard Focus Indication Styles */
.monaco-shell [tabindex="0"]:focus,
......
......@@ -412,7 +412,7 @@ export class CommandsHandler extends QuickOpenHandler {
// Other Actions
const menu = this.editorService.invokeWithinEditorContext(accessor => this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService)));
const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], <MenuItemAction[]>[]);
const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], <MenuItemAction[]>[]).filter(action => action instanceof MenuItemAction) as MenuItemAction[];
const commandEntries = this.menuItemActionsToEntries(menuActions, searchValue);
// Concat
......
......@@ -5,7 +5,7 @@
'use strict';
import * as assert from 'assert';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions';
import { MenuService } from 'vs/workbench/services/actions/common/menuService';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { NullCommandService } from 'vs/platform/commands/common/commands';
......@@ -190,13 +190,15 @@ suite('MenuService', function () {
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;
if (isIMenuItem(item)) {
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);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册