main.ts 14.4 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 * as perf from 'vs/base/common/performance';
B
Benjamin Pasero 已提交
8 9
import { Workbench } from 'vs/workbench/electron-browser/workbench';
import { ElectronWindow } from 'vs/workbench/electron-browser/window';
10
import * as browser from 'vs/base/browser/browser';
J
Johannes Rieken 已提交
11
import { domContentLoaded } from 'vs/base/browser/dom';
12
import { onUnexpectedError } from 'vs/base/common/errors';
13 14
import * as comparer from 'vs/base/common/comparers';
import * as platform from 'vs/base/common/platform';
15
import { URI as uri } from 'vs/base/common/uri';
16
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
17 18
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
19
import { stat } from 'vs/base/node/pfs';
J
Johannes Rieken 已提交
20
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
21
import * as gracefulFs from 'graceful-fs';
22
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService';
B
Benjamin Pasero 已提交
23
import { IWindowConfiguration, IWindowsService } from 'vs/platform/windows/common/windows';
J
Joao Moreno 已提交
24
import { WindowsChannelClient } from 'vs/platform/windows/node/windowsIpc';
B
Benjamin Pasero 已提交
25
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
26
import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser';
27
import { webFrame } from 'electron';
J
Joao Moreno 已提交
28
import { UpdateChannelClient } from 'vs/platform/update/node/updateIpc';
29
import { IUpdateService } from 'vs/platform/update/common/update';
J
Joao Moreno 已提交
30
import { URLHandlerChannel, URLServiceChannelClient } from 'vs/platform/url/node/urlIpc';
31
import { IURLService } from 'vs/platform/url/common/url';
J
Joao Moreno 已提交
32
import { WorkspacesChannelClient } from 'vs/platform/workspaces/node/workspacesIpc';
M
Martin Aeschlimann 已提交
33
import { IWorkspacesService, ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
34
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
35
import * as fs from 'fs';
S
Sandeep Somavarapu 已提交
36
import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log';
37
import { StorageService } from 'vs/platform/storage/node/storageService';
J
Joao Moreno 已提交
38
import { IssueChannelClient } from 'vs/platform/issue/node/issueIpc';
39
import { IIssueService } from 'vs/platform/issue/common/issue';
J
Joao Moreno 已提交
40
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
J
Joao Moreno 已提交
41
import { RelayURLService } from 'vs/platform/url/common/urlService';
J
Joao Moreno 已提交
42
import { MenubarChannelClient } from 'vs/platform/menubar/node/menubarIpc';
43
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
44
import { Schemas } from 'vs/base/common/network';
45
import { sanitizeFilePath } from 'vs/base/node/extfs';
46
import { basename } from 'vs/base/common/paths.node';
47
import { createHash } from 'crypto';
48
import { IdleValue } from 'vs/base/common/async';
49
import { setGlobalLeakWarningThreshold } from 'vs/base/common/event';
B
wip  
Benjamin Pasero 已提交
50
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
B
Benjamin Pasero 已提交
51 52 53 54
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
B
Benjamin Pasero 已提交
55
import { Disposable } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
56

B
Benjamin Pasero 已提交
57
export class CodeWindow extends Disposable {
E
Erich Gamma 已提交
58

B
Benjamin Pasero 已提交
59 60
	constructor(private readonly configuration: IWindowConfiguration) {
		super();
61

B
Benjamin Pasero 已提交
62 63
		this.init();
	}
M
Martin Aeschlimann 已提交
64

B
Benjamin Pasero 已提交
65
	private init(): void {
66

B
Benjamin Pasero 已提交
67 68
		// Enable gracefulFs
		gracefulFs.gracefulify(fs);
69

B
Benjamin Pasero 已提交
70 71
		// Massage configuration file URIs
		this.reviveUris();
72

B
Benjamin Pasero 已提交
73 74
		// Setup perf
		perf.importEntries(this.configuration.perfEntries);
A
Alex Dima 已提交
75

B
Benjamin Pasero 已提交
76 77
		// Configure emitter leak warning threshold
		setGlobalLeakWarningThreshold(175);
78

B
Benjamin Pasero 已提交
79 80 81 82 83
		// Browser config
		browser.setZoomFactor(webFrame.getZoomFactor()); // Ensure others can listen to zoom level changes
		browser.setZoomLevel(webFrame.getZoomLevel(), true /* isTrusted */); // Can be trusted because we are not setting it ourselves (https://github.com/Microsoft/vscode/issues/26151)
		browser.setFullscreen(!!this.configuration.fullscreen);
		browser.setAccessibilitySupport(this.configuration.accessibilitySupport ? platform.AccessibilitySupport.Enabled : platform.AccessibilitySupport.Disabled);
E
Erich Gamma 已提交
84

B
Benjamin Pasero 已提交
85 86 87 88 89 90 91 92 93 94 95
		// Keyboard support
		KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged();

		// Setup Intl for comparers
		comparer.setFileNameComparer(new IdleValue(() => {
			const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
			return {
				collator: collator,
				collatorIsNumeric: collator.resolvedOptions().numeric
			};
		}));
M
Martin Aeschlimann 已提交
96
	}
97

B
Benjamin Pasero 已提交
98 99 100 101 102 103
	private reviveUris() {
		if (this.configuration.folderUri) {
			this.configuration.folderUri = uri.revive(this.configuration.folderUri);
		}
		if (this.configuration.workspace) {
			this.configuration.workspace = reviveWorkspaceIdentifier(this.configuration.workspace);
M
Martin Aeschlimann 已提交
104
		}
B
Benjamin Pasero 已提交
105

B
Benjamin Pasero 已提交
106 107 108 109 110 111 112 113 114 115 116
		const filesToWaitPaths = this.configuration.filesToWait && this.configuration.filesToWait.paths;
		[filesToWaitPaths, this.configuration.filesToOpen, this.configuration.filesToCreate, this.configuration.filesToDiff].forEach(paths => {
			if (Array.isArray(paths)) {
				paths.forEach(path => {
					if (path.fileUri) {
						path.fileUri = uri.revive(path.fileUri);
					}
				});
			}
		});
	}
B
Benjamin Pasero 已提交
117

B
Benjamin Pasero 已提交
118 119
	open(): Promise<void> {
		const mainProcessClient = this._register(new ElectronIPCClient(`window:${this.configuration.windowId}`));
B
Benjamin Pasero 已提交
120

B
Benjamin Pasero 已提交
121
		return this.initServices(mainProcessClient).then(services => {
B
Benjamin Pasero 已提交
122

B
Benjamin Pasero 已提交
123 124 125
			return domContentLoaded().then(() => {
				perf.mark('willStartWorkbench');

B
Benjamin Pasero 已提交
126 127 128 129
				const instantiationService = new InstantiationService(services, true);

				// Create Workbench
				const workbench: Workbench = instantiationService.createInstance(
B
Benjamin Pasero 已提交
130 131
					Workbench,
					document.body,
B
Benjamin Pasero 已提交
132 133
					this.configuration,
					services,
B
Benjamin Pasero 已提交
134 135
					mainProcessClient
				);
B
Benjamin Pasero 已提交
136 137 138 139 140 141

				// Workbench Lifecycle
				this._register(workbench.onShutdown(() => this.dispose()));
				this._register(workbench.onWillShutdown(event => event.join((services.get(IStorageService) as StorageService).close())));

				// Startup
B
Benjamin Pasero 已提交
142 143 144
				workbench.startup();

				// Window
B
Benjamin Pasero 已提交
145
				this._register(instantiationService.createInstance(ElectronWindow));
B
Benjamin Pasero 已提交
146 147 148 149 150

				// Inform user about loading issues from the loader
				(<any>self).require.config({
					onError: err => {
						if (err.errorCode === 'load') {
151
							onUnexpectedError(new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err))));
152
						}
B
Benjamin Pasero 已提交
153
					}
154
				});
155 156
			});
		});
