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

6
import * as nls from 'vs/nls';
7
import { URI } from 'vs/base/common/uri';
8
import * as errors from 'vs/base/common/errors';
R
Robo 已提交
9
import { equals } from 'vs/base/common/objects';
10
import * as DOM from 'vs/base/browser/dom';
11
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
12
import { IAction } from 'vs/base/common/actions';
S
SteVen Batten 已提交
13
import { IFileService } from 'vs/platform/files/common/files';
14
import { toResource, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor';
15
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
R
Robo 已提交
16
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
17 18
import { IWindowSettings, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest } from 'vs/platform/windows/common/windows';
import { IRunActionInWindowRequest, IRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/windows/node/window';
B
Benjamin Pasero 已提交
19
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
20
import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService';
21 22
import { applyZoom } from 'vs/platform/windows/electron-sandbox/window';
import { setFullscreen, getZoomLevel } from 'vs/base/browser/browser';
23
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
24
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
P
Peng Lyu 已提交
25
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService';
R
Robo 已提交
26
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
B
Benjamin Pasero 已提交
27
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
28
import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
29
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
30
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
31
import { RunOnceScheduler } from 'vs/base/common/async';
B
Benjamin Pasero 已提交
32
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
33
import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
34
import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
35
import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity';
R
Robo 已提交
36 37
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { IProductService } from 'vs/platform/product/common/productService';
38
import { INotificationService } from 'vs/platform/notification/common/notification';
39
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
40
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
41
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
42
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
B
Benjamin Pasero 已提交
43
import { coalesce } from 'vs/base/common/arrays';
44
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
45 46 47 48
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenubarControl } from '../browser/parts/titlebar/menubarControl';
import { ILabelService } from 'vs/platform/label/common/label';
import { IUpdateService } from 'vs/platform/update/common/update';
R
Robo 已提交
49
import { IStorageService } from 'vs/platform/storage/common/storage';
50
import { IPreferencesService } from '../services/preferences/common/preferences';
B
Benjamin Pasero 已提交
51 52
import { IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/common/menubar';
import { IMenubarService } from 'vs/platform/menubar/electron-sandbox/menubar';
B
Benjamin Pasero 已提交
53
import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types';
M
Matt Bierner 已提交
54
import { IOpenerService, OpenOptions } from 'vs/platform/opener/common/opener';
B
Benjamin Pasero 已提交
55
import { Schemas } from 'vs/base/common/network';
56
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
57 58
import { posix, dirname } from 'vs/base/common/path';
import { getBaseLabel } from 'vs/base/common/labels';
59
import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/remote/common/tunnel';
60
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
61
import { IHostService } from 'vs/workbench/services/host/browser/host';
62 63
import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
64
import { Event } from 'vs/base/common/event';
65
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
66
import { clearAllFontInfos } from 'vs/editor/browser/config/configuration';
67
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
A
Alex Ross 已提交
68
import { IAddressProvider, IAddress } from 'vs/platform/remote/common/remoteAgentConnection';
S
SteVen Batten 已提交
69

70
export class NativeWindow extends Disposable {
71

B
Benjamin Pasero 已提交
72
	private touchBarMenu: IMenu | undefined;
73
	private readonly touchBarDisposables = this._register(new DisposableStore());
B
Benjamin Pasero 已提交
74
	private lastInstalledTouchedBar: ICommandAction[][] | undefined;
75

76
	private readonly customTitleContextMenuDisposable = this._register(new DisposableStore());
77

B
Benjamin Pasero 已提交
78
	private previousConfiguredZoomLevel: number | undefined;
79

80 81
	private readonly addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100));
	private pendingFoldersToAdd: URI[] = [];
82

B
Benjamin Pasero 已提交
83
	private readonly closeEmptyWindowScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50));
84 85

	private isDocumentedEdited = false;
86

