main.ts 20.2 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';
J
Johannes Rieken 已提交
8
import { WorkbenchShell } from 'vs/workbench/electron-browser/shell';
9
import * as browser from 'vs/base/browser/browser';
J
Johannes Rieken 已提交
10
import { domContentLoaded } from 'vs/base/browser/dom';
11 12 13
import * as errors from 'vs/base/common/errors';
import * as comparer from 'vs/base/common/comparers';
import * as platform from 'vs/base/common/platform';
14
import { URI as uri } from 'vs/base/common/uri';
15
import { IWorkspaceContextService, Workspace, WorkbenchState } from 'vs/platform/workspace/common/workspace';
16
import { WorkspaceService, ISingleFolderWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload } 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, exists, writeFile, readdir } 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';
25
import { IStorageLegacyService, StorageLegacyService, inMemoryLocalStorageInstance, IStorageLegacy } from 'vs/platform/storage/common/storageLegacyService';
B
Benjamin Pasero 已提交
26
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
27
import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser';
28
import { webFrame } from 'electron';
J
Joao Moreno 已提交
29
import { UpdateChannelClient } from 'vs/platform/update/node/updateIpc';
30
import { IUpdateService } from 'vs/platform/update/common/update';
J
Joao Moreno 已提交
31
import { URLHandlerChannel, URLServiceChannelClient } from 'vs/platform/url/node/urlIpc';
32
import { IURLService } from 'vs/platform/url/common/url';
J
Joao Moreno 已提交
33
import { WorkspacesChannelClient } from 'vs/platform/workspaces/node/workspacesIpc';
34
import { IWorkspacesService, ISingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
35
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
36
import * as fs from 'fs';
S
Sandeep Somavarapu 已提交
37
import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log';
38
import { StorageService, DelegatingStorageService } from 'vs/platform/storage/node/storageService';
J
Joao Moreno 已提交
39
import { IssueChannelClient } from 'vs/platform/issue/node/issueIpc';
40
import { IIssueService } from 'vs/platform/issue/common/issue';
J
Joao Moreno 已提交
41
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
J
Joao Moreno 已提交
42
import { RelayURLService } from 'vs/platform/url/common/urlService';
J
Joao Moreno 已提交
43
import { MenubarChannelClient } from 'vs/platform/menubar/node/menubarIpc';
44
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
45
import { Schemas } from 'vs/base/common/network';
46 47
import { sanitizeFilePath, mkdirp } from 'vs/base/node/extfs';
import { basename, join } from 'path';
48
import { createHash } from 'crypto';
49 50
import { parseStorage, StorageObject } from 'vs/platform/storage/common/storageLegacyMigration';
import { StorageScope } from 'vs/platform/storage/common/storage';
51
import { endsWith } from 'vs/base/common/strings';
J
Joao Moreno 已提交
52

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

55
export function startup(configuration: IWindowConfiguration): Promise<void> {
56

57
	// Massage configuration file URIs
M
Martin Aeschlimann 已提交
58 59
	revive(configuration);

60
	// Setup perf
61 62
	perf.importEntries(configuration.perfEntries);

63 64 65
	// 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)
66
	browser.setFullscreen(!!configuration.fullscreen);
67
	browser.setAccessibilitySupport(configuration.accessibilitySupport ? platform.AccessibilitySupport.Enabled : platform.AccessibilitySupport.Disabled);
68

69
	// Keyboard support
70
	KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged();
A
Alex Dima 已提交
71

72
	// Setup Intl for comparers
73 74
	comparer.setFileNameComparer(new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }));

75
	// Open workbench
B
Benjamin Pasero 已提交
76
	return openWorkbench(configuration);
E
Erich Gamma 已提交
77 78
}

M
Martin Aeschlimann 已提交
79 80 81 82
function revive(workbench: IWindowConfiguration) {
	if (workbench.folderUri) {
		workbench.folderUri = uri.revive(workbench.folderUri);
	}
83

M
Martin Aeschlimann 已提交
84 85 86 87 88 89 90 91
	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 已提交
92
		}
M
Martin Aeschlimann 已提交
93
	});
M
Martin Aeschlimann 已提交
94 95
}

