@sussudio/platform
Version:
Internal APIs for VS Code's service injection the base services.
516 lines (515 loc) • 20.5 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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;
}