configurationService.ts 32.8 KB
Newer Older
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import URI from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import { TPromise } from 'vs/base/common/winjs.base';
S
Sandeep Somavarapu 已提交
10
import { dirname } from 'path';
11
import * as assert from 'vs/base/common/assert';
12
import Event, { Emitter } from 'vs/base/common/event';
13
import { StrictResourceMap } from 'vs/base/common/map';
14
import { equals } from 'vs/base/common/objects';
S
Sandeep Somavarapu 已提交
15 16 17
import { Disposable } from 'vs/base/common/lifecycle';
import { Queue } from 'vs/base/common/async';
import { stat, writeFile } from 'vs/base/node/pfs';
18
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
19
import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
S
Sandeep Somavarapu 已提交
20
import { FileChangesEvent } from 'vs/platform/files/common/files';
21
import { isLinux } from 'vs/base/common/platform';
22
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
S
Sandeep Somavarapu 已提交
23
import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
24
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
25 26
import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels';
import { IWorkspaceConfigurationService, FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration';
27
import { Registry } from 'vs/platform/registry/common/platform';
28
import { IConfigurationNode, IConfigurationRegistry, Extensions, settingsSchema, resourceSettingsSchema, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
29
import { createHash } from 'crypto';
S
Sandeep Somavarapu 已提交
30
import { getWorkspaceLabel, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
31
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
32 33 34
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/node/product';
35 36
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
S
Sandeep Somavarapu 已提交
37 38 39 40
import { WorkspaceConfiguration, FolderConfiguration } from 'vs/workbench/services/configuration/node/configuration';
import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService';
import { Schemas } from 'vs/base/common/network';
import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces';
S
Sandeep Somavarapu 已提交
41
import { distinct } from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
42
import { UserConfiguration } from 'vs/platform/configuration/node/configuration';
B
Benjamin Pasero 已提交
43
import { getBaseLabel } from 'vs/base/common/labels';
44

S
Sandeep Somavarapu 已提交
45
export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService {
46 47 48

	public _serviceBrand: any;

49
	private workspace: Workspace;
50
	private _configuration: Configuration;
S
Sandeep Somavarapu 已提交
51 52
	private defaultConfiguration: DefaultConfigurationModel;
	private userConfiguration: UserConfiguration;
53
	private workspaceConfiguration: WorkspaceConfiguration;
54
	private cachedFolderConfigs: StrictResourceMap<FolderConfiguration>;
55

S
Sandeep Somavarapu 已提交
56 57
	private workspaceEditingQueue: Queue<void>;

58 59
	protected readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
	public readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
60

61 62
	protected readonly _onDidChangeWorkspaceFolders: Emitter<IWorkspaceFoldersChangeEvent> = this._register(new Emitter<IWorkspaceFoldersChangeEvent>());
	public readonly onDidChangeWorkspaceFolders: Event<IWorkspaceFoldersChangeEvent> = this._onDidChangeWorkspaceFolders.event;
63

S
Sandeep Somavarapu 已提交
64 65 66
	protected readonly _onDidChangeWorkspaceName: Emitter<void> = this._register(new Emitter<void>());
	public readonly onDidChangeWorkspaceName: Event<void> = this._onDidChangeWorkspaceName.event;

67 68 69
	protected readonly _onDidChangeWorkbenchState: Emitter<WorkbenchState> = this._register(new Emitter<WorkbenchState>());
	public readonly onDidChangeWorkbenchState: Event<WorkbenchState> = this._onDidChangeWorkbenchState.event;

S
Sandeep Somavarapu 已提交
70
	private configurationEditingService: ConfigurationEditingService;
S
Sandeep Somavarapu 已提交
71
	private jsonEditingService: JSONEditingService;
72

S
Sandeep Somavarapu 已提交
73
	constructor(private environmentService: IEnvironmentService, private workspaceSettingsRootFolder: string = FOLDER_CONFIG_FOLDER_NAME) {
74
		super();
75

S
Sandeep Somavarapu 已提交
76 77
		this.defaultConfiguration = new DefaultConfigurationModel();
		this.userConfiguration = this._register(new UserConfiguration(environmentService.appSettingsPath));
78
		this.workspaceConfiguration = this._register(new WorkspaceConfiguration());
S
Sandeep Somavarapu 已提交
79
		this._register(this.userConfiguration.onDidChangeConfiguration(() => this.onUserConfigurationChanged()));
80 81
		this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged()));

82
		this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidRegisterConfiguration(e => this.registerConfigurationSchemas()));
S
Sandeep Somavarapu 已提交
83
		this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
S
Sandeep Somavarapu 已提交
84 85

		this.workspaceEditingQueue = new Queue<void>();
86 87
	}

88 89
	// Workspace Context Service Impl

90
	public getWorkspace(): Workspace {
91 92 93
		return this.workspace;
	}

94
	public getWorkbenchState(): WorkbenchState {
95 96 97 98 99 100
		// Workspace has configuration file
		if (this.workspace.configuration) {
			return WorkbenchState.WORKSPACE;
		}

		// Folder has single root
S
Sandeep Somavarapu 已提交
101
		if (this.workspace.folders.length === 1) {
102
			return WorkbenchState.FOLDER;
103
		}
104 105

		// Empty
106
		return WorkbenchState.EMPTY;
107 108
	}

S
Sandeep Somavarapu 已提交
109
	public getWorkspaceFolder(resource: URI): IWorkspaceFolder {
S
Sandeep Somavarapu 已提交
110
		return this.workspace.getFolder(resource);
111 112
	}

B
Benjamin Pasero 已提交
113
	public addFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number): TPromise<void> {
S
Sandeep Somavarapu 已提交
114
		assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
B
Benjamin Pasero 已提交
115
		return this.workspaceEditingQueue.queue(() => this.doAddFolders(foldersToAdd, index));
S
Sandeep Somavarapu 已提交
116 117 118 119 120 121 122
	}

	public removeFolders(foldersToRemove: URI[]): TPromise<void> {
		assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
		return this.workspaceEditingQueue.queue(() => this.doRemoveFolders(foldersToRemove));
	}