B
Benjamin Pasero 已提交
157
	}
158

B
Benjamin Pasero 已提交
159 160
	private initServices(mainProcessClient: ElectronIPCClient): Promise<ServiceCollection> {
		const serviceCollection = new ServiceCollection();
161

B
Benjamin Pasero 已提交
162 163 164
		// Windows Channel
		const windowsChannel = mainProcessClient.getChannel('windows');
		serviceCollection.set(IWindowsService, new WindowsChannelClient(windowsChannel));
165

B
Benjamin Pasero 已提交
166 167 168
		// Update Channel
		const updateChannel = mainProcessClient.getChannel('update');
		serviceCollection.set(IUpdateService, new SyncDescriptor(UpdateChannelClient, [updateChannel]));
169

B
Benjamin Pasero 已提交
170 171 172 173 174
		// URL Channel
		const urlChannel = mainProcessClient.getChannel('url');
		const mainUrlService = new URLServiceChannelClient(urlChannel);
		const urlService = new RelayURLService(mainUrlService);
		serviceCollection.set(IURLService, urlService);
175

B
Benjamin Pasero 已提交
176 177 178
		// URLHandler Channel
		const urlHandlerChannel = new URLHandlerChannel(urlService);
		mainProcessClient.registerChannel('urlHandler', urlHandlerChannel);
179

B
Benjamin Pasero 已提交
180 181 182 183 184 185 186
		// Issue Channel
		const issueChannel = mainProcessClient.getChannel('issue');
		serviceCollection.set(IIssueService, new SyncDescriptor(IssueChannelClient, [issueChannel]));

		// Menubar Channel
		const menubarChannel = mainProcessClient.getChannel('menubar');
		serviceCollection.set(IMenubarService, new SyncDescriptor(MenubarChannelClient, [menubarChannel]));
187

B
Benjamin Pasero 已提交
188 189 190
		// Workspaces Channel
		const workspacesChannel = mainProcessClient.getChannel('workspaces');
		serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel));
