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 Nov 24, 2020
1 parent 6ef0867 commit 783f1cf
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- [file-search] Deprecate dependency on `@theia/process` and replaced its usage by node's `child_process` api.
- [electron] Removed `attachWillPreventUnload` method from the Electron main application. The `confirmExit` logic is handled on the frontend. [#8732](https://github.com/eclipse-theia/theia/pull/8732)
- [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.7.0 - 29/10/2020

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 @@ -106,10 +116,14 @@ export class PreferenceSchemaProvider extends PreferenceProvider {

protected readonly preferences: { [name: string]: any } = {};
protected readonly combinedSchema: 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 @@ -121,6 +135,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 @@ -231,7 +248,8 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
}
this.combinedSchema.properties[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 @@ -287,8 +305,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);
}
}

setSchema(schema: PreferenceSchema): Disposable {
Expand Down Expand Up @@ -329,10 +405,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 @@ -347,7 +423,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 @@ -375,4 +451,9 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
testOverrideValue(name: string, value: any): value is PreferenceSchemaProperties {
return PreferenceSchemaProperties.is(value) && OVERRIDE_PROPERTY_PATTERN.test(name);
}

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 783f1cf

Please sign in to comment.