123
	public isInsideWorkspace(resource: URI): boolean {
S
Sandeep Somavarapu 已提交
124
		return !!this.getWorkspaceFolder(resource);
125 126
	}

127
	public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean {
128 129
		switch (this.getWorkbenchState()) {
			case WorkbenchState.FOLDER:
130
				return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && this.pathEquals(this.workspace.folders[0].uri.fsPath, workspaceIdentifier);
131 132
			case WorkbenchState.WORKSPACE:
				return isWorkspaceIdentifier(workspaceIdentifier) && this.workspace.id === workspaceIdentifier.id;
133
		}
134
		return false;
135 136
	}

B
Benjamin Pasero 已提交
137
	private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number): TPromise<void> {
S
Sandeep Somavarapu 已提交
138 139 140 141 142 143 144 145 146 147 148 149 150
		if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
			return TPromise.as(void 0); // we need a workspace to begin with
		}

		const currentWorkspaceFolders = this.getWorkspace().folders;
		const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri);
		const currentStoredFolders = currentWorkspaceFolders.map(folder => folder.raw);

		const storedFoldersToAdd: IStoredWorkspaceFolder[] = [];

		const workspaceConfigFolder = dirname(this.getWorkspace().configuration.fsPath);

		foldersToAdd.forEach(folderToAdd => {
151
			if (this.contains(currentWorkspaceFolderUris, folderToAdd.uri)) {
S
Sandeep Somavarapu 已提交
152 153 154
				return; // already existing
			}

155 156
			let storedFolder: IStoredWorkspaceFolder;

S
Sandeep Somavarapu 已提交
157
			// File resource: use "path" property
158
			if (folderToAdd.uri.scheme === Schemas.file) {
159
				storedFolder = {
160
					path: massageFolderPathForWorkspace(folderToAdd.uri.fsPath, workspaceConfigFolder, currentStoredFolders)
161
				};
S
Sandeep Somavarapu 已提交
162 163 164 165
			}

			// Any other resource: use "uri" property
			else {
166
				storedFolder = {
167
					uri: folderToAdd.uri.toString(true)
168
				};
S
Sandeep Somavarapu 已提交
169
			}
170

171 172
			if (folderToAdd.name) {
				storedFolder.name = folderToAdd.name;
173 174 175
			}

			storedFoldersToAdd.push(storedFolder);
S
Sandeep Somavarapu 已提交
176 177 178
		});

		if (storedFoldersToAdd.length > 0) {
B
Benjamin Pasero 已提交
179 180
			let newStoredWorkspaceFolders: IStoredWorkspaceFolder[] = [];

181
			if (typeof index === 'number' && index >= 0 && index < currentStoredFolders.length) {
B
Benjamin Pasero 已提交
182 183 184 185 186 187 188
				newStoredWorkspaceFolders = currentStoredFolders.slice(0);
				newStoredWorkspaceFolders.splice(index, 0, ...storedFoldersToAdd);
			} else {
				newStoredWorkspaceFolders = [...currentStoredFolders, ...storedFoldersToAdd];
			}

			return this.setFolders(newStoredWorkspaceFolders);
S
Sandeep Somavarapu 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
		}

		return TPromise.as(void 0);
	}

	private doRemoveFolders(foldersToRemove: URI[]): TPromise<void> {
		if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
			return TPromise.as(void 0); // we need a workspace to begin with
		}

		const currentWorkspaceFolders = this.getWorkspace().folders;
		const currentStoredFolders = currentWorkspaceFolders.map(folder => folder.raw);

		const newStoredFolders: IStoredWorkspaceFolder[] = currentStoredFolders.filter((folder, index) => {
			if (!isStoredWorkspaceFolder(folder)) {
				return true; // keep entries which are unrelated
			}

			return !this.contains(foldersToRemove, currentWorkspaceFolders[index].uri); // keep entries which are unrelated
		});

		if (newStoredFolders.length !== currentStoredFolders.length) {
S
Sandeep Somavarapu 已提交
211
			return this.setFolders(newStoredFolders);
S
Sandeep Somavarapu 已提交
212 213 214 215 216
		}

		return TPromise.as(void 0);
	}

