Path: blob/main/src/vs/workbench/browser/parts/titlebar/menubarControl.ts
5303 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/menubarControl.css';6import { localize, localize2 } from '../../../../nls.js';7import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuItemAction, MenuRegistry } from '../../../../platform/actions/common/actions.js';8import { MenuBarVisibility, IWindowOpenable, getMenuBarVisibility, MenuSettings, hasNativeMenu } from '../../../../platform/window/common/window.js';9import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';10import { IAction, Action, SubmenuAction, Separator, IActionRunner, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, toAction } from '../../../../base/common/actions.js';11import { addDisposableListener, Dimension, EventType } from '../../../../base/browser/dom.js';12import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';13import { isMacintosh, isWeb, isIOS, isNative } from '../../../../base/common/platform.js';14import { IConfigurationService, IConfigurationChangeEvent } from '../../../../platform/configuration/common/configuration.js';15import { Event, Emitter } from '../../../../base/common/event.js';16import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';17import { IRecentlyOpened, isRecentFolder, IRecent, isRecentWorkspace, IWorkspacesService } from '../../../../platform/workspaces/common/workspaces.js';18import { RunOnceScheduler } from '../../../../base/common/async.js';19import { URI } from '../../../../base/common/uri.js';20import { ILabelService, Verbosity } from '../../../../platform/label/common/label.js';21import { IUpdateService, StateType } from '../../../../platform/update/common/update.js';22import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';23import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';24import { IPreferencesService } from '../../../services/preferences/common/preferences.js';25import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';26import { MenuBar, IMenuBarOptions } from '../../../../base/browser/ui/menu/menubar.js';27import { HorizontalDirection, IMenuDirection, VerticalDirection } from '../../../../base/browser/ui/menu/menu.js';28import { mnemonicMenuLabel, unmnemonicLabel } from '../../../../base/common/labels.js';29import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';30import { isFullscreen, onDidChangeFullscreen } from '../../../../base/browser/browser.js';31import { IHostService } from '../../../services/host/browser/host.js';32import { BrowserFeatures } from '../../../../base/browser/canIUse.js';33import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';34import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';35import { IsMacNativeContext, IsWebContext } from '../../../../platform/contextkey/common/contextkeys.js';36import { ICommandService } from '../../../../platform/commands/common/commands.js';37import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';38import { OpenRecentAction } from '../../actions/windowActions.js';39import { isICommandActionToggleInfo } from '../../../../platform/action/common/action.js';40import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';41import { defaultMenuStyles } from '../../../../platform/theme/browser/defaultStyles.js';42import { mainWindow } from '../../../../base/browser/window.js';43import { ActivityBarPosition } from '../../../services/layout/browser/layoutService.js';4445export type IOpenRecentAction = IAction & { uri: URI; remoteAuthority?: string };4647MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, {48submenu: MenuId.MenubarFileMenu,49title: {50value: 'File',51original: 'File',52mnemonicTitle: localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File"),53},54order: 155});5657MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, {58submenu: MenuId.MenubarEditMenu,59title: {60value: 'Edit',61original: 'Edit',62mnemonicTitle: localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")63},64order: 265});6667MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, {68submenu: MenuId.MenubarSelectionMenu,69title: {70value: 'Selection',71original: 'Selection',72mnemonicTitle: localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")73},74order: 375});7677MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, {78submenu: MenuId.MenubarViewMenu,79title: {80value: 'View',81original: 'View',82mnemonicTitle: localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")83},84order: 485});8687MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, {88submenu: MenuId.MenubarGoMenu,89title: {90value: 'Go',91original: 'Go',92mnemonicTitle: localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")93},94order: 595});9697MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, {98submenu: MenuId.MenubarTerminalMenu,99title: {100value: 'Terminal',101original: 'Terminal',102mnemonicTitle: localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")103},104order: 7105});106107MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, {108submenu: MenuId.MenubarHelpMenu,109title: {110value: 'Help',111original: 'Help',112mnemonicTitle: localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")113},114order: 8115});116117MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, {118submenu: MenuId.MenubarPreferencesMenu,119title: {120value: 'Preferences',121original: 'Preferences',122mnemonicTitle: localize({ key: 'mPreferences', comment: ['&& denotes a mnemonic'] }, "Preferences")123},124when: IsMacNativeContext,125order: 9126});127128export abstract class MenubarControl extends Disposable {129130protected keys = [131MenuSettings.MenuBarVisibility,132'window.enableMenuBarMnemonics',133'window.customMenuBarAltFocus',134'workbench.sideBar.location',135'window.nativeTabs'136];137138protected mainMenu: IMenu;139protected menus: {140[index: string]: IMenu | undefined;141} = {};142143protected topLevelTitles: { [menu: string]: string } = {};144145protected readonly mainMenuDisposables: DisposableStore;146147protected recentlyOpened: IRecentlyOpened = { files: [], workspaces: [] };148149protected menuUpdater: RunOnceScheduler;150151protected static readonly MAX_MENU_RECENT_ENTRIES = 10;152153constructor(154protected readonly menuService: IMenuService,155protected readonly workspacesService: IWorkspacesService,156protected readonly contextKeyService: IContextKeyService,157protected readonly keybindingService: IKeybindingService,158protected readonly configurationService: IConfigurationService,159protected readonly labelService: ILabelService,160protected readonly updateService: IUpdateService,161protected readonly storageService: IStorageService,162protected readonly notificationService: INotificationService,163protected readonly preferencesService: IPreferencesService,164protected readonly environmentService: IWorkbenchEnvironmentService,165protected readonly accessibilityService: IAccessibilityService,166protected readonly hostService: IHostService,167protected readonly commandService: ICommandService168) {169170super();171172this.mainMenu = this._register(this.menuService.createMenu(MenuId.MenubarMainMenu, this.contextKeyService));173this.mainMenuDisposables = this._register(new DisposableStore());174175this.setupMainMenu();176177this.menuUpdater = this._register(new RunOnceScheduler(() => this.doUpdateMenubar(false), 200));178179this.notifyUserOfCustomMenubarAccessibility();180}181182protected abstract doUpdateMenubar(firstTime: boolean): void;183184protected registerListeners(): void {185// Listen for window focus changes186this._register(this.hostService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e)));187188// Update when config changes189this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));190191// Listen to update service192this._register(this.updateService.onStateChange(() => this.onUpdateStateChange()));193194// Listen for changes in recently opened menu195this._register(this.workspacesService.onDidChangeRecentlyOpened(() => { this.onDidChangeRecentlyOpened(); }));196197// Listen to keybindings change198this._register(this.keybindingService.onDidUpdateKeybindings(() => this.updateMenubar()));199200// Update recent menu items on formatter registration201this._register(this.labelService.onDidChangeFormatters(() => { this.onDidChangeRecentlyOpened(); }));202203// Listen for changes on the main menu204this._register(this.mainMenu.onDidChange(() => { this.setupMainMenu(); this.doUpdateMenubar(true); }));205}206207protected setupMainMenu(): void {208this.mainMenuDisposables.clear();209this.menus = {};210this.topLevelTitles = {};211212const [, mainMenuActions] = this.mainMenu.getActions()[0];213for (const mainMenuAction of mainMenuActions) {214if (mainMenuAction instanceof SubmenuItemAction && typeof mainMenuAction.item.title !== 'string') {215this.menus[mainMenuAction.item.title.original] = this.mainMenuDisposables.add(this.menuService.createMenu(mainMenuAction.item.submenu, this.contextKeyService, { emitEventsForSubmenuChanges: true }));216this.topLevelTitles[mainMenuAction.item.title.original] = mainMenuAction.item.title.mnemonicTitle ?? mainMenuAction.item.title.value;217}218}219}220221protected updateMenubar(): void {222this.menuUpdater.schedule();223}224225protected calculateActionLabel(action: { id: string; label: string }): string {226const label = action.label;227switch (action.id) {228default:229break;230}231232return label;233}234235protected onUpdateStateChange(): void {236this.updateMenubar();237}238239protected onUpdateKeybindings(): void {240this.updateMenubar();241}242243protected getOpenRecentActions(): (Separator | IOpenRecentAction)[] {244if (!this.recentlyOpened) {245return [];246}247248const { workspaces, files } = this.recentlyOpened;249250const result = [];251252if (workspaces.length > 0) {253for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) {254result.push(this.createOpenRecentMenuAction(workspaces[i]));255}256257result.push(new Separator());258}259260if (files.length > 0) {261for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) {262result.push(this.createOpenRecentMenuAction(files[i]));263}264265result.push(new Separator());266}267268return result;269}270271protected onDidChangeWindowFocus(hasFocus: boolean): void {272// When we regain focus, update the recent menu items273if (hasFocus) {274this.onDidChangeRecentlyOpened();275}276}277278private onConfigurationUpdated(event: IConfigurationChangeEvent): void {279if (this.keys.some(key => event.affectsConfiguration(key))) {280this.updateMenubar();281}282283if (event.affectsConfiguration('editor.accessibilitySupport')) {284this.notifyUserOfCustomMenubarAccessibility();285}286287// Since we try not update when hidden, we should288// try to update the recently opened list on visibility changes289if (event.affectsConfiguration(MenuSettings.MenuBarVisibility)) {290this.onDidChangeRecentlyOpened();291}292}293294private get menubarHidden(): boolean {295return isMacintosh && isNative ? false : getMenuBarVisibility(this.configurationService) === 'hidden';296}297298protected onDidChangeRecentlyOpened(): void {299300// Do not update recently opened when the menubar is hidden #108712301if (!this.menubarHidden) {302this.workspacesService.getRecentlyOpened().then(recentlyOpened => {303this.recentlyOpened = recentlyOpened;304this.updateMenubar();305});306}307}308309private createOpenRecentMenuAction(recent: IRecent): IOpenRecentAction {310311let label: string;312let uri: URI;313let commandId: string;314let openable: IWindowOpenable;315const remoteAuthority = recent.remoteAuthority;316317if (isRecentFolder(recent)) {318uri = recent.folderUri;319label = recent.label || this.labelService.getWorkspaceLabel(uri, { verbose: Verbosity.LONG });320commandId = 'openRecentFolder';321openable = { folderUri: uri };322} else if (isRecentWorkspace(recent)) {323uri = recent.workspace.configPath;324label = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: Verbosity.LONG });325commandId = 'openRecentWorkspace';326openable = { workspaceUri: uri };327} else {328uri = recent.fileUri;329label = recent.label || this.labelService.getUriLabel(uri, { appendWorkspaceSuffix: true });330commandId = 'openRecentFile';331openable = { fileUri: uri };332}333334const ret = toAction({335id: commandId, label: unmnemonicLabel(label), run: (browserEvent: KeyboardEvent) => {336const openInNewWindow = browserEvent && ((!isMacintosh && (browserEvent.ctrlKey || browserEvent.shiftKey)) || (isMacintosh && (browserEvent.metaKey || browserEvent.altKey)));337338return this.hostService.openWindow([openable], {339forceNewWindow: !!openInNewWindow,340remoteAuthority: remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable341});342}343});344345return Object.assign(ret, { uri, remoteAuthority });346}347348private notifyUserOfCustomMenubarAccessibility(): void {349if (isWeb || isMacintosh) {350return;351}352353const hasBeenNotified = this.storageService.getBoolean('menubar/accessibleMenubarNotified', StorageScope.APPLICATION, false);354const usingCustomMenubar = !hasNativeMenu(this.configurationService);355356if (hasBeenNotified || usingCustomMenubar || !this.accessibilityService.isScreenReaderOptimized()) {357return;358}359360const message = localize('menubar.customTitlebarAccessibilityNotification', "Accessibility support is enabled for you. For the most accessible experience, we recommend the custom menu style.");361this.notificationService.prompt(Severity.Info, message, [362{363label: localize('goToSetting', "Open Settings"),364run: () => {365return this.preferencesService.openUserSettings({ query: MenuSettings.MenuStyle });366}367}368]);369370this.storageService.store('menubar/accessibleMenubarNotified', true, StorageScope.APPLICATION, StorageTarget.USER);371}372}373374// This is a bit complex due to the issue https://github.com/microsoft/vscode/issues/205836375let focusMenuBarEmitter: Emitter<void> | undefined = undefined;376function enableFocusMenuBarAction(): Emitter<void> {377if (!focusMenuBarEmitter) {378focusMenuBarEmitter = new Emitter<void>();379380registerAction2(class extends Action2 {381constructor() {382super({383id: `workbench.actions.menubar.focus`,384title: localize2('focusMenu', 'Focus Application Menu'),385keybinding: {386primary: KeyMod.Alt | KeyCode.F10,387weight: KeybindingWeight.WorkbenchContrib,388when: IsWebContext389},390f1: true391});392}393394async run(): Promise<void> {395focusMenuBarEmitter?.fire();396}397});398}399400return focusMenuBarEmitter;401}402403export class CustomMenubarControl extends MenubarControl {404private menubar: MenuBar | undefined;405private container: HTMLElement | undefined;406private alwaysOnMnemonics: boolean = false;407private focusInsideMenubar: boolean = false;408private pendingFirstTimeUpdate: boolean = false;409private visible: boolean = true;410private actionRunner: IActionRunner;411private readonly webNavigationMenu = this._register(this.menuService.createMenu(MenuId.MenubarHomeMenu, this.contextKeyService));412413private readonly _onVisibilityChange: Emitter<boolean>;414private readonly _onFocusStateChange: Emitter<boolean>;415416constructor(417@IMenuService menuService: IMenuService,418@IWorkspacesService workspacesService: IWorkspacesService,419@IContextKeyService contextKeyService: IContextKeyService,420@IKeybindingService keybindingService: IKeybindingService,421@IConfigurationService configurationService: IConfigurationService,422@ILabelService labelService: ILabelService,423@IUpdateService updateService: IUpdateService,424@IStorageService storageService: IStorageService,425@INotificationService notificationService: INotificationService,426@IPreferencesService preferencesService: IPreferencesService,427@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,428@IAccessibilityService accessibilityService: IAccessibilityService,429@ITelemetryService private readonly telemetryService: ITelemetryService,430@IHostService hostService: IHostService,431@ICommandService commandService: ICommandService432) {433super(menuService, workspacesService, contextKeyService, keybindingService, configurationService, labelService, updateService, storageService, notificationService, preferencesService, environmentService, accessibilityService, hostService, commandService);434435this._onVisibilityChange = this._register(new Emitter<boolean>());436this._onFocusStateChange = this._register(new Emitter<boolean>());437438this.actionRunner = this._register(new ActionRunner());439this._register(this.actionRunner.onDidRun(e => {440this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: 'menu' });441}));442443this.workspacesService.getRecentlyOpened().then((recentlyOpened) => {444this.recentlyOpened = recentlyOpened;445});446447this.registerListeners();448}449450protected doUpdateMenubar(firstTime: boolean): void {451if (!this.focusInsideMenubar) {452this.setupCustomMenubar(firstTime);453}454455if (firstTime) {456this.pendingFirstTimeUpdate = true;457}458}459460private getUpdateAction(): IAction | null {461const state = this.updateService.state;462463switch (state.type) {464case StateType.Idle:465return toAction({466id: 'update.check', label: localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), enabled: true, run: () =>467this.updateService.checkForUpdates(true)468});469470case StateType.CheckingForUpdates:471return toAction({ id: 'update.checking', label: localize('checkingForUpdates', "Checking for Updates..."), enabled: false, run: () => { } });472473case StateType.AvailableForDownload:474return toAction({475id: 'update.downloadNow', label: localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Update"), enabled: true, run: () =>476this.updateService.downloadUpdate(true)477});478479case StateType.Downloading:480case StateType.Overwriting:481return toAction({ id: 'update.downloading', label: localize('DownloadingUpdate', "Downloading Update..."), enabled: false, run: () => { } });482483case StateType.Downloaded:484return isMacintosh ? null : toAction({485id: 'update.install', label: localize({ key: 'installUpdate...', comment: ['&& denotes a mnemonic'] }, "Install &&Update..."), enabled: true, run: () =>486this.updateService.applyUpdate()487});488489case StateType.Updating:490return toAction({ id: 'update.updating', label: localize('installingUpdate', "Installing Update..."), enabled: false, run: () => { } });491492case StateType.Ready:493return toAction({494id: 'update.restart', label: localize({ key: 'restartToUpdate', comment: ['&& denotes a mnemonic'] }, "Restart to &&Update"), enabled: true, run: () =>495this.updateService.quitAndInstall()496});497498default:499return null;500}501}502503private get currentMenubarVisibility(): MenuBarVisibility {504return getMenuBarVisibility(this.configurationService);505}506507private get currentDisableMenuBarAltFocus(): boolean {508const settingValue = this.configurationService.getValue<boolean>('window.customMenuBarAltFocus');509510let disableMenuBarAltBehavior = false;511if (typeof settingValue === 'boolean') {512disableMenuBarAltBehavior = !settingValue;513}514515return disableMenuBarAltBehavior;516}517518private insertActionsBefore(nextAction: IAction, target: IAction[]): void {519switch (nextAction.id) {520case OpenRecentAction.ID:521target.push(...this.getOpenRecentActions());522break;523524case 'workbench.action.showAboutDialog':525if (!isMacintosh && !isWeb) {526const updateAction = this.getUpdateAction();527if (updateAction) {528updateAction.label = mnemonicMenuLabel(updateAction.label);529target.push(updateAction);530target.push(new Separator());531}532}533534break;535536default:537break;538}539}540541private get currentEnableMenuBarMnemonics(): boolean {542let enableMenuBarMnemonics = this.configurationService.getValue<boolean>('window.enableMenuBarMnemonics');543if (typeof enableMenuBarMnemonics !== 'boolean') {544enableMenuBarMnemonics = true;545}546547return enableMenuBarMnemonics && (!isWeb || isFullscreen(mainWindow));548}549550private get currentCompactMenuMode(): IMenuDirection | undefined {551if (this.currentMenubarVisibility !== 'compact') {552return undefined;553}554555// Menu bar lives in activity bar and should flow based on its location556const currentSidebarLocation = this.configurationService.getValue<string>('workbench.sideBar.location');557const horizontalDirection = currentSidebarLocation === 'right' ? HorizontalDirection.Left : HorizontalDirection.Right;558559const activityBarLocation = this.configurationService.getValue<string>('workbench.activityBar.location');560const verticalDirection = activityBarLocation === ActivityBarPosition.BOTTOM ? VerticalDirection.Above : VerticalDirection.Below;561562return { horizontal: horizontalDirection, vertical: verticalDirection };563}564565private onDidVisibilityChange(visible: boolean): void {566this.visible = visible;567this.onDidChangeRecentlyOpened();568this._onVisibilityChange.fire(visible);569}570571private toActionsArray(menu: IMenu): IAction[] {572return getFlatContextMenuActions(menu.getActions({ shouldForwardArgs: true }));573}574575private readonly reinstallDisposables = this._register(new DisposableStore());576private readonly updateActionsDisposables = this._register(new DisposableStore());577private setupCustomMenubar(firstTime: boolean): void {578// If there is no container, we cannot setup the menubar579if (!this.container) {580return;581}582583if (firstTime) {584// Reset and create new menubar585if (this.menubar) {586this.reinstallDisposables.clear();587}588589this.menubar = this.reinstallDisposables.add(new MenuBar(this.container, this.getMenuBarOptions(), defaultMenuStyles));590591this.accessibilityService.alwaysUnderlineAccessKeys().then(val => {592this.alwaysOnMnemonics = val;593this.menubar?.update(this.getMenuBarOptions());594});595596this.reinstallDisposables.add(this.menubar.onFocusStateChange(focused => {597this._onFocusStateChange.fire(focused);598599// When the menubar loses focus, update it to clear any pending updates600if (!focused) {601if (this.pendingFirstTimeUpdate) {602this.setupCustomMenubar(true);603this.pendingFirstTimeUpdate = false;604} else {605this.updateMenubar();606}607608this.focusInsideMenubar = false;609}610}));611612this.reinstallDisposables.add(this.menubar.onVisibilityChange(e => this.onDidVisibilityChange(e)));613614// Before we focus the menubar, stop updates to it so that focus-related context keys will work615this.reinstallDisposables.add(addDisposableListener(this.container, EventType.FOCUS_IN, () => {616this.focusInsideMenubar = true;617}));618619this.reinstallDisposables.add(addDisposableListener(this.container, EventType.FOCUS_OUT, () => {620this.focusInsideMenubar = false;621}));622623// Fire visibility change for the first install if menu is shown624if (this.menubar.isVisible) {625this.onDidVisibilityChange(true);626}627} else {628this.menubar?.update(this.getMenuBarOptions());629}630631// Update the menu actions632const updateActions = (menuActions: readonly IAction[], target: IAction[], topLevelTitle: string, store: DisposableStore) => {633target.splice(0);634635for (const menuItem of menuActions) {636this.insertActionsBefore(menuItem, target);637638if (menuItem instanceof Separator) {639target.push(menuItem);640} else if (menuItem instanceof SubmenuItemAction || menuItem instanceof MenuItemAction) {641// use mnemonicTitle whenever possible642let title = typeof menuItem.item.title === 'string'643? menuItem.item.title644: menuItem.item.title.mnemonicTitle ?? menuItem.item.title.value;645646if (menuItem instanceof SubmenuItemAction) {647const submenuActions: SubmenuAction[] = [];648updateActions(menuItem.actions, submenuActions, topLevelTitle, store);649650if (submenuActions.length > 0) {651target.push(new SubmenuAction(menuItem.id, mnemonicMenuLabel(title), submenuActions));652}653} else {654if (isICommandActionToggleInfo(menuItem.item.toggled)) {655title = menuItem.item.toggled.mnemonicTitle ?? menuItem.item.toggled.title ?? title;656}657658const newAction = store.add(new Action(menuItem.id, mnemonicMenuLabel(title), menuItem.class, menuItem.enabled, () => this.commandService.executeCommand(menuItem.id)));659newAction.tooltip = menuItem.tooltip;660newAction.checked = menuItem.checked;661target.push(newAction);662}663}664665}666667// Append web navigation menu items to the file menu when not compact668if (topLevelTitle === 'File' && this.currentCompactMenuMode === undefined) {669const webActions = this.getWebNavigationActions();670if (webActions.length) {671target.push(...webActions);672}673}674};675676for (const title of Object.keys(this.topLevelTitles)) {677const menu = this.menus[title];678if (firstTime && menu) {679const menuChangedDisposable = this.reinstallDisposables.add(new DisposableStore());680this.reinstallDisposables.add(menu.onDidChange(() => {681if (!this.focusInsideMenubar) {682const actions: IAction[] = [];683menuChangedDisposable.clear();684updateActions(this.toActionsArray(menu), actions, title, menuChangedDisposable);685this.menubar?.updateMenu({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });686}687}));688689// For the file menu, we need to update if the web nav menu updates as well690if (menu === this.menus.File) {691const webMenuChangedDisposable = this.reinstallDisposables.add(new DisposableStore());692this.reinstallDisposables.add(this.webNavigationMenu.onDidChange(() => {693if (!this.focusInsideMenubar) {694const actions: IAction[] = [];695webMenuChangedDisposable.clear();696updateActions(this.toActionsArray(menu), actions, title, webMenuChangedDisposable);697this.menubar?.updateMenu({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });698}699}));700}701}702703const actions: IAction[] = [];704if (menu) {705this.updateActionsDisposables.clear();706updateActions(this.toActionsArray(menu), actions, title, this.updateActionsDisposables);707}708709if (this.menubar) {710if (!firstTime) {711this.menubar.updateMenu({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });712} else {713this.menubar.push({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });714}715}716}717}718719private getWebNavigationActions(): IAction[] {720if (!isWeb) {721return []; // only for web722}723724const webNavigationActions = [];725for (const groups of this.webNavigationMenu.getActions()) {726const [, actions] = groups;727for (const action of actions) {728if (action instanceof MenuItemAction) {729const title = typeof action.item.title === 'string'730? action.item.title731: action.item.title.mnemonicTitle ?? action.item.title.value;732webNavigationActions.push(toAction({733id: action.id, label: mnemonicMenuLabel(title), class: action.class, enabled: action.enabled, run: async (event?: unknown) => {734this.commandService.executeCommand(action.id, event);735}736}));737}738}739740webNavigationActions.push(new Separator());741}742743if (webNavigationActions.length) {744webNavigationActions.pop();745}746747return webNavigationActions;748}749750private getMenuBarOptions(): IMenuBarOptions {751return {752enableMnemonics: this.currentEnableMenuBarMnemonics,753disableAltFocus: this.currentDisableMenuBarAltFocus,754visibility: this.currentMenubarVisibility,755actionRunner: this.actionRunner,756getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id),757alwaysOnMnemonics: this.alwaysOnMnemonics,758compactMode: this.currentCompactMenuMode,759getCompactMenuActions: () => {760if (!isWeb) {761return []; // only for web762}763764return this.getWebNavigationActions();765}766};767}768769protected override onDidChangeWindowFocus(hasFocus: boolean): void {770if (!this.visible) {771return;772}773774super.onDidChangeWindowFocus(hasFocus);775776if (this.container) {777if (hasFocus) {778this.container.classList.remove('inactive');779} else {780this.container.classList.add('inactive');781this.menubar?.blur();782}783}784}785786protected override onUpdateStateChange(): void {787if (!this.visible) {788return;789}790791super.onUpdateStateChange();792}793794protected override onDidChangeRecentlyOpened(): void {795if (!this.visible) {796return;797}798799super.onDidChangeRecentlyOpened();800}801802protected override onUpdateKeybindings(): void {803if (!this.visible) {804return;805}806807super.onUpdateKeybindings();808}809810protected override registerListeners(): void {811super.registerListeners();812813this._register(addDisposableListener(mainWindow, EventType.RESIZE, () => {814if (this.menubar && !(isIOS && BrowserFeatures.pointerEvents)) {815this.menubar.blur();816}817}));818819// Mnemonics require fullscreen in web820if (isWeb) {821this._register(onDidChangeFullscreen(windowId => {822if (windowId === mainWindow.vscodeWindowId) {823this.updateMenubar();824}825}));826this._register(this.webNavigationMenu.onDidChange(() => this.updateMenubar()));827this._register(enableFocusMenuBarAction().event(() => this.menubar?.toggleFocus()));828}829}830831get onVisibilityChange(): Event<boolean> {832return this._onVisibilityChange.event;833}834835get onFocusStateChange(): Event<boolean> {836return this._onFocusStateChange.event;837}838839getMenubarItemsDimensions(): Dimension {840if (this.menubar) {841return new Dimension(this.menubar.getWidth(), this.menubar.getHeight());842}843844return new Dimension(0, 0);845}846847create(parent: HTMLElement): HTMLElement {848this.container = parent;849850// Build the menubar851if (this.container) {852this.doUpdateMenubar(true);853}854855return this.container;856}857858layout(dimension: Dimension) {859this.menubar?.update(this.getMenuBarOptions());860}861862toggleFocus() {863this.menubar?.toggleFocus();864}865}866867868