extHostConfiguration.ts 11.6 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

J
Johannes Rieken 已提交
6
import { mixin, deepClone } from 'vs/base/common/objects';
7
import { URI } from 'vs/base/common/uri';
M
Matt Bierner 已提交
8
import { Event, Emitter } from 'vs/base/common/event';
9
import * as vscode from 'vscode';
10
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
S
Sandeep Somavarapu 已提交
11
import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from './extHost.protocol';
S
Sandeep Somavarapu 已提交
12
import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes';
13
import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
14
import { Configuration, ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
15
import { WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels';
16
import { ResourceMap } from 'vs/base/common/map';
S
Sandeep Somavarapu 已提交
17
import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
S
Sandeep Somavarapu 已提交
18
import { isObject } from 'vs/base/common/types';
19
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
20
import { Barrier } from 'vs/base/common/async';
S
Sandeep Somavarapu 已提交
21

22 23 24 25 26 27 28 29 30 31 32
function lookUp(tree: any, key: string) {
	if (key) {
		const parts = key.split('.');
		let node = tree;
		for (let i = 0; node && i < parts.length; i++) {
			node = node[parts[i]];
		}
		return node;
	}
}

S
Sandeep Somavarapu 已提交
33 34 35 36 37
type ConfigurationInspect<T> = {
	key: string;
	defaultValue?: T;
	globalValue?: T;
	workspaceValue?: T;
S
Sandeep Somavarapu 已提交
38
	workspaceFolderValue?: T;
S
Sandeep Somavarapu 已提交
39 40
};

41
export class ExtHostConfiguration implements ExtHostConfigurationShape {
E
Erich Gamma 已提交
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
	private readonly _proxy: MainThreadConfigurationShape;
	private readonly _extHostWorkspace: ExtHostWorkspace;
	private readonly _barrier: Barrier;
	private _actual: ExtHostConfigProvider;

	constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace) {
		this._proxy = proxy;
		this._extHostWorkspace = extHostWorkspace;
		this._barrier = new Barrier();
		this._actual = null;
	}

	public getConfigProvider(): Promise<ExtHostConfigProvider> {
		return this._barrier.wait().then(_ => this._actual);
	}

	$initializeConfiguration(data: IConfigurationInitData): void {
		this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data);
		this._barrier.open();
	}

	$acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData): void {
		this._actual.$acceptConfigurationChanged(data, eventData);
	}
}

export class ExtHostConfigProvider {

S
Sandeep Somavarapu 已提交
71
	private readonly _onDidChangeConfiguration = new Emitter<vscode.ConfigurationChangeEvent>();
J
Johannes Rieken 已提交
72 73
	private readonly _proxy: MainThreadConfigurationShape;
	private readonly _extHostWorkspace: ExtHostWorkspace;
S
Sandeep Somavarapu 已提交
74
	private _configurationScopes: { [key: string]: ConfigurationScope };
75
	private _configuration: Configuration;
E
Erich Gamma 已提交
76

S
Sandeep Somavarapu 已提交
77
	constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData) {
J
Johannes Rieken 已提交
78
		this._proxy = proxy;
J
Johannes Rieken 已提交
79
		this._extHostWorkspace = extHostWorkspace;
80
		this._configuration = ExtHostConfigProvider.parse(data);
S
Sandeep Somavarapu 已提交
81
		this._configurationScopes = data.configurationScopes;
E
Erich Gamma 已提交
82 83
	}

S
Sandeep Somavarapu 已提交
84
	get onDidChangeConfiguration(): Event<vscode.ConfigurationChangeEvent> {
E
Erich Gamma 已提交
85 86 87
		return this._onDidChangeConfiguration && this._onDidChangeConfiguration.event;
	}

88 89 90
	$acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData) {
		this._configuration = ExtHostConfigProvider.parse(data);
		this._configurationScopes = data.configurationScopes;
S
Sandeep Somavarapu 已提交
91
		this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(eventData));
E
Erich Gamma 已提交
92 93
	}