E
Erich Gamma 已提交
87
	constructor(
88
		@IEditorService private readonly editorService: IEditorService,
89
		@IConfigurationService private readonly configurationService: IConfigurationService,
90
		@ITitleService private readonly titleService: ITitleService,
91
		@IWorkbenchThemeService protected themeService: IWorkbenchThemeService,
92 93 94 95 96 97 98 99
		@INotificationService private readonly notificationService: INotificationService,
		@ICommandService private readonly commandService: ICommandService,
		@IKeybindingService private readonly keybindingService: IKeybindingService,
		@ITelemetryService private readonly telemetryService: ITelemetryService,
		@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
		@IFileService private readonly fileService: IFileService,
		@IMenuService private readonly menuService: IMenuService,
		@ILifecycleService private readonly lifecycleService: ILifecycleService,
100
		@IIntegrityService private readonly integrityService: IIntegrityService,
101
		@IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService,
102
		@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
B
Benjamin Pasero 已提交
103
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
B
Benjamin Pasero 已提交
104
		@IInstantiationService private readonly instantiationService: IInstantiationService,
105
		@IOpenerService private readonly openerService: IOpenerService,
106 107
		@IElectronService private readonly electronService: IElectronService,
		@ITunnelService private readonly tunnelService: ITunnelService,
108
		@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
109
		@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
R
Robo 已提交
110
		@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
111
		@IProductService private readonly productService: IProductService,
R
Robo 已提交
112
		@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService
E
Erich Gamma 已提交
113
	) {
114
		super();
115

E
Erich Gamma 已提交
116
		this.registerListeners();
117
		this.create();
E
Erich Gamma 已提交
118 119 120 121
	}

	private registerListeners(): void {

122
		// React to editor input changes
B
Benjamin Pasero 已提交
123
		this._register(this.editorService.onDidActiveEditorChange(() => this.updateTouchbarMenu()));
E
Erich Gamma 已提交
124

125
		// prevent opening a real URL inside the shell
126 127 128 129
		[DOM.EventType.DRAG_OVER, DOM.EventType.DROP].forEach(event => {
			window.document.body.addEventListener(event, (e: DragEvent) => {
				DOM.EventHelper.stop(e);
			});
E
Erich Gamma 已提交
130 131
		});

132
		// Support runAction event
133
		ipcRenderer.on('vscode:runAction', async (event: unknown, request: IRunActionInWindowRequest) => {
134
			const args: unknown[] = request.args || [];
135 136 137 138

			// If we run an action from the touchbar, we fill in the currently active resource
			// as payload because the touch bar items are context aware depending on the editor
			if (request.from === 'touchbar') {
B
Benjamin Pasero 已提交
139
				const activeEditor = this.editorService.activeEditor;
140
				if (activeEditor) {
B
Benjamin Pasero 已提交
141
					const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY });
142 143 144 145 146
					if (resource) {
						args.push(resource);
					}
				}
			} else {
B
Benjamin Pasero 已提交
147
				args.push({ from: request.from });
148 149
			}

150 151 152
			try {
				await this.commandService.executeCommand(request.id, ...args);

153 154 155 156 157
				type CommandExecutedClassifcation = {
					id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
					from: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
				};
				this.telemetryService.publicLog2<{ id: String, from: String }, CommandExecutedClassifcation>('commandExecuted', { id: request.id, from: request.from });
158 159 160
			} catch (error) {
				this.notificationService.error(error);
			}
161 162
		});

163
		// Support runKeybinding event
164
		ipcRenderer.on('vscode:runKeybinding', (event: unknown, request: IRunKeybindingInWindowRequest) => {
165 166 167
			if (document.activeElement) {
				this.keybindingService.dispatchByUserSettingsLabel(request.userSettingsLabel, document.activeElement);
			}
168 169
		});

B
Benjamin Pasero 已提交
170
		// Error reporting from main
171
		ipcRenderer.on('vscode:reportError', (event: unknown, error: string) => {
172
			if (error) {
173
				errors.onUnexpectedError(JSON.parse(error));
174 175 176 177
			}
		});

		// Support openFiles event for existing and new files
178
		ipcRenderer.on('vscode:openFiles', (event: unknown, request: IOpenFileRequest) => this.onOpenFiles(request));
179

