diff --git a/packages/core/src/browser/preferences/preference-proxy.ts b/packages/core/src/browser/preferences/preference-proxy.ts index 17e1cc1ce7a35..3fc56f6ba9568 100644 --- a/packages/core/src/browser/preferences/preference-proxy.ts +++ b/packages/core/src/browser/preferences/preference-proxy.ts @@ -33,6 +33,21 @@ export interface PreferenceEventEmitter { readonly ready: Promise; } +/** + * Generic interface to declare a typesafe get function based on the given + * configuration type. + * + * ### Illustration + * + * ```ts + * interface PreferenceConfiguration { + * 'myext.enabled': boolean, + * } + * const enabled : boolean = prefs.get('myext.enabled'); // valid + * const debug : string = prefs.get('myext.enabled'); // invalid + * prefs.get('foobar'); // invalid + * ``` + */ export interface PreferenceRetrieval { get(preferenceName: K | { preferenceName: K, @@ -40,14 +55,66 @@ export interface PreferenceRetrieval { }, defaultValue?: T[K], resourceUri?: string): T[K]; } +/** + * Typesafe schema-based preferences utility based on the {@link PreferenceService}. + * Can be used to get preferences as well as listen to preference changes. + * + * See {@link createPreferenceProxy} on how to instantiate preference proxies. + * + * ### Example usage + * + * ```ts + * preferences.onPreferenceChanged(({ preferenceName, newValue }) => { ... }); + * const enabled = preferences['myext.enabled']; + * ``` + */ export type PreferenceProxy = Readonly & Disposable & PreferenceEventEmitter & PreferenceRetrieval; +/** + * Proxy configuration parameters. + */ export interface PreferenceProxyOptions { + /** + * Prefix which is transparently added to all preference identifiers. + */ prefix?: string; + /** + * The default resourceUri to use if none was specified when calling "set" or "get". + */ resourceUri?: string; + /** + * The overrideIdentifier to use with the underlying preferenceService. + * Useful to potentially override existing values while keeping both values in store. + * + * For example to store different editor settings, e.g. "[markdown].editor.autoIndent", + * "[json].editor.autoIndent" and "editor.autoIndent" + */ overrideIdentifier?: string; + /** + * Indicates whether '.' in schema properties shall be interpreted as regular names (flat), + * as declaring nested objects (deep) or both. Default is flat. + * + * When 'deep' or 'both' is given, nested preference proxies can be retrieved. + */ style?: 'flat' | 'deep' | 'both'; } +/** + * Creates a preference proxy for typesafe preference handling. + * + * @param preferences the underlying preference service to use for preference handling. + * @param schema the JSON Schema which describes which preferences are available including types and descriptions. + * @param options configuration options. + * + * @returns the created preference proxy. + * + * ### Usage + * + * 1. Create JSON Schema specifying your preferences + * 2. Create Configuration type based on the JSON Schema + * 3. Bind the return value of `createPreferenceProxy` to make your preferences available wherever needed. + * + * See {@link CorePreferences} for an example. + */ export function createPreferenceProxy(preferences: PreferenceService, schema: PreferenceSchema, options?: PreferenceProxyOptions): PreferenceProxy { const opts = options || {}; const prefix = opts.prefix || ''; diff --git a/packages/core/src/browser/preferences/preference-service.ts b/packages/core/src/browser/preferences/preference-service.ts index a6c9ca0ad7e73..ff0c78292f05e 100644 --- a/packages/core/src/browser/preferences/preference-service.ts +++ b/packages/core/src/browser/preferences/preference-service.ts @@ -66,34 +66,167 @@ export interface PreferenceChanges { } export const PreferenceService = Symbol('PreferenceService'); +/** + * Service to manage preferences including, among others, getting and setting preference values as well + * as listening to preference changes. + * + * Depending on your use case you might also want to look at {@link createPreferenceProxy} with which + * you can easily create a typesafe schema-based interface for your preferences. Internally the proxy + * uses the PreferenceService so both approaches are compatible. + */ export interface PreferenceService extends Disposable { + /** + * Promise indicating whether the service successfully initialized. + */ readonly ready: Promise; + /** + * Retrieve the stored value for the given preference. + * + * @param preferenceName the preference identifier. + * + * @returns the value stored for the given preference when it exists, `undefined` otherwise. + */ get(preferenceName: string): T | undefined; + /** + * Retrieve the stored value for the given preference. + * + * @param preferenceName the preference identifier. + * @param defaultValue the value to return when no value for the given preference is stored. + * + * @returns the value stored for the given preference when it exists, otherwise the given default value. + */ get(preferenceName: string, defaultValue: T): T; + /** + * Retrieve the stored value for the given preference and resourceUri. + * + * @param preferenceName the preference identifier. + * @param defaultValue the value to return when no value for the given preference is stored. + * @param resourceUri the uri of the resource for which the preference is stored. This used to retrieve + * a potentially different value for the same preference for different resources, for example `files.encoding`. + * + * @returns the value stored for the given preference and resourceUri when it exists, otherwise the given + * default value. + */ get(preferenceName: string, defaultValue: T, resourceUri?: string): T; + /** + * Retrieve the stored value for the given preference and resourceUri. + * + * @param preferenceName the preference identifier. + * @param defaultValue the value to return when no value for the given preference is stored. + * @param resourceUri the uri of the resource for which the preference is stored. This used to retrieve + * a potentially different value for the same preference for different resources, for example `files.encoding`. + * + * @returns the value stored for the given preference and resourceUri when it exists, otherwise the given + * default value. + */ get(preferenceName: string, defaultValue?: T, resourceUri?: string): T | undefined; + /** + * Sets the given preference to the given value. + * + * @param preferenceName the preference identifier. + * @param value the new value of the preference. + * @param scope the scope for which the value shall be set, i.e. user, workspace etc. + * When the folder scope is specified a resourceUri must be provided. + * @param resourceUri the uri of the resource for which the preference is stored. This used to store + * a potentially different value for the same preference for different resources, for example `files.encoding`. + * + * @returns a promise which resolves to `undefined` when setting the preference was successful. Otherwise it rejects + * with an error. + */ set(preferenceName: string, value: any, scope?: PreferenceScope, resourceUri?: string): Promise; + /** + * Registers a callback which will be called whenever a preference is changed. + */ onPreferenceChanged: Event; + /** + * Registers a callback which will be called whenever one or more preferences are changed. + */ onPreferencesChanged: Event; - - inspect(preferenceName: string, resourceUri?: string): { - preferenceName: string, - defaultValue: T | undefined, - globalValue: T | undefined, // User Preference - workspaceValue: T | undefined, // Workspace Preference - workspaceFolderValue: T | undefined // Folder Preference - } | undefined; - + /** + * Retrieve the stored value for the given preference and resourceUri in all available scopes. + * + * @param preferenceName the preference identifier. + * @param resourceUri the uri of the resource for which the preference is stored. + * + * @return an object containing the value of the given preference for all scopes. + */ + inspect(preferenceName: string, resourceUri?: string): PreferenceInspection | undefined; + /** + * Returns a new preference identifier based on the given OverridePreferenceName. + * + * @param options the override specification. + * + * @returns the calculated string based on the given OverridePreferenceName. + */ overridePreferenceName(options: OverridePreferenceName): string; + /** + * Tries to split the given preference identifier into the original OverridePreferenceName attributes + * with which this identifier was created. Returns `undefined` if this is not possible, for example + * when the given preference identifier was not generated by `overridePreferenceName`. + * + * This method is checked when resolving preferences. Therefore together with "overridePreferenceName" + * this can be used to handle specialized preferences, e.g. "[markdown].editor.autoIndent" and "editor.autoIndent". + * + * @param preferenceName the preferenceName which might have been created via {@link PreferenceService.overridePreferenceName}. + * + * @returns the OverridePreferenceName which was used to create the given `preferenceName` if this was the case, + * `undefined` otherwise. + */ overriddenPreferenceName(preferenceName: string): OverridePreferenceName | undefined; - + /** + * Retrieve the stored value for the given preference and resourceUri. + * + * @param preferenceName the preference identifier. + * @param defaultValue the value to return when no value for the given preference is stored. + * @param resourceUri the uri of the resource for which the preference is stored. This used to retrieve + * a potentially different value for the same preference for different resources, for example `files.encoding`. + * + * @returns an object containing the value stored for the given preference and resourceUri when it exists, + * otherwise the given default value. If determinable the object will also contain the uri of the configuration + * resource in which the preference was stored. + */ resolve(preferenceName: string, defaultValue?: T, resourceUri?: string): PreferenceResolveResult; + /** + * Returns the uri of the configuration resource for the given scope and optional resource uri. + * + * @param scope the PreferenceScope to query for. + * @param resourceUri the optional uri of the resource-specific preference handling + * + * @returns the uri of the configuration resource for the given scope and optional resource uri it it exists, + * `undefined` otherwise. + */ getConfigUri(scope: PreferenceScope, resourceUri?: string): URI | undefined; } +/** + * Return type of the {@link PreferenceService.inspect} call. + */ +export interface PreferenceInspection { + /** + * The preference identifier. + */ + preferenceName: string, + /** + * Value in default scope. + */ + defaultValue: T | undefined, + /** + * Value in user scope. + */ + globalValue: T | undefined, + /** + * Value in workspace scope. + */ + workspaceValue: T | undefined, + /** + * Value in folder scope. + */ + workspaceFolderValue: T | undefined +} + /** * We cannot load providers directly in the case if they depend on `PreferenceService` somehow. - * It allows to load them lazilly after DI is configured. + * It allows to load them lazily after DI is configured. */ export const PreferenceProviderProvider = Symbol('PreferenceProviderProvider'); export type PreferenceProviderProvider = (scope: PreferenceScope, uri?: URI) => PreferenceProvider; @@ -233,10 +366,7 @@ export class PreferenceServiceImpl implements PreferenceService { return this.resolve(preferenceName, defaultValue, resourceUri).value; } - resolve(preferenceName: string, defaultValue?: T, resourceUri?: string): { - configUri?: URI, - value?: T - } { + resolve(preferenceName: string, defaultValue?: T, resourceUri?: string): PreferenceResolveResult { const { value, configUri } = this.doResolve(preferenceName, defaultValue, resourceUri); if (value === undefined) { const overridden = this.overriddenPreferenceName(preferenceName);