configuration.ts 13.4 KB
Newer Older
S
Sandeep Somavarapu 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import URI from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { Emitter } from 'vs/base/common/event';
import { readFile } from 'vs/base/node/pfs';
import * as errors from 'vs/base/common/errors';
import * as collections from 'vs/base/common/collections';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
import { isLinux } from 'vs/base/common/platform';
import { ConfigWatcher } from 'vs/base/node/config';
S
Sandeep Somavarapu 已提交
18 19 20
import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels';
import { WorkspaceConfigurationModelParser, FolderSettingsModelParser, StandaloneConfigurationModelParser, WorkspaceSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels';
import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
21 22 23
import { IStoredWorkspace, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import * as extfs from 'vs/base/node/extfs';
import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService';
S
Sandeep Somavarapu 已提交
24 25
import { WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
26
import { relative } from 'path';
S
Sandeep Somavarapu 已提交
27
import { equals } from 'vs/base/common/objects';
S
Sandeep Somavarapu 已提交
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

// node.hs helper functions

interface IStat {
	resource: URI;
	isDirectory?: boolean;
	children?: { resource: URI; }[];
}

interface IContent {
	resource: URI;
	value: string;
}

function resolveContents(resources: URI[]): TPromise<IContent[]> {
	const contents: IContent[] = [];

	return TPromise.join(resources.map(resource => {
		return resolveContent(resource).then(content => {
			contents.push(content);
		});
	})).then(() => contents);
}

function resolveContent(resource: URI): TPromise<IContent> {
	return readFile(resource.fsPath).then(contents => ({ resource, value: contents.toString() }));
}

function resolveStat(resource: URI): TPromise<IStat> {
	return new TPromise<IStat>((c, e) => {
		extfs.readdir(resource.fsPath, (error, children) => {
			if (error) {
				if ((<any>error).code === 'ENOTDIR') {
					c({ resource });
				} else {
					e(error);
				}
			} else {
				c({
					resource,
					isDirectory: true,
					children: children.map(child => { return { resource: URI.file(paths.join(resource.fsPath, child)) }; })
				});
			}
		});
	});
}

export class WorkspaceConfiguration extends Disposable {

	private _workspaceConfigPath: URI;
S
Sandeep Somavarapu 已提交
79
	private _workspaceConfigurationWatcher: ConfigWatcher<WorkspaceConfigurationModelParser>;
S
Sandeep Somavarapu 已提交
80 81 82 83 84 85 86 87 88 89 90 91
	private _workspaceConfigurationWatcherDisposables: IDisposable[] = [];

	private _onDidUpdateConfiguration: Emitter<void> = this._register(new Emitter<void>());
	public readonly onDidUpdateConfiguration: Event<void> = this._onDidUpdateConfiguration.event;

	load(workspaceConfigPath: URI): TPromise<void> {
		if (this._workspaceConfigPath && this._workspaceConfigPath.fsPath === workspaceConfigPath.fsPath) {
			return this.reload();
		}

		this._workspaceConfigPath = workspaceConfigPath;

S
Sandeep Somavarapu 已提交
92
		this.stopListeningToWatcher();
S
Sandeep Somavarapu 已提交
93
		return new TPromise<void>((c, e) => {
S
Sandeep Somavarapu 已提交
94 95
			const defaultConfig = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath);
			defaultConfig.parse(JSON.stringify({ folders: [] } as IStoredWorkspace, null, '\t'));
S
Sandeep Somavarapu 已提交
96 97 98
			this._workspaceConfigurationWatcher = new ConfigWatcher(this._workspaceConfigPath.fsPath, {
				changeBufferDelay: 300,
				onError: error => errors.onUnexpectedError(error),
S
Sandeep Somavarapu 已提交
99
				defaultConfig,
S
Sandeep Somavarapu 已提交
100
				parse: (content: string, parseErrors: any[]) => {
S
Sandeep Somavarapu 已提交
101 102
					const workspaceConfigurationModel = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath);
					workspaceConfigurationModel.parse(content);
S
Sandeep Somavarapu 已提交
103 104 105 106
					parseErrors = [...workspaceConfigurationModel.errors];
					return workspaceConfigurationModel;
				}, initCallback: () => c(null)
			});
S
Sandeep Somavarapu 已提交
107
			this.listenToWatcher();
S
Sandeep Somavarapu 已提交
108 109 110
		});
	}

S
Sandeep Somavarapu 已提交
111 112
	private get workspaceConfigurationModelParser(): WorkspaceConfigurationModelParser {
		return this._workspaceConfigurationWatcher ? this._workspaceConfigurationWatcher.getConfig() : new WorkspaceConfigurationModelParser(this._workspaceConfigPath ? this._workspaceConfigPath.fsPath : '');
S
Sandeep Somavarapu 已提交
113 114 115
	}

	reload(): TPromise<void> {
S
Sandeep Somavarapu 已提交
116 117 118 119 120
		this.stopListeningToWatcher();
		return new TPromise<void>(c => this._workspaceConfigurationWatcher.reload(() => {
			this.listenToWatcher();
			c(null);
		}));
S
Sandeep Somavarapu 已提交
121 122 123
	}

	getFolders(): IStoredWorkspaceFolder[] {
S
Sandeep Somavarapu 已提交
124
		return this.workspaceConfigurationModelParser.folders;
S
Sandeep Somavarapu 已提交
125 126 127 128 129 130 131 132
	}

	setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: JSONEditingService): TPromise<void> {
		return jsonEditingService.write(this._workspaceConfigPath, { key: 'folders', value: folders }, true)
			.then(() => this.reload());
	}

	getConfiguration(): ConfigurationModel {
S
Sandeep Somavarapu 已提交
133
		return this.workspaceConfigurationModelParser.workspaceSettingsModel;
S
Sandeep Somavarapu 已提交
134 135
	}

S
Sandeep Somavarapu 已提交
136
	getWorkspaceSettings(): WorkspaceSettingsModel {
S
Sandeep Somavarapu 已提交
137 138 139 140 141 142
		return this.workspaceConfigurationModelParser.workspaceSettingsModel;
	}

	reprocessWorkspaceSettings(): ConfigurationModel {
		this.workspaceConfigurationModelParser.reprocessWorkspaceSettings();
		return this.getConfiguration();
S
Sandeep Somavarapu 已提交
143 144
	}

S
Sandeep Somavarapu 已提交
145 146 147 148 149 150 151 152 153
	private listenToWatcher() {
		this._workspaceConfigurationWatcherDisposables.push(this._workspaceConfigurationWatcher);
		this._workspaceConfigurationWatcher.onDidUpdateConfiguration(() => this._onDidUpdateConfiguration.fire(), this, this._workspaceConfigurationWatcherDisposables);
	}

	private stopListeningToWatcher() {
		this._workspaceConfigurationWatcherDisposables = dispose(this._workspaceConfigurationWatcherDisposables);
	}

S
Sandeep Somavarapu 已提交
154 155 156 157 158 159 160 161
	dispose(): void {
		dispose(this._workspaceConfigurationWatcherDisposables);
		super.dispose();
	}
}

