mirror of
https://github.com/coder/code-server.git
synced 2026-05-06 20:41:59 +02:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createCSSRule, asCSSUrl, ModifierKeyEmitter } from 'vs/base/browser/dom';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IAction, Separator } from 'vs/base/common/actions';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon } from 'vs/platform/actions/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
|
||||
|
||||
export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): IDisposable {
|
||||
const groups = menu.getActions(options);
|
||||
const useAlternativeActions = ModifierKeyEmitter.getInstance().keyStatus.altKey;
|
||||
fillInActions(groups, target, useAlternativeActions, isPrimaryGroup);
|
||||
return asDisposable(groups);
|
||||
}
|
||||
|
||||
export function createAndFillInActionBarActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): IDisposable {
|
||||
const groups = menu.getActions(options);
|
||||
// Action bars handle alternative actions on their own so the alternative actions should be ignored
|
||||
fillInActions(groups, target, false, isPrimaryGroup);
|
||||
return asDisposable(groups);
|
||||
}
|
||||
|
||||
function asDisposable(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
for (const [, actions] of groups) {
|
||||
for (const action of actions) {
|
||||
disposables.add(action);
|
||||
}
|
||||
}
|
||||
return disposables;
|
||||
}
|
||||
|
||||
function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, useAlternativeActions: boolean, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
|
||||
for (let tuple of groups) {
|
||||
let [group, actions] = tuple;
|
||||
if (useAlternativeActions) {
|
||||
actions = actions.map(a => (a instanceof MenuItemAction) && !!a.alt ? a.alt : a);
|
||||
}
|
||||
|
||||
if (isPrimaryGroup(group)) {
|
||||
const to = Array.isArray(target) ? target : target.primary;
|
||||
|
||||
to.unshift(...actions);
|
||||
} else {
|
||||
const to = Array.isArray(target) ? target : target.secondary;
|
||||
|
||||
if (to.length > 0) {
|
||||
to.push(new Separator());
|
||||
}
|
||||
|
||||
to.push(...actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ids = new IdGenerator('menu-item-action-item-icon-');
|
||||
|
||||
const ICON_PATH_TO_CSS_RULES = new Map<string /* path*/, string /* CSS rule */>();
|
||||
|
||||
export class MenuEntryActionViewItem extends ActionViewItem {
|
||||
|
||||
private _wantsAltCommand: boolean = false;
|
||||
private readonly _itemClassDispose = this._register(new MutableDisposable());
|
||||
private readonly _altKey: ModifierKeyEmitter;
|
||||
|
||||
constructor(
|
||||
readonly _action: MenuItemAction,
|
||||
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
|
||||
@INotificationService protected _notificationService: INotificationService
|
||||
) {
|
||||
super(undefined, _action, { icon: !!(_action.class || _action.item.icon), label: !_action.class && !_action.item.icon });
|
||||
this._altKey = ModifierKeyEmitter.getInstance();
|
||||
}
|
||||
|
||||
protected get _commandAction(): IAction {
|
||||
return this._wantsAltCommand && (<MenuItemAction>this._action).alt || this._action;
|
||||
}
|
||||
|
||||
onClick(event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.actionRunner.run(this._commandAction, this._context)
|
||||
.then(undefined, err => this._notificationService.error(err));
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this._updateItemClass(this._action.item);
|
||||
|
||||
let mouseOver = false;
|
||||
|
||||
let alternativeKeyDown = this._altKey.keyStatus.altKey;
|
||||
|
||||
const updateAltState = () => {
|
||||
const wantsAltCommand = mouseOver && alternativeKeyDown;
|
||||
if (wantsAltCommand !== this._wantsAltCommand) {
|
||||
this._wantsAltCommand = wantsAltCommand;
|
||||
this.updateLabel();
|
||||
this.updateTooltip();
|
||||
this.updateClass();
|
||||
}
|
||||
};
|
||||
|
||||
if (this._action.alt) {
|
||||
this._register(this._altKey.event(value => {
|
||||
alternativeKeyDown = value.altKey;
|
||||
updateAltState();
|
||||
}));
|
||||
}
|
||||
|
||||
this._register(domEvent(container, 'mouseleave')(_ => {
|
||||
mouseOver = false;
|
||||
updateAltState();
|
||||
}));
|
||||
|
||||
this._register(domEvent(container, 'mouseenter')(e => {
|
||||
mouseOver = true;
|
||||
updateAltState();
|
||||
}));
|
||||
}
|
||||
|
||||
updateLabel(): void {
|
||||
if (this.options.label && this.label) {
|
||||
this.label.textContent = this._commandAction.label;
|
||||
}
|
||||
}
|
||||
|
||||
updateTooltip(): void {
|
||||
if (this.label) {
|
||||
const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id);
|
||||
const keybindingLabel = keybinding && keybinding.getLabel();
|
||||
|
||||
const tooltip = this._commandAction.tooltip || this._commandAction.label;
|
||||
this.label.title = keybindingLabel
|
||||
? localize('titleAndKb', "{0} ({1})", tooltip, keybindingLabel)
|
||||
: tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
updateClass(): void {
|
||||
if (this.options.icon) {
|
||||
if (this._commandAction !== this._action) {
|
||||
if (this._action.alt) {
|
||||
this._updateItemClass(this._action.alt.item);
|
||||
}
|
||||
} else if ((<MenuItemAction>this._action).alt) {
|
||||
this._updateItemClass(this._action.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _updateItemClass(item: ICommandAction): void {
|
||||
this._itemClassDispose.value = undefined;
|
||||
|
||||
const icon = this._commandAction.checked && (item.toggled as { icon?: Icon })?.icon ? (item.toggled as { icon: Icon }).icon : item.icon;
|
||||
|
||||
if (ThemeIcon.isThemeIcon(icon)) {
|
||||
// theme icons
|
||||
const iconClass = ThemeIcon.asClassName(icon);
|
||||
if (this.label && iconClass) {
|
||||
this.label.classList.add(...iconClass.split(' '));
|
||||
this._itemClassDispose.value = toDisposable(() => {
|
||||
if (this.label) {
|
||||
this.label.classList.remove(...iconClass.split(' '));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} else if (icon) {
|
||||
// icon path
|
||||
let iconClass: string;
|
||||
|
||||
if (icon.dark?.scheme) {
|
||||
|
||||
const iconPathMapKey = icon.dark.toString();
|
||||
|
||||
if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
|
||||
iconClass = ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!;
|
||||
} else {
|
||||
iconClass = ids.nextId();
|
||||
createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon.light || icon.dark)}`);
|
||||
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(icon.dark)}`);
|
||||
ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass);
|
||||
}
|
||||
|
||||
if (this.label) {
|
||||
this.label.classList.add('icon', ...iconClass.split(' '));
|
||||
this._itemClassDispose.value = toDisposable(() => {
|
||||
if (this.label) {
|
||||
this.label.classList.remove('icon', ...iconClass.split(' '));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem {
|
||||
|
||||
constructor(
|
||||
action: SubmenuItemAction,
|
||||
@INotificationService _notificationService: INotificationService,
|
||||
@IContextMenuService _contextMenuService: IContextMenuService
|
||||
) {
|
||||
let classNames: string | string[] | undefined;
|
||||
|
||||
if (action.item.icon) {
|
||||
if (ThemeIcon.isThemeIcon(action.item.icon)) {
|
||||
classNames = ThemeIcon.asClassName(action.item.icon)!;
|
||||
} else if (action.item.icon.dark?.scheme) {
|
||||
const iconPathMapKey = action.item.icon.dark.toString();
|
||||
|
||||
if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
|
||||
classNames = ['icon', ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!];
|
||||
} else {
|
||||
const className = ids.nextId();
|
||||
classNames = ['icon', className];
|
||||
createCSSRule(`.icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.light || action.item.icon.dark)}`);
|
||||
createCSSRule(`.vs-dark .icon.${className}, .hc-black .icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.dark)}`);
|
||||
ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, className);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super(action, action.actions, _contextMenuService, { classNames: classNames, menuAsChild: true });
|
||||
}
|
||||
}
|
||||
535
lib/vscode/src/vs/platform/actions/common/actions.ts
Normal file
535
lib/vscode/src/vs/platform/actions/common/actions.ts
Normal file
@@ -0,0 +1,535 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
|
||||
import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IConstructorSignature2, createDecorator, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindings, KeybindingsRegistry, IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { UriDto } from 'vs/base/common/types';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
|
||||
export interface ILocalizedString {
|
||||
value: string;
|
||||
original: string;
|
||||
}
|
||||
|
||||
export type Icon = { dark?: URI; light?: URI; } | ThemeIcon;
|
||||
|
||||
export interface ICommandAction {
|
||||
id: string;
|
||||
title: string | ILocalizedString;
|
||||
category?: string | ILocalizedString;
|
||||
tooltip?: string | ILocalizedString;
|
||||
icon?: Icon;
|
||||
precondition?: ContextKeyExpression;
|
||||
toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString };
|
||||
}
|
||||
|
||||
export type ISerializableCommandAction = UriDto<ICommandAction>;
|
||||
|
||||
export interface IMenuItem {
|
||||
command: ICommandAction;
|
||||
alt?: ICommandAction;
|
||||
when?: ContextKeyExpression;
|
||||
group?: 'navigation' | string;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export interface ISubmenuItem {
|
||||
title: string | ILocalizedString;
|
||||
submenu: MenuId;
|
||||
icon?: Icon;
|
||||
when?: ContextKeyExpression;
|
||||
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 _idPool = 0;
|
||||
|
||||
static readonly CommandPalette = new MenuId('CommandPalette');
|
||||
static readonly DebugBreakpointsContext = new MenuId('DebugBreakpointsContext');
|
||||
static readonly DebugCallStackContext = new MenuId('DebugCallStackContext');
|
||||
static readonly DebugConsoleContext = new MenuId('DebugConsoleContext');
|
||||
static readonly DebugVariablesContext = new MenuId('DebugVariablesContext');
|
||||
static readonly DebugWatchContext = new MenuId('DebugWatchContext');
|
||||
static readonly DebugToolBar = new MenuId('DebugToolBar');
|
||||
static readonly EditorContext = new MenuId('EditorContext');
|
||||
static readonly EditorContextPeek = new MenuId('EditorContextPeek');
|
||||
static readonly EditorTitle = new MenuId('EditorTitle');
|
||||
static readonly EditorTitleContext = new MenuId('EditorTitleContext');
|
||||
static readonly EmptyEditorGroupContext = new MenuId('EmptyEditorGroupContext');
|
||||
static readonly ExplorerContext = new MenuId('ExplorerContext');
|
||||
static readonly ExtensionContext = new MenuId('ExtensionContext');
|
||||
static readonly GlobalActivity = new MenuId('GlobalActivity');
|
||||
static readonly MenubarAppearanceMenu = new MenuId('MenubarAppearanceMenu');
|
||||
static readonly MenubarDebugMenu = new MenuId('MenubarDebugMenu');
|
||||
static readonly MenubarEditMenu = new MenuId('MenubarEditMenu');
|
||||
static readonly MenubarFileMenu = new MenuId('MenubarFileMenu');
|
||||
static readonly MenubarGoMenu = new MenuId('MenubarGoMenu');
|
||||
static readonly MenubarHelpMenu = new MenuId('MenubarHelpMenu');
|
||||
static readonly MenubarLayoutMenu = new MenuId('MenubarLayoutMenu');
|
||||
static readonly MenubarNewBreakpointMenu = new MenuId('MenubarNewBreakpointMenu');
|
||||
static readonly MenubarPreferencesMenu = new MenuId('MenubarPreferencesMenu');
|
||||
static readonly MenubarRecentMenu = new MenuId('MenubarRecentMenu');
|
||||
static readonly MenubarSelectionMenu = new MenuId('MenubarSelectionMenu');
|
||||
static readonly MenubarSwitchEditorMenu = new MenuId('MenubarSwitchEditorMenu');
|
||||
static readonly MenubarSwitchGroupMenu = new MenuId('MenubarSwitchGroupMenu');
|
||||
static readonly MenubarTerminalMenu = new MenuId('MenubarTerminalMenu');
|
||||
static readonly MenubarViewMenu = new MenuId('MenubarViewMenu');
|
||||
static readonly MenubarWebNavigationMenu = new MenuId('MenubarWebNavigationMenu');
|
||||
static readonly OpenEditorsContext = new MenuId('OpenEditorsContext');
|
||||
static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext');
|
||||
static readonly SCMChangeContext = new MenuId('SCMChangeContext');
|
||||
static readonly SCMResourceContext = new MenuId('SCMResourceContext');
|
||||
static readonly SCMResourceFolderContext = new MenuId('SCMResourceFolderContext');
|
||||
static readonly SCMResourceGroupContext = new MenuId('SCMResourceGroupContext');
|
||||
static readonly SCMSourceControl = new MenuId('SCMSourceControl');
|
||||
static readonly SCMTitle = new MenuId('SCMTitle');
|
||||
static readonly SearchContext = new MenuId('SearchContext');
|
||||
static readonly StatusBarWindowIndicatorMenu = new MenuId('StatusBarWindowIndicatorMenu');
|
||||
static readonly TouchBarContext = new MenuId('TouchBarContext');
|
||||
static readonly TitleBarContext = new MenuId('TitleBarContext');
|
||||
static readonly TunnelContext = new MenuId('TunnelContext');
|
||||
static readonly TunnelInline = new MenuId('TunnelInline');
|
||||
static readonly TunnelTitle = new MenuId('TunnelTitle');
|
||||
static readonly ViewItemContext = new MenuId('ViewItemContext');
|
||||
static readonly ViewContainerTitleContext = new MenuId('ViewContainerTitleContext');
|
||||
static readonly ViewTitle = new MenuId('ViewTitle');
|
||||
static readonly ViewTitleContext = new MenuId('ViewTitleContext');
|
||||
static readonly CommentThreadTitle = new MenuId('CommentThreadTitle');
|
||||
static readonly CommentThreadActions = new MenuId('CommentThreadActions');
|
||||
static readonly CommentTitle = new MenuId('CommentTitle');
|
||||
static readonly CommentActions = new MenuId('CommentActions');
|
||||
static readonly NotebookCellTitle = new MenuId('NotebookCellTitle');
|
||||
static readonly NotebookCellInsert = new MenuId('NotebookCellInsert');
|
||||
static readonly NotebookCellBetween = new MenuId('NotebookCellBetween');
|
||||
static readonly NotebookCellListTop = new MenuId('NotebookCellTop');
|
||||
static readonly NotebookDiffCellInputTitle = new MenuId('NotebookDiffCellInputTitle');
|
||||
static readonly NotebookDiffCellMetadataTitle = new MenuId('NotebookDiffCellMetadataTitle');
|
||||
static readonly NotebookDiffCellOutputsTitle = new MenuId('NotebookDiffCellOutputsTitle');
|
||||
static readonly BulkEditTitle = new MenuId('BulkEditTitle');
|
||||
static readonly BulkEditContext = new MenuId('BulkEditContext');
|
||||
static readonly TimelineItemContext = new MenuId('TimelineItemContext');
|
||||
static readonly TimelineTitle = new MenuId('TimelineTitle');
|
||||
static readonly TimelineTitleContext = new MenuId('TimelineTitleContext');
|
||||
static readonly AccountsContext = new MenuId('AccountsContext');
|
||||
|
||||
readonly id: number;
|
||||
readonly _debugName: string;
|
||||
|
||||
constructor(debugName: string) {
|
||||
this.id = MenuId._idPool++;
|
||||
this._debugName = debugName;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IMenuActionOptions {
|
||||
arg?: any;
|
||||
shouldForwardArgs?: boolean;
|
||||
}
|
||||
|
||||
export interface IMenu extends IDisposable {
|
||||
readonly onDidChange: Event<IMenu | undefined>;
|
||||
getActions(options?: IMenuActionOptions): [string, Array<MenuItemAction | SubmenuItemAction>][];
|
||||
}
|
||||
|
||||
export const IMenuService = createDecorator<IMenuService>('menuService');
|
||||
|
||||
export interface IMenuService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
createMenu(id: MenuId, scopedKeybindingService: IContextKeyService): IMenu;
|
||||
}
|
||||
|
||||
export type ICommandsMap = Map<string, ICommandAction>;
|
||||
|
||||
export interface IMenuRegistryChangeEvent {
|
||||
has(id: MenuId): boolean;
|
||||
}
|
||||
|
||||
export interface IMenuRegistry {
|
||||
readonly onDidChangeMenu: Event<IMenuRegistryChangeEvent>;
|
||||
addCommands(newCommands: Iterable<ICommandAction>): IDisposable;
|
||||
addCommand(userCommand: ICommandAction): IDisposable;
|
||||
getCommand(id: string): ICommandAction | undefined;
|
||||
getCommands(): ICommandsMap;
|
||||
appendMenuItems(items: Iterable<{ id: MenuId, item: IMenuItem | ISubmenuItem }>): IDisposable;
|
||||
appendMenuItem(menu: MenuId, item: IMenuItem | ISubmenuItem): IDisposable;
|
||||
getMenuItems(loc: MenuId): Array<IMenuItem | ISubmenuItem>;
|
||||
}
|
||||
|
||||
export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {
|
||||
|
||||
private readonly _commands = new Map<string, ICommandAction>();
|
||||
private readonly _menuItems = new Map<MenuId, LinkedList<IMenuItem | ISubmenuItem>>();
|
||||
private readonly _onDidChangeMenu = new Emitter<IMenuRegistryChangeEvent>();
|
||||
|
||||
readonly onDidChangeMenu: Event<IMenuRegistryChangeEvent> = this._onDidChangeMenu.event;
|
||||
|
||||
addCommand(command: ICommandAction): IDisposable {
|
||||
return this.addCommands(Iterable.single(command));
|
||||
}
|
||||
|
||||
private readonly _commandPaletteChangeEvent: IMenuRegistryChangeEvent = {
|
||||
has: id => id === MenuId.CommandPalette
|
||||
};
|
||||
|
||||
addCommands(commands: Iterable<ICommandAction>): IDisposable {
|
||||
for (const command of commands) {
|
||||
this._commands.set(command.id, command);
|
||||
}
|
||||
this._onDidChangeMenu.fire(this._commandPaletteChangeEvent);
|
||||
return toDisposable(() => {
|
||||
let didChange = false;
|
||||
for (const command of commands) {
|
||||
didChange = this._commands.delete(command.id) || didChange;
|
||||
}
|
||||
if (didChange) {
|
||||
this._onDidChangeMenu.fire(this._commandPaletteChangeEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCommand(id: string): ICommandAction | undefined {
|
||||
return this._commands.get(id);
|
||||
}
|
||||
|
||||
getCommands(): ICommandsMap {
|
||||
const map = new Map<string, ICommandAction>();
|
||||
this._commands.forEach((value, key) => map.set(key, value));
|
||||
return map;
|
||||
}
|
||||
|
||||
appendMenuItem(id: MenuId, item: IMenuItem | ISubmenuItem): IDisposable {
|
||||
return this.appendMenuItems(Iterable.single({ id, item }));
|
||||
}
|
||||
|
||||
appendMenuItems(items: Iterable<{ id: MenuId, item: IMenuItem | ISubmenuItem }>): IDisposable {
|
||||
|
||||
const changedIds = new Set<MenuId>();
|
||||
const toRemove = new LinkedList<Function>();
|
||||
|
||||
for (const { id, item } of items) {
|
||||
let list = this._menuItems.get(id);
|
||||
if (!list) {
|
||||
list = new LinkedList();
|
||||
this._menuItems.set(id, list);
|
||||
}
|
||||
toRemove.push(list.push(item));
|
||||
changedIds.add(id);
|
||||
}
|
||||
|
||||
this._onDidChangeMenu.fire(changedIds);
|
||||
|
||||
return toDisposable(() => {
|
||||
if (toRemove.size > 0) {
|
||||
for (let fn of toRemove) {
|
||||
fn();
|
||||
}
|
||||
this._onDidChangeMenu.fire(changedIds);
|
||||
toRemove.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getMenuItems(id: MenuId): Array<IMenuItem | ISubmenuItem> {
|
||||
let result: Array<IMenuItem | ISubmenuItem>;
|
||||
if (this._menuItems.has(id)) {
|
||||
result = [...this._menuItems.get(id)!];
|
||||
} else {
|
||||
result = [];
|
||||
}
|
||||
if (id === MenuId.CommandPalette) {
|
||||
// CommandPalette is special because it shows
|
||||
// all commands by default
|
||||
this._appendImplicitItems(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _appendImplicitItems(result: Array<IMenuItem | ISubmenuItem>) {
|
||||
const set = new Set<string>();
|
||||
|
||||
for (const item of result) {
|
||||
if (isIMenuItem(item)) {
|
||||
set.add(item.command.id);
|
||||
if (item.alt) {
|
||||
set.add(item.alt.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._commands.forEach((command, id) => {
|
||||
if (!set.has(id)) {
|
||||
result.push({ command });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export class ExecuteCommandAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@ICommandService private readonly _commandService: ICommandService) {
|
||||
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(...args: any[]): Promise<any> {
|
||||
return this._commandService.executeCommand(this.id, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmenuItemAction extends SubmenuAction {
|
||||
|
||||
readonly item: ISubmenuItem;
|
||||
|
||||
constructor(
|
||||
item: ISubmenuItem,
|
||||
menuService: IMenuService,
|
||||
contextKeyService: IContextKeyService,
|
||||
options?: IMenuActionOptions
|
||||
) {
|
||||
const result: IAction[] = [];
|
||||
const menu = menuService.createMenu(item.submenu, contextKeyService);
|
||||
const groups = menu.getActions(options);
|
||||
menu.dispose();
|
||||
|
||||
for (let group of groups) {
|
||||
const [, actions] = group;
|
||||
|
||||
if (actions.length > 0) {
|
||||
result.push(...actions);
|
||||
result.push(new Separator());
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length) {
|
||||
result.pop(); // remove last separator
|
||||
}
|
||||
|
||||
super(`submenuitem.${item.submenu.id}`, typeof item.title === 'string' ? item.title : item.title.value, result, 'submenu');
|
||||
this.item = item;
|
||||
}
|
||||
}
|
||||
|
||||
export class MenuItemAction extends ExecuteCommandAction {
|
||||
|
||||
readonly item: ICommandAction;
|
||||
readonly alt: MenuItemAction | undefined;
|
||||
|
||||
private _options: IMenuActionOptions;
|
||||
|
||||
constructor(
|
||||
item: ICommandAction,
|
||||
alt: ICommandAction | undefined,
|
||||
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 = undefined;
|
||||
this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition);
|
||||
this._tooltip = item.tooltip ? typeof item.tooltip === 'string' ? item.tooltip : item.tooltip.value : undefined;
|
||||
|
||||
if (item.toggled) {
|
||||
const toggled = ((item.toggled as { condition: ContextKeyExpression }).condition ? item.toggled : { condition: item.toggled }) as {
|
||||
condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString
|
||||
};
|
||||
this._checked = contextKeyService.contextMatchesRules(toggled.condition);
|
||||
if (this._checked && toggled.tooltip) {
|
||||
this._tooltip = typeof toggled.tooltip === 'string' ? toggled.tooltip : toggled.tooltip.value;
|
||||
}
|
||||
}
|
||||
|
||||
this._options = options || {};
|
||||
|
||||
this.item = item;
|
||||
this.alt = alt ? new MenuItemAction(alt, undefined, this._options, contextKeyService, commandService) : undefined;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.alt) {
|
||||
this.alt.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
run(...args: any[]): Promise<any> {
|
||||
let runArgs: any[] = [];
|
||||
|
||||
if (this._options.arg) {
|
||||
runArgs = [...runArgs, this._options.arg];
|
||||
}
|
||||
|
||||
if (this._options.shouldForwardArgs) {
|
||||
runArgs = [...runArgs, ...args];
|
||||
}
|
||||
|
||||
return super.run(...runArgs);
|
||||
}
|
||||
}
|
||||
|
||||
export class SyncActionDescriptor {
|
||||
|
||||
private readonly _descriptor: SyncDescriptor0<Action>;
|
||||
|
||||
private readonly _id: string;
|
||||
private readonly _label?: string;
|
||||
private readonly _keybindings: IKeybindings | undefined;
|
||||
private readonly _keybindingContext: ContextKeyExpression | undefined;
|
||||
private readonly _keybindingWeight: number | undefined;
|
||||
|
||||
public static create<Services extends BrandedService[]>(ctor: { new(id: string, label: string, ...services: Services): Action },
|
||||
id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpression, keybindingWeight?: number
|
||||
): SyncActionDescriptor {
|
||||
return new SyncActionDescriptor(ctor as IConstructorSignature2<string, string | undefined, Action>, id, label, keybindings, keybindingContext, keybindingWeight);
|
||||
}
|
||||
|
||||
public static from<Services extends BrandedService[]>(
|
||||
ctor: {
|
||||
new(id: string, label: string, ...services: Services): Action;
|
||||
readonly ID: string;
|
||||
readonly LABEL: string;
|
||||
},
|
||||
keybindings?: IKeybindings, keybindingContext?: ContextKeyExpression, keybindingWeight?: number
|
||||
): SyncActionDescriptor {
|
||||
return SyncActionDescriptor.create(ctor, ctor.ID, ctor.LABEL, keybindings, keybindingContext, keybindingWeight);
|
||||
}
|
||||
|
||||
private constructor(ctor: IConstructorSignature2<string, string | undefined, Action>,
|
||||
id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpression, keybindingWeight?: number
|
||||
) {
|
||||
this._id = id;
|
||||
this._label = label;
|
||||
this._keybindings = keybindings;
|
||||
this._keybindingContext = keybindingContext;
|
||||
this._keybindingWeight = keybindingWeight;
|
||||
this._descriptor = createSyncDescriptor(ctor, this._id, this._label);
|
||||
}
|
||||
|
||||
public get syncDescriptor(): SyncDescriptor0<Action> {
|
||||
return this._descriptor;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get label(): string | undefined {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
public get keybindings(): IKeybindings | undefined {
|
||||
return this._keybindings;
|
||||
}
|
||||
|
||||
public get keybindingContext(): ContextKeyExpression | undefined {
|
||||
return this._keybindingContext;
|
||||
}
|
||||
|
||||
public get keybindingWeight(): number | undefined {
|
||||
return this._keybindingWeight;
|
||||
}
|
||||
}
|
||||
|
||||
//#region --- IAction2
|
||||
|
||||
type OneOrN<T> = T | T[];
|
||||
|
||||
export interface IAction2Options extends ICommandAction {
|
||||
|
||||
/**
|
||||
* Shorthand to add this command to the command palette
|
||||
*/
|
||||
f1?: boolean;
|
||||
|
||||
/**
|
||||
* One or many menu items.
|
||||
*/
|
||||
menu?: OneOrN<{ id: MenuId } & Omit<IMenuItem, 'command'>>;
|
||||
|
||||
/**
|
||||
* One keybinding.
|
||||
*/
|
||||
keybinding?: OneOrN<Omit<IKeybindingRule, 'id'>>;
|
||||
|
||||
/**
|
||||
* Metadata about this command, used for API commands or when
|
||||
* showing keybindings that have no other UX.
|
||||
*/
|
||||
description?: ICommandHandlerDescription;
|
||||
}
|
||||
|
||||
export abstract class Action2 {
|
||||
constructor(readonly desc: Readonly<IAction2Options>) { }
|
||||
abstract run(accessor: ServicesAccessor, ...args: any[]): any;
|
||||
}
|
||||
|
||||
export function registerAction2(ctor: { new(): Action2 }): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
const action = new ctor();
|
||||
|
||||
const { f1, menu, keybinding, description, ...command } = action.desc;
|
||||
|
||||
// command
|
||||
disposables.add(CommandsRegistry.registerCommand({
|
||||
id: command.id,
|
||||
handler: (accessor, ...args) => action.run(accessor, ...args),
|
||||
description: description,
|
||||
}));
|
||||
|
||||
// menu
|
||||
if (Array.isArray(menu)) {
|
||||
disposables.add(MenuRegistry.appendMenuItems(menu.map(item => ({ id: item.id, item: { command, ...item } }))));
|
||||
|
||||
} else if (menu) {
|
||||
disposables.add(MenuRegistry.appendMenuItem(menu.id, { command, ...menu }));
|
||||
}
|
||||
if (f1) {
|
||||
disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command, when: command.precondition }));
|
||||
disposables.add(MenuRegistry.addCommand(command));
|
||||
}
|
||||
|
||||
// keybinding
|
||||
if (Array.isArray(keybinding)) {
|
||||
for (let item of keybinding) {
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
...item,
|
||||
id: command.id,
|
||||
when: command.precondition ? ContextKeyExpr.and(command.precondition, item.when) : item.when
|
||||
});
|
||||
}
|
||||
} else if (keybinding) {
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
...keybinding,
|
||||
id: command.id,
|
||||
when: command.precondition ? ContextKeyExpr.and(command.precondition, keybinding.when) : keybinding.when
|
||||
});
|
||||
}
|
||||
|
||||
return disposables;
|
||||
}
|
||||
//#endregion
|
||||
187
lib/vscode/src/vs/platform/actions/common/menuService.ts
Normal file
187
lib/vscode/src/vs/platform/actions/common/menuService.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction, ILocalizedString } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IContextKeyService, IContextKeyChangeEvent, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export class MenuService implements IMenuService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu {
|
||||
return new Menu(id, this._commandService, contextKeyService, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type MenuItemGroup = [string, Array<IMenuItem | ISubmenuItem>];
|
||||
|
||||
class Menu implements IMenu {
|
||||
|
||||
private readonly _onDidChange = new Emitter<IMenu | undefined>();
|
||||
private readonly _dispoables = new DisposableStore();
|
||||
|
||||
private _menuGroups: MenuItemGroup[] = [];
|
||||
private _contextKeys: Set<string> = new Set();
|
||||
|
||||
constructor(
|
||||
private readonly _id: MenuId,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly _menuService: IMenuService
|
||||
) {
|
||||
this._build();
|
||||
|
||||
// rebuild this menu whenever the menu registry reports an
|
||||
// event for this MenuId
|
||||
this._dispoables.add(Event.debounce(
|
||||
Event.filter(MenuRegistry.onDidChangeMenu, set => set.has(this._id)),
|
||||
() => { },
|
||||
50
|
||||
)(this._build, this));
|
||||
|
||||
// when context keys change we need to check if the menu also
|
||||
// has changed
|
||||
this._dispoables.add(Event.debounce<IContextKeyChangeEvent, boolean>(
|
||||
this._contextKeyService.onDidChangeContext,
|
||||
(last, event) => last || event.affectsSome(this._contextKeys),
|
||||
50
|
||||
)(e => e && this._onDidChange.fire(undefined), this));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._dispoables.dispose();
|
||||
this._onDidChange.dispose();
|
||||
}
|
||||
|
||||
private _build(): void {
|
||||
|
||||
// reset
|
||||
this._menuGroups.length = 0;
|
||||
this._contextKeys.clear();
|
||||
|
||||
const menuItems = MenuRegistry.getMenuItems(this._id);
|
||||
|
||||
let group: MenuItemGroup | undefined;
|
||||
menuItems.sort(Menu._compareMenuItems);
|
||||
|
||||
for (let item of menuItems) {
|
||||
// group by groupId
|
||||
const groupName = item.group || '';
|
||||
if (!group || group[0] !== groupName) {
|
||||
group = [groupName, []];
|
||||
this._menuGroups.push(group);
|
||||
}
|
||||
group![1].push(item);
|
||||
|
||||
// keep keys for eventing
|
||||
Menu._fillInKbExprKeys(item.when, this._contextKeys);
|
||||
|
||||
// keep precondition keys for event if applicable
|
||||
if (isIMenuItem(item) && item.command.precondition) {
|
||||
Menu._fillInKbExprKeys(item.command.precondition, this._contextKeys);
|
||||
}
|
||||
|
||||
// keep toggled keys for event if applicable
|
||||
if (isIMenuItem(item) && item.command.toggled) {
|
||||
const toggledExpression: ContextKeyExpression = (item.command.toggled as { condition: ContextKeyExpression }).condition || item.command.toggled;
|
||||
Menu._fillInKbExprKeys(toggledExpression, this._contextKeys);
|
||||
}
|
||||
}
|
||||
this._onDidChange.fire(this);
|
||||
}
|
||||
|
||||
get onDidChange(): Event<IMenu | undefined> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
getActions(options: IMenuActionOptions): [string, Array<MenuItemAction | SubmenuItemAction>][] {
|
||||
const result: [string, Array<MenuItemAction | SubmenuItemAction>][] = [];
|
||||
for (let group of this._menuGroups) {
|
||||
const [id, items] = group;
|
||||
const activeActions: Array<MenuItemAction | SubmenuItemAction> = [];
|
||||
for (const item of items) {
|
||||
if (this._contextKeyService.contextMatchesRules(item.when)) {
|
||||
const action = isIMenuItem(item)
|
||||
? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService)
|
||||
: new SubmenuItemAction(item, this._menuService, this._contextKeyService, options);
|
||||
|
||||
activeActions.push(action);
|
||||
}
|
||||
}
|
||||
if (activeActions.length > 0) {
|
||||
result.push([id, activeActions]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _fillInKbExprKeys(exp: ContextKeyExpression | undefined, set: Set<string>): void {
|
||||
if (exp) {
|
||||
for (let key of exp.keys()) {
|
||||
set.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static _compareMenuItems(a: IMenuItem | ISubmenuItem, b: IMenuItem | ISubmenuItem): number {
|
||||
|
||||
let aGroup = a.group;
|
||||
let bGroup = b.group;
|
||||
|
||||
if (aGroup !== bGroup) {
|
||||
|
||||
// Falsy groups come last
|
||||
if (!aGroup) {
|
||||
return 1;
|
||||
} else if (!bGroup) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 'navigation' group comes first
|
||||
if (aGroup === 'navigation') {
|
||||
return -1;
|
||||
} else if (bGroup === 'navigation') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// lexical sort for groups
|
||||
let value = aGroup.localeCompare(bGroup);
|
||||
if (value !== 0) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// sort on priority - default is 0
|
||||
let aPrio = a.order || 0;
|
||||
let bPrio = b.order || 0;
|
||||
if (aPrio < bPrio) {
|
||||
return -1;
|
||||
} else if (aPrio > bPrio) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// sort on titles
|
||||
return Menu._compareTitles(
|
||||
isIMenuItem(a) ? a.command.title : a.title,
|
||||
isIMenuItem(b) ? b.command.title : b.title
|
||||
);
|
||||
}
|
||||
|
||||
private static _compareTitles(a: string | ILocalizedString, b: string | ILocalizedString) {
|
||||
const aStr = typeof a === 'string' ? a : a.original;
|
||||
const bStr = typeof b === 'string' ? b : b.original;
|
||||
return aStr.localeCompare(bStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions';
|
||||
import { MenuService } from 'vs/platform/actions/common/menuService';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { NullCommandService } from 'vs/platform/commands/common/commands';
|
||||
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||
|
||||
// --- service instances
|
||||
|
||||
const contextKeyService = new class extends MockContextKeyService {
|
||||
contextMatchesRules() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// --- tests
|
||||
|
||||
suite('MenuService', function () {
|
||||
|
||||
let menuService: MenuService;
|
||||
const disposables = new DisposableStore();
|
||||
let testMenuId: MenuId;
|
||||
|
||||
setup(function () {
|
||||
menuService = new MenuService(NullCommandService);
|
||||
testMenuId = new MenuId('testo');
|
||||
disposables.clear();
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
disposables.clear();
|
||||
});
|
||||
|
||||
test('group sorting', function () {
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'one', title: 'FOO' },
|
||||
group: '0_hello'
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'two', title: 'FOO' },
|
||||
group: 'hello'
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'three', title: 'FOO' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'four', title: 'FOO' },
|
||||
group: ''
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'five', title: 'FOO' },
|
||||
group: 'navigation'
|
||||
}));
|
||||
|
||||
const groups = menuService.createMenu(testMenuId, 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.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'a', title: 'aaa' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'b', title: 'fff' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'c', title: 'zzz' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
const groups = menuService.createMenu(testMenuId, contextKeyService).getActions();
|
||||
|
||||
assert.equal(groups.length, 1);
|
||||
const [, actions] = groups[0];
|
||||
|
||||
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.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'a', title: 'aaa' },
|
||||
group: 'Hello',
|
||||
order: 10
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'b', title: 'fff' },
|
||||
group: 'Hello'
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'c', title: 'zzz' },
|
||||
group: 'Hello',
|
||||
order: -1
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'd', title: 'yyy' },
|
||||
group: 'Hello',
|
||||
order: -1
|
||||
}));
|
||||
|
||||
const groups = menuService.createMenu(testMenuId, contextKeyService).getActions();
|
||||
|
||||
assert.equal(groups.length, 1);
|
||||
const [, actions] = groups[0];
|
||||
|
||||
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.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'a', title: 'aaa' },
|
||||
group: 'navigation',
|
||||
order: 1.3
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'b', title: 'fff' },
|
||||
group: 'navigation',
|
||||
order: 1.2
|
||||
}));
|
||||
|
||||
disposables.add(MenuRegistry.appendMenuItem(testMenuId, {
|
||||
command: { id: 'c', title: 'zzz' },
|
||||
group: 'navigation',
|
||||
order: 1.1
|
||||
}));
|
||||
|
||||
const groups = menuService.createMenu(testMenuId, 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.add(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 (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);
|
||||
assert.equal(foundB, true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user