Skip to content

Commit

Permalink
fix #1106, fix #3540: support language specific preferences
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Feb 7, 2019
1 parent 318f29b commit 549668d
Show file tree
Hide file tree
Showing 15 changed files with 469 additions and 115 deletions.
15 changes: 13 additions & 2 deletions .theia/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
"editor.formatOnSave": true,
"editor.insertSpaces": true,
"[typescript]": {
"editor.tabSize": 4
},
"[json]": {
"editor.tabSize": 2
},
"[jsonc]": {
"editor.tabSize": 2
},
"typescript.tsdk": "node_modules/typescript/lib"
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
"[json]": {
"editor.tabSize": 2
},
"[jsonc]": {
"editor.tabSize": 2
},
"typescript.tsdk": "node_modules/typescript/lib",
"files.insertFinalNewline": true
}
159 changes: 128 additions & 31 deletions packages/core/src/browser/preferences/preference-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@

import * as Ajv from 'ajv';
import { inject, injectable, interfaces, named, postConstruct } from 'inversify';
import { ContributionProvider, bindContributionProvider } from '../../common';
import { ContributionProvider, bindContributionProvider, escapeRegExpCharacters, Emitter, Event } from '../../common';
import { PreferenceScope } from './preference-service';
import { PreferenceProvider, PreferenceProviderPriority, PreferenceProviderDataChange } from './preference-provider';

// tslint:disable:no-any
// tslint:disable:forin

