UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

516 lines (515 loc) 20.5 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { distinct } from '@sussudio/base/common/arrays.mjs'; import { Emitter } from '@sussudio/base/common/event.mjs'; import * as types from '@sussudio/base/common/types.mjs'; import * as nls from 'vscode-nls.mjs'; import { getLanguageTagSettingPlainKey } from './configuration.mjs'; import { Extensions as JSONExtensions } from '../../jsonschemas/common/jsonContributionRegistry.mjs'; import { Registry } from '../../registry/common/platform.mjs'; export var EditPresentationTypes; (function (EditPresentationTypes) { EditPresentationTypes['Multiline'] = 'multilineText'; EditPresentationTypes['Singleline'] = 'singlelineText'; })(EditPresentationTypes || (EditPresentationTypes = {})); export const Extensions = { Configuration: 'base.contributions.configuration', }; export const allSettings = { properties: {}, patternProperties: {} }; export const applicationSettings = { properties: {}, patternProperties: {} }; export const machineSettings = { properties: {}, patternProperties: {} }; export const machineOverridableSettings = { properties: {}, patternProperties: {} }; export const windowSettings = { properties: {}, patternProperties: {} }; export const resourceSettings = { properties: {}, patternProperties: {} }; export const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage'; export const configurationDefaultsSchemaId = 'vscode://schemas/settings/configurationDefaults'; const contributionRegistry = Registry.as(JSONExtensions.JSONContribution); class ConfigurationRegistry { configurationDefaultsOverrides; defaultLanguageConfigurationOverridesNode; configurationContributors; configurationProperties; policyConfigurations; excludedConfigurationProperties; resourceLanguageSettingsSchema; overrideIdentifiers = new Set(); _onDidSchemaChange = new Emitter(); onDidSchemaChange = this._onDidSchemaChange.event; _onDidUpdateConfiguration = new Emitter(); onDidUpdateConfiguration = this._onDidUpdateConfiguration.event; constructor() { this.configurationDefaultsOverrides = new Map(); this.defaultLanguageConfigurationOverridesNode = { id: 'defaultOverrides', title: nls.localize('defaultLanguageConfigurationOverrides.title', 'Default Language Configuration Overrides'), properties: {}, }; this.configurationContributors = [this.defaultLanguageConfigurationOverridesNode]; this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: true, allowTrailingCommas: true, allowComments: true, }; this.configurationProperties = {}; this.policyConfigurations = new Map(); this.excludedConfigurationProperties = {}; contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); this.registerOverridePropertyPatternKey(); } registerConfiguration(configuration, validate = true) { this.registerConfigurations([configuration], validate); } registerConfigurations(configurations, validate = true) { const properties = new Set(); this.doRegisterConfigurations(configurations, validate, properties); contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire({ properties }); } deregisterConfigurations(configurations) { const properties = new Set(); this.doDeregisterConfigurations(configurations, properties); contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire({ properties }); } updateConfigurations({ add, remove }) { const properties = new Set(); this.doDeregisterConfigurations(remove, properties); this.doRegisterConfigurations(add, false, properties); contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire({ properties }); } registerDefaultConfigurations(configurationDefaults) { const properties = new Set(); this.doRegisterDefaultConfigurations(configurationDefaults, properties); this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true }); } doRegisterDefaultConfigurations(configurationDefaults, bucket) { const overrideIdentifiers = []; for (const { overrides, source } of configurationDefaults) { for (const key in overrides) { bucket.add(key); if (OVERRIDE_PROPERTY_REGEX.test(key)) { const configurationDefaultOverride = this.configurationDefaultsOverrides.get(key); const valuesSources = configurationDefaultOverride?.valuesSources ?? new Map(); if (source) { for (const configuration of Object.keys(overrides[key])) { valuesSources.set(configuration, source); } } const defaultValue = { ...(configurationDefaultOverride?.value || {}), ...overrides[key] }; this.configurationDefaultsOverrides.set(key, { source, value: defaultValue, valuesSources }); const plainKey = getLanguageTagSettingPlainKey(key); const property = { type: 'object', default: defaultValue, description: nls.localize( 'defaultLanguageConfiguration.description', 'Configure settings to be overridden for the {0} language.', plainKey, ), $ref: resourceLanguageSettingsSchemaId, defaultDefaultValue: defaultValue, source: types.isString(source) ? undefined : source, defaultValueSource: source, }; overrideIdentifiers.push(...overrideIdentifiersFromKey(key)); this.configurationProperties[key] = property; this.defaultLanguageConfigurationOverridesNode.properties[key] = property; } else { this.configurationDefaultsOverrides.set(key, { value: overrides[key], source }); const property = this.configurationProperties[key]; if (property) { this.updatePropertyDefaultValue(key, property); this.updateSchema(key, property); } } } } this.doRegisterOverrideIdentifiers(overrideIdentifiers); } deregisterDefaultConfigurations(defaultConfigurations) { const properties = new Set(); this.doDeregisterDefaultConfigurations(defaultConfigurations, properties); this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true }); } doDeregisterDefaultConfigurations(defaultConfigurations, bucket) { for (const { overrides, source } of defaultConfigurations) { for (const key in overrides) { const configurationDefaultsOverride = this.configurationDefaultsOverrides.get(key); const id = types.isString(source) ? source : source?.id; const configurationDefaultsOverrideSourceId = types.isString(configurationDefaultsOverride?.source) ? configurationDefaultsOverride?.source : configurationDefaultsOverride?.source?.id; if (id !== configurationDefaultsOverrideSourceId) { continue; } bucket.add(key); this.configurationDefaultsOverrides.delete(key); if (OVERRIDE_PROPERTY_REGEX.test(key)) { delete this.configurationProperties[key]; delete this.defaultLanguageConfigurationOverridesNode.properties[key]; } else { const property = this.configurationProperties[key]; if (property) { this.updatePropertyDefaultValue(key, property); this.updateSchema(key, property); } } } } this.updateOverridePropertyPatternKey(); } deltaConfiguration(delta) { // defaults: remove let defaultsOverrides = false; const properties = new Set(); if (delta.removedDefaults) { this.doDeregisterDefaultConfigurations(delta.removedDefaults, properties); defaultsOverrides = true; } // defaults: add if (delta.addedDefaults) { this.doRegisterDefaultConfigurations(delta.addedDefaults, properties); defaultsOverrides = true; } // configurations: remove if (delta.removedConfigurations) { this.doDeregisterConfigurations(delta.removedConfigurations, properties); } // configurations: add if (delta.addedConfigurations) { this.doRegisterConfigurations(delta.addedConfigurations, false, properties); } this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides }); } notifyConfigurationSchemaUpdated(...configurations) { this._onDidSchemaChange.fire(); } registerOverrideIdentifiers(overrideIdentifiers) { this.doRegisterOverrideIdentifiers(overrideIdentifiers); this._onDidSchemaChange.fire(); } doRegisterOverrideIdentifiers(overrideIdentifiers) { for (const overrideIdentifier of overrideIdentifiers) { this.overrideIdentifiers.add(overrideIdentifier); } this.updateOverridePropertyPatternKey(); } doRegisterConfigurations(configurations, validate, bucket) { configurations.forEach((configuration) => { this.validateAndRegisterProperties( configuration, validate, configuration.extensionInfo, configuration.restrictedProperties, undefined, bucket, ); this.configurationContributors.push(configuration); this.registerJSONConfiguration(configuration); }); } doDeregisterConfigurations(configurations, bucket) { const deregisterConfiguration = (configuration) => { if (configuration.properties) { for (const key in configuration.properties) { bucket.add(key); const property = this.configurationProperties[key]; if (property?.policy?.name) { this.policyConfigurations.delete(property.policy.name); } delete this.configurationProperties[key]; this.removeFromSchema(key, configuration.properties[key]); } } configuration.allOf?.forEach((node) => deregisterConfiguration(node)); }; for (const configuration of configurations) { deregisterConfiguration(configuration); const index = this.configurationContributors.indexOf(configuration); if (index !== -1) { this.configurationContributors.splice(index, 1); } } } validateAndRegisterProperties( configuration, validate = true, extensionInfo, restrictedProperties, scope = 3 /* ConfigurationScope.WINDOW */, bucket, ) { scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope; const properties = configuration.properties; if (properties) { for (const key in properties) { const property = properties[key]; if (validate && validateProperty(key, property)) { delete properties[key]; continue; } property.source = extensionInfo; // update default value property.defaultDefaultValue = properties[key].default; this.updatePropertyDefaultValue(key, property); // update scope if (OVERRIDE_PROPERTY_REGEX.test(key)) { property.scope = undefined; // No scope for overridable properties `[${identifier}]` } else { property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope; property.restricted = types.isUndefinedOrNull(property.restricted) ? !!restrictedProperties?.includes(key) : property.restricted; } // Add to properties maps // Property is included by default if 'included' is unspecified if (properties[key].hasOwnProperty('included') && !properties[key].included) { this.excludedConfigurationProperties[key] = properties[key]; delete properties[key]; continue; } else { this.configurationProperties[key] = properties[key]; if (properties[key].policy?.name) { this.policyConfigurations.set(properties[key].policy.name, key); } } if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) { // If not set, default deprecationMessage to the markdown source properties[key].deprecationMessage = properties[key].markdownDeprecationMessage; } bucket.add(key); } } const subNodes = configuration.allOf; if (subNodes) { for (const node of subNodes) { this.validateAndRegisterProperties(node, validate, extensionInfo, restrictedProperties, scope, bucket); } } } // TODO: @sandy081 - Remove this method and include required info in getConfigurationProperties getConfigurations() { return this.configurationContributors; } getConfigurationProperties() { return this.configurationProperties; } getPolicyConfigurations() { return this.policyConfigurations; } getExcludedConfigurationProperties() { return this.excludedConfigurationProperties; } getConfigurationDefaultsOverrides() { return this.configurationDefaultsOverrides; } registerJSONConfiguration(configuration) { const register = (configuration) => { const properties = configuration.properties; if (properties) { for (const key in properties) { this.updateSchema(key, properties[key]); } } const subNodes = configuration.allOf; subNodes?.forEach(register); }; register(configuration); } updateSchema(key, property) { allSettings.properties[key] = property; switch (property.scope) { case 1 /* ConfigurationScope.APPLICATION */: applicationSettings.properties[key] = property; break; case 2 /* ConfigurationScope.MACHINE */: machineSettings.properties[key] = property; break; case 6 /* ConfigurationScope.MACHINE_OVERRIDABLE */: machineOverridableSettings.properties[key] = property; break; case 3 /* ConfigurationScope.WINDOW */: windowSettings.properties[key] = property; break; case 4 /* ConfigurationScope.RESOURCE */: resourceSettings.properties[key] = property; break; case 5 /* ConfigurationScope.LANGUAGE_OVERRIDABLE */: resourceSettings.properties[key] = property; this.resourceLanguageSettingsSchema.properties[key] = property; break; } } removeFromSchema(key, property) { delete allSettings.properties[key]; switch (property.scope) { case 1 /* ConfigurationScope.APPLICATION */: delete applicationSettings.properties[key]; break; case 2 /* ConfigurationScope.MACHINE */: delete machineSettings.properties[key]; break; case 6 /* ConfigurationScope.MACHINE_OVERRIDABLE */: delete machineOverridableSettings.properties[key]; break; case 3 /* ConfigurationScope.WINDOW */: delete windowSettings.properties[key]; break; case 4 /* ConfigurationScope.RESOURCE */: case 5 /* ConfigurationScope.LANGUAGE_OVERRIDABLE */: delete resourceSettings.properties[key]; delete this.resourceLanguageSettingsSchema.properties[key]; break; } } updateOverridePropertyPatternKey() { for (const overrideIdentifier of this.overrideIdentifiers.values()) { const overrideIdentifierProperty = `[${overrideIdentifier}]`; const resourceLanguagePropertiesSchema = { type: 'object', description: nls.localize( 'overrideSettings.defaultDescription', 'Configure editor settings to be overridden for a language.', ), errorMessage: nls.localize( 'overrideSettings.errorMessage', 'This setting does not support per-language configuration.', ), $ref: resourceLanguageSettingsSchemaId, }; this.updatePropertyDefaultValue(overrideIdentifierProperty, resourceLanguagePropertiesSchema); allSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; applicationSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; machineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; machineOverridableSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; windowSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; resourceSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; } } registerOverridePropertyPatternKey() { const resourceLanguagePropertiesSchema = { type: 'object', description: nls.localize( 'overrideSettings.defaultDescription', 'Configure editor settings to be overridden for a language.', ), errorMessage: nls.localize( 'overrideSettings.errorMessage', 'This setting does not support per-language configuration.', ), $ref: resourceLanguageSettingsSchemaId, }; allSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; applicationSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; machineSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; machineOverridableSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; windowSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; resourceSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; this._onDidSchemaChange.fire(); } updatePropertyDefaultValue(key, property) { const configurationdefaultOverride = this.configurationDefaultsOverrides.get(key); let defaultValue = configurationdefaultOverride?.value; let defaultSource = configurationdefaultOverride?.source; if (types.isUndefined(defaultValue)) { defaultValue = property.defaultDefaultValue; defaultSource = undefined; } if (types.isUndefined(defaultValue)) { defaultValue = getDefaultValue(property.type); } property.default = defaultValue; property.defaultValueSource = defaultSource; } } const OVERRIDE_IDENTIFIER_PATTERN = `\\[([^\\]]+)\\]`; const OVERRIDE_IDENTIFIER_REGEX = new RegExp(OVERRIDE_IDENTIFIER_PATTERN, 'g'); export const OVERRIDE_PROPERTY_PATTERN = `^(${OVERRIDE_IDENTIFIER_PATTERN})+$`; export const OVERRIDE_PROPERTY_REGEX = new RegExp(OVERRIDE_PROPERTY_PATTERN); export function overrideIdentifiersFromKey(key) { const identifiers = []; if (OVERRIDE_PROPERTY_REGEX.test(key)) { let matches = OVERRIDE_IDENTIFIER_REGEX.exec(key); while (matches?.length) { const identifier = matches[1].trim(); if (identifier) { identifiers.push(identifier); } matches = OVERRIDE_IDENTIFIER_REGEX.exec(key); } } return distinct(identifiers); } export function keyFromOverrideIdentifiers(overrideIdentifiers) { return overrideIdentifiers.reduce((result, overrideIdentifier) => `${result}[${overrideIdentifier}]`, ''); } export function getDefaultValue(type) { const t = Array.isArray(type) ? type[0] : type; switch (t) { case 'boolean': return false; case 'integer': case 'number': return 0; case 'string': return ''; case 'array': return []; case 'object': return {}; default: return null; } } const configurationRegistry = new ConfigurationRegistry(); Registry.add(Extensions.Configuration, configurationRegistry); export function validateProperty(property, schema) { if (!property.trim()) { return nls.localize('config.property.empty', 'Cannot register an empty property'); } if (OVERRIDE_PROPERTY_REGEX.test(property)) { return nls.localize( 'config.property.languageDefault', "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property, ); } if (configurationRegistry.getConfigurationProperties()[property] !== undefined) { return nls.localize( 'config.property.duplicate', "Cannot register '{0}'. This property is already registered.", property, ); } if (schema.policy?.name && configurationRegistry.getPolicyConfigurations().get(schema.policy?.name) !== undefined) { 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), ); } return null; } export function getScopes() { const scopes = []; const configurationProperties = configurationRegistry.getConfigurationProperties(); for (const key of Object.keys(configurationProperties)) { scopes.push([key, configurationProperties[key].scope]); } scopes.push(['launch', 4 /* ConfigurationScope.RESOURCE */]); scopes.push(['task', 4 /* ConfigurationScope.RESOURCE */]); return scopes; }