180
		// Support addFolders event if we have a workspace opened
181
		ipcRenderer.on('vscode:addFolders', (event: unknown, request: IAddFoldersRequest) => this.onAddFoldersRequest(request));
182

183
		// Message support
184
		ipcRenderer.on('vscode:showInfoMessage', (event: unknown, message: string) => {
185
			this.notificationService.info(message);
186 187
		});

188
		// Display change events
189
		ipcRenderer.on('vscode:displayChanged', () => {
190 191 192
			clearAllFontInfos();
		});

193
		// Fullscreen Events
194
		ipcRenderer.on('vscode:enterFullScreen', async () => {
195
			await this.lifecycleService.when(LifecyclePhase.Ready);
196
			setFullscreen(true);
197 198
		});

199
		ipcRenderer.on('vscode:leaveFullScreen', async () => {
200
			await this.lifecycleService.when(LifecyclePhase.Ready);
201
			setFullscreen(false);
202 203 204
		});

		// High Contrast Events
205
		ipcRenderer.on('vscode:enterHighContrast', async () => {
206
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
207
			if (windowConfig?.autoDetectHighContrast) {
208 209
				await this.lifecycleService.when(LifecyclePhase.Ready);
				this.themeService.setColorTheme(VS_HC_THEME, undefined);
210
			}
211 212
		});

213
		ipcRenderer.on('vscode:leaveHighContrast', async () => {
214
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
215
			if (windowConfig?.autoDetectHighContrast) {
216 217
				await this.lifecycleService.when(LifecyclePhase.Ready);
				this.themeService.restoreColorTheme();
218
			}
219 220
		});

A
Alex Dima 已提交
221
		// keyboard layout changed event
222
		ipcRenderer.on('vscode:keyboardLayoutChanged', () => {
223
			KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged();
A
Alex Dima 已提交
224 225
		});

I
isidor 已提交
226
		// accessibility support changed event
227
		ipcRenderer.on('vscode:accessibilitySupportChanged', (event: unknown, accessibilitySupportEnabled: boolean) => {
228
			this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled);
229 230
		});

S
Sandeep Somavarapu 已提交
231
		// Zoom level changes
232
		this.updateWindowZoomLevel();
B
Benjamin Pasero 已提交
233
		this._register(this.configurationService.onDidChangeConfiguration(e => {
S
Sandeep Somavarapu 已提交
234
			if (e.affectsConfiguration('window.zoomLevel')) {
235
				this.updateWindowZoomLevel();
236
			} else if (e.affectsConfiguration('keyboard.touchbar.enabled') || e.affectsConfiguration('keyboard.touchbar.ignored')) {
237
				this.updateTouchbarMenu();
S
Sandeep Somavarapu 已提交
238 239
			}
		}));
240

241 242 243 244
		// Listen to visible editor changes
		this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange()));

		// Listen to editor closing (if we run with --wait)
245
		const filesToWait = this.environmentService.configuration.filesToWait;
246
		if (filesToWait) {
B
Benjamin Pasero 已提交
247
			this.trackClosedWaitFiles(filesToWait.waitMarkerFileUri, coalesce(filesToWait.paths.map(path => path.fileUri)));
248
		}
249

250
		// macOS OS integration
251
		if (isMacintosh) {
252
			this._register(this.editorService.onDidActiveEditorChange(() => {
B
Benjamin Pasero 已提交
253
				const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file });
254 255

				// Represented Filename
256
				this.updateRepresentedFilename(file?.fsPath);
257 258

				// Custom title menu
259
				this.provideCustomTitleContextMenu(file?.fsPath);
260
			}));
261
		}
262 263 264

		// Maximize/Restore on doubleclick (for macOS custom title)
		if (isMacintosh && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
B
Benjamin Pasero 已提交
265
			const titlePart = assertIsDefined(this.layoutService.getContainer(Parts.TITLEBAR_PART));
266 267 268 269 270 271 272

			this._register(DOM.addDisposableListener(titlePart, DOM.EventType.DBLCLICK, e => {
				DOM.EventHelper.stop(e);

				this.electronService.handleTitleDoubleClick();
			}));
		}