S
Sandeep Somavarapu 已提交
217 218 219 220 221
	private setFolders(folders: IStoredWorkspaceFolder[]): TPromise<void> {
		return this.workspaceConfiguration.setFolders(folders, this.jsonEditingService)
			.then(() => this.onWorkspaceConfigurationChanged());
	}

S
Sandeep Somavarapu 已提交
222 223 224 225 226 227 228 229 230 231
	private contains(resources: URI[], toCheck: URI): boolean {
		return resources.some(resource => {
			if (isLinux) {
				return resource.toString() === toCheck.toString();
			}

			return resource.toString().toLowerCase() === toCheck.toString().toLowerCase();
		});
	}

232 233
	// Workspace Configuration Service Impl

234 235 236 237
	getConfigurationData(): IConfigurationData {
		return this._configuration.toData();
	}

238 239 240 241 242 243 244 245
	getValue<T>(): T;
	getValue<T>(section: string): T;
	getValue<T>(overrides: IConfigurationOverrides): T;
	getValue<T>(section: string, overrides: IConfigurationOverrides): T;
	getValue(arg1?: any, arg2?: any): any {
		const section = typeof arg1 === 'string' ? arg1 : void 0;
		const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0;
		return this._configuration.getValue(section, overrides);
246 247
	}

248 249 250 251 252
	updateValue(key: string, value: any): TPromise<void>;
	updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise<void>;
	updateValue(key: string, value: any, target: ConfigurationTarget): TPromise<void>;
	updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise<void>;
	updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError: boolean): TPromise<void>;
253
	updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): TPromise<void> {
254 255 256
		assert.ok(this.configurationEditingService, 'Workbench is not initialized yet');
		const overrides = isConfigurationOverrides(arg3) ? arg3 : void 0;
		const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3);
257
		return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError)
258
			: TPromise.as(null);
259 260
	}

261 262 263 264
	reloadConfiguration(folder?: IWorkspaceFolder, key?: string): TPromise<void> {
		if (folder) {
			return this.reloadWorkspaceFolderConfiguration(folder, key);
		}
S
Sandeep Somavarapu 已提交
265
		return this.reloadUserConfiguration()
S
Sandeep Somavarapu 已提交
266
			.then(() => this.reloadWorkspaceConfiguration())
S
Sandeep Somavarapu 已提交
267
			.then(() => this.loadConfiguration());
268
	}
269

270 271 272 273 274
	inspect<T>(key: string, overrides?: IConfigurationOverrides): {
		default: T,
		user: T,
		workspace: T,
		workspaceFolder: T,
275
		memory?: T,
276 277
		value: T
	} {
278
		return this._configuration.inspect<T>(key, overrides);
279 280
	}

