configurationService.ts 33.0 KB
Newer Older
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  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 { TPromise } from 'vs/base/common/winjs.base';
9
import { dirname, basename } from 'path';
10
import * as assert from 'vs/base/common/assert';
M
Matt Bierner 已提交
11
import { Event, Emitter } from 'vs/base/common/event';
12
import { StrictResourceMap } from 'vs/base/common/map';
13
import { equals } from 'vs/base/common/objects';
S
Sandeep Somavarapu 已提交
14 15 16
import { Disposable } from 'vs/base/common/lifecycle';
import { Queue } from 'vs/base/common/async';
import { stat, writeFile } from 'vs/base/node/pfs';
17
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
18
import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
S
Sandeep Somavarapu 已提交
19
import { FileChangesEvent } from 'vs/platform/files/common/files';
20
import { isLinux } from 'vs/base/common/platform';
21
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
S
Sandeep Somavarapu 已提交
22
import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
23
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
24 25
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';
26
import { Registry } from 'vs/platform/registry/common/platform';
27
import { IConfigurationNode, IConfigurationRegistry, Extensions, settingsSchema, resourceSettingsSchema, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
28
import { createHash } from 'crypto';
S
Sandeep Somavarapu 已提交
29
import { getWorkspaceLabel, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
30
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
31
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
32 33
import { ICommandService } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/node/product';
34 35
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
S
Sandeep Somavarapu 已提交
36 37 38 39
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 已提交
40
import { distinct } from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
41
import { UserConfiguration } from 'vs/platform/configuration/node/configuration';
B
Benjamin Pasero 已提交
42
import { getBaseLabel } from 'vs/base/common/labels';
43

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

	public _serviceBrand: any;

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

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

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

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

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

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

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

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

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

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

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

87 88
	// Workspace Context Service Impl

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

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

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

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

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

B
Benjamin Pasero 已提交
112
	public addFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number): TPromise<void> {
113
		return this.updateFolders(foldersToAdd, [], index);
S
Sandeep Somavarapu 已提交
114 115 116
	}

	public removeFolders(foldersToRemove: URI[]): TPromise<void> {
117 118 119 120
		return this.updateFolders([], foldersToRemove);
	}

	public updateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): TPromise<void> {
S
Sandeep Somavarapu 已提交
121
		assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
122
		return this.workspaceEditingQueue.queue(() => this.doUpdateFolders(foldersToAdd, foldersToRemove, index));
S
Sandeep Somavarapu 已提交
123 124
	}

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

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

139
	private doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): TPromise<void> {
S
Sandeep Somavarapu 已提交
140 141 142 143
		if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
			return TPromise.as(void 0); // we need a workspace to begin with
		}

144 145 146
		if (foldersToAdd.length + foldersToRemove.length === 0) {
			return TPromise.as(void 0); // nothing to do
		}
S
Sandeep Somavarapu 已提交
147

148
		let foldersHaveChanged = false;
S
Sandeep Somavarapu 已提交
149