273

274 275 276 277 278 279
		// Document edited: indicate for dirty working copies
		this._register(this.workingCopyService.onDidChangeDirty(workingCopy => {
			const gotDirty = workingCopy.isDirty();
			if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
				return; // do not indicate dirty of working copies that are auto saved after short delay
			}
280

281 282
			this.updateDocumentEdited(gotDirty);
		}));
283

284
		this.updateDocumentEdited();
285

286
		// Detect minimize / maximize
287
		this._register(Event.any(
288 289
			Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronService.windowId), () => true),
			Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronService.windowId), () => false)
290 291 292 293 294
		)(e => this.onDidChangeMaximized(e)));

		this.onDidChangeMaximized(this.environmentService.configuration.maximized ?? false);
	}

295 296
	private updateDocumentEdited(isDirty = this.workingCopyService.hasDirty): void {
		if ((!this.isDocumentedEdited && isDirty) || (this.isDocumentedEdited && !isDirty)) {
297
			this.isDocumentedEdited = isDirty;
298

299
			this.electronService.setDocumentEdited(isDirty);
300 301 302
		}
	}

303 304
	private onDidChangeMaximized(maximized: boolean): void {
		this.layoutService.updateWindowMaximizedState(maximized);
305 306 307 308 309 310 311
	}

	private onDidVisibleEditorsChange(): void {

		// Close when empty: check if we should close the window based on the setting
		// Overruled by: window has a workspace opened or this window is for extension development
		// or setting is disabled. Also enabled when running with --wait from the command line.
312 313
		const visibleEditorPanes = this.editorService.visibleEditorPanes;
		if (visibleEditorPanes.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) {
314
			const closeWhenEmpty = this.configurationService.getValue<boolean>('window.closeWhenEmpty');
315 316 317 318 319 320 321
			if (closeWhenEmpty || this.environmentService.args.wait) {
				this.closeEmptyWindowScheduler.schedule();
			}
		}
	}

	private onAllEditorsClosed(): void {
322 323
		const visibleEditorPanes = this.editorService.visibleEditorPanes.length;
		if (visibleEditorPanes === 0) {
324
			this.electronService.closeWindow();
325 326 327
		}
	}

328
	private updateWindowZoomLevel(): void {
329
		const windowConfig = this.configurationService.getValue<IWindowsConfiguration>();
330

331
		let configuredZoomLevel = 0;
332
		if (windowConfig.window && typeof windowConfig.window.zoomLevel === 'number') {
333
			configuredZoomLevel = windowConfig.window.zoomLevel;
334

335
			// Leave early if the configured zoom level did not change (https://github.com/Microsoft/vscode/issues/1536)
336
			if (this.previousConfiguredZoomLevel === configuredZoomLevel) {
337
				return;
338
			}
339

340
			this.previousConfiguredZoomLevel = configuredZoomLevel;
341 342
		}

343 344
		if (getZoomLevel() !== configuredZoomLevel) {
			applyZoom(configuredZoomLevel);
345 346 347
		}
	}

348 349 350 351 352
	private updateRepresentedFilename(filePath: string | undefined): void {
		this.electronService.setRepresentedFilename(filePath ? filePath : '');
	}

	private provideCustomTitleContextMenu(filePath: string | undefined): void {
353 354 355 356 357

		// Clear old menu
		this.customTitleContextMenuDisposable.clear();

		// Provide new menu if a file is opened and we are on a custom title
358
		if (!filePath || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') {
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
			return;
		}

		// Split up filepath into segments
		const segments = filePath.split(posix.sep);
		for (let i = segments.length; i > 0; i--) {
			const isFile = (i === segments.length);

			let pathOffset = i;
			if (!isFile) {
				pathOffset++; // for segments which are not the file name we want to open the folder
			}

			const path = segments.slice(0, pathOffset).join(posix.sep);

			let label: string;
			if (!isFile) {
				label = getBaseLabel(dirname(path));
			} else {
				label = getBaseLabel(path);
			}

			const commandId = `workbench.action.revealPathInFinder${i}`;
			this.customTitleContextMenuDisposable.add(CommandsRegistry.registerCommand(commandId, () => this.electronService.showItemInFolder(path)));
			this.customTitleContextMenuDisposable.add(MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { command: { id: commandId, title: label || posix.sep }, order: -i }));
		}
	}

