menus.ts 43.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

J
Joao Moreno 已提交
8 9 10
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import * as arrays from 'vs/base/common/arrays';
11
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
J
Joao Moreno 已提交
12
import { ipcMain as ipc, app, shell, dialog, Menu, MenuItem } from 'electron';
J
Joao Moreno 已提交
13
import { IWindowsMainService } from 'vs/code/electron-main/windows';
J
Joao Moreno 已提交
14
import { IPath, VSCodeWindow } from 'vs/code/electron-main/window';
B
Benjamin Pasero 已提交
15
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
16
import { IStorageService } from 'vs/code/electron-main/storage';
B
Benjamin Pasero 已提交
17
import { IFilesConfiguration, AutoSaveConfiguration } from 'vs/platform/files/common/files';
18
import { IUpdateService, State as UpdateState } from 'vs/code/electron-main/update-manager';
A
Alexandru Dima 已提交
19
import { Keybinding } from 'vs/base/common/keybinding';
J
Joao Moreno 已提交
20
import product from 'vs/platform/product';
E
Erich Gamma 已提交
21 22

interface IResolvedKeybinding {
B
Benjamin Pasero 已提交
23 24
	id: string;
	binding: number;
E
Erich Gamma 已提交
25 26
}

B
Benjamin Pasero 已提交
27 28 29 30 31 32 33
interface IConfiguration extends IFilesConfiguration {
	workbench: {
		sideBar: {
			location: 'left' | 'right';
		},
		statusBar: {
			visible: boolean;
S
Sanders Lauture 已提交
34 35 36
		},
		activityBar: {
			visible: boolean;
B
Benjamin Pasero 已提交
37 38 39 40
		}
	};
}

E
Erich Gamma 已提交
41 42 43 44
export class VSCodeMenu {

	private static lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings';

45
	private static MAX_MENU_RECENT_ENTRIES = 10;
46

B
Benjamin Pasero 已提交
47
	private currentAutoSaveSetting: string;
B
Benjamin Pasero 已提交
48 49
	private currentSidebarLocation: 'left' | 'right';
	private currentStatusbarVisible: boolean;
S
Sanders Lauture 已提交
50
	private currentActivityBarVisible: boolean;
B
Benjamin Pasero 已提交
51

B
Benjamin Pasero 已提交
52
	private isQuitting: boolean;
E
Erich Gamma 已提交
53 54 55 56 57 58 59
	private appMenuInstalled: boolean;

	private actionIdKeybindingRequests: string[];
	private mapLastKnownKeybindingToActionId: { [id: string]: string; };
	private mapResolvedKeybindingToActionId: { [id: string]: string; };
	private keybindingsResolved: boolean;

J
Joao Moreno 已提交
60 61
	constructor(
		@IStorageService private storageService: IStorageService,
B
Benjamin Pasero 已提交
62
		@IUpdateService private updateService: IUpdateService,
B
Benjamin Pasero 已提交
63
		@IConfigurationService private configurationService: IConfigurationService,
J
Joao Moreno 已提交
64
		@IWindowsMainService private windowsService: IWindowsMainService,
65
		@IEnvironmentService private environmentService: IEnvironmentService
J
Joao Moreno 已提交
66
	) {
E
Erich Gamma 已提交
67 68 69
		this.actionIdKeybindingRequests = [];

		this.mapResolvedKeybindingToActionId = Object.create(null);
J
Joao Moreno 已提交
70
		this.mapLastKnownKeybindingToActionId = this.storageService.getItem<{ [id: string]: string; }>(VSCodeMenu.lastKnownKeybindingsMapStorageKey) || Object.create(null);
B
Benjamin Pasero 已提交
71

B
Benjamin Pasero 已提交
72
		this.onConfigurationUpdated(this.configurationService.getConfiguration<IConfiguration>());
E
Erich Gamma 已提交
73 74 75 76 77 78 79 80 81 82 83
	}

	public ready(): void {
		this.registerListeners();
		this.install();
	}

	private registerListeners(): void {

		// Keep flag when app quits
		app.on('will-quit', () => {
B
Benjamin Pasero 已提交
84
			this.isQuitting = true;
E
Erich Gamma 已提交
85 86
		});

B
Benjamin Pasero 已提交
87
		// Listen to "open" & "close" event from window service
B
Benjamin Pasero 已提交
88
		this.windowsService.onOpen(paths => this.onOpen(paths));
B
Benjamin Pasero 已提交
89
		this.windowsService.onClose(_ => this.onClose(this.windowsService.getWindowCount()));
E
Erich Gamma 已提交
90 91

		// Resolve keybindings when any first workbench is loaded
B
Benjamin Pasero 已提交
92
		this.windowsService.onReady(win => this.resolveKeybindings(win));
E
Erich Gamma 已提交
93 94 95

		// Listen to resolved keybindings
		ipc.on('vscode:keybindingsResolved', (event, rawKeybindings) => {
B
Benjamin Pasero 已提交
96
			let keybindings: IResolvedKeybinding[] = [];
E
Erich Gamma 已提交
97 98 99 100 101 102
			try {
				keybindings = JSON.parse(rawKeybindings);
			} catch (error) {
				// Should not happen
			}

103
			// Fill hash map of resolved keybindings
E
Erich Gamma 已提交
104
			let needsMenuUpdate = false;
B
Benjamin Pasero 已提交
105
			keybindings.forEach(keybinding => {
B
Benjamin Pasero 已提交
106
				const accelerator = new Keybinding(keybinding.binding)._toElectronAccelerator();
E
Erich Gamma 已提交
107 108 109 110 111 112 113 114
				if (accelerator) {
					this.mapResolvedKeybindingToActionId[keybinding.id] = accelerator;
					if (this.mapLastKnownKeybindingToActionId[keybinding.id] !== accelerator) {
						needsMenuUpdate = true; // we only need to update when something changed!
					}
				}
			});

115 116 117 118 119
			// A keybinding might have been unassigned, so we have to account for that too
			if (Object.keys(this.mapLastKnownKeybindingToActionId).length !== Object.keys(this.mapResolvedKeybindingToActionId).length) {
				needsMenuUpdate = true;
			}

E
Erich Gamma 已提交
120
			if (needsMenuUpdate) {
J
Joao Moreno 已提交
121
				this.storageService.setItem(VSCodeMenu.lastKnownKeybindingsMapStorageKey, this.mapResolvedKeybindingToActionId); // keep to restore instantly after restart
122 123
				this.mapLastKnownKeybindingToActionId = this.mapResolvedKeybindingToActionId; // update our last known map

E
Erich Gamma 已提交
124 125 126 127
				this.updateMenu();
			}
		});

B
Benjamin Pasero 已提交
128
		// Update when auto save config changes
B
Benjamin Pasero 已提交
129
		this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config, true /* update menu if changed */));