150 151 152 153 154
		// Remove first (if any)
		let currentWorkspaceFolders = this.getWorkspace().folders;
		let newStoredFolders: IStoredWorkspaceFolder[] = currentWorkspaceFolders.map(f => f.raw).filter((folder, index) => {
			if (!isStoredWorkspaceFolder(folder)) {
				return true; // keep entries which are unrelated
S
Sandeep Somavarapu 已提交
155 156
			}

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

160
		foldersHaveChanged = currentWorkspaceFolders.length !== newStoredFolders.length;
S
Sandeep Somavarapu 已提交
161

162 163
		// Add afterwards (if any)
		if (foldersToAdd.length) {
164

165 166 167 168
			// Recompute current workspace folders if we have folders to add
			const workspaceConfigFolder = dirname(this.getWorkspace().configuration.fsPath);
			currentWorkspaceFolders = toWorkspaceFolders(newStoredFolders, URI.file(workspaceConfigFolder));
			const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri);
169

170
			const storedFoldersToAdd: IStoredWorkspaceFolder[] = [];
S
Sandeep Somavarapu 已提交
171

172 173 174 175
			foldersToAdd.forEach(folderToAdd => {
				if (this.contains(currentWorkspaceFolderUris, folderToAdd.uri)) {
					return; // already existing
				}
B
Benjamin Pasero 已提交
176

177
				let storedFolder: IStoredWorkspaceFolder;
B
Benjamin Pasero 已提交
178

179 180 181 182 183 184
				// File resource: use "path" property
				if (folderToAdd.uri.scheme === Schemas.file) {
					storedFolder = {
						path: massageFolderPathForWorkspace(folderToAdd.uri.fsPath, workspaceConfigFolder, newStoredFolders)
					};
				}
S
Sandeep Somavarapu 已提交
185

186 187 188 189 190 191
				// Any other resource: use "uri" property
				else {
					storedFolder = {
						uri: folderToAdd.uri.toString(true)
					};
				}
S
Sandeep Somavarapu 已提交
192

193 194 195
				if (folderToAdd.name) {
					storedFolder.name = folderToAdd.name;
				}
S
Sandeep Somavarapu 已提交
196

197 198
				storedFoldersToAdd.push(storedFolder);
			});
S
Sandeep Somavarapu 已提交
199

200 201 202
			// Apply to array of newStoredFolders
			if (storedFoldersToAdd.length > 0) {
				foldersHaveChanged = true;
S
Sandeep Somavarapu 已提交
203

204 205 206 207 208 209 210 211
				if (typeof index === 'number' && index >= 0 && index < newStoredFolders.length) {
					newStoredFolders = newStoredFolders.slice(0);
					newStoredFolders.splice(index, 0, ...storedFoldersToAdd);
				} else {
					newStoredFolders = [...newStoredFolders, ...storedFoldersToAdd];
				}
			}
		}
S
Sandeep Somavarapu 已提交
212

213 214
		// Set folders if we recorded a change
		if (foldersHaveChanged) {
S
Sandeep Somavarapu 已提交
215
			return this.setFolders(newStoredFolders);
S
Sandeep Somavarapu 已提交
216 217 218 219 220
		}

		return TPromise.as(void 0);
	}

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

S
Sandeep Somavarapu 已提交
226 227 228 229 230 231 232 233 234 235
	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();
		});
	}

236 237
	// Workspace Configuration Service Impl

238 239 240 241
	getConfigurationData(): IConfigurationData {
		return this._configuration.toData();
	}

242 243 244 245 246 247 248 249
	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);
250 251
	}

252 253 254 255 256
	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>;
257
	updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): TPromise<void> {
258 259 260
		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);
261
		return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError)
262
			: TPromise.as(null);
263 264
	}

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

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

285 286 287 288 289 290 291
	keys(): {
		default: string[];
		user: string[];
		workspace: string[];
		workspaceFolder: string[];
	} {
		return this._configuration.keys();
292 293
	}

294
	getUnsupportedWorkspaceKeys(): string[] {
295
		const unsupportedWorkspaceKeys = [...this.workspaceConfiguration.getUnsupportedKeys()];
S
Sandeep Somavarapu 已提交
296
		for (const folder of this.workspace.folders) {
S
Sandeep Somavarapu 已提交
297
			unsupportedWorkspaceKeys.push(...this.cachedFolderConfigs.get(folder.uri).getUnsupportedKeys());
S
Sandeep Somavarapu 已提交
298 299
		}
		return distinct(unsupportedWorkspaceKeys);
300 301
	}

302
	initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<any> {
303
		return this.createWorkspace(arg)
S
Sandeep Somavarapu 已提交
304
			.then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace));
305 306
	}

307
	setInstantiationService(instantiationService: IInstantiationService): void {
308
		this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService);
S
Sandeep Somavarapu 已提交
309
		this.jsonEditingService = instantiationService.createInstance(JSONEditingService);
310 311
	}

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

322
	private createWorkspace(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<Workspace> {
323
		if (isWorkspaceIdentifier(arg)) {
324
			return this.createMulitFolderWorkspace(arg);
325 326 327
		}

		if (isSingleFolderWorkspaceIdentifier(arg)) {
328
			return this.createSingleFolderWorkspace(arg);
329 330
		}

331
		return this.createEmptyWorkspace(arg);
332 333
	}

334
	private createMulitFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): TPromise<Workspace> {
335
		const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
336
		return this.workspaceConfiguration.load(workspaceConfigPath)
337
			.then(() => {
338
				const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(dirname(workspaceConfigPath.fsPath)));
339
				const workspaceId = workspaceIdentifier.id;
340
				const workspaceName = getWorkspaceLabel({ id: workspaceId, configPath: workspaceConfigPath.fsPath }, this.environmentService);
341
				return new Workspace(workspaceId, workspaceName, workspaceFolders, workspaceConfigPath);
342 343 344
			});
	}