281 282 283 284 285 286 287
	keys(): {
		default: string[];
		user: string[];
		workspace: string[];
		workspaceFolder: string[];
	} {
		return this._configuration.keys();
288 289
	}

290
	getUnsupportedWorkspaceKeys(): string[] {
S
Sandeep Somavarapu 已提交
291 292
		const unsupportedWorkspaceKeys = [...this.workspaceConfiguration.getWorkspaceSettings().unsupportedKeys];
		for (const folder of this.workspace.folders) {
S
Sandeep Somavarapu 已提交
293
			unsupportedWorkspaceKeys.push(...this.cachedFolderConfigs.get(folder.uri).getUnsupportedKeys());
S
Sandeep Somavarapu 已提交
294 295
		}
		return distinct(unsupportedWorkspaceKeys);
296 297
	}

298
	initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<any> {
299
		return this.createWorkspace(arg)
S
Sandeep Somavarapu 已提交
300
			.then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace));
301 302
	}

303
	setInstantiationService(instantiationService: IInstantiationService): void {
304
		this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService);
S
Sandeep Somavarapu 已提交
305
		this.jsonEditingService = instantiationService.createInstance(JSONEditingService);
306 307
	}

308
	handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<void> {
309 310
		switch (this.getWorkbenchState()) {
			case WorkbenchState.FOLDER:
311
				return this.onSingleFolderFileChanges(event);
312
			case WorkbenchState.WORKSPACE:
313
				return this.onWorkspaceFileChanges(event);
314
		}
315
		return TPromise.as(void 0);
316 317
	}

318
	private createWorkspace(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<Workspace> {
319
		if (isWorkspaceIdentifier(arg)) {
320
			return this.createMulitFolderWorkspace(arg);
321 322 323
		}

		if (isSingleFolderWorkspaceIdentifier(arg)) {
324
			return this.createSingleFolderWorkspace(arg);
325 326
		}

327
		return this.createEmptyWorkspace(arg);
328 329
	}

330
	private createMulitFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): TPromise<Workspace> {
331
		const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
332
		return this.workspaceConfiguration.load(workspaceConfigPath)
333
			.then(() => {
S
Sandeep Somavarapu 已提交
334
				const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(paths.dirname(workspaceConfigPath.fsPath)));
335
				const workspaceId = workspaceIdentifier.id;
336
				const workspaceName = getWorkspaceLabel({ id: workspaceId, configPath: workspaceConfigPath.fsPath }, this.environmentService);
337
				return new Workspace(workspaceId, workspaceName, workspaceFolders, workspaceConfigPath);
338 339 340
			});
	}

341
	private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): TPromise<Workspace> {
342
		const folderPath = URI.file(singleFolderWorkspaceIdentifier);
343
		return stat(folderPath.fsPath)
344 345
			.then(workspaceStat => {
				const ctime = isLinux ? workspaceStat.ino : workspaceStat.birthtime.getTime(); // On Linux, birthtime is ctime, so we cannot use it! We use the ino instead!
346 347
				const id = createHash('md5').update(folderPath.fsPath).update(ctime ? String(ctime) : '').digest('hex');
				const folder = URI.file(folderPath.fsPath);
B
Benjamin Pasero 已提交
348
				return new Workspace(id, getBaseLabel(folder), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
349 350 351
			});
	}

352
	private createEmptyWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
353
		let id = configuration.backupPath ? URI.from({ path: paths.basename(configuration.backupPath), scheme: 'empty' }).toString() : '';
354 355 356
		return TPromise.as(new Workspace(id));
	}

S
Sandeep Somavarapu 已提交
357
	private updateWorkspaceAndInitializeConfiguration(workspace: Workspace): TPromise<void> {
S
Sandeep Somavarapu 已提交
358 359 360 361 362 363 364 365 366
		const hasWorkspaceBefore = !!this.workspace;
		let previousState;
		let previousWorkspacePath;
		let previousFolders;

		if (hasWorkspaceBefore) {
			previousState = this.getWorkbenchState();
			previousWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
			previousFolders = this.workspace.folders;
S
Sandeep Somavarapu 已提交
367 368 369
			this.workspace.update(workspace);
		} else {
			this.workspace = workspace;
370
		}
S
Sandeep Somavarapu 已提交
371 372

		return this.initializeConfiguration().then(() => {
S
Sandeep Somavarapu 已提交
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
			// Trigger changes after configuration initialization so that configuration is up to date.
			if (hasWorkspaceBefore) {
				const newState = this.getWorkbenchState();
				if (previousState && newState !== previousState) {
					this._onDidChangeWorkbenchState.fire(newState);
				}

				const newWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
				if (previousWorkspacePath && newWorkspacePath !== previousWorkspacePath || newState !== previousState) {
					this._onDidChangeWorkspaceName.fire();
				}

				const folderChanges = this.compareFolders(previousFolders, this.workspace.folders);
				if (folderChanges && (folderChanges.added.length || folderChanges.removed.length || folderChanges.changed.length)) {
					this._onDidChangeWorkspaceFolders.fire(folderChanges);
				}
S
Sandeep Somavarapu 已提交
389 390
			}
		});
391 392
	}

