main.ts 14.0 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 } 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
import { sanitizeFilePath } from 'vs/base/node/extfs';
47
import { basename } 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 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
	// Resolve a workspace payload that we can get the workspace ID from
	return createWorkspaceInitializationPayload(configuration, environmentService).then(payload => {
		return Promise.all([

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

			// Create and load storage service
			createStorageService(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))));
						}
143
					}
144
				});
145 146 147 148 149
			});
		});
	});
}

150
function createWorkspaceInitializationPayload(configuration: IWindowConfiguration, environmentService: EnvironmentService): Promise<IWorkspaceInitializationPayload> {
151 152 153

	// Multi-root workspace
	if (configuration.workspace) {
154
		return Promise.resolve(configuration.workspace as IMultiFolderWorkspaceInitializationPayload);
155 156 157
	}

	// Single-folder workspace
158 159
	let workspaceInitializationPayload: Promise<IWorkspaceInitializationPayload> = Promise.resolve(void 0);
	if (configuration.folderUri) {
160 161 162 163 164
		workspaceInitializationPayload = resolveSingleFolderWorkspaceInitializationPayload(configuration.folderUri, configuration.verbose);
	}

	return workspaceInitializationPayload.then(payload => {

165
		// Fallback to empty workspace if we have no payload yet
166 167 168 169
		if (!payload) {
			payload = { id: configuration.backupPath ? uri.from({ path: basename(configuration.backupPath), scheme: 'empty' }).toString() : '' } as IEmptyWorkspaceInitializationPayload;
		}

170
		return payload;
171
	});
172 173
}

174
function resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier, verbose: boolean): Promise<ISingleFolderWorkspaceInitializationPayload> {
175

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
	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();
				}
			}

			return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex');
		}

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

197 198
	// Return early the folder is not local
	if (folderUri.scheme !== Schemas.file) {
199
		return Promise.resolve({ id: singleFolderId(folderUri), folder: folderUri });
E
Erich Gamma 已提交
200 201
	}

202
	// For local: ensure path is absolute and exists
203
	const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd());
204
	return stat(sanitizedFolderPath).then(stat => {
205
		const sanitizedFolderUri = uri.file(sanitizedFolderPath);
206
		return {
207 208
			id: singleFolderId(sanitizedFolderUri, stat),
			folder: sanitizedFolderUri
209 210
		} as ISingleFolderWorkspaceInitializationPayload;
	}, error => {
211
		if (verbose) {
212 213 214 215 216
			errors.onUnexpectedError(error);
		}

		// Treat any error case as empty workbench case (no folder path)
		return null;
217
	});
E
Erich Gamma 已提交
218 219
}

220 221 222 223 224 225
function createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService): Promise<WorkspaceService> {
	const workspaceService = new WorkspaceService(environmentService);

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

226 227
function createStorageService(environmentService: IEnvironmentService, logService: ILogService): Promise<StorageService> {
	const storageService = new StorageService(':memory:', logService, environmentService);
228

B
Benjamin Pasero 已提交
229
	return storageService.init().then(() => storageService);
230 231
}

232
function createStorageLegacyService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageLegacyService {
233 234 235
	let workspaceId: string;
	let secondaryWorkspaceId: number;

236
	switch (workspaceService.getWorkbenchState()) {
237 238

		// in multi root workspace mode we use the provided ID as key for workspace storage
239
		case WorkbenchState.WORKSPACE:
240
			workspaceId = uri.from({ path: workspaceService.getWorkspace().id, scheme: 'root' }).toString();
241 242 243 244
			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
245
		case WorkbenchState.FOLDER:
246
			const workspace: Workspace = <Workspace>workspaceService.getWorkspace();
247
			workspaceId = workspace.folders[0].uri.toString();
248 249 250
			secondaryWorkspaceId = workspace.ctime;
			break;

B
Benjamin Pasero 已提交
251
		// finally, if we do not have a workspace open, we need to find another identifier for the window to store
252 253 254 255 256
		// 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.
257
		case WorkbenchState.EMPTY:
258
			workspaceId = workspaceService.getWorkspace().id;
259
			break;
260 261
	}

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

264
	let storage: IStorageLegacy;
265 266 267 268 269
	if (disableStorage) {
		storage = inMemoryLocalStorageInstance;
	} else {
		storage = window.localStorage;
	}
270

271
	return new StorageLegacyService(storage, storage, workspaceId, secondaryWorkspaceId);
272 273
}

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

S
Sandeep Somavarapu 已提交
280 281 282
	return new FollowerLogService(logLevelClient, logService);
}

283
function createMainProcessServices(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration): ServiceCollection {
284 285 286 287 288 289 290 291
	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 已提交
292 293 294 295
	const urlChannel = mainProcessClient.getChannel('url');
	const mainUrlService = new URLServiceChannelClient(urlChannel);
	const urlService = new RelayURLService(mainUrlService);
	serviceCollection.set(IURLService, urlService);
J
Joao Moreno 已提交
296

J
Joao Moreno 已提交
297
	const urlHandlerChannel = new URLHandlerChannel(urlService);
J
Joao Moreno 已提交
298
	mainProcessClient.registerChannel('urlHandler', urlHandlerChannel);
299

300 301 302
	const issueChannel = mainProcessClient.getChannel('issue');
	serviceCollection.set(IIssueService, new SyncDescriptor(IssueChannelClient, issueChannel));

303 304 305
	const menubarChannel = mainProcessClient.getChannel('menubar');
	serviceCollection.set(IMenubarService, new SyncDescriptor(MenubarChannelClient, menubarChannel));

306
	const workspacesChannel = mainProcessClient.getChannel('workspaces');
307
	serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel));
308 309

	return serviceCollection;
310
}