345
	private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): TPromise<Workspace> {
346
		const folderPath = URI.file(singleFolderWorkspaceIdentifier);
347
		return stat(folderPath.fsPath)
348 349
			.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!
350 351
				const id = createHash('md5').update(folderPath.fsPath).update(ctime ? String(ctime) : '').digest('hex');
				const folder = URI.file(folderPath.fsPath);
B
Benjamin Pasero 已提交
352
				return new Workspace(id, getBaseLabel(folder), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
353 354 355
			});
	}

356
	private createEmptyWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
357
		let id = configuration.backupPath ? URI.from({ path: basename(configuration.backupPath), scheme: 'empty' }).toString() : '';
358 359 360
		return TPromise.as(new Workspace(id));
	}

S
Sandeep Somavarapu 已提交
361
	private updateWorkspaceAndInitializeConfiguration(workspace: Workspace): TPromise<void> {
S
Sandeep Somavarapu 已提交
362
		const hasWorkspaceBefore = !!this.workspace;
363 364 365
		let previousState: WorkbenchState;
		let previousWorkspacePath: string;
		let previousFolders: WorkspaceFolder[];
S
Sandeep Somavarapu 已提交
366 367 368 369 370

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

		return this.initializeConfiguration().then(() => {
S
Sandeep Somavarapu 已提交
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
			// 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 已提交
393 394
			}
		});
395 396
	}

S
Sandeep Somavarapu 已提交
397
	private compareFolders(currentFolders: IWorkspaceFolder[], newFolders: IWorkspaceFolder[]): IWorkspaceFoldersChangeEvent {
398
		const result = { added: [], removed: [], changed: [] } as IWorkspaceFoldersChangeEvent;
399
		result.added = newFolders.filter(newFolder => !currentFolders.some(currentFolder => newFolder.uri.toString() === currentFolder.uri.toString()));
S
Sandeep Somavarapu 已提交
400 401 402 403 404 405 406 407 408 409 410 411
		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);
			}
		}
412 413 414
		return result;
	}

415
	private initializeConfiguration(): TPromise<void> {
S
Sandeep Somavarapu 已提交
416
		this.registerConfigurationSchemas();
417
		return this.loadConfiguration();
418 419
	}

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

	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);
	}

439 440
	private loadConfiguration(): TPromise<void> {
		// reset caches
441
		this.cachedFolderConfigs = new StrictResourceMap<FolderConfiguration>();
442

443 444 445 446 447
		const folders = this.workspace.folders;
		return this.loadFolderConfigurations(folders)
			.then((folderConfigurations) => {

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

451
				const currentConfiguration = this._configuration;
S
Sandeep Somavarapu 已提交
452
				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
453

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

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

S
Sandeep Somavarapu 已提交
474 475 476 477 478
	private onDefaultConfigurationChanged(keys: string[]): void {
		this.defaultConfiguration = new DefaultConfigurationModel();
		this.registerConfigurationSchemas();
		if (this.workspace && this._configuration) {
			this._configuration.updateDefaultConfiguration(this.defaultConfiguration);
S
Sandeep Somavarapu 已提交
479 480 481 482 483 484
			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 已提交
485 486 487 488
			this.triggerConfigurationChange(new ConfigurationChangeEvent().change(keys), ConfigurationTarget.DEFAULT);
		}
	}

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

			if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) {
496 497
				jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema);
				jsonRegistry.registerSchema(folderSettingsSchemaId, resourceSettingsSchema);
S
Sandeep Somavarapu 已提交
498
			} else {
499 500
				jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema);
				jsonRegistry.registerSchema(folderSettingsSchemaId, settingsSchema);
S
Sandeep Somavarapu 已提交
501 502 503 504
			}
		}
	}

S
Sandeep Somavarapu 已提交
505 506 507
	private onUserConfigurationChanged(): void {
		let keys = this._configuration.compareAndUpdateUserConfiguration(this.userConfiguration.configurationModel);
		this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
508 509
	}

510 511
	private onWorkspaceConfigurationChanged(): TPromise<void> {
		if (this.workspace && this.workspace.configuration && this._configuration) {
S
Sandeep Somavarapu 已提交
512
			const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration());
513
			let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(dirname(this.workspace.configuration.fsPath)));
514
			const changes = this.compareFolders(this.workspace.folders, configuredFolders);