S
Sandeep Somavarapu 已提交
393
	private compareFolders(currentFolders: IWorkspaceFolder[], newFolders: IWorkspaceFolder[]): IWorkspaceFoldersChangeEvent {
394 395
		const result = { added: [], removed: [], changed: [] };
		result.added = newFolders.filter(newFolder => !currentFolders.some(currentFolder => newFolder.uri.toString() === currentFolder.uri.toString()));
S
Sandeep Somavarapu 已提交
396 397 398 399 400 401 402 403 404 405 406 407
		for (let currentIndex = 0; currentIndex < currentFolders.length; currentIndex++) {
			let currentFolder = currentFolders[currentIndex];
			let newIndex = 0;
			for (newIndex = 0; newIndex < newFolders.length && currentFolder.uri.toString() !== newFolders[newIndex].uri.toString(); newIndex++) { }
			if (newIndex < newFolders.length) {
				if (currentIndex !== newIndex || currentFolder.name !== newFolders[newIndex].name) {
					result.changed.push(currentFolder);
				}
			} else {
				result.removed.push(currentFolder);
			}
		}
408 409 410
		return result;
	}

411
	private initializeConfiguration(): TPromise<void> {
S
Sandeep Somavarapu 已提交
412
		this.registerConfigurationSchemas();
413
		return this.loadConfiguration();
414 415
	}

S
Sandeep Somavarapu 已提交
416
	private reloadUserConfiguration(key?: string): TPromise<void> {
S
Sandeep Somavarapu 已提交
417
		return this.userConfiguration.reload();
S
Sandeep Somavarapu 已提交
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
	}

	private reloadWorkspaceConfiguration(key?: string): TPromise<void> {
		const workbenchState = this.getWorkbenchState();
		if (workbenchState === WorkbenchState.FOLDER) {
			return this.onWorkspaceFolderConfigurationChanged(this.workspace.folders[0], key);
		}
		if (workbenchState === WorkbenchState.WORKSPACE) {
			return this.workspaceConfiguration.reload().then(() => this.onWorkspaceConfigurationChanged());
		}
		return TPromise.as(null);
	}

	private reloadWorkspaceFolderConfiguration(folder: IWorkspaceFolder, key?: string): TPromise<void> {
		return this.onWorkspaceFolderConfigurationChanged(folder, key);
	}

435 436
	private loadConfiguration(): TPromise<void> {
		// reset caches
437
		this.cachedFolderConfigs = new StrictResourceMap<FolderConfiguration>();
438

439 440 441 442 443
		const folders = this.workspace.folders;
		return this.loadFolderConfigurations(folders)
			.then((folderConfigurations) => {

				let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations);
S
Sandeep Somavarapu 已提交
444
				const folderConfigurationModels = new StrictResourceMap<ConfigurationModel>();
445 446
				folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration));

447
				const currentConfiguration = this._configuration;
S
Sandeep Somavarapu 已提交
448
				this._configuration = new Configuration(this.defaultConfiguration, this.userConfiguration.configurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new StrictResourceMap<ConfigurationModel>(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: Sandy Avoid passing null
449

450 451 452 453
				if (currentConfiguration) {
					const changedKeys = this._configuration.compare(currentConfiguration);
					this.triggerConfigurationChange(new ConfigurationChangeEvent().change(changedKeys), ConfigurationTarget.WORKSPACE);
				} else {
S
Sandeep Somavarapu 已提交
454
					this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE)));
455
				}
456
			});
457 458
	}

