main.ts 15.1 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 } 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 } 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 } 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';
B
Benjamin Pasero 已提交
38
import { StorageService, DelegatingStorageService } from 'vs/platform/storage/electron-browser/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';
J
Joao Moreno 已提交
49

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

52
export function startup(configuration: IWindowConfiguration): Promise<void> {
53

54
	// Massage configuration file URIs
M
Martin Aeschlimann 已提交
55 56
	revive(configuration);

57
	// Setup perf
58 59
	perf.importEntries(configuration.perfEntries);

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

66
	// Keyboard support
67
	KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged();
A
Alex Dima 已提交
68

69
	// Setup Intl for comparers
70 71
	comparer.setFileNameComparer(new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }));

72
	// Open workbench
B
Benjamin Pasero 已提交
73
	return openWorkbench(configuration);
E
Erich Gamma 已提交
74 75
}

M
Martin Aeschlimann 已提交
76 77 78 79
function revive(workbench: IWindowConfiguration) {
	if (workbench.folderUri) {
		workbench.folderUri = uri.revive(workbench.folderUri);
	}
80

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

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

97
	const environmentService = new EnvironmentService(configuration, configuration.execPath);
98

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

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

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
		// Prepare the workspace storage folder
		return prepareWorkspaceStorageFolder(payload, environmentService).then(storagePath => {
			return Promise.all([

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

				// Create and load storage service
				createStorageService(storagePath, environmentService, logService)
			]).then(services => {
				const workspaceService = services[0];
				const storageLegacyService = createStorageLegacyService(workspaceService, environmentService);
				const storageService = new DelegatingStorageService(services[1], storageLegacyService, logService);

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

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

					// 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))));
							}
146
						}
147
					});
148
				});
149 150 151 152 153
			});
		});
	});
}

154
function createWorkspaceInitializationPayload(configuration: IWindowConfiguration, environmentService: EnvironmentService): Promise<IWorkspaceInitializationPayload> {
155 156 157

	// Multi-root workspace
	if (configuration.workspace) {
158
		return Promise.resolve(configuration.workspace as IMultiFolderWorkspaceInitializationPayload);
159 160 161
	}

	// Single-folder workspace
162 163
	let workspaceInitializationPayload: Promise<IWorkspaceInitializationPayload> = Promise.resolve(void 0);
	if (configuration.folderUri) {
164 165 166 167 168
		workspaceInitializationPayload = resolveSingleFolderWorkspaceInitializationPayload(configuration.folderUri, configuration.verbose);
	}

	return workspaceInitializationPayload.then(payload => {

169 170
		// Fallback to empty workspace if we have no payload yet. We know the
		// backupPath must be a unique path so we leverage its name as workspace ID
171
		if (!payload) {
172 173 174 175 176
			if (!configuration.backupPath) {
				return Promise.reject(new Error('Unexpected missing backupPath for empty window')); // should not happen in this case
			}

			payload = { id: basename(configuration.backupPath) } as IEmptyWorkspaceInitializationPayload;
177 178
		}

179
		return payload;
180
	});
181 182
}

183
function resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier, verbose: boolean): Promise<ISingleFolderWorkspaceInitializationPayload> {
184

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
	function singleFolderId(folder: uri, stat?: fs.Stats): string {
		if (folder.scheme === Schemas.file && stat) {
			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();
				}
			}

200 201
			// 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
202 203 204 205 206 207
			return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex');
		}

		return createHash('md5').update(folder.toString()).digest('hex');
	}

208 209
	// Return early the folder is not local
	if (folderUri.scheme !== Schemas.file) {
210
		return Promise.resolve({ id: singleFolderId(folderUri), folder: folderUri });
E
Erich Gamma 已提交
211 212
	}

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

		// Treat any error case as empty workbench case (no folder path)
		return null;
228
	});
E
Erich Gamma 已提交
229 230
}