B
Benjamin Pasero 已提交
130

B
Benjamin Pasero 已提交
131 132
		// Listen to update service
		this.updateService.on('change', () => this.updateMenu());
E
Erich Gamma 已提交
133 134
	}

B
Benjamin Pasero 已提交
135
	private onConfigurationUpdated(config: IConfiguration, handleMenu?: boolean): void {
B
Benjamin Pasero 已提交
136
		let updateMenu = false;
B
Benjamin Pasero 已提交
137 138 139
		const newAutoSaveSetting = config && config.files && config.files.autoSave;
		if (newAutoSaveSetting !== this.currentAutoSaveSetting) {
			this.currentAutoSaveSetting = newAutoSaveSetting;
B
Benjamin Pasero 已提交
140 141 142
			updateMenu = true;
		}

B
Benjamin Pasero 已提交
143 144 145 146 147 148
		if (config && config.workbench) {
			const newSidebarLocation = config.workbench.sideBar && config.workbench.sideBar.location || 'left';
			if (newSidebarLocation !== this.currentSidebarLocation) {
				this.currentSidebarLocation = newSidebarLocation;
				updateMenu = true;
			}
B
Benjamin Pasero 已提交
149

B
Benjamin Pasero 已提交
150 151 152 153 154 155 156 157
			let newStatusbarVisible = config.workbench.statusBar && config.workbench.statusBar.visible;
			if (typeof newStatusbarVisible !== 'boolean') {
				newStatusbarVisible = true;
			}
			if (newStatusbarVisible !== this.currentStatusbarVisible) {
				this.currentStatusbarVisible = newStatusbarVisible;
				updateMenu = true;
			}
S
Sanders Lauture 已提交
158 159 160 161 162 163 164 165 166

			let newActivityBarVisible = config.workbench.activityBar && config.workbench.activityBar.visible;
			if (typeof newActivityBarVisible !== 'boolean') {
				newActivityBarVisible = true;
			}
			if (newActivityBarVisible !== this.currentActivityBarVisible) {
				this.currentActivityBarVisible = newActivityBarVisible;
				updateMenu = true;
			}
B
Benjamin Pasero 已提交
167 168 169
		}

		if (handleMenu && updateMenu) {
B
Benjamin Pasero 已提交
170 171 172 173
			this.updateMenu();
		}
	}

J
Joao Moreno 已提交
174
	private resolveKeybindings(win: VSCodeWindow): void {
E
Erich Gamma 已提交
175 176 177 178 179 180 181 182
		if (this.keybindingsResolved) {
			return; // only resolve once
		}

		this.keybindingsResolved = true;

		// Resolve keybindings when workbench window is up
		if (this.actionIdKeybindingRequests.length) {
183
			win.send('vscode:resolveKeybindings', JSON.stringify(this.actionIdKeybindingRequests));
E
Erich Gamma 已提交
184 185 186 187 188 189 190
		}
	}

	private updateMenu(): void {

		// 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.
M
Martin Aeschlimann 已提交
191
		// See also https://github.com/electron/electron/issues/846
E
Erich Gamma 已提交
192 193 194 195 196 197 198 199 200 201 202
		//
		// Run delayed to prevent updating menu while it is open
		if (!this.isQuitting) {
			setTimeout(() => {
				if (!this.isQuitting) {
					this.install();
				}
			}, 10 /* delay this because there is an issue with updating a menu when it is open */);
		}
	}

J
Joao Moreno 已提交
203
	private onOpen(path: IPath): void {
E
Erich Gamma 已提交
204 205 206
		this.updateMenu();
	}

B
Benjamin Pasero 已提交
207
	private onClose(remainingWindowCount: number): void {
E
Erich Gamma 已提交
208 209 210 211 212 213 214 215
		if (remainingWindowCount === 0 && platform.isMacintosh) {
			this.updateMenu();
		}
	}

	private install(): void {

		// Menus
B
Benjamin Pasero 已提交
216
		const menubar = new Menu();
E
Erich Gamma 已提交
217 218

		// Mac: Application
B
Benjamin Pasero 已提交
219
		let macApplicationMenuItem: Electron.MenuItem;
E
Erich Gamma 已提交
220
		if (platform.isMacintosh) {
B
Benjamin Pasero 已提交
221
			const applicationMenu = new Menu();
B
Benjamin Pasero 已提交
222
			macApplicationMenuItem = new MenuItem({ label: product.nameShort, submenu: applicationMenu });
E
Erich Gamma 已提交
223 224 225 226
			this.setMacApplicationMenu(applicationMenu);
		}

		// File
B
Benjamin Pasero 已提交
227 228
		const fileMenu = new Menu();
		const fileMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu });
E
Erich Gamma 已提交
229 230 231
		this.setFileMenu(fileMenu);

		// Edit
B
Benjamin Pasero 已提交
232 233
		const editMenu = new Menu();
		const editMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu });
E
Erich Gamma 已提交
234 235 236
		this.setEditMenu(editMenu);

		// View
B
Benjamin Pasero 已提交
237 238
		const viewMenu = new Menu();
		const viewMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu });
E
Erich Gamma 已提交
239 240 241
		this.setViewMenu(viewMenu);

		// Goto
B
Benjamin Pasero 已提交
242 243
		const gotoMenu = new Menu();
		const gotoMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
E
Erich Gamma 已提交
244 245 246
		this.setGotoMenu(gotoMenu);

		// Mac: Window
B
Benjamin Pasero 已提交
247
		let macWindowMenuItem: Electron.MenuItem;
E
Erich Gamma 已提交
248
		if (platform.isMacintosh) {
B
Benjamin Pasero 已提交
249
			const windowMenu = new Menu();
E
Erich Gamma 已提交
250 251 252 253 254
			macWindowMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize('mWindow', "Window")), submenu: windowMenu, role: 'window' });
			this.setMacWindowMenu(windowMenu);
		}

		// Help
B
Benjamin Pasero 已提交
255 256
		const helpMenu = new Menu();
		const helpMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' });
E
Erich Gamma 已提交
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
		this.setHelpMenu(helpMenu);

		// Menu Structure
		if (macApplicationMenuItem) {
			menubar.append(macApplicationMenuItem);
		}

		menubar.append(fileMenuItem);
		menubar.append(editMenuItem);
		menubar.append(viewMenuItem);
		menubar.append(gotoMenuItem);

		if (macWindowMenuItem) {
			menubar.append(macWindowMenuItem);
		}

		menubar.append(helpMenuItem);

		Menu.setApplicationMenu(menubar);

		// Dock Menu
		if (platform.isMacintosh && !this.appMenuInstalled) {
			this.appMenuInstalled = true;

B
Benjamin Pasero 已提交
281
			const dockMenu = new Menu();
B
Benjamin Pasero 已提交
282
			dockMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), click: () => this.windowsService.openNewWindow() }));