191

B
Benjamin Pasero 已提交
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
		// Environment
		const environmentService = new EnvironmentService(this.configuration, this.configuration.execPath);
		serviceCollection.set(IEnvironmentService, environmentService);

		// Log
		const logService = this._register(this.createLogService(mainProcessClient, environmentService));
		serviceCollection.set(ILogService, logService);

		// Resolve a workspace payload that we can get the workspace ID from
		return this.resolveWorkspaceInitializationPayload(environmentService).then(payload => {

			return Promise.all([

				// Create and initialize workspace/configuration service
				this.createWorkspaceService(payload, environmentService, logService),

				// Create and initialize storage service
				this.createStorageService(payload, environmentService, logService, mainProcessClient)
			]).then(services => {
				serviceCollection.set(IWorkspaceContextService, services[0]);
				serviceCollection.set(IConfigurationService, services[0]);
				serviceCollection.set(IStorageService, services[1]);

				return serviceCollection;
			});
		});
218
	}
219

B
Benjamin Pasero 已提交
220 221 222 223 224
	private resolveWorkspaceInitializationPayload(environmentService: EnvironmentService): Promise<IWorkspaceInitializationPayload> {

		// Multi-root workspace
		if (this.configuration.workspace) {
			return Promise.resolve(this.configuration.workspace as IMultiFolderWorkspaceInitializationPayload);
225 226
		}

B
Benjamin Pasero 已提交
227 228 229 230 231
		// Single-folder workspace
		let workspaceInitializationPayload: Promise<IWorkspaceInitializationPayload | undefined> = Promise.resolve(undefined);
		if (this.configuration.folderUri) {
			workspaceInitializationPayload = this.resolveSingleFolderWorkspaceInitializationPayload(this.configuration.folderUri);
		}
E
Erich Gamma 已提交
232

B
Benjamin Pasero 已提交
233 234 235 236 237 238 239 240 241 242 243 244
		return workspaceInitializationPayload.then(payload => {

			// Fallback to empty workspace if we have no payload yet.
			if (!payload) {
				let id: string;
				if (this.configuration.backupPath) {
					id = basename(this.configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID
				} else if (environmentService.isExtensionDevelopment) {
					id = 'ext-dev'; // extension development window never stores backups and is a singleton
				} else {
					return Promise.reject(new Error('Unexpected window configuration without backupPath'));
				}
E
Erich Gamma 已提交
245

B
Benjamin Pasero 已提交
246 247
				payload = { id } as IEmptyWorkspaceInitializationPayload;
			}
248

B
Benjamin Pasero 已提交
249 250 251
			return payload;
		});
	}
252

B
Benjamin Pasero 已提交
253
	private resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise<ISingleFolderWorkspaceInitializationPayload | undefined> {
254

B
Benjamin Pasero 已提交
255 256 257 258
		// Return early the folder is not local
		if (folderUri.scheme !== Schemas.file) {
			return Promise.resolve({ id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri });
		}
259

B
Benjamin Pasero 已提交
260 261 262 263 264 265 266 267 268 269 270 271 272
		function computeLocalDiskFolderId(folder: uri, stat: fs.Stats): string {
			let ctime: number | undefined;
			if (platform.isLinux) {
				ctime = stat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
			} else if (platform.isMacintosh) {
				ctime = stat.birthtime.getTime(); // macOS: birthtime is fine to use as is
			} else if (platform.isWindows) {
				if (typeof stat.birthtimeMs === 'number') {
					ctime = Math.floor(stat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
				} else {
					ctime = stat.birthtime.getTime();
				}
			}
273

B
Benjamin Pasero 已提交
274 275 276 277
			// we use the ctime as extra salt to the ID so that we catch the case of a folder getting
			// deleted and recreated. in that case we do not want to carry over previous state
			return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex');
		}
278

B
Benjamin Pasero 已提交
279 280 281 282 283 284 285 286 287 288
		// For local: ensure path is absolute and exists
		const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd());
		return stat(sanitizedFolderPath).then(stat => {
			const sanitizedFolderUri = uri.file(sanitizedFolderPath);
			return {
				id: computeLocalDiskFolderId(sanitizedFolderUri, stat),
				folder: sanitizedFolderUri
			} as ISingleFolderWorkspaceInitializationPayload;
		}, error => onUnexpectedError(error));
	}
289

B
Benjamin Pasero 已提交
290 291
	private createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService): Promise<WorkspaceService> {
		const workspaceService = new WorkspaceService(environmentService);
S
Sandeep Somavarapu 已提交
292

B
Benjamin Pasero 已提交
293 294 295
		return workspaceService.initialize(payload).then(() => workspaceService, error => {
			onUnexpectedError(error);
			logService.error(error);
296

B
Benjamin Pasero 已提交
297 298 299
			return workspaceService;
		});
	}
