Skip to content

Commit

Permalink
preferences for multi-root workspace
Browse files Browse the repository at this point in the history
With changes in 543b119, URIs of root folders in a multi-root workspace are stored in a file with the extension of "theia-workspace", while the workspace preferences are in the ".theia/settings.json" under the first root.

In this pull request, workspace preferences are stored
- in the workspace file under the "settings" property, if a workspace file exists, or
- in the ".theia/settings.json" if the workspace data is not saved in a workspace file.

Also, this change supports
- having 4 levels of preferences (from highest priority to the lowest): FolderPreference, WorkspacePreference, UserPreference, and DefaultPreference, with
- an updated preference editor that supports viewing & editing the FolderPreferences, WorkspacePreferences, and UserPreferneces.

This is the 3rd patch of #1660.

Signed-off-by: elaihau <liang.huang@ericsson.com>
  • Loading branch information
elaihau committed Nov 5, 2018
1 parent 6cb7b7e commit 7c201ea
Show file tree
Hide file tree
Showing 48 changed files with 1,582 additions and 574 deletions.
4 changes: 3 additions & 1 deletion packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { LabelParser } from './label-parser';
import { LabelProvider, LabelProviderContribution, DefaultUriLabelProviderContribution } from './label-provider';
import {
PreferenceProviderProvider, PreferenceProvider, PreferenceScope, PreferenceService,
PreferenceServiceImpl, bindPreferenceSchemaProvider
PreferenceServiceImpl, bindPreferenceSchemaProvider, bindDefaultPreferenceProvider
} from './preferences';
import { ContextMenuRenderer } from './context-menu-renderer';
import { ThemingCommandContribution, ThemeService, BuiltinThemeProvider } from './theming';
Expand Down Expand Up @@ -173,8 +173,10 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo
bind(LabelProviderContribution).to(DefaultUriLabelProviderContribution).inSingletonScope();
bind(LabelProviderContribution).to(DiffUriLabelProviderContribution).inSingletonScope();

bindDefaultPreferenceProvider(bind);
bind(PreferenceProvider).toSelf().inSingletonScope().whenTargetNamed(PreferenceScope.User);
bind(PreferenceProvider).toSelf().inSingletonScope().whenTargetNamed(PreferenceScope.Workspace);
bind(PreferenceProvider).toSelf().inSingletonScope().whenTargetNamed(PreferenceScope.Folders);
bind(PreferenceProviderProvider).toFactory(ctx => (scope: PreferenceScope) => ctx.container.getNamed(PreferenceProvider, scope));
bind(PreferenceServiceImpl).toSelf().inSingletonScope();
bind(PreferenceService).toService(PreferenceServiceImpl);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/********************************************************************************
* Copyright (C) 2018 Ericsson 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 WITH Classpath-exception-2.0
********************************************************************************/

import { inject, injectable, named, interfaces } from 'inversify';
import { ContributionProvider } from '../../common';
import { PreferenceProvider } from './preference-provider';
import { PreferenceScope } from './preference-service';
import { PreferenceContribution } from './preference-contribution';

export function bindDefaultPreferenceProvider(bind: interfaces.Bind): void {
bind(PreferenceProvider).to(DefaultPrefrenceProvider).inSingletonScope().whenTargetNamed(PreferenceScope.Default);
}

@injectable()
export class DefaultPrefrenceProvider extends PreferenceProvider {

protected readonly preferences: { [name: string]: any } = {};

constructor(
@inject(ContributionProvider) @named(PreferenceContribution)
protected readonly preferenceContributions: ContributionProvider<PreferenceContribution>
) {
super();
this.preferenceContributions.getContributions().forEach(contrib => {
for (const prefName of Object.keys(contrib.schema.properties)) {
this.preferences[prefName] = contrib.schema.properties[prefName].default;
}
});
this._ready.resolve();
}

getPreferences(): { [name: string]: any } {
return this.preferences;
}

async setPreference(): Promise<void> {
throw new Error('Unsupported');
}

canProvide(preferenceName: string, resourceUri?: string): { priority: number, provider: PreferenceProvider } {
return { priority: 0, provider: this };
}
}
1 change: 1 addition & 0 deletions packages/core/src/browser/preferences/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export * from './preference-service';
export * from './preference-proxy';
export * from './preference-contribution';
export * from './preference-provider';
export * from './default-preference-provider';
28 changes: 10 additions & 18 deletions packages/core/src/browser/preferences/preference-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as Ajv from 'ajv';
import { inject, injectable, named, interfaces } from 'inversify';
import { ContributionProvider, bindContributionProvider } from '../../common';
import { PreferenceProvider } from './preference-provider';
import { PreferenceScope } from './preference-service';

// tslint:disable:no-any

