Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/electron-browser/parts/titlebar/menubarControl.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IAction, Separator } from '../../../../base/common/actions.js';
7
import { IMenuService, SubmenuItemAction, MenuItemAction } from '../../../../platform/actions/common/actions.js';
8
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
9
import { IWorkspacesService } from '../../../../platform/workspaces/common/workspaces.js';
10
import { isMacintosh } from '../../../../base/common/platform.js';
11
import { INotificationService } from '../../../../platform/notification/common/notification.js';
12
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
13
import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';
14
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
15
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
16
import { ILabelService } from '../../../../platform/label/common/label.js';
17
import { IUpdateService } from '../../../../platform/update/common/update.js';
18
import { IOpenRecentAction, MenubarControl } from '../../../browser/parts/titlebar/menubarControl.js';
19
import { IStorageService } from '../../../../platform/storage/common/storage.js';
20
import { IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from '../../../../platform/menubar/common/menubar.js';
21
import { IMenubarService } from '../../../../platform/menubar/electron-browser/menubar.js';
22
import { INativeHostService } from '../../../../platform/native/common/native.js';
23
import { IHostService } from '../../../services/host/browser/host.js';
24
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
25
import { ICommandService } from '../../../../platform/commands/common/commands.js';
26
import { OpenRecentAction } from '../../../browser/actions/windowActions.js';
27
import { isICommandActionToggleInfo } from '../../../../platform/action/common/action.js';
28
import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
29
30
export class NativeMenubarControl extends MenubarControl {
31
32
constructor(
33
@IMenuService menuService: IMenuService,
34
@IWorkspacesService workspacesService: IWorkspacesService,
35
@IContextKeyService contextKeyService: IContextKeyService,
36
@IKeybindingService keybindingService: IKeybindingService,
37
@IConfigurationService configurationService: IConfigurationService,
38
@ILabelService labelService: ILabelService,
39
@IUpdateService updateService: IUpdateService,
40
@IStorageService storageService: IStorageService,
41
@INotificationService notificationService: INotificationService,
42
@IPreferencesService preferencesService: IPreferencesService,
43
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
44
@IAccessibilityService accessibilityService: IAccessibilityService,
45
@IMenubarService private readonly menubarService: IMenubarService,
46
@IHostService hostService: IHostService,
47
@INativeHostService private readonly nativeHostService: INativeHostService,
48
@ICommandService commandService: ICommandService,
49
) {
50
super(menuService, workspacesService, contextKeyService, keybindingService, configurationService, labelService, updateService, storageService, notificationService, preferencesService, environmentService, accessibilityService, hostService, commandService);
51
52
(async () => {
53
this.recentlyOpened = await this.workspacesService.getRecentlyOpened();
54
55
this.doUpdateMenubar();
56
})();
57
58
this.registerListeners();
59
}
60
61
protected override setupMainMenu(): void {
62
super.setupMainMenu();
63
64
for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {
65
const menu = this.menus[topLevelMenuName];
66
if (menu) {
67
this.mainMenuDisposables.add(menu.onDidChange(() => this.updateMenubar()));
68
}
69
}
70
}
71
72
protected doUpdateMenubar(): void {
73
// Since the native menubar is shared between windows (main process)
74
// only allow the focused window to update the menubar
75
if (!this.hostService.hasFocus) {
76
return;
77
}
78
79
// Send menus to main process to be rendered by Electron
80
const menubarData = { menus: {}, keybindings: {} };
81
if (this.getMenubarMenus(menubarData)) {
82
this.menubarService.updateMenubar(this.nativeHostService.windowId, menubarData);
83
}
84
}
85
86
private getMenubarMenus(menubarData: IMenubarData): boolean {
87
if (!menubarData) {
88
return false;
89
}
90
91
menubarData.keybindings = this.getAdditionalKeybindings();
92
for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {
93
const menu = this.menus[topLevelMenuName];
94
if (menu) {
95
const menubarMenu: IMenubarMenu = { items: [] };
96
const menuActions = getFlatContextMenuActions(menu.getActions({ shouldForwardArgs: true }));
97
this.populateMenuItems(menuActions, menubarMenu, menubarData.keybindings);
98
if (menubarMenu.items.length === 0) {
99
return false; // Menus are incomplete
100
}
101
menubarData.menus[topLevelMenuName] = menubarMenu;
102
}
103
}
104
105
return true;
106
}
107
108
private populateMenuItems(menuActions: readonly IAction[], menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding | undefined }) {
109
for (const menuItem of menuActions) {
110
if (menuItem instanceof Separator) {
111
menuToPopulate.items.push({ id: 'vscode.menubar.separator' });
112
} else if (menuItem instanceof MenuItemAction || menuItem instanceof SubmenuItemAction) {
113
114
// use mnemonicTitle whenever possible
115
const title = typeof menuItem.item.title === 'string'
116
? menuItem.item.title
117
: menuItem.item.title.mnemonicTitle ?? menuItem.item.title.value;
118
119
if (menuItem instanceof SubmenuItemAction) {
120
const submenu = { items: [] };
121
122
this.populateMenuItems(menuItem.actions, submenu, keybindings);
123
124
if (submenu.items.length > 0) {
125
const menubarSubmenuItem: IMenubarMenuItemSubmenu = {
126
id: menuItem.id,
127
label: title,
128
submenu
129
};
130
131
menuToPopulate.items.push(menubarSubmenuItem);
132
}
133
} else {
134
if (menuItem.id === OpenRecentAction.ID) {
135
const actions = this.getOpenRecentActions().map(this.transformOpenRecentAction);
136
menuToPopulate.items.push(...actions);
137
}
138
139
const menubarMenuItem: IMenubarMenuItemAction = {
140
id: menuItem.id,
141
label: title
142
};
143
144
if (isICommandActionToggleInfo(menuItem.item.toggled)) {
145
menubarMenuItem.label = menuItem.item.toggled.mnemonicTitle ?? menuItem.item.toggled.title ?? title;
146
}
147
148
if (menuItem.checked) {
149
menubarMenuItem.checked = true;
150
}
151
152
if (!menuItem.enabled) {
153
menubarMenuItem.enabled = false;
154
}
155
156
keybindings[menuItem.id] = this.getMenubarKeybinding(menuItem.id);
157
menuToPopulate.items.push(menubarMenuItem);
158
}
159
}
160
}
161
}
162
163
private transformOpenRecentAction(action: Separator | IOpenRecentAction): MenubarMenuItem {
164
if (action instanceof Separator) {
165
return { id: 'vscode.menubar.separator' };
166
}
167
168
return {
169
id: action.id,
170
uri: action.uri,
171
remoteAuthority: action.remoteAuthority,
172
enabled: action.enabled,
173
label: action.label
174
};
175
}
176
177
private getAdditionalKeybindings(): { [id: string]: IMenubarKeybinding } {
178
const keybindings: { [id: string]: IMenubarKeybinding } = {};
179
if (isMacintosh) {
180
const keybinding = this.getMenubarKeybinding('workbench.action.quit');
181
if (keybinding) {
182
keybindings['workbench.action.quit'] = keybinding;
183
}
184
}
185
186
return keybindings;
187
}
188
189
private getMenubarKeybinding(id: string): IMenubarKeybinding | undefined {
190
const binding = this.keybindingService.lookupKeybinding(id);
191
if (!binding) {
192
return undefined;
193
}
194
195
// first try to resolve a native accelerator
196
const electronAccelerator = binding.getElectronAccelerator();
197
if (electronAccelerator) {
198
return { label: electronAccelerator, userSettingsLabel: binding.getUserSettingsLabel() ?? undefined };
199
}
200
201
// we need this fallback to support keybindings that cannot show in electron menus (e.g. chords)
202
const acceleratorLabel = binding.getLabel();
203
if (acceleratorLabel) {
204
return { label: acceleratorLabel, isNative: false, userSettingsLabel: binding.getUserSettingsLabel() ?? undefined };
205
}
206
207
return undefined;
208
}
209
}
210
211