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
import { onUnexpectedError } from 'vs/base/common/errors';
12 13
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 } 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, IWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload } 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
import { sanitizeFilePath } from 'vs/base/node/extfs';
47
import { basename } from 'path';
48
import { createHash } from 'crypto';
49
import { IdleValue } from 'vs/base/common/async';
50
import { setGlobalLeakWarningThreshold } from 'vs/base/common/event';
B
wip  
Benjamin Pasero 已提交
51
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
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
	// Configure emitter leak warning threshold
64
	setGlobalLeakWarningThreshold(100);
65

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

72
	// Keyboard support
73
	KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged();
A
Alex Dima 已提交
74

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

84
	// Open workbench
B
Benjamin Pasero 已提交
85
	return openWorkbench(configuration);
E
Erich Gamma 已提交
86 87
}

M
Martin Aeschlimann 已提交
88 89 90 91
function revive(workbench: IWindowConfiguration) {
	if (workbench.folderUri) {
		workbench.folderUri = uri.revive(workbench.folderUri);
	}
92

M
Martin Aeschlimann 已提交
93 94 95 96 97 98 99 100
	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 已提交
101
		}
M
Martin Aeschlimann 已提交
102
	});
M
Martin Aeschlimann 已提交
103 104
}

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

109
	const environmentService = new EnvironmentService(configuration, configuration.execPath);
110

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

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

B
Benjamin Pasero 已提交
117 118 119 120 121 122
		return Promise.all([

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

			// Create and initialize storage service
B
wip  
Benjamin Pasero 已提交
123
			createStorageService(payload, environmentService, logService, mainProcessClient)
B
Benjamin Pasero 已提交
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
		]).then(services => {
			const workspaceService = services[0];
			const storageService = new DelegatingStorageService(services[1], createStorageLegacyService(workspaceService, environmentService), logService, workspaceService);

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

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

				// Gracefully Shutdown Storage
141
				shell.onWillShutdown(event => {
B
Benjamin Pasero 已提交
142 143 144 145 146 147 148 149 150 151 152
					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))));
153
						}
B
Benjamin Pasero 已提交
154
					}
155
				});
156 157 158 159 160
			});
		});
	});
}

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

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

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

	return workspaceInitializationPayload.then(payload => {

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

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

190
		return payload;
191
	});
192 193
}

194
function resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise<ISingleFolderWorkspaceInitializationPayload> {
195

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

201 202 203 204 205 206 207 208 209 210 211 212
	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();
			}
213 214
		}

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

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

231
function createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService): Promise<WorkspaceService> {
232 233
	const workspaceService = new WorkspaceService(environmentService);

234
	return workspaceService.initialize(payload).then(() => workspaceService, error => {
235 236
		onUnexpectedError(error);
		logService.error(error);
237 238 239

		return workspaceService;
	});
240 241
}

B
wip  
Benjamin Pasero 已提交
242
function createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService, mainProcessClient: ElectronIPCClient): Thenable<StorageService> {
243
	const useInMemoryStorage = !!environmentService.extensionTestsPath; // no storage during extension tests!
B
wip  
Benjamin Pasero 已提交
244
	const storageService = new StorageService({ storeInMemory: useInMemoryStorage, globalStorage: new GlobalStorageDatabaseChannelClient(mainProcessClient.getChannel('storage')) }, logService, environmentService);
245

246 247 248
	return storageService.initialize(payload).then(() => storageService, error => {
		onUnexpectedError(error);
		logService.error(error);
249

250
		return storageService;
251
	});
252 253
}

254
function createStorageLegacyService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageLegacyService {
255 256
	let workspaceId: string;

257
	switch (workspaceService.getWorkbenchState()) {
258 259

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

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

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

284
	let storage: IStorageLegacy;
285 286 287 288 289
	if (disableStorage) {
		storage = inMemoryLocalStorageInstance;
	} else {
		storage = window.localStorage;
	}
290

B
Benjamin Pasero 已提交
291
	return new StorageLegacyService(storage, storage, workspaceId);
292 293
}

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

S
Sandeep Somavarapu 已提交
300 301 302
	return new FollowerLogService(logLevelClient, logService);
}

B
wip  
Benjamin Pasero 已提交
303
function createMainProcessServices(mainProcessClient: ElectronIPCClient): ServiceCollection {
304 305 306 307 308 309
	const serviceCollection = new ServiceCollection();

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

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

J
Joao Moreno 已提交
312 313 314 315
	const urlChannel = mainProcessClient.getChannel('url');
	const mainUrlService = new URLServiceChannelClient(urlChannel);
	const urlService = new RelayURLService(mainUrlService);
	serviceCollection.set(IURLService, urlService);
J
Joao Moreno 已提交
316

J
Joao Moreno 已提交
317
	const urlHandlerChannel = new URLHandlerChannel(urlService);
J
Joao Moreno 已提交
318
	mainProcessClient.registerChannel('urlHandler', urlHandlerChannel);
319

320
	const issueChannel = mainProcessClient.getChannel('issue');
321
	serviceCollection.set(IIssueService, new SyncDescriptor(IssueChannelClient, [issueChannel]));
322

323
	const menubarChannel = mainProcessClient.getChannel('menubar');
324
	serviceCollection.set(IMenubarService, new SyncDescriptor(MenubarChannelClient, [menubarChannel]));
325

326
	const workspacesChannel = mainProcessClient.getChannel('workspaces');
327
	serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel));
328 329

	return serviceCollection;
330
}