E
Erich Gamma 已提交
283

284
			app.dock.setMenu(dockMenu);
E
Erich Gamma 已提交
285 286 287
		}
	}

B
Benjamin Pasero 已提交
288
	private setMacApplicationMenu(macApplicationMenu: Electron.Menu): void {
B
Benjamin Pasero 已提交
289
		const about = new MenuItem({ label: nls.localize('mAbout', "About {0}", product.nameLong), role: 'about' });
B
Benjamin Pasero 已提交
290 291
		const checkForUpdates = this.getUpdateMenuItems();
		const preferences = this.getPreferencesMenu();
B
Benjamin Pasero 已提交
292
		const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' });
B
Benjamin Pasero 已提交
293 294
		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' });
B
Benjamin Pasero 已提交
295
		const quit = new MenuItem({ label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => this.quit(), accelerator: 'Command+Q' });
B
Benjamin Pasero 已提交
296 297

		const actions = [about];
E
Erich Gamma 已提交
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
		actions.push(...checkForUpdates);
		actions.push(...[
			__separator__(),
			preferences,
			__separator__(),
			hide,
			hideOthers,
			showAll,
			__separator__(),
			quit
		]);

		actions.forEach(i => macApplicationMenu.append(i));
	}

B
Benjamin Pasero 已提交
313
	private setFileMenu(fileMenu: Electron.Menu): void {
B
Benjamin Pasero 已提交
314
		const hasNoWindows = (this.windowsService.getWindowCount() === 0);
E
Erich Gamma 已提交
315

B
Benjamin Pasero 已提交
316
		let newFile: Electron.MenuItem;
E
Erich Gamma 已提交
317
		if (hasNoWindows) {
B
Benjamin Pasero 已提交
318
			newFile = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), accelerator: this.getAccelerator('workbench.action.files.newUntitledFile'), click: () => this.windowsService.openNewWindow() });
E
Erich Gamma 已提交
319
		} else {
B
Benjamin Pasero 已提交
320
			newFile = this.createMenuItem(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File"), 'workbench.action.files.newUntitledFile');
E
Erich Gamma 已提交
321 322
		}

B
Benjamin Pasero 已提交
323 324
		const open = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), accelerator: this.getAccelerator('workbench.action.files.openFileFolder'), click: () => this.windowsService.openFileFolderPicker() });
		const openFolder = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), accelerator: this.getAccelerator('workbench.action.files.openFolder'), click: () => this.windowsService.openFolderPicker() });
E
Erich Gamma 已提交
325

326 327
		let openFile: Electron.MenuItem;
		if (hasNoWindows) {
B
Benjamin Pasero 已提交
328
			openFile = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")), accelerator: this.getAccelerator('workbench.action.files.openFile'), click: () => this.windowsService.openFilePicker() });
329 330 331 332
		} else {
			openFile = this.createMenuItem(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File..."), 'workbench.action.files.openFile');
		}

B
Benjamin Pasero 已提交
333
		const openRecentMenu = new Menu();
E
Erich Gamma 已提交
334
		this.setOpenRecentMenu(openRecentMenu);
B
Benjamin Pasero 已提交
335
		const openRecent = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu, enabled: openRecentMenu.items.length > 0 });
E
Erich Gamma 已提交
336

B
Benjamin Pasero 已提交
337 338 339
		const saveFile = this.createMenuItem(nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), 'workbench.action.files.save', this.windowsService.getWindowCount() > 0);
		const saveFileAs = this.createMenuItem(nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As..."), 'workbench.action.files.saveAs', this.windowsService.getWindowCount() > 0);
		const saveAllFiles = this.createMenuItem(nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), 'workbench.action.files.saveAll', this.windowsService.getWindowCount() > 0);
E
Erich Gamma 已提交
340

B
Benjamin Pasero 已提交
341 342
		const autoSaveEnabled = [AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => this.currentAutoSaveSetting === s);
		const autoSave = new MenuItem({ label: mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), type: 'checkbox', checked: autoSaveEnabled, enabled: this.windowsService.getWindowCount() > 0, click: () => this.windowsService.sendToFocused('vscode.toggleAutoSave') });
B
Benjamin Pasero 已提交
343

B
Benjamin Pasero 已提交
344
		const preferences = this.getPreferencesMenu();
E
Erich Gamma 已提交
345

B
Benjamin Pasero 已提交
346 347 348
		const newWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), accelerator: this.getAccelerator('workbench.action.newWindow'), click: () => this.windowsService.openNewWindow() });
		const revertFile = this.createMenuItem(nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Revert F&&ile"), 'workbench.action.files.revert', this.windowsService.getWindowCount() > 0);
		const closeWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Close &&Window")), accelerator: this.getAccelerator('workbench.action.closeWindow'), click: () => this.windowsService.getLastActiveWindow().win.close(), enabled: this.windowsService.getWindowCount() > 0 });
E
Erich Gamma 已提交
349

B
Benjamin Pasero 已提交
350 351
		const closeFolder = this.createMenuItem(nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), 'workbench.action.closeFolder');
		const closeEditor = this.createMenuItem(nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "Close &&Editor"), 'workbench.action.closeActiveEditor');
E
Erich Gamma 已提交
352

B
Benjamin Pasero 已提交
353
		const exit = this.createMenuItem(nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit"), () => this.quit());
E
Erich Gamma 已提交
354 355 356 357 358 359 360 361 362 363 364 365 366 367

		arrays.coalesce([
			newFile,
			newWindow,
			__separator__(),
			platform.isMacintosh ? open : null,
			!platform.isMacintosh ? openFile : null,
			!platform.isMacintosh ? openFolder : null,
			openRecent,
			__separator__(),
			saveFile,
			saveFileAs,
			saveAllFiles,
			__separator__(),
B
Benjamin Pasero 已提交
368 369
			autoSave,
			__separator__(),
E
Erich Gamma 已提交
370 371 372 373 374 375 376 377
			!platform.isMacintosh ? preferences : null,
			!platform.isMacintosh ? __separator__() : null,
			revertFile,
			closeEditor,
			closeFolder,
			!platform.isMacintosh ? closeWindow : null,
			!platform.isMacintosh ? __separator__() : null,
			!platform.isMacintosh ? exit : null
B
Benjamin Pasero 已提交
378
		]).forEach(item => fileMenu.append(item));
E
Erich Gamma 已提交
379 380
	}