231 232 233 234 235 236 237 238 239 240 241 242 243 244
function prepareWorkspaceStorageFolder(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService): Thenable<string> {

	// Workspace storage: scope by workspace identifier
	const storagePath = join(environmentService.workspaceStorageHome, payload.id);

	return exists(storagePath).then(exists => {
		if (exists) {
			return storagePath;
		}

		return mkdirp(storagePath).then(() => storagePath);
	});
}

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

	return workspaceService.initialize(payload).then(() => workspaceService, error => workspaceService);
}

251 252
function createStorageService(workspaceStorageFolder: string, environmentService: IEnvironmentService, logService: ILogService): Promise<StorageService> {
	const storageService = new StorageService(join(workspaceStorageFolder, 'storage.db'), logService, environmentService);
253

B
Benjamin Pasero 已提交
254
	return storageService.init().then(() => storageService);
255 256
}

257
function createStorageLegacyService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageLegacyService {
258 259 260
	let workspaceId: string;
	let secondaryWorkspaceId: number;

261
	switch (workspaceService.getWorkbenchState()) {
262 263

		// in multi root workspace mode we use the provided ID as key for workspace storage
264
		case WorkbenchState.WORKSPACE:
265
			workspaceId = uri.from({ path: workspaceService.getWorkspace().id, scheme: 'root' }).toString();
266 267 268 269
			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
270
		case WorkbenchState.FOLDER:
271
			const workspace: Workspace = <Workspace>workspaceService.getWorkspace();
272
			workspaceId = workspace.folders[0].uri.toString();
273 274 275
			secondaryWorkspaceId = workspace.ctime;
			break;

B
Benjamin Pasero 已提交
276
		// finally, if we do not have a workspace open, we need to find another identifier for the window to store
277 278 279 280 281
		// 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.
282
		case WorkbenchState.EMPTY:
283
			workspaceId = workspaceService.getWorkspace().id;
284
			break;
285 286
	}

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

289
	let storage: IStorageLegacy;
290 291 292 293 294
	if (disableStorage) {
		storage = inMemoryLocalStorageInstance;
	} else {
		storage = window.localStorage;
	}
295

296
	return new StorageLegacyService(storage, storage, workspaceId, secondaryWorkspaceId);
297 298
}

S
Sandeep Somavarapu 已提交
299
function createLogService(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration, environmentService: IEnvironmentService): ILogService {
S
Sandeep Somavarapu 已提交
300 301
	const spdlogService = createSpdLogService(`renderer${configuration.windowId}`, configuration.logLevel, environmentService.logsPath);
	const consoleLogService = new ConsoleLogService(configuration.logLevel);
S
Sandeep Somavarapu 已提交
302
	const logService = new MultiplexLogService([consoleLogService, spdlogService]);
303
	const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel'));
304

S
Sandeep Somavarapu 已提交
305 306 307
	return new FollowerLogService(logLevelClient, logService);
}

308
function createMainProcessServices(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration): ServiceCollection {
309 310 311 312 313 314 315 316
	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 已提交
317 318 319 320
	const urlChannel = mainProcessClient.getChannel('url');
	const mainUrlService = new URLServiceChannelClient(urlChannel);
	const urlService = new RelayURLService(mainUrlService);
	serviceCollection.set(IURLService, urlService);
J
Joao Moreno 已提交
321

J
Joao Moreno 已提交
322
	const urlHandlerChannel = new URLHandlerChannel(urlService);
J
Joao Moreno 已提交
323
	mainProcessClient.registerChannel('urlHandler', urlHandlerChannel);
324

325 326 327
	const issueChannel = mainProcessClient.getChannel('issue');
	serviceCollection.set(IIssueService, new SyncDescriptor(IssueChannelClient, issueChannel));

328 329 330
	const menubarChannel = mainProcessClient.getChannel('menubar');
	serviceCollection.set(IMenubarService, new SyncDescriptor(MenubarChannelClient, menubarChannel));

331
	const workspacesChannel = mainProcessClient.getChannel('workspaces');
332
	serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel));
333 334

	return serviceCollection;
335
}