export class FolderConfiguration extends Disposable {

162
	private static readonly RELOAD_CONFIGURATION_DELAY = 50;
S
Sandeep Somavarapu 已提交
163 164

	private bulkFetchFromWorkspacePromise: TPromise;
S
Sandeep Somavarapu 已提交
165 166 167 168 169
	private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise<ConfigurationModelParser> };

	private _folderSettingsModelParser: FolderSettingsModelParser;
	private _standAloneConfigurations: ConfigurationModel[] = [];
	private _cache: ConfigurationModel = new ConfigurationModel();
S
Sandeep Somavarapu 已提交
170 171

	private reloadConfigurationScheduler: RunOnceScheduler;
S
Sandeep Somavarapu 已提交
172
	private reloadConfigurationEventEmitter: Emitter<ConfigurationModel> = new Emitter<ConfigurationModel>();
S
Sandeep Somavarapu 已提交
173

S
Sandeep Somavarapu 已提交
174
	constructor(private folder: URI, private configFolderRelativePath: string, workbenchState: WorkbenchState) {
S
Sandeep Somavarapu 已提交
175 176
		super();

S
Sandeep Somavarapu 已提交
177
		this._folderSettingsModelParser = new FolderSettingsModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? ConfigurationScope.RESOURCE : void 0);