S
Sandeep Somavarapu 已提交
459
	private getWorkspaceConfigurationModel(folderConfigurations: ConfigurationModel[]): ConfigurationModel {
460 461 462 463
		switch (this.getWorkbenchState()) {
			case WorkbenchState.FOLDER:
				return folderConfigurations[0];
			case WorkbenchState.WORKSPACE:
S
Sandeep Somavarapu 已提交
464
				return this.workspaceConfiguration.getConfiguration();
465
			default:
466
				return new ConfigurationModel();
467
		}
468 469
	}

S
Sandeep Somavarapu 已提交
470 471 472 473 474
	private onDefaultConfigurationChanged(keys: string[]): void {
		this.defaultConfiguration = new DefaultConfigurationModel();
		this.registerConfigurationSchemas();
		if (this.workspace && this._configuration) {
			this._configuration.updateDefaultConfiguration(this.defaultConfiguration);
S
Sandeep Somavarapu 已提交
475 476 477 478 479 480
			if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
				this._configuration.updateWorkspaceConfiguration(this.cachedFolderConfigs.get(this.workspace.folders[0].uri).reprocess());
			} else {
				this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reprocessWorkspaceSettings());
				this.workspace.folders.forEach(folder => this._configuration.updateFolderConfiguration(folder.uri, this.cachedFolderConfigs.get(folder.uri).reprocess()));
			}
S
Sandeep Somavarapu 已提交
481 482 483 484
			this.triggerConfigurationChange(new ConfigurationChangeEvent().change(keys), ConfigurationTarget.DEFAULT);
		}
	}

S
Sandeep Somavarapu 已提交
485 486
	private registerConfigurationSchemas(): void {
		if (this.workspace) {
487 488 489
			const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
			jsonRegistry.registerSchema(defaultSettingsSchemaId, settingsSchema);
			jsonRegistry.registerSchema(userSettingsSchemaId, settingsSchema);
S
Sandeep Somavarapu 已提交
490 491

			if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) {
492 493
				jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema);
				jsonRegistry.registerSchema(folderSettingsSchemaId, resourceSettingsSchema);
S
Sandeep Somavarapu 已提交
494
			} else {
495 496
				jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema);
				jsonRegistry.registerSchema(folderSettingsSchemaId, settingsSchema);
S
Sandeep Somavarapu 已提交
497 498 499 500
			}
		}
	}

S
Sandeep Somavarapu 已提交
501 502 503
	private onUserConfigurationChanged(): void {
		let keys = this._configuration.compareAndUpdateUserConfiguration(this.userConfiguration.configurationModel);
		this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
504 505
	}

506 507
	private onWorkspaceConfigurationChanged(): TPromise<void> {
		if (this.workspace && this.workspace.configuration && this._configuration) {
S
Sandeep Somavarapu 已提交
508
			const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration());
S
Sandeep Somavarapu 已提交
509
			let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(paths.dirname(this.workspace.configuration.fsPath)));
510
			const changes = this.compareFolders(this.workspace.folders, configuredFolders);
511
			if (changes.added.length || changes.removed.length || changes.changed.length) {
512
				this.workspace.folders = configuredFolders;
513
				return this.onFoldersChanged()
514 515
					.then(foldersConfigurationChangeEvent => {
						this.triggerConfigurationChange(foldersConfigurationChangeEvent.change(workspaceConfigurationChangeEvent), ConfigurationTarget.WORKSPACE_FOLDER);
S
Sandeep Somavarapu 已提交
516
						this._onDidChangeWorkspaceFolders.fire(changes);
517 518
					});
			} else {
519
				this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE);
S
Sandeep Somavarapu 已提交
520
			}
521
		}
522 523 524 525 526 527 528 529
		return TPromise.as(null);
	}

	private onWorkspaceFileChanges(event: FileChangesEvent): TPromise<void> {
		return TPromise.join(this.workspace.folders.map(folder =>
			// handle file event for each folder
			this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event)
				// Update folder configuration if handled
S
Sandeep Somavarapu 已提交
530
				.then(folderConfiguration => folderConfiguration ? this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration) : new ConfigurationChangeEvent()))
531 532 533 534
		).then(changeEvents => {
			const consolidateChangeEvent = changeEvents.reduce((consolidated, e) => consolidated.change(e), new ConfigurationChangeEvent());
			this.triggerConfigurationChange(consolidateChangeEvent, ConfigurationTarget.WORKSPACE_FOLDER);
		});
535 536
	}