96
function openWorkbench(configuration: IWindowConfiguration): Promise<void> {
J
Joao Moreno 已提交
97
	const mainProcessClient = new ElectronIPCClient(`window:${configuration.windowId}`);
98
	const mainServices = createMainProcessServices(mainProcessClient, configuration);
99

100
	const environmentService = new EnvironmentService(configuration, configuration.execPath);
101

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

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

108
		// Prepare the workspace storage folder
109
		return prepareWorkspaceStorageFolder(payload, environmentService).then(workspaceStoragePath => {
110 111
			return Promise.all([

112
				// Create and initialize workspace/configuration service
113 114
				createWorkspaceService(payload, environmentService),

115
				// Create and initialize storage service
B
Benjamin Pasero 已提交
116
				createStorageService(workspaceStoragePath, payload, environmentService, logService)
117 118
			]).then(services => {
				const workspaceService = services[0];
119
				const storageService = new DelegatingStorageService(services[1], createStorageLegacyService(workspaceService, environmentService), logService);
120 121 122 123 124 125 126 127 128 129 130 131 132

				return domContentLoaded().then(() => {
					perf.mark('willStartWorkbench');

					// Create Shell
					const shell = new WorkbenchShell(document.body, {
						contextService: workspaceService,
						configurationService: workspaceService,
						environmentService,
						logService,
						storageService
					}, mainServices, mainProcessClient, configuration);

133 134 135 136 137
					// Store meta file in workspace storage after workbench is running
					shell.onRunning(() => {
						ensureWorkspaceStorageFolderMeta(workspaceStoragePath, workspaceService);
					});

138 139 140 141 142 143 144 145 146 147 148 149 150 151
					// Gracefully Shutdown Storage
					shell.onShutdown(event => {
						event.join(storageService.close());
					});

					// Open Shell
					shell.open();

					// Inform user about loading issues from the loader
					(<any>self).require.config({
						onError: err => {
							if (err.errorCode === 'load') {
								shell.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
						}
153
					});
154
				});
155 156 157 158 159
			});
		});
	});
}

160
function createWorkspaceInitializationPayload(configuration: IWindowConfiguration, environmentService: EnvironmentService): Promise<IWorkspaceInitializationPayload> {
161 162 163

	// Multi-root workspace
	if (configuration.workspace) {
164
		return Promise.resolve(configuration.workspace as IMultiFolderWorkspaceInitializationPayload);
165 166 167
	}

	// Single-folder workspace
168 169
	let workspaceInitializationPayload: Promise<IWorkspaceInitializationPayload> = Promise.resolve(void 0);
	if (configuration.folderUri) {
170 171 172 173 174
		workspaceInitializationPayload = resolveSingleFolderWorkspaceInitializationPayload(configuration.folderUri, configuration.verbose);
	}

	return workspaceInitializationPayload.then(payload => {

175
		// Fallback to empty workspace if we have no payload yet.
176
		if (!payload) {
177 178 179
			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
180 181
			} else if (environmentService.isExtensionDevelopment) {
				id = 'extension-development-workspace'; // extension development window never stores backups and is a singleton
182
			} else {
183
				return Promise.reject(new Error('Unexpected window configuration without backupPath'));
184 185
			}

186
			payload = { id } as IEmptyWorkspaceInitializationPayload;
187 188
		}

189
		return payload;
190
	});
191 192
}

193
function resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier, verbose: boolean): Promise<ISingleFolderWorkspaceInitializationPayload> {
194

195 196 197 198
	// 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 });
	}
199

200 201 202 203 204 205 206 207 208 209 210 211
	function computeLocalDiskFolderId(folder: uri, stat: fs.Stats): string {
		let ctime: number;
		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();
			}
212 213
		}

214 215 216
		// 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 已提交
217 218
	}

219
	// For local: ensure path is absolute and exists
220
	const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd());
221
	return stat(sanitizedFolderPath).then(stat => {
222
		const sanitizedFolderUri = uri.file(sanitizedFolderPath);
223
		return {
224
			id: computeLocalDiskFolderId(sanitizedFolderUri, stat),
225
			folder: sanitizedFolderUri
226 227
		} as ISingleFolderWorkspaceInitializationPayload;
	}, error => {
228
		if (verbose) {
229 230 231
			errors.onUnexpectedError(error);
		}

232 233
		// Do not bubble up the error
		return void 0;
234
	});
E
Erich Gamma 已提交
235 236
}

237
function prepareWorkspaceStorageFolder(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService): Thenable<string> {
B
Benjamin Pasero 已提交
238
	const workspaceStoragePath = join(environmentService.workspaceStorageHome, payload.id); // workspace home + workspace id
239

240
	return exists(workspaceStoragePath).then(exists => {
241
		if (exists) {
242
			return workspaceStoragePath;
243 244
		}

245
		return mkdirp(workspaceStoragePath).then(() => workspaceStoragePath);
246 247 248
	});
}

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
function ensureWorkspaceStorageFolderMeta(workspaceStoragePath: string, workspaceService: IWorkspaceContextService): void {
	const state = workspaceService.getWorkbenchState();
	if (state === WorkbenchState.EMPTY) {
		return; // no storage meta for empty workspaces
	}

	const workspaceStorageMetaPath = join(workspaceStoragePath, 'workspace.json');

	exists(workspaceStorageMetaPath).then(exists => {
		if (exists) {
			return void 0; // already existing
		}

		const workspace = workspaceService.getWorkspace();

		return writeFile(workspaceStorageMetaPath, JSON.stringify({
			configuration: workspace.configuration ? uri.revive(workspace.configuration).toString() : void 0,
			folder: state === WorkbenchState.FOLDER ? uri.revive(workspace.folders[0].uri).toString() : void 0
		}, undefined, 2));
	}).then(null, error => errors.onUnexpectedError(error));
}

