Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/configuration/common/configurationRegistry.ts
5270 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 { distinct } from '../../../base/common/arrays.js';
7
import { IStringDictionary } from '../../../base/common/collections.js';
8
import { Emitter, Event } from '../../../base/common/event.js';
9
import { IJSONSchema } from '../../../base/common/jsonSchema.js';
10
import * as types from '../../../base/common/types.js';
11
import * as nls from '../../../nls.js';
12
import { getLanguageTagSettingPlainKey } from './configuration.js';
13
import { Extensions as JSONExtensions, IJSONContributionRegistry } from '../../jsonschemas/common/jsonContributionRegistry.js';
14
import { Registry } from '../../registry/common/platform.js';
15
import { IPolicy, PolicyName } from '../../../base/common/policy.js';
16
import { Disposable } from '../../../base/common/lifecycle.js';
17
import product from '../../product/common/product.js';
18
19
export enum EditPresentationTypes {
20
Multiline = 'multilineText',
21
Singleline = 'singlelineText'
22
}
23
24
export const Extensions = {
25
Configuration: 'base.contributions.configuration'
26
};
27
28
export interface IConfigurationDelta {
29
removedDefaults?: IConfigurationDefaults[];
30
removedConfigurations?: IConfigurationNode[];
31
addedDefaults?: IConfigurationDefaults[];
32
addedConfigurations?: IConfigurationNode[];
33
}
34
35
export interface IConfigurationRegistry {
36
37
/**
38
* Register a configuration to the registry.
39
*/
40
registerConfiguration(configuration: IConfigurationNode): IConfigurationNode;
41
42
/**
43
* Register multiple configurations to the registry.
44
*/
45
registerConfigurations(configurations: IConfigurationNode[], validate?: boolean): void;
46
47
/**
48
* Deregister multiple configurations from the registry.
49
*/
50
deregisterConfigurations(configurations: IConfigurationNode[]): void;
51
52
/**
53
* update the configuration registry by
54
* - registering the configurations to add
55
* - dereigstering the configurations to remove
56
*/
57
updateConfigurations(configurations: { add: IConfigurationNode[]; remove: IConfigurationNode[] }): void;
58
59
/**
60
* Register multiple default configurations to the registry.
61
*/
62
registerDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void;
63
64
/**
65
* Deregister multiple default configurations from the registry.
66
*/
67
deregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void;
68
69
/**
70
* Bulk update of the configuration registry (default and configurations, remove and add)
71
* @param delta
72
*/
73
deltaConfiguration(delta: IConfigurationDelta): void;
74
75
/**
76
* Return the registered default configurations
77
*/
78
getRegisteredDefaultConfigurations(): IConfigurationDefaults[];
79
80
/**
81
* Return the registered configuration defaults overrides
82
*/
83
getConfigurationDefaultsOverrides(): Map<string, IConfigurationDefaultOverrideValue>;
84
85
/**
86
* Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values.
87
* Property or default value changes are not allowed.
88
*/
89
notifyConfigurationSchemaUpdated(...configurations: IConfigurationNode[]): void;
90
91
/**
92
* Event that fires whenever a configuration has been
93
* registered.
94
*/
95
readonly onDidSchemaChange: Event<void>;
96
97
/**
98
* Event that fires whenever a configuration has been
99
* registered.
100
*/
101
readonly onDidUpdateConfiguration: Event<{ properties: ReadonlySet<string>; defaultsOverrides?: boolean }>;
102
103
/**
104
* Returns all configuration nodes contributed to this registry.
105
*/
106
getConfigurations(): IConfigurationNode[];
107
108
/**
109
* Returns all configurations settings of all configuration nodes contributed to this registry.
110
*/
111
getConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema>;
112
113
/**
114
* Return all configurations by policy name
115
*/
116
getPolicyConfigurations(): Map<PolicyName, string>;
117
118
/**
119
* Returns all excluded configurations settings of all configuration nodes contributed to this registry.
120
*/
121
getExcludedConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema>;
122
123
/**
124
* Register the identifiers for editor configurations
125
*/
126
registerOverrideIdentifiers(identifiers: string[]): void;
127
}
128
129
export const enum ConfigurationScope {
130
/**
131
* Application specific configuration, which can be configured only in default profile user settings.
132
*/
133
APPLICATION = 1,
134
/**
135
* Machine specific configuration, which can be configured only in local and remote user settings.
136
*/
137
MACHINE,
138
/**
139
* An application machine specific configuration, which can be configured only in default profile user settings and remote user settings.
140
*/
141
APPLICATION_MACHINE,
142
/**
143
* Window specific configuration, which can be configured in the user or workspace settings.
144
*/
145
WINDOW,
146
/**
147
* Resource specific configuration, which can be configured in the user, workspace or folder settings.
148
*/
149
RESOURCE,
150
/**
151
* Resource specific configuration that can be configured in language specific settings
152
*/
153
LANGUAGE_OVERRIDABLE,
154
/**
155
* Machine specific configuration that can also be configured in workspace or folder settings.
156
*/
157
MACHINE_OVERRIDABLE,
158
}
159
160
161
export interface IConfigurationPropertySchema extends IJSONSchema {
162
163
scope?: ConfigurationScope;
164
165
/**
166
* When restricted, value of this configuration will be read only from trusted sources.
167
* For eg., If the workspace is not trusted, then the value of this configuration is not read from workspace settings file.
168
*/
169
restricted?: boolean;
170
171
/**
172
* When `false` this property is excluded from the registry. Default is to include.
173
*/
174
included?: boolean;
175
176
/**
177
* List of tags associated to the property.
178
* - A tag can be used for filtering
179
* - Use `experimental` tag for marking the setting as experimental.
180
*/
181
tags?: string[];
182
183
/**
184
* When enabled this setting is ignored during sync and user can override this.
185
*/
186
ignoreSync?: boolean;
187
188
/**
189
* When enabled this setting is ignored during sync and user cannot override this.
190
*/
191
disallowSyncIgnore?: boolean;
192
193
/**
194
* Disallow extensions to contribute configuration default value for this setting.
195
*/
196
disallowConfigurationDefault?: boolean;
197
198
/**
199
* Labels for enumeration items
200
*/
201
enumItemLabels?: string[];
202
203
/**
204
* Optional keywords used for search purposes.
205
*/
206
keywords?: string[];
207
208
/**
209
* When specified, controls the presentation format of string settings.
210
* Otherwise, the presentation format defaults to `singleline`.
211
*/
212
editPresentation?: EditPresentationTypes;
213
214
/**
215
* When specified, gives an order number for the setting
216
* within the settings editor. Otherwise, the setting is placed at the end.
217
*/
218
order?: number;
219
220
/**
221
* When specified, this setting's value can always be overwritten by
222
* a system-wide policy.
223
*/
224
policy?: IPolicy;
225
226
/**
227
* When specified, this setting's default value can always be overwritten by
228
* an experiment.
229
*/
230
experiment?: {
231
/**
232
* The mode of the experiment.
233
* - `startup`: The setting value is updated to the experiment value only on startup.
234
* - `auto`: The setting value is updated to the experiment value automatically (whenever the experiment value changes).
235
*/
236
mode: 'startup' | 'auto';
237
238
/**
239
* The name of the experiment. By default, this is `config.${settingId}`
240
*/
241
name?: string;
242
};
243
}
244
245
export interface IExtensionInfo {
246
id: string;
247
displayName?: string;
248
}
249
250
export interface IConfigurationNode {
251
id?: string;
252
order?: number;
253
type?: string | string[];
254
title?: string;
255
description?: string;
256
properties?: IStringDictionary<IConfigurationPropertySchema>;
257
allOf?: IConfigurationNode[];
258
scope?: ConfigurationScope;
259
extensionInfo?: IExtensionInfo;
260
restrictedProperties?: string[];
261
}
262
263
export type ConfigurationDefaultValueSource = IExtensionInfo | Map<string, IExtensionInfo>;
264
265
export interface IConfigurationDefaults {
266
overrides: IStringDictionary<unknown>;
267
source?: IExtensionInfo;
268
donotCache?: boolean;
269
}
270
271
export type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & {
272
section?: {
273
id?: string;
274
title?: string;
275
order?: number;
276
extensionInfo?: IExtensionInfo;
277
};
278
defaultDefaultValue?: unknown;
279
source?: IExtensionInfo; // Source of the Property
280
defaultValueSource?: ConfigurationDefaultValueSource; // Source of the Default Value
281
};
282
283
export interface IConfigurationDefaultOverride {
284
readonly value: unknown;
285
readonly source?: IExtensionInfo; // Source of the default override
286
}
287
288
export interface IConfigurationDefaultOverrideValue {
289
readonly value: unknown;
290
readonly source?: ConfigurationDefaultValueSource;
291
}
292
293
export const allSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
294
export const applicationSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
295
export const applicationMachineSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
296
export const machineSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
297
export const machineOverridableSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
298
export const windowSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
299
export const resourceSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };
300
301
export const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage';
302
export const configurationDefaultsSchemaId = 'vscode://schemas/settings/configurationDefaults';
303
304
const contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
305
306
class ConfigurationRegistry extends Disposable implements IConfigurationRegistry {
307
308
private readonly registeredConfigurationDefaults: IConfigurationDefaults[] = [];
309
private readonly configurationDefaultsOverrides: Map<string, { configurationDefaultOverrides: IConfigurationDefaultOverride[]; configurationDefaultOverrideValue?: IConfigurationDefaultOverrideValue }>;
310
private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode;
311
private readonly configurationContributors: IConfigurationNode[];
312
private readonly configurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>;
313
private readonly policyConfigurations: Map<PolicyName, string>;
314
private readonly excludedConfigurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>;
315
private readonly resourceLanguageSettingsSchema: IJSONSchema;
316
private readonly overrideIdentifiers = new Set<string>();
317
318
private readonly _onDidSchemaChange = this._register(new Emitter<void>());
319
readonly onDidSchemaChange: Event<void> = this._onDidSchemaChange.event;
320
321
private readonly _onDidUpdateConfiguration = this._register(new Emitter<{ properties: ReadonlySet<string>; defaultsOverrides?: boolean }>());
322
readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event;
323
324
constructor() {
325
super();
326
this.configurationDefaultsOverrides = new Map();
327
this.defaultLanguageConfigurationOverridesNode = {
328
id: 'defaultOverrides',
329
title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"),
330
properties: {}
331
};
332
this.configurationContributors = [this.defaultLanguageConfigurationOverridesNode];
333
this.resourceLanguageSettingsSchema = {
334
properties: {},
335
patternProperties: {},
336
additionalProperties: true,
337
allowTrailingCommas: true,
338
allowComments: true
339
};
340
this.configurationProperties = {};
341
this.policyConfigurations = new Map<PolicyName, string>();
342
this.excludedConfigurationProperties = {};
343
344
contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);
345
this.registerOverridePropertyPatternKey();
346
}
347
348
public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): IConfigurationNode {
349
this.registerConfigurations([configuration], validate);
350
return configuration;
351
}
352
353
public registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void {
354
const properties = new Set<string>();
355
this.doRegisterConfigurations(configurations, validate, properties);
356
357
contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);
358
this._onDidSchemaChange.fire();
359
this._onDidUpdateConfiguration.fire({ properties });
360
}
361
362
public deregisterConfigurations(configurations: IConfigurationNode[]): void {
363
const properties = new Set<string>();
364
this.doDeregisterConfigurations(configurations, properties);
365
366
contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);
367
this._onDidSchemaChange.fire();
368
this._onDidUpdateConfiguration.fire({ properties });
369
}
370
371
public updateConfigurations({ add, remove }: { add: IConfigurationNode[]; remove: IConfigurationNode[] }): void {
372
const properties = new Set<string>();
373
this.doDeregisterConfigurations(remove, properties);
374
this.doRegisterConfigurations(add, false, properties);
375
376
contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);
377
this._onDidSchemaChange.fire();
378
this._onDidUpdateConfiguration.fire({ properties });
379
}
380
381
public registerDefaultConfigurations(configurationDefaults: IConfigurationDefaults[]): void {
382
const properties = new Set<string>();
383
this.doRegisterDefaultConfigurations(configurationDefaults, properties);
384
this._onDidSchemaChange.fire();
385
this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true });
386
}
387
388
private doRegisterDefaultConfigurations(configurationDefaults: IConfigurationDefaults[], bucket: Set<string>) {
389
390
this.registeredConfigurationDefaults.push(...configurationDefaults);
391
392
const overrideIdentifiers: string[] = [];
393
394
for (const { overrides, source } of configurationDefaults) {
395
for (const key in overrides) {
396
bucket.add(key);
397
398
const configurationDefaultOverridesForKey = this.configurationDefaultsOverrides.get(key)
399
?? this.configurationDefaultsOverrides.set(key, { configurationDefaultOverrides: [] }).get(key)!;
400
401
const value = overrides[key];
402
configurationDefaultOverridesForKey.configurationDefaultOverrides.push({ value, source });
403
404
// Configuration defaults for Override Identifiers
405
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
406
const newDefaultOverride = this.mergeDefaultConfigurationsForOverrideIdentifier(key, value as IStringDictionary<unknown>, source, configurationDefaultOverridesForKey.configurationDefaultOverrideValue);
407
if (!newDefaultOverride) {
408
continue;
409
}
410
411
configurationDefaultOverridesForKey.configurationDefaultOverrideValue = newDefaultOverride;
412
this.updateDefaultOverrideProperty(key, newDefaultOverride, source);
413
overrideIdentifiers.push(...overrideIdentifiersFromKey(key));
414
}
415
416
// Configuration defaults for Configuration Properties
417
else {
418
const newDefaultOverride = this.mergeDefaultConfigurationsForConfigurationProperty(key, value, source, configurationDefaultOverridesForKey.configurationDefaultOverrideValue);
419
if (!newDefaultOverride) {
420
continue;
421
}
422
423
configurationDefaultOverridesForKey.configurationDefaultOverrideValue = newDefaultOverride;
424
const property = this.configurationProperties[key];
425
if (property) {
426
this.updatePropertyDefaultValue(key, property);
427
this.updateSchema(key, property);
428
}
429
}
430
431
}
432
}
433
434
this.doRegisterOverrideIdentifiers(overrideIdentifiers);
435
}
436
437
public deregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void {
438
const properties = new Set<string>();
439
this.doDeregisterDefaultConfigurations(defaultConfigurations, properties);
440
this._onDidSchemaChange.fire();
441
this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true });
442
}
443
444
private doDeregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[], bucket: Set<string>): void {
445
for (const defaultConfiguration of defaultConfigurations) {
446
const index = this.registeredConfigurationDefaults.indexOf(defaultConfiguration);
447
if (index !== -1) {
448
this.registeredConfigurationDefaults.splice(index, 1);
449
}
450
}
451
452
for (const { overrides, source } of defaultConfigurations) {
453
for (const key in overrides) {
454
const configurationDefaultOverridesForKey = this.configurationDefaultsOverrides.get(key);
455
if (!configurationDefaultOverridesForKey) {
456
continue;
457
}
458
459
const index = configurationDefaultOverridesForKey.configurationDefaultOverrides
460
.findIndex(configurationDefaultOverride => source ? configurationDefaultOverride.source?.id === source.id : configurationDefaultOverride.value === overrides[key]);
461
if (index === -1) {
462
continue;
463
}
464
465
configurationDefaultOverridesForKey.configurationDefaultOverrides.splice(index, 1);
466
if (configurationDefaultOverridesForKey.configurationDefaultOverrides.length === 0) {
467
this.configurationDefaultsOverrides.delete(key);
468
}
469
470
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
471
let configurationDefaultOverrideValue: IConfigurationDefaultOverrideValue | undefined;
472
for (const configurationDefaultOverride of configurationDefaultOverridesForKey.configurationDefaultOverrides) {
473
configurationDefaultOverrideValue = this.mergeDefaultConfigurationsForOverrideIdentifier(key, configurationDefaultOverride.value as IStringDictionary<unknown>, configurationDefaultOverride.source, configurationDefaultOverrideValue);
474
}
475
if (configurationDefaultOverrideValue && !types.isEmptyObject(configurationDefaultOverrideValue.value)) {
476
configurationDefaultOverridesForKey.configurationDefaultOverrideValue = configurationDefaultOverrideValue;
477
this.updateDefaultOverrideProperty(key, configurationDefaultOverrideValue, source);
478
} else {
479
this.configurationDefaultsOverrides.delete(key);
480
delete this.configurationProperties[key];
481
delete this.defaultLanguageConfigurationOverridesNode.properties![key];
482
}
483
} else {
484
let configurationDefaultOverrideValue: IConfigurationDefaultOverrideValue | undefined;
485
for (const configurationDefaultOverride of configurationDefaultOverridesForKey.configurationDefaultOverrides) {
486
configurationDefaultOverrideValue = this.mergeDefaultConfigurationsForConfigurationProperty(key, configurationDefaultOverride.value, configurationDefaultOverride.source, configurationDefaultOverrideValue);
487
}
488
configurationDefaultOverridesForKey.configurationDefaultOverrideValue = configurationDefaultOverrideValue;
489
const property = this.configurationProperties[key];
490
if (property) {
491
this.updatePropertyDefaultValue(key, property);
492
this.updateSchema(key, property);
493
}
494
}
495
bucket.add(key);
496
}
497
}
498
this.updateOverridePropertyPatternKey();
499
}
500
501
private updateDefaultOverrideProperty(key: string, newDefaultOverride: IConfigurationDefaultOverrideValue, source: IExtensionInfo | undefined): void {
502
const property: IRegisteredConfigurationPropertySchema = {
503
section: {
504
id: this.defaultLanguageConfigurationOverridesNode.id,
505
title: this.defaultLanguageConfigurationOverridesNode.title,
506
order: this.defaultLanguageConfigurationOverridesNode.order,
507
extensionInfo: this.defaultLanguageConfigurationOverridesNode.extensionInfo
508
},
509
type: 'object',
510
default: newDefaultOverride.value,
511
description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0}.", getLanguageTagSettingPlainKey(key)),
512
$ref: resourceLanguageSettingsSchemaId,
513
defaultDefaultValue: newDefaultOverride.value,
514
source,
515
defaultValueSource: source
516
};
517
this.configurationProperties[key] = property;
518
this.defaultLanguageConfigurationOverridesNode.properties![key] = property;
519
}
520
521
private mergeDefaultConfigurationsForOverrideIdentifier(overrideIdentifier: string, configurationValueObject: IStringDictionary<unknown>, valueSource: IExtensionInfo | undefined, existingDefaultOverride: IConfigurationDefaultOverrideValue | undefined): IConfigurationDefaultOverrideValue | undefined {
522
const defaultValue = existingDefaultOverride?.value || {};
523
const source = existingDefaultOverride?.source ?? new Map<string, IExtensionInfo>();
524
525
// This should not happen
526
if (!(source instanceof Map)) {
527
console.error('objectConfigurationSources is not a Map');
528
return undefined;
529
}
530
531
for (const propertyKey of Object.keys(configurationValueObject)) {
532
const propertyDefaultValue = configurationValueObject[propertyKey];
533
534
const isObjectSetting = types.isObject(propertyDefaultValue) &&
535
(types.isUndefined((defaultValue as IStringDictionary<unknown>)[propertyKey]) || types.isObject((defaultValue as IStringDictionary<unknown>)[propertyKey]));
536
537
// If the default value is an object, merge the objects and store the source of each keys
538
if (isObjectSetting) {
539
(defaultValue as IStringDictionary<unknown>)[propertyKey] = { ...((defaultValue as IStringDictionary<unknown>)[propertyKey] ?? {}), ...propertyDefaultValue };
540
// Track the source of each value in the object
541
if (valueSource) {
542
for (const objectKey in propertyDefaultValue) {
543
source.set(`${propertyKey}.${objectKey}`, valueSource);
544
}
545
}
546
}
547
548
// Primitive values are overridden
549
else {
550
(defaultValue as IStringDictionary<unknown>)[propertyKey] = propertyDefaultValue;
551
if (valueSource) {
552
source.set(propertyKey, valueSource);
553
} else {
554
source.delete(propertyKey);
555
}
556
}
557
}
558
559
return { value: defaultValue, source };
560
}
561
562
private mergeDefaultConfigurationsForConfigurationProperty(propertyKey: string, value: unknown, valuesSource: IExtensionInfo | undefined, existingDefaultOverride: IConfigurationDefaultOverrideValue | undefined): IConfigurationDefaultOverrideValue | undefined {
563
const property = this.configurationProperties[propertyKey];
564
const existingDefaultValue = existingDefaultOverride?.value ?? property?.defaultDefaultValue;
565
let source: ConfigurationDefaultValueSource | undefined = valuesSource;
566
567
const isObjectSetting = types.isObject(value) &&
568
(
569
property !== undefined && property.type === 'object' ||
570
property === undefined && (types.isUndefined(existingDefaultValue) || types.isObject(existingDefaultValue))
571
);
572
573
// If the default value is an object, merge the objects and store the source of each keys
574
if (isObjectSetting) {
575
source = existingDefaultOverride?.source ?? new Map<string, IExtensionInfo>();
576
577
// This should not happen
578
if (!(source instanceof Map)) {
579
console.error('defaultValueSource is not a Map');
580
return undefined;
581
}
582
583
for (const objectKey in (value as IStringDictionary<unknown>)) {
584
if (valuesSource) {
585
source.set(`${propertyKey}.${objectKey}`, valuesSource);
586
}
587
}
588
value = { ...(types.isObject(existingDefaultValue) ? existingDefaultValue : {}), ...(value as IStringDictionary<unknown>) };
589
}
590
591
return { value, source };
592
}
593
594
public deltaConfiguration(delta: IConfigurationDelta): void {
595
// defaults: remove
596
let defaultsOverrides = false;
597
const properties = new Set<string>();
598
if (delta.removedDefaults) {
599
this.doDeregisterDefaultConfigurations(delta.removedDefaults, properties);
600
defaultsOverrides = true;
601
}
602
// defaults: add
603
if (delta.addedDefaults) {
604
this.doRegisterDefaultConfigurations(delta.addedDefaults, properties);
605
defaultsOverrides = true;
606
}
607
// configurations: remove
608
if (delta.removedConfigurations) {
609
this.doDeregisterConfigurations(delta.removedConfigurations, properties);
610
}
611
// configurations: add
612
if (delta.addedConfigurations) {
613
this.doRegisterConfigurations(delta.addedConfigurations, false, properties);
614
}
615
this._onDidSchemaChange.fire();
616
this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides });
617
}
618
619
public notifyConfigurationSchemaUpdated(...configurations: IConfigurationNode[]) {
620
this._onDidSchemaChange.fire();
621
}
622
623
public registerOverrideIdentifiers(overrideIdentifiers: string[]): void {
624
this.doRegisterOverrideIdentifiers(overrideIdentifiers);
625
this._onDidSchemaChange.fire();
626
}
627
628
private doRegisterOverrideIdentifiers(overrideIdentifiers: string[]) {
629
for (const overrideIdentifier of overrideIdentifiers) {
630
this.overrideIdentifiers.add(overrideIdentifier);
631
}
632
this.updateOverridePropertyPatternKey();
633
}
634
635
private doRegisterConfigurations(configurations: IConfigurationNode[], validate: boolean, bucket: Set<string>): void {
636
637
configurations.forEach(configuration => {
638
639
this.validateAndRegisterProperties(configuration, validate, configuration.extensionInfo, configuration.restrictedProperties, undefined, bucket);
640
641
this.configurationContributors.push(configuration);
642
this.registerJSONConfiguration(configuration);
643
});
644
}
645
646
private doDeregisterConfigurations(configurations: IConfigurationNode[], bucket: Set<string>): void {
647
648
const deregisterConfiguration = (configuration: IConfigurationNode) => {
649
if (configuration.properties) {
650
for (const key in configuration.properties) {
651
bucket.add(key);
652
const property = this.configurationProperties[key];
653
if (property?.policy?.name) {
654
this.policyConfigurations.delete(property.policy.name);
655
}
656
delete this.configurationProperties[key];
657
this.removeFromSchema(key, configuration.properties[key]);
658
}
659
}
660
configuration.allOf?.forEach(node => deregisterConfiguration(node));
661
};
662
for (const configuration of configurations) {
663
deregisterConfiguration(configuration);
664
const index = this.configurationContributors.indexOf(configuration);
665
if (index !== -1) {
666
this.configurationContributors.splice(index, 1);
667
}
668
}
669
}
670
671
private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, extensionInfo: IExtensionInfo | undefined, restrictedProperties: string[] | undefined, scope: ConfigurationScope = ConfigurationScope.WINDOW, bucket: Set<string>): void {
672
scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope;
673
const properties = configuration.properties;
674
if (properties) {
675
for (const key in properties) {
676
const property: IRegisteredConfigurationPropertySchema = properties[key];
677
property.section = {
678
id: configuration.id,
679
title: configuration.title,
680
order: configuration.order,
681
extensionInfo: configuration.extensionInfo
682
};
683
if (validate && validateProperty(key, property, extensionInfo?.id)) {
684
delete properties[key];
685
continue;
686
}
687
688
property.source = extensionInfo;
689
690
// update default value
691
property.defaultDefaultValue = properties[key].default;
692
this.updatePropertyDefaultValue(key, property);
693
694
// update scope
695
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
696
property.scope = undefined; // No scope for overridable properties `[${identifier}]`
697
} else {
698
property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope;
699
property.restricted = types.isUndefinedOrNull(property.restricted) ? !!restrictedProperties?.includes(key) : property.restricted;
700
}
701
702
if (property.experiment) {
703
if (!property.tags?.some(tag => tag.toLowerCase() === 'onexp')) {
704
property.tags = property.tags ?? [];
705
property.tags.push('onExP');
706
}
707
} else if (property.tags?.some(tag => tag.toLowerCase() === 'onexp')) {
708
console.error(`Invalid tag 'onExP' found for property '${key}'. Please use 'experiment' property instead.`);
709
property.experiment = { mode: 'startup' };
710
}
711
712
const excluded = properties[key].hasOwnProperty('included') && !properties[key].included;
713
const policyName = properties[key].policy?.name;
714
715
if (excluded) {
716
this.excludedConfigurationProperties[key] = properties[key];
717
if (policyName) {
718
this.policyConfigurations.set(policyName, key);
719
bucket.add(key);
720
}
721
delete properties[key];
722
} else {
723
bucket.add(key);
724
if (policyName) {
725
this.policyConfigurations.set(policyName, key);
726
}
727
this.configurationProperties[key] = properties[key];
728
if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) {
729
// If not set, default deprecationMessage to the markdown source
730
properties[key].deprecationMessage = properties[key].markdownDeprecationMessage;
731
}
732
}
733
734
735
}
736
}
737
const subNodes = configuration.allOf;
738
if (subNodes) {
739
for (const node of subNodes) {
740
this.validateAndRegisterProperties(node, validate, extensionInfo, restrictedProperties, scope, bucket);
741
}
742
}
743
}
744
745
// Only for tests
746
getConfigurations(): IConfigurationNode[] {
747
return this.configurationContributors;
748
}
749
750
getConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema> {
751
return this.configurationProperties;
752
}
753
754
getPolicyConfigurations(): Map<PolicyName, string> {
755
return this.policyConfigurations;
756
}
757
758
getExcludedConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema> {
759
return this.excludedConfigurationProperties;
760
}
761
762
getRegisteredDefaultConfigurations(): IConfigurationDefaults[] {
763
return [...this.registeredConfigurationDefaults];
764
}
765
766
getConfigurationDefaultsOverrides(): Map<string, IConfigurationDefaultOverrideValue> {
767
const configurationDefaultsOverrides = new Map<string, IConfigurationDefaultOverrideValue>();
768
for (const [key, value] of this.configurationDefaultsOverrides) {
769
if (value.configurationDefaultOverrideValue) {
770
configurationDefaultsOverrides.set(key, value.configurationDefaultOverrideValue);
771
}
772
}
773
return configurationDefaultsOverrides;
774
}
775
776
private registerJSONConfiguration(configuration: IConfigurationNode) {
777
const register = (configuration: IConfigurationNode) => {
778
const properties = configuration.properties;
779
if (properties) {
780
for (const key in properties) {
781
this.updateSchema(key, properties[key]);
782
}
783
}
784
const subNodes = configuration.allOf;
785
subNodes?.forEach(register);
786
};
787
register(configuration);
788
}
789
790
private updateSchema(key: string, property: IConfigurationPropertySchema): void {
791
allSettings.properties[key] = property;
792
switch (property.scope) {
793
case ConfigurationScope.APPLICATION:
794
applicationSettings.properties[key] = property;
795
break;
796
case ConfigurationScope.MACHINE:
797
machineSettings.properties[key] = property;
798
break;
799
case ConfigurationScope.APPLICATION_MACHINE:
800
applicationMachineSettings.properties[key] = property;
801
break;
802
case ConfigurationScope.MACHINE_OVERRIDABLE:
803
machineOverridableSettings.properties[key] = property;
804
break;
805
case ConfigurationScope.WINDOW:
806
windowSettings.properties[key] = property;
807
break;
808
case ConfigurationScope.RESOURCE:
809
resourceSettings.properties[key] = property;
810
break;
811
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
812
resourceSettings.properties[key] = property;
813
this.resourceLanguageSettingsSchema.properties![key] = property;
814
break;
815
}
816
}
817
818
private removeFromSchema(key: string, property: IConfigurationPropertySchema): void {
819
delete allSettings.properties[key];
820
switch (property.scope) {
821
case ConfigurationScope.APPLICATION:
822
delete applicationSettings.properties[key];
823
break;
824
case ConfigurationScope.MACHINE:
825
delete machineSettings.properties[key];
826
break;
827
case ConfigurationScope.APPLICATION_MACHINE:
828
delete applicationMachineSettings.properties[key];
829
break;
830
case ConfigurationScope.MACHINE_OVERRIDABLE:
831
delete machineOverridableSettings.properties[key];
832
break;
833
case ConfigurationScope.WINDOW:
834
delete windowSettings.properties[key];
835
break;
836
case ConfigurationScope.RESOURCE:
837
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
838
delete resourceSettings.properties[key];
839
delete this.resourceLanguageSettingsSchema.properties![key];
840
break;
841
}
842
}
843
844
private updateOverridePropertyPatternKey(): void {
845
for (const overrideIdentifier of this.overrideIdentifiers.values()) {
846
const overrideIdentifierProperty = `[${overrideIdentifier}]`;
847
const resourceLanguagePropertiesSchema: IJSONSchema = {
848
type: 'object',
849
description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."),
850
errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."),
851
$ref: resourceLanguageSettingsSchemaId,
852
};
853
this.updatePropertyDefaultValue(overrideIdentifierProperty, resourceLanguagePropertiesSchema);
854
allSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
855
applicationSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
856
applicationMachineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
857
machineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
858
machineOverridableSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
859
windowSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
860
resourceSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
861
}
862
}
863
864
private registerOverridePropertyPatternKey(): void {
865
const resourceLanguagePropertiesSchema: IJSONSchema = {
866
type: 'object',
867
description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."),
868
errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."),
869
$ref: resourceLanguageSettingsSchemaId,
870
};
871
allSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
872
applicationSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
873
applicationMachineSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
874
machineSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
875
machineOverridableSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
876
windowSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
877
resourceSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
878
this._onDidSchemaChange.fire();
879
}
880
881
private updatePropertyDefaultValue(key: string, property: IRegisteredConfigurationPropertySchema): void {
882
const configurationdefaultOverride = this.configurationDefaultsOverrides.get(key)?.configurationDefaultOverrideValue;
883
let defaultValue = undefined;
884
let defaultSource = undefined;
885
if (configurationdefaultOverride
886
&& (!property.disallowConfigurationDefault || !configurationdefaultOverride.source) // Prevent overriding the default value if the property is disallowed to be overridden by configuration defaults from extensions
887
) {
888
defaultValue = configurationdefaultOverride.value;
889
defaultSource = configurationdefaultOverride.source;
890
}
891
if (types.isUndefined(defaultValue)) {
892
defaultValue = property.defaultDefaultValue;
893
defaultSource = undefined;
894
}
895
if (types.isUndefined(defaultValue)) {
896
defaultValue = getDefaultValue(property.type);
897
}
898
property.default = defaultValue;
899
property.defaultValueSource = defaultSource;
900
}
901
}
902
903
const OVERRIDE_IDENTIFIER_PATTERN = `\\[([^\\]]+)\\]`;
904
const OVERRIDE_IDENTIFIER_REGEX = new RegExp(OVERRIDE_IDENTIFIER_PATTERN, 'g');
905
export const OVERRIDE_PROPERTY_PATTERN = `^(${OVERRIDE_IDENTIFIER_PATTERN})+$`;
906
export const OVERRIDE_PROPERTY_REGEX = new RegExp(OVERRIDE_PROPERTY_PATTERN);
907
908
export function overrideIdentifiersFromKey(key: string): string[] {
909
const identifiers: string[] = [];
910
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
911
let matches = OVERRIDE_IDENTIFIER_REGEX.exec(key);
912
while (matches?.length) {
913
const identifier = matches[1].trim();
914
if (identifier) {
915
identifiers.push(identifier);
916
}
917
matches = OVERRIDE_IDENTIFIER_REGEX.exec(key);
918
}
919
}
920
return distinct(identifiers);
921
}
922
923
export function keyFromOverrideIdentifiers(overrideIdentifiers: string[]): string {
924
return overrideIdentifiers.reduce((result, overrideIdentifier) => `${result}[${overrideIdentifier}]`, '');
925
}
926
927
export function getDefaultValue(type: string | string[] | undefined) {
928
const t = Array.isArray(type) ? type[0] : <string>type;
929
switch (t) {
930
case 'boolean':
931
return false;
932
case 'integer':
933
case 'number':
934
return 0;
935
case 'string':
936
return '';
937
case 'array':
938
return [];
939
case 'object':
940
return {};
941
default:
942
return null;
943
}
944
}
945
946
const configurationRegistry = new ConfigurationRegistry();
947
Registry.add(Extensions.Configuration, configurationRegistry);
948
949
export function validateProperty(property: string, schema: IRegisteredConfigurationPropertySchema, extensionId?: string): string | null {
950
if (!property.trim()) {
951
return nls.localize('config.property.empty', "Cannot register an empty property");
952
}
953
if (OVERRIDE_PROPERTY_REGEX.test(property)) {
954
return nls.localize('config.property.languageDefault', "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property);
955
}
956
if (configurationRegistry.getConfigurationProperties()[property] !== undefined && (!extensionId || !EXTENSION_UNIFICATION_EXTENSION_IDS.has(extensionId.toLowerCase()))) {
957
return nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", property);
958
}
959
if (schema.policy?.name && configurationRegistry.getPolicyConfigurations().get(schema.policy?.name) !== undefined) {
960
return nls.localize('config.policy.duplicate', "Cannot register '{0}'. The associated policy {1} is already registered with {2}.", property, schema.policy?.name, configurationRegistry.getPolicyConfigurations().get(schema.policy?.name));
961
}
962
return null;
963
}
964
965
export function getScopes(): [string, ConfigurationScope | undefined][] {
966
const scopes: [string, ConfigurationScope | undefined][] = [];
967
const configurationProperties = configurationRegistry.getConfigurationProperties();
968
for (const key of Object.keys(configurationProperties)) {
969
scopes.push([key, configurationProperties[key].scope]);
970
}
971
scopes.push(['launch', ConfigurationScope.RESOURCE]);
972
scopes.push(['task', ConfigurationScope.RESOURCE]);
973
return scopes;
974
}
975
976
export function getAllConfigurationProperties(configurationNode: IConfigurationNode[]): IStringDictionary<IRegisteredConfigurationPropertySchema> {
977
const result: IStringDictionary<IRegisteredConfigurationPropertySchema> = {};
978
for (const configuration of configurationNode) {
979
const properties = configuration.properties;
980
if (types.isObject(properties)) {
981
for (const key in properties) {
982
result[key] = properties[key];
983
}
984
}
985
if (configuration.allOf) {
986
Object.assign(result, getAllConfigurationProperties(configuration.allOf));
987
}
988
}
989
return result;
990
}
991
992
export function parseScope(scope: string): ConfigurationScope {
993
switch (scope) {
994
case 'application':
995
return ConfigurationScope.APPLICATION;
996
case 'machine':
997
return ConfigurationScope.MACHINE;
998
case 'resource':
999
return ConfigurationScope.RESOURCE;
1000
case 'machine-overridable':
1001
return ConfigurationScope.MACHINE_OVERRIDABLE;
1002
case 'language-overridable':
1003
return ConfigurationScope.LANGUAGE_OVERRIDABLE;
1004
default:
1005
return ConfigurationScope.WINDOW;
1006
}
1007
}
1008
1009
// Used for extension unification. Should be removed when complete.
1010
export const EXTENSION_UNIFICATION_EXTENSION_IDS: Set<string> = new Set(product.defaultChatAgent ? [product.defaultChatAgent.extensionId, product.defaultChatAgent.chatExtensionId].map(id => id.toLowerCase()) : []);
1011
1012