387 388
	private create(): void {

389 390 391 392 393
		// Native menu controller
		if (isMacintosh || getTitleBarStyle(this.configurationService, this.environmentService) === 'native') {
			this._register(this.instantiationService.createInstance(NativeMenubarControl));
		}

B
Benjamin Pasero 已提交
394 395
		// Handle open calls
		this.setupOpenHandlers();
396

397 398
		// Notify main side when window ready
		this.lifecycleService.when(LifecyclePhase.Ready).then(() => this.electronService.notifyReady());
399

400 401 402 403
		// Integrity warning
		this.integrityService.isPure().then(res => this.titleService.updateProperties({ isPure: res.isPure }));

		// Root warning
404
		this.lifecycleService.when(LifecyclePhase.Restored).then(async () => {
405
			const isAdmin = await this.electronService.isAdmin();
406

407 408
			// Update title
			this.titleService.updateProperties({ isAdmin });
409

410 411
			// Show warning message (unix only)
			if (isAdmin && !isWindows) {
412
				this.notificationService.warn(nls.localize('runningAsRoot', "It is not recommended to run {0} as root user.", this.productService.nameShort));
413
			}
414
		});
415 416 417

		// Touchbar menu (if enabled)
		this.updateTouchbarMenu();
418 419
	}

B
Benjamin Pasero 已提交
420 421
	private setupOpenHandlers(): void {

422 423
		// Block window.open() calls
		window.open = function (): Window | null {
B
Benjamin Pasero 已提交
424
			throw new Error('Prevented call to window.open(). Use IOpenerService instead!');
B
Benjamin Pasero 已提交
425 426
		};

427 428 429 430 431 432 433
		// Handle external open() calls
		this.openerService.setExternalOpener({
			openExternal: async (href: string) => {
				const success = await this.electronService.openExternal(href);
				if (!success) {
					const fileCandidate = URI.parse(href);
					if (fileCandidate.scheme === Schemas.file) {
434
						// if opening failed, and this is a file, we can still try to reveal it
435
						await this.electronService.showItemInFolder(fileCandidate.fsPath);
436
					}
B
Benjamin Pasero 已提交
437 438
				}

439
				return true;
B
Benjamin Pasero 已提交
440 441
			}
		});
442

443
		// Register external URI resolver
444
		this.openerService.registerExternalUriResolver({
445
			resolveExternalUri: async (uri: URI, options?: OpenOptions) => {
B
Benjamin Pasero 已提交
446
				if (options?.allowTunneling) {
447 448
					const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri);
					if (portMappingRequest) {
A
Alex Ross 已提交
449 450 451 452 453 454 455
						const remoteAuthority = this.environmentService.configuration.remoteAuthority;
						const addressProvider: IAddressProvider | undefined = remoteAuthority ? {
							getAddress: async (): Promise<IAddress> => {
								return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority;
							}
						} : undefined;
						const tunnel = await this.tunnelService.openTunnel(addressProvider, undefined, portMappingRequest.port);
456
						if (tunnel) {
457 458 459 460
							return {
								resolved: uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }),
								dispose: () => tunnel.dispose(),
							};
461 462 463
						}
					}
				}
464
				return undefined;
465 466
			}
		});
B
Benjamin Pasero 已提交
467 468
	}

469
	private updateTouchbarMenu(): void {
470 471
		if (!isMacintosh) {
			return; // macOS only
472 473
		}

474
		// Dispose old
475
		this.touchBarDisposables.clear();
R
Rob Lourens 已提交
476
		this.touchBarMenu = undefined;
477

478
		// Create new (delayed)
B
Benjamin Pasero 已提交
479 480
		const scheduler: RunOnceScheduler = this.touchBarDisposables.add(new RunOnceScheduler(() => this.doUpdateTouchbarMenu(scheduler), 300));
		scheduler.schedule();
481 482
	}