94
	getConfiguration(section?: string, resource?: URI, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration {
95
		const config = this._toReadonlyValue(section
96
			? lookUp(this._configuration.getValue(null, { resource }, this._extHostWorkspace.workspace), section)
97
			: this._configuration.getValue(null, { resource }, this._extHostWorkspace.workspace));
E
Erich Gamma 已提交
98

S
Sandeep Somavarapu 已提交
99 100 101 102
		if (section) {
			this._validateConfigurationAccess(section, resource, extensionId);
		}

S
Sandeep Somavarapu 已提交
103
		function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget {
R
Rob Lourens 已提交
104
			if (arg === undefined || arg === null) {
S
Sandeep Somavarapu 已提交
105
				return null;
S
Sandeep Somavarapu 已提交
106 107 108 109 110 111 112 113
			}
			if (typeof arg === 'boolean') {
				return arg ? ConfigurationTarget.USER : ConfigurationTarget.WORKSPACE;
			}

			switch (arg) {
				case ExtHostConfigurationTarget.Global: return ConfigurationTarget.USER;
				case ExtHostConfigurationTarget.Workspace: return ConfigurationTarget.WORKSPACE;
114
				case ExtHostConfigurationTarget.WorkspaceFolder: return ConfigurationTarget.WORKSPACE_FOLDER;
S
Sandeep Somavarapu 已提交
115 116 117
			}
		}

118
		const result: vscode.WorkspaceConfiguration = {
119
			has(key: string): boolean {
120
				return typeof lookUp(config, key) !== 'undefined';
121
			},
S
Sandeep Somavarapu 已提交
122 123
			get: <T>(key: string, defaultValue?: T) => {
				this._validateConfigurationAccess(section ? `${section}.${key}` : key, resource, extensionId);
124
				let result = lookUp(config, key);
125
				if (typeof result === 'undefined') {
126
					result = defaultValue;
S
Sandeep Somavarapu 已提交
127
				} else {
R
Rob Lourens 已提交
128
					let clonedConfig = undefined;
S
Sandeep Somavarapu 已提交
129
					const cloneOnWriteProxy = (target: any, accessor: string): any => {
R
Rob Lourens 已提交
130
						let clonedTarget = undefined;
S
Sandeep Somavarapu 已提交
131 132 133 134
						const cloneTarget = () => {
							clonedConfig = clonedConfig ? clonedConfig : deepClone(config);
							clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
						};
S
Sandeep Somavarapu 已提交
135 136 137
						return isObject(target) ?
							new Proxy(target, {
								get: (target: any, property: string) => {
S
Sandeep Somavarapu 已提交
138 139 140 141
									if (typeof property === 'string' && property.toLowerCase() === 'tojson') {
										cloneTarget();
										return () => clonedTarget;
									}
S
Sandeep Somavarapu 已提交
142 143 144 145 146
									if (clonedConfig) {
										clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
										return clonedTarget[property];
									}
									const result = target[property];
S
Sandeep Somavarapu 已提交
147
									if (typeof property === 'string') {
S
Sandeep Somavarapu 已提交
148 149 150 151
										return cloneOnWriteProxy(result, `${accessor}.${property}`);
									}
									return result;
								},
M
Matt Bierner 已提交
152
								set: (_target: any, property: string, value: any) => {
S
Sandeep Somavarapu 已提交
153
									cloneTarget();
S
Sandeep Somavarapu 已提交
154 155
									clonedTarget[property] = value;
									return true;
S
Sandeep Somavarapu 已提交
156
								},
M
Matt Bierner 已提交
157
								deleteProperty: (_target: any, property: string) => {
S
Sandeep Somavarapu 已提交
158 159 160 161
									cloneTarget();
									delete clonedTarget[property];
									return true;
								},
M
Matt Bierner 已提交
162
								defineProperty: (_target: any, property: string, descriptor: any) => {
S
Sandeep Somavarapu 已提交
163 164 165
									cloneTarget();
									Object.defineProperty(clonedTarget, property, descriptor);
									return true;
S
Sandeep Somavarapu 已提交
166 167 168 169
								}
							}) : target;
					};
					result = cloneOnWriteProxy(result, key);
170
				}
171
				return result;
172
			},
S
Sandeep Somavarapu 已提交
173
			update: (key: string, value: any, arg: ExtHostConfigurationTarget | boolean) => {
174
				key = section ? `${section}.${key}` : key;
S
Sandeep Somavarapu 已提交
175
				const target = parseConfigurationTarget(arg);
R
Rob Lourens 已提交
176
				if (value !== undefined) {
S
Sandeep Somavarapu 已提交
177
					return this._proxy.$updateConfigurationOption(target, key, value, resource);
178
				} else {
S
Sandeep Somavarapu 已提交
179
					return this._proxy.$removeConfigurationOption(target, key, resource);
180
				}
181
			},
S
Sandeep Somavarapu 已提交
182
			inspect: <T>(key: string): ConfigurationInspect<T> => {
183
				key = section ? `${section}.${key}` : key;
J
Johannes Rieken 已提交
184
				const config = deepClone(this._configuration.inspect<T>(key, { resource }, this._extHostWorkspace.workspace));
185
				if (config) {
S
Sandeep Somavarapu 已提交
186
					return {
187 188 189
						key,
						defaultValue: config.default,
						globalValue: config.user,
190
						workspaceValue: config.workspace,
191
						workspaceFolderValue: config.workspaceFolder
192 193
					};
				}
194
				return undefined;
E
Erich Gamma 已提交
195
			}
B
Benjamin Pasero 已提交
196
		};
197

198 199
		if (typeof config === 'object') {
			mixin(result, config, false);
200 201
		}

202
		return <vscode.WorkspaceConfiguration>Object.freeze(result);
203
	}
204

205 206 207 208 209
	private _toReadonlyValue(result: any): any {
		const readonlyProxy = (target) => {
			return isObject(target) ?
				new Proxy(target, {
					get: (target: any, property: string) => readonlyProxy(target[property]),
M
Matt Bierner 已提交
210 211 212 213
					set: (_target: any, property: string, _value: any) => { throw new Error(`TypeError: Cannot assign to read only property '${property}' of object`); },
					deleteProperty: (_target: any, property: string) => { throw new Error(`TypeError: Cannot delete read only property '${property}' of object`); },
					defineProperty: (_target: any, property: string) => { throw new Error(`TypeError: Cannot define property '${property}' for a readonly object`); },
					setPrototypeOf: (_target: any) => { throw new Error(`TypeError: Cannot set prototype for a readonly object`); },
214 215 216 217 218 219 220
					isExtensible: () => false,
					preventExtensions: () => true
				}) : target;
		};
		return readonlyProxy(result);
	}

221
	private _validateConfigurationAccess(key: string, resource: URI, extensionId: ExtensionIdentifier): void {
S
Sandeep Somavarapu 已提交
222
		const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes[key];
223
		const extensionIdText = extensionId ? `[${extensionId.value}] ` : '';
S
Sandeep Somavarapu 已提交
224
		if (ConfigurationScope.RESOURCE === scope) {
R
Rob Lourens 已提交
225
			if (resource === undefined) {
226
				console.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`);
S
Sandeep Somavarapu 已提交
227 228 229 230 231 232 233 234 235 236 237 238
			}
			return;
		}
		if (ConfigurationScope.WINDOW === scope) {
			if (resource) {
				console.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`);
			}
			return;
		}
	}

	private _toConfigurationChangeEvent(data: IWorkspaceConfigurationChangeEventData): vscode.ConfigurationChangeEvent {
239
		const changedConfiguration = new ConfigurationModel(data.changedConfiguration.contents, data.changedConfiguration.keys, data.changedConfiguration.overrides);
240
		const changedConfigurationByResource: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>();
241 242 243 244 245
		for (const key of Object.keys(data.changedConfigurationByResource)) {
			const resource = URI.parse(key);
			const model = data.changedConfigurationByResource[key];
			changedConfigurationByResource.set(resource, new ConfigurationModel(model.contents, model.keys, model.overrides));
		}
S
Sandeep Somavarapu 已提交
246 247 248 249
		const event = new WorkspaceConfigurationChangeEvent(new ConfigurationChangeEvent(changedConfiguration, changedConfigurationByResource), this._extHostWorkspace.workspace);
		return Object.freeze({
			affectsConfiguration: (section: string, resource?: URI) => event.affectsConfiguration(section, resource)
		});
250
	}
251 252

	private static parse(data: IConfigurationData): Configuration {
253 254 255
		const defaultConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.defaults);
		const userConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.user);
		const workspaceConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.workspace);
256
		const folders: ResourceMap<ConfigurationModel> = Object.keys(data.folders).reduce((result, key) => {
257
			result.set(URI.parse(key), ExtHostConfigProvider.parseConfigurationModel(data.folders[key]));
258
			return result;
259 260
		}, new ResourceMap<ConfigurationModel>());
		return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), false);
261 262 263 264 265
	}

	private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel {
		return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze();
	}
266
}