271 272 273
function createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService): Promise<WorkspaceService> {
	const workspaceService = new WorkspaceService(environmentService);

274 275 276 277 278
	return workspaceService.initialize(payload).then(() => workspaceService, error => {
		errors.onUnexpectedError(error);

		return workspaceService;
	});
279 280
}

B
Benjamin Pasero 已提交
281
function createStorageService(workspaceStorageFolder: string, payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService): Thenable<StorageService> {
282

283
	// Return early if we are using in-memory storage
B
Benjamin Pasero 已提交
284
	const useInMemoryStorage = !!environmentService.extensionTestsPath; /* never keep any state when running extension tests */
285 286 287 288 289 290 291
	if (useInMemoryStorage) {
		const storageService = new StorageService(StorageService.IN_MEMORY_PATH, logService, environmentService);

		return storageService.init().then(() => storageService);
	}

	// Otherwise do a migration of previous workspace data if the DB does not exist yet
292
	// TODO@Ben remove me after one milestone
293
	const workspaceStorageDBPath = join(workspaceStorageFolder, 'storage.db');
294 295 296 297 298 299 300 301
	return exists(workspaceStorageDBPath).then(exists => {
		const storageService = new StorageService(workspaceStorageDBPath, logService, environmentService);

		return storageService.init().then(() => {
			if (exists) {
				return storageService; // return early if DB was already there
			}

302
			return readdir(environmentService.extensionsPath).then(extensions => {
303

304
				// Otherwise, we migrate data from window.localStorage over
B
Benjamin Pasero 已提交
305 306 307 308 309 310 311 312 313 314
				try {
					const parsedStorage = parseStorage(window.localStorage);

					let workspaceItems: StorageObject;
					if (isWorkspaceIdentifier(payload)) {
						workspaceItems = parsedStorage.multiRoot.get(`root:${payload.id}`);
					} else if (isSingleFolderWorkspaceInitializationPayload(payload)) {
						workspaceItems = parsedStorage.folder.get(payload.folder.toString());
					} else {
						workspaceItems = parsedStorage.empty.get(`empty:${payload.id}`);
315
					}
316

B
Benjamin Pasero 已提交
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
					const supportedKeys = new Set<string>();
					[
						'workbench.search.history',
						'history.entries',
						'ignoreNetVersionError',
						'ignoreEnospcError',
						'extensionUrlHandler.urlToHandle',
						'terminal.integrated.isWorkspaceShellAllowed',
						'workbench.tasks.ignoreTask010Shown',
						'workbench.tasks.recentlyUsedTasks',
						'workspaces.dontPromptToOpen',
						'output.activechannel',
						'outline/state',
						'extensionsAssistant/workspaceRecommendationsIgnore',
						'extensionsAssistant/dynamicWorkspaceRecommendations',
						'debug.repl.history',
						'editor.matchCase',
						'editor.wholeWord',
						'editor.isRegex',
						'lifecyle.lastShutdownReason',
						'debug.selectedroot',
						'debug.selectedconfigname',
						'debug.breakpoint',
						'debug.breakpointactivated',
						'debug.functionbreakpoint',
						'debug.exceptionbreakpoint',
						'debug.watchexpressions',
						'workbench.sidebar.activeviewletid',
						'workbench.panelpart.activepanelid',
						'workbench.zenmode.active',
						'workbench.centerededitorlayout.active',
						'workbench.sidebar.restore',
						'workbench.sidebar.hidden',
						'workbench.panel.hidden',
						'workbench.panel.location',
						'extensionsIdentifiers/disabled',
						'extensionsIdentifiers/enabled',
						'scm.views',
						'suggest/memories/first',
						'suggest/memories/recentlyUsed',
						'suggest/memories/recentlyUsedByPrefix'
					].forEach(key => supportedKeys.add(key));

					// Support extension storage as well (always the ID of the extension)
					extensions.forEach(extension => {
B
Benjamin Pasero 已提交
362 363 364 365 366 367 368
						let extensionId: string;
						if (extension.indexOf('-') >= 0) {
							extensionId = extension.substring(0, extension.lastIndexOf('-')); // convert "author.extension-0.2.5" => "author.extension"
						} else {
							extensionId = extension;
						}

B
Benjamin Pasero 已提交
369 370
						if (extensionId) {
							supportedKeys.add(extensionId);
371 372
						}
					});
B
Benjamin Pasero 已提交
373 374 375 376 377 378 379 380 381 382 383

					if (workspaceItems) {
						Object.keys(workspaceItems).forEach(key => {
							if (supportedKeys.has(key) || key.indexOf('memento/') === 0 || key.indexOf('viewservice.') === 0 || endsWith(key, '.state') || endsWith(key, '.numberOfVisibleViews')) {
								storageService.store(key, workspaceItems[key], StorageScope.WORKSPACE);
							}
						});
					}
				} catch (error) {
					errors.onUnexpectedError(error);
					logService.error(error);
384 385 386 387
				}

				return storageService;
			});
388 389
		});
	});
390 391
}