B
Benjamin Pasero 已提交
483
	private doUpdateTouchbarMenu(scheduler: RunOnceScheduler): void {
484 485
		if (!this.touchBarMenu) {
			this.touchBarMenu = this.editorService.invokeWithinEditorContext(accessor => this.menuService.createMenu(MenuId.TouchBarContext, accessor.get(IContextKeyService)));
486
			this.touchBarDisposables.add(this.touchBarMenu);
B
Benjamin Pasero 已提交
487
			this.touchBarDisposables.add(this.touchBarMenu.onDidChange(() => scheduler.schedule()));
488 489
		}

490
		const actions: Array<MenuItemAction | Separator> = [];
491

492 493 494
		const disabled = this.configurationService.getValue<boolean>('keyboard.touchbar.enabled') === false;
		const ignoredItems = this.configurationService.getValue<string[]>('keyboard.touchbar.ignored') || [];

495
		// Fill actions into groups respecting order
496
		this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions));
497 498 499 500

		// Convert into command action multi array
		const items: ICommandAction[][] = [];
		let group: ICommandAction[] = [];
501 502 503 504 505 506 507 508
		if (!disabled) {
			for (const action of actions) {

				// Command
				if (action instanceof MenuItemAction) {
					if (ignoredItems.indexOf(action.item.id) >= 0) {
						continue; // ignored
					}
509

510
					group.push(action.item);
511 512
				}

513 514 515 516 517
				// Separator
				else if (action instanceof Separator) {
					if (group.length) {
						items.push(group);
					}
518

519
					group = [];
520 521
				}
			}
522

523 524 525
			if (group.length) {
				items.push(group);
			}
526 527 528
		}

		// Only update if the actions have changed
529
		if (!equals(this.lastInstalledTouchedBar, items)) {
530
			this.lastInstalledTouchedBar = items;
531
			this.electronService.updateTouchBar(items);
532
		}
533 534
	}

535 536 537
	private onAddFoldersRequest(request: IAddFoldersRequest): void {

		// Buffer all pending requests
B
Benjamin Pasero 已提交
538
		this.pendingFoldersToAdd.push(...request.foldersToAdd.map(folder => URI.revive(folder)));
539 540 541 542 543 544 545 546 547 548

		// Delay the adding of folders a bit to buffer in case more requests are coming
		if (!this.addFoldersScheduler.isScheduled()) {
			this.addFoldersScheduler.schedule();
		}
	}

	private doAddFolders(): void {
		const foldersToAdd: IWorkspaceFolderCreationData[] = [];

549 550
		this.pendingFoldersToAdd.forEach(folder => {
			foldersToAdd.push(({ uri: folder }));
551 552 553
		});

		this.pendingFoldersToAdd = [];
554

555
		this.workspaceEditingService.addFolders(foldersToAdd);
556 557
	}

558
	private async onOpenFiles(request: INativeOpenFileRequest): Promise<void> {
559
		const inputs: IResourceEditorInputType[] = [];
560
		const diffMode = !!(request.filesToDiff && (request.filesToDiff.length === 2));
561

562 563
		if (!diffMode && request.filesToOpenOrCreate) {
			inputs.push(...(await pathsToEditors(request.filesToOpenOrCreate, this.fileService)));
564 565
		}

566
		if (diffMode && request.filesToDiff) {
567
			inputs.push(...(await pathsToEditors(request.filesToDiff, this.fileService)));
568 569 570
		}

		if (inputs.length) {
571
			this.openResources(inputs, diffMode);
572
		}
573 574 575 576 577

		if (request.filesToWait && inputs.length) {
			// In wait mode, listen to changes to the editors and wait until the files
			// are closed that the user wants to wait for. When this happens we delete
			// the wait marker file to signal to the outside that editing is done.
B
Benjamin Pasero 已提交
578
			this.trackClosedWaitFiles(URI.revive(request.filesToWait.waitMarkerFileUri), coalesce(request.filesToWait.paths.map(p => URI.revive(p.fileUri))));
B
Benjamin Pasero 已提交
579 580 581
		}
	}