300

B
Benjamin Pasero 已提交
301 302 303
	private createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService, mainProcessClient: ElectronIPCClient): Promise<StorageService> {
		const globalStorageDatabase = new GlobalStorageDatabaseChannelClient(mainProcessClient.getChannel('storage'));
		const storageService = new StorageService(globalStorageDatabase, logService, environmentService);
304

B
Benjamin Pasero 已提交
305 306 307
		return storageService.initialize(payload).then(() => storageService, error => {
			onUnexpectedError(error);
			logService.error(error);
J
Joao Moreno 已提交
308

B
Benjamin Pasero 已提交
309 310 311
			return storageService;
		});
	}
312

B
Benjamin Pasero 已提交
313 314 315 316 317
	private createLogService(mainProcessClient: ElectronIPCClient, environmentService: IEnvironmentService): ILogService {
		const spdlogService = createSpdLogService(`renderer${this.configuration.windowId}`, this.configuration.logLevel, environmentService.logsPath);
		const consoleLogService = new ConsoleLogService(this.configuration.logLevel);
		const logService = new MultiplexLogService([consoleLogService, spdlogService]);
		const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel'));
318

B
Benjamin Pasero 已提交
319 320 321
		return new FollowerLogService(logLevelClient, logService);
	}
}
322

B
Benjamin Pasero 已提交
323 324
export function main(configuration: IWindowConfiguration): Promise<void> {
	const window = new CodeWindow(configuration);
325

B
Benjamin Pasero 已提交
326
	return window.open();
327
}