UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

913 lines (912 loc) 33.5 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); }; }; import { app, BrowserWindow, Menu, MenuItem } from 'electron'; import { RunOnceScheduler } from '@sussudio/base/common/async.mjs'; import { CancellationToken } from '@sussudio/base/common/cancellation.mjs'; import { mnemonicButtonLabel, mnemonicMenuLabel } from '@sussudio/base/common/labels.mjs'; import { isMacintosh, language } from '@sussudio/base/common/platform.mjs'; import { URI } from '@sussudio/base/common/uri.mjs'; import * as nls from 'vscode-nls.mjs'; import { IConfigurationService } from '../../configuration/common/configuration.mjs'; import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.mjs'; import { ILifecycleMainService } from '../../lifecycle/electron-main/lifecycleMainService.mjs'; import { ILogService } from '../../log/common/log.mjs'; import { isMenubarMenuItemAction, isMenubarMenuItemRecentAction, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, } from '../common/menubar.mjs'; import { INativeHostMainService } from '../../native/electron-main/nativeHostMainService.mjs'; import { IProductService } from '../../product/common/productService.mjs'; import { IStateMainService } from '../../state/electron-main/state.mjs'; import { ITelemetryService } from '../../telemetry/common/telemetry.mjs'; import { IUpdateService } from '../../update/common/update.mjs'; import { getTitleBarStyle } from '../../window/common/window.mjs'; import { IWindowsMainService } from '../../windows/electron-main/windows.mjs'; import { IWorkspacesHistoryMainService } from '../../workspaces/electron-main/workspacesHistoryMainService.mjs'; const telemetryFrom = 'menu'; let Menubar = class Menubar { updateService; configurationService; windowsMainService; environmentMainService; telemetryService; workspacesHistoryMainService; stateMainService; lifecycleMainService; logService; nativeHostMainService; productService; static lastKnownMenubarStorageKey = 'lastKnownMenubarData'; willShutdown; appMenuInstalled; closedLastWindow; noActiveWindow; menuUpdater; menuGC; // Array to keep menus around so that GC doesn't cause crash as explained in #55347 // TODO@sbatten Remove this when fixed upstream by Electron oldMenus; menubarMenus; keybindings; fallbackMenuHandlers = Object.create(null); constructor( updateService, configurationService, windowsMainService, environmentMainService, telemetryService, workspacesHistoryMainService, stateMainService, lifecycleMainService, logService, nativeHostMainService, productService, ) { this.updateService = updateService; this.configurationService = configurationService; this.windowsMainService = windowsMainService; this.environmentMainService = environmentMainService; this.telemetryService = telemetryService; this.workspacesHistoryMainService = workspacesHistoryMainService; this.stateMainService = stateMainService; this.lifecycleMainService = lifecycleMainService; this.logService = logService; this.nativeHostMainService = nativeHostMainService; this.productService = productService; this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); this.menuGC = new RunOnceScheduler(() => { this.oldMenus = []; }, 10000); this.menubarMenus = Object.create(null); this.keybindings = Object.create(null); if (isMacintosh || getTitleBarStyle(this.configurationService) === 'native') { this.restoreCachedMenubarData(); } this.addFallbackHandlers(); this.closedLastWindow = false; this.noActiveWindow = false; this.oldMenus = []; this.install(); this.registerListeners(); } restoreCachedMenubarData() { const menubarData = this.stateMainService.getItem(Menubar.lastKnownMenubarStorageKey); if (menubarData) { if (menubarData.menus) { this.menubarMenus = menubarData.menus; } if (menubarData.keybindings) { this.keybindings = menubarData.keybindings; } } } addFallbackHandlers() { // File Menu Items this.fallbackMenuHandlers['workbench.action.files.newUntitledFile'] = (menuItem, win, event) => this.windowsMainService.openEmptyWindow({ context: 2 /* OpenContext.MENU */, contextWindowId: win?.id }); this.fallbackMenuHandlers['workbench.action.newWindow'] = (menuItem, win, event) => this.windowsMainService.openEmptyWindow({ context: 2 /* OpenContext.MENU */, contextWindowId: win?.id }); this.fallbackMenuHandlers['workbench.action.files.openFileFolder'] = (menuItem, win, event) => this.nativeHostMainService.pickFileFolderAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom }, }); this.fallbackMenuHandlers['workbench.action.files.openFolder'] = (menuItem, win, event) => this.nativeHostMainService.pickFolderAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom }, }); this.fallbackMenuHandlers['workbench.action.openWorkspace'] = (menuItem, win, event) => this.nativeHostMainService.pickWorkspaceAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom }, }); // Recent Menu Items this.fallbackMenuHandlers['workbench.action.clearRecentFiles'] = () => this.workspacesHistoryMainService.clearRecentlyOpened(); // Help Menu Items const twitterUrl = this.productService.twitterUrl; if (twitterUrl) { this.fallbackMenuHandlers['workbench.action.openTwitterUrl'] = () => this.openUrl(twitterUrl, 'openTwitterUrl'); } const requestFeatureUrl = this.productService.requestFeatureUrl; if (requestFeatureUrl) { this.fallbackMenuHandlers['workbench.action.openRequestFeatureUrl'] = () => this.openUrl(requestFeatureUrl, 'openUserVoiceUrl'); } const reportIssueUrl = this.productService.reportIssueUrl; if (reportIssueUrl) { this.fallbackMenuHandlers['workbench.action.openIssueReporter'] = () => this.openUrl(reportIssueUrl, 'openReportIssues'); } const licenseUrl = this.productService.licenseUrl; if (licenseUrl) { this.fallbackMenuHandlers['workbench.action.openLicenseUrl'] = () => { if (language) { const queryArgChar = licenseUrl.indexOf('?') > 0 ? '&' : '?'; this.openUrl(`${licenseUrl}${queryArgChar}lang=${language}`, 'openLicenseUrl'); } else { this.openUrl(licenseUrl, 'openLicenseUrl'); } }; } const privacyStatementUrl = this.productService.privacyStatementUrl; if (privacyStatementUrl && licenseUrl) { this.fallbackMenuHandlers['workbench.action.openPrivacyStatementUrl'] = () => { this.openUrl(privacyStatementUrl, 'openPrivacyStatement'); }; } } registerListeners() { // Keep flag when app quits this.lifecycleMainService.onWillShutdown(() => (this.willShutdown = true)); // Listen to some events from window service to update menu this.windowsMainService.onDidChangeWindowsCount((e) => this.onDidChangeWindowsCount(e)); this.nativeHostMainService.onDidBlurWindow(() => this.onDidChangeWindowFocus()); this.nativeHostMainService.onDidFocusWindow(() => this.onDidChangeWindowFocus()); } get currentEnableMenuBarMnemonics() { const enableMenuBarMnemonics = this.configurationService.getValue('window.enableMenuBarMnemonics'); if (typeof enableMenuBarMnemonics !== 'boolean') { return true; } return enableMenuBarMnemonics; } get currentEnableNativeTabs() { if (!isMacintosh) { return false; } const enableNativeTabs = this.configurationService.getValue('window.nativeTabs'); if (typeof enableNativeTabs !== 'boolean') { return false; } return enableNativeTabs; } updateMenu(menubarData, windowId) { this.menubarMenus = menubarData.menus; this.keybindings = menubarData.keybindings; // Save off new menu and keybindings this.stateMainService.setItem(Menubar.lastKnownMenubarStorageKey, menubarData); this.scheduleUpdateMenu(); } scheduleUpdateMenu() { this.menuUpdater.schedule(); // buffer multiple attempts to update the menu } doUpdateMenu() { // Due to limitations in Electron, it is not possible to update menu items dynamically. The suggested // workaround from Electron is to set the application menu again. // See also https://github.com/electron/electron/issues/846 // // Run delayed to prevent updating menu while it is open if (!this.willShutdown) { setTimeout(() => { if (!this.willShutdown) { this.install(); } }, 10 /* delay this because there is an issue with updating a menu when it is open */); } } onDidChangeWindowsCount(e) { if (!isMacintosh) { return; } // Update menu if window count goes from N > 0 or 0 > N to update menu item enablement if ((e.oldCount === 0 && e.newCount > 0) || (e.oldCount > 0 && e.newCount === 0)) { this.closedLastWindow = e.newCount === 0; this.scheduleUpdateMenu(); } } onDidChangeWindowFocus() { if (!isMacintosh) { return; } this.noActiveWindow = !BrowserWindow.getFocusedWindow(); this.scheduleUpdateMenu(); } install() { // Store old menu in our array to avoid GC to collect the menu and crash. See #55347 // TODO@sbatten Remove this when fixed upstream by Electron const oldMenu = Menu.getApplicationMenu(); if (oldMenu) { this.oldMenus.push(oldMenu); } // If we don't have a menu yet, set it to null to avoid the electron menu. // This should only happen on the first launch ever if (Object.keys(this.menubarMenus).length === 0) { Menu.setApplicationMenu(isMacintosh ? new Menu() : null); return; } // Menus const menubar = new Menu(); // Mac: Application let macApplicationMenuItem; if (isMacintosh) { const applicationMenu = new Menu(); macApplicationMenuItem = new MenuItem({ label: this.productService.nameShort, submenu: applicationMenu }); this.setMacApplicationMenu(applicationMenu); menubar.append(macApplicationMenuItem); } // Mac: Dock if (isMacintosh && !this.appMenuInstalled) { this.appMenuInstalled = true; const dockMenu = new Menu(); dockMenu.append( new MenuItem({ label: this.mnemonicLabel( nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, 'New &&Window'), ), click: () => this.windowsMainService.openEmptyWindow({ context: 1 /* OpenContext.DOCK */ }), }), ); app.dock.setMenu(dockMenu); } // File if (this.shouldDrawMenu('File')) { const fileMenu = new Menu(); const fileMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, '&&File')), submenu: fileMenu, }); this.setMenuById(fileMenu, 'File'); menubar.append(fileMenuItem); } // Edit if (this.shouldDrawMenu('Edit')) { const editMenu = new Menu(); const editMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, '&&Edit')), submenu: editMenu, }); this.setMenuById(editMenu, 'Edit'); menubar.append(editMenuItem); } // Selection if (this.shouldDrawMenu('Selection')) { const selectionMenu = new Menu(); const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel( nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, '&&Selection'), ), submenu: selectionMenu, }); this.setMenuById(selectionMenu, 'Selection'); menubar.append(selectionMenuItem); } // View if (this.shouldDrawMenu('View')) { const viewMenu = new Menu(); const viewMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, '&&View')), submenu: viewMenu, }); this.setMenuById(viewMenu, 'View'); menubar.append(viewMenuItem); } // Go if (this.shouldDrawMenu('Go')) { const gotoMenu = new Menu(); const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, '&&Go')), submenu: gotoMenu, }); this.setMenuById(gotoMenu, 'Go'); menubar.append(gotoMenuItem); } // Debug if (this.shouldDrawMenu('Run')) { const debugMenu = new Menu(); const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, '&&Run')), submenu: debugMenu, }); this.setMenuById(debugMenu, 'Run'); menubar.append(debugMenuItem); } // Terminal if (this.shouldDrawMenu('Terminal')) { const terminalMenu = new Menu(); const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, '&&Terminal')), submenu: terminalMenu, }); this.setMenuById(terminalMenu, 'Terminal'); menubar.append(terminalMenuItem); } // Mac: Window let macWindowMenuItem; if (this.shouldDrawMenu('Window')) { const windowMenu = new Menu(); macWindowMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize('mWindow', 'Window')), submenu: windowMenu, role: 'window', }); this.setMacWindowMenu(windowMenu); } if (macWindowMenuItem) { menubar.append(macWindowMenuItem); } // Help if (this.shouldDrawMenu('Help')) { const helpMenu = new Menu(); const helpMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, '&&Help')), submenu: helpMenu, role: 'help', }); this.setMenuById(helpMenu, 'Help'); menubar.append(helpMenuItem); } if (menubar.items && menubar.items.length > 0) { Menu.setApplicationMenu(menubar); } else { Menu.setApplicationMenu(null); } // Dispose of older menus after some time this.menuGC.schedule(); } setMacApplicationMenu(macApplicationMenu) { const about = this.createMenuItem( nls.localize('mAbout', 'About {0}', this.productService.nameLong), 'workbench.action.showAboutDialog', ); const checkForUpdates = this.getUpdateMenuItems(); let preferences; if (this.shouldDrawMenu('Preferences')) { const preferencesMenu = new Menu(); this.setMenuById(preferencesMenu, 'Preferences'); preferences = new MenuItem({ label: this.mnemonicLabel( nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, '&&Preferences'), ), submenu: preferencesMenu, }); } const servicesMenu = new Menu(); const services = new MenuItem({ label: nls.localize('mServices', 'Services'), role: 'services', submenu: servicesMenu, }); const hide = new MenuItem({ label: nls.localize('mHide', 'Hide {0}', this.productService.nameLong), role: 'hide', accelerator: 'Command+H', }); const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', 'Hide Others'), role: 'hideOthers', accelerator: 'Command+Alt+H', }); const showAll = new MenuItem({ label: nls.localize('mShowAll', 'Show All'), role: 'unhide' }); const quit = new MenuItem( this.likeAction('workbench.action.quit', { label: nls.localize('miQuit', 'Quit {0}', this.productService.nameLong), click: async (item, window, event) => { const lastActiveWindow = this.windowsMainService.getLastActiveWindow(); if ( this.windowsMainService.getWindowCount() === 0 || // allow to quit when no more windows are open !!BrowserWindow.getFocusedWindow() || // allow to quit when window has focus (fix for https://github.com/microsoft/vscode/issues/39191) lastActiveWindow?.isMinimized() // allow to quit when window has no focus but is minimized (https://github.com/microsoft/vscode/issues/63000) ) { const confirmed = await this.confirmBeforeQuit(event); if (confirmed) { this.nativeHostMainService.quit(undefined); } } }, }), ); const actions = [about]; actions.push(...checkForUpdates); if (preferences) { actions.push(...[__separator__(), preferences]); } actions.push(...[__separator__(), services, __separator__(), hide, hideOthers, showAll, __separator__(), quit]); actions.forEach((i) => macApplicationMenu.append(i)); } async confirmBeforeQuit(event) { if (this.windowsMainService.getWindowCount() === 0) { return true; // never confirm when no windows are opened } const confirmBeforeClose = this.configurationService.getValue('window.confirmBeforeClose'); if (confirmBeforeClose === 'always' || (confirmBeforeClose === 'keyboardOnly' && this.isKeyboardEvent(event))) { const options = { title: this.productService.nameLong, type: 'question', buttons: [ mnemonicButtonLabel(nls.localize({ key: 'quit', comment: ['&& denotes a mnemonic'] }, '&&Quit')), mnemonicButtonLabel(nls.localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, '&&Cancel')), ], message: nls.localize('quitMessage', 'Are you sure you want to quit?'), noLink: true, defaultId: 0, cancelId: 1, }; const { response } = await this.nativeHostMainService.showMessageBox( this.windowsMainService.getFocusedWindow()?.id, options, ); return response === 0; } return true; } shouldDrawMenu(menuId) { // We need to draw an empty menu to override the electron default if (!isMacintosh && getTitleBarStyle(this.configurationService) === 'custom') { return false; } switch (menuId) { case 'File': case 'Help': if (isMacintosh) { return ( (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (this.windowsMainService.getWindowCount() > 0 && this.noActiveWindow) || (!!this.menubarMenus && !!this.menubarMenus[menuId]) ); } case 'Window': if (isMacintosh) { return ( (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (this.windowsMainService.getWindowCount() > 0 && this.noActiveWindow) || !!this.menubarMenus ); } default: return this.windowsMainService.getWindowCount() > 0 && !!this.menubarMenus && !!this.menubarMenus[menuId]; } } setMenu(menu, items) { items.forEach((item) => { if (isMenubarMenuItemSeparator(item)) { menu.append(__separator__()); } else if (isMenubarMenuItemSubmenu(item)) { const submenu = new Menu(); const submenuItem = new MenuItem({ label: this.mnemonicLabel(item.label), submenu }); this.setMenu(submenu, item.submenu.items); menu.append(submenuItem); } else if (isMenubarMenuItemRecentAction(item)) { menu.append(this.createOpenRecentMenuItem(item)); } else if (isMenubarMenuItemAction(item)) { if (item.id === 'workbench.action.showAboutDialog') { this.insertCheckForUpdatesItems(menu); } if (isMacintosh) { if ( (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (this.windowsMainService.getWindowCount() > 0 && this.noActiveWindow) ) { // In the fallback scenario, we are either disabled or using a fallback handler if (this.fallbackMenuHandlers[item.id]) { menu.append( new MenuItem( this.likeAction(item.id, { label: this.mnemonicLabel(item.label), click: this.fallbackMenuHandlers[item.id], }), ), ); } else { menu.append(this.createMenuItem(item.label, item.id, false, item.checked)); } } else { menu.append( this.createMenuItem(item.label, item.id, item.enabled === false ? false : true, !!item.checked), ); } } else { menu.append(this.createMenuItem(item.label, item.id, item.enabled === false ? false : true, !!item.checked)); } } }); } setMenuById(menu, menuId) { if (this.menubarMenus && this.menubarMenus[menuId]) { this.setMenu(menu, this.menubarMenus[menuId].items); } } insertCheckForUpdatesItems(menu) { const updateItems = this.getUpdateMenuItems(); if (updateItems.length) { updateItems.forEach((i) => menu.append(i)); menu.append(__separator__()); } } createOpenRecentMenuItem(item) { const revivedUri = URI.revive(item.uri); const commandId = item.id; const openable = commandId === 'openRecentFile' ? { fileUri: revivedUri } : commandId === 'openRecentWorkspace' ? { workspaceUri: revivedUri } : { folderUri: revivedUri }; return new MenuItem( this.likeAction( commandId, { label: item.label, click: async (menuItem, win, event) => { const openInNewWindow = this.isOptionClick(event); const success = ( await this.windowsMainService.open({ context: 2 /* OpenContext.MENU */, cli: this.environmentMainService.args, urisToOpen: [openable], forceNewWindow: openInNewWindow, gotoLineMode: false, remoteAuthority: item.remoteAuthority, }) ).length > 0; if (!success) { await this.workspacesHistoryMainService.removeRecentlyOpened([revivedUri]); } }, }, false, ), ); } isOptionClick(event) { return !!( event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey))) ); } isKeyboardEvent(event) { return !!(event.triggeredByAccelerator || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey); } createRoleMenuItem(label, commandId, role) { const options = { label: this.mnemonicLabel(label), role, enabled: true, }; return new MenuItem(this.withKeybinding(commandId, options)); } setMacWindowMenu(macWindowMenu) { const minimize = new MenuItem({ label: nls.localize('mMinimize', 'Minimize'), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsMainService.getWindowCount() > 0, }); const zoom = new MenuItem({ label: nls.localize('mZoom', 'Zoom'), role: 'zoom', enabled: this.windowsMainService.getWindowCount() > 0, }); const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', 'Bring All to Front'), role: 'front', enabled: this.windowsMainService.getWindowCount() > 0, }); const switchWindow = this.createMenuItem( nls.localize({ key: 'miSwitchWindow', comment: ['&& denotes a mnemonic'] }, 'Switch &&Window...'), 'workbench.action.switchWindow', ); const nativeTabMenuItems = []; if (this.currentEnableNativeTabs) { nativeTabMenuItems.push(__separator__()); nativeTabMenuItems.push(this.createMenuItem(nls.localize('mNewTab', 'New Tab'), 'workbench.action.newWindowTab')); nativeTabMenuItems.push( this.createRoleMenuItem( nls.localize('mShowPreviousTab', 'Show Previous Tab'), 'workbench.action.showPreviousWindowTab', 'selectPreviousTab', ), ); nativeTabMenuItems.push( this.createRoleMenuItem( nls.localize('mShowNextTab', 'Show Next Tab'), 'workbench.action.showNextWindowTab', 'selectNextTab', ), ); nativeTabMenuItems.push( this.createRoleMenuItem( nls.localize('mMoveTabToNewWindow', 'Move Tab to New Window'), 'workbench.action.moveWindowTabToNewWindow', 'moveTabToNewWindow', ), ); nativeTabMenuItems.push( this.createRoleMenuItem( nls.localize('mMergeAllWindows', 'Merge All Windows'), 'workbench.action.mergeAllWindowTabs', 'mergeAllWindows', ), ); } [minimize, zoom, __separator__(), switchWindow, ...nativeTabMenuItems, __separator__(), bringAllToFront].forEach( (item) => macWindowMenu.append(item), ); } getUpdateMenuItems() { const state = this.updateService.state; switch (state.type) { case 'uninitialized' /* StateType.Uninitialized */: return []; case 'idle' /* StateType.Idle */: return [ new MenuItem({ label: this.mnemonicLabel(nls.localize('miCheckForUpdates', 'Check for &&Updates...')), click: () => setTimeout(() => { this.reportMenuActionTelemetry('CheckForUpdate'); this.updateService.checkForUpdates(true); }, 0), }), ]; case 'checking for updates' /* StateType.CheckingForUpdates */: return [ new MenuItem({ label: nls.localize('miCheckingForUpdates', 'Checking for Updates...'), enabled: false }), ]; case 'available for download' /* StateType.AvailableForDownload */: return [ new MenuItem({ label: this.mnemonicLabel(nls.localize('miDownloadUpdate', 'D&&ownload Available Update')), click: () => { this.updateService.downloadUpdate(); }, }), ]; case 'downloading' /* StateType.Downloading */: return [new MenuItem({ label: nls.localize('miDownloadingUpdate', 'Downloading Update...'), enabled: false })]; case 'downloaded' /* StateType.Downloaded */: return [ new MenuItem({ label: this.mnemonicLabel(nls.localize('miInstallUpdate', 'Install &&Update...')), click: () => { this.reportMenuActionTelemetry('InstallUpdate'); this.updateService.applyUpdate(); }, }), ]; case 'updating' /* StateType.Updating */: return [new MenuItem({ label: nls.localize('miInstallingUpdate', 'Installing Update...'), enabled: false })]; case 'ready' /* StateType.Ready */: return [ new MenuItem({ label: this.mnemonicLabel(nls.localize('miRestartToUpdate', 'Restart to &&Update')), click: () => { this.reportMenuActionTelemetry('RestartToUpdate'); this.updateService.quitAndInstall(); }, }), ]; } } createMenuItem(arg1, arg2, arg3, arg4) { const label = this.mnemonicLabel(arg1); const click = typeof arg2 === 'function' ? arg2 : (menuItem, win, event) => { const userSettingsLabel = menuItem ? menuItem.userSettingsLabel : null; let commandId = arg2; if (Array.isArray(arg2)) { commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking } if (userSettingsLabel && event.triggeredByAccelerator) { this.runActionInRenderer({ type: 'keybinding', userSettingsLabel }); } else { this.runActionInRenderer({ type: 'commandId', commandId }); } }; const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0; const checked = typeof arg4 === 'boolean' ? arg4 : false; const options = { label, click, enabled, }; if (checked) { options.type = 'checkbox'; options.checked = checked; } let commandId; if (typeof arg2 === 'string') { commandId = arg2; } else if (Array.isArray(arg2)) { commandId = arg2[0]; } if (isMacintosh) { // Add role for special case menu items if (commandId === 'editor.action.clipboardCutAction') { options.role = 'cut'; } else if (commandId === 'editor.action.clipboardCopyAction') { options.role = 'copy'; } else if (commandId === 'editor.action.clipboardPasteAction') { options.role = 'paste'; } // Add context aware click handlers for special case menu items if (commandId === 'undo') { options.click = this.makeContextAwareClickHandler(click, { inDevTools: (devTools) => devTools.undo(), inNoWindow: () => Menu.sendActionToFirstResponder('undo:'), }); } else if (commandId === 'redo') { options.click = this.makeContextAwareClickHandler(click, { inDevTools: (devTools) => devTools.redo(), inNoWindow: () => Menu.sendActionToFirstResponder('redo:'), }); } else if (commandId === 'editor.action.selectAll') { options.click = this.makeContextAwareClickHandler(click, { inDevTools: (devTools) => devTools.selectAll(), inNoWindow: () => Menu.sendActionToFirstResponder('selectAll:'), }); } } return new MenuItem(this.withKeybinding(commandId, options)); } makeContextAwareClickHandler(click, contextSpecificHandlers) { return (menuItem, win, event) => { // No Active Window const activeWindow = BrowserWindow.getFocusedWindow(); if (!activeWindow) { return contextSpecificHandlers.inNoWindow(); } // DevTools focused if (activeWindow.webContents.isDevToolsFocused() && activeWindow.webContents.devToolsWebContents) { return contextSpecificHandlers.inDevTools(activeWindow.webContents.devToolsWebContents); } // Finally execute command in Window click(menuItem, win || activeWindow, event); }; } runActionInRenderer(invocation) { // We make sure to not run actions when the window has no focus, this helps // for https://github.com/microsoft/vscode/issues/25907 and specifically for // https://github.com/microsoft/vscode/issues/11928 // Still allow to run when the last active window is minimized though for // https://github.com/microsoft/vscode/issues/63000 let activeBrowserWindow = BrowserWindow.getFocusedWindow(); if (!activeBrowserWindow) { const lastActiveWindow = this.windowsMainService.getLastActiveWindow(); if (lastActiveWindow?.isMinimized()) { activeBrowserWindow = lastActiveWindow.win; } } const activeWindow = activeBrowserWindow ? this.windowsMainService.getWindowById(activeBrowserWindow.id) : undefined; if (activeWindow) { this.logService.trace('menubar#runActionInRenderer', invocation); if (isMacintosh && !this.environmentMainService.isBuilt && !activeWindow.isReady) { if ( (invocation.type === 'commandId' && invocation.commandId === 'workbench.action.toggleDevTools') || (invocation.type !== 'commandId' && invocation.userSettingsLabel === 'alt+cmd+i') ) { // prevent this action from running twice on macOS (https://github.com/microsoft/vscode/issues/62719) // we already register a keybinding in bootstrap-window.js for opening developer tools in case something // goes wrong and that keybinding is only removed when the application has loaded (= window ready). return; } } if (invocation.type === 'commandId') { const runActionPayload = { id: invocation.commandId, from: 'menu' }; activeWindow.sendWhenReady('vscode:runAction', CancellationToken.None, runActionPayload); } else { const runKeybindingPayload = { userSettingsLabel: invocation.userSettingsLabel }; activeWindow.sendWhenReady('vscode:runKeybinding', CancellationToken.None, runKeybindingPayload); } } else { this.logService.trace('menubar#runActionInRenderer: no active window found', invocation); } } withKeybinding(commandId, options) { const binding = typeof commandId === 'string' ? this.keybindings[commandId] : undefined; // Apply binding if there is one if (binding?.label) { // if the binding is native, we can just apply it if (binding.isNative !== false) { options.accelerator = binding.label; options.userSettingsLabel = binding.userSettingsLabel; } // the keybinding is not native so we cannot show it as part of the accelerator of // the menu item. we fallback to a different strategy so that we always display it else if (typeof options.label === 'string') { const bindingIndex = options.label.indexOf('['); if (bindingIndex >= 0) { options.label = `${options.label.substr(0, bindingIndex)} [${binding.label}]`; } else { options.label = `${options.label} [${binding.label}]`; } } } // Unset bindings if there is none else { options.accelerator = undefined; } return options; } likeAction(commandId, options, setAccelerator = !options.accelerator) { if (setAccelerator) { options = this.withKeybinding(commandId, options); } const originalClick = options.click; options.click = (item, window, event) => { this.reportMenuActionTelemetry(commandId); originalClick?.(item, window, event); }; return options; } openUrl(url, id) { this.nativeHostMainService.openExternal(undefined, url); this.reportMenuActionTelemetry(id); } reportMenuActionTelemetry(id) { this.telemetryService.publicLog2('workbenchActionExecuted', { id, from: telemetryFrom }); } mnemonicLabel(label) { return mnemonicMenuLabel(label, !this.currentEnableMenuBarMnemonics); } }; Menubar = __decorate( [ __param(0, IUpdateService), __param(1, IConfigurationService), __param(2, IWindowsMainService), __param(3, IEnvironmentMainService), __param(4, ITelemetryService), __param(5, IWorkspacesHistoryMainService), __param(6, IStateMainService), __param(7, ILifecycleMainService), __param(8, ILogService), __param(9, INativeHostMainService), __param(10, IProductService), ], Menubar, ); export { Menubar }; function __separator__() { return new MenuItem({ type: 'separator' }); }