Skip to content

Commit

Permalink
Allow extenders to modify preferences
Browse files Browse the repository at this point in the history
Signed-off-by: Nigel Westbury <[email protected]>
  • Loading branch information
westbury committed Feb 10, 2021
1 parent f1d4690 commit a3f9be0
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## v1.11.0

- [preferences] preferences schemas can now be modified or completely removed from the UI by downstream packages [#8626](https://github.com/eclipse-theia/theia/pull/8626)

## v1.10.0 - 1/28/2021

- [api-samples] added example on how to contribute toggleable toolbar items [#8968](https://github.com/eclipse-theia/theia/pull/8968)
Expand Down
95 changes: 88 additions & 7 deletions packages/core/src/browser/preferences/preference-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ import { ContributionProvider, bindContributionProvider, escapeRegExpCharacters,
import { PreferenceScope } from './preference-scope';
import { PreferenceProvider, PreferenceProviderDataChange } from './preference-provider';
import {
PreferenceSchema, PreferenceSchemaProperties, PreferenceDataSchema, PreferenceItem, PreferenceSchemaProperty, PreferenceDataProperty, JsonType
PreferenceSchema, PreferenceSchemaProperties, PreferenceDataSchema, PreferenceItem, PreferenceSchemaProperty, PreferenceDataProperty, JsonType,
PreferenceSchemaModification, PreferenceSchemaPropertyModifications, PreferenceSchemaPropertyModification
} from '../../common/preferences/preference-schema';
import { FrontendApplicationConfigProvider } from '../frontend-application-config-provider';
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
import { bindPreferenceConfigurations, PreferenceConfigurations } from './preference-configurations';
export { PreferenceSchema, PreferenceSchemaProperties, PreferenceDataSchema, PreferenceItem, PreferenceSchemaProperty, PreferenceDataProperty, JsonType };
export {
PreferenceSchema, PreferenceSchemaProperties, PreferenceDataSchema, PreferenceItem, PreferenceSchemaProperty, PreferenceDataProperty, JsonType,
PreferenceSchemaModification
};
import { Mutable } from '../../common/types';

/* eslint-disable guard-for-in, @typescript-eslint/no-explicit-any */
Expand Down Expand Up @@ -60,10 +64,16 @@ export interface PreferenceContribution {
readonly schema: PreferenceSchema;
}

export const PreferenceModificationContribution = Symbol('PreferenceModificationContribution');
export interface PreferenceModificationContribution {
readonly schemaModification: PreferenceSchemaModification;
}

export function bindPreferenceSchemaProvider(bind: interfaces.Bind): void {
bindPreferenceConfigurations(bind);
bind(PreferenceSchemaProvider).toSelf().inSingletonScope();
bindContributionProvider(bind, PreferenceContribution);
bindContributionProvider(bind, PreferenceModificationContribution);
}

export interface OverridePreferenceName {
Expand Down Expand Up @@ -108,10 +118,14 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
protected readonly combinedSchema: PreferenceDataSchema = { properties: {}, patternProperties: {} };
protected readonly workspaceSchema: PreferenceDataSchema = { properties: {}, patternProperties: {} };
protected readonly folderSchema: PreferenceDataSchema = { properties: {}, patternProperties: {} };
protected readonly modifications: PreferenceSchemaPropertyModifications = {};

@inject(ContributionProvider) @named(PreferenceContribution)
protected readonly preferenceContributions: ContributionProvider<PreferenceContribution>;

@inject(ContributionProvider) @named(PreferenceModificationContribution)
protected readonly preferenceModificationContributions: ContributionProvider<PreferenceModificationContribution>;

@inject(PreferenceConfigurations)
protected readonly configurations: PreferenceConfigurations;

Expand All @@ -123,6 +137,9 @@ export class PreferenceSchemaProvider extends PreferenceProvider {

@postConstruct()
protected init(): void {
this.preferenceModificationContributions.getContributions().forEach(contrib => {
this.doSetSchemaModification(contrib.schemaModification);
});
this.preferenceContributions.getContributions().forEach(contrib => {
this.doSetSchema(contrib.schema);
});
Expand Down Expand Up @@ -233,7 +250,8 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
}
this.updateSchemaProps(preferenceName, schemaProps);

const value = schemaProps.defaultValue = this.getDefaultValue(schemaProps, preferenceName);
const modifiedProperties = this.schema(preferenceName, schemaProps);
const value = schemaProps.defaultValue = this.getDefaultValue(modifiedProperties, preferenceName);
if (this.testOverrideValue(preferenceName, value)) {
for (const overriddenPreferenceName in value) {
const overrideValue = value[overriddenPreferenceName];
Expand Down Expand Up @@ -289,8 +307,66 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
return null;
}

protected doSetSchemaModification(schemaModification: PreferenceSchemaModification): void {
for (const preferenceName of Object.keys(schemaModification.properties)) {
const modifiableProperties = this.combinedSchema.properties[preferenceName];
const previousModifications = this.modifications[preferenceName] ?? {};
const newModifications: PreferenceSchemaPropertyModification = schemaModification.properties[preferenceName];
this.modifications[preferenceName] = { ...previousModifications, ...newModifications };

// Validate that the modifications only constrain the schema, cannot allow extra values
const modifiedEnum = newModifications.enum;
if (modifiedEnum !== undefined) {
if (modifiableProperties) {
if (modifiableProperties.type !== 'string') {
console.warn(`Override of preference ${preferenceName} cannot constrain to enum values because the property is not string type.`);
continue;
}
if (modifiableProperties.enum && modifiableProperties.enum.some(v => !modifiedEnum.includes(v))) {
console.warn(`Override of preference enum ${preferenceName} cannot add enum values, it can only constrain the set of values.`);
continue;
}
}
}
const modifiedMinimum = newModifications.minimum;
if (modifiedMinimum !== undefined) {
if (modifiableProperties) {
if (modifiableProperties.minimum && modifiedMinimum < modifiableProperties.minimum) {
console.warn(`Override of preference minimum ${preferenceName} cannot reduce the minimum, it can only increase it.`);
continue;
}
}
}
}
}

protected schema(preferenceName: string, propertySchema: PreferenceDataProperty): PreferenceDataProperty {
const modifications = this.modifications[preferenceName];
return modifications ? { ...propertySchema, ...modifications } : propertySchema;
}

getCombinedSchema(): PreferenceDataSchema {
return this.combinedSchema;
const properties: { [key: string]: PreferenceDataProperty; } = {};
for (const preferenceName of Object.keys(this.combinedSchema.properties)) {
const value = this.combinedSchema.properties[preferenceName];
const modifications = this.modifications[preferenceName];
if (modifications) {
if (!modifications.hidden) {
const modifiedValue = this.schema(preferenceName, value);
properties[preferenceName] = modifiedValue;
}
} else {
properties[preferenceName] = value;
}
}
return { ...this.combinedSchema, properties };
}

getPropertySchema(preferenceName: string): PreferenceDataProperty | undefined {
const property = this.combinedSchema.properties[preferenceName];
if (property) {
return this.schema(preferenceName, property);
}
}

getSchema(scope: PreferenceScope): PreferenceDataSchema {
Expand Down Expand Up @@ -343,10 +419,10 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
}
if (!property) {
// try from overridden value
property = this.combinedSchema.properties[overridden.preferenceName];
property = this.getPropertySchema(overridden.preferenceName);
}
} else {
property = this.combinedSchema.properties[preferenceName];
property = this.getPropertySchema(preferenceName);
}
return property && property.scope! >= scope;
}
Expand All @@ -361,7 +437,7 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
}

*getOverridePreferenceNames(preferenceName: string): IterableIterator<string> {
const preference = this.combinedSchema.properties[preferenceName];
const preference = this.getPropertySchema(preferenceName);
if (preference && preference.overridable) {
for (const overrideIdentifier of this.overrideIdentifiers) {
yield this.overridePreferenceName({ preferenceName, overrideIdentifier });
Expand Down Expand Up @@ -416,4 +492,9 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
break;
}
}

isPropertyHidden(preferenceName: string): boolean {
const modifications = this.modifications[preferenceName];
return modifications && !!modifications.hidden;
}
}
23 changes: 15 additions & 8 deletions packages/core/src/browser/preferences/preference-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,14 +494,21 @@ export class PreferenceServiceImpl implements PreferenceService {
}
protected doResolve<T>(preferenceName: string, defaultValue?: T, resourceUri?: string): PreferenceResolveResult<T> {
const result: PreferenceResolveResult<T> = {};
for (const scope of PreferenceScope.getScopes()) {
if (this.schema.isValidInScope(preferenceName, scope)) {
const provider = this.getProvider(scope);
if (provider) {
const { configUri, value } = provider.resolve<T>(preferenceName, resourceUri);
if (value !== undefined) {
result.configUri = configUri;
result.value = PreferenceProvider.merge(result.value as any, value as any) as any;
const propertyIsHidden = this.schema.isPropertyHidden(preferenceName);
if (propertyIsHidden) {
const { configUri, value } = this.schema.resolve<T>(preferenceName, resourceUri);
result.configUri = configUri;
result.value = value;
} else {
for (const scope of PreferenceScope.getScopes()) {
if (this.schema.isValidInScope(preferenceName, scope)) {
const provider = this.getProvider(scope);
if (provider) {
const { configUri, value } = provider.resolve<T>(preferenceName, resourceUri);
if (value !== undefined) {
result.configUri = configUri;
result.value = PreferenceProvider.merge(result.value as any, value as any) as any;
}
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/common/preferences/preference-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface PreferenceItem {
additionalProperties?: object | boolean;
[name: string]: any;
overridable?: boolean;
hidden?: boolean;
}

export interface PreferenceSchemaProperty extends PreferenceItem {
Expand All @@ -100,4 +101,20 @@ export namespace PreferenceDataProperty {
}
}

export interface PreferenceSchemaModification {
properties: PreferenceSchemaPropertyModifications
}

export interface PreferenceSchemaPropertyModifications {
[name: string]: PreferenceSchemaPropertyModification
}

export interface PreferenceSchemaPropertyModification {
minimum?: number;
default?: any;
enum?: string[];
description?: string;
hidden?: boolean;
}

export type JsonType = 'string' | 'array' | 'number' | 'integer' | 'object' | 'boolean' | 'null';
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export abstract class AbstractResourcePreferenceProvider extends PreferenceProvi
for (const prefName of prefNames.values()) {
const oldValue = oldPrefs[prefName];
const newValue = newPrefs[prefName];
const schemaProperties = this.schemaProvider.getCombinedSchema().properties[prefName];
const schemaProperties = this.schemaProvider.getPropertySchema(prefName);
if (schemaProperties) {
const scope = schemaProperties.scope;
// do not emit the change event if the change is made out of the defined preference scope
Expand Down

0 comments on commit a3f9be0

Please sign in to comment.