-
Notifications
You must be signed in to change notification settings - Fork 27.9k
/
menu.ts
144 lines (120 loc) · 4.33 KB
/
menu.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
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 | ISubmenuItem)[]];
export class Menu implements IMenu {
private _menuGroups: MenuItemGroup[] = [];
private _disposables: IDisposable[] = [];
private _onDidChange = new Emitter<IMenu>();
constructor(
id: MenuId,
startupSignal: Thenable<boolean>,
@ICommandService private readonly _commandService: ICommandService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService
) {
startupSignal.then(_ => {
const menuItems = MenuRegistry.getMenuItems(id);
const keysFilter = new Set<string>();
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, keysFilter);
// keep precondition keys for event if applicable
if (isIMenuItem(item) && item.command.precondition) {
Menu._fillInKbExprKeys(item.command.precondition, keysFilter);
}
// keep toggled keys for event if applicable
if (isIMenuItem(item) && item.command.toggled) {
Menu._fillInKbExprKeys(item.command.toggled, keysFilter);
}
}
// subscribe to context changes
this._disposables.push(this._contextKeyService.onDidChangeContext(event => {
if (event.affectsSome(keysFilter)) {
this._onDidChange.fire();
}
}));
this._onDidChange.fire(this);
});
}
dispose() {
this._disposables = dispose(this._disposables);
this._onDidChange.dispose();
}
get onDidChange(): Event<IMenu> {
return this._onDidChange.event;
}
getActions(options: IMenuActionOptions): [string, (MenuItemAction | SubmenuItemAction)[]][] {
const result: [string, (MenuItemAction | SubmenuItemAction)[]][] = [];
for (let group of this._menuGroups) {
const [id, items] = group;
const activeActions: (MenuItemAction | SubmenuItemAction)[] = [];
for (const item of items) {
if (this._contextKeyService.contextMatchesRules(item.when || null)) {
const action = isIMenuItem(item) ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) : new SubmenuItemAction(item);
activeActions.push(action);
}
}
if (activeActions.length > 0) {
result.push([id, activeActions]);
}
}
return result;
}
private static _fillInKbExprKeys(exp: ContextKeyExpr | undefined, set: Set<string>): void {
if (exp) {
for (let key of exp.keys()) {
set.add(key);
}
}
}
private static _compareMenuItems(a: IMenuItem, b: IMenuItem): 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
const aTitle = typeof a.command.title === 'string' ? a.command.title : a.command.title.value;
const bTitle = typeof b.command.title === 'string' ? b.command.title : b.command.title.value;
return aTitle.localeCompare(bTitle);
}
}