537 538 539 540 541 542
	private onSingleFolderFileChanges(event: FileChangesEvent): TPromise<void> {
		const folder = this.workspace.folders[0];
		return this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event)
			.then(folderConfiguration => {
				if (folderConfiguration) {
					// File change handled
S
Sandeep Somavarapu 已提交
543 544
					this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
545 546 547 548 549 550 551 552 553
					this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE);
				}
			});
	}

	private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): TPromise<void> {
		this.disposeFolderConfiguration(folder);
		return this.loadFolderConfigurations([folder])
			.then(([folderConfiguration]) => {
S
Sandeep Somavarapu 已提交
554
				const folderChangedKeys = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
555
				if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
S
Sandeep Somavarapu 已提交
556
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
557 558 559 560 561 562 563
					this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE);
				} else {
					this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER);
				}
			});
	}

564 565
	private onFoldersChanged(): TPromise<ConfigurationChangeEvent> {
		let changeEvent = new ConfigurationChangeEvent();
566

567 568
		// Remove the configurations of deleted folders
		for (const key of this.cachedFolderConfigs.keys()) {
569
			if (!this.workspace.folders.filter(folder => folder.uri.toString() === key.toString())[0]) {
570
				this.cachedFolderConfigs.delete(key);
S
Sandeep Somavarapu 已提交
571
				changeEvent = changeEvent.change(this._configuration.compareAndDeleteFolderConfiguration(key));
572 573 574
			}
		}

575
		const toInitialize = this.workspace.folders.filter(folder => !this.cachedFolderConfigs.has(folder.uri));
576
		if (toInitialize.length) {
577 578 579
			return this.loadFolderConfigurations(toInitialize)
				.then(folderConfigurations => {
					folderConfigurations.forEach((folderConfiguration, index) => {
S
Sandeep Somavarapu 已提交
580
						changeEvent = changeEvent.change(this._configuration.compareAndUpdateFolderConfiguration(toInitialize[index].uri, folderConfiguration));
581
					});
582
					return changeEvent;
583
				});
584
		}
585
		return TPromise.as(changeEvent);
586 587
	}

S
Sandeep Somavarapu 已提交
588
	private loadFolderConfigurations(folders: IWorkspaceFolder[]): TPromise<ConfigurationModel[]> {
589
		return TPromise.join([...folders.map(folder => {
S
Sandeep Somavarapu 已提交
590
			const folderConfiguration = new FolderConfiguration(folder.uri, this.workspaceSettingsRootFolder, this.getWorkbenchState());
591 592 593
			this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration));
			return folderConfiguration.loadConfiguration();
		})]);
594 595
	}

596
	private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides, donotNotifyError: boolean): TPromise<void> {
597 598 599 600 601 602
		if (target === ConfigurationTarget.DEFAULT) {
			return TPromise.wrapError(new Error('Invalid configuration target'));
		}

		if (target === ConfigurationTarget.MEMORY) {
			this._configuration.updateValue(key, value, overrides);
603
			this.triggerConfigurationChange(new ConfigurationChangeEvent().change(overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier)] : [key], overrides.resource), target);
604 605 606
			return TPromise.as(null);
		}

S
Sandeep Somavarapu 已提交
607
		return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError })
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
			.then(() => {
				switch (target) {
					case ConfigurationTarget.USER:
						return this.reloadUserConfiguration();
					case ConfigurationTarget.WORKSPACE:
						return this.reloadWorkspaceConfiguration();
					case ConfigurationTarget.WORKSPACE_FOLDER:
						const workspaceFolder = overrides && overrides.resource ? this.workspace.getFolder(overrides.resource) : null;
						if (workspaceFolder) {
							return this.reloadWorkspaceFolderConfiguration(this.workspace.getFolder(overrides.resource), key);
						}
				}
				return null;
			});
	}

	private deriveConfigurationTarget(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): ConfigurationTarget {
		if (target) {
			return target;
		}

		if (value === void 0) {
			// Ignore. But expected is to remove the value from all targets
			return void 0;
		}

		const inspect = this.inspect(key, overrides);
		if (equals(value, inspect.value)) {
			// No change. So ignore.
			return void 0;
638
		}
639 640 641 642 643 644 645 646 647 648

		if (inspect.workspaceFolder !== void 0) {
			return ConfigurationTarget.WORKSPACE_FOLDER;
		}

		if (inspect.workspace !== void 0) {
			return ConfigurationTarget.WORKSPACE;
		}

		return ConfigurationTarget.USER;
649 650
	}

