src/vs/workbench/api/common/extHostConfiguration.ts

347 LOC · 97 covered · 250 uncovered · 19 ranges · 107 concepts · 1 introducers · 71 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

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