S
Sandeep Somavarapu 已提交
178 179 180 181
		this.workspaceFilePathToConfiguration = Object.create(null);
		this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configuration => this.reloadConfigurationEventEmitter.fire(configuration), errors.onUnexpectedError), FolderConfiguration.RELOAD_CONFIGURATION_DELAY));
	}

S
Sandeep Somavarapu 已提交
182
	loadConfiguration(): TPromise<ConfigurationModel> {
S
Sandeep Somavarapu 已提交
183 184
		// Load workspace locals
		return this.loadWorkspaceConfigFiles().then(workspaceConfigFiles => {
S
Sandeep Somavarapu 已提交
185
			this._standAloneConfigurations = Object.keys(workspaceConfigFiles).filter(key => key !== FOLDER_SETTINGS_PATH).map(key => <ConfigurationModel>workspaceConfigFiles[key].configurationModel);
S
Sandeep Somavarapu 已提交
186
			// Consolidate (support *.json files in the workspace settings folder)
S
Sandeep Somavarapu 已提交
187 188
			this.consolidate();
			return this._cache;
S
Sandeep Somavarapu 已提交
189 190 191
		});
	}

S
Sandeep Somavarapu 已提交
192
	reprocess(): ConfigurationModel {
S
Sandeep Somavarapu 已提交
193
		const oldContents = this._folderSettingsModelParser.folderSettingsModel.contents;
S
Sandeep Somavarapu 已提交
194
		this._folderSettingsModelParser.reprocess();
S
Sandeep Somavarapu 已提交
195
		if (!equals(oldContents, this._folderSettingsModelParser.folderSettingsModel.contents)) {
S
Sandeep Somavarapu 已提交
196 197 198 199 200 201 202 203 204 205 206 207 208 209
			this.consolidate();
		}
		return this._cache;
	}

	getUnsupportedKeys(): string[] {
		return this._folderSettingsModelParser.folderSettingsModel.unsupportedKeys;
	}

	private consolidate(): void {
		this._cache = this._folderSettingsModelParser.folderSettingsModel.merge(...this._standAloneConfigurations);
	}

	private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModelParser }> {
S
Sandeep Somavarapu 已提交
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
		// once: when invoked for the first time we fetch json files that contribute settings
		if (!this.bulkFetchFromWorkspacePromise) {
			this.bulkFetchFromWorkspacePromise = resolveStat(this.toResource(this.configFolderRelativePath)).then(stat => {
				if (!stat.isDirectory) {
					return TPromise.as([]);
				}

				return resolveContents(stat.children.filter(stat => {
					const isJson = paths.extname(stat.resource.fsPath) === '.json';
					if (!isJson) {
						return false; // only JSON files
					}

					return this.isWorkspaceConfigurationFile(this.toFolderRelativePath(stat.resource)); // only workspace config files
				}).map(stat => stat.resource));
			}, err => [] /* never fail this call */)
				.then((contents: IContent[]) => {
S
Sandeep Somavarapu 已提交
227
					contents.forEach(content => this.workspaceFilePathToConfiguration[this.toFolderRelativePath(content.resource)] = TPromise.as(this.createConfigurationModelParser(content)));
S
Sandeep Somavarapu 已提交
228 229 230 231 232 233 234 235
				}, errors.onUnexpectedError);
		}

		// on change: join on *all* configuration file promises so that we can merge them into a single configuration object. this
		// happens whenever a config file changes, is deleted, or added
		return this.bulkFetchFromWorkspacePromise.then(() => TPromise.join(this.workspaceFilePathToConfiguration));
	}