515
			if (changes.added.length || changes.removed.length || changes.changed.length) {
516
				this.workspace.folders = configuredFolders;
517
				return this.onFoldersChanged()
518 519
					.then(foldersConfigurationChangeEvent => {
						this.triggerConfigurationChange(foldersConfigurationChangeEvent.change(workspaceConfigurationChangeEvent), ConfigurationTarget.WORKSPACE_FOLDER);
S
Sandeep Somavarapu 已提交
520
						this._onDidChangeWorkspaceFolders.fire(changes);
521 522
					});
			} else {
523
				this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE);
S
Sandeep Somavarapu 已提交
524
			}
525
		}
526 527 528 529 530 531 532 533
		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 已提交
534
				.then(folderConfiguration => folderConfiguration ? this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration) : new ConfigurationChangeEvent()))
535 536 537 538
		).then(changeEvents => {
			const consolidateChangeEvent = changeEvents.reduce((consolidated, e) => consolidated.change(e), new ConfigurationChangeEvent());
			this.triggerConfigurationChange(consolidateChangeEvent, ConfigurationTarget.WORKSPACE_FOLDER);
		});
539 540
	}

541 542 543 544 545 546
	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 已提交
547 548
					this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
549 550 551 552 553 554 555 556 557
					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 已提交
558
				const folderChangedKeys = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
559
				if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
S
Sandeep Somavarapu 已提交
560
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
561 562 563 564 565 566 567
					this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE);
				} else {
					this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER);
				}
			});
	}

568 569
	private onFoldersChanged(): TPromise<ConfigurationChangeEvent> {
		let changeEvent = new ConfigurationChangeEvent();
570

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

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

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

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

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

S
Sandeep Somavarapu 已提交
611
		return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError })
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 638 639 640 641
			.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;
642
		}
643 644 645 646 647 648 649 650 651 652

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

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

		return ConfigurationTarget.USER;
653 654
	}

655 656 657
	private triggerConfigurationChange(configurationEvent: ConfigurationChangeEvent, target: ConfigurationTarget): void {
		if (configurationEvent.affectedKeys.length) {
			configurationEvent.telemetryData(target, this.getTargetConfiguration(target));
658
			this._onDidChangeConfiguration.fire(new WorkspaceConfigurationChangeEvent(configurationEvent, this.workspace));
659
		}
660
	}
661

662 663 664 665 666 667 668 669
	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;
670
		}
671
		return {};
672
	}
673 674 675 676 677 678 679 680 681

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

		return path1 === path2;
	}
682 683 684 685 686 687 688

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

691 692 693 694 695 696 697 698 699 700 701 702 703
interface IExportedConfigurationNode {
	name: string;
	description: string;
	default: any;
	type: string | string[];
	enum?: any[];
	enumDescriptions?: string[];
}

interface IConfigurationExport {
	settings: IExportedConfigurationNode[];
	buildTime: number;
	commit: string;
704
	buildNumber: number;
705 706 707
}

export class DefaultConfigurationExportHelper {
708 709 710 711 712

	constructor(
		@IEnvironmentService environmentService: IEnvironmentService,
		@IExtensionService private extensionService: IExtensionService,
		@ICommandService private commandService: ICommandService) {
713 714
		if (environmentService.args['export-default-configuration']) {
			this.writeConfigModelAndQuit(environmentService.args['export-default-configuration']);
715 716 717 718
		}
	}

	private writeConfigModelAndQuit(targetPath: string): TPromise<void> {
719
		return this.extensionService.whenInstalledExtensionsRegistered()
720 721 722 723 724 725
			.then(() => this.writeConfigModel(targetPath))
			.then(() => this.commandService.executeCommand('workbench.action.quit'))
			.then(() => { });
	}

	private writeConfigModel(targetPath: string): TPromise<void> {
726 727 728 729 730 731 732
		const config = this.getConfigModel();

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

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

		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);
		};

756 757 758
		const processConfig = (config: IConfigurationNode) => {
			if (config.properties) {
				for (let name in config.properties) {
759
					processProperty(name, config.properties[name]);
760 761 762 763 764 765 766 767
				}
			}

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

768
		configurations.forEach(processConfig);
769

770 771 772 773 774
		const excludedProps = configRegistry.getExcludedConfigurationProperties();
		for (let name in excludedProps) {
			processProperty(name, excludedProps[name]);
		}

775 776 777 778
		const result: IConfigurationExport = {
			settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
			buildTime: Date.now(),
			commit: product.commit,
779
			buildNumber: product.settingsSearchBuildId
780
		};
781

782
		return result;
783 784
	}
}