main.ts 13.9 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 'path';
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';
J
Joao Moreno 已提交
55

B
Benjamin Pasero 已提交
56
gracefulFs.gracefulify(fs); // enable gracefulFs
E
Erich Gamma 已提交
57

58
export function startup(configuration: IWindowConfiguration): Promise<void> {
59

60
	// Massage configuration file URIs
M
Martin Aeschlimann 已提交
61 62
	revive(configuration);

63
	// Setup perf
64 65
	perf.importEntries(configuration.perfEntries);

66
	// Configure emitter leak warning threshold
J
Johannes Rieken 已提交
67
	setGlobalLeakWarningThreshold(175);
68

69 70 71
	// 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)
72
	browser.setFullscreen(!!configuration.fullscreen);
73
	browser.setAccessibilitySupport(configuration.accessibilitySupport ? platform.AccessibilitySupport.Enabled : platform.AccessibilitySupport.Disabled);
74

75
	// Keyboard support
76
	KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged();
A
Alex Dima 已提交
77

78
	// Setup Intl for comparers
79 80 81 82 83 84 85
	comparer.setFileNameComparer(new IdleValue(() => {
		const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
		return {
			collator: collator,
			collatorIsNumeric: collator.resolvedOptions().numeric
		};
	}));
86

87
	// Open workbench
B
Benjamin Pasero 已提交
88
	return openWorkbench(configuration);
E
Erich Gamma 已提交
89 90
}

M
Martin Aeschlimann 已提交
91 92 93 94
function revive(workbench: IWindowConfiguration) {
	if (workbench.folderUri) {
		workbench.folderUri = uri.revive(workbench.folderUri);
	}
M
Martin Aeschlimann 已提交
95 96 97
	if (workbench.workspace) {
		workbench.workspace = reviveWorkspaceIdentifier(workbench.workspace);
	}
98

M
Martin Aeschlimann 已提交
99 100 101 102 103 104 105 106
	const filesToWaitPaths = workbench.filesToWait && workbench.filesToWait.paths;
	[filesToWaitPaths, workbench.filesToOpen, workbench.filesToCreate, workbench.filesToDiff].forEach(paths => {
		if (Array.isArray(paths)) {
			paths.forEach(path => {
				if (path.fileUri) {
					path.fileUri = uri.revive(path.fileUri);
				}
			});
M
Martin Aeschlimann 已提交
107
		}
M
Martin Aeschlimann 已提交
108
	});
M
Martin Aeschlimann 已提交
109 110
}

111
function openWorkbench(configuration: IWindowConfiguration): Promise<void> {
J
Joao Moreno 已提交
112
	const mainProcessClient = new ElectronIPCClient(`window:${configuration.windowId}`);
B
wip  
Benjamin Pasero 已提交
113
	const mainServices = createMainProcessServices(mainProcessClient);
114

115
	const environmentService = new EnvironmentService(configuration, configuration.execPath);
116

S
Sandeep Somavarapu 已提交
117
	const logService = createLogService(mainProcessClient, configuration, environmentService);
J
Joao Moreno 已提交
118
	logService.trace('openWorkbench configuration', JSON.stringify(configuration));
119

120 121 122
	// Resolve a workspace payload that we can get the workspace ID from
	return createWorkspaceInitializationPayload(configuration, environmentService).then(payload => {

B
Benjamin Pasero 已提交
123 124 125 126 127 128
		return Promise.all([

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

			// Create and initialize storage service
B
wip  
Benjamin Pasero 已提交
129
			createStorageService(payload, environmentService, logService, mainProcessClient)
B
Benjamin Pasero 已提交
130 131
		]).then(services => {
			const workspaceService = services[0];
132
			const storageService = services[1];
B
Benjamin Pasero 已提交
133

B
Benjamin Pasero 已提交
134 135 136 137 138 139 140 141 142 143 144 145 146 147
			// Core services
			const serviceCollection = new ServiceCollection();
			serviceCollection.set(IWorkspaceContextService, workspaceService);
			serviceCollection.set(IConfigurationService, workspaceService);
			serviceCollection.set(IEnvironmentService, environmentService);
			serviceCollection.set(ILogService, logService);
			serviceCollection.set(IStorageService, storageService);

			mainServices.forEach((serviceIdentifier, serviceInstance) => {
				serviceCollection.set(serviceIdentifier, serviceInstance);
			});

			const instantiationService = new InstantiationService(serviceCollection, true);

B
Benjamin Pasero 已提交
148 149 150
			return domContentLoaded().then(() => {
				perf.mark('willStartWorkbench');

B
Benjamin Pasero 已提交
151 152 153 154 155 156 157 158 159 160 161 162
				// Startup Workbench
				const workbench = instantiationService.createInstance(
					Workbench,
					document.body,
					configuration,
					serviceCollection,
					mainProcessClient
				);
				workbench.startup();

				// Window
				workbench.getInstantiationService().createInstance(ElectronWindow);
B
Benjamin Pasero 已提交
163 164 165 166 167

				// Inform user about loading issues from the loader
				(<any>self).require.config({
					onError: err => {
						if (err.errorCode === 'load') {
168
							onUnexpectedError(new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err))));
169
						}
B
Benjamin Pasero 已提交
170
					}
171
				});
172 173 174 175 176
			});
		});
	});
}

