Path: blob/main/src/vs/platform/configuration/common/configurationRegistry.ts
5270 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { distinct } from '../../../base/common/arrays.js';6import { IStringDictionary } from '../../../base/common/collections.js';7import { Emitter, Event } from '../../../base/common/event.js';8import { IJSONSchema } from '../../../base/common/jsonSchema.js';9import * as types from '../../../base/common/types.js';10import * as nls from '../../../nls.js';11import { getLanguageTagSettingPlainKey } from './configuration.js';12import { Extensions as JSONExtensions, IJSONContributionRegistry } from '../../jsonschemas/common/jsonContributionRegistry.js';13import { Registry } from '../../registry/common/platform.js';14import { IPolicy, PolicyName } from '../../../base/common/policy.js';15import { Disposable } from '../../../base/common/lifecycle.js';16import product from '../../product/common/product.js';1718export enum EditPresentationTypes {19Multiline = 'multilineText',20Singleline = 'singlelineText'21}2223export const Extensions = {24Configuration: 'base.contributions.configuration'25};2627export interface IConfigurationDelta {28removedDefaults?: IConfigurationDefaults[];29removedConfigurations?: IConfigurationNode[];30addedDefaults?: IConfigurationDefaults[];31addedConfigurations?: IConfigurationNode[];32}3334export interface IConfigurationRegistry {3536/**37* Register a configuration to the registry.38*/39registerConfiguration(configuration: IConfigurationNode): IConfigurationNode;4041/**42* Register multiple configurations to the registry.43*/44registerConfigurations(configurations: IConfigurationNode[], validate?: boolean): void;4546/**47* Deregister multiple configurations from the registry.48*/49deregisterConfigurations(configurations: IConfigurationNode[]): void;5051/**52* update the configuration registry by53* - registering the configurations to add54* - dereigstering the configurations to remove55*/56updateConfigurations(configurations: { add: IConfigurationNode[]; remove: IConfigurationNode[] }): void;5758/**59* Register multiple default configurations to the registry.60*/61registerDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void;6263/**64* Deregister multiple default configurations from the registry.65*/66deregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void;6768/**69* Bulk update of the configuration registry (default and configurations, remove and add)70* @param delta71*/72deltaConfiguration(delta: IConfigurationDelta): void;7374/**75* Return the registered default configurations76*/77getRegisteredDefaultConfigurations(): IConfigurationDefaults[];7879/**80* Return the registered configuration defaults overrides81*/82getConfigurationDefaultsOverrides(): Map<string, IConfigurationDefaultOverrideValue>;8384/**85* Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values.86* Property or default value changes are not allowed.87*/88notifyConfigurationSchemaUpdated(...configurations: IConfigurationNode[]): void;8990/**91* Event that fires whenever a configuration has been92* registered.93*/94readonly onDidSchemaChange: Event<void>;9596/**97* Event that fires whenever a configuration has been98* registered.99*/100readonly onDidUpdateConfiguration: Event<{ properties: ReadonlySet<string>; defaultsOverrides?: boolean }>;101102/**103* Returns all configuration nodes contributed to this registry.104*/105getConfigurations(): IConfigurationNode[];106107/**108* Returns all configurations settings of all configuration nodes contributed to this registry.109*/110getConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema>;111112/**113* Return all configurations by policy name114*/115getPolicyConfigurations(): Map<PolicyName, string>;116117/**118* Returns all excluded configurations settings of all configuration nodes contributed to this registry.119*/120getExcludedConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema>;121122/**123* Register the identifiers for editor configurations124*/125registerOverrideIdentifiers(identifiers: string[]): void;126}127128export const enum ConfigurationScope {129/**130* Application specific configuration, which can be configured only in default profile user settings.131*/132APPLICATION = 1,133/**134* Machine specific configuration, which can be configured only in local and remote user settings.135*/136MACHINE,137/**138* An application machine specific configuration, which can be configured only in default profile user settings and remote user settings.139*/140APPLICATION_MACHINE,141/**142* Window specific configuration, which can be configured in the user or workspace settings.143*/144WINDOW,145/**146* Resource specific configuration, which can be configured in the user, workspace or folder settings.147*/148RESOURCE,149/**150* Resource specific configuration that can be configured in language specific settings151*/152LANGUAGE_OVERRIDABLE,153/**154* Machine specific configuration that can also be configured in workspace or folder settings.155*/156MACHINE_OVERRIDABLE,157}158159160export interface IConfigurationPropertySchema extends IJSONSchema {161162scope?: ConfigurationScope;163164/**165* When restricted, value of this configuration will be read only from trusted sources.166* For eg., If the workspace is not trusted, then the value of this configuration is not read from workspace settings file.167*/168restricted?: boolean;169170/**171* When `false` this property is excluded from the registry. Default is to include.172*/173included?: boolean;174175/**176* List of tags associated to the property.177* - A tag can be used for filtering178* - Use `experimental` tag for marking the setting as experimental.179*/180tags?: string[];181182/**183* When enabled this setting is ignored during sync and user can override this.184*/185ignoreSync?: boolean;186187/**188* When enabled this setting is ignored during sync and user cannot override this.189*/190disallowSyncIgnore?: boolean;191192/**193* Disallow extensions to contribute configuration default value for this setting.194*/195disallowConfigurationDefault?: boolean;196197/**198* Labels for enumeration items199*/200enumItemLabels?: string[];201202/**203* Optional keywords used for search purposes.204*/205keywords?: string[];206207/**208* When specified, controls the presentation format of string settings.209* Otherwise, the presentation format defaults to `singleline`.210*/211editPresentation?: EditPresentationTypes;212213/**214* When specified, gives an order number for the setting215* within the settings editor. Otherwise, the setting is placed at the end.216*/217order?: number;218219/**220* When specified, this setting's value can always be overwritten by221* a system-wide policy.222*/223policy?: IPolicy;224225/**226* When specified, this setting's default value can always be overwritten by227* an experiment.228*/229experiment?: {230/**231* The mode of the experiment.232* - `startup`: The setting value is updated to the experiment value only on startup.233* - `auto`: The setting value is updated to the experiment value automatically (whenever the experiment value changes).234*/235mode: 'startup' | 'auto';236237/**238* The name of the experiment. By default, this is `config.${settingId}`239*/240name?: string;241};242}243244export interface IExtensionInfo {245id: string;246displayName?: string;247}248249export interface IConfigurationNode {250id?: string;251order?: number;252type?: string | string[];253title?: string;254description?: string;255properties?: IStringDictionary<IConfigurationPropertySchema>;256allOf?: IConfigurationNode[];257scope?: ConfigurationScope;258extensionInfo?: IExtensionInfo;259restrictedProperties?: string[];260}261262export type ConfigurationDefaultValueSource = IExtensionInfo | Map<string, IExtensionInfo>;263264export interface IConfigurationDefaults {265overrides: IStringDictionary<unknown>;266source?: IExtensionInfo;267donotCache?: boolean;268}269270export type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & {271section?: {272id?: string;273title?: string;274order?: number;275extensionInfo?: IExtensionInfo;276};277defaultDefaultValue?: unknown;278source?: IExtensionInfo; // Source of the Property279defaultValueSource?: ConfigurationDefaultValueSource; // Source of the Default Value280};281282export interface IConfigurationDefaultOverride {283readonly value: unknown;284readonly source?: IExtensionInfo; // Source of the default override285}286287export interface IConfigurationDefaultOverrideValue {288readonly value: unknown;289readonly source?: ConfigurationDefaultValueSource;290}291292export const allSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };293export const applicationSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };294export const applicationMachineSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };295export const machineSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };296export const machineOverridableSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };297export const windowSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };298export const resourceSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} };299300export const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage';301export const configurationDefaultsSchemaId = 'vscode://schemas/settings/configurationDefaults';302303const contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);304305class ConfigurationRegistry extends Disposable implements IConfigurationRegistry {306307private readonly registeredConfigurationDefaults: IConfigurationDefaults[] = [];308private readonly configurationDefaultsOverrides: Map<string, { configurationDefaultOverrides: IConfigurationDefaultOverride[]; configurationDefaultOverrideValue?: IConfigurationDefaultOverrideValue }>;309private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode;310private readonly configurationContributors: IConfigurationNode[];311private readonly configurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>;312private readonly policyConfigurations: Map<PolicyName, string>;313private readonly excludedConfigurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>;314private readonly resourceLanguageSettingsSchema: IJSONSchema;315private readonly overrideIdentifiers = new Set<string>();316317private readonly _onDidSchemaChange = this._register(new Emitter<void>());318readonly onDidSchemaChange: Event<void> = this._onDidSchemaChange.event;319320private readonly _onDidUpdateConfiguration = this._register(new Emitter<{ properties: ReadonlySet<string>; defaultsOverrides?: boolean }>());321readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event;322323constructor() {324super();325this.configurationDefaultsOverrides = new Map();326this.defaultLanguageConfigurationOverridesNode = {327id: 'defaultOverrides',328title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"),329properties: {}330};331this.configurationContributors = [this.defaultLanguageConfigurationOverridesNode];332this.resourceLanguageSettingsSchema = {333properties: {},334patternProperties: {},335additionalProperties: true,336allowTrailingCommas: true,337allowComments: true338};339this.configurationProperties = {};340this.policyConfigurations = new Map<PolicyName, string>();341this.excludedConfigurationProperties = {};342343contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);344this.registerOverridePropertyPatternKey();345}346347public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): IConfigurationNode {348this.registerConfigurations([configuration], validate);349return configuration;350}351352public registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void {353const properties = new Set<string>();354this.doRegisterConfigurations(configurations, validate, properties);355356contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);357this._onDidSchemaChange.fire();358this._onDidUpdateConfiguration.fire({ properties });359}360361public deregisterConfigurations(configurations: IConfigurationNode[]): void {362const properties = new Set<string>();363this.doDeregisterConfigurations(configurations, properties);364365contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);366this._onDidSchemaChange.fire();367this._onDidUpdateConfiguration.fire({ properties });368}369370public updateConfigurations({ add, remove }: { add: IConfigurationNode[]; remove: IConfigurationNode[] }): void {371const properties = new Set<string>();372this.doDeregisterConfigurations(remove, properties);373this.doRegisterConfigurations(add, false, properties);374375contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);376this._onDidSchemaChange.fire();377this._onDidUpdateConfiguration.fire({ properties });378}379380public registerDefaultConfigurations(configurationDefaults: IConfigurationDefaults[]): void {381const properties = new Set<string>();382this.doRegisterDefaultConfigurations(configurationDefaults, properties);383this._onDidSchemaChange.fire();384this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true });385}386387private doRegisterDefaultConfigurations(configurationDefaults: IConfigurationDefaults[], bucket: Set<string>) {388389this.registeredConfigurationDefaults.push(...configurationDefaults);390391const overrideIdentifiers: string[] = [];392393for (const { overrides, source } of configurationDefaults) {394for (const key in overrides) {395bucket.add(key);396397const configurationDefaultOverridesForKey = this.configurationDefaultsOverrides.get(key)398?? this.configurationDefaultsOverrides.set(key, { configurationDefaultOverrides: [] }).get(key)!;399400const value = overrides[key];401configurationDefaultOverridesForKey.configurationDefaultOverrides.push({ value, source });402403// Configuration defaults for Override Identifiers404if (OVERRIDE_PROPERTY_REGEX.test(key)) {405const newDefaultOverride = this.mergeDefaultConfigurationsForOverrideIdentifier(key, value as IStringDictionary<unknown>, source, configurationDefaultOverridesForKey.configurationDefaultOverrideValue);406if (!newDefaultOverride) {407continue;408}409410configurationDefaultOverridesForKey.configurationDefaultOverrideValue = newDefaultOverride;411this.updateDefaultOverrideProperty(key, newDefaultOverride, source);412overrideIdentifiers.push(...overrideIdentifiersFromKey(key));413}414415// Configuration defaults for Configuration Properties416else {417const newDefaultOverride = this.mergeDefaultConfigurationsForConfigurationProperty(key, value, source, configurationDefaultOverridesForKey.configurationDefaultOverrideValue);418if (!newDefaultOverride) {419continue;420}421422configurationDefaultOverridesForKey.configurationDefaultOverrideValue = newDefaultOverride;423const property = this.configurationProperties[key];424if (property) {425this.updatePropertyDefaultValue(key, property);426this.updateSchema(key, property);427}428}429430}431}432433this.doRegisterOverrideIdentifiers(overrideIdentifiers);434}435436public deregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void {437const properties = new Set<string>();438this.doDeregisterDefaultConfigurations(defaultConfigurations, properties);439this._onDidSchemaChange.fire();440this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true });441}442443private doDeregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[], bucket: Set<string>): void {444for (const defaultConfiguration of defaultConfigurations) {445const index = this.registeredConfigurationDefaults.indexOf(defaultConfiguration);446if (index !== -1) {447this.registeredConfigurationDefaults.splice(index, 1);448}449}450451for (const { overrides, source } of defaultConfigurations) {452for (const key in overrides) {453const configurationDefaultOverridesForKey = this.configurationDefaultsOverrides.get(key);454if (!configurationDefaultOverridesForKey) {455continue;456}457458const index = configurationDefaultOverridesForKey.configurationDefaultOverrides459.findIndex(configurationDefaultOverride => source ? configurationDefaultOverride.source?.id === source.id : configurationDefaultOverride.value === overrides[key]);460if (index === -1) {461continue;462}463464configurationDefaultOverridesForKey.configurationDefaultOverrides.splice(index, 1);465if (configurationDefaultOverridesForKey.configurationDefaultOverrides.length === 0) {466this.configurationDefaultsOverrides.delete(key);467}468469if (OVERRIDE_PROPERTY_REGEX.test(key)) {470let configurationDefaultOverrideValue: IConfigurationDefaultOverrideValue | undefined;471for (const configurationDefaultOverride of configurationDefaultOverridesForKey.configurationDefaultOverrides) {472configurationDefaultOverrideValue = this.mergeDefaultConfigurationsForOverrideIdentifier(key, configurationDefaultOverride.value as IStringDictionary<unknown>, configurationDefaultOverride.source, configurationDefaultOverrideValue);473}474if (configurationDefaultOverrideValue && !types.isEmptyObject(configurationDefaultOverrideValue.value)) {475configurationDefaultOverridesForKey.configurationDefaultOverrideValue = configurationDefaultOverrideValue;476this.updateDefaultOverrideProperty(key, configurationDefaultOverrideValue, source);477} else {478this.configurationDefaultsOverrides.delete(key);479delete this.configurationProperties[key];480delete this.defaultLanguageConfigurationOverridesNode.properties![key];481}482} else {483let configurationDefaultOverrideValue: IConfigurationDefaultOverrideValue | undefined;484for (const configurationDefaultOverride of configurationDefaultOverridesForKey.configurationDefaultOverrides) {485configurationDefaultOverrideValue = this.mergeDefaultConfigurationsForConfigurationProperty(key, configurationDefaultOverride.value, configurationDefaultOverride.source, configurationDefaultOverrideValue);486}487configurationDefaultOverridesForKey.configurationDefaultOverrideValue = configurationDefaultOverrideValue;488const property = this.configurationProperties[key];489if (property) {490this.updatePropertyDefaultValue(key, property);491this.updateSchema(key, property);492}493}494bucket.add(key);495}496}497this.updateOverridePropertyPatternKey();498}499500private updateDefaultOverrideProperty(key: string, newDefaultOverride: IConfigurationDefaultOverrideValue, source: IExtensionInfo | undefined): void {501const property: IRegisteredConfigurationPropertySchema = {502section: {503id: this.defaultLanguageConfigurationOverridesNode.id,504title: this.defaultLanguageConfigurationOverridesNode.title,505order: this.defaultLanguageConfigurationOverridesNode.order,506extensionInfo: this.defaultLanguageConfigurationOverridesNode.extensionInfo507},508type: 'object',509default: newDefaultOverride.value,510description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0}.", getLanguageTagSettingPlainKey(key)),511$ref: resourceLanguageSettingsSchemaId,512defaultDefaultValue: newDefaultOverride.value,513source,514defaultValueSource: source515};516this.configurationProperties[key] = property;517this.defaultLanguageConfigurationOverridesNode.properties![key] = property;518}519520private mergeDefaultConfigurationsForOverrideIdentifier(overrideIdentifier: string, configurationValueObject: IStringDictionary<unknown>, valueSource: IExtensionInfo | undefined, existingDefaultOverride: IConfigurationDefaultOverrideValue | undefined): IConfigurationDefaultOverrideValue | undefined {521const defaultValue = existingDefaultOverride?.value || {};522const source = existingDefaultOverride?.source ?? new Map<string, IExtensionInfo>();523524// This should not happen525if (!(source instanceof Map)) {526console.error('objectConfigurationSources is not a Map');527return undefined;528}529530for (const propertyKey of Object.keys(configurationValueObject)) {531const propertyDefaultValue = configurationValueObject[propertyKey];532533const isObjectSetting = types.isObject(propertyDefaultValue) &&534(types.isUndefined((defaultValue as IStringDictionary<unknown>)[propertyKey]) || types.isObject((defaultValue as IStringDictionary<unknown>)[propertyKey]));535536// If the default value is an object, merge the objects and store the source of each keys537if (isObjectSetting) {538(defaultValue as IStringDictionary<unknown>)[propertyKey] = { ...((defaultValue as IStringDictionary<unknown>)[propertyKey] ?? {}), ...propertyDefaultValue };539// Track the source of each value in the object540if (valueSource) {541for (const objectKey in propertyDefaultValue) {542source.set(`${propertyKey}.${objectKey}`, valueSource);543}544}545}546547// Primitive values are overridden548else {549(defaultValue as IStringDictionary<unknown>)[propertyKey] = propertyDefaultValue;550if (valueSource) {551source.set(propertyKey, valueSource);552} else {553source.delete(propertyKey);554}555}556}557558return { value: defaultValue, source };559}560561private mergeDefaultConfigurationsForConfigurationProperty(propertyKey: string, value: unknown, valuesSource: IExtensionInfo | undefined, existingDefaultOverride: IConfigurationDefaultOverrideValue | undefined): IConfigurationDefaultOverrideValue | undefined {562const property = this.configurationProperties[propertyKey];563const existingDefaultValue = existingDefaultOverride?.value ?? property?.defaultDefaultValue;564let source: ConfigurationDefaultValueSource | undefined = valuesSource;565566const isObjectSetting = types.isObject(value) &&567(568property !== undefined && property.type === 'object' ||569property === undefined && (types.isUndefined(existingDefaultValue) || types.isObject(existingDefaultValue))570);571572// If the default value is an object, merge the objects and store the source of each keys573if (isObjectSetting) {574source = existingDefaultOverride?.source ?? new Map<string, IExtensionInfo>();575576// This should not happen577if (!(source instanceof Map)) {578console.error('defaultValueSource is not a Map');579return undefined;580}581582for (const objectKey in (value as IStringDictionary<unknown>)) {583if (valuesSource) {584source.set(`${propertyKey}.${objectKey}`, valuesSource);585}586}587value = { ...(types.isObject(existingDefaultValue) ? existingDefaultValue : {}), ...(value as IStringDictionary<unknown>) };588}589590return { value, source };591}592593public deltaConfiguration(delta: IConfigurationDelta): void {594// defaults: remove595let defaultsOverrides = false;596const properties = new Set<string>();597if (delta.removedDefaults) {598this.doDeregisterDefaultConfigurations(delta.removedDefaults, properties);599defaultsOverrides = true;600}601// defaults: add602if (delta.addedDefaults) {603this.doRegisterDefaultConfigurations(delta.addedDefaults, properties);604defaultsOverrides = true;605}606// configurations: remove607if (delta.removedConfigurations) {608this.doDeregisterConfigurations(delta.removedConfigurations, properties);609}610// configurations: add611if (delta.addedConfigurations) {612this.doRegisterConfigurations(delta.addedConfigurations, false, properties);613}614this._onDidSchemaChange.fire();615this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides });616}617618public notifyConfigurationSchemaUpdated(...configurations: IConfigurationNode[]) {619this._onDidSchemaChange.fire();620}621622public registerOverrideIdentifiers(overrideIdentifiers: string[]): void {623this.doRegisterOverrideIdentifiers(overrideIdentifiers);624this._onDidSchemaChange.fire();625}626627private doRegisterOverrideIdentifiers(overrideIdentifiers: string[]) {628for (const overrideIdentifier of overrideIdentifiers) {629this.overrideIdentifiers.add(overrideIdentifier);630}631this.updateOverridePropertyPatternKey();632}633634private doRegisterConfigurations(configurations: IConfigurationNode[], validate: boolean, bucket: Set<string>): void {635636configurations.forEach(configuration => {637638this.validateAndRegisterProperties(configuration, validate, configuration.extensionInfo, configuration.restrictedProperties, undefined, bucket);639640this.configurationContributors.push(configuration);641this.registerJSONConfiguration(configuration);642});643}644645private doDeregisterConfigurations(configurations: IConfigurationNode[], bucket: Set<string>): void {646647const deregisterConfiguration = (configuration: IConfigurationNode) => {648if (configuration.properties) {649for (const key in configuration.properties) {650bucket.add(key);651const property = this.configurationProperties[key];652if (property?.policy?.name) {653this.policyConfigurations.delete(property.policy.name);654}655delete this.configurationProperties[key];656this.removeFromSchema(key, configuration.properties[key]);657}658}659configuration.allOf?.forEach(node => deregisterConfiguration(node));660};661for (const configuration of configurations) {662deregisterConfiguration(configuration);663const index = this.configurationContributors.indexOf(configuration);664if (index !== -1) {665this.configurationContributors.splice(index, 1);666}667}668}669670private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, extensionInfo: IExtensionInfo | undefined, restrictedProperties: string[] | undefined, scope: ConfigurationScope = ConfigurationScope.WINDOW, bucket: Set<string>): void {671scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope;672const properties = configuration.properties;673if (properties) {674for (const key in properties) {675const property: IRegisteredConfigurationPropertySchema = properties[key];676property.section = {677id: configuration.id,678title: configuration.title,679order: configuration.order,680extensionInfo: configuration.extensionInfo681};682if (validate && validateProperty(key, property, extensionInfo?.id)) {683delete properties[key];684continue;685}686687property.source = extensionInfo;688689// update default value690property.defaultDefaultValue = properties[key].default;691this.updatePropertyDefaultValue(key, property);692693// update scope694if (OVERRIDE_PROPERTY_REGEX.test(key)) {695property.scope = undefined; // No scope for overridable properties `[${identifier}]`696} else {697property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope;698property.restricted = types.isUndefinedOrNull(property.restricted) ? !!restrictedProperties?.includes(key) : property.restricted;699}700701if (property.experiment) {702if (!property.tags?.some(tag => tag.toLowerCase() === 'onexp')) {703property.tags = property.tags ?? [];704property.tags.push('onExP');705}706} else if (property.tags?.some(tag => tag.toLowerCase() === 'onexp')) {707console.error(`Invalid tag 'onExP' found for property '${key}'. Please use 'experiment' property instead.`);708property.experiment = { mode: 'startup' };709}710711const excluded = properties[key].hasOwnProperty('included') && !properties[key].included;712const policyName = properties[key].policy?.name;713714if (excluded) {715this.excludedConfigurationProperties[key] = properties[key];716if (policyName) {717this.policyConfigurations.set(policyName, key);718bucket.add(key);719}720delete properties[key];721} else {722bucket.add(key);723if (policyName) {724this.policyConfigurations.set(policyName, key);725}726this.configurationProperties[key] = properties[key];727if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) {728// If not set, default deprecationMessage to the markdown source729properties[key].deprecationMessage = properties[key].markdownDeprecationMessage;730}731}732733734}735}736const subNodes = configuration.allOf;737if (subNodes) {738for (const node of subNodes) {739this.validateAndRegisterProperties(node, validate, extensionInfo, restrictedProperties, scope, bucket);740}741}742}743744// Only for tests745getConfigurations(): IConfigurationNode[] {746return this.configurationContributors;747}748749getConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema> {750return this.configurationProperties;751}752753getPolicyConfigurations(): Map<PolicyName, string> {754return this.policyConfigurations;755}756757getExcludedConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema> {758return this.excludedConfigurationProperties;759}760761getRegisteredDefaultConfigurations(): IConfigurationDefaults[] {762return [...this.registeredConfigurationDefaults];763}764765getConfigurationDefaultsOverrides(): Map<string, IConfigurationDefaultOverrideValue> {766const configurationDefaultsOverrides = new Map<string, IConfigurationDefaultOverrideValue>();767for (const [key, value] of this.configurationDefaultsOverrides) {768if (value.configurationDefaultOverrideValue) {769configurationDefaultsOverrides.set(key, value.configurationDefaultOverrideValue);770}771}772return configurationDefaultsOverrides;773}774775private registerJSONConfiguration(configuration: IConfigurationNode) {776const register = (configuration: IConfigurationNode) => {777const properties = configuration.properties;778if (properties) {779for (const key in properties) {780this.updateSchema(key, properties[key]);781}782}783const subNodes = configuration.allOf;784subNodes?.forEach(register);785};786register(configuration);787}788789private updateSchema(key: string, property: IConfigurationPropertySchema): void {790allSettings.properties[key] = property;791switch (property.scope) {792case ConfigurationScope.APPLICATION:793applicationSettings.properties[key] = property;794break;795case ConfigurationScope.MACHINE:796machineSettings.properties[key] = property;797break;798case ConfigurationScope.APPLICATION_MACHINE:799applicationMachineSettings.properties[key] = property;800break;801case ConfigurationScope.MACHINE_OVERRIDABLE:802machineOverridableSettings.properties[key] = property;803break;804case ConfigurationScope.WINDOW:805windowSettings.properties[key] = property;806break;807case ConfigurationScope.RESOURCE:808resourceSettings.properties[key] = property;809break;810case ConfigurationScope.LANGUAGE_OVERRIDABLE:811resourceSettings.properties[key] = property;812this.resourceLanguageSettingsSchema.properties![key] = property;813break;814}815}816817private removeFromSchema(key: string, property: IConfigurationPropertySchema): void {818delete allSettings.properties[key];819switch (property.scope) {820case ConfigurationScope.APPLICATION:821delete applicationSettings.properties[key];822break;823case ConfigurationScope.MACHINE:824delete machineSettings.properties[key];825break;826case ConfigurationScope.APPLICATION_MACHINE:827delete applicationMachineSettings.properties[key];828break;829case ConfigurationScope.MACHINE_OVERRIDABLE:830delete machineOverridableSettings.properties[key];831break;832case ConfigurationScope.WINDOW:833delete windowSettings.properties[key];834break;835case ConfigurationScope.RESOURCE:836case ConfigurationScope.LANGUAGE_OVERRIDABLE:837delete resourceSettings.properties[key];838delete this.resourceLanguageSettingsSchema.properties![key];839break;840}841}842843private updateOverridePropertyPatternKey(): void {844for (const overrideIdentifier of this.overrideIdentifiers.values()) {845const overrideIdentifierProperty = `[${overrideIdentifier}]`;846const resourceLanguagePropertiesSchema: IJSONSchema = {847type: 'object',848description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."),849errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."),850$ref: resourceLanguageSettingsSchemaId,851};852this.updatePropertyDefaultValue(overrideIdentifierProperty, resourceLanguagePropertiesSchema);853allSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;854applicationSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;855applicationMachineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;856machineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;857machineOverridableSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;858windowSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;859resourceSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;860}861}862863private registerOverridePropertyPatternKey(): void {864const resourceLanguagePropertiesSchema: IJSONSchema = {865type: 'object',866description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."),867errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."),868$ref: resourceLanguageSettingsSchemaId,869};870allSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;871applicationSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;872applicationMachineSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;873machineSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;874machineOverridableSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;875windowSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;876resourceSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;877this._onDidSchemaChange.fire();878}879880private updatePropertyDefaultValue(key: string, property: IRegisteredConfigurationPropertySchema): void {881const configurationdefaultOverride = this.configurationDefaultsOverrides.get(key)?.configurationDefaultOverrideValue;882let defaultValue = undefined;883let defaultSource = undefined;884if (configurationdefaultOverride885&& (!property.disallowConfigurationDefault || !configurationdefaultOverride.source) // Prevent overriding the default value if the property is disallowed to be overridden by configuration defaults from extensions886) {887defaultValue = configurationdefaultOverride.value;888defaultSource = configurationdefaultOverride.source;889}890if (types.isUndefined(defaultValue)) {891defaultValue = property.defaultDefaultValue;892defaultSource = undefined;893}894if (types.isUndefined(defaultValue)) {895defaultValue = getDefaultValue(property.type);896}897property.default = defaultValue;898property.defaultValueSource = defaultSource;899}900}901902const OVERRIDE_IDENTIFIER_PATTERN = `\\[([^\\]]+)\\]`;903const OVERRIDE_IDENTIFIER_REGEX = new RegExp(OVERRIDE_IDENTIFIER_PATTERN, 'g');904export const OVERRIDE_PROPERTY_PATTERN = `^(${OVERRIDE_IDENTIFIER_PATTERN})+$`;905export const OVERRIDE_PROPERTY_REGEX = new RegExp(OVERRIDE_PROPERTY_PATTERN);906907export function overrideIdentifiersFromKey(key: string): string[] {908const identifiers: string[] = [];909if (OVERRIDE_PROPERTY_REGEX.test(key)) {910let matches = OVERRIDE_IDENTIFIER_REGEX.exec(key);911while (matches?.length) {912const identifier = matches[1].trim();913if (identifier) {914identifiers.push(identifier);915}916matches = OVERRIDE_IDENTIFIER_REGEX.exec(key);917}918}919return distinct(identifiers);920}921922export function keyFromOverrideIdentifiers(overrideIdentifiers: string[]): string {923return overrideIdentifiers.reduce((result, overrideIdentifier) => `${result}[${overrideIdentifier}]`, '');924}925926export function getDefaultValue(type: string | string[] | undefined) {927const t = Array.isArray(type) ? type[0] : <string>type;928switch (t) {929case 'boolean':930return false;931case 'integer':932case 'number':933return 0;934case 'string':935return '';936case 'array':937return [];938case 'object':939return {};940default:941return null;942}943}944945const configurationRegistry = new ConfigurationRegistry();946Registry.add(Extensions.Configuration, configurationRegistry);947948export function validateProperty(property: string, schema: IRegisteredConfigurationPropertySchema, extensionId?: string): string | null {949if (!property.trim()) {950return nls.localize('config.property.empty', "Cannot register an empty property");951}952if (OVERRIDE_PROPERTY_REGEX.test(property)) {953return nls.localize('config.property.languageDefault', "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property);954}955if (configurationRegistry.getConfigurationProperties()[property] !== undefined && (!extensionId || !EXTENSION_UNIFICATION_EXTENSION_IDS.has(extensionId.toLowerCase()))) {956return nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", property);957}958if (schema.policy?.name && configurationRegistry.getPolicyConfigurations().get(schema.policy?.name) !== undefined) {959return 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));960}961return null;962}963964export function getScopes(): [string, ConfigurationScope | undefined][] {965const scopes: [string, ConfigurationScope | undefined][] = [];966const configurationProperties = configurationRegistry.getConfigurationProperties();967for (const key of Object.keys(configurationProperties)) {968scopes.push([key, configurationProperties[key].scope]);969}970scopes.push(['launch', ConfigurationScope.RESOURCE]);971scopes.push(['task', ConfigurationScope.RESOURCE]);972return scopes;973}974975export function getAllConfigurationProperties(configurationNode: IConfigurationNode[]): IStringDictionary<IRegisteredConfigurationPropertySchema> {976const result: IStringDictionary<IRegisteredConfigurationPropertySchema> = {};977for (const configuration of configurationNode) {978const properties = configuration.properties;979if (types.isObject(properties)) {980for (const key in properties) {981result[key] = properties[key];982}983}984if (configuration.allOf) {985Object.assign(result, getAllConfigurationProperties(configuration.allOf));986}987}988return result;989}990991export function parseScope(scope: string): ConfigurationScope {992switch (scope) {993case 'application':994return ConfigurationScope.APPLICATION;995case 'machine':996return ConfigurationScope.MACHINE;997case 'resource':998return ConfigurationScope.RESOURCE;999case 'machine-overridable':1000return ConfigurationScope.MACHINE_OVERRIDABLE;1001case 'language-overridable':1002return ConfigurationScope.LANGUAGE_OVERRIDABLE;1003default:1004return ConfigurationScope.WINDOW;1005}1006}10071008// Used for extension unification. Should be removed when complete.1009export const EXTENSION_UNIFICATION_EXTENSION_IDS: Set<string> = new Set(product.defaultChatAgent ? [product.defaultChatAgent.extensionId, product.defaultChatAgent.chatExtensionId].map(id => id.toLowerCase()) : []);101010111012