B
Benjamin Pasero 已提交
582
	private async trackClosedWaitFiles(waitMarkerFile: URI, resourcesToWaitFor: URI[]): Promise<void> {
B
Benjamin Pasero 已提交
583

B
Benjamin Pasero 已提交
584
		// Wait for the resources to be closed in the editor...
585
		await this.editorService.whenClosed(resourcesToWaitFor.map(resource => ({ resource })), { waitForSaved: true });
B
Benjamin Pasero 已提交
586

B
Benjamin Pasero 已提交
587 588
		// ...before deleting the wait marker file
		await this.fileService.del(waitMarkerFile);
589 590
	}

591
	private async openResources(resources: Array<IResourceEditorInput | IUntitledTextResourceEditorInput>, diffMode: boolean): Promise<unknown> {
592
		await this.lifecycleService.when(LifecyclePhase.Ready);
N
Nick Snyder 已提交
593

594
		// In diffMode we open 2 resources as diff
595 596
		if (diffMode && resources.length === 2 && resources[0].resource && resources[1].resource) {
			return this.editorService.openEditor({ leftResource: resources[0].resource, rightResource: resources[1].resource, options: { pinned: true } });
597
		}
598

599 600 601 602
		// For one file, just put it into the current active editor
		if (resources.length === 1) {
			return this.editorService.openEditor(resources[0]);
		}
603

604 605
		// Otherwise open all
		return this.editorService.openEditors(resources);
606
	}
J
Johannes Rieken 已提交
607
}
608 609 610 611

class NativeMenubarControl extends MenubarControl {
	constructor(
		@IMenuService menuService: IMenuService,
612
		@IWorkspacesService workspacesService: IWorkspacesService,
613 614 615 616 617 618 619 620
		@IContextKeyService contextKeyService: IContextKeyService,
		@IKeybindingService keybindingService: IKeybindingService,
		@IConfigurationService configurationService: IConfigurationService,
		@ILabelService labelService: ILabelService,
		@IUpdateService updateService: IUpdateService,
		@IStorageService storageService: IStorageService,
		@INotificationService notificationService: INotificationService,
		@IPreferencesService preferencesService: IPreferencesService,
621
		@IWorkbenchEnvironmentService protected readonly environmentService: INativeWorkbenchEnvironmentService,
622
		@IAccessibilityService accessibilityService: IAccessibilityService,
623
		@IMenubarService private readonly menubarService: IMenubarService,
624
		@IHostService hostService: IHostService,
625
		@IElectronService private readonly electronService: IElectronService
626 627 628
	) {
		super(
			menuService,
629
			workspacesService,
630 631 632 633 634 635 636 637 638
			contextKeyService,
			keybindingService,
			configurationService,
			labelService,
			updateService,
			storageService,
			notificationService,
			preferencesService,
			environmentService,
639 640 641
			accessibilityService,
			hostService
		);
642

643
		if (isMacintosh) {
644 645 646 647 648 649 650 651 652 653 654
			this.menus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService));
			this.topLevelTitles['Preferences'] = nls.localize('mPreferences', "Preferences");
		}

		for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {
			const menu = this.menus[topLevelMenuName];
			if (menu) {
				this._register(menu.onDidChange(() => this.updateMenubar()));
			}
		}

655
		(async () => {
656
			this.recentlyOpened = await this.workspacesService.getRecentlyOpened();
657 658

			this.doUpdateMenubar(true);
659
		})();