S
Sandeep Somavarapu 已提交
236
	public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<ConfigurationModel> {
S
Sandeep Somavarapu 已提交
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
		const events = event.changes;
		let affectedByChanges = false;

		// Find changes that affect workspace configuration files
		for (let i = 0, len = events.length; i < len; i++) {
			const resource = events[i].resource;
			const isJson = paths.extname(resource.fsPath) === '.json';
			const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && paths.isEqual(paths.basename(resource.fsPath), this.configFolderRelativePath));
			if (!isJson && !isDeletedSettingsFolder) {
				continue; // only JSON files or the actual settings folder
			}

			const workspacePath = this.toFolderRelativePath(resource);
			if (!workspacePath) {
				continue; // event is not inside workspace
			}

			// Handle case where ".vscode" got deleted
			if (workspacePath === this.configFolderRelativePath && events[i].type === FileChangeType.DELETED) {
				this.workspaceFilePathToConfiguration = Object.create(null);
				affectedByChanges = true;
			}

			// only valid workspace config files
			if (!this.isWorkspaceConfigurationFile(workspacePath)) {
				continue;
			}

			// insert 'fetch-promises' for add and update events and
			// remove promises for delete events
			switch (events[i].type) {
				case FileChangeType.DELETED:
					affectedByChanges = collections.remove(this.workspaceFilePathToConfiguration, workspacePath);
					break;
				case FileChangeType.UPDATED:
				case FileChangeType.ADDED:
S
Sandeep Somavarapu 已提交
273
					this.workspaceFilePathToConfiguration[workspacePath] = resolveContent(resource).then(content => this.createConfigurationModelParser(content), errors.onUnexpectedError);
S
Sandeep Somavarapu 已提交
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
					affectedByChanges = true;
			}
		}

		if (!affectedByChanges) {
			return TPromise.as(null);
		}

		return new TPromise((c, e) => {
			let disposable = this.reloadConfigurationEventEmitter.event(configuration => {
				disposable.dispose();
				c(configuration);
			});
			// trigger reload of the configuration if we are affected by changes
			if (!this.reloadConfigurationScheduler.isScheduled()) {
				this.reloadConfigurationScheduler.schedule();
			}
		});
	}

S
Sandeep Somavarapu 已提交
294
	private createConfigurationModelParser(content: IContent): ConfigurationModelParser {
S
Sandeep Somavarapu 已提交
295
		const path = this.toFolderRelativePath(content.resource);
S
Sandeep Somavarapu 已提交
296 297 298
		if (path === FOLDER_SETTINGS_PATH) {
			this._folderSettingsModelParser.parse(content.value);
			return this._folderSettingsModelParser;
S
Sandeep Somavarapu 已提交
299 300 301
		} else {
			const matches = /\/([^\.]*)*\.json/.exec(path);
			if (matches && matches[1]) {
S
Sandeep Somavarapu 已提交
302 303 304
				const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(content.resource.toString(), matches[1]);
				standAloneConfigurationModelParser.parse(content.value);
				return standAloneConfigurationModelParser;
S
Sandeep Somavarapu 已提交
305 306
			}
		}
S
Sandeep Somavarapu 已提交
307
		return new ConfigurationModelParser(null);
S
Sandeep Somavarapu 已提交
308 309 310
	}

	private isWorkspaceConfigurationFile(folderRelativePath: string): boolean {
S
Sandeep Somavarapu 已提交
311
		return [FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY], WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY]].some(p => p === folderRelativePath);
S
Sandeep Somavarapu 已提交
312 313 314 315 316 317 318 319 320 321 322 323
	}

	private toResource(folderRelativePath: string): URI {
		if (typeof folderRelativePath === 'string') {
			return URI.file(paths.join(this.folder.fsPath, folderRelativePath));
		}

		return null;
	}

	private toFolderRelativePath(resource: URI, toOSPath?: boolean): string {
		if (this.contains(resource)) {
324
			return paths.normalize(relative(this.folder.fsPath, resource.fsPath), toOSPath);
S
Sandeep Somavarapu 已提交
325 326 327 328 329 330 331 332 333 334 335 336 337
		}

		return null;
	}

	private contains(resource: URI): boolean {
		if (resource) {
			return paths.isEqualOrParent(resource.fsPath, this.folder.fsPath, !isLinux /* ignorecase */);
		}

		return false;
	}
}