177
function createWorkspaceInitializationPayload(configuration: IWindowConfiguration, environmentService: EnvironmentService): Promise<IWorkspaceInitializationPayload> {
178 179 180

	// Multi-root workspace
	if (configuration.workspace) {
181
		return Promise.resolve(configuration.workspace as IMultiFolderWorkspaceInitializationPayload);
182 183 184
	}

	// Single-folder workspace
J
Johannes Rieken 已提交
185
	let workspaceInitializationPayload: Promise<IWorkspaceInitializationPayload | undefined> = Promise.resolve(undefined);
186
	if (configuration.folderUri) {
187
		workspaceInitializationPayload = resolveSingleFolderWorkspaceInitializationPayload(configuration.folderUri);
188 189 190 191
	}

	return workspaceInitializationPayload.then(payload => {

192
		// Fallback to empty workspace if we have no payload yet.
193
		if (!payload) {
194 195 196
			let id: string;
			if (configuration.backupPath) {
				id = basename(configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID
197
			} else if (environmentService.isExtensionDevelopment) {
198
				id = 'ext-dev'; // extension development window never stores backups and is a singleton
199
			} else {
J
Johannes Rieken 已提交
200
				return Promise.reject<any>(new Error('Unexpected window configuration without backupPath'));
201 202
			}

203
			payload = { id } as IEmptyWorkspaceInitializationPayload;
204 205
		}

206
		return payload;
207
	});
208 209
}

210
function resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise<ISingleFolderWorkspaceInitializationPayload | undefined> {
211

212 213 214 215
	// 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 });
	}
216

217
	function computeLocalDiskFolderId(folder: uri, stat: fs.Stats): string {
218
		let ctime: number | undefined;
219 220 221 222 223 224 225 226 227 228
		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();
			}
229 230
		}

231 232 233
		// 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');
E
Erich Gamma 已提交
234 235
	}

236
	// For local: ensure path is absolute and exists
237
	const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd());
238
	return stat(sanitizedFolderPath).then(stat => {
239
		const sanitizedFolderUri = uri.file(sanitizedFolderPath);
240
		return {
241
			id: computeLocalDiskFolderId(sanitizedFolderUri, stat),
242
			folder: sanitizedFolderUri
243
		} as ISingleFolderWorkspaceInitializationPayload;
244
	}, error => onUnexpectedError(error));
E
Erich Gamma 已提交
245 246
}

247
function createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService): Promise<WorkspaceService> {
248 249
	const workspaceService = new WorkspaceService(environmentService);

250
	return workspaceService.initialize(payload).then(() => workspaceService, error => {
251 252
		onUnexpectedError(error);
		logService.error(error);
253 254 255

		return workspaceService;
	});
256 257
}

J
Johannes Rieken 已提交
258
function createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService, mainProcessClient: ElectronIPCClient): Promise<StorageService> {
259 260
	const globalStorageDatabase = new GlobalStorageDatabaseChannelClient(mainProcessClient.getChannel('storage'));
	const storageService = new StorageService(globalStorageDatabase, logService, environmentService);
261

262 263 264
	return storageService.initialize(payload).then(() => storageService, error => {
		onUnexpectedError(error);
		logService.error(error);
265

266
		return storageService;
267
	});
268 269
}

S
Sandeep Somavarapu 已提交
270
function createLogService(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration, environmentService: IEnvironmentService): ILogService {
S
Sandeep Somavarapu 已提交
271 272
	const spdlogService = createSpdLogService(`renderer${configuration.windowId}`, configuration.logLevel, environmentService.logsPath);
	const consoleLogService = new ConsoleLogService(configuration.logLevel);
S
Sandeep Somavarapu 已提交
273
	const logService = new MultiplexLogService([consoleLogService, spdlogService]);
274
	const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel'));
275

S
Sandeep Somavarapu 已提交
276 277 278
	return new FollowerLogService(logLevelClient, logService);
}

B
wip  
Benjamin Pasero 已提交
279
function createMainProcessServices(mainProcessClient: ElectronIPCClient): ServiceCollection {
280 281 282 283 284 285
	const serviceCollection = new ServiceCollection();

	const windowsChannel = mainProcessClient.getChannel('windows');
	serviceCollection.set(IWindowsService, new WindowsChannelClient(windowsChannel));

	const updateChannel = mainProcessClient.getChannel('update');
286
	serviceCollection.set(IUpdateService, new SyncDescriptor(UpdateChannelClient, [updateChannel]));
287

J
Joao Moreno 已提交
288 289 290 291
	const urlChannel = mainProcessClient.getChannel('url');
	const mainUrlService = new URLServiceChannelClient(urlChannel);
	const urlService = new RelayURLService(mainUrlService);
	serviceCollection.set(IURLService, urlService);
J
Joao Moreno 已提交
292

J
Joao Moreno 已提交
293
	const urlHandlerChannel = new URLHandlerChannel(urlService);
J
Joao Moreno 已提交
294
	mainProcessClient.registerChannel('urlHandler', urlHandlerChannel);
295

296
	const issueChannel = mainProcessClient.getChannel('issue');
297
	serviceCollection.set(IIssueService, new SyncDescriptor(IssueChannelClient, [issueChannel]));
298

299
	const menubarChannel = mainProcessClient.getChannel('menubar');
300
	serviceCollection.set(IMenubarService, new SyncDescriptor(MenubarChannelClient, [menubarChannel]));
301

302
	const workspacesChannel = mainProcessClient.getChannel('workspaces');
303
	serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel));
304 305

	return serviceCollection;
306
}