export const PreferenceContribution = Symbol('PreferenceContribution');
export interface PreferenceContribution {
Expand All @@ -30,9 +31,8 @@ export interface PreferenceContribution {
export interface PreferenceSchema {
[name: string]: any,
scope?: 'application' | 'window' | 'resource' | PreferenceScope,
properties: {
[name: string]: PreferenceSchemaProperty
}
overridable?: boolean;
properties: PreferenceSchemaProperties
}
export namespace PreferenceSchema {
export function getDefaultScope(schema: PreferenceSchema): PreferenceScope {
Expand All @@ -46,12 +46,19 @@ export namespace PreferenceSchema {
}
}

export interface PreferenceSchemaProperties {
[name: string]: PreferenceSchemaProperty
}

export interface PreferenceDataSchema {
[name: string]: any,
scope?: PreferenceScope,
properties: {
[name: string]: PreferenceDataProperty
}
patternProperties: {
[name: string]: PreferenceDataProperty
};
}

export interface PreferenceItem {
Expand All @@ -63,6 +70,7 @@ export interface PreferenceItem {
properties?: { [name: string]: PreferenceItem };
additionalProperties?: object;
[name: string]: any;
overridable?: boolean;
}

export interface PreferenceSchemaProperty extends PreferenceItem {
Expand Down Expand Up @@ -92,15 +100,26 @@ export function bindPreferenceSchemaProvider(bind: interfaces.Bind): void {
bindContributionProvider(bind, PreferenceContribution);
}

const OVERRIDE_PROPERTY = '\\[(.*)\\]$';
export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY);

const OVERRIDE_PATTERN_WITH_SUBSTITUTION = '\\[(${0})\\]$';

@injectable()
export class PreferenceSchemaProvider extends PreferenceProvider {

protected readonly preferences: { [name: string]: any } = {};
protected readonly combinedSchema: PreferenceDataSchema = { properties: {} };
protected validateFunction: Ajv.ValidateFunction;
protected readonly combinedSchema: PreferenceDataSchema = { properties: {}, patternProperties: {} };

@inject(ContributionProvider) @named(PreferenceContribution)
protected readonly preferenceContributions: ContributionProvider<PreferenceContribution>;
protected validateFunction: Ajv.ValidateFunction;

protected readonly onDidPreferenceSchemaChangedEmitter = new Emitter<void>();
readonly onDidPreferenceSchemaChanged: Event<void> = this.onDidPreferenceSchemaChangedEmitter.event;
protected fireDidPreferenceSchemaChanged(): void {
this.onDidPreferenceSchemaChangedEmitter.fire(undefined);
}

@postConstruct()
protected init(): void {
Expand All @@ -109,27 +128,79 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
});
this.combinedSchema.additionalProperties = false;
this.updateValidate();
this.onDidPreferencesChanged(() => this.updateValidate());
this._ready.resolve();
}

protected doSetSchema(schema: PreferenceSchema): void {
protected readonly overrideIdentifiers = new Set<string>();
registerOverrideIdentifier(overrideIdentifier: string): void {
if (this.overrideIdentifiers.has(overrideIdentifier)) {
return;
}
this.overrideIdentifiers.add(overrideIdentifier);
this.updateOverridePatternPropertiesKey();
}

protected readonly overridePatternProperties: Required<Pick<PreferenceDataProperty, 'properties'>> & PreferenceDataProperty = {
type: 'object',
description: 'Configure editor settings to be overridden for a language.',
errorMessage: 'Unknown Identifier. Use language identifiers',
properties: {}
};
protected overridePatternPropertiesKey: string | undefined;
protected updateOverridePatternPropertiesKey(): void {
const oldKey = this.overridePatternPropertiesKey;
const newKey = this.computeOverridePatternPropertiesKey();
if (oldKey === newKey) {
return;
}
if (oldKey) {
delete this.combinedSchema.patternProperties[oldKey];
}
this.overridePatternPropertiesKey = newKey;
if (newKey) {
this.combinedSchema.patternProperties[newKey] = this.overridePatternProperties;
}
this.fireDidPreferenceSchemaChanged();
}
protected computeOverridePatternPropertiesKey(): string | undefined {
let param: string = '';
for (const overrideIdentifier of this.overrideIdentifiers.keys()) {
if (param.length) {
param += '|';
}
param += new RegExp(escapeRegExpCharacters(overrideIdentifier)).source;
}
return param.length ? OVERRIDE_PATTERN_WITH_SUBSTITUTION.replace('${0}', param) : undefined;
}

protected doSetSchema(schema: PreferenceSchema): PreferenceProviderDataChange[] {
const scope = this.getScope();
const domain = this.getDomain();
const changes: PreferenceProviderDataChange[] = [];
const defaultScope = PreferenceSchema.getDefaultScope(schema);
const props: string[] = [];
for (const property of Object.keys(schema.properties)) {
const schemaProps = schema.properties[property];
if (this.combinedSchema.properties[property]) {
console.error('Preference name collision detected in the schema for property: ' + property);
const overridable = schema.overridable || false;
for (const preferenceName of Object.keys(schema.properties)) {
if (this.combinedSchema.properties[preferenceName]) {
console.error('Preference name collision detected in the schema for property: ' + preferenceName);
} else {
this.combinedSchema.properties[property] = PreferenceDataProperty.fromPreferenceSchemaProperty(schemaProps, defaultScope);
props.push(property);
const schemaProps = PreferenceDataProperty.fromPreferenceSchemaProperty(schema.properties[preferenceName], defaultScope);
if (typeof schemaProps.overridable !== 'boolean' && overridable) {
schemaProps.overridable = true;
}
if (schemaProps.overridable) {
this.overridePatternProperties.properties[preferenceName] = schemaProps;
}
const newValue = schemaProps.default = this.getDefaultValue(schemaProps);
this.combinedSchema.properties[preferenceName] = schemaProps;
this.preferences[preferenceName] = newValue;
changes.push({ preferenceName, newValue, scope, domain });
}
}
for (const property of props) {
this.preferences[property] = this.getDefaultValue(this.combinedSchema.properties[property]);
}
return changes;
}

protected getDefaultValue(property: PreferenceDataProperty): any {
protected getDefaultValue(property: PreferenceItem): any {
if (property.default) {
return property.default;
}
Expand Down Expand Up @@ -164,15 +235,8 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
}

setSchema(schema: PreferenceSchema): void {
this.doSetSchema(schema);
this.updateValidate();
const changes: PreferenceProviderDataChange[] = [];
for (const property of Object.keys(schema.properties)) {
const schemaProps = schema.properties[property];
changes.push({
preferenceName: property, newValue: schemaProps.default, oldValue: undefined, scope: this.getScope(), domain: this.getDomain()
});
}
const changes = this.doSetSchema(schema);
this.fireDidPreferenceSchemaChanged();
this.emitPreferencesChangedEvent(changes);
}

Expand All @@ -188,11 +252,44 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
return { priority: PreferenceProviderPriority.Default, provider: this };
}

isValidInScope(prefName: string, scope: PreferenceScope): boolean {
const schemaProps = this.combinedSchema.properties[prefName];
if (schemaProps) {
return schemaProps.scope! >= scope;
isValidInScope(preferenceName: string, scope: PreferenceScope): boolean {
const preference = this.getPreferenceProperty(preferenceName);
if (preference) {
return preference.scope! >= scope;
}
return false;
}

*getPreferenceNames(): IterableIterator<string> {
for (const preferenceName in this.combinedSchema.properties) {
yield preferenceName;
const preference = this.combinedSchema.properties[preferenceName];
if (preference.overridable) {
for (const overrideIdentifier of this.overrideIdentifiers) {
yield `[${overrideIdentifier}].${preferenceName}`;
}
}
}
}

getPreferenceProperty(preferenceName: string): PreferenceItem | undefined {
const overridenName = this.testOverridenPreferenceName(preferenceName);
return this.combinedSchema.properties[overridenName || preferenceName];
}

testOverridenPreferenceName(name: string): string | undefined {
const index = name.indexOf('.');
if (index === -1) {
return undefined;
}
const matches = name.substr(0, index).match(OVERRIDE_PROPERTY_PATTERN);
if (!matches || !this.overrideIdentifiers.has(matches[1])) {
return undefined;
}
return name.substr(index + 1);
}

testOverrideValue(name: string, value: any): boolean {
return typeof value === 'object' && OVERRIDE_PROPERTY_PATTERN.test(name);
}
}
Loading

0 comments on commit 549668d

Please sign in to comment.