392
function createStorageLegacyService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageLegacyService {
393 394 395
	let workspaceId: string;
	let secondaryWorkspaceId: number;

396
	switch (workspaceService.getWorkbenchState()) {
397 398

		// in multi root workspace mode we use the provided ID as key for workspace storage
399
		case WorkbenchState.WORKSPACE:
400
			workspaceId = uri.from({ path: workspaceService.getWorkspace().id, scheme: 'root' }).toString();
401 402 403 404
			break;

		// in single folder mode we use the path of the opened folder as key for workspace storage
		// the ctime is used as secondary workspace id to clean up stale UI state if necessary
405
		case WorkbenchState.FOLDER:
406
			const workspace: Workspace = <Workspace>workspaceService.getWorkspace();
407
			workspaceId = workspace.folders[0].uri.toString();
408 409 410
			secondaryWorkspaceId = workspace.ctime;
			break;

B
Benjamin Pasero 已提交
411
		// finally, if we do not have a workspace open, we need to find another identifier for the window to store
412 413 414 415 416
		// workspace UI state. if we have a backup path in the configuration we can use that because this
		// will be a unique identifier per window that is stable between restarts as long as there are
		// dirty files in the workspace.
		// We use basename() to produce a short identifier, we do not need the full path. We use a custom
		// scheme so that we can later distinguish these identifiers from the workspace one.
417
		case WorkbenchState.EMPTY:
418
			workspaceId = workspaceService.getWorkspace().id;
419
			break;
420 421
	}

422
	const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests!
423

424
	let storage: IStorageLegacy;
425 426 427 428 429
	if (disableStorage) {
		storage = inMemoryLocalStorageInstance;
	} else {
		storage = window.localStorage;
	}
430

431
	return new StorageLegacyService(storage, storage, workspaceId, secondaryWorkspaceId);
432 433
}

S
Sandeep Somavarapu 已提交
434
function createLogService(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration, environmentService: IEnvironmentService): ILogService {
S
Sandeep Somavarapu 已提交
435 436
	const spdlogService = createSpdLogService(`renderer${configuration.windowId}`, configuration.logLevel, environmentService.logsPath);
	const consoleLogService = new ConsoleLogService(configuration.logLevel);
S
Sandeep Somavarapu 已提交
437
	const logService = new MultiplexLogService([consoleLogService, spdlogService]);
438
	const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel'));
439

S
Sandeep Somavarapu 已提交
440 441 442
	return new FollowerLogService(logLevelClient, logService);
}

443
function createMainProcessServices(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration): ServiceCollection {
444 445 446 447 448 449 450 451
	const serviceCollection = new ServiceCollection();

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

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

J
Joao Moreno 已提交
452 453 454 455
	const urlChannel = mainProcessClient.getChannel('url');
	const mainUrlService = new URLServiceChannelClient(urlChannel);
	const urlService = new RelayURLService(mainUrlService);
	serviceCollection.set(IURLService, urlService);
J
Joao Moreno 已提交
456

J
Joao Moreno 已提交
457
	const urlHandlerChannel = new URLHandlerChannel(urlService);
J
Joao Moreno 已提交
458
	mainProcessClient.registerChannel('urlHandler', urlHandlerChannel);
459

460 461 462
	const issueChannel = mainProcessClient.getChannel('issue');
	serviceCollection.set(IIssueService, new SyncDescriptor(IssueChannelClient, issueChannel));

463 464 465
	const menubarChannel = mainProcessClient.getChannel('menubar');
	serviceCollection.set(IMenubarService, new SyncDescriptor(MenubarChannelClient, menubarChannel));

466
	const workspacesChannel = mainProcessClient.getChannel('workspaces');
467
	serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel));
468 469

	return serviceCollection;
470
}