extHostConfiguration.ts 11.9 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';
11
import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from '../common/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
	private readonly _proxy: MainThreadConfigurationShape;
	private readonly _extHostWorkspace: ExtHostWorkspace;
	private readonly _barrier: Barrier;
46
	private _actual: ExtHostConfigProvider | null;
47 48 49 50 51 52 53 54 55

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

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

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

	$acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData): void {
65
		this.getConfigProvider().then(provider => provider.$acceptConfigurationChanged(data, eventData));
66 67 68 69 70
	}
}

export class ExtHostConfigProvider {

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

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 97
			? lookUp(this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace), section)
			: this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace));
E
Erich Gamma 已提交
98

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

103
		function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null {
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 {
128
					let clonedConfig: any | undefined = undefined;
S
Sandeep Somavarapu 已提交
129
					const cloneOnWriteProxy = (target: any, accessor: string): any => {
130
						let clonedTarget: any | undefined = 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();
154 155 156
									if (clonedTarget) {
										clonedTarget[property] = value;
									}
S
Sandeep Somavarapu 已提交
157
									return true;
S
Sandeep Somavarapu 已提交
158
								},
M
Matt Bierner 已提交
159
								deleteProperty: (_target: any, property: string) => {
S
Sandeep Somavarapu 已提交
160
									cloneTarget();
161 162 163
									if (clonedTarget) {
										delete clonedTarget[property];
									}
S
Sandeep Somavarapu 已提交
164 165
									return true;
								},
M
Matt Bierner 已提交
166
								defineProperty: (_target: any, property: string, descriptor: any) => {
S
Sandeep Somavarapu 已提交
167
									cloneTarget();
168 169 170
									if (clonedTarget) {
										Object.defineProperty(clonedTarget, property, descriptor);
									}
S
Sandeep Somavarapu 已提交
171
									return true;
S
Sandeep Somavarapu 已提交
172 173 174 175
								}
							}) : target;
					};
					result = cloneOnWriteProxy(result, key);
176
				}
177
				return result;
178
			},
S
Sandeep Somavarapu 已提交
179
			update: (key: string, value: any, arg: ExtHostConfigurationTarget | boolean) => {
180
				key = section ? `${section}.${key}` : key;
S
Sandeep Somavarapu 已提交
181
				const target = parseConfigurationTarget(arg);
R
Rob Lourens 已提交
182
				if (value !== undefined) {
S
Sandeep Somavarapu 已提交
183
					return this._proxy.$updateConfigurationOption(target, key, value, resource);
184
				} else {
S
Sandeep Somavarapu 已提交
185
					return this._proxy.$removeConfigurationOption(target, key, resource);
186
				}
187
			},
188
			inspect: <T>(key: string): ConfigurationInspect<T> | undefined => {
189
				key = section ? `${section}.${key}` : key;
J
Johannes Rieken 已提交
190
				const config = deepClone(this._configuration.inspect<T>(key, { resource }, this._extHostWorkspace.workspace));
191
				if (config) {
S
Sandeep Somavarapu 已提交
192
					return {
193 194 195
						key,
						defaultValue: config.default,
						globalValue: config.user,
196
						workspaceValue: config.workspace,
197
						workspaceFolderValue: config.workspaceFolder
198 199
					};
				}
200
				return undefined;
E
Erich Gamma 已提交
201
			}
B
Benjamin Pasero 已提交
202
		};
203

204 205
		if (typeof config === 'object') {
			mixin(result, config, false);
206 207
		}

208
		return <vscode.WorkspaceConfiguration>Object.freeze(result);
209
	}
210

211
	private _toReadonlyValue(result: any): any {
212
		const readonlyProxy = (target: any): any => {
213 214 215
			return isObject(target) ?
				new Proxy(target, {
					get: (target: any, property: string) => readonlyProxy(target[property]),
M
Matt Bierner 已提交
216 217 218 219
					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`); },
220 221 222 223 224 225 226
					isExtensible: () => false,
					preventExtensions: () => true
				}) : target;
		};
		return readonlyProxy(result);
	}

227
	private _validateConfigurationAccess(key: string, resource: URI | undefined, extensionId?: ExtensionIdentifier): void {
S
Sandeep Somavarapu 已提交
228
		const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes[key];
229
		const extensionIdText = extensionId ? `[${extensionId.value}] ` : '';
S
Sandeep Somavarapu 已提交
230
		if (ConfigurationScope.RESOURCE === scope) {
R
Rob Lourens 已提交
231
			if (resource === undefined) {
232
				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 已提交
233 234 235 236 237 238 239 240 241 242 243 244
			}
			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 {
245
		const changedConfiguration = new ConfigurationModel(data.changedConfiguration.contents, data.changedConfiguration.keys, data.changedConfiguration.overrides);
246
		const changedConfigurationByResource: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>();
247 248 249 250 251
		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 已提交
252 253 254 255
		const event = new WorkspaceConfigurationChangeEvent(new ConfigurationChangeEvent(changedConfiguration, changedConfigurationByResource), this._extHostWorkspace.workspace);
		return Object.freeze({
			affectsConfiguration: (section: string, resource?: URI) => event.affectsConfiguration(section, resource)
		});
256
	}
257 258

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

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