651 652 653
	private triggerConfigurationChange(configurationEvent: ConfigurationChangeEvent, target: ConfigurationTarget): void {
		if (configurationEvent.affectedKeys.length) {
			configurationEvent.telemetryData(target, this.getTargetConfiguration(target));
654
			this._onDidChangeConfiguration.fire(new WorkspaceConfigurationChangeEvent(configurationEvent, this.workspace));
655
		}
656
	}
657

658 659 660 661 662 663 664 665
	private getTargetConfiguration(target: ConfigurationTarget): any {
		switch (target) {
			case ConfigurationTarget.DEFAULT:
				return this._configuration.defaults.contents;
			case ConfigurationTarget.USER:
				return this._configuration.user.contents;
			case ConfigurationTarget.WORKSPACE:
				return this._configuration.workspace.contents;
666
		}
667
		return {};
668
	}
669 670 671 672 673 674 675 676 677

	private pathEquals(path1: string, path2: string): boolean {
		if (!isLinux) {
			path1 = path1.toLowerCase();
			path2 = path2.toLowerCase();
		}

		return path1 === path2;
	}
678 679 680 681 682 683 684

	private disposeFolderConfiguration(folder: IWorkspaceFolder): void {
		const folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
		if (folderConfiguration) {
			folderConfiguration.dispose();
		}
	}
685
}
686

687 688 689 690 691 692 693 694 695 696 697 698 699
interface IExportedConfigurationNode {
	name: string;
	description: string;
	default: any;
	type: string | string[];
	enum?: any[];
	enumDescriptions?: string[];
}

interface IConfigurationExport {
	settings: IExportedConfigurationNode[];
	buildTime: number;
	commit: string;
700
	buildNumber: number;
701 702 703
}

export class DefaultConfigurationExportHelper {
704 705 706 707 708

	constructor(
		@IEnvironmentService environmentService: IEnvironmentService,
		@IExtensionService private extensionService: IExtensionService,
		@ICommandService private commandService: ICommandService) {
709 710
		if (environmentService.args['export-default-configuration']) {
			this.writeConfigModelAndQuit(environmentService.args['export-default-configuration']);
711 712 713 714
		}
	}

	private writeConfigModelAndQuit(targetPath: string): TPromise<void> {
715
		return this.extensionService.whenInstalledExtensionsRegistered()
716 717 718 719 720 721
			.then(() => this.writeConfigModel(targetPath))
			.then(() => this.commandService.executeCommand('workbench.action.quit'))
			.then(() => { });
	}

	private writeConfigModel(targetPath: string): TPromise<void> {
722 723 724 725 726 727 728
		const config = this.getConfigModel();

		const resultString = JSON.stringify(config, undefined, '  ');
		return writeFile(targetPath, resultString);
	}

	private getConfigModel(): IConfigurationExport {
729 730
		const configRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
		const configurations = configRegistry.getConfigurations().slice();
731
		const settings: IExportedConfigurationNode[] = [];
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751

		const processProperty = (name: string, prop: IConfigurationPropertySchema) => {
			const propDetails: IExportedConfigurationNode = {
				name,
				description: prop.description,
				default: prop.default,
				type: prop.type
			};

			if (prop.enum) {
				propDetails.enum = prop.enum;
			}

			if (prop.enumDescriptions) {
				propDetails.enumDescriptions = prop.enumDescriptions;
			}

			settings.push(propDetails);
		};

752 753 754
		const processConfig = (config: IConfigurationNode) => {
			if (config.properties) {
				for (let name in config.properties) {
755
					processProperty(name, config.properties[name]);
756 757 758 759 760 761 762 763
				}
			}

			if (config.allOf) {
				config.allOf.forEach(processConfig);
			}
		};

764
		configurations.forEach(processConfig);
765

766 767 768 769 770
		const excludedProps = configRegistry.getExcludedConfigurationProperties();
		for (let name in excludedProps) {
			processProperty(name, excludedProps[name]);
		}

771 772 773 774
		const result: IConfigurationExport = {
			settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
			buildTime: Date.now(),
			commit: product.commit,
775
			buildNumber: product.settingsSearchBuildId
776
		};
777

778
		return result;
779 780
	}
}