Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/common/extHostConfiguration.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { mixin, deepClone } from '../../../base/common/objects.js';
7
import { Event, Emitter } from '../../../base/common/event.js';
8
import type * as vscode from 'vscode';
9
import { ExtHostWorkspace, IExtHostWorkspace } from './extHostWorkspace.js';
10
import { ExtHostConfigurationShape, MainThreadConfigurationShape, IConfigurationInitData, MainContext } from './extHost.protocol.js';
11
import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes.js';
12
import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } from '../../../platform/configuration/common/configuration.js';
13
import { Configuration, ConfigurationChangeEvent } from '../../../platform/configuration/common/configurationModels.js';
14
import { ConfigurationScope, OVERRIDE_PROPERTY_REGEX } from '../../../platform/configuration/common/configurationRegistry.js';
15
import { isObject } from '../../../base/common/types.js';
16
import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
17
import { Barrier } from '../../../base/common/async.js';
18
import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
19
import { IExtHostRpcService } from './extHostRpcService.js';
20
import { ILogService } from '../../../platform/log/common/log.js';
21
import { Workspace } from '../../../platform/workspace/common/workspace.js';
22
import { URI } from '../../../base/common/uri.js';
23
24
function lookUp(tree: any, key: string) {
25
if (key) {
26
const parts = key.split('.');
27
let node = tree;
28
for (let i = 0; node && i < parts.length; i++) {
29
node = node[parts[i]];
30
}
31
return node;
32
}
33
}
34
35
export type ConfigurationInspect<T> = {
36
key: string;
37
38
defaultValue?: T;
39
globalLocalValue?: T;
40
globalRemoteValue?: T;
41
globalValue?: T;
42
workspaceValue?: T;
43
workspaceFolderValue?: T;
44
45
defaultLanguageValue?: T;
46
globalLocalLanguageValue?: T;
47
globalRemoteLanguageValue?: T;
48
globalLanguageValue?: T;
49
workspaceLanguageValue?: T;
50
workspaceFolderLanguageValue?: T;
51
52
languageIds?: string[];
53
};
54
55
function isUri(thing: any): thing is vscode.Uri {
56
return thing instanceof URI;
57
}
58
59
function isResourceLanguage(thing: any): thing is { uri: URI; languageId: string } {
60
return thing
61
&& thing.uri instanceof URI
62
&& (thing.languageId && typeof thing.languageId === 'string');
63
}
64
65
function isLanguage(thing: any): thing is { languageId: string } {
66
return thing
67
&& !thing.uri
68
&& (thing.languageId && typeof thing.languageId === 'string');
69
}
70
71
function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder {
72
return thing
73
&& thing.uri instanceof URI
74
&& (!thing.name || typeof thing.name === 'string')
75
&& (!thing.index || typeof thing.index === 'number');
76
}
77
78
function scopeToOverrides(scope: vscode.ConfigurationScope | undefined | null): IConfigurationOverrides | undefined {
79
if (isUri(scope)) {
80
return { resource: scope };
81
}
82
if (isResourceLanguage(scope)) {
83
return { resource: scope.uri, overrideIdentifier: scope.languageId };
84
}
85
if (isLanguage(scope)) {
86
return { overrideIdentifier: scope.languageId };
87
}
88
if (isWorkspaceFolder(scope)) {
89
return { resource: scope.uri };
90
}
91
if (scope === null) {
92
return { resource: null };
93
}
94
return undefined;
95
}
96
97
export class ExtHostConfiguration implements ExtHostConfigurationShape {
98
99
readonly _serviceBrand: undefined;
100
101
private readonly _proxy: MainThreadConfigurationShape;
102
private readonly _logService: ILogService;
103
private readonly _extHostWorkspace: ExtHostWorkspace;
104
private readonly _barrier: Barrier;
105
private _actual: ExtHostConfigProvider | null;
106
107
constructor(
108
@IExtHostRpcService extHostRpc: IExtHostRpcService,
109
@IExtHostWorkspace extHostWorkspace: IExtHostWorkspace,
110
@ILogService logService: ILogService,
111
) {
112
this._proxy = extHostRpc.getProxy(MainContext.MainThreadConfiguration);
113
this._extHostWorkspace = extHostWorkspace;
114
this._logService = logService;
115
this._barrier = new Barrier();
116
this._actual = null;
117
}
118
119
public getConfigProvider(): Promise<ExtHostConfigProvider> {
120
return this._barrier.wait().then(_ => this._actual!);
121
}
122
123
$initializeConfiguration(data: IConfigurationInitData): void {
124
this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data, this._logService);
125
this._barrier.open();
126
}
127
128
$acceptConfigurationChanged(data: IConfigurationInitData, change: IConfigurationChange): void {
129
this.getConfigProvider().then(provider => provider.$acceptConfigurationChanged(data, change));
130
}
131
}
132
133
export class ExtHostConfigProvider {
134
135
private readonly _onDidChangeConfiguration = new Emitter<vscode.ConfigurationChangeEvent>();
136
private readonly _proxy: MainThreadConfigurationShape;
137
private readonly _extHostWorkspace: ExtHostWorkspace;
138
private _configurationScopes: Map<string, ConfigurationScope | undefined>;
139
private _configuration: Configuration;
140
private _logService: ILogService;
141
142
constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData, logService: ILogService) {
143
this._proxy = proxy;
144
this._logService = logService;
145
this._extHostWorkspace = extHostWorkspace;
146
this._configuration = Configuration.parse(data, logService);
147
this._configurationScopes = this._toMap(data.configurationScopes);
148
}
149
150
get onDidChangeConfiguration(): Event<vscode.ConfigurationChangeEvent> {
151
return this._onDidChangeConfiguration && this._onDidChangeConfiguration.event;
152
}
153
154
$acceptConfigurationChanged(data: IConfigurationInitData, change: IConfigurationChange) {
155
const previous = { data: this._configuration.toData(), workspace: this._extHostWorkspace.workspace };
156
this._configuration = Configuration.parse(data, this._logService);
157
this._configurationScopes = this._toMap(data.configurationScopes);
158
this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous));
159
}
160
161
getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionDescription?: IExtensionDescription): vscode.WorkspaceConfiguration {
162
const overrides = scopeToOverrides(scope) || {};
163
const config = this._toReadonlyValue(this._configuration.getValue(section, overrides, this._extHostWorkspace.workspace));
164
165
if (section) {
166
this._validateConfigurationAccess(section, overrides, extensionDescription?.identifier);
167
}
168
169
function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null {
170
if (arg === undefined || arg === null) {
171
return null;
172
}
173
if (typeof arg === 'boolean') {
174
return arg ? ConfigurationTarget.USER : ConfigurationTarget.WORKSPACE;
175
}
176
177
switch (arg) {
178
case ExtHostConfigurationTarget.Global: return ConfigurationTarget.USER;
179
case ExtHostConfigurationTarget.Workspace: return ConfigurationTarget.WORKSPACE;
180
case ExtHostConfigurationTarget.WorkspaceFolder: return ConfigurationTarget.WORKSPACE_FOLDER;
181
}
182
}
183
184
const result: vscode.WorkspaceConfiguration = {
185
has(key: string): boolean {
186
return typeof lookUp(config, key) !== 'undefined';
187
},
188
get: <T>(key: string, defaultValue?: T) => {
189
this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionDescription?.identifier);
190
let result = lookUp(config, key);
191
if (typeof result === 'undefined') {
192
result = defaultValue;
193
} else {
194
let clonedConfig: any | undefined = undefined;
195
const cloneOnWriteProxy = (target: any, accessor: string): any => {
196
if (isObject(target)) {
197
let clonedTarget: any | undefined = undefined;
198
const cloneTarget = () => {
199
clonedConfig = clonedConfig ? clonedConfig : deepClone(config);
200
clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
201
};
202
return new Proxy(target, {
203
get: (target: any, property: PropertyKey) => {
204
if (typeof property === 'string' && property.toLowerCase() === 'tojson') {
205
cloneTarget();
206
return () => clonedTarget;
207
}
208
if (clonedConfig) {
209
clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
210
return clonedTarget[property];
211
}
212
const result = target[property];
213
if (typeof property === 'string') {
214
return cloneOnWriteProxy(result, `${accessor}.${property}`);
215
}
216
return result;
217
},
218
set: (_target: any, property: PropertyKey, value: any) => {
219
cloneTarget();
220
if (clonedTarget) {
221
clonedTarget[property] = value;
222
}
223
return true;
224
},
225
deleteProperty: (_target: any, property: PropertyKey) => {
226
cloneTarget();
227
if (clonedTarget) {
228
delete clonedTarget[property];
229
}
230
return true;
231
},
232
defineProperty: (_target: any, property: PropertyKey, descriptor: any) => {
233
cloneTarget();
234
if (clonedTarget) {
235
Object.defineProperty(clonedTarget, property, descriptor);
236
}
237
return true;
238
}
239
});
240
}
241
if (Array.isArray(target)) {
242
return deepClone(target);
243
}
244
return target;
245
};
246
result = cloneOnWriteProxy(result, key);
247
}
248
return result;
249
},
250
update: (key: string, value: unknown, extHostConfigurationTarget: ExtHostConfigurationTarget | boolean, scopeToLanguage?: boolean) => {
251
key = section ? `${section}.${key}` : key;
252
const target = parseConfigurationTarget(extHostConfigurationTarget);
253
if (value !== undefined) {
254
return this._proxy.$updateConfigurationOption(target, key, value, overrides, scopeToLanguage);
255
} else {
256
return this._proxy.$removeConfigurationOption(target, key, overrides, scopeToLanguage);
257
}
258
},
259
inspect: <T>(key: string): ConfigurationInspect<T> | undefined => {
260
key = section ? `${section}.${key}` : key;
261
const config = this._configuration.inspect<T>(key, overrides, this._extHostWorkspace.workspace);
262
if (config) {
263
return {
264
key,
265
266
defaultValue: deepClone(config.policy?.value ?? config.default?.value),
267
globalLocalValue: deepClone(config.userLocal?.value),
268
globalRemoteValue: deepClone(config.userRemote?.value),
269
globalValue: deepClone(config.user?.value ?? config.application?.value),
270
workspaceValue: deepClone(config.workspace?.value),
271
workspaceFolderValue: deepClone(config.workspaceFolder?.value),
272
273
defaultLanguageValue: deepClone(config.default?.override),
274
globalLocalLanguageValue: deepClone(config.userLocal?.override),
275
globalRemoteLanguageValue: deepClone(config.userRemote?.override),
276
globalLanguageValue: deepClone(config.user?.override ?? config.application?.override),
277
workspaceLanguageValue: deepClone(config.workspace?.override),
278
workspaceFolderLanguageValue: deepClone(config.workspaceFolder?.override),
279
280
languageIds: deepClone(config.overrideIdentifiers)
281
};
282
}
283
return undefined;
284
}
285
};
286
287
if (typeof config === 'object') {
288
mixin(result, config, false);
289
}
290
291
return Object.freeze(result);
292
}
293
294
private _toReadonlyValue(result: any): any {
295
const readonlyProxy = (target: any): any => {
296
return isObject(target) ?
297
new Proxy(target, {
298
get: (target: any, property: PropertyKey) => readonlyProxy(target[property]),
299
set: (_target: any, property: PropertyKey, _value: any) => { throw new Error(`TypeError: Cannot assign to read only property '${String(property)}' of object`); },
300
deleteProperty: (_target: any, property: PropertyKey) => { throw new Error(`TypeError: Cannot delete read only property '${String(property)}' of object`); },
301
defineProperty: (_target: any, property: PropertyKey) => { throw new Error(`TypeError: Cannot define property '${String(property)}' for a readonly object`); },
302
setPrototypeOf: (_target: unknown) => { throw new Error(`TypeError: Cannot set prototype for a readonly object`); },
303
isExtensible: () => false,
304
preventExtensions: () => true
305
}) : target;
306
};
307
return readonlyProxy(result);
308
}
309
310
private _validateConfigurationAccess(key: string, overrides?: IConfigurationOverrides, extensionId?: ExtensionIdentifier): void {
311
const scope = OVERRIDE_PROPERTY_REGEX.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes.get(key);
312
const extensionIdText = extensionId ? `[${extensionId.value}] ` : '';
313
if (ConfigurationScope.RESOURCE === scope) {
314
if (typeof overrides?.resource === 'undefined') {
315
this._logService.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.`);
316
}
317
return;
318
}
319
if (ConfigurationScope.WINDOW === scope) {
320
if (overrides?.resource) {
321
this._logService.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'.`);
322
}
323
return;
324
}
325
}
326
327
private _toConfigurationChangeEvent(change: IConfigurationChange, previous: { data: IConfigurationData; workspace: Workspace | undefined }): vscode.ConfigurationChangeEvent {
328
const event = new ConfigurationChangeEvent(change, previous, this._configuration, this._extHostWorkspace.workspace, this._logService);
329
return Object.freeze({
330
affectsConfiguration: (section: string, scope?: vscode.ConfigurationScope) => event.affectsConfiguration(section, scopeToOverrides(scope))
331
});
332
}
333
334
private _toMap(scopes: [string, ConfigurationScope | undefined][]): Map<string, ConfigurationScope | undefined> {
335
return scopes.reduce((result, scope) => { result.set(scope[0], scope[1]); return result; }, new Map<string, ConfigurationScope | undefined>());
336
}
337
338
}
339
340
export const IExtHostConfiguration = createDecorator<IExtHostConfiguration>('IExtHostConfiguration');
341
export interface IExtHostConfiguration extends ExtHostConfiguration { }
342
343