@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
403 lines • 18.2 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2025 STMicroelectronics and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
Object.defineProperty(exports, "__esModule", { value: true });
exports.PreferenceSchemaServiceImpl = exports.OVERRIDE_PROPERTY = exports.NO_OVERRIDE = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("inversify");
const disposable_1 = require("../disposable");
const event_1 = require("../event");
const preference_schema_1 = require("./preference-schema");
const preference_scope_1 = require("./preference-scope");
const preference_provider_1 = require("./preference-provider");
const contribution_provider_1 = require("../contribution-provider");
const promise_util_1 = require("../promise-util");
exports.NO_OVERRIDE = {};
exports.OVERRIDE_PROPERTY = '\\[(.*)\\]$';
let PreferenceSchemaServiceImpl = class PreferenceSchemaServiceImpl {
constructor() {
// Storage structures
this.schemas = new Set();
this.properties = new Map();
/**
* This map stores default overrides. The primary map key is the base preference name.
* The preference name maps to a second map keyed by the override identifier or a special object value `NO_OVERRIDE',
* representing default overrides for the base property. The value in this second map is an array
* of entries in reverse order of their insertion. This is necessary becuaus multiple clients might register
* overrides for the same preference key/override combination. The elements in this array consist of a unique, generated
* identifier and the actual override value. This allows us to always return the last registerd override even
* when overrides are later removed.
*/
this.defaultOverrides = new Map();
this._overrideIdentifiers = new Set();
this.jsonSchemas = [];
this._ready = new promise_util_1.Deferred();
this.nextSchemaTitle = 1;
this.nextOverrideValueId = 1;
// Event emitters
this.defaultValueChangedEmitter = new event_1.Emitter();
this.schemaChangedEmitter = new event_1.Emitter();
// Public events
this.onDidChangeDefaultValue = this.defaultValueChangedEmitter.event;
this.onDidChangeSchema = this.schemaChangedEmitter.event;
}
get ready() {
return this._ready.promise;
}
get overrideIdentifiers() {
return this._overrideIdentifiers;
}
getSchemaProperties() {
return this.properties;
}
init() {
for (const scope of this.validScopes) {
this.jsonSchemas[scope] = {
type: 'object',
properties: {},
patternProperties: {},
additionalProperties: false
};
}
const promises = [];
this.preferenceContributions.getContributions().forEach(contrib => {
if (contrib.schema) {
this.addSchema(contrib.schema);
}
if (contrib.initSchema) {
promises.push(contrib.initSchema(this));
}
});
Promise.all(promises).then(() => this._ready.resolve());
}
dispose() {
this.defaultValueChangedEmitter.dispose();
this.schemaChangedEmitter.dispose();
}
registerOverrideIdentifier(overrideIdentifier) {
if (!this._overrideIdentifiers.has(overrideIdentifier)) {
this.addOverrideToJsonSchema(overrideIdentifier);
this._overrideIdentifiers.add(overrideIdentifier);
this.schemaChangedEmitter.fire();
return disposable_1.Disposable.create(() => {
if (this._overrideIdentifiers.delete(overrideIdentifier)) {
this.schemaChangedEmitter.fire();
}
});
}
return disposable_1.Disposable.NULL;
}
addSchema(schema) {
this.schemas.add(schema);
for (const [key, property] of Object.entries(schema.properties)) {
if (this.properties.has(key)) {
console.warn(`Property with id '${key}' already exists`);
continue;
}
if (property.scope === undefined) {
property.scope = schema.scope;
}
if (property.overridable === undefined) {
property.overridable = schema.defaultOverridable;
}
this.properties.set(key, property);
this.setJSONSchemasProperty(key, property);
if (property.default !== undefined) {
this.defaultValueChangedEmitter.fire(this.changeFor(key, undefined, this.defaultOverrides.get(key), undefined, property.default));
}
}
this.schemaChangedEmitter.fire();
return disposable_1.Disposable.create(() => {
if (this.schemas.delete(schema)) {
for (const [key, property] of Object.entries(schema.properties)) {
this.deleteFromJSONSchemas(key, property);
this.properties.delete(key);
const overrides = this.defaultOverrides.get(key);
const baseOverride = overrides === null || overrides === void 0 ? void 0 : overrides.get(exports.NO_OVERRIDE);
if (baseOverride !== undefined) {
this.defaultValueChangedEmitter.fire(this.changeFor(key, undefined, overrides, baseOverride, undefined));
}
else if (property.default !== undefined) {
this.defaultValueChangedEmitter.fire(this.changeFor(key, undefined, overrides, property.default, undefined));
}
if (overrides) {
for (const [overrideKey, value] of overrides) {
if (typeof overrideKey === 'string') {
this.defaultValueChangedEmitter.fire(this.changeFor(key, overrideKey, overrides, value[0][1], undefined));
}
}
}
}
this.schemaChangedEmitter.fire();
}
});
}
isValidInScope(preferenceName, scope) {
const property = this.properties.get(preferenceName);
if (!property) {
return false;
}
// A property is valid in a scope if:
// 1. It is included (undefined or true)
// 2. Its scope is not defined (valid in all scopes) or its scope includes the given scope
return (property.included !== false) &&
(property.scope === undefined || property.scope >= scope);
}
getSchemaProperty(key) {
return this.properties.get(key);
}
updateSchemaProperty(key, property) {
var _a;
const existing = this.properties.get(key);
if (existing) {
// Update the property with new values
const updatedProperty = { ...existing, ...property };
this.properties.set(key, updatedProperty);
const hasNoBaseOverrideValue = ((_a = this.defaultOverrides.get(key)) === null || _a === void 0 ? void 0 : _a.get(exports.NO_OVERRIDE)) === undefined;
if (hasNoBaseOverrideValue && !preference_provider_1.PreferenceUtils.deepEqual(property.default, existing.default)) {
this.defaultValueChangedEmitter.fire(this.changeFor(key, undefined, this.defaultOverrides.get(key), undefined, property.default));
}
this.setJSONSchemasProperty(key, updatedProperty);
this.schemaChangedEmitter.fire();
}
else {
console.warn(`Trying to update non-existent property ${key}`);
}
}
registerOverride(key, overrideIdentifier, value) {
const overrideId = overrideIdentifier || exports.NO_OVERRIDE;
const property = this.properties.get(key);
if (!property) {
console.warn(`Trying to register default override for non-existent preference: ${key}`);
}
else if (!property.overridable && overrideIdentifier) {
console.warn(`Trying to register default override for identifier ${overrideIdentifier} for non-overridable preference: ${key}`);
}
let overrides = this.defaultOverrides.get(key);
if (!overrides) {
overrides = new Map();
this.defaultOverrides.set(key, overrides);
}
const oldValue = this.getDefaultValue(key, overrideIdentifier);
const overrideValueId = this.nextOverrideValueId;
let override = overrides.get(overrideId);
if (!override) {
override = [];
overrides.set(overrideId, override);
}
override.unshift([overrideValueId, value]);
// Fire event only if the value actually changed
if (!preference_provider_1.PreferenceUtils.deepEqual(oldValue, value)) {
const evt = this.changeFor(key, overrideIdentifier, overrides, oldValue, value);
this.defaultValueChangedEmitter.fire(evt);
}
if (property) {
this.setJSONSchemasProperty(key, property);
}
return disposable_1.Disposable.create(() => {
this.removeOverride(key, overrideIdentifier, overrideValueId);
});
}
changeFor(key, overrideIdentifier, overrides, oldValue, newValue) {
const affectedOverrides = [];
if (!overrideIdentifier) {
for (const id of this._overrideIdentifiers) {
if (!(overrides === null || overrides === void 0 ? void 0 : overrides.has(id))) {
affectedOverrides.push(id);
}
}
}
return {
key,
overrideIdentifier: overrideIdentifier,
otherAffectedOverrides: affectedOverrides,
oldValue,
newValue
};
}
removeOverride(key, overrideIdentifier, overrideValueId) {
const overrideId = overrideIdentifier || exports.NO_OVERRIDE;
const overrides = this.defaultOverrides.get(key);
if (overrides) {
const values = overrides.get(overrideId);
if (values) {
const index = values.findIndex(v => v[0] === overrideValueId);
if (index) {
const oldValue = this.getDefaultValue(key, overrideIdentifier);
values.splice(index, 1);
const newValue = this.getDefaultValue(key, overrideIdentifier);
if (!preference_provider_1.PreferenceUtils.deepEqual(oldValue, newValue)) {
const affectedOverrides = [];
if (!overrideIdentifier) {
for (const id of this._overrideIdentifiers) {
if (!overrides.has(id)) {
affectedOverrides.push(id);
}
}
}
this.defaultValueChangedEmitter.fire({
key,
overrideIdentifier,
otherAffectedOverrides: affectedOverrides,
oldValue,
newValue
});
}
}
if (values.length === 0) {
overrides.delete(overrideId);
}
}
if (overrides.size === 0) {
this.defaultOverrides.delete(key);
}
}
}
getDefaultValue(key, overrideIdentifier) {
const overrideId = overrideIdentifier || exports.NO_OVERRIDE;
const overrides = this.defaultOverrides.get(key);
if (overrides) {
const values = overrides.get(overrideId);
if (values) {
return values[0][1]; // there will be no empty values arrays in the data structure
}
}
const property = this.properties.get(key);
return property === null || property === void 0 ? void 0 : property.default;
}
inspectDefaultValue(key, overrideIdentifier) {
const overrideId = overrideIdentifier || exports.NO_OVERRIDE;
const overrides = this.defaultOverrides.get(key);
if (overrides) {
const values = overrides.get(overrideId);
if (values) {
return values[0][1]; // there will be no empty values arrays in the data structure
}
}
if (!overrideIdentifier) {
const property = this.properties.get(key);
return property === null || property === void 0 ? void 0 : property.default;
}
return undefined;
}
getJSONSchema(scope) {
return this.jsonSchemas[scope];
}
setJSONSchemasProperty(key, property) {
for (const scope of this.validScopes) {
if (this.isValidInScope(key, scope)) {
this.setJSONSchemaProperty(this.jsonSchemas[scope], key, property);
}
}
}
deleteFromJSONSchemas(key, property) {
for (const scope of this.validScopes) {
if (this.isValidInScope(key, scope)) {
const schema = this.jsonSchemas[scope];
for (const name of Object.keys(schema.properties)) {
if (name.match(exports.OVERRIDE_PROPERTY)) {
const value = schema.properties[name];
delete value.properties[key];
}
else {
delete schema.properties[key];
}
}
}
}
}
setJSONSchemaProperty(schema, key, property) {
// Add property to the schema
const prop = { ...property, default: this.getDefaultValue(key, undefined) };
schema.properties[key] = prop;
delete prop['scope'];
delete prop['overridable'];
if (property.overridable) {
for (const overrideIdentifier of this._overrideIdentifiers) {
const overrideSchema = schema.properties[`[${overrideIdentifier}]`] || {
type: 'object',
properties: {},
patternProperties: {},
additionalProperties: false
};
schema.properties[`[${overrideIdentifier}]`] = overrideSchema;
overrideSchema.properties[key] = { ...property, default: this.getDefaultValue(key, overrideIdentifier) };
}
}
}
addOverrideToJsonSchema(overrideIdentifier) {
for (const scope of this.validScopes) {
const schema = this.jsonSchemas[scope];
const overrideSchema = {
type: 'object',
properties: {},
patternProperties: {},
additionalProperties: false
};
schema.properties[`[${overrideIdentifier}]`] = overrideSchema;
for (const [key, property] of this.properties.entries()) {
if (property.overridable && this.isValidInScope(key, scope)) {
overrideSchema.properties[key] = { ...property, default: this.getDefaultValue(key, overrideIdentifier) };
}
}
}
}
getDefaultValues() {
const result = {};
for (const [key, property] of this.properties.entries()) {
if (this.isValidInScope(key, preference_scope_1.PreferenceScope.Default)) {
if (property.default !== undefined) {
result[key] = property.default;
}
const overrides = this.defaultOverrides.get(key);
if (overrides) {
for (const [overrideId, values] of overrides.entries()) {
if (overrideId === exports.NO_OVERRIDE) {
result[key] = values[0][1];
}
else {
const overrideKey = `[${overrideId}]`;
const target = result[overrideKey] || {};
target[key] = values[0][1];
result[overrideKey] = target;
}
}
}
}
}
return result;
}
};
exports.PreferenceSchemaServiceImpl = PreferenceSchemaServiceImpl;
tslib_1.__decorate([
(0, inversify_1.inject)(preference_scope_1.ValidPreferenceScopes),
tslib_1.__metadata("design:type", Array)
], PreferenceSchemaServiceImpl.prototype, "validScopes", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(contribution_provider_1.ContributionProvider),
(0, inversify_1.named)(preference_schema_1.PreferenceContribution),
tslib_1.__metadata("design:type", Object)
], PreferenceSchemaServiceImpl.prototype, "preferenceContributions", void 0);
tslib_1.__decorate([
(0, inversify_1.postConstruct)(),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", []),
tslib_1.__metadata("design:returntype", void 0)
], PreferenceSchemaServiceImpl.prototype, "init", null);
exports.PreferenceSchemaServiceImpl = PreferenceSchemaServiceImpl = tslib_1.__decorate([
(0, inversify_1.injectable)()
], PreferenceSchemaServiceImpl);
//# sourceMappingURL=preference-schema-service.js.map