660 661 662 663 664

		this.registerListeners();
	}

	protected doUpdateMenubar(firstTime: boolean): void {
S
SteVen Batten 已提交
665 666 667 668 669
		// Since the native menubar is shared between windows (main process)
		// only allow the focused window to update the menubar
		if (!this.hostService.hasFocus) {
			return;
		}
670 671 672 673

		// Send menus to main process to be rendered by Electron
		const menubarData = { menus: {}, keybindings: {} };
		if (this.getMenubarMenus(menubarData)) {
674
			this.menubarService.updateMenubar(this.electronService.windowId, menubarData);
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708
		}
	}

	private getMenubarMenus(menubarData: IMenubarData): boolean {
		if (!menubarData) {
			return false;
		}

		menubarData.keybindings = this.getAdditionalKeybindings();
		for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {
			const menu = this.menus[topLevelMenuName];
			if (menu) {
				const menubarMenu: IMenubarMenu = { items: [] };
				this.populateMenuItems(menu, menubarMenu, menubarData.keybindings);
				if (menubarMenu.items.length === 0) {
					return false; // Menus are incomplete
				}
				menubarData.menus[topLevelMenuName] = menubarMenu;
			}
		}

		return true;
	}

	private populateMenuItems(menu: IMenu, menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding | undefined }) {
		let groups = menu.getActions();
		for (let group of groups) {
			const [, actions] = group;

			actions.forEach(menuItem => {

				if (menuItem instanceof SubmenuItemAction) {
					const submenu = { items: [] };

J
Johannes Rieken 已提交
709 710
					if (!this.menus[menuItem.item.submenu.id]) {
						const menu = this.menus[menuItem.item.submenu.id] = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService);
711
						this._register(menu.onDidChange(() => this.updateMenubar()));
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
					}

					const menuToDispose = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService);
					this.populateMenuItems(menuToDispose, submenu, keybindings);

					let menubarSubmenuItem: IMenubarMenuItemSubmenu = {
						id: menuItem.id,
						label: menuItem.label,
						submenu: submenu
					};

					menuToPopulate.items.push(menubarSubmenuItem);
					menuToDispose.dispose();
				} else {
					if (menuItem.id === 'workbench.action.openRecent') {
						const actions = this.getOpenRecentActions().map(this.transformOpenRecentAction);
						menuToPopulate.items.push(...actions);
					}

					let menubarMenuItem: IMenubarMenuItemAction = {
						id: menuItem.id,
						label: menuItem.label
					};

					if (menuItem.checked) {
						menubarMenuItem.checked = true;
					}

					if (!menuItem.enabled) {
						menubarMenuItem.enabled = false;
					}

					menubarMenuItem.label = this.calculateActionLabel(menubarMenuItem);
					keybindings[menuItem.id] = this.getMenubarKeybinding(menuItem.id);
					menuToPopulate.items.push(menubarMenuItem);
				}
			});

			menuToPopulate.items.push({ id: 'vscode.menubar.separator' });
		}

		if (menuToPopulate.items.length > 0) {
			menuToPopulate.items.pop();
		}
	}

	private transformOpenRecentAction(action: Separator | (IAction & { uri: URI })): MenubarMenuItem {
		if (action instanceof Separator) {
			return { id: 'vscode.menubar.separator' };
		}

		return {
			id: action.id,
			uri: action.uri,
			enabled: action.enabled,
			label: action.label
		};
	}

	private getAdditionalKeybindings(): { [id: string]: IMenubarKeybinding } {
		const keybindings: { [id: string]: IMenubarKeybinding } = {};
		if (isMacintosh) {
			const keybinding = this.getMenubarKeybinding('workbench.action.quit');
			if (keybinding) {
				keybindings['workbench.action.quit'] = keybinding;
			}
		}

		return keybindings;
	}

	private getMenubarKeybinding(id: string): IMenubarKeybinding | undefined {
		const binding = this.keybindingService.lookupKeybinding(id);
		if (!binding) {
			return undefined;
		}

		// first try to resolve a native accelerator
		const electronAccelerator = binding.getElectronAccelerator();
		if (electronAccelerator) {
			return { label: electronAccelerator, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) };
		}

		// we need this fallback to support keybindings that cannot show in electron menus (e.g. chords)
		const acceleratorLabel = binding.getLabel();
		if (acceleratorLabel) {
			return { label: acceleratorLabel, isNative: false, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) };
		}

		return undefined;
	}
B
Benjamin Pasero 已提交
803
}