Expand All @@ -38,18 +38,17 @@ export interface PreferenceSchema {
export interface PreferenceItem {
type?: JsonType | JsonType[];
minimum?: number;
// tslint:disable-next-line:no-any
default?: any;
enum?: string[];
items?: PreferenceItem;
properties?: { [name: string]: PreferenceItem };
additionalProperties?: object;
// tslint:disable-next-line:no-any
[name: string]: any;
}

export interface PreferenceProperty extends PreferenceItem {
description: string;
scopes: PreferenceScope;
}

export type JsonType = 'string' | 'array' | 'number' | 'integer' | 'object' | 'boolean' | 'null';
Expand All @@ -60,22 +59,20 @@ export function bindPreferenceSchemaProvider(bind: interfaces.Bind): void {
}

@injectable()
export class PreferenceSchemaProvider extends PreferenceProvider {
export class PreferenceSchemaProvider {

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

constructor(
@inject(ContributionProvider) @named(PreferenceContribution)
protected readonly preferenceContributions: ContributionProvider<PreferenceContribution>
) {
super();
this.preferenceContributions.getContributions().forEach(contrib => {
this.doSetSchema(contrib.schema);
});
this.combinedSchema.additionalProperties = false;
this.updateValidate();
this._ready.resolve();
}

protected doSetSchema(schema: PreferenceSchema): void {
Expand All @@ -88,10 +85,6 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
props.push(property);
}
}
// tslint:disable-next-line:forin
for (const property of props) {
this.preferences[property] = this.combinedSchema.properties[property].default;
}
}

protected updateValidate(): void {
Expand All @@ -106,17 +99,16 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
return this.combinedSchema;
}

getPreferences(): { [name: string]: any } {
return this.preferences;
}

setSchema(schema: PreferenceSchema): void {
this.doSetSchema(schema);
this.updateValidate();
}

async setPreference(): Promise<void> {
throw new Error('Unsupported');
isValidInScope(prefName: string, scope: PreferenceScope): boolean {
const schemaProps = this.combinedSchema.properties[prefName];
if (schemaProps) {
return (schemaProps.scopes & scope) > 0;
}
return false;
}

}
31 changes: 24 additions & 7 deletions packages/core/src/browser/preferences/preference-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
import { injectable } from 'inversify';
import { Disposable, DisposableCollection, Emitter, Event } from '../../common';
import { Deferred } from '../../common/promise-util';
import { PreferenceScope, PreferenceChange } from './preference-service';

@injectable()
export class PreferenceProvider implements Disposable {
protected readonly onDidPreferencesChangedEmitter = new Emitter<void>();
readonly onDidPreferencesChanged: Event<void> = this.onDidPreferencesChangedEmitter.event;

protected readonly onDidPreferencesChangedEmitter = new Emitter<PreferenceChange>();
readonly onDidPreferencesChanged: Event<PreferenceChange> = this.onDidPreferencesChangedEmitter.event;

protected readonly toDispose = new DisposableCollection();

Expand All @@ -41,20 +43,35 @@ export class PreferenceProvider implements Disposable {
this.toDispose.dispose();
}

protected fireOnDidPreferencesChanged(): void {
this.onDidPreferencesChangedEmitter.fire(undefined);
get<T>(preferenceName: string, resourceUri?: string): T | undefined {
const value = this.getPreferences(resourceUri)[preferenceName];
if (value !== undefined && value !== null) {
return value;
}
}

getPreferences(): { [p: string]: any } {
return [];
getPreferences(resourceUri?: string): { [p: string]: any } {
return {};
}

setPreference(key: string, value: any): Promise<void> {
setPreference(key: string, value: any, resourceUri?: string): Promise<void> {
return Promise.resolve();
}

/** See `_ready`. */
get ready() {
return this._ready.promise;
}

canProvide(preferenceName: string, resourceUri?: string): { priority: number, provider: PreferenceProvider } {
return { priority: -1, provider: this };
}

getDomain(): string[] {
return [];
}

protected getScope() {
return PreferenceScope.Default;
}
}
25 changes: 18 additions & 7 deletions packages/core/src/browser/preferences/preference-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,37 @@
// tslint:disable:no-any

import { Disposable, DisposableCollection, Event, Emitter } from '../../common';
import { PreferenceService, PreferenceChange } from './preference-service';
import { PreferenceService, PreferenceDataChange } from './preference-service';
import { PreferenceSchema } from './preference-contribution';

export interface PreferenceChangeEvent<T> {
readonly preferenceName: keyof T
readonly newValue?: T[keyof T]
readonly oldValue?: T[keyof T]
readonly preferenceName: keyof T;
readonly newValue?: T[keyof T];
readonly oldValue?: T[keyof T];
canAffect(resourceUri?: string): boolean;
}

export interface PreferenceEventEmitter<T> {
readonly onPreferenceChanged: Event<PreferenceChangeEvent<T>>;
readonly ready: Promise<void>;
}

export type PreferenceProxy<T> = Readonly<T> & Disposable & PreferenceEventEmitter<T>;
export interface PreferenceRetrieval<T> {
get<K extends keyof T>(preferenceName: K, defaultValue?: T[K], resourceUri?: string): T[K];
}

export type PreferenceProxy<T> = Readonly<T> & Disposable & PreferenceEventEmitter<T> & PreferenceRetrieval<T>;

export function createPreferenceProxy<T>(preferences: PreferenceService, schema: PreferenceSchema): PreferenceProxy<T> {
const toDispose = new DisposableCollection();
const onPreferenceChangedEmitter = new Emitter<PreferenceChange>();
const onPreferenceChangedEmitter = new Emitter<PreferenceDataChange>();
toDispose.push(onPreferenceChangedEmitter);
toDispose.push(preferences.onPreferenceChanged(e => {
if (schema.properties[e.preferenceName]) {
onPreferenceChangedEmitter.fire(e);
}
}));

const unsupportedOperation = (_: any, __: string) => {
throw new Error('Unsupported operation');
};
Expand All @@ -57,7 +65,10 @@ export function createPreferenceProxy<T>(preferences: PreferenceService, schema:
if (property === 'ready') {
return preferences.ready;
}
throw new Error('unexpected property: ' + property);
if (property === 'get') {
return preferences.get.bind(preferences);
}
throw new Error(`unexpected property: ${property}`);
},
ownKeys: () => Object.keys(schema.properties),
getOwnPropertyDescriptor: (_, property: string) => {
Expand Down
Loading

0 comments on commit 7c201ea

Please sign in to comment.