B
Benjamin Pasero 已提交
381
	private getPreferencesMenu(): Electron.MenuItem {
B
Benjamin Pasero 已提交
382 383 384 385 386 387 388 389
		const userSettings = this.createMenuItem(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&User Settings"), 'workbench.action.openGlobalSettings');
		const workspaceSettings = this.createMenuItem(nls.localize({ key: 'miOpenWorkspaceSettings', comment: ['&& denotes a mnemonic'] }, "&&Workspace Settings"), 'workbench.action.openWorkspaceSettings');
		const kebindingSettings = this.createMenuItem(nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts"), 'workbench.action.openGlobalKeybindings');
		const snippetsSettings = this.createMenuItem(nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), 'workbench.action.openSnippets');
		const colorThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme"), 'workbench.action.selectTheme');
		const iconThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme"), 'workbench.action.selectIconTheme');

		const preferencesMenu = new Menu();
E
Erich Gamma 已提交
390 391 392 393 394 395 396
		preferencesMenu.append(userSettings);
		preferencesMenu.append(workspaceSettings);
		preferencesMenu.append(__separator__());
		preferencesMenu.append(kebindingSettings);
		preferencesMenu.append(__separator__());
		preferencesMenu.append(snippetsSettings);
		preferencesMenu.append(__separator__());
397 398
		preferencesMenu.append(colorThemeSelection);
		preferencesMenu.append(iconThemeSelection);
E
Erich Gamma 已提交
399

B
Benjamin Pasero 已提交
400
		return new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences")), submenu: preferencesMenu });
E
Erich Gamma 已提交
401 402 403 404
	}

	private quit(): void {

A
Alex Dima 已提交
405
		// If the user selected to exit from an extension development host window, do not quit, but just
E
Erich Gamma 已提交
406
		// close the window unless this is the last window that is opened.
B
Benjamin Pasero 已提交
407
		const vscodeWindow = this.windowsService.getFocusedWindow();
B
Benjamin Pasero 已提交
408
		if (vscodeWindow && vscodeWindow.isPluginDevelopmentHost && this.windowsService.getWindowCount() > 1) {
E
Erich Gamma 已提交
409 410 411 412 413 414 415 416 417 418 419 420 421
			vscodeWindow.win.close();
		}

		// Otherwise: normal quit
		else {
			setTimeout(() => {
				this.isQuitting = true;

				app.quit();
			}, 10 /* delay this because there is an issue with quitting while the menu is open */);
		}
	}

B
Benjamin Pasero 已提交
422
	private setOpenRecentMenu(openRecentMenu: Electron.Menu): void {
423
		openRecentMenu.append(this.createMenuItem(nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), 'workbench.action.reopenClosedEditor'));
D
Daniel Imms 已提交
424

425
		const {folders, files} = this.windowsService.getRecentPathsList();
E
Erich Gamma 已提交
426 427

		// Folders
428
		if (folders.length > 0) {
429
			openRecentMenu.append(__separator__());
430 431 432 433

			for (let i = 0; i < VSCodeMenu.MAX_MENU_RECENT_ENTRIES && i < folders.length; i++) {
				openRecentMenu.append(this.createOpenRecentMenuItem(folders[i]));
			}
434
		}
E
Erich Gamma 已提交
435 436

		// Files
437
		if (files.length > 0) {
438
			openRecentMenu.append(__separator__());
E
Erich Gamma 已提交
439

440 441 442
			for (let i = 0; i < VSCodeMenu.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) {
				openRecentMenu.append(this.createOpenRecentMenuItem(files[i]));
			}
E
Erich Gamma 已提交
443 444
		}

445
		if (folders.length || files.length) {
E
Erich Gamma 已提交
446
			openRecentMenu.append(__separator__());
447
			openRecentMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miClearItems', comment: ['&& denotes a mnemonic'] }, "&&Clear Items")), click: () => { this.windowsService.clearRecentPathsList(); this.updateMenu(); } }));
E
Erich Gamma 已提交
448 449 450
		}
	}

B
Benjamin Pasero 已提交
451 452
	private createOpenRecentMenuItem(path: string): Electron.MenuItem {
		return new MenuItem({
453 454
			label: unMnemonicLabel(path), click: (menuItem, win, event) => {
				const openInNewWindow = event && ((!platform.isMacintosh && event.ctrlKey) || (platform.isMacintosh && event.metaKey));
455
				const success = !!this.windowsService.open({ cli: this.environmentService.args, pathsToOpen: [path], forceNewWindow: openInNewWindow });
B
Benjamin Pasero 已提交
456
				if (!success) {
457
					this.windowsService.removeFromRecentPathsList(path);
B
Benjamin Pasero 已提交
458 459
					this.updateMenu();
				}
E
Erich Gamma 已提交
460
			}
B
Benjamin Pasero 已提交
461
		});
E
Erich Gamma 已提交
462 463
	}

B
Benjamin Pasero 已提交
464
	private createRoleMenuItem(label: string, actionId: string, role: Electron.MenuItemRole): Electron.MenuItem {
B
Benjamin Pasero 已提交
465
		const options: Electron.MenuItemOptions = {
466 467
			label: mnemonicLabel(label),
			accelerator: this.getAccelerator(actionId),
B
Benjamin Pasero 已提交
468
			role,
469 470 471 472 473 474
			enabled: true
		};

		return new MenuItem(options);
	}

B
Benjamin Pasero 已提交
475 476 477 478 479 480 481
	private setEditMenu(winLinuxEditMenu: Electron.Menu): void {
		let undo: Electron.MenuItem;
		let redo: Electron.MenuItem;
		let cut: Electron.MenuItem;
		let copy: Electron.MenuItem;
		let paste: Electron.MenuItem;
		let selectAll: Electron.MenuItem;
E
Erich Gamma 已提交
482 483

		if (platform.isMacintosh) {
B
Benjamin Pasero 已提交
484 485
			undo = this.createDevToolsAwareMenuItem(nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), 'undo', devTools => devTools.undo());
			redo = this.createDevToolsAwareMenuItem(nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), 'redo', devTools => devTools.redo());
486 487 488 489
			cut = this.createRoleMenuItem(nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "&&Cut"), 'editor.action.clipboardCutAction', 'cut');
			copy = this.createRoleMenuItem(nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "C&&opy"), 'editor.action.clipboardCopyAction', 'copy');
			paste = this.createRoleMenuItem(nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), 'editor.action.clipboardPasteAction', 'paste');
			selectAll = this.createDevToolsAwareMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll', (devTools) => devTools.selectAll());
E
Erich Gamma 已提交
490
		} else {
B
Benjamin Pasero 已提交
491 492 493 494 495 496
			undo = this.createMenuItem(nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), 'undo');
			redo = this.createMenuItem(nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), 'redo');
			cut = this.createMenuItem(nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "&&Cut"), 'editor.action.clipboardCutAction');
			copy = this.createMenuItem(nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "C&&opy"), 'editor.action.clipboardCopyAction');
			paste = this.createMenuItem(nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), 'editor.action.clipboardPasteAction');
			selectAll = this.createMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll');
E
Erich Gamma 已提交
497 498
		}

B
Benjamin Pasero 已提交
499 500
		const find = this.createMenuItem(nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"), 'actions.find');
		const replace = this.createMenuItem(nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"), 'editor.action.startFindReplaceAction');
S
Sandeep Somavarapu 已提交
501
		const findInFiles = this.createMenuItem(nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files"), 'workbench.action.findInFiles');
B
Benjamin Pasero 已提交
502
		const replaceInFiles = this.createMenuItem(nls.localize({ key: 'miReplaceInFiles', comment: ['&& denotes a mnemonic'] }, "Replace &&in Files"), 'workbench.action.replaceInFiles');
E
Erich Gamma 已提交
503 504 505 506 507 508 509 510 511 512 513 514 515

		[
			undo,
			redo,
			__separator__(),
			cut,
			copy,
			paste,
			selectAll,
			__separator__(),
			find,
			replace,
			__separator__(),
S
Sandeep Somavarapu 已提交
516 517
			findInFiles,
			replaceInFiles
B
Benjamin Pasero 已提交
518
		].forEach(item => winLinuxEditMenu.append(item));
E
Erich Gamma 已提交
519 520
	}

B
Benjamin Pasero 已提交
521
	private setViewMenu(viewMenu: Electron.Menu): void {
B
Benjamin Pasero 已提交
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
		const explorer = this.createMenuItem(nls.localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer"), 'workbench.view.explorer');
		const search = this.createMenuItem(nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search"), 'workbench.view.search');
		const git = this.createMenuItem(nls.localize({ key: 'miViewGit', comment: ['&& denotes a mnemonic'] }, "&&Git"), 'workbench.view.git');
		const debug = this.createMenuItem(nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'workbench.view.debug');
		const extensions = this.createMenuItem(nls.localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions"), 'workbench.view.extensions');
		const output = this.createMenuItem(nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output"), 'workbench.action.output.toggleOutput');
		const debugConsole = this.createMenuItem(nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console"), 'workbench.debug.action.toggleRepl');
		const integratedTerminal = this.createMenuItem(nls.localize({ key: 'miToggleIntegratedTerminal', comment: ['&& denotes a mnemonic'] }, "&&Integrated Terminal"), 'workbench.action.terminal.toggleTerminal');
		const problems = this.createMenuItem(nls.localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems"), 'workbench.actions.view.problems');

		const commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands');

		const fullscreen = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), accelerator: this.getAccelerator('workbench.action.toggleFullScreen'), click: () => this.windowsService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsService.getWindowCount() > 0 });
		const toggleMenuBar = this.createMenuItem(nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar"), 'workbench.action.toggleMenuBar');
		const splitEditor = this.createMenuItem(nls.localize({ key: 'miSplitEditor', comment: ['&& denotes a mnemonic'] }, "Split &&Editor"), 'workbench.action.splitEditor');
537
		const toggleEditorLayout = this.createMenuItem(nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Toggle Editor Group &&Layout"), 'workbench.action.toggleEditorGroupLayout');
B
Benjamin Pasero 已提交
538
		const toggleSidebar = this.createMenuItem(nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar"), 'workbench.action.toggleSidebarVisibility');
B
Benjamin Pasero 已提交
539 540 541 542 543 544 545 546 547 548

		let moveSideBarLabel: string;
		if (this.currentSidebarLocation !== 'right') {
			moveSideBarLabel = nls.localize({ key: 'miMoveSidebarRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Right");
		} else {
			moveSideBarLabel = nls.localize({ key: 'miMoveSidebarLeft', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left");
		}

		const moveSidebar = this.createMenuItem(moveSideBarLabel, 'workbench.action.toggleSidebarPosition');

B
Benjamin Pasero 已提交
549
		const togglePanel = this.createMenuItem(nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel"), 'workbench.action.togglePanel');
B
Benjamin Pasero 已提交
550 551 552 553 554 555 556 557

		let statusBarLabel: string;
		if (this.currentStatusbarVisible) {
			statusBarLabel = nls.localize({ key: 'miHideStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Hide Status Bar");
		} else {
			statusBarLabel = nls.localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Show Status Bar");
		}
		const toggleStatusbar = this.createMenuItem(statusBarLabel, 'workbench.action.toggleStatusbarVisibility');
E
Erich Gamma 已提交
558

S
Sanders Lauture 已提交
559 560 561 562 563 564 565 566
		let activityBarLabel: string;
		if (this.currentActivityBarVisible) {
			activityBarLabel = 'Show Activity Bar';
		} else {
			activityBarLabel = 'Hide Activity Bar';
		}
		const toggleActivtyBar = this.createMenuItem(activityBarLabel, 'workbench.action.toggleActivityBarVisibility');

B
Benjamin Pasero 已提交
567
		const toggleWordWrap = this.createMenuItem(nls.localize({ key: 'miToggleWordWrap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Word Wrap"), 'editor.action.toggleWordWrap');
568
		const toggleRenderWhitespace = this.createMenuItem(nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "Toggle &&Render Whitespace"), 'editor.action.toggleRenderWhitespace');
A
Alex Dima 已提交
569
		const toggleRenderControlCharacters = this.createMenuItem(nls.localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Toggle &&Control Characters"), 'editor.action.toggleRenderControlCharacter');
570

B
Benjamin Pasero 已提交
571 572 573
		const zoomIn = this.createMenuItem(nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), 'workbench.action.zoomIn');
		const zoomOut = this.createMenuItem(nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "Zoom O&&ut"), 'workbench.action.zoomOut');
		const resetZoom = this.createMenuItem(nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), 'workbench.action.zoomReset');
C
Chris Dias 已提交
574

B
Benjamin Pasero 已提交
575
		arrays.coalesce([
576 577
			commands,
			__separator__(),
578 579 580 581
			explorer,
			search,
			git,
			debug,
J
Joao Moreno 已提交
582
			extensions,
583 584 585 586 587
			__separator__(),
			output,
			problems,
			debugConsole,
			integratedTerminal,
C
Chris Dias 已提交
588
			__separator__(),
E
Erich Gamma 已提交
589
			fullscreen,
B
Benjamin Pasero 已提交
590
			platform.isWindows || platform.isLinux ? toggleMenuBar : void 0,
E
Erich Gamma 已提交
591 592
			__separator__(),
			splitEditor,
593
			toggleEditorLayout,
E
Erich Gamma 已提交
594
			moveSidebar,
B
Benjamin Pasero 已提交
595 596 597
			toggleSidebar,
			togglePanel,
			toggleStatusbar,
S
Sanders Lauture 已提交
598
			toggleActivtyBar,
E
Erich Gamma 已提交
599
			__separator__(),
J
Joao Moreno 已提交
600
			toggleWordWrap,
601
			toggleRenderWhitespace,
602
			toggleRenderControlCharacters,
J
Joao Moreno 已提交
603
			__separator__(),
E
Erich Gamma 已提交
604
			zoomIn,
605 606
			zoomOut,
			resetZoom
B
Benjamin Pasero 已提交
607
		]).forEach(item => viewMenu.append(item));
E
Erich Gamma 已提交
608 609
	}

B
Benjamin Pasero 已提交
610
	private setGotoMenu(gotoMenu: Electron.Menu): void {
B
Benjamin Pasero 已提交
611 612
		const back = this.createMenuItem(nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"), 'workbench.action.navigateBack');
		const forward = this.createMenuItem(nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"), 'workbench.action.navigateForward');
B
Benjamin Pasero 已提交
613

B
Benjamin Pasero 已提交
614
		const switchEditorMenu = new Menu();
B
Benjamin Pasero 已提交
615

B
Benjamin Pasero 已提交
616 617 618 619
		const nextEditor = this.createMenuItem(nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor"), 'workbench.action.nextEditor');
		const previousEditor = this.createMenuItem(nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor"), 'workbench.action.previousEditor');
		const nextEditorInGroup = this.createMenuItem(nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group"), 'workbench.action.openNextRecentlyUsedEditorInGroup');
		const previousEditorInGroup = this.createMenuItem(nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group"), 'workbench.action.openPreviousRecentlyUsedEditorInGroup');
B
Benjamin Pasero 已提交
620 621 622 623 624

		[
			nextEditor,
			previousEditor,
			__separator__(),
625
			nextEditorInGroup,
B
Benjamin Pasero 已提交
626 627 628
			previousEditorInGroup
		].forEach(item => switchEditorMenu.append(item));

B
Benjamin Pasero 已提交
629
		const switchEditor = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor")), submenu: switchEditorMenu, enabled: true });
B
Benjamin Pasero 已提交
630

B
Benjamin Pasero 已提交
631
		const switchGroupMenu = new Menu();
B
Benjamin Pasero 已提交
632

633 634 635
		const focusFirstGroup = this.createMenuItem(nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "&&First Group"), 'workbench.action.focusFirstEditorGroup');
		const focusSecondGroup = this.createMenuItem(nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "&&Second Group"), 'workbench.action.focusSecondEditorGroup');
		const focusThirdGroup = this.createMenuItem(nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "&&Third Group"), 'workbench.action.focusThirdEditorGroup');
B
Benjamin Pasero 已提交
636 637
		const nextGroup = this.createMenuItem(nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group"), 'workbench.action.focusNextGroup');
		const previousGroup = this.createMenuItem(nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group"), 'workbench.action.focusPreviousGroup');
B
Benjamin Pasero 已提交
638 639 640 641 642 643 644 645 646 647

		[
			focusFirstGroup,
			focusSecondGroup,
			focusThirdGroup,
			__separator__(),
			nextGroup,
			previousGroup
		].forEach(item => switchGroupMenu.append(item));

B
Benjamin Pasero 已提交
648
		const switchGroup = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group")), submenu: switchGroupMenu, enabled: true });
B
Benjamin Pasero 已提交
649

B
Benjamin Pasero 已提交
650
		const gotoFile = this.createMenuItem(nls.localize({ key: 'miGotoFile', comment: ['&& denotes a mnemonic'] }, "Go to &&File..."), 'workbench.action.quickOpen');
651 652
		const gotoSymbolInFile = this.createMenuItem(nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File..."), 'workbench.action.gotoSymbol');
		const gotoSymbolInWorkspace = this.createMenuItem(nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace..."), 'workbench.action.showAllSymbols');
B
Benjamin Pasero 已提交
653 654
		const gotoDefinition = this.createMenuItem(nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition"), 'editor.action.goToDeclaration');
		const gotoLine = this.createMenuItem(nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line..."), 'workbench.action.gotoLine');
E
Erich Gamma 已提交
655 656 657 658 659

		[
			back,
			forward,
			__separator__(),
B
Benjamin Pasero 已提交
660 661
			switchEditor,
			switchGroup,
E
Erich Gamma 已提交
662 663
			__separator__(),
			gotoFile,
664 665
			gotoSymbolInFile,
			gotoSymbolInWorkspace,
E
Erich Gamma 已提交
666 667
			gotoDefinition,
			gotoLine
B
Benjamin Pasero 已提交
668
		].forEach(item => gotoMenu.append(item));
E
Erich Gamma 已提交
669 670
	}

B
Benjamin Pasero 已提交
671
	private setMacWindowMenu(macWindowMenu: Electron.Menu): void {
B
Benjamin Pasero 已提交
672 673 674
		const minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsService.getWindowCount() > 0 });
		const close = new MenuItem({ label: nls.localize('mClose', "Close"), role: 'close', accelerator: 'Command+W', enabled: this.windowsService.getWindowCount() > 0 });
		const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsService.getWindowCount() > 0 });
E
Erich Gamma 已提交
675 676 677 678 679 680

		[
			minimize,
			close,
			__separator__(),
			bringAllToFront
B
Benjamin Pasero 已提交
681
		].forEach(item => macWindowMenu.append(item));
E
Erich Gamma 已提交
682 683
	}

J
fix npe  
Joao Moreno 已提交
684
	private toggleDevTools(): void {
B
Benjamin Pasero 已提交
685
		const w = this.windowsService.getFocusedWindow();
J
fix npe  
Joao Moreno 已提交
686 687 688 689 690
		if (w && w.win) {
			w.win.webContents.toggleDevTools();
		}
	}

B
Benjamin Pasero 已提交
691
	private setHelpMenu(helpMenu: Electron.Menu): void {
B
Benjamin Pasero 已提交
692
		const toggleDevToolsItem = new MenuItem({
B
Benjamin Pasero 已提交
693
			label: mnemonicLabel(nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")),
E
Erich Gamma 已提交
694
			accelerator: this.getAccelerator('workbench.action.toggleDevTools'),
J
fix npe  
Joao Moreno 已提交
695
			click: () => this.toggleDevTools(),
B
Benjamin Pasero 已提交
696
			enabled: (this.windowsService.getWindowCount() > 0)
E
Erich Gamma 已提交
697 698
		});

699 700 701 702 703 704 705 706
		const showAccessibilityOptions = new MenuItem({
			label: mnemonicLabel(nls.localize({ key: 'miAccessibilityOptions', comment: ['&& denotes a mnemonic'] }, "Accessibility &&Options")),
			accelerator: null,
			click: () => {
				this.windowsService.openAccessibilityOptions();
			}
		});

B
Benjamin Pasero 已提交
707
		let reportIssuesItem: Electron.MenuItem = null;
B
Benjamin Pasero 已提交
708
		if (product.reportIssueUrl) {
B
Benjamin Pasero 已提交
709 710 711 712 713 714 715 716
			const label = nls.localize({ key: 'miReportIssues', comment: ['&& denotes a mnemonic'] }, "Report &&Issues");

			if (this.windowsService.getWindowCount() > 0) {
				reportIssuesItem = this.createMenuItem(label, 'workbench.action.reportIssues');
			} else {
				reportIssuesItem = new MenuItem({ label: mnemonicLabel(label), click: () => this.openUrl(product.reportIssueUrl, 'openReportIssues') });
			}
		}
J
Joao Moreno 已提交
717

I
isidor 已提交
718
		const keyboardShortcutsUrl = platform.isLinux ? product.keyboardShortcutsUrlLinux : platform.isMacintosh ? product.keyboardShortcutsUrlMac : product.keyboardShortcutsUrlWin;
E
Erich Gamma 已提交
719
		arrays.coalesce([
B
Benjamin Pasero 已提交
720 721 722
			product.documentationUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation")), click: () => this.openUrl(product.documentationUrl, 'openDocumentationUrl') }) : null,
			product.releaseNotesUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes")), click: () => this.windowsService.sendToFocused('vscode:runAction', 'update.showCurrentReleaseNotes') }) : null,
			(product.documentationUrl || product.releaseNotesUrl) ? __separator__() : null,
I
isidor 已提交
723 724
			keyboardShortcutsUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference")), click: () => this.openUrl(keyboardShortcutsUrl, 'openKeyboardShortcutsUrl') }) : null,
			product.introductoryVideosUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos")), click: () => this.openUrl(product.introductoryVideosUrl, 'openIntroductoryVideosUrl') }) : null,
I
isidor 已提交
725
			(product.introductoryVideosUrl || keyboardShortcutsUrl) ? __separator__() : null,
B
Benjamin Pasero 已提交
726 727
			product.twitterUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")), click: () => this.openUrl(product.twitterUrl, 'openTwitterUrl') }) : null,
			product.requestFeatureUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests")), click: () => this.openUrl(product.requestFeatureUrl, 'openUserVoiceUrl') }) : null,
B
Benjamin Pasero 已提交
728
			reportIssuesItem,
B
Benjamin Pasero 已提交
729 730
			(product.twitterUrl || product.requestFeatureUrl || product.reportIssueUrl) ? __separator__() : null,
			product.licenseUrl ? new MenuItem({
I
isidor 已提交
731
				label: mnemonicLabel(nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License")), click: () => {
B
Benjamin Pasero 已提交
732
					if (platform.language) {
B
Benjamin Pasero 已提交
733 734
						const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
						this.openUrl(`${product.licenseUrl}${queryArgChar}lang=${platform.language}`, 'openLicenseUrl');
B
Benjamin Pasero 已提交
735
					} else {
B
Benjamin Pasero 已提交
736
						this.openUrl(product.licenseUrl, 'openLicenseUrl');
B
Benjamin Pasero 已提交
737
					}
738
				}
B
Benjamin Pasero 已提交
739
			}) : null,
B
Benjamin Pasero 已提交
740
			product.privacyStatementUrl ? new MenuItem({
741 742
				label: mnemonicLabel(nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")), click: () => {
					if (platform.language) {
B
Benjamin Pasero 已提交
743 744
						const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
						this.openUrl(`${product.privacyStatementUrl}${queryArgChar}lang=${platform.language}`, 'openPrivacyStatement');
745
					} else {
B
Benjamin Pasero 已提交
746
						this.openUrl(product.privacyStatementUrl, 'openPrivacyStatement');
747 748 749
					}
				}
			}) : null,
B
Benjamin Pasero 已提交
750
			(product.licenseUrl || product.privacyStatementUrl) ? __separator__() : null,
E
Erich Gamma 已提交
751
			toggleDevToolsItem,
B
Benjamin Pasero 已提交
752
			platform.isWindows && product.quality !== 'stable' ? showAccessibilityOptions : null
B
Benjamin Pasero 已提交
753
		]).forEach(item => helpMenu.append(item));
E
Erich Gamma 已提交
754 755 756 757 758 759 760 761 762

		if (!platform.isMacintosh) {
			const updateMenuItems = this.getUpdateMenuItems();
			if (updateMenuItems.length) {
				helpMenu.append(__separator__());
				updateMenuItems.forEach(i => helpMenu.append(i));
			}

			helpMenu.append(__separator__());
J
fix npe  
Joao Moreno 已提交
763
			helpMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")), click: () => this.openAboutDialog() }));
E
Erich Gamma 已提交
764 765 766
		}
	}

B
Benjamin Pasero 已提交
767
	private getUpdateMenuItems(): Electron.MenuItem[] {
B
Benjamin Pasero 已提交
768
		switch (this.updateService.state) {
J
Joao Moreno 已提交
769
			case UpdateState.Uninitialized:
E
Erich Gamma 已提交
770 771
				return [];

J
Joao Moreno 已提交
772
			case UpdateState.UpdateDownloaded:
B
Benjamin Pasero 已提交
773
				const update = this.updateService.availableUpdate;
B
Benjamin Pasero 已提交
774 775
				return [new MenuItem({
					label: nls.localize('miRestartToUpdate', "Restart To Update..."), click: () => {
J
fix npe  
Joao Moreno 已提交
776
						this.reportMenuActionTelemetry('RestartToUpdate');
B
Benjamin Pasero 已提交
777 778 779
						update.quitAndUpdate();
					}
				})];
E
Erich Gamma 已提交
780

J
Joao Moreno 已提交
781
			case UpdateState.CheckingForUpdate:
E
Erich Gamma 已提交
782 783
				return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })];

J
Joao Moreno 已提交
784
			case UpdateState.UpdateAvailable:
J
Joao Moreno 已提交
785
				if (platform.isLinux) {
B
Benjamin Pasero 已提交
786
					const update = this.updateService.availableUpdate;
J
Joao Moreno 已提交
787
					return [new MenuItem({
J
Joao Moreno 已提交
788
						label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
J
Joao Moreno 已提交
789 790 791 792 793
							update.quitAndUpdate();
						}
					})];
				}

B
Benjamin Pasero 已提交
794
				const updateAvailableLabel = platform.isWindows
E
Erich Gamma 已提交
795 796 797 798 799 800
					? nls.localize('miDownloadingUpdate', "Downloading Update...")
					: nls.localize('miInstallingUpdate', "Installing Update...");

				return [new MenuItem({ label: updateAvailableLabel, enabled: false })];

			default:
B
Benjamin Pasero 已提交
801
				const result = [new MenuItem({
B
Benjamin Pasero 已提交
802
					label: nls.localize('miCheckForUpdates', "Check For Updates..."), click: () => setTimeout(() => {
J
fix npe  
Joao Moreno 已提交
803
						this.reportMenuActionTelemetry('CheckForUpdate');
B
Benjamin Pasero 已提交
804
						this.updateService.checkForUpdates(true);
B
Benjamin Pasero 已提交
805 806
					}, 0)
				})];
E
Erich Gamma 已提交
807 808 809 810 811

				return result;
		}
	}

B
Benjamin Pasero 已提交
812 813 814
	private createMenuItem(label: string, actionId: string, enabled?: boolean, checked?: boolean): Electron.MenuItem;
	private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem;
	private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem {
B
Benjamin Pasero 已提交
815 816 817
		const label = mnemonicLabel(arg1);
		const click: () => void = (typeof arg2 === 'function') ? arg2 : () => this.windowsService.sendToFocused('vscode:runAction', arg2);
		const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsService.getWindowCount() > 0;
B
Benjamin Pasero 已提交
818
		const checked = typeof arg4 === 'boolean' ? arg4 : false;
E
Erich Gamma 已提交
819

B
Benjamin Pasero 已提交
820
		let actionId: string;
E
Erich Gamma 已提交
821 822 823 824
		if (typeof arg2 === 'string') {
			actionId = arg2;
		}

B
Benjamin Pasero 已提交
825
		const options: Electron.MenuItemOptions = {
B
Benjamin Pasero 已提交
826
			label,
E
Erich Gamma 已提交
827
			accelerator: this.getAccelerator(actionId),
B
Benjamin Pasero 已提交
828 829
			click,
			enabled
E
Erich Gamma 已提交
830 831
		};

B
Benjamin Pasero 已提交
832 833 834 835 836
		if (checked) {
			options['type'] = 'checkbox';
			options['checked'] = checked;
		}

E
Erich Gamma 已提交
837 838 839
		return new MenuItem(options);
	}

840 841 842 843
	private createDevToolsAwareMenuItem(label: string, actionId: string, devToolsFocusedFn: (contents: Electron.WebContents) => void): Electron.MenuItem {
		return new MenuItem({
			label: mnemonicLabel(label),
			accelerator: this.getAccelerator(actionId),
B
Benjamin Pasero 已提交
844
			enabled: this.windowsService.getWindowCount() > 0,
845
			click: () => {
B
Benjamin Pasero 已提交
846
				const windowInFocus = this.windowsService.getFocusedWindow();
847 848 849 850
				if (!windowInFocus) {
					return;
				}

B
Benjamin Pasero 已提交
851 852
				if (windowInFocus.win.webContents.isDevToolsFocused()) {
					devToolsFocusedFn(windowInFocus.win.webContents.devToolsWebContents);
853
				} else {
B
Benjamin Pasero 已提交
854
					this.windowsService.sendToFocused('vscode:runAction', actionId);
855 856 857 858 859
				}
			}
		});
	}

B
Benjamin Pasero 已提交
860
	private getAccelerator(actionId: string): string {
E
Erich Gamma 已提交
861
		if (actionId) {
B
Benjamin Pasero 已提交
862
			const resolvedKeybinding = this.mapResolvedKeybindingToActionId[actionId];
E
Erich Gamma 已提交
863 864 865 866 867 868 869 870
			if (resolvedKeybinding) {
				return resolvedKeybinding; // keybinding is fully resolved
			}

			if (!this.keybindingsResolved) {
				this.actionIdKeybindingRequests.push(actionId); // keybinding needs to be resolved
			}

B
Benjamin Pasero 已提交
871
			const lastKnownKeybinding = this.mapLastKnownKeybindingToActionId[actionId];
E
Erich Gamma 已提交
872 873 874 875

			return lastKnownKeybinding; // return the last known keybining (chance of mismatch is very low unless it changed)
		}

B
Benjamin Pasero 已提交
876
		return void (0);
E
Erich Gamma 已提交
877 878
	}

J
fix npe  
Joao Moreno 已提交
879
	private openAboutDialog(): void {
B
Benjamin Pasero 已提交
880
		const lastActiveWindow = this.windowsService.getFocusedWindow() || this.windowsService.getLastActiveWindow();
J
fix npe  
Joao Moreno 已提交
881 882

		dialog.showMessageBox(lastActiveWindow && lastActiveWindow.win, {
B
Benjamin Pasero 已提交
883
			title: product.nameLong,
J
fix npe  
Joao Moreno 已提交
884
			type: 'info',
B
Benjamin Pasero 已提交
885
			message: product.nameLong,
J
fix npe  
Joao Moreno 已提交
886 887 888
			detail: nls.localize('aboutDetail',
				"\nVersion {0}\nCommit {1}\nDate {2}\nShell {3}\nRenderer {4}\nNode {5}",
				app.getVersion(),
B
Benjamin Pasero 已提交
889 890
				product.commit || 'Unknown',
				product.date || 'Unknown',
J
fix npe  
Joao Moreno 已提交
891 892 893 894 895 896
				process.versions['electron'],
				process.versions['chrome'],
				process.versions['node']
			),
			buttons: [nls.localize('okButton', "OK")],
			noLink: true
B
Benjamin Pasero 已提交
897
		}, result => null);
J
fix npe  
Joao Moreno 已提交
898 899 900

		this.reportMenuActionTelemetry('showAboutDialog');
	}
E
Erich Gamma 已提交
901

J
fix npe  
Joao Moreno 已提交
902 903 904
	private openUrl(url: string, id: string): void {
		shell.openExternal(url);
		this.reportMenuActionTelemetry(id);
E
Erich Gamma 已提交
905 906
	}

J
fix npe  
Joao Moreno 已提交
907
	private reportMenuActionTelemetry(id: string): void {
B
Benjamin Pasero 已提交
908
		this.windowsService.sendToFocused('vscode:telemetry', { eventName: 'workbenchActionExecuted', data: { id, from: 'menu' } });
J
fix npe  
Joao Moreno 已提交
909
	}
E
Erich Gamma 已提交
910 911
}

B
Benjamin Pasero 已提交
912
function __separator__(): Electron.MenuItem {
E
Erich Gamma 已提交
913 914 915 916 917
	return new MenuItem({ type: 'separator' });
}

function mnemonicLabel(label: string): string {
	if (platform.isMacintosh) {
918
		return label.replace(/\(&&\w\)|&&/g, ''); // no mnemonic support on mac
E
Erich Gamma 已提交
919 920 921 922
	}

	return label.replace(/&&/g, '&');
}
923 924 925 926 927 928 929

function unMnemonicLabel(label: string): string {
	if (platform.isMacintosh) {
		return label; // no mnemonic support on mac
	}

	return label.replace(/&/g, '&&');
930
}