UNPKG

28.4 kBPlain TextView Raw
1// *****************************************************************************
2// Copyright (C) 2018 Ericsson and others.
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License v. 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0.
7//
8// This Source Code may also be made available under the following Secondary
9// Licenses when the conditions for such availability set forth in the Eclipse
10// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11// with the GNU Classpath Exception which is available at
12// https://www.gnu.org/software/classpath/license.html.
13//
14// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15// *****************************************************************************
16
17/* eslint-disable @typescript-eslint/no-explicit-any */
18
19import { injectable, inject, postConstruct } from 'inversify';
20import { Event, Emitter, DisposableCollection, Disposable, deepFreeze, unreachable } from '../../common';
21import { Deferred } from '../../common/promise-util';
22import { PreferenceProvider, PreferenceProviderDataChange, PreferenceProviderDataChanges, PreferenceResolveResult } from './preference-provider';
23import { PreferenceSchemaProvider } from './preference-contribution';
24import URI from '../../common/uri';
25import { PreferenceScope } from './preference-scope';
26import { PreferenceConfigurations } from './preference-configurations';
27import { JSONExt, JSONValue } from '@phosphor/coreutils/lib/json';
28import { OverridePreferenceName, PreferenceLanguageOverrideService } from './preference-language-override-service';
29
30export { PreferenceScope };
31
32/**
33 * Representation of a preference change. A preference value can be set to `undefined` for a specific scope.
34 * This means that the value from a more general scope will be used.
35 */
36export interface PreferenceChange extends PreferenceProviderDataChange {
37 /**
38 * Tests wether the given resource is affected by the preference change.
39 * @param resourceUri the uri of the resource to test.
40 */
41 affects(resourceUri?: string): boolean;
42}
43
44export class PreferenceChangeImpl implements PreferenceChange {
45 protected readonly change: PreferenceProviderDataChange;
46 constructor(change: PreferenceProviderDataChange) {
47 this.change = deepFreeze(change);
48 }
49
50 get preferenceName(): string {
51 return this.change.preferenceName;
52 }
53 get newValue(): string {
54 return this.change.newValue;
55 }
56 get oldValue(): string {
57 return this.change.oldValue;
58 }
59 get scope(): PreferenceScope {
60 return this.change.scope;
61 }
62 get domain(): string[] | undefined {
63 return this.change.domain;
64 }
65
66 // TODO add tests
67 affects(resourceUri?: string): boolean {
68 const resourcePath = resourceUri && new URI(resourceUri).path;
69 const domain = this.change.domain;
70 return !resourcePath || !domain || domain.some(uri => new URI(uri).path.relativity(resourcePath) >= 0);
71 }
72}
73/**
74 * A key-value storage for {@link PreferenceChange}s. Used to aggregate multiple simultaneous preference changes.
75 */
76export interface PreferenceChanges {
77 [preferenceName: string]: PreferenceChange
78}
79
80export const PreferenceService = Symbol('PreferenceService');
81/**
82 * Service to manage preferences including, among others, getting and setting preference values as well
83 * as listening to preference changes.
84 *
85 * Depending on your use case you might also want to look at {@link createPreferenceProxy} with which
86 * you can easily create a typesafe schema-based interface for your preferences. Internally the proxy
87 * uses the PreferenceService so both approaches are compatible.
88 */
89export interface PreferenceService extends Disposable {
90 /**
91 * Promise indicating whether the service successfully initialized.
92 */
93 readonly ready: Promise<void>;
94 /**
95 * Indicates whether the service has successfully initialized. Will be `true` when {@link PreferenceService.ready the `ready` Promise} resolves.
96 */
97 readonly isReady: boolean;
98 /**
99 * Retrieve the stored value for the given preference.
100 *
101 * @param preferenceName the preference identifier.
102 *
103 * @returns the value stored for the given preference when it exists, `undefined` otherwise.
104 */
105 get<T>(preferenceName: string): T | undefined;
106 /**
107 * Retrieve the stored value for the given preference.
108 *
109 * @param preferenceName the preference identifier.
110 * @param defaultValue the value to return when no value for the given preference is stored.
111 *
112 * @returns the value stored for the given preference when it exists, otherwise the given default value.
113 */
114 get<T>(preferenceName: string, defaultValue: T): T;
115 /**
116 * Retrieve the stored value for the given preference and resourceUri.
117 *
118 * @param preferenceName the preference identifier.
119 * @param defaultValue the value to return when no value for the given preference is stored.
120 * @param resourceUri the uri of the resource for which the preference is stored. This used to retrieve
121 * a potentially different value for the same preference for different resources, for example `files.encoding`.
122 *
123 * @returns the value stored for the given preference and resourceUri when it exists, otherwise the given
124 * default value.
125 */
126 get<T>(preferenceName: string, defaultValue: T, resourceUri?: string): T;
127 /**
128 * Retrieve the stored value for the given preference and resourceUri.
129 *
130 * @param preferenceName the preference identifier.
131 * @param defaultValue the value to return when no value for the given preference is stored.
132 * @param resourceUri the uri of the resource for which the preference is stored. This used to retrieve
133 * a potentially different value for the same preference for different resources, for example `files.encoding`.
134 *
135 * @returns the value stored for the given preference and resourceUri when it exists, otherwise the given
136 * default value.
137 */
138 get<T>(preferenceName: string, defaultValue?: T, resourceUri?: string): T | undefined;
139 /**
140 * Sets the given preference to the given value.
141 *
142 * @param preferenceName the preference identifier.
143 * @param value the new value of the preference.
144 * @param scope the scope for which the value shall be set, i.e. user, workspace etc.
145 * When the folder scope is specified a resourceUri must be provided.
146 * @param resourceUri the uri of the resource for which the preference is stored. This used to store
147 * a potentially different value for the same preference for different resources, for example `files.encoding`.
148 *
149 * @returns a promise which resolves to `undefined` when setting the preference was successful. Otherwise it rejects
150 * with an error.
151 */
152 set(preferenceName: string, value: any, scope?: PreferenceScope, resourceUri?: string): Promise<void>;
153
154 /**
155 * Determines and applies the changes necessary to apply `value` to either the `resourceUri` supplied or the active session.
156 * If there is no setting for the `preferenceName`, the change will be applied in user scope.
157 * If there is a setting conflicting with the specified `value`, the change will be applied in the most specific scope with a conflicting value.
158 *
159 * @param preferenceName the identifier of the preference to modify.
160 * @param value the value to which to set the preference. `undefined` will reset the preference to its default value.
161 * @param resourceUri the uri of the resource to which the change is to apply. If none is provided, folder scope will be ignored.
162 */
163 updateValue(preferenceName: string, value: any, resourceUri?: string): Promise<void>
164
165 /**
166 * Registers a callback which will be called whenever a preference is changed.
167 */
168 onPreferenceChanged: Event<PreferenceChange>;
169 /**
170 * Registers a callback which will be called whenever one or more preferences are changed.
171 */
172 onPreferencesChanged: Event<PreferenceChanges>;
173 /**
174 * Retrieve the stored value for the given preference and resourceUri in all available scopes.
175 *
176 * @param preferenceName the preference identifier.
177 * @param resourceUri the uri of the resource for which the preference is stored.
178 * @param forceLanguageOverride if `true` and `preferenceName` is a language override, only values for the specified override will be returned.
179 * Otherwise, values for the override will be returned where defined, and values from the base preference will be returned otherwise.
180 *
181 * @return an object containing the value of the given preference for all scopes.
182 */
183 inspect<T extends JSONValue>(preferenceName: string, resourceUri?: string, forceLanguageOverride?: boolean): PreferenceInspection<T> | undefined;
184 /**
185 * For behavior, see {@link PreferenceService.inspect}.
186 *
187 * @returns the value in the scope specified.
188 */
189 inspectInScope<T extends JSONValue>(preferenceName: string, scope: PreferenceScope, resourceUri?: string, forceLanguageOverride?: boolean): T | undefined
190 /**
191 * Returns a new preference identifier based on the given OverridePreferenceName.
192 *
193 * @param options the override specification.
194 *
195 * @returns the calculated string based on the given OverridePreferenceName.
196 */
197 overridePreferenceName(options: OverridePreferenceName): string;
198 /**
199 * Tries to split the given preference identifier into the original OverridePreferenceName attributes
200 * with which this identifier was created. Returns `undefined` if this is not possible, for example
201 * when the given preference identifier was not generated by `overridePreferenceName`.
202 *
203 * This method is checked when resolving preferences. Therefore together with "overridePreferenceName"
204 * this can be used to handle specialized preferences, e.g. "[markdown].editor.autoIndent" and "editor.autoIndent".
205 *
206 * @param preferenceName the preferenceName which might have been created via {@link PreferenceService.overridePreferenceName}.
207 *
208 * @returns the OverridePreferenceName which was used to create the given `preferenceName` if this was the case,
209 * `undefined` otherwise.
210 */
211 overriddenPreferenceName(preferenceName: string): OverridePreferenceName | undefined;
212 /**
213 * Retrieve the stored value for the given preference and resourceUri.
214 *
215 * @param preferenceName the preference identifier.
216 * @param defaultValue the value to return when no value for the given preference is stored.
217 * @param resourceUri the uri of the resource for which the preference is stored. This used to retrieve
218 * a potentially different value for the same preference for different resources, for example `files.encoding`.
219 *
220 * @returns an object containing the value stored for the given preference and resourceUri when it exists,
221 * otherwise the given default value. If determinable the object will also contain the uri of the configuration
222 * resource in which the preference was stored.
223 */
224 resolve<T>(preferenceName: string, defaultValue?: T, resourceUri?: string): PreferenceResolveResult<T>;
225 /**
226 * Returns the uri of the configuration resource for the given scope and optional resource uri.
227 *
228 * @param scope the PreferenceScope to query for.
229 * @param resourceUri the optional uri of the resource-specific preference handling
230 * @param sectionName the optional preference section to query for.
231 *
232 * @returns the uri of the configuration resource for the given scope and optional resource uri it it exists,
233 * `undefined` otherwise.
234 */
235 getConfigUri(scope: PreferenceScope, resourceUri?: string, sectionName?: string): URI | undefined;
236}
237
238/**
239 * Return type of the {@link PreferenceService.inspect} call.
240 */
241export interface PreferenceInspection<T = JSONValue> {
242 /**
243 * The preference identifier.
244 */
245 preferenceName: string,
246 /**
247 * Value in default scope.
248 */
249 defaultValue: T | undefined,
250 /**
251 * Value in user scope.
252 */
253 globalValue: T | undefined,
254 /**
255 * Value in workspace scope.
256 */
257 workspaceValue: T | undefined,
258 /**
259 * Value in folder scope.
260 */
261 workspaceFolderValue: T | undefined,
262 /**
263 * The value that is active, i.e. the value set in the lowest scope available.
264 */
265 value: T | undefined;
266}
267
268export type PreferenceInspectionScope = keyof Omit<PreferenceInspection<unknown>, 'preferenceName'>;
269
270/**
271 * We cannot load providers directly in the case if they depend on `PreferenceService` somehow.
272 * It allows to load them lazily after DI is configured.
273 */
274export const PreferenceProviderProvider = Symbol('PreferenceProviderProvider');
275export type PreferenceProviderProvider = (scope: PreferenceScope, uri?: URI) => PreferenceProvider;
276
277@injectable()
278export class PreferenceServiceImpl implements PreferenceService {
279
280 protected readonly onPreferenceChangedEmitter = new Emitter<PreferenceChange>();
281 readonly onPreferenceChanged = this.onPreferenceChangedEmitter.event;
282
283 protected readonly onPreferencesChangedEmitter = new Emitter<PreferenceChanges>();
284 readonly onPreferencesChanged = this.onPreferencesChangedEmitter.event;
285
286 protected readonly toDispose = new DisposableCollection(this.onPreferenceChangedEmitter, this.onPreferencesChangedEmitter);
287
288 @inject(PreferenceSchemaProvider)
289 protected readonly schema: PreferenceSchemaProvider;
290
291 @inject(PreferenceProviderProvider)
292 protected readonly providerProvider: PreferenceProviderProvider;
293
294 @inject(PreferenceConfigurations)
295 protected readonly configurations: PreferenceConfigurations;
296
297 @inject(PreferenceLanguageOverrideService)
298 protected readonly preferenceOverrideService: PreferenceLanguageOverrideService;
299
300 protected readonly preferenceProviders = new Map<PreferenceScope, PreferenceProvider>();
301
302 protected async initializeProviders(): Promise<void> {
303 try {
304 for (const scope of PreferenceScope.getScopes()) {
305 const provider = this.providerProvider(scope);
306 this.preferenceProviders.set(scope, provider);
307 this.toDispose.push(provider.onDidPreferencesChanged(changes =>
308 this.reconcilePreferences(changes)
309 ));
310 await provider.ready;
311 }
312 this._ready.resolve();
313 this._isReady = true;
314 } catch (e) {
315 this._ready.reject(e);
316 }
317 }
318
319 @postConstruct()
320 protected init(): void {
321 this.toDispose.push(Disposable.create(() => this._ready.reject(new Error('preference service is disposed'))));
322 this.initializeProviders();
323 }
324
325 dispose(): void {
326 this.toDispose.dispose();
327 }
328
329 protected readonly _ready = new Deferred<void>();
330 get ready(): Promise<void> {
331 return this._ready.promise;
332 }
333
334 protected _isReady = false;
335 get isReady(): boolean {
336 return this._isReady;
337 }
338
339 protected reconcilePreferences(changes: PreferenceProviderDataChanges): void {
340 const changesToEmit: PreferenceChanges = {};
341 const acceptChange = (change: PreferenceProviderDataChange) =>
342 this.getAffectedPreferenceNames(change, preferenceName =>
343 changesToEmit[preferenceName] = new PreferenceChangeImpl({ ...change, preferenceName })
344 );
345
346 for (const preferenceName of Object.keys(changes)) {
347 let change = changes[preferenceName];
348 if (change.newValue === undefined) {
349 const overridden = this.overriddenPreferenceName(change.preferenceName);
350 if (overridden) {
351 change = {
352 ...change, newValue: this.doGet(overridden.preferenceName)
353 };
354 }
355 }
356 if (this.schema.isValidInScope(preferenceName, PreferenceScope.Folder)) {
357 acceptChange(change);
358 continue;
359 }
360 for (const scope of PreferenceScope.getReversedScopes()) {
361 if (this.schema.isValidInScope(preferenceName, scope)) {
362 const provider = this.getProvider(scope);
363 if (provider) {
364 const value = provider.get(preferenceName);
365 if (scope > change.scope && value !== undefined) {
366 // preference defined in a more specific scope
367 break;
368 } else if (scope === change.scope && change.newValue !== undefined) {
369 // preference is changed into something other than `undefined`
370 acceptChange(change);
371 } else if (scope < change.scope && change.newValue === undefined && value !== undefined) {
372 // preference is changed to `undefined`, use the value from a more general scope
373 change = {
374 ...change,
375 newValue: value,
376 scope
377 };
378 acceptChange(change);
379 }
380 }
381 } else if (change.newValue === undefined && change.scope === PreferenceScope.Default) {
382 // preference is removed
383 acceptChange(change);
384 break;
385 }
386 }
387 }
388
389 // emit the changes
390 const changedPreferenceNames = Object.keys(changesToEmit);
391 if (changedPreferenceNames.length > 0) {
392 this.onPreferencesChangedEmitter.fire(changesToEmit);
393 }
394 changedPreferenceNames.forEach(preferenceName => this.onPreferenceChangedEmitter.fire(changesToEmit[preferenceName]));
395 }
396 protected getAffectedPreferenceNames(change: PreferenceProviderDataChange, accept: (affectedPreferenceName: string) => void): void {
397 accept(change.preferenceName);
398 for (const overridePreferenceName of this.schema.getOverridePreferenceNames(change.preferenceName)) {
399 if (!this.doHas(overridePreferenceName)) {
400 accept(overridePreferenceName);
401 }
402 }
403 }
404
405 protected getProvider(scope: PreferenceScope): PreferenceProvider | undefined {
406 return this.preferenceProviders.get(scope);
407 }
408
409 has(preferenceName: string, resourceUri?: string): boolean {
410 return this.get(preferenceName, undefined, resourceUri) !== undefined;
411 }
412
413 get<T>(preferenceName: string): T | undefined;
414 get<T>(preferenceName: string, defaultValue: T): T;
415 get<T>(preferenceName: string, defaultValue: T, resourceUri: string): T;
416 get<T>(preferenceName: string, defaultValue?: T, resourceUri?: string): T | undefined;
417 get<T>(preferenceName: string, defaultValue?: T, resourceUri?: string): T | undefined {
418 return this.resolve<T>(preferenceName, defaultValue, resourceUri).value;
419 }
420
421 resolve<T>(preferenceName: string, defaultValue?: T, resourceUri?: string): PreferenceResolveResult<T> {
422 const { value, configUri } = this.doResolve(preferenceName, defaultValue, resourceUri);
423 if (value === undefined) {
424 const overridden = this.overriddenPreferenceName(preferenceName);
425 if (overridden) {
426 return this.doResolve(overridden.preferenceName, defaultValue, resourceUri);
427 }
428 }
429 return { value, configUri };
430 }
431
432 async set(preferenceName: string, value: any, scope: PreferenceScope | undefined, resourceUri?: string): Promise<void> {
433 const resolvedScope = scope ?? (!resourceUri ? PreferenceScope.Workspace : PreferenceScope.Folder);
434 if (resolvedScope === PreferenceScope.Folder && !resourceUri) {
435 throw new Error('Unable to write to Folder Settings because no resource is provided.');
436 }
437 const provider = this.getProvider(resolvedScope);
438 if (provider && await provider.setPreference(preferenceName, value, resourceUri)) {
439 return;
440 }
441 throw new Error(`Unable to write to ${PreferenceScope[resolvedScope]} Settings.`);
442 }
443
444 getBoolean(preferenceName: string): boolean | undefined;
445 getBoolean(preferenceName: string, defaultValue: boolean): boolean;
446 getBoolean(preferenceName: string, defaultValue: boolean, resourceUri: string): boolean;
447 getBoolean(preferenceName: string, defaultValue?: boolean, resourceUri?: string): boolean | undefined {
448 const value = resourceUri ? this.get(preferenceName, defaultValue, resourceUri) : this.get(preferenceName, defaultValue);
449 // eslint-disable-next-line no-null/no-null
450 return value !== null && value !== undefined ? !!value : defaultValue;
451 }
452
453 getString(preferenceName: string): string | undefined;
454 getString(preferenceName: string, defaultValue: string): string;
455 getString(preferenceName: string, defaultValue: string, resourceUri: string): string;
456 getString(preferenceName: string, defaultValue?: string, resourceUri?: string): string | undefined {
457 const value = resourceUri ? this.get(preferenceName, defaultValue, resourceUri) : this.get(preferenceName, defaultValue);
458 // eslint-disable-next-line no-null/no-null
459 if (value === null || value === undefined) {
460 return defaultValue;
461 }
462 return value.toString();
463 }
464
465 getNumber(preferenceName: string): number | undefined;
466 getNumber(preferenceName: string, defaultValue: number): number;
467 getNumber(preferenceName: string, defaultValue: number, resourceUri: string): number;
468 getNumber(preferenceName: string, defaultValue?: number, resourceUri?: string): number | undefined {
469 const value = resourceUri ? this.get(preferenceName, defaultValue, resourceUri) : this.get(preferenceName, defaultValue);
470 // eslint-disable-next-line no-null/no-null
471 if (value === null || value === undefined) {
472 return defaultValue;
473 }
474 if (typeof value === 'number') {
475 return value;
476 }
477 return Number(value);
478 }
479
480 inspect<T extends JSONValue>(preferenceName: string, resourceUri?: string, forceLanguageOverride?: boolean): PreferenceInspection<T> | undefined {
481 const defaultValue = this.inspectInScope<T>(preferenceName, PreferenceScope.Default, resourceUri, forceLanguageOverride);
482 const globalValue = this.inspectInScope<T>(preferenceName, PreferenceScope.User, resourceUri, forceLanguageOverride);
483 const workspaceValue = this.inspectInScope<T>(preferenceName, PreferenceScope.Workspace, resourceUri, forceLanguageOverride);
484 const workspaceFolderValue = this.inspectInScope<T>(preferenceName, PreferenceScope.Folder, resourceUri, forceLanguageOverride);
485
486 const valueApplied = workspaceFolderValue ?? workspaceValue ?? globalValue ?? defaultValue;
487
488 return { preferenceName, defaultValue, globalValue, workspaceValue, workspaceFolderValue, value: valueApplied };
489 }
490
491 inspectInScope<T extends JSONValue>(preferenceName: string, scope: PreferenceScope, resourceUri?: string, forceLanguageOverride?: boolean): T | undefined {
492 const value = this.doInspectInScope<T>(preferenceName, scope, resourceUri);
493 if (value === undefined && !forceLanguageOverride) {
494 const overridden = this.overriddenPreferenceName(preferenceName);
495 if (overridden) {
496 return this.doInspectInScope(overridden.preferenceName, scope, resourceUri);
497 }
498 }
499 return value;
500 }
501
502 protected getScopedValueFromInspection<T>(inspection: PreferenceInspection<T>, scope: PreferenceScope): T | undefined {
503 switch (scope) {
504 case PreferenceScope.Default:
505 return inspection.defaultValue;
506 case PreferenceScope.User:
507 return inspection.globalValue;
508 case PreferenceScope.Workspace:
509 return inspection.workspaceValue;
510 case PreferenceScope.Folder:
511 return inspection.workspaceFolderValue;
512 }
513 unreachable(scope, 'Not all PreferenceScope enum variants handled.');
514 }
515
516 async updateValue(preferenceName: string, value: any, resourceUri?: string): Promise<void> {
517 const inspection = this.inspect<any>(preferenceName, resourceUri);
518 if (inspection) {
519 const scopesToChange = this.getScopesToChange(inspection, value);
520 const isDeletion = value === undefined
521 || (scopesToChange.length === 1 && scopesToChange[0] === PreferenceScope.User && JSONExt.deepEqual(value, inspection.defaultValue));
522 const effectiveValue = isDeletion ? undefined : value;
523 await Promise.all(scopesToChange.map(scope => this.set(preferenceName, effectiveValue, scope, resourceUri)));
524 }
525 }
526
527 protected getScopesToChange(inspection: PreferenceInspection<any>, intendedValue: any): PreferenceScope[] {
528 if (JSONExt.deepEqual(inspection.value, intendedValue)) {
529 return [];
530 }
531
532 // Scopes in ascending order of scope breadth.
533 const allScopes = PreferenceScope.getReversedScopes();
534 // Get rid of Default scope. We can't set anything there.
535 allScopes.pop();
536
537 const isScopeDefined = (scope: PreferenceScope) => this.getScopedValueFromInspection(inspection, scope) !== undefined;
538
539 if (intendedValue === undefined) {
540 return allScopes.filter(isScopeDefined);
541 }
542
543 return [allScopes.find(isScopeDefined) ?? PreferenceScope.User];
544 }
545
546 overridePreferenceName(options: OverridePreferenceName): string {
547 return this.preferenceOverrideService.overridePreferenceName(options);
548 }
549 overriddenPreferenceName(preferenceName: string): OverridePreferenceName | undefined {
550 return this.preferenceOverrideService.overriddenPreferenceName(preferenceName);
551 }
552
553 protected doHas(preferenceName: string, resourceUri?: string): boolean {
554 return this.doGet(preferenceName, undefined, resourceUri) !== undefined;
555 }
556 protected doInspectInScope<T>(preferenceName: string, scope: PreferenceScope, resourceUri?: string): T | undefined {
557 const provider = this.getProvider(scope);
558 return provider && provider.get<T>(preferenceName, resourceUri);
559 }
560 protected doGet<T>(preferenceName: string, defaultValue?: T, resourceUri?: string): T | undefined {
561 return this.doResolve(preferenceName, defaultValue, resourceUri).value;
562 }
563 protected doResolve<T>(preferenceName: string, defaultValue?: T, resourceUri?: string): PreferenceResolveResult<T> {
564 const result: PreferenceResolveResult<T> = {};
565 for (const scope of PreferenceScope.getScopes()) {
566 if (this.schema.isValidInScope(preferenceName, scope)) {
567 const provider = this.getProvider(scope);
568 if (provider?.canHandleScope(scope)) {
569 const { configUri, value } = provider.resolve<T>(preferenceName, resourceUri);
570 if (value !== undefined) {
571 result.configUri = configUri;
572 result.value = PreferenceProvider.merge(result.value as any, value as any) as any;
573 }
574 }
575 }
576 }
577 return {
578 configUri: result.configUri,
579 value: result.value !== undefined ? deepFreeze(result.value) : defaultValue
580 };
581 }
582
583 getConfigUri(scope: PreferenceScope, resourceUri?: string, sectionName: string = this.configurations.getConfigName()): URI | undefined {
584 const provider = this.getProvider(scope);
585 if (!provider || !this.configurations.isAnyConfig(sectionName)) {
586 return undefined;
587 }
588 const configUri = provider.getConfigUri(resourceUri, sectionName);
589 if (configUri) {
590 return configUri;
591 }
592 return provider.getContainingConfigUri && provider.getContainingConfigUri(resourceUri, sectionName);
593 }
594}