From 8dbc74e36fbc7429ae7b6603e1fb62ca34e33e66 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Oct 2017 09:59:58 +0200 Subject: [PATCH 01/28] Configuration API blueprint --- .../configuration/common/configuration2.ts | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/vs/platform/configuration/common/configuration2.ts diff --git a/src/vs/platform/configuration/common/configuration2.ts b/src/vs/platform/configuration/common/configuration2.ts new file mode 100644 index 00000000000..a3edb5c0048 --- /dev/null +++ b/src/vs/platform/configuration/common/configuration2.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TPromise } from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import Event from 'vs/base/common/event'; + +export const IConfigurationService = createDecorator('configurationService'); + +export interface IConfigurationOverrides { + overrideIdentifier?: string; + resource?: URI; +} + +export enum ConfigurationTarget { + USER, + WORKSPACE, + WORKSPACE_FOLDER, + MEMORY +} + +export interface IConfigurationServiceEvent { + sections: string[]; + keys: string[]; +} + +export interface IConfiguration { + readonly [key: string]: any; +} + +export interface IConfigurationService { + _serviceBrand: any; + + onDidUpdateConfiguration: Event; + + getConfiguration(): T; + getConfiguration(section: string): T; + getConfiguration(overrides: IConfigurationOverrides): T; + getConfiguration(section: string, overrides: IConfigurationOverrides): T; + + updateConfiguration(key: string, value: any): TPromise; + updateConfiguration(key: string, value: any, overrides: IConfigurationOverrides): TPromise; + updateConfiguration(key: string, value: any, target: ConfigurationTarget): TPromise; + updateConfiguration(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise; + + inspect(key: string): { + default: T, + user: T, + workspace: T, + workspaceFolder: T + value: T, + }; + + keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + }; +} \ No newline at end of file From 7ec6ecda504c22197b1fec07dc85839f55e4a1c5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Oct 2017 10:09:58 +0200 Subject: [PATCH 02/28] Adopt to new configuration API --- .../configuration/common/configuration.ts | 55 +++++--------- .../common/configurationRegistry.ts | 14 ++-- .../node/configurationService.ts | 73 ++++++++++--------- .../configuration/common/configuration.ts | 2 +- 4 files changed, 61 insertions(+), 83 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index d541ed739df..16dc333a62f 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -240,7 +240,13 @@ export class Configuration { return section ? configModel.getContentsFor(section) : configModel.contents; } - lookup(key: string, overrides: IConfigurationOverrides = {}): IConfigurationValue { + lookup(key: string, overrides: IConfigurationOverrides = {}): { + default: C, + user: C, + workspace: C, + workspaceFolder: C + value: C, + } { // make sure to clone the configuration so that the receiver does not tamper with the values const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); @@ -248,53 +254,26 @@ export class Configuration { default: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._defaults.override(overrides.overrideIdentifier).contents : this._defaults.contents, key)), user: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._user.override(overrides.overrideIdentifier).contents : this._user.contents, key)), workspace: objects.clone(this._workspace ? getConfigurationValue(overrides.overrideIdentifier ? this._workspaceConfiguration.override(overrides.overrideIdentifier).contents : this._workspaceConfiguration.contents, key) : void 0), //Check on workspace exists or not because _workspaceConfiguration is never null - folder: objects.clone(folderConfigurationModel ? getConfigurationValue(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0), + workspaceFolder: objects.clone(folderConfigurationModel ? getConfigurationValue(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0), value: objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)) }; } - keys(overrides: IConfigurationOverrides = {}): IConfigurationKeys { - const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); + keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + } { + const folderConfigurationModel = this.getFolderConfigurationModelForResource(); return { default: this._defaults.keys, user: this._user.keys, workspace: this._workspaceConfiguration.keys, - folder: folderConfigurationModel ? folderConfigurationModel.keys : [] + workspaceFolder: folderConfigurationModel ? folderConfigurationModel.keys : [] }; } - values(): IConfigurationValues { - const result = Object.create(null); - const keyset = this.keys(); - const keys = [...keyset.workspace, ...keyset.user, ...keyset.default].sort(); - - let lastKey: string; - for (const key of keys) { - if (key !== lastKey) { - lastKey = key; - result[key] = this.lookup(key); - } - } - - return result; - } - - values2(): Map> { - const result: Map> = new Map>(); - const keyset = this.keys(); - const keys = [...keyset.workspace, ...keyset.user, ...keyset.default].sort(); - - let lastKey: string; - for (const key of keys) { - if (key !== lastKey) { - lastKey = key; - result.set(key, this.lookup(key)); - } - } - - return result; - } - private getConsolidateConfigurationModel(overrides: IConfigurationOverrides): ConfigurationModel { let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides); return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; @@ -317,7 +296,7 @@ export class Configuration { return this._foldersConsolidatedConfigurations.get(root.uri) || this._workspaceConsolidatedConfiguration; } - private getFolderConfigurationModelForResource(resource: URI): ConfigurationModel { + private getFolderConfigurationModelForResource(resource?: URI): ConfigurationModel { if (!this._workspace || !resource) { return null; } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 0d3276bf69d..f93837028b1 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -35,7 +35,7 @@ export interface IConfigurationRegistry { * Event that fires whenver a configuratio has been * registered. */ - onDidRegisterConfiguration: Event; + onDidRegisterConfiguration: Event; /** * Returns all configuration nodes contributed to this registry. @@ -90,27 +90,25 @@ export const editorConfigurationSchemaId = 'vscode://schemas/settings/editor'; const contributionRegistry = Registry.as(JSONExtensions.JSONContribution); class ConfigurationRegistry implements IConfigurationRegistry { + private configurationContributors: IConfigurationNode[]; private configurationProperties: { [qualifiedKey: string]: IJSONSchema }; private editorConfigurationSchema: IJSONSchema; - private _onDidRegisterConfiguration: Emitter; private overrideIdentifiers: string[] = []; private overridePropertyPattern: string; + private _onDidRegisterConfiguration: Emitter = new Emitter(); + readonly onDidRegisterConfiguration: Event = this._onDidRegisterConfiguration.event; + constructor() { this.configurationContributors = []; this.editorConfigurationSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting' }; - this._onDidRegisterConfiguration = new Emitter(); this.configurationProperties = {}; this.computeOverridePropertyPattern(); contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema); } - public get onDidRegisterConfiguration() { - return this._onDidRegisterConfiguration.event; - } - public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void { this.registerConfigurations([configuration], validate); } @@ -123,7 +121,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updateSchemaForOverrideSettingsConfiguration(configuration); }); - this._onDidRegisterConfiguration.fire(this); + this._onDidRegisterConfiguration.fire(configurations); } public registerOverrideIdentifiers(overrideIdentifiers: string[]): void { diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index 8f6005c6430..843cd285b4d 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ConfigWatcher } from 'vs/base/node/config'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { ConfigurationSource, IConfigurationService, IConfigurationServiceEvent, IConfigurationValue, IConfigurationKeys, ConfigurationModel, IConfigurationOverrides, Configuration, IConfigurationValues, IConfigurationData } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationServiceEvent, IConfigurationOverrides, IConfiguration } from 'vs/platform/configuration/common/configuration2'; +import { ConfigurationModel, Configuration } from 'vs/platform/configuration/common/configuration'; import { CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/model'; import Event, { Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -40,52 +40,53 @@ export class ConfigurationService extends Disposable implements IConfiguratio this._register(this.userConfigModelWatcher); // Listeners - this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this.onConfigurationChange(ConfigurationSource.User))); - this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(() => this.onConfigurationChange(ConfigurationSource.Default))); + this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this.onDidUpdateConfigModel())); + this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(configurationNodes => this.onDidRegisterConfiguration(configurationNodes))); } - public configuration(): Configuration { + public get configuration(): Configuration { return this._configuration || (this._configuration = this.consolidateConfigurations()); } - private onConfigurationChange(source: ConfigurationSource): void { + private onDidUpdateConfigModel(): void { + // get the diff + // reset and trigger + this.onConfigurationChange([], []); + } + + private onDidRegisterConfiguration(configurations: IConfigurationNode[]): void { + // get the diff + // reset and trigger + this.onConfigurationChange([], []); + } + + private onConfigurationChange(sections: string[], keys: string[]): void { this.reset(); // reset our caches - const cache = this.configuration(); - - this._onDidUpdateConfiguration.fire({ - source, - sourceConfig: source === ConfigurationSource.Default ? cache.defaults.contents : cache.user.contents - }); + this._onDidUpdateConfiguration.fire({ sections, keys }); } - public reloadConfiguration(section?: string): TPromise { - return new TPromise(c => { - this.userConfigModelWatcher.reload(() => { - this.reset(); // reset our caches - c(this.getConfiguration(section)); - }); - }); + public getConfiguration(section?: string, options?: IConfigurationOverrides): IConfiguration { + return this.configuration.getValue(section, options); } - public getConfiguration(section?: string, options?: IConfigurationOverrides): C { - return this.configuration().getValue(section, options); + public inspect(key: string): { + default: T, + user: T, + workspace: T, + workspaceFolder: T + value: T + } { + return this.configuration.lookup(key); } - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { - return this.configuration().lookup(key, overrides); - } - - public keys(overrides?: IConfigurationOverrides): IConfigurationKeys { - return this.configuration().keys(overrides); - } - - public values(): IConfigurationValues { - return this._configuration.values(); - } - - public getConfigurationData(): IConfigurationData { - return this.configuration().toData(); + public keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + } { + return this.configuration.keys(); } private reset(): void { diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 28c1b711fed..9dffb9cba33 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration2'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const CONFIG_DEFAULT_NAME = 'settings'; From 75cf7dabb50ace4604806404c7f134e546406828 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Oct 2017 23:13:18 +0200 Subject: [PATCH 03/28] Refactor Configuration Service - API to get a value given a key - API to update value smartly - Fine grained configuration change event - Smart reload API - Remove unnecessary APIs --- .../browser/referencesController.ts | 2 +- .../standalone/browser/simpleServices.ts | 49 +- .../configuration/common/configuration.ts | 148 ++-- .../configuration/common/configuration2.ts | 63 -- .../common/configurationRegistry.ts | 22 +- .../node/configurationService.ts | 106 ++- .../test/common/testConfigurationService.ts | 38 +- .../test/node/configurationService.test.ts | 13 +- .../telemetry/common/telemetryUtils.ts | 8 +- .../electron-browser/telemetryService.test.ts | 18 +- .../mainThreadConfiguration.ts | 2 +- .../mainThreadSaveParticipant.ts | 8 +- .../api/node/extHostConfiguration.ts | 2 +- .../browser/parts/editor/editorStatus.ts | 2 +- .../browser/parts/titlebar/titlebarPart.ts | 2 +- src/vs/workbench/electron-browser/actions.ts | 4 +- src/vs/workbench/electron-browser/main.ts | 2 +- src/vs/workbench/electron-browser/window.ts | 2 +- .../workbench/electron-browser/workbench.ts | 30 +- .../electron-browser/wordWrapMigration.ts | 2 +- .../parts/debug/browser/debugActionItems.ts | 2 +- .../debug/electron-browser/debugService.ts | 2 +- .../browser/preferencesRenderers.ts | 6 +- .../preferences/browser/preferencesService.ts | 2 + .../preferences/common/preferencesModels.ts | 4 +- .../electron-browser/terminalConfigHelper.ts | 4 +- .../terminalConfigHelper.test.ts | 11 +- .../electron-browser/themes.contribution.ts | 4 +- .../watermark/electron-browser/watermark.ts | 4 +- .../page/electron-browser/welcomePage.ts | 4 +- .../electron-browser/walkThroughPart.ts | 6 +- .../configuration/common/configuration.ts | 9 +- .../common/configurationExtensionPoint.ts | 212 ++++++ .../common/configurationModels.ts | 91 ++- .../node/configurationEditingService.ts | 6 +- ...nfiguration.ts => configurationService.ts} | 717 +++++++----------- .../node/configurationEditingService.test.ts | 14 +- ...n.test.ts => configurationService.test.ts} | 62 +- .../node/configurationResolverService.test.ts | 11 +- .../electron-browser/extensionHost.ts | 2 +- .../electron-browser/workbenchThemeService.ts | 16 +- .../workspace/node/workspaceEditingService.ts | 4 +- src/vs/workbench/workbench.main.ts | 3 + 43 files changed, 969 insertions(+), 750 deletions(-) delete mode 100644 src/vs/platform/configuration/common/configuration2.ts create mode 100644 src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts rename src/vs/workbench/services/configuration/node/{configuration.ts => configurationService.ts} (56%) rename src/vs/workbench/services/configuration/test/node/{configuration.test.ts => configurationService.test.ts} (90%) diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts index d268a392aff..7678766238c 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts @@ -123,7 +123,7 @@ export class ReferencesController implements editorCommon.IEditorContribution { switch (kind) { case 'open': if (event.source === 'editor' - && this._configurationService.lookup('editor.stablePeek').value) { + && this._configurationService.getValue('editor.stablePeek')) { // when stable peek is configured we don't close // the peek window on selecting the editor diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 41ddb0f59ee..75aaa4492f9 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -8,7 +8,7 @@ import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IConfigurationService, IConfigurationServiceEvent, IConfigurationValue, IConfigurationKeys, IConfigurationValues, Configuration, IConfigurationData, ConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent, Configuration, ConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IEditor, IEditorInput, IEditorOptions, IEditorService, IResourceInput, Position } from 'vs/platform/editor/common/editor'; import { ICommandService, ICommand, ICommandEvent, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -428,12 +428,19 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { } } +function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { + return thing + && typeof thing === 'object' + && (!thing.overrideIdentifier || typeof thing.overrideIdentifier === 'string') + && (!thing.resource || thing.resource instanceof URI); +} + export class SimpleConfigurationService implements IConfigurationService { _serviceBrand: any; - private _onDidUpdateConfiguration = new Emitter(); - public onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + private _onDidUpdateConfiguration = new Emitter(); + public onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; private _configuration: Configuration; @@ -445,28 +452,40 @@ export class SimpleConfigurationService implements IConfigurationService { return this._configuration; } - public reloadConfiguration(section?: string): TPromise { - return TPromise.as(this.getConfiguration(section)); + getConfiguration(): T + getConfiguration(section: string): T + getConfiguration(overrides: IConfigurationOverrides): T + getConfiguration(section: string, overrides: IConfigurationOverrides): T + getConfiguration(arg1?: any, arg2?: any): any { + const section = typeof arg1 === 'string' ? arg1 : void 0; + const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; + return this.configuration().getValue(section, overrides); } - public getConfiguration(section?: string, options?: IConfigurationOverrides): C { - return this.configuration().getValue(section, options); + public getValue(key: string, options?: IConfigurationOverrides): C { + return this.configuration().getValue2(key, options); } - public lookup(key: string, options?: IConfigurationOverrides): IConfigurationValue { + public updateValue(key: string, value: any, arg3?: any, arg4?: any): TPromise { + return TPromise.as(null); + } + + public inspect(key: string, options?: IConfigurationOverrides): { + default: C, + user: C, + workspace: C, + workspaceFolder: C + value: C, + } { return this.configuration().lookup(key, options); } - public keys(): IConfigurationKeys { + public keys() { return this.configuration().keys(); } - public values(): IConfigurationValues { - return this._configuration.values(); - } - - public getConfigurationData(): IConfigurationData { - return this.configuration().toData(); + public reloadConfiguration(): TPromise { + return TPromise.as(null); } } diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 16dc333a62f..20f1b668201 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -9,9 +9,10 @@ import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import { StrictResourceMap } from 'vs/base/common/map'; -import { Workspace } from 'vs/platform/workspace/common/workspace'; +import { Workspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import Event from 'vs/base/common/event'; +import { OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; export const IConfigurationService = createDecorator('configurationService'); @@ -20,80 +21,79 @@ export interface IConfigurationOverrides { resource?: URI; } -export type IConfigurationValues = { [key: string]: IConfigurationValue }; +export enum ConfigurationTarget { + DEFAULT = 1, + USER, + WORKSPACE, + WORKSPACE_FOLDER, + MEMORY +} + +export interface IConfigurationChangeEvent { + keys: string[]; + sections: string[]; + overrideIdentifiers?: string[]; + + hasSectionChanged(section: string): boolean; + hasKeyChanged(key: string): boolean; + + // Following data is used for telemetry + source: ConfigurationTarget; + sourceConfig: any; +} export interface IConfigurationService { _serviceBrand: any; - getConfigurationData(): IConfigurationData; + onDidUpdateConfiguration: Event; - /** - * Fetches the appropriate section of the configuration JSON file. - * This will be an object keyed off the section name. - */ - getConfiguration(section?: string, overrides?: IConfigurationOverrides): T; + getConfiguration(): T; + getConfiguration(section: string): T; + getConfiguration(overrides: IConfigurationOverrides): T; + getConfiguration(section: string, overrides: IConfigurationOverrides): T; - /** - * Resolves a configuration key to its values in the different scopes - * the setting is defined. - */ - lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue; + getValue(key: string, overrides?: IConfigurationOverrides): T; - /** - * Returns the defined keys of configurations in the different scopes - * the key is defined. - */ - keys(overrides?: IConfigurationOverrides): IConfigurationKeys; + updateValue(key: string, value: any): TPromise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise; + updateValue(key: string, value: any, target: ConfigurationTarget): TPromise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise; - /** - * Similar to #getConfiguration() but ensures that the latest configuration - * from disk is fetched. - */ - reloadConfiguration(section?: string): TPromise; + reloadConfiguration(): TPromise; + reloadConfiguration(folder?: IWorkspaceFolder): TPromise; - /** - * Event that fires when the configuration changes. - */ - onDidUpdateConfiguration: Event; + inspect(key: string): { + default: T, + user: T, + workspace: T, + workspaceFolder: T + value: T, + }; - /** - * Returns the defined values of configurations in the different scopes. - */ - values(): IConfigurationValues; + keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + }; } -export enum ConfigurationSource { - Default = 1, - User, - Workspace -} +export function toConfigurationUpdateEvent(udpated: string[], source: ConfigurationTarget, sourceConfig: any): IConfigurationChangeEvent { + const overrideIdentifiers = []; + const keys: string[] = []; + for (const key of udpated) { + if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + overrideIdentifiers.push(key); + } else { + keys.push(key); + } + } + const sections = arrays.distinct(keys.map(key => key.split('.')[0])); + const hasSectionChanged = (section) => sections.indexOf(section) !== -1; + const hasKeyChanged = (key) => keys.indexOf(key) !== -1; -export interface IConfigurationServiceEvent { - /** - * The type of source that triggered this event. - */ - source: ConfigurationSource; - /** - * The part of the configuration contributed by the source of this event. - */ - sourceConfig: any; + return { keys, sections, overrideIdentifiers, source, sourceConfig, hasSectionChanged, hasKeyChanged }; } - -export interface IConfigurationValue { - value: T; - default: T; - user: T; - workspace: T; - folder: T; -} - -export interface IConfigurationKeys { - default: string[]; - user: string[]; - workspace: string[]; - folder: string[]; -} - /** * A helper function to get the configuration value with a specific settings path (e.g. config.some.setting) */ @@ -197,6 +197,22 @@ export class ConfigurationModel implements IConfiguraionModel { } } +export function compare(from: ConfigurationModel, to: ConfigurationModel): { added: string[], removed: string[], updated: string[] } { + const added = to.keys.filter(key => from.keys.indexOf(key) === -1); + const removed = from.keys.filter(key => to.keys.indexOf(key) === -1); + const updated = []; + + for (const key of from.keys) { + const value1 = getConfigurationValue(from.contents, key); + const value2 = getConfigurationValue(to.contents, key); + if (!objects.equals(value1, value2)) { + updated.push(key); + } + } + + return { added, removed, updated }; +} + export interface IConfigurationData { defaults: IConfiguraionModel; user: IConfiguraionModel; @@ -222,6 +238,10 @@ export class Configuration { return this._user; } + get workspace(): ConfigurationModel { + return this._workspaceConfiguration; + } + protected merge(): void { this._globalConfiguration = new ConfigurationModel().merge(this._defaults).merge(this._user); this._workspaceConsolidatedConfiguration = new ConfigurationModel().merge(this._globalConfiguration).merge(this._workspaceConfiguration); @@ -240,6 +260,12 @@ export class Configuration { return section ? configModel.getContentsFor(section) : configModel.contents; } + getValue2(key: string, overrides: IConfigurationOverrides = {}): any { + // make sure to clone the configuration so that the receiver does not tamper with the values + const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); + return objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)); + } + lookup(key: string, overrides: IConfigurationOverrides = {}): { default: C, user: C, diff --git a/src/vs/platform/configuration/common/configuration2.ts b/src/vs/platform/configuration/common/configuration2.ts deleted file mode 100644 index a3edb5c0048..00000000000 --- a/src/vs/platform/configuration/common/configuration2.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TPromise } from 'vs/base/common/winjs.base'; -import URI from 'vs/base/common/uri'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import Event from 'vs/base/common/event'; - -export const IConfigurationService = createDecorator('configurationService'); - -export interface IConfigurationOverrides { - overrideIdentifier?: string; - resource?: URI; -} - -export enum ConfigurationTarget { - USER, - WORKSPACE, - WORKSPACE_FOLDER, - MEMORY -} - -export interface IConfigurationServiceEvent { - sections: string[]; - keys: string[]; -} - -export interface IConfiguration { - readonly [key: string]: any; -} - -export interface IConfigurationService { - _serviceBrand: any; - - onDidUpdateConfiguration: Event; - - getConfiguration(): T; - getConfiguration(section: string): T; - getConfiguration(overrides: IConfigurationOverrides): T; - getConfiguration(section: string, overrides: IConfigurationOverrides): T; - - updateConfiguration(key: string, value: any): TPromise; - updateConfiguration(key: string, value: any, overrides: IConfigurationOverrides): TPromise; - updateConfiguration(key: string, value: any, target: ConfigurationTarget): TPromise; - updateConfiguration(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise; - - inspect(key: string): { - default: T, - user: T, - workspace: T, - workspaceFolder: T - value: T, - }; - - keys(): { - default: string[]; - user: string[]; - workspace: string[]; - workspaceFolder: string[]; - }; -} \ No newline at end of file diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index f93837028b1..83d78eda6da 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -32,10 +32,10 @@ export interface IConfigurationRegistry { registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void; /** - * Event that fires whenver a configuratio has been + * Event that fires whenver a configuration has been * registered. */ - onDidRegisterConfiguration: Event; + onDidRegisterConfiguration: Event; /** * Returns all configuration nodes contributed to this registry. @@ -97,8 +97,8 @@ class ConfigurationRegistry implements IConfigurationRegistry { private overrideIdentifiers: string[] = []; private overridePropertyPattern: string; - private _onDidRegisterConfiguration: Emitter = new Emitter(); - readonly onDidRegisterConfiguration: Event = this._onDidRegisterConfiguration.event; + private _onDidRegisterConfiguration: Emitter = new Emitter(); + readonly onDidRegisterConfiguration: Event = this._onDidRegisterConfiguration.event; constructor() { this.configurationContributors = []; @@ -114,14 +114,15 @@ class ConfigurationRegistry implements IConfigurationRegistry { } public registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void { + const properties = []; configurations.forEach(configuration => { - this.validateAndRegisterProperties(configuration, validate); // fills in defaults + properties.push(this.validateAndRegisterProperties(configuration, validate)); // fills in defaults this.configurationContributors.push(configuration); this.registerJSONConfiguration(configuration); this.updateSchemaForOverrideSettingsConfiguration(configuration); }); - this._onDidRegisterConfiguration.fire(configurations); + this._onDidRegisterConfiguration.fire(properties); } public registerOverrideIdentifiers(overrideIdentifiers: string[]): void { @@ -153,9 +154,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } - private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false) { + private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false): string[] { scope = configuration.scope !== void 0 && configuration.scope !== null ? configuration.scope : scope; overridable = configuration.overridable || overridable; + let propertyKeys = []; let properties = configuration.properties; if (properties) { for (let key in properties) { @@ -180,14 +182,16 @@ class ConfigurationRegistry implements IConfigurationRegistry { } // add to properties map this.configurationProperties[key] = properties[key]; + propertyKeys.push(key); } } let subNodes = configuration.allOf; if (subNodes) { for (let node of subNodes) { - this.validateAndRegisterProperties(node, validate, scope, overridable); + propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope, overridable)); } } + return propertyKeys; } validateProperty(property: string): boolean { @@ -302,4 +306,4 @@ export function validateProperty(property: string): string { return nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", property); } return null; -} +} \ No newline at end of file diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index 843cd285b4d..0d97199766b 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -6,14 +6,24 @@ import { ConfigWatcher } from 'vs/base/node/config'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IConfigurationService, IConfigurationServiceEvent, IConfigurationOverrides, IConfiguration } from 'vs/platform/configuration/common/configuration2'; -import { ConfigurationModel, Configuration } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, toConfigurationUpdateEvent, ConfigurationModel, Configuration, compare } from 'vs/platform/configuration/common/configuration'; import { CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/model'; import Event, { Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { onUnexpectedError } from 'vs/base/common/errors'; +import URI from 'vs/base/common/uri'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { equals } from 'vs/base/common/objects'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; + +export function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { + return thing + && typeof thing === 'object' + && (!thing.overrideIdentifier || typeof thing.overrideIdentifier === 'string') + && (!thing.resource || thing.resource instanceof URI); +} export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { @@ -22,8 +32,8 @@ export class ConfigurationService extends Disposable implements IConfiguratio private _configuration: Configuration; private userConfigModelWatcher: ConfigWatcher>; - private _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + private _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); + readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; constructor( @IEnvironmentService environmentService: IEnvironmentService @@ -39,38 +49,40 @@ export class ConfigurationService extends Disposable implements IConfiguratio }); this._register(this.userConfigModelWatcher); + this.reset(); + // Listeners this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this.onDidUpdateConfigModel())); - this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(configurationNodes => this.onDidRegisterConfiguration(configurationNodes))); + this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDidRegisterConfiguration(configurationProperties))); } - public get configuration(): Configuration { - return this._configuration || (this._configuration = this.consolidateConfigurations()); + get configuration(): Configuration { + return this._configuration; } - private onDidUpdateConfigModel(): void { - // get the diff - // reset and trigger - this.onConfigurationChange([], []); + getConfiguration(): T + getConfiguration(section: string): T + getConfiguration(overrides: IConfigurationOverrides): T + getConfiguration(section: string, overrides: IConfigurationOverrides): T + getConfiguration(arg1?: any, arg2?: any): any { + const section = typeof arg1 === 'string' ? arg1 : void 0; + const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; + return this.configuration.getValue(section, overrides); } - private onDidRegisterConfiguration(configurations: IConfigurationNode[]): void { - // get the diff - // reset and trigger - this.onConfigurationChange([], []); + getValue(key: string, overrides: IConfigurationOverrides): any { + return this.configuration.getValue2(key, overrides); } - private onConfigurationChange(sections: string[], keys: string[]): void { - this.reset(); // reset our caches - - this._onDidUpdateConfiguration.fire({ sections, keys }); + updateValue(key: string, value: any): TPromise + updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise + updateValue(key: string, value: any, target: ConfigurationTarget): TPromise + updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise + updateValue(key: string, value: any, arg3?: any, arg4?: any): TPromise { + return TPromise.wrapError(new Error('not supported')); } - public getConfiguration(section?: string, options?: IConfigurationOverrides): IConfiguration { - return this.configuration.getValue(section, options); - } - - public inspect(key: string): { + inspect(key: string): { default: T, user: T, workspace: T, @@ -80,7 +92,7 @@ export class ConfigurationService extends Disposable implements IConfiguratio return this.configuration.lookup(key); } - public keys(): { + keys(): { default: string[]; user: string[]; workspace: string[]; @@ -89,13 +101,47 @@ export class ConfigurationService extends Disposable implements IConfiguratio return this.configuration.keys(); } - private reset(): void { - this._configuration = this.consolidateConfigurations(); + reloadConfiguration(folder?: IWorkspaceFolder): TPromise { + return folder ? TPromise.as(null) : + new TPromise((c, e) => this.userConfigModelWatcher.reload(() => c(this.onDidUpdateConfigModel()))); } - private consolidateConfigurations(): Configuration { + private onDidUpdateConfigModel(): void { + let changedKeys = []; + const { added, updated, removed } = compare(this._configuration.user, this.userConfigModelWatcher.getConfig()); + changedKeys = [...added, ...updated, ...removed]; + if (changedKeys.length) { + const oldConfiguartion = this._configuration; + this.reset(); + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue2(key), this._configuration.getValue2(key))); + if (changedKeys.length) { + this.trigger(changedKeys, ConfigurationTarget.USER); + } + } + } + + private onDidRegisterConfiguration(keys: string[]): void { + this.reset(); // reset our caches + this.trigger(keys, ConfigurationTarget.DEFAULT); + } + + private reset(): void { const defaults = new DefaultConfigurationModel(); const user = this.userConfigModelWatcher.getConfig(); - return new Configuration(defaults, user); + this._configuration = new Configuration(defaults, user); + } + + private trigger(keys: string[], source: ConfigurationTarget): void { + this._onDidUpdateConfiguration.fire(toConfigurationUpdateEvent(keys, source, this.getTargetConfiguration(source))); + } + + private getTargetConfiguration(target: ConfigurationTarget): any { + switch (target) { + case ConfigurationTarget.DEFAULT: + return this._configuration.defaults.contents; + case ConfigurationTarget.USER: + return this._configuration.user.contents; + } + return {}; } } \ No newline at end of file diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index aed4574d678..5653f211b14 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -10,7 +10,7 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { EventEmitter } from 'vs/base/common/eventEmitter'; import { getConfigurationKeys } from 'vs/platform/configuration/common/model'; -import { IConfigurationOverrides, IConfigurationService, getConfigurationValue, IConfigurationValue, IConfigurationKeys, IConfigurationValues, IConfigurationData, Configuration, ConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationOverrides, IConfigurationService, getConfigurationValue } from 'vs/platform/configuration/common/configuration'; export class TestConfigurationService extends EventEmitter implements IConfigurationService { public _serviceBrand: any; @@ -19,11 +19,11 @@ export class TestConfigurationService extends EventEmitter implements IConfigura private configurationByRoot: TernarySearchTree = TernarySearchTree.forPaths(); - public reloadConfiguration(section?: string): TPromise { + public reloadConfiguration(): TPromise { return TPromise.as(this.getConfiguration()); } - public getConfiguration(section?: string, overrides?: IConfigurationOverrides): any { + public getConfiguration(section?: any, overrides?: any): C { if (overrides && overrides.resource) { const configForResource = this.configurationByRoot.findSubstr(overrides.resource.fsPath); return configForResource || this.configuration; @@ -32,8 +32,12 @@ export class TestConfigurationService extends EventEmitter implements IConfigura return this.configuration; } - public getConfigurationData(): IConfigurationData { - return new Configuration(new ConfigurationModel(), new ConfigurationModel(this.configuration)).toData(); + public getValue(key: string, overrides?: IConfigurationOverrides): any { + return this.inspect(key).value; + } + + public updateValue(key: string, overrides?: IConfigurationOverrides): TPromise { + return TPromise.as(null); } public setUserConfiguration(key: any, value: any, root?: URI): Thenable { @@ -52,28 +56,30 @@ export class TestConfigurationService extends EventEmitter implements IConfigura return { dispose() { } }; } - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { + public inspect(key: string, overrides?: IConfigurationOverrides): { + default: T, + user: T, + workspace: T, + workspaceFolder: T + value: T, + } { const config = this.getConfiguration(undefined, overrides); return { - value: getConfigurationValue(config, key), - default: getConfigurationValue(config, key), - user: getConfigurationValue(config, key), + value: getConfigurationValue(config, key), + default: getConfigurationValue(config, key), + user: getConfigurationValue(config, key), workspace: null, - folder: null + workspaceFolder: null }; } - public keys(): IConfigurationKeys { + public keys() { return { default: getConfigurationKeys(), user: Object.keys(this.configuration), workspace: [], - folder: [] + workspaceFolder: [] }; } - - public values(): IConfigurationValues { - return {}; - } } diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index 594a6eb5287..60652d4b614 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -122,7 +122,8 @@ suite('ConfigurationService - Node', () => { assert.equal(config.foo, 'bar'); // force a reload to get latest - service.reloadConfiguration<{ foo: string }>().then(config => { + service.reloadConfiguration().then(() => { + config = service.getConfiguration<{ foo: string }>(); assert.ok(config); assert.equal(config.foo, 'changed'); @@ -202,12 +203,12 @@ suite('ConfigurationService - Node', () => { testFile((testFile, cleanUp) => { const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile)); - let res = service.lookup('something.missing'); + let res = service.inspect('something.missing'); assert.strictEqual(res.value, void 0); assert.strictEqual(res.default, void 0); assert.strictEqual(res.user, void 0); - res = service.lookup('lookup.service.testSetting'); + res = service.inspect('lookup.service.testSetting'); assert.strictEqual(res.default, 'isSet'); assert.strictEqual(res.value, 'isSet'); assert.strictEqual(res.user, void 0); @@ -215,7 +216,7 @@ suite('ConfigurationService - Node', () => { fs.writeFileSync(testFile, '{ "lookup.service.testSetting": "bar" }'); return service.reloadConfiguration().then(() => { - res = service.lookup('lookup.service.testSetting'); + res = service.inspect('lookup.service.testSetting'); assert.strictEqual(res.default, 'isSet'); assert.strictEqual(res.user, 'bar'); assert.strictEqual(res.value, 'bar'); @@ -242,7 +243,7 @@ suite('ConfigurationService - Node', () => { testFile((testFile, cleanUp) => { const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile)); - let res = service.lookup('lookup.service.testNullSetting'); + let res = service.inspect('lookup.service.testNullSetting'); assert.strictEqual(res.default, null); assert.strictEqual(res.value, null); assert.strictEqual(res.user, void 0); @@ -250,7 +251,7 @@ suite('ConfigurationService - Node', () => { fs.writeFileSync(testFile, '{ "lookup.service.testNullSetting": null }'); return service.reloadConfiguration().then(() => { - res = service.lookup('lookup.service.testNullSetting'); + res = service.inspect('lookup.service.testNullSetting'); assert.strictEqual(res.default, null); assert.strictEqual(res.value, null); assert.strictEqual(res.user, null); diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index a910df65983..9ef38db2fa9 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { guessMimeTypes } from 'vs/base/common/mime'; import paths = require('vs/base/common/paths'); import URI from 'vs/base/common/uri'; -import { ConfigurationSource, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; @@ -186,7 +186,7 @@ const configurationValueWhitelist = [ export function configurationTelemetry(telemetryService: ITelemetryService, configurationService: IConfigurationService): IDisposable { return configurationService.onDidUpdateConfiguration(event => { - if (event.source !== ConfigurationSource.Default) { + if (event.source !== ConfigurationTarget.DEFAULT) { /* __GDPR__ "updateConfiguration" : { "configurationSource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -194,7 +194,7 @@ export function configurationTelemetry(telemetryService: ITelemetryService, conf } */ telemetryService.publicLog('updateConfiguration', { - configurationSource: ConfigurationSource[event.source], + configurationSource: ConfigurationTarget[event.source], configurationKeys: flattenKeys(event.sourceConfig) }); /* __GDPR__ @@ -204,7 +204,7 @@ export function configurationTelemetry(telemetryService: ITelemetryService, conf } */ telemetryService.publicLog('updateConfigurationValues', { - configurationSource: ConfigurationSource[event.source], + configurationSource: ConfigurationTarget[event.source], configurationValues: flattenValues(event.sourceConfig, configurationValueWhitelist) }); } diff --git a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts index 494159a5f56..45173045d9a 100644 --- a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts @@ -681,24 +681,24 @@ suite('TelemetryService', () => { enableTelemetry: enableTelemetry } as any; }, - getConfigurationData(): any { + getValue(key) { + return getConfigurationValue(this.getConfiguration(), key); + }, + updateValue() { return null; }, - reloadConfiguration() { - return TPromise.as(this.getConfiguration()); - }, - lookup(key: string) { + inspect(key: string) { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspace: null, - folder: null + workspaceFolder: null }; }, - keys() { return { default: [], user: [], workspace: [], folder: [] }; }, - values() { return {}; }, - onDidUpdateConfiguration: emitter.event + keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; }, + onDidUpdateConfiguration: emitter.event, + reloadConfiguration() { return null; } }); assert.equal(service.isOptedIn, false); diff --git a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts index 71f54113cfd..46648b2b1ae 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts @@ -29,7 +29,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { const proxy = extHostContext.get(ExtHostContext.ExtHostConfiguration); this._configurationListener = configurationService.onDidUpdateConfiguration(() => { - proxy.$acceptConfigurationChanged(configurationService.getConfigurationData()); + proxy.$acceptConfigurationChanged(configurationService.getConfiguration()); }); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts index 777ec4918af..d46aff79813 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts @@ -41,7 +41,7 @@ class TrimWhitespaceParticipant implements INamedSaveParticpant { } public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void { - if (this.configurationService.lookup('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) { + if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); } } @@ -99,7 +99,7 @@ export class FinalNewLineParticipant implements INamedSaveParticpant { } public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void { - if (this.configurationService.lookup('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) { + if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doInsertFinalNewLine(model.textEditorModel); } } @@ -139,7 +139,7 @@ export class TrimFinalNewLinesParticipant implements INamedSaveParticpant { } public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void { - if (this.configurationService.lookup('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) { + if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doTrimFinalNewLines(model.textEditorModel); } } @@ -189,7 +189,7 @@ class FormatOnSaveParticipant implements INamedSaveParticpant { const model = editorModel.textEditorModel; if (env.reason === SaveReason.AUTO - || !this._configurationService.lookup('editor.formatOnSave', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }).value) { + || !this._configurationService.getValue('editor.formatOnSave', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() })) { return undefined; } diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 931b01fc875..9e5101340bf 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -104,7 +104,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { defaultValue: config.default, globalValue: config.user, workspaceValue: config.workspace, - workspaceFolderValue: config.folder + workspaceFolderValue: config.workspaceFolder }; } return undefined; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 28a91b2db63..ab16b258da8 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -966,7 +966,7 @@ export class ChangeModeAction extends Action { TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).done(() => { this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).done(language => { if (language) { - const fileAssociationsConfig = this.configurationService.lookup(ChangeModeAction.FILE_ASSOCIATION_KEY); + const fileAssociationsConfig = this.configurationService.inspect(ChangeModeAction.FILE_ASSOCIATION_KEY); let associationKey: string; if (extension && basename[0] !== '.') { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 806c5d7d2c8..bfba7464b3d 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -118,7 +118,7 @@ export class TitlebarPart extends Part implements ITitleService { private onConfigurationChanged(update?: boolean): void { const currentTitleTemplate = this.titleTemplate; - this.titleTemplate = this.configurationService.lookup('window.title').value; + this.titleTemplate = this.configurationService.getValue('window.title'); if (update && currentTitleTemplate !== this.titleTemplate) { this.setTitle(this.getWindowTitle()); diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 67796b9bc2f..70406345142 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -164,7 +164,7 @@ export class ToggleMenuBarAction extends Action { } public run(): TPromise { - let currentVisibilityValue = this.configurationService.lookup(ToggleMenuBarAction.menuBarVisibilityKey).value; + let currentVisibilityValue = this.configurationService.getValue(ToggleMenuBarAction.menuBarVisibilityKey); if (typeof currentVisibilityValue !== 'string') { currentVisibilityValue = 'default'; } @@ -210,7 +210,7 @@ export abstract class BaseZoomAction extends Action { protected setConfiguredZoomLevel(level: number): void { let target = ConfigurationTarget.USER; - if (typeof this.configurationService.lookup(BaseZoomAction.SETTING_KEY).workspace === 'number') { + if (typeof this.configurationService.inspect(BaseZoomAction.SETTING_KEY).workspace === 'number') { target = ConfigurationTarget.WORKSPACE; } diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index f461e27e464..7784777acea 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -17,7 +17,7 @@ import paths = require('vs/base/common/paths'); import uri from 'vs/base/common/uri'; import strings = require('vs/base/common/strings'); import { IWorkspaceContextService, Workspace, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configuration'; +import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { realpath } from 'vs/base/node/pfs'; diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index bdf4e7eeef2..d90e9c76efc 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -498,7 +498,7 @@ export class ElectronWindow extends Themable { } private toggleAutoSave(): void { - const setting = this.configurationService.lookup(ElectronWindow.AUTO_SAVE_SETTING); + const setting = this.configurationService.inspect(ElectronWindow.AUTO_SAVE_SETTING); let userAutoSaveConfig = setting.user; if (types.isUndefinedOrNull(userAutoSaveConfig)) { userAutoSaveConfig = setting.default; // use default if setting not defined diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index c565be4c877..59b300fc28b 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -46,7 +46,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ContextMenuService } from 'vs/workbench/services/contextview/electron-browser/contextmenuService'; import { WorkbenchKeybindingService } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { WorkspaceService, DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configuration'; +import { WorkspaceService, DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationService'; import { IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; @@ -407,7 +407,7 @@ export class Workbench implements IPartService { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: isDeveloping ? null : KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', localize('file', "File")); // Actions for macOS native tabs management (only when enabled) - const windowConfig = this.configurationService.getConfiguration(); + const windowConfig = this.configurationService.getConfiguration(); if (windowConfig && windowConfig.window && windowConfig.window.nativeTabs) { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousWindowTab, ShowPreviousWindowTab.ID, ShowPreviousWindowTab.LABEL), 'Show Previous Window Tab'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextWindowTab, ShowNextWindowTab.ID, ShowNextWindowTab.LABEL), 'Show Next Window Tab'); @@ -484,13 +484,13 @@ export class Workbench implements IPartService { } private openUntitledFile() { - const startupEditor = this.configurationService.lookup('workbench.startupEditor'); + const startupEditor = this.configurationService.inspect('workbench.startupEditor'); // Fallback to previous workbench.welcome.enabled setting in case startupEditor is not defined if (!startupEditor.user && !startupEditor.workspace) { - const welcomeEnabled = this.configurationService.lookup('workbench.welcome.enabled'); - if (typeof welcomeEnabled.value === 'boolean') { - return !welcomeEnabled.value; + const welcomeEnabledValue = this.configurationService.getValue('workbench.welcome.enabled'); + if (typeof welcomeEnabledValue === 'boolean') { + return !welcomeEnabledValue; } } @@ -639,19 +639,19 @@ export class Workbench implements IPartService { } // Sidebar position - const sideBarPosition = this.configurationService.lookup(Workbench.sidebarPositionConfigurationKey).value; + const sideBarPosition = this.configurationService.getValue(Workbench.sidebarPositionConfigurationKey); this.sideBarPosition = (sideBarPosition === 'right') ? Position.RIGHT : Position.LEFT; // Statusbar visibility - const statusBarVisible = this.configurationService.lookup(Workbench.statusbarVisibleConfigurationKey).value; + const statusBarVisible = this.configurationService.getValue(Workbench.statusbarVisibleConfigurationKey); this.statusBarHidden = !statusBarVisible; // Activity bar visibility - const activityBarVisible = this.configurationService.lookup(Workbench.activityBarVisibleConfigurationKey).value; + const activityBarVisible = this.configurationService.getValue(Workbench.activityBarVisibleConfigurationKey); this.activityBarHidden = !activityBarVisible; // Font aliasing - this.fontAliasing = this.configurationService.lookup(Workbench.fontAliasingConfigurationKey).value; + this.fontAliasing = this.configurationService.getValue(Workbench.fontAliasingConfigurationKey); // Zen mode this.zenMode = { @@ -1036,7 +1036,7 @@ export class Workbench implements IPartService { // Overruled by: window has a workspace opened or this window is for extension development // or setting is disabled. Also enabled when running with --wait from the command line. if (visibleEditors === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) { - const closeWhenEmpty = this.configurationService.lookup(Workbench.closeWhenEmptyConfigurationKey).value; + const closeWhenEmpty = this.configurationService.getValue(Workbench.closeWhenEmptyConfigurationKey); if (closeWhenEmpty || this.environmentService.args.wait) { this.closeEmptyWindowScheduler.schedule(); } @@ -1068,24 +1068,24 @@ export class Workbench implements IPartService { } private onDidUpdateConfiguration(skipLayout?: boolean): void { - const newSidebarPositionValue = this.configurationService.lookup(Workbench.sidebarPositionConfigurationKey).value; + const newSidebarPositionValue = this.configurationService.getValue(Workbench.sidebarPositionConfigurationKey); const newSidebarPosition = (newSidebarPositionValue === 'right') ? Position.RIGHT : Position.LEFT; if (newSidebarPosition !== this.getSideBarPosition()) { this.setSideBarPosition(newSidebarPosition); } - const fontAliasing = this.configurationService.lookup(Workbench.fontAliasingConfigurationKey).value; + const fontAliasing = this.configurationService.getValue(Workbench.fontAliasingConfigurationKey); if (fontAliasing !== this.fontAliasing) { this.setFontAliasing(fontAliasing); } if (!this.zenMode.active) { - const newStatusbarHiddenValue = !this.configurationService.lookup(Workbench.statusbarVisibleConfigurationKey).value; + const newStatusbarHiddenValue = !this.configurationService.getValue(Workbench.statusbarVisibleConfigurationKey); if (newStatusbarHiddenValue !== this.statusBarHidden) { this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout); } - const newActivityBarHiddenValue = !this.configurationService.lookup(Workbench.activityBarVisibleConfigurationKey).value; + const newActivityBarHiddenValue = !this.configurationService.getValue(Workbench.activityBarVisibleConfigurationKey); if (newActivityBarHiddenValue !== this.activityBarHidden) { this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout); } diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/wordWrapMigration.ts b/src/vs/workbench/parts/codeEditor/electron-browser/wordWrapMigration.ts index 83b7176dcbc..e2e99c99949 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/wordWrapMigration.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/wordWrapMigration.ts @@ -83,7 +83,7 @@ class WordWrapMigrationController extends Disposable implements IEditorContribut } WordWrapMigrationController._checked = true; - let result = this.configurationService.lookup('editor.wrappingColumn'); + let result = this.configurationService.inspect('editor.wrappingColumn'); if (typeof result.value === 'undefined') { // Setting is not used return; diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index 346461ba287..18b677b7f61 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -55,7 +55,7 @@ export class StartDebugActionItem extends EventEmitter implements IActionItem { private registerListeners(): void { this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => { - if (e.sourceConfig.launch) { + if (e.hasSectionChanged('launch')) { this.updateOptions(); } })); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 3ea09044e57..15d1484701b 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -648,7 +648,7 @@ export class DebugService implements debug.IDebugService { public startDebugging(root: IWorkspaceFolder, configOrName?: debug.IConfig | string, noDebug = false, topCompoundName?: string): TPromise { // make sure to save all files and that the configuration is up to date - return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration().then(() => + return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(root).then(() => this.extensionService.onReady().then(() => { if (this.model.getProcesses().length === 0) { this.removeReplExpressions(); diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts index d74dd56da46..e3491f2e20a 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts @@ -561,8 +561,10 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr range, options: { stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'findMatch' - } + className: 'findMatch', + + }, + }; } diff --git a/src/vs/workbench/parts/preferences/browser/preferencesService.ts b/src/vs/workbench/parts/preferences/browser/preferencesService.ts index 5e5597721c6..b8e39c05e11 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesService.ts @@ -129,6 +129,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic .then(preferencesEditorModel => preferencesEditorModel ? preferencesEditorModel.content : null); } + // vsode://DefaultSettings/1 createPreferencesEditorModel(uri: URI): TPromise> { let promise = this.defaultPreferencesEditorModels.get(uri); if (promise) { @@ -264,6 +265,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { if (openDefaultSettings) { + // get a new URI for default settings here const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(configurationTarget)); const preferencesEditorInput = new PreferencesEditorInput(this.getPreferencesEditorInputName(configurationTarget, resource), editableSettingsEditorInput.getDescription(), defaultPreferencesEditorInput, editableSettingsEditorInput); this.lastOpenedSettingsInput = preferencesEditorInput; diff --git a/src/vs/workbench/parts/preferences/common/preferencesModels.ts b/src/vs/workbench/parts/preferences/common/preferencesModels.ts index f17231a589b..26e4c93d348 100644 --- a/src/vs/workbench/parts/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/parts/preferences/common/preferencesModels.ts @@ -244,7 +244,9 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti constructor(reference: IReference, private _configurationTarget: ConfigurationTarget, @ITextFileService protected textFileService: ITextFileService) { super(); this.settingsModel = reference.object.textEditorModel; - this._register(this.onDispose(() => reference.dispose())); + this._register(this.onDispose(() => { + reference.dispose(); + })); this._register(this.settingsModel.onDidChangeContent(() => { this._settingsGroups = null; })); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index 6507587aa97..a5e20e79b5d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -114,8 +114,8 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig): void { // Check whether there is a workspace setting const platformKey = platform.isWindows ? 'windows' : platform.isMacintosh ? 'osx' : 'linux'; - const shellConfigValue = this._workspaceConfigurationService.lookup(`terminal.integrated.shell.${platformKey}`); - const shellArgsConfigValue = this._workspaceConfigurationService.lookup(`terminal.integrated.shellArgs.${platformKey}`); + const shellConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shell.${platformKey}`); + const shellArgsConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shellArgs.${platformKey}`); // Check if workspace setting exists and whether it's whitelisted let isWorkspaceShellAllowed = false; diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts index e95533d078f..faa3f2ac009 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts @@ -6,7 +6,7 @@ 'use strict'; import * as assert from 'assert'; -import { IConfigurationService, getConfigurationValue, IConfigurationValue, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, getConfigurationValue, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Platform } from 'vs/base/common/platform'; import { TPromise } from 'vs/base/common/winjs.base'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; @@ -17,13 +17,14 @@ class MockConfigurationService implements IConfigurationService { public _serviceBrand: any; public serviceId = IConfigurationService; public constructor(private configuration: any = {}) { } - public reloadConfiguration(section?: string): TPromise { return TPromise.as(this.getConfiguration()); } - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspace: void 0, folder: void 0 }; } - public keys() { return { default: [], user: [], workspace: [], folder: [] }; } - public values() { return {}; } + public inspect(key: string, overrides?: IConfigurationOverrides): any { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspace: void 0, workspaceFolder: void 0 }; } + public keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; } public getConfiguration(): any { return this.configuration; } + public getValue(key: string, overrides?: IConfigurationOverrides): T { return getConfigurationValue(this.getConfiguration(), key); } + public updateValue(): TPromise { return null; } public getConfigurationData(): any { return null; } public onDidUpdateConfiguration() { return { dispose() { } }; } + public reloadConfiguration() { return null; } } suite('Workbench - TerminalConfigHelper', () => { diff --git a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts index 2c44384ef13..10069d1f991 100644 --- a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts +++ b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts @@ -60,7 +60,7 @@ export class SelectColorThemeAction extends Action { } let target = null; if (applyTheme) { - let confValue = this.configurationService.lookup(COLOR_THEME_SETTING); + let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } @@ -126,7 +126,7 @@ class SelectIconThemeAction extends Action { } let target = null; if (applyTheme) { - let confValue = this.configurationService.lookup(ICON_THEME_SETTING); + let confValue = this.configurationService.inspect(ICON_THEME_SETTING); target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } this.themeService.setFileIconTheme(theme && theme.id, target).done(null, diff --git a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts index 5420ab82b04..21c46e71bdd 100644 --- a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts +++ b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts @@ -120,13 +120,13 @@ export class WatermarkContribution implements IWorkbenchContribution { lifecycleService.onShutdown(this.dispose, this); this.partService.joinCreation().then(() => { - this.enabled = this.configurationService.lookup('workbench.tips.enabled').value; + this.enabled = this.configurationService.getValue('workbench.tips.enabled'); if (this.enabled) { this.create(); } }); this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => { - const enabled = this.configurationService.lookup('workbench.tips.enabled').value; + const enabled = this.configurationService.getValue('workbench.tips.enabled'); if (enabled !== this.enabled) { this.enabled = enabled; if (this.enabled) { diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index 93be1d9e534..c54020a8d3f 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -83,9 +83,9 @@ export class WelcomePageContribution implements IWorkbenchContribution { } function isWelcomePageEnabled(configurationService: IConfigurationService) { - const startupEditor = configurationService.lookup(configurationKey); + const startupEditor = configurationService.inspect(configurationKey); if (!startupEditor.user && !startupEditor.workspace) { - const welcomeEnabled = configurationService.lookup(oldConfigurationKey); + const welcomeEnabled = configurationService.inspect(oldConfigurationKey); if (welcomeEnabled.value !== undefined && welcomeEnabled.value !== null) { return welcomeEnabled.value; } diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts index 3795d6aabd1..173483768ba 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts @@ -285,7 +285,7 @@ export class WalkThroughPart extends BaseEditor { } private getArrowScrollHeight() { - let fontSize = this.configurationService.lookup('editor.fontSize').value; + let fontSize = this.configurationService.getValue('editor.fontSize'); if (typeof fontSize !== 'number' || fontSize < 1) { fontSize = 12; } @@ -510,8 +510,8 @@ export class WalkThroughPart extends BaseEditor { private multiCursorModifier() { const labels = UILabelProvider.modifierLabels[OS]; - const setting = this.configurationService.lookup('editor.multiCursorModifier'); - const modifier = labels[setting.value === 'ctrlCmd' ? (OS === OperatingSystem.Macintosh ? 'metaKey' : 'ctrlKey') : 'altKey']; + const value = this.configurationService.getValue('editor.multiCursorModifier'); + const modifier = labels[value === 'ctrlCmd' ? (OS === OperatingSystem.Macintosh ? 'metaKey' : 'ctrlKey') : 'altKey']; const keys = this.content.querySelectorAll('.multi-cursor-modifier'); Array.prototype.forEach.call(keys, (key: Element) => { while (key.firstChild) { diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 9dffb9cba33..3d4eb1394b2 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IConfigurationService } from 'vs/platform/configuration/common/configuration2'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const CONFIG_DEFAULT_NAME = 'settings'; @@ -13,14 +13,17 @@ export const WORKSPACE_CONFIG_DEFAULT_PATH = `${WORKSPACE_CONFIG_FOLDER_DEFAULT_ export const IWorkspaceConfigurationService = createDecorator('configurationService'); export interface IWorkspaceConfigurationService extends IConfigurationService { - /** * Returns untrusted configuration keys for the current workspace. */ getUnsupportedWorkspaceKeys(): string[]; - } +export const defaultSettingsSchemaId = 'vscode://schemas/settings/default'; +export const userSettingsSchemaId = 'vscode://schemas/settings/user'; +export const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace'; +export const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; + export const TASKS_CONFIGURATION_KEY = 'tasks'; export const LAUNCH_CONFIGURATION_KEY = 'launch'; diff --git a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts new file mode 100644 index 00000000000..7614f64f2e3 --- /dev/null +++ b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts @@ -0,0 +1,212 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as objects from 'vs/base/common/objects'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { workspaceSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; + +const configurationRegistry = Registry.as(Extensions.Configuration); + +const configurationEntrySchema: IJSONSchema = { + type: 'object', + defaultSnippets: [{ body: { title: '', properties: {} } }], + properties: { + title: { + description: nls.localize('vscode.extension.contributes.configuration.title', 'A summary of the settings. This label will be used in the settings file as separating comment.'), + type: 'string' + }, + properties: { + description: nls.localize('vscode.extension.contributes.configuration.properties', 'Description of the configuration properties.'), + type: 'object', + additionalProperties: { + anyOf: [ + { $ref: 'http://json-schema.org/draft-04/schema#' }, + { + type: 'object', + properties: { + isExecutable: { + type: 'boolean' + }, + scope: { + type: 'string', + enum: ['window', 'resource'], + default: 'window', + enumDescriptions: [ + nls.localize('scope.window.description', "Window specific configuration, which can be configured in the User or Workspace settings."), + nls.localize('scope.resource.description', "Resource specific configuration, which can be configured in the User, Workspace or Folder settings.") + ], + description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `window` and `resource`.") + } + } + } + ] + } + } + } +}; + + +// BEGIN VSCode extension point `configuration` +const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configuration', [], { + description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'), + oneOf: [ + configurationEntrySchema, + { + type: 'array', + items: configurationEntrySchema + } + ] +}); +configurationExtPoint.setHandler(extensions => { + const configurations: IConfigurationNode[] = []; + + function handleConfiguration(node: IConfigurationNode, id: string, collector: ExtensionMessageCollector) { + let configuration = objects.clone(node); + + if (configuration.title && (typeof configuration.title !== 'string')) { + collector.error(nls.localize('invalid.title', "'configuration.title' must be a string")); + } + + validateProperties(configuration, collector); + + configuration.id = id; + configurations.push(configuration); + }; + + for (let extension of extensions) { + const collector = extension.collector; + const value = extension.value; + const id = extension.description.id; + if (!Array.isArray(value)) { + handleConfiguration(value, id, collector); + } else { + value.forEach(v => handleConfiguration(v, id, collector)); + } + } + configurationRegistry.registerConfigurations(configurations, false); +}); +// END VSCode extension point `configuration` + +// BEGIN VSCode extension point `configurationDefaults` +const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configurationDefaults', [], { + description: nls.localize('vscode.extension.contributes.defaultConfiguration', 'Contributes default editor configuration settings by language.'), + type: 'object', + defaultSnippets: [{ body: {} }], + patternProperties: { + '\\[.*\\]$': { + type: 'object', + default: {}, + $ref: editorConfigurationSchemaId, + } + } +}); +defaultConfigurationExtPoint.setHandler(extensions => { + const defaultConfigurations: IDefaultConfigurationExtension[] = extensions.map(extension => { + const id = extension.description.id; + const name = extension.description.name; + const defaults = objects.clone(extension.value); + return { + id, name, defaults + }; + }); + configurationRegistry.registerDefaultConfigurations(defaultConfigurations); +}); +// END VSCode extension point `configurationDefaults` + +function validateProperties(configuration: IConfigurationNode, collector: ExtensionMessageCollector): void { + let properties = configuration.properties; + if (properties) { + if (typeof properties !== 'object') { + collector.error(nls.localize('invalid.properties', "'configuration.properties' must be an object")); + configuration.properties = {}; + } + for (let key in properties) { + const message = validateProperty(key); + const propertyConfiguration = configuration.properties[key]; + propertyConfiguration.scope = propertyConfiguration.scope && propertyConfiguration.scope.toString() === 'resource' ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW; + propertyConfiguration.isFromExtensions = true; + if (message) { + collector.warn(message); + delete properties[key]; + } + } + } + let subNodes = configuration.allOf; + if (subNodes) { + collector.error(nls.localize('invalid.allOf', "'configuration.allOf' is deprecated and should no longer be used. Instead, pass multiple configuration sections as an array to the 'configuration' contribution point.")); + for (let node of subNodes) { + validateProperties(node, collector); + } + } +} + +const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); +jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { + default: { + folders: [ + { + path: '' + } + ], + settings: { + } + }, + required: ['folders'], + properties: { + 'folders': { + minItems: 0, + uniqueItems: true, + description: nls.localize('workspaceConfig.folders.description', "List of folders to be loaded in the workspace."), + items: { + type: 'object', + default: { path: '' }, + oneOf: [{ + properties: { + path: { + type: 'string', + description: nls.localize('workspaceConfig.path.description', "A file path. e.g. `/root/folderA` or `./folderA` for a relative path that will be resolved against the location of the workspace file.") + }, + name: { + type: 'string', + description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ") + } + }, + required: ['path'] + }, { + properties: { + uri: { + type: 'string', + description: nls.localize('workspaceConfig.uri.description', "URI of the folder") + }, + name: { + type: 'string', + description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ") + } + }, + required: ['uri'] + }] + } + }, + 'settings': { + type: 'object', + default: {}, + description: nls.localize('workspaceConfig.settings.description', "Workspace settings"), + $ref: workspaceSettingsSchemaId + }, + 'extensions': { + type: 'object', + default: {}, + description: nls.localize('workspaceConfig.extensions.description', "Workspace extensions"), + $ref: 'vscode://schemas/extensions' + } + }, + additionalProperties: false, + errorMessage: nls.localize('unknownWorkspaceProperty', "Unknown workspace configuration property") +}); \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index dd7f73103ed..b23dc396e7c 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -4,13 +4,16 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { clone } from 'vs/base/common/objects'; +import { clone, equals } from 'vs/base/common/objects'; import { CustomConfigurationModel, toValuesTree } from 'vs/platform/configuration/common/model'; -import { ConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel, Configuration as BaseConfiguration, compare } from 'vs/platform/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, IConfigurationPropertySchema, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; +import { Workspace } from 'vs/platform/workspace/common/workspace'; +import { StrictResourceMap } from 'vs/base/common/map'; +import URI from 'vs/base/common/uri'; export class WorkspaceConfigurationModel extends CustomConfigurationModel { @@ -178,4 +181,88 @@ export class FolderConfigurationModel extends CustomConfigurationModel { this.workspaceSettingsConfig.reprocess(); this.consolidate(); } +} + +export class Configuration extends BaseConfiguration { + + constructor(defaults: ConfigurationModel, user: ConfigurationModel, workspaceConfiguration: ConfigurationModel, protected folders: StrictResourceMap>, workspace: Workspace) { + super(defaults, user, workspaceConfiguration, folders, workspace); + } + + updateDefaultConfiguration(defaults: ConfigurationModel): void { + this._defaults = defaults; + this.merge(); + } + + updateUserConfiguration(user: ConfigurationModel): string[] { + let changedKeys = []; + const { added, updated, removed } = compare(this._user, user); + changedKeys = [...added, ...updated, ...removed]; + if (changedKeys.length) { + const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._workspace); + + this._user = user; + this.merge(); + + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue2(key), this.getValue2(key))); + return changedKeys; + } + return []; + } + + updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): string[] { + let changedKeys = []; + const { added, updated, removed } = compare(this._workspaceConfiguration, workspaceConfiguration); + changedKeys = [...added, ...updated, ...removed]; + if (changedKeys.length) { + const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._workspace); + + this._workspaceConfiguration = workspaceConfiguration; + this.merge(); + + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue2(key), this.getValue2(key))); + return changedKeys; + } + return []; + } + + updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel): string[] { + const currentFolderConfiguration = this.folders.get(resource); + + if (currentFolderConfiguration) { + let changedKeys = []; + const { added, updated, removed } = compare(currentFolderConfiguration, configuration); + changedKeys = [...added, ...updated, ...removed]; + if (changedKeys.length) { + const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._workspace); + + this.folders.set(resource, configuration); + this.mergeFolder(resource); + + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue2(key, { resource }), this.getValue2(key, { resource }))); + return changedKeys; + } + return []; + } + + this.folders.set(resource, configuration); + this.mergeFolder(resource); + return configuration.keys; + } + + deleteFolderConfiguration(folder: URI): string[] { + if (this._workspace && this._workspace.folders.length > 0 && this._workspace.folders[0].uri.toString() === folder.toString()) { + // Do not remove workspace configuration + return []; + } + + const keys = this.folders.get(folder).keys; + this.folders.delete(folder); + this._foldersConsolidatedConfigurations.delete(folder); + return keys; + } + + getFolderConfigurationModel(folder: URI): FolderConfigurationModel { + return >this.folders.get(folder); + } } \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index 9787cfcbc1c..694c6122734 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -27,7 +27,7 @@ import { IConfigurationService, IConfigurationOverrides } from 'vs/platform/conf import { keyFromOverrideIdentifier } from 'vs/platform/configuration/common/model'; import { WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; -import { IConfigurationEditingService, ConfigurationEditingErrorCode, ConfigurationEditingError, ConfigurationTarget, IConfigurationValue, IConfigurationEditingOptions } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { ConfigurationTarget, ConfigurationEditingErrorCode, ConfigurationEditingError, IConfigurationValue, IConfigurationEditingOptions, IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IChoiceService, IMessageService, Severity } from 'vs/platform/message/common/message'; @@ -95,9 +95,7 @@ export class ConfigurationEditingService implements IConfigurationEditingService private writeToBuffer(model: editorCommon.IModel, operation: IConfigurationEditOperation, save: boolean): TPromise { const edit = this.getEdits(model, operation)[0]; if (this.applyEditsToBuffer(edit, model) && save) { - return this.textFileService.save(operation.resource, { skipSaveParticipants: true /* programmatic change */ }) - // Reload the configuration so that we make sure all parties are updated - .then(() => this.configurationService.reloadConfiguration()); + return this.textFileService.save(operation.resource, { skipSaveParticipants: true /* programmatic change */ }); } return TPromise.as(null); } diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configurationService.ts similarity index 56% rename from src/vs/workbench/services/configuration/node/configuration.ts rename to src/vs/workbench/services/configuration/node/configurationService.ts index 74556f8b5ab..f5b7cef1e68 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -9,8 +9,8 @@ import * as paths from 'vs/base/common/paths'; import { TPromise } from 'vs/base/common/winjs.base'; import Event, { Emitter } from 'vs/base/common/event'; import { StrictResourceMap } from 'vs/base/common/map'; -import * as objects from 'vs/base/common/objects'; import * as errors from 'vs/base/common/errors'; +import { equals } from 'vs/base/common/objects'; import * as collections from 'vs/base/common/collections'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -23,27 +23,23 @@ import { isLinux } from 'vs/base/common/platform'; import { ConfigWatcher } from 'vs/base/node/config'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { CustomConfigurationModel } from 'vs/platform/configuration/common/model'; -import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels'; -import { IConfigurationServiceEvent, ConfigurationSource, IConfigurationKeys, IConfigurationValue, ConfigurationModel, IConfigurationOverrides, Configuration as BaseConfiguration, IConfigurationValues, IConfigurationData } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; -import { ConfigurationService as GlobalConfigurationService } from 'vs/platform/configuration/node/configurationService'; -import * as nls from 'vs/nls'; +import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; +import { IConfigurationChangeEvent, ConfigurationTarget, toConfigurationUpdateEvent, ConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; +import { ConfigurationService as GlobalConfigurationService, isConfigurationOverrides } from 'vs/platform/configuration/node/configurationService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, ConfigurationScope, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry'; import { createHash } from 'crypto'; import { getWorkspaceLabel, IWorkspacesService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import product from 'vs/platform/node/product'; import pkg from 'vs/platform/node/package'; - -const defaultSettingsSchemaId = 'vscode://schemas/settings/default'; -const userSettingsSchemaId = 'vscode://schemas/settings/user'; -const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace'; -const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; +import { distinct, flatten } from 'vs/base/common/arrays'; +import { IConfigurationEditingService, ConfigurationTarget as EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; interface IStat { resource: URI; @@ -56,212 +52,6 @@ interface IContent { value: string; } -interface IWorkspaceConfiguration { - workspace: T; - consolidated: any; -} - -type IWorkspaceFoldersConfiguration = { [rootFolder: string]: { folders: string[]; } }; - -const configurationRegistry = Registry.as(Extensions.Configuration); - -const configurationEntrySchema: IJSONSchema = { - type: 'object', - defaultSnippets: [{ body: { title: '', properties: {} } }], - properties: { - title: { - description: nls.localize('vscode.extension.contributes.configuration.title', 'A summary of the settings. This label will be used in the settings file as separating comment.'), - type: 'string' - }, - properties: { - description: nls.localize('vscode.extension.contributes.configuration.properties', 'Description of the configuration properties.'), - type: 'object', - additionalProperties: { - anyOf: [ - { $ref: 'http://json-schema.org/draft-04/schema#' }, - { - type: 'object', - properties: { - isExecutable: { - type: 'boolean' - }, - scope: { - type: 'string', - enum: ['window', 'resource'], - default: 'window', - enumDescriptions: [ - nls.localize('scope.window.description', "Window specific configuration, which can be configured in the User or Workspace settings."), - nls.localize('scope.resource.description', "Resource specific configuration, which can be configured in the User, Workspace or Folder settings.") - ], - description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `window` and `resource`.") - } - } - } - ] - } - } - } -}; - - -// BEGIN VSCode extension point `configuration` -const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configuration', [], { - description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'), - oneOf: [ - configurationEntrySchema, - { - type: 'array', - items: configurationEntrySchema - } - ] -}); -configurationExtPoint.setHandler(extensions => { - const configurations: IConfigurationNode[] = []; - - function handleConfiguration(node: IConfigurationNode, id: string, collector: ExtensionMessageCollector) { - let configuration = objects.clone(node); - - if (configuration.title && (typeof configuration.title !== 'string')) { - collector.error(nls.localize('invalid.title', "'configuration.title' must be a string")); - } - - validateProperties(configuration, collector); - - configuration.id = id; - configurations.push(configuration); - }; - - for (let extension of extensions) { - const collector = extension.collector; - const value = extension.value; - const id = extension.description.id; - if (!Array.isArray(value)) { - handleConfiguration(value, id, collector); - } else { - value.forEach(v => handleConfiguration(v, id, collector)); - } - } - configurationRegistry.registerConfigurations(configurations, false); -}); -// END VSCode extension point `configuration` - -// BEGIN VSCode extension point `configurationDefaults` -const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configurationDefaults', [], { - description: nls.localize('vscode.extension.contributes.defaultConfiguration', 'Contributes default editor configuration settings by language.'), - type: 'object', - defaultSnippets: [{ body: {} }], - patternProperties: { - '\\[.*\\]$': { - type: 'object', - default: {}, - $ref: editorConfigurationSchemaId, - } - } -}); -defaultConfigurationExtPoint.setHandler(extensions => { - const defaultConfigurations: IDefaultConfigurationExtension[] = extensions.map(extension => { - const id = extension.description.id; - const name = extension.description.name; - const defaults = objects.clone(extension.value); - return { - id, name, defaults - }; - }); - configurationRegistry.registerDefaultConfigurations(defaultConfigurations); -}); -// END VSCode extension point `configurationDefaults` - -function validateProperties(configuration: IConfigurationNode, collector: ExtensionMessageCollector): void { - let properties = configuration.properties; - if (properties) { - if (typeof properties !== 'object') { - collector.error(nls.localize('invalid.properties', "'configuration.properties' must be an object")); - configuration.properties = {}; - } - for (let key in properties) { - const message = validateProperty(key); - const propertyConfiguration = configuration.properties[key]; - propertyConfiguration.scope = propertyConfiguration.scope && propertyConfiguration.scope.toString() === 'resource' ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW; - propertyConfiguration.isFromExtensions = true; - if (message) { - collector.warn(message); - delete properties[key]; - } - } - } - let subNodes = configuration.allOf; - if (subNodes) { - collector.error(nls.localize('invalid.allOf', "'configuration.allOf' is deprecated and should no longer be used. Instead, pass multiple configuration sections as an array to the 'configuration' contribution point.")); - for (let node of subNodes) { - validateProperties(node, collector); - } - } -} - -const contributionRegistry = Registry.as(JSONExtensions.JSONContribution); -contributionRegistry.registerSchema('vscode://schemas/workspaceConfig', { - default: { - folders: [ - { - path: '' - } - ], - settings: { - } - }, - required: ['folders'], - properties: { - 'folders': { - minItems: 0, - uniqueItems: true, - description: nls.localize('workspaceConfig.folders.description', "List of folders to be loaded in the workspace."), - items: { - type: 'object', - default: { path: '' }, - oneOf: [{ - properties: { - path: { - type: 'string', - description: nls.localize('workspaceConfig.path.description', "A file path. e.g. `/root/folderA` or `./folderA` for a relative path that will be resolved against the location of the workspace file.") - }, - name: { - type: 'string', - description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ") - } - }, - required: ['path'] - }, { - properties: { - uri: { - type: 'string', - description: nls.localize('workspaceConfig.uri.description', "URI of the folder") - }, - name: { - type: 'string', - description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ") - } - }, - required: ['uri'] - }] - } - }, - 'settings': { - type: 'object', - default: {}, - description: nls.localize('workspaceConfig.settings.description', "Workspace settings"), - $ref: workspaceSettingsSchemaId - }, - 'extensions': { - type: 'object', - default: {}, - description: nls.localize('workspaceConfig.extensions.description', "Workspace extensions"), - $ref: 'vscode://schemas/extensions' - } - }, - additionalProperties: false, - errorMessage: nls.localize('unknownWorkspaceProperty', "Unknown workspace configuration property") -}); - export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService { public _serviceBrand: any; @@ -272,8 +62,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat private workspaceConfiguration: WorkspaceConfiguration; private cachedFolderConfigs: StrictResourceMap>; - protected readonly _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + protected readonly _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); + public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; protected readonly _onDidChangeWorkspaceFolders: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkspaceFolders: Event = this._onDidChangeWorkspaceFolders.event; @@ -284,6 +74,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat protected readonly _onDidChangeWorkbenchState: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkbenchState: Event = this._onDidChangeWorkbenchState.event; + private configurationEditingService: IConfigurationEditingService; + constructor(private environmentService: IEnvironmentService, private workspacesService: IWorkspacesService, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) { super(); @@ -292,9 +84,11 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat this.baseConfigurationService = this._register(new GlobalConfigurationService(environmentService)); this._register(this.baseConfigurationService.onDidUpdateConfiguration(e => this.onBaseConfigurationChanged(e))); - this._register(configurationRegistry.onDidRegisterConfiguration(e => this.registerConfigurationSchemas())); + this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(e => this.registerConfigurationSchemas())); } + // Workspace Context Service Impl + public getWorkspace(): Workspace { return this.workspace; } @@ -332,64 +126,112 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return false; } - public getConfigurationData(): IConfigurationData { - return this._configuration.toData(); + // Workspace Configuration Service Impl + + getConfiguration(): T + getConfiguration(section: string): T + getConfiguration(overrides: IConfigurationOverrides): T + getConfiguration(section: string, overrides: IConfigurationOverrides): T + getConfiguration(arg1?: any, arg2?: any): any { + const section = typeof arg1 === 'string' ? arg1 : void 0; + const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; + const contents = this._configuration.getValue(section, overrides); + return typeof contents === 'object' ? { toJSON: () => this._configuration.toData(), ...contents } + : contents; } - public getConfiguration(section?: string, overrides?: IConfigurationOverrides): C { - return this._configuration.getValue(section, overrides); + getValue(key: string, overrides?: IConfigurationOverrides): T { + return this._configuration.getValue2(key, overrides); } - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { - return this._configuration.lookup(key, overrides); - } + updateValue(key: string, value: any): TPromise + updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise + updateValue(key: string, value: any, target: ConfigurationTarget): TPromise + updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise + updateValue(key: string, value: any, arg3?: any, arg4?: any): TPromise { + if (this.configurationEditingService) { + const overrides = isConfigurationOverrides(arg3) ? arg3 : void 0; + const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3); - public keys(overrides?: IConfigurationOverrides): IConfigurationKeys { - return this._configuration.keys(overrides); - } - - public values(): IConfigurationValues { - return this._configuration.values(); - } - - public reloadConfiguration(section?: string): TPromise { - const current = this._configuration; - // Reload and reinitialize to ensure we are hitting the disk - return this.baseConfigurationService.reloadConfiguration() - .then(() => { - if (this.workspace.configuration) { - return this.workspaceConfiguration.load(this.workspace.configuration) - .then(() => this.initializeConfiguration(false)); + if (target) { + if (target === ConfigurationTarget.MEMORY) { + return TPromise.as(null); + } else { + return this.writeConfigurationValue(key, value, target, overrides); } - return this.initializeConfiguration(false); - }) - .then(() => { - // Check and trigger - if (!this._configuration.equals(current)) { - this.triggerConfigurationChange(); - } - return this.getConfiguration(section); - }); + } + } + return TPromise.as(null); } - public getUnsupportedWorkspaceKeys(): string[] { + reloadConfiguration(folder?: IWorkspaceFolder, key?: string): TPromise { + if (folder) { + return this.reloadWorkspaceFolderConfiguration(folder, key); + } + return this.loadConfiguration(); + } + + inspect(key: string, overrides?: IConfigurationOverrides): { + default: T, + user: T, + workspace: T, + workspaceFolder: T, + value: T + } { + return this._configuration.lookup(key); + } + + keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + } { + return this._configuration.keys(); + } + + getUnsupportedWorkspaceKeys(): string[] { return this.getWorkbenchState() === WorkbenchState.FOLDER ? this._configuration.getFolderConfigurationModel(this.workspace.folders[0].uri).workspaceSettingsConfig.unsupportedKeys : []; } - public handleWorkspaceFileEvents(event: FileChangesEvent): void { - TPromise.join(this.workspace.folders.map(folder => this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event))) // handle file event for each folder - .then(folderConfigurations => - folderConfigurations.map((configuration, index) => ({ configuration, folder: this.workspace.folders[index] })) - .filter(folderConfiguration => !!folderConfiguration.configuration) // Filter folders which are not impacted by events - .map(folderConfiguration => this.updateFolderConfiguration(folderConfiguration.folder, folderConfiguration.configuration, true)) // Update the configuration of impacted folders - .reduce((result, value) => result || value, false)) // Check if the effective configuration of folder is changed - .then(changed => changed ? this.triggerConfigurationChange() : void 0); // Trigger event if changed + reloadUserConfiguration(key?: string): TPromise { + return this.baseConfigurationService.reloadConfiguration(); } - public initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise { + reloadWorkspaceConfiguration(key?: string): TPromise { + const workbenchState = this.getWorkbenchState(); + if (workbenchState === WorkbenchState.FOLDER) { + return this.onWorkspaceFolderConfigurationChanged(this.workspace.folders[0], key); + } + if (workbenchState === WorkbenchState.WORKSPACE) { + return this.onWorkspaceConfigurationChanged(); + } + return TPromise.as(null); + } + + reloadWorkspaceFolderConfiguration(folder: IWorkspaceFolder, key?: string): TPromise { + return this.onWorkspaceFolderConfigurationChanged(folder, key); + } + + initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise { return this.createWorkspace(arg) .then(workspace => this.setWorkspace(workspace)) - .then(() => this.initializeConfiguration(true)); + .then(() => this.initializeConfiguration()); + } + + aquireDelayedServices(instantiationService: IInstantiationService): void { + this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService); + } + + handleWorkspaceFileEvents(event: FileChangesEvent): void { + switch (this.getWorkbenchState()) { + case WorkbenchState.FOLDER: + this.onSingleFolderFileChanges(event); + return; + case WorkbenchState.WORKSPACE: + this.onWorkspaceFileChanges(event); + return; + } } private createWorkspace(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise { @@ -470,137 +312,232 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return result; } - private initializeConfiguration(trigger: boolean = true): TPromise { + private initializeConfiguration(): TPromise { this.registerConfigurationSchemas(); - this.resetCaches(); - return this.updateConfiguration() - .then(() => { - if (trigger) { - this.triggerConfigurationChange(); - } + return this.loadConfiguration(); + } + + private loadConfiguration(): TPromise { + // reset caches + this.cachedFolderConfigs = new StrictResourceMap>(); + + const folders = this.workspace.folders; + return this.loadFolderConfigurations(folders) + .then((folderConfigurations) => { + + let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations); + const folderConfigurationModels = new StrictResourceMap>(); + folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); + + this._configuration = new Configuration(this.baseConfigurationService.configuration.defaults, this.baseConfigurationService.configuration.user, workspaceConfiguration, folderConfigurationModels, this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: @Sandy Avoid passing null + // TODO: compare with old values?? + + const keys = this._configuration.keys(); + this.triggerConfigurationChange([...keys.default, ...keys.user, ...keys.workspace, ...keys.workspaceFolder], ConfigurationTarget.WORKSPACE); }); } - private resetCaches(): void { - this.cachedFolderConfigs = new StrictResourceMap>(); - this._configuration = new Configuration(this.baseConfigurationService.configuration(), new ConfigurationModel(), new StrictResourceMap>(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: @Sandy Avoid passing null - this.initCachesForFolders(this.workspace.folders); - } - - private initCachesForFolders(folders: IWorkspaceFolder[]): void { - for (const folder of folders) { - this.cachedFolderConfigs.set(folder.uri, this._register(new FolderConfiguration(folder.uri, this.workspaceSettingsRootFolder, this.getWorkbenchState() === WorkbenchState.WORKSPACE ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW))); - this.updateFolderConfiguration(folder, new FolderConfigurationModel(new FolderSettingsModel(null), [], ConfigurationScope.RESOURCE), false); + private getWorkspaceConfigurationModel(folderConfigurations: FolderConfigurationModel[]): ConfigurationModel { + switch (this.getWorkbenchState()) { + case WorkbenchState.FOLDER: + return folderConfigurations[0]; + case WorkbenchState.WORKSPACE: + return this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration; + default: + return new ConfigurationModel(); } } - private updateConfiguration(folders: IWorkspaceFolder[] = this.workspace.folders): TPromise { - return TPromise.join([...folders.map(folder => this.cachedFolderConfigs.get(folder.uri).loadConfiguration() - .then(configuration => this.updateFolderConfiguration(folder, configuration, true)))]) - .then(changed => changed.reduce((result, value) => result || value, false)) - .then(changed => this.updateWorkspaceConfiguration(true) || changed); - } - private registerConfigurationSchemas(): void { if (this.workspace) { - - contributionRegistry.registerSchema(defaultSettingsSchemaId, settingsSchema); - contributionRegistry.registerSchema(userSettingsSchemaId, settingsSchema); + const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); + jsonRegistry.registerSchema(defaultSettingsSchemaId, settingsSchema); + jsonRegistry.registerSchema(userSettingsSchemaId, settingsSchema); if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) { - contributionRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); - contributionRegistry.registerSchema(folderSettingsSchemaId, resourceSettingsSchema); + jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); + jsonRegistry.registerSchema(folderSettingsSchemaId, resourceSettingsSchema); } else { - contributionRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); - contributionRegistry.registerSchema(folderSettingsSchemaId, settingsSchema); + jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); + jsonRegistry.registerSchema(folderSettingsSchemaId, settingsSchema); } } } - private onBaseConfigurationChanged({ source, sourceConfig }: IConfigurationServiceEvent): void { - if (this.workspace) { - if (source === ConfigurationSource.Default) { + private onBaseConfigurationChanged(e: IConfigurationChangeEvent): void { + if (this.workspace && this._configuration) { + if (e.source === ConfigurationTarget.DEFAULT) { this.workspace.folders.forEach(folder => this._configuration.getFolderConfigurationModel(folder.uri).update()); - } - if (this._configuration.updateBaseConfiguration(this.baseConfigurationService.configuration())) { - this._onDidUpdateConfiguration.fire({ source, sourceConfig }); + this._configuration.updateDefaultConfiguration(this.baseConfigurationService.configuration.defaults); + this._onDidUpdateConfiguration.fire(e); + } else { + let keys = this._configuration.updateUserConfiguration(this.baseConfigurationService.configuration.user); + this.triggerConfigurationChange(keys, e.source); } } } - private onWorkspaceConfigurationChanged(): void { - if (this.workspace && this.workspace.configuration) { + private onWorkspaceConfigurationChanged(): TPromise { + if (this.workspace && this.workspace.configuration && this._configuration) { + const changedWorkspaceKeys = this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration); let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.workspaceConfigurationModel.folders, URI.file(paths.dirname(this.workspace.configuration.fsPath))); const changes = this.compareFolders(this.workspace.folders, configuredFolders); - if (changes.added.length || changes.removed.length || changes.changed.length) { // TODO@Sandeep be smarter here about detecting changes + if (changes.added.length || changes.removed.length || changes.changed.length) { this.workspace.folders = configuredFolders; - this.onFoldersChanged() - .then(configurationChanged => { - if (configurationChanged) { - this.triggerConfigurationChange(); - } + return this.onFoldersChanged() + .then(changedFolderKeys => { + this.triggerConfigurationChange([...changedFolderKeys, ...changedWorkspaceKeys], ConfigurationTarget.WORKSPACE_FOLDER); this._onDidChangeWorkspaceFolders.fire(changes); }); } else { - const configurationChanged = this.updateWorkspaceConfiguration(true); - if (configurationChanged) { - this.triggerConfigurationChange(); - } + this.triggerConfigurationChange(changedWorkspaceKeys, ConfigurationTarget.WORKSPACE); } } + return TPromise.as(null); } - private onFoldersChanged(): TPromise { - let configurationChangedOnRemoval = false; + private onWorkspaceFileChanges(event: FileChangesEvent): TPromise { + return TPromise.join(this.workspace.folders.map(folder => + // handle file event for each folder + this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event) + // Update folder configuration if handled + .then(folderConfiguration => folderConfiguration ? this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration) : [])) + ).then(changedKeys => this.triggerConfigurationChange(flatten(changedKeys), ConfigurationTarget.WORKSPACE_FOLDER)); + } + + private onSingleFolderFileChanges(event: FileChangesEvent): TPromise { + const folder = this.workspace.folders[0]; + return this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event) + .then(folderConfiguration => { + if (folderConfiguration) { + // File change handled + this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration); + const workspaceChangedKeys = this._configuration.updateWorkspaceConfiguration(folderConfiguration); + this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE); + } + }); + } + + private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): TPromise { + this.disposeFolderConfiguration(folder); + return this.loadFolderConfigurations([folder]) + .then(([folderConfiguration]) => { + const folderChangedKeys = this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration); + if (this.getWorkbenchState() === WorkbenchState.FOLDER) { + const workspaceChangedKeys = this._configuration.updateWorkspaceConfiguration(folderConfiguration); + this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE); + } else { + this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER); + } + }); + } + + private onFoldersChanged(): TPromise { + let changedKeys = []; // Remove the configurations of deleted folders for (const key of this.cachedFolderConfigs.keys()) { if (!this.workspace.folders.filter(folder => folder.uri.toString() === key.toString())[0]) { this.cachedFolderConfigs.delete(key); - if (this._configuration.deleteFolderConfiguration(key)) { - configurationChangedOnRemoval = true; - } + changedKeys.push(...this._configuration.deleteFolderConfiguration(key)); } } - // Initialize the newly added folders const toInitialize = this.workspace.folders.filter(folder => !this.cachedFolderConfigs.has(folder.uri)); if (toInitialize.length) { - this.initCachesForFolders(toInitialize); - return this.updateConfiguration(toInitialize) - .then(changed => configurationChangedOnRemoval || changed); - } else if (configurationChangedOnRemoval) { - this.updateWorkspaceConfiguration(false); - return TPromise.as(true); + return this.loadFolderConfigurations(toInitialize) + .then(folderConfigurations => { + folderConfigurations.forEach((folderConfiguration, index) => { + changedKeys.push(...this._configuration.updateFolderConfiguration(toInitialize[index].uri, folderConfiguration)); + }); + return changedKeys; + }); } - return TPromise.as(false); + return TPromise.as(changedKeys); } - private updateFolderConfiguration(folder: IWorkspaceFolder, folderConfiguration: FolderConfigurationModel, compare: boolean): boolean { - let configurationChanged = this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration, compare); - if (this.getWorkbenchState() === WorkbenchState.FOLDER) { - // Workspace configuration changed - configurationChanged = this.updateWorkspaceConfiguration(compare) || configurationChanged; - } - return configurationChanged; + private loadFolderConfigurations(folders: IWorkspaceFolder[]): TPromise[]> { + return TPromise.join([...folders.map(folder => { + const folderConfiguration = new FolderConfiguration(folder.uri, this.workspaceSettingsRootFolder, this.getWorkbenchState() === WorkbenchState.WORKSPACE ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW); + this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration)); + return folderConfiguration.loadConfiguration(); + })]); } - private updateWorkspaceConfiguration(compare: boolean): boolean { - const workbennchState = this.getWorkbenchState(); - if (workbennchState === WorkbenchState.EMPTY) { - return false; - } - - const workspaceConfiguration = workbennchState === WorkbenchState.WORKSPACE ? this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration : this._configuration.getFolderConfigurationModel(this.workspace.folders[0].uri); - return this._configuration.updateWorkspaceConfiguration(workspaceConfiguration, compare); + private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides): TPromise { + return this.configurationEditingService.writeConfiguration(this.toEditableConfigurationTarget(target), { key, value }, { scopes: overrides }) + .then(() => { + switch (target) { + case ConfigurationTarget.USER: + return this.reloadUserConfiguration(); + case ConfigurationTarget.WORKSPACE: + return this.reloadWorkspaceConfiguration(); + case ConfigurationTarget.WORKSPACE_FOLDER: + const workspaceFolder = overrides && overrides.resource ? this.workspace.getFolder(overrides.resource) : null; + if (workspaceFolder) { + return this.reloadWorkspaceFolderConfiguration(this.workspace.getFolder(overrides.resource), key); + } + } + return null; + }); } - private triggerConfigurationChange(): void { - if (this.getWorkbenchState() === WorkbenchState.EMPTY) { - this._onDidUpdateConfiguration.fire({ source: ConfigurationSource.User, sourceConfig: this._configuration.user.contents }); - } else { - this._onDidUpdateConfiguration.fire({ source: ConfigurationSource.Workspace, sourceConfig: this.workspace.folders.length ? this._configuration.getFolderConfigurationModel(this.workspace.folders[0].uri).contents : void 0 }); // TODO@Sandeep debt? + private deriveConfigurationTarget(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): ConfigurationTarget { + if (target) { + return target; } + + if (value === void 0) { + // Ignore. But expected is to remove the value from all targets + return void 0; + } + + const inspect = this.inspect(key, overrides); + if (equals(value, inspect.value)) { + // No change. So ignore. + return void 0; + } + + if (inspect.workspaceFolder !== void 0) { + return ConfigurationTarget.WORKSPACE_FOLDER; + } + + if (inspect.workspace !== void 0) { + return ConfigurationTarget.WORKSPACE; + } + + return ConfigurationTarget.USER; + } + + private toEditableConfigurationTarget(target: ConfigurationTarget): EditableConfigurationTarget { + switch (target) { + case ConfigurationTarget.USER: + return EditableConfigurationTarget.USER; + case ConfigurationTarget.WORKSPACE: + return EditableConfigurationTarget.WORKSPACE; + case ConfigurationTarget.WORKSPACE_FOLDER: + return EditableConfigurationTarget.FOLDER; + default: + return EditableConfigurationTarget.WORKSPACE; + } + } + + private triggerConfigurationChange(keys: string[], target: ConfigurationTarget): void { + if (keys.length) { + this._onDidUpdateConfiguration.fire(toConfigurationUpdateEvent(distinct(keys), target, this.getTargetConfiguration(target))); + } + } + + private getTargetConfiguration(target: ConfigurationTarget): any { + switch (target) { + case ConfigurationTarget.DEFAULT: + return this._configuration.defaults.contents; + case ConfigurationTarget.USER: + return this._configuration.user.contents; + case ConfigurationTarget.WORKSPACE: + return this._configuration.workspace.contents; + } + return {}; } private pathEquals(path1: string, path2: string): boolean { @@ -611,6 +548,13 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return path1 === path2; } + + private disposeFolderConfiguration(folder: IWorkspaceFolder): void { + const folderConfiguration = this.cachedFolderConfigs.get(folder.uri); + if (folderConfiguration) { + folderConfiguration.dispose(); + } + } } class WorkspaceConfiguration extends Disposable { @@ -622,7 +566,6 @@ class WorkspaceConfiguration extends Disposable { private _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; - load(workspaceConfigPath: URI): TPromise { if (this._workspaceConfigPath && this._workspaceConfigPath.fsPath === workspaceConfigPath.fsPath) { return this._reload(); @@ -852,80 +795,6 @@ function resolveStat(resource: URI): TPromise { }); } -export class Configuration extends BaseConfiguration { - - constructor(private _baseConfiguration: BaseConfiguration, workspaceConfiguration: ConfigurationModel, protected folders: StrictResourceMap>, workspace: Workspace) { - super(_baseConfiguration.defaults, _baseConfiguration.user, workspaceConfiguration, folders, workspace); - } - - updateBaseConfiguration(baseConfiguration: BaseConfiguration): boolean { - const current = new Configuration(this._baseConfiguration, this._workspaceConfiguration, this.folders, this._workspace); - - this._baseConfiguration = baseConfiguration; - this._defaults = this._baseConfiguration.defaults; - this._user = this._baseConfiguration.user; - this.merge(); - - return !this.equals(current); - } - - updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel, compare: boolean = true): boolean { - const current = new Configuration(this._baseConfiguration, this._workspaceConfiguration, this.folders, this._workspace); - - this._workspaceConfiguration = workspaceConfiguration; - this.merge(); - - return compare && !this.equals(current); - } - - updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel, compare: boolean): boolean { - const current = this.getValue(null, { resource }); - - this.folders.set(resource, configuration); - this.mergeFolder(resource); - - return compare && !objects.equals(current, this.getValue(null, { resource })); - } - - deleteFolderConfiguration(folder: URI): boolean { - if (this._workspace && this._workspace.folders.length > 0 && this._workspace.folders[0].uri.toString() === folder.toString()) { - // Do not remove workspace configuration - return false; - } - - const changed = this.folders.get(folder).keys.length > 0; - this.folders.delete(folder); - this._foldersConsolidatedConfigurations.delete(folder); - return changed; - } - - getFolderConfigurationModel(folder: URI): FolderConfigurationModel { - return >this.folders.get(folder); - } - - equals(other: any): boolean { - if (!other || !(other instanceof Configuration)) { - return false; - } - - if (!objects.equals(this.getValue(), other.getValue())) { - return false; - } - - if (this._foldersConsolidatedConfigurations.size !== other._foldersConsolidatedConfigurations.size) { - return false; - } - - for (const resource of this._foldersConsolidatedConfigurations.keys()) { - if (!objects.equals(this.getValue(null, { resource }), other.getValue(null, { resource }))) { - return false; - } - } - - return true; - } -} - interface IExportedConfigurationNode { name: string; description: string; @@ -1019,4 +888,4 @@ function versionStringToNumber(versionStr: string): number { } return parseInt(match[1], 10) * 10000 + parseInt(match[2], 10) * 100 + parseInt(match[3], 10); -} +} \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts index 268e832f7db..b486834f14e 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts @@ -21,7 +21,7 @@ import extfs = require('vs/base/node/extfs'); import { TestTextFileService, TestEditorGroupService, TestLifecycleService, TestBackupFileService, TestTextResourceConfigurationService } from 'vs/workbench/test/workbenchTestServices'; import uuid = require('vs/base/common/uuid'); import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configuration'; +import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { FileService } from 'vs/workbench/services/files/node/fileService'; import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; import { ConfigurationTarget, ConfigurationEditingError, ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditing'; @@ -228,7 +228,7 @@ suite('ConfigurationEditingService', () => { const contents = fs.readFileSync(globalSettingsFile).toString('utf8'); const parsed = json.parse(contents); assert.equal(parsed['configurationEditing.service.testSetting'], 'value'); - assert.equal(instantiationService.get(IConfigurationService).lookup('configurationEditing.service.testSetting').value, 'value'); + assert.equal(instantiationService.get(IConfigurationService).getValue('configurationEditing.service.testSetting'), 'value'); }); }); @@ -242,8 +242,8 @@ suite('ConfigurationEditingService', () => { assert.equal(parsed['my.super.setting'], 'my.super.value'); const configurationService = instantiationService.get(IConfigurationService); - assert.equal(configurationService.lookup('configurationEditing.service.testSetting').value, 'value'); - assert.equal(configurationService.lookup('my.super.setting').value, 'my.super.value'); + assert.equal(configurationService.getValue('configurationEditing.service.testSetting'), 'value'); + assert.equal(configurationService.getValue('my.super.setting'), 'my.super.value'); }); }); @@ -255,7 +255,7 @@ suite('ConfigurationEditingService', () => { const parsed = json.parse(contents); assert.equal(parsed['service.testSetting'], 'value'); const configurationService = instantiationService.get(IConfigurationService); - assert.equal(configurationService.lookup('tasks.service.testSetting').value, 'value'); + assert.equal(configurationService.getValue('tasks.service.testSetting'), 'value'); }); }); @@ -270,8 +270,8 @@ suite('ConfigurationEditingService', () => { assert.equal(parsed['my.super.setting'], 'my.super.value'); const configurationService = instantiationService.get(IConfigurationService); - assert.equal(configurationService.lookup('launch.service.testSetting').value, 'value'); - assert.equal(configurationService.lookup('launch.my.super.setting').value, 'my.super.value'); + assert.equal(configurationService.getValue('launch.service.testSetting'), 'value'); + assert.equal(configurationService.getValue('launch.my.super.setting'), 'my.super.value'); }); }); diff --git a/src/vs/workbench/services/configuration/test/node/configuration.test.ts b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts similarity index 90% rename from src/vs/workbench/services/configuration/test/node/configuration.test.ts rename to src/vs/workbench/services/configuration/test/node/configurationService.test.ts index 8073bb82a98..c6fb36f60c9 100644 --- a/src/vs/workbench/services/configuration/test/node/configuration.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts @@ -19,7 +19,7 @@ import { parseArgs } from 'vs/platform/environment/node/argv'; import extfs = require('vs/base/node/extfs'); import uuid = require('vs/base/common/uuid'); import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configuration'; +import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -167,7 +167,7 @@ suite('WorkspaceConfigurationService - Node', () => { return createService(workspaceDir, globalSettingsFile).then(service => { fs.writeFileSync(globalSettingsFile, '{ "testworkbench.editor.tabs": true }'); - service.reloadConfiguration().then(() => { + service.reloadConfiguration(service.getWorkspace().folders[0]).then(() => { const config = service.getConfiguration<{ testworkbench: { editor: { tabs: boolean } } }>(); assert.equal(config.testworkbench.editor.tabs, true); @@ -197,7 +197,7 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(globalSettingsFile, '{ "testworkbench.editor.tabs": false }'); // this has to trigger the event since the config changes - service.reloadConfiguration().done(); + service.reloadUserConfiguration().done(); }); }); @@ -229,7 +229,7 @@ suite('WorkspaceConfigurationService - Node', () => { return createService(workspaceDir, globalSettingsFile).then(service => { fs.writeFileSync(globalSettingsFile, '{ "workspace.service.testSetting": "isChanged" }'); - service.reloadConfiguration().then(() => { + service.reloadUserConfiguration().then(() => { const config = service.getConfiguration(); assert.equal(config.workspace.service.testSetting, 'isChanged'); @@ -246,7 +246,7 @@ suite('WorkspaceConfigurationService - Node', () => { return createService(workspaceDir, globalSettingsFile).then(service => { fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "testworkbench.editor.icons": true }'); - service.reloadConfiguration().then(() => { + service.reloadWorkspaceConfiguration().then(() => { const config = service.getConfiguration<{ testworkbench: { editor: { icons: boolean } } }>(); assert.equal(config.testworkbench.editor.icons, true); @@ -264,7 +264,7 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(globalSettingsFile, '{ "testworkbench.editor.icons": false, "testworkbench.other.setting": true }'); fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "testworkbench.editor.icons": true }'); - service.reloadConfiguration().then(() => { + service.reloadWorkspaceConfiguration().then(() => { const config = service.getConfiguration<{ testworkbench: { editor: { icons: boolean }, other: { setting: string } } }>(); assert.equal(config.testworkbench.editor.icons, true); assert.equal(config.testworkbench.other.setting, true); @@ -310,7 +310,7 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(settingsFile, '{ "testworkbench.editor.icons": false }'); - service.reloadConfiguration().done(() => { + service.reloadWorkspaceConfiguration().done(() => { assert.ok(target.calledOnce); service.dispose(); @@ -326,11 +326,11 @@ suite('WorkspaceConfigurationService - Node', () => { const settingsFile = path.join(workspaceDir, '.vscode', 'settings.json'); fs.writeFileSync(settingsFile, '{ "testworkbench.editor.icons": true }'); - service.reloadConfiguration().done(() => { + service.reloadWorkspaceConfiguration().done(() => { const target = sinon.stub(); service.onDidUpdateConfiguration(event => target()); - service.reloadConfiguration().done(() => { + service.reloadWorkspaceConfiguration().done(() => { assert.ok(!target.called); service.dispose(); @@ -346,7 +346,7 @@ suite('WorkspaceConfigurationService - Node', () => { return createService(workspaceDir, globalSettingsFile).then(service => { const target = sinon.stub(); service.onDidUpdateConfiguration(event => target()); - service.reloadConfiguration().done(() => { + service.reloadUserConfiguration().done(() => { assert.ok(!target.called); service.dispose(); cleanUp(done); @@ -371,13 +371,13 @@ suite('WorkspaceConfigurationService - Node', () => { createWorkspace((workspaceDir, globalSettingsFile, cleanUp) => { return createService(workspaceDir, globalSettingsFile).then(service => { - let res = service.lookup('something.missing'); + let res = service.inspect('something.missing'); assert.ok(!res.default); assert.ok(!res.user); assert.ok(!res.workspace); assert.ok(!res.value); - res = service.lookup('workspaceLookup.service.testSetting'); + res = service.inspect('workspaceLookup.service.testSetting'); assert.equal(res.default, 'isSet'); assert.equal(res.value, 'isSet'); assert.ok(!res.user); @@ -385,8 +385,8 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(globalSettingsFile, '{ "workspaceLookup.service.testSetting": true }'); - return service.reloadConfiguration().then(() => { - res = service.lookup('workspaceLookup.service.testSetting'); + return service.reloadUserConfiguration().then(() => { + res = service.inspect('workspaceLookup.service.testSetting'); assert.equal(res.default, 'isSet'); assert.equal(res.user, true); assert.equal(res.value, true); @@ -395,8 +395,8 @@ suite('WorkspaceConfigurationService - Node', () => { const settingsFile = path.join(workspaceDir, '.vscode', 'settings.json'); fs.writeFileSync(settingsFile, '{ "workspaceLookup.service.testSetting": 55 }'); - return service.reloadConfiguration().then(() => { - res = service.lookup('workspaceLookup.service.testSetting'); + return service.reloadWorkspaceConfiguration().then(() => { + res = service.inspect('workspaceLookup.service.testSetting'); assert.equal(res.default, 'isSet'); assert.equal(res.user, true); assert.equal(res.workspace, 55); @@ -444,7 +444,7 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(globalSettingsFile, '{ "workspaceLookup.service.testSetting": true }'); - return service.reloadConfiguration().then(() => { + return service.reloadUserConfiguration().then(() => { keys = service.keys(); assert.ok(contains(keys.default, 'workspaceLookup.service.testSetting')); @@ -454,7 +454,7 @@ suite('WorkspaceConfigurationService - Node', () => { const settingsFile = path.join(workspaceDir, '.vscode', 'settings.json'); fs.writeFileSync(settingsFile, '{ "workspaceLookup.service.testSetting": 55 }'); - return service.reloadConfiguration().then(() => { + return service.reloadWorkspaceConfiguration().then(() => { keys = service.keys(); assert.ok(contains(keys.default, 'workspaceLookup.service.testSetting')); @@ -464,7 +464,7 @@ suite('WorkspaceConfigurationService - Node', () => { const settingsFile = path.join(workspaceDir, '.vscode', 'tasks.json'); fs.writeFileSync(settingsFile, '{ "workspaceLookup.service.taskTestSetting": 55 }'); - return service.reloadConfiguration().then(() => { + return service.reloadWorkspaceConfiguration().then(() => { keys = service.keys(); assert.ok(!contains(keys.default, 'tasks.workspaceLookup.service.taskTestSetting')); @@ -496,31 +496,31 @@ suite('WorkspaceConfigurationService - Node', () => { createWorkspace((workspaceDir, globalSettingsFile, cleanUp) => { return createService(workspaceDir, globalSettingsFile).then(service => { - let values = service.values(); - let value = values['workspaceLookup.service.testSetting']; + let values = service.inspect('workspaceLookup.service.testSetting'); + let value = values.value; assert.ok(value); - assert.equal(value.default, 'isSet'); + assert.equal(values.default, 'isSet'); fs.writeFileSync(globalSettingsFile, '{ "workspaceLookup.service.testSetting": true }'); - return service.reloadConfiguration().then(() => { - values = service.values(); - value = values['workspaceLookup.service.testSetting']; + return service.reloadUserConfiguration().then(() => { + values = service.inspect('workspaceLookup.service.testSetting'); + value = values.value; assert.ok(value); - assert.equal(value.user, true); + assert.equal(values.user, true); const settingsFile = path.join(workspaceDir, '.vscode', 'settings.json'); fs.writeFileSync(settingsFile, '{ "workspaceLookup.service.testSetting": 55 }'); - return service.reloadConfiguration().then(() => { - values = service.values(); - value = values['workspaceLookup.service.testSetting']; + return service.reloadWorkspaceConfiguration().then(() => { + values = service.inspect('workspaceLookup.service.testSetting'); + value = values.value; assert.ok(value); - assert.equal(value.user, true); - assert.equal(value.workspace, 55); + assert.equal(values.user, true); + assert.equal(values.workspace, 55); done(); }); diff --git a/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts index 7841abf9606..2360f632988 100644 --- a/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts @@ -7,7 +7,7 @@ import assert = require('assert'); import uri from 'vs/base/common/uri'; import platform = require('vs/base/common/platform'); import { TPromise } from 'vs/base/common/winjs.base'; -import { IConfigurationService, getConfigurationValue, IConfigurationOverrides, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, getConfigurationValue, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/node/configurationResolverService'; @@ -348,13 +348,14 @@ class MockConfigurationService implements IConfigurationService { public _serviceBrand: any; public serviceId = IConfigurationService; public constructor(private configuration: any = {}) { } - public reloadConfiguration(section?: string): TPromise { return TPromise.as(this.getConfiguration()); } - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspace: void 0, folder: void 0 }; } - public keys() { return { default: [], user: [], workspace: [], folder: [] }; } - public values() { return {}; } + public inspect(key: string, overrides?: IConfigurationOverrides): any { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspaceFolder: void 0, folder: void 0 }; } + public keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; } public getConfiguration(): any { return this.configuration; } + public getValue(key: string): any { return getConfigurationValue(this.getConfiguration(), key); } + public updateValue(): TPromise { return null; } public getConfigurationData(): any { return null; } public onDidUpdateConfiguration() { return { dispose() { } }; } + public reloadConfiguration() { return null; } } class MockCommandService implements ICommandService { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index d4b17a7fbc3..553e69ad627 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -360,7 +360,7 @@ export class ExtensionHostProcessWorker { }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : this._contextService.getWorkspace(), extensions: extensionDescriptions, - configuration: this._configurationService.getConfigurationData(), + configuration: this._configurationService.getConfiguration(), telemetryInfo }; return r; diff --git a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts index 7fb9b9a90c8..7d1e3e4382f 100644 --- a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts @@ -344,8 +344,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.updateColorCustomizations(false); - let colorThemeSetting = this.configurationService.lookup(COLOR_THEME_SETTING).value; - let iconThemeSetting = this.configurationService.lookup(ICON_THEME_SETTING).value || ''; + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING) || ''; return Promise.join([ this.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { @@ -359,7 +359,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private installConfigurationListener() { this.configurationService.onDidUpdateConfiguration(e => { - let colorThemeSetting = this.configurationService.lookup(COLOR_THEME_SETTING).value; + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { this.findThemeDataBySettingsId(colorThemeSetting, null).then(theme => { if (theme) { @@ -368,7 +368,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); } - let iconThemeSetting = this.configurationService.lookup(ICON_THEME_SETTING).value || ''; + let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING) || ''; if (iconThemeSetting !== this.currentIconTheme.settingsId) { this.findIconThemeBySettingsId(iconThemeSetting).then(theme => { this.setFileIconTheme(theme && theme.id, null); @@ -530,14 +530,14 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private updateColorCustomizations(notify = true): void { - let newColorCustomizations = this.configurationService.lookup(CUSTOM_WORKBENCH_COLORS_SETTING).value || {}; + let newColorCustomizations = this.configurationService.getValue(CUSTOM_WORKBENCH_COLORS_SETTING) || {}; let newColorIds = Object.keys(newColorCustomizations); if (newColorIds.length === 0) { - newColorCustomizations = this.configurationService.lookup(DEPRECATED_CUSTOM_COLORS_SETTING).value || {}; + newColorCustomizations = this.configurationService.getValue(DEPRECATED_CUSTOM_COLORS_SETTING) || {}; newColorIds = Object.keys(newColorCustomizations); } - let newTokenColorCustomizations = this.configurationService.lookup(CUSTOM_EDITOR_COLORS_SETTING).value || {}; + let newTokenColorCustomizations = this.configurationService.getValue(CUSTOM_EDITOR_COLORS_SETTING) || {}; if (this.hasCustomizationChanged(newColorCustomizations, newColorIds, newTokenColorCustomizations)) { this.colorCustomizations = newColorCustomizations; @@ -962,7 +962,7 @@ class ConfigurationWriter { } public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget): TPromise { - let settings = this.configurationService.lookup(key); + let settings = this.configurationService.inspect(key); if (settingsTarget === ConfigurationTarget.USER) { if (value === settings.user) { return TPromise.as(null); // nothing to do diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index aa99ca9f75a..ea53130e9cb 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -17,7 +17,7 @@ import { dirname } from 'path'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces'; import { isLinux } from 'vs/base/common/platform'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configuration'; +import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { migrateStorageToMultiRootWorkspace } from 'vs/platform/storage/common/migration'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { StorageService } from 'vs/platform/storage/common/storageService'; @@ -187,7 +187,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { const targetWorkspaceConfiguration = {}; for (const key of this.workspaceConfigurationService.keys().workspace) { if (configurationProperties[key] && !configurationProperties[key].isFromExtensions && configurationProperties[key].scope === ConfigurationScope.WINDOW) { - targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.lookup(key).workspace; + targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.inspect(key).workspace; } } diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index b162fb6a023..4844c81d0aa 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -9,6 +9,9 @@ import 'vs/base/common/strings'; import 'vs/base/common/errors'; +// Configuration +import 'vs/workbench/services/configuration/common/configurationExtensionPoint'; + // Editor import 'vs/editor/editor.all'; From 74e07a6f1cd24cd9a342056096b2d3d7d2a648e1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Oct 2017 23:22:55 +0200 Subject: [PATCH 04/28] Populate register configuration event correctly --- src/vs/platform/configuration/common/configurationRegistry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 83d78eda6da..11f9f2cd686 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -114,9 +114,9 @@ class ConfigurationRegistry implements IConfigurationRegistry { } public registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void { - const properties = []; + const properties: string[] = []; configurations.forEach(configuration => { - properties.push(this.validateAndRegisterProperties(configuration, validate)); // fills in defaults + properties.push(...this.validateAndRegisterProperties(configuration, validate)); // fills in defaults this.configurationContributors.push(configuration); this.registerJSONConfiguration(configuration); this.updateSchemaForOverrideSettingsConfiguration(configuration); From e1c29b6188971db4d294b05a71984c645f4d759c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Oct 2017 23:30:34 +0200 Subject: [PATCH 05/28] Populate override identifer keys in the event correctly --- src/vs/platform/configuration/common/configuration.ts | 10 +++++++++- src/vs/platform/configuration/common/model.ts | 10 +--------- .../parts/preferences/browser/preferencesRenderers.ts | 2 +- .../configuration/node/configurationEditingService.ts | 3 +-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 20f1b668201..19bcce38e70 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -78,12 +78,20 @@ export interface IConfigurationService { }; } +export function overrideIdentifierFromKey(key: string): string { + return key.substring(1, key.length - 1); +} + +export function keyFromOverrideIdentifier(overrideIdentifier: string): string { + return `[${overrideIdentifier}]`; +} + export function toConfigurationUpdateEvent(udpated: string[], source: ConfigurationTarget, sourceConfig: any): IConfigurationChangeEvent { const overrideIdentifiers = []; const keys: string[] = []; for (const key of udpated) { if (OVERRIDE_PROPERTY_PATTERN.test(key)) { - overrideIdentifiers.push(key); + overrideIdentifiers.push(overrideIdentifierFromKey(key).trim()); } else { keys.push(key); } diff --git a/src/vs/platform/configuration/common/model.ts b/src/vs/platform/configuration/common/model.ts index 53b122efd0e..2eca51661ff 100644 --- a/src/vs/platform/configuration/common/model.ts +++ b/src/vs/platform/configuration/common/model.ts @@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as json from 'vs/base/common/json'; import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; -import { ConfigurationModel, IOverrides } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel, IOverrides, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration'; export function getDefaultValues(): any { const valueTreeRoot: any = Object.create(null); @@ -191,12 +191,4 @@ export class CustomConfigurationModel extends ConfigurationModel { this._contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)); this._keys = Object.keys(raw); } -} - -export function overrideIdentifierFromKey(key: string): string { - return key.substring(1, key.length - 1); -} - -export function keyFromOverrideIdentifier(overrideIdentifier: string): string { - return `[${overrideIdentifier}]`; } \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts index e3491f2e20a..98996425277 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts @@ -24,7 +24,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RangeHighlightDecorations } from 'vs/workbench/common/editor/rangeDecorations'; import { IConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { overrideIdentifierFromKey } from 'vs/platform/configuration/common/model'; import { IMarkerService, IMarkerData } from 'vs/platform/markers/common/markers'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; @@ -33,6 +32,7 @@ import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorE import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { MarkdownString } from 'vs/base/common/htmlContent'; +import { overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration'; export interface IPreferencesRenderer extends IDisposable { preferencesModel: IPreferencesEditorModel; diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index 694c6122734..b264dbd9662 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -23,8 +23,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; -import { keyFromOverrideIdentifier } from 'vs/platform/configuration/common/model'; +import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; import { WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { ConfigurationTarget, ConfigurationEditingErrorCode, ConfigurationEditingError, IConfigurationValue, IConfigurationEditingOptions, IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; From 2a8be527e5a773d3bd01ce1c274cdb2cf5138fc4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Oct 2017 23:41:20 +0200 Subject: [PATCH 06/28] :lipstick: --- src/vs/platform/configuration/common/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 19bcce38e70..43da9ac3cfb 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -60,7 +60,7 @@ export interface IConfigurationService { updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise; reloadConfiguration(): TPromise; - reloadConfiguration(folder?: IWorkspaceFolder): TPromise; + reloadConfiguration(folder: IWorkspaceFolder): TPromise; inspect(key: string): { default: T, From 7f89b24319054ae22758d046ef33f0b78c5650cc Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Oct 2017 23:50:44 +0200 Subject: [PATCH 07/28] Fix usage of getConfiguration --- src/vs/editor/common/services/modelServiceImpl.ts | 2 +- .../workbench/api/electron-browser/mainThreadWorkspace.ts | 2 +- src/vs/workbench/parts/files/browser/views/explorerView.ts | 2 +- .../workbench/parts/files/browser/views/explorerViewer.ts | 2 +- src/vs/workbench/parts/search/common/queryBuilder.ts | 6 +++--- .../services/files/node/watcher/nsfw/watcherService.ts | 2 +- src/vs/workbench/services/history/browser/history.ts | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 7eb325a6761..fe046a165d5 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -272,7 +272,7 @@ export class ModelServiceImpl implements IModelService { public getCreationOptions(language: string, resource: URI): editorCommon.ITextModelCreationOptions { let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource]; if (!creationOptions) { - creationOptions = ModelServiceImpl._readModelOptions(this._configurationService.getConfiguration(null, { overrideIdentifier: language, resource })); + creationOptions = ModelServiceImpl._readModelOptions(this._configurationService.getConfiguration({ overrideIdentifier: language, resource })); this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions; } return creationOptions; diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 69d00f909db..69e09705743 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -68,7 +68,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } const useRipgrep = folderQueries.every(folderQuery => { - const folderConfig = this._configurationService.getConfiguration(undefined, { resource: folderQuery.folder }); + const folderConfig = this._configurationService.getConfiguration({ resource: folderQuery.folder }); return folderConfig.search.useRipgrep; }); diff --git a/src/vs/workbench/parts/files/browser/views/explorerView.ts b/src/vs/workbench/parts/files/browser/views/explorerView.ts index 1a84751abf2..9db721ab8b4 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerView.ts @@ -120,7 +120,7 @@ export class ExplorerView extends ViewsViewletPanel { private getFileEventsExcludes(root?: URI): glob.IExpression { const scope = root ? { resource: root } : void 0; - const configuration = this.configurationService.getConfiguration(undefined, scope); + const configuration = this.configurationService.getConfiguration(scope); return (configuration && configuration.files && configuration.files.exclude) || Object.create(null); } diff --git a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts index c088be1988d..329a15b0bf9 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts @@ -687,7 +687,7 @@ export class FileFilter implements IFilter { public updateConfiguration(): boolean { let needsRefresh = false; this.contextService.getWorkspace().folders.forEach(folder => { - const configuration = this.configurationService.getConfiguration(undefined, { resource: folder.uri }); + const configuration = this.configurationService.getConfiguration({ resource: folder.uri }); const excludesConfig = (configuration && configuration.files && configuration.files.exclude) || Object.create(null); needsRefresh = needsRefresh || !objects.equals(this.hiddenExpressionPerRoot.get(folder.uri.toString()), excludesConfig); this.hiddenExpressionPerRoot.set(folder.uri.toString(), objects.clone(excludesConfig)); // do not keep the config, as it gets mutated under our hoods diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index 9b3120dcdc6..cf614eeaeb0 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -56,7 +56,7 @@ export class QueryBuilder { } const useRipgrep = !folderResources || folderResources.every(folder => { - const folderConfig = this.configurationService.getConfiguration(undefined, { resource: folder }); + const folderConfig = this.configurationService.getConfiguration({ resource: folder }); return folderConfig.search.useRipgrep; }); @@ -244,7 +244,7 @@ export class QueryBuilder { private getFolderQueryForSearchPath(searchPath: ISearchPathPattern): IFolderQuery { const folder = searchPath.searchPath; - const folderConfig = this.configurationService.getConfiguration(undefined, { resource: folder }); + const folderConfig = this.configurationService.getConfiguration({ resource: folder }); return { folder, includePattern: searchPath.pattern && patternListToIExpression([searchPath.pattern]), @@ -253,7 +253,7 @@ export class QueryBuilder { } private getFolderQueryForRoot(folder: uri, options?: IQueryOptions): IFolderQuery { - const folderConfig = this.configurationService.getConfiguration(undefined, { resource: folder }); + const folderConfig = this.configurationService.getConfiguration({ resource: folder }); return { folder, excludePattern: this.getExcludesForFolder(folderConfig, options), diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index 9e3744b2638..05a68b0cb7a 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -95,7 +95,7 @@ export class FileWatcher { this.service.setRoots(this.contextService.getWorkspace().folders.map(folder => { // Fetch the root's watcherExclude setting and return it - const configuration = this.configurationService.getConfiguration(undefined, { + const configuration = this.configurationService.getConfiguration({ resource: folder.uri }); let ignored: string[] = []; diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index f9fafed499d..1a0c498f2dc 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -224,7 +224,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic private getExcludes(root?: URI): IExpression { const scope = root ? { resource: root } : void 0; - return getExcludes(this.configurationService.getConfiguration(void 0, scope)); + return getExcludes(this.configurationService.getConfiguration(scope)); } private registerListeners(): void { From 878233682f1f2798ac42a349fc0f194e2fe4678c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Oct 2017 00:36:54 +0200 Subject: [PATCH 08/28] Watermark: Adopt to new configuration update event. Check if key has changed. --- .../watermark/electron-browser/watermark.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts index 21c46e71bdd..effbdb62019 100644 --- a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts +++ b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts @@ -100,6 +100,7 @@ const folderEntries = [ ]; const UNBOUND = nls.localize('watermark.unboundCommand', "unbound"); +const WORKBENCH_TIPS_ENABLED_KEY = 'workbench.tips.enabled'; export class WatermarkContribution implements IWorkbenchContribution { @@ -120,19 +121,21 @@ export class WatermarkContribution implements IWorkbenchContribution { lifecycleService.onShutdown(this.dispose, this); this.partService.joinCreation().then(() => { - this.enabled = this.configurationService.getValue('workbench.tips.enabled'); + this.enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); if (this.enabled) { this.create(); } }); this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => { - const enabled = this.configurationService.getValue('workbench.tips.enabled'); - if (enabled !== this.enabled) { - this.enabled = enabled; - if (this.enabled) { - this.create(); - } else { - this.destroy(); + if (e.hasKeyChanged(WORKBENCH_TIPS_ENABLED_KEY)) { + const enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); + if (enabled !== this.enabled) { + this.enabled = enabled; + if (this.enabled) { + this.create(); + } else { + this.destroy(); + } } } })); From 3151c10d70814f7346c7a236d0ec74f610b876b3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Oct 2017 11:38:58 +0200 Subject: [PATCH 09/28] - Ability to write configuration into memory - CLean up: Separate models and interfaces --- .../standalone/browser/simpleServices.ts | 4 +- .../configuration/common/configuration.ts | 339 ++++----------- .../common/configurationModels.ts | 405 ++++++++++++++++++ src/vs/platform/configuration/common/model.ts | 194 --------- .../node/configurationService.ts | 4 +- .../test/common/configuration.model.test.ts | 2 +- ...del.test.ts => configurationModel.test.ts} | 2 +- .../test/common/testConfigurationService.ts | 3 +- .../api/node/extHostConfiguration.ts | 3 +- .../common/configurationModels.ts | 21 +- .../node/configurationService.ts | 46 +- .../api/extHostConfiguration.test.ts | 2 +- 12 files changed, 560 insertions(+), 465 deletions(-) create mode 100644 src/vs/platform/configuration/common/configurationModels.ts delete mode 100644 src/vs/platform/configuration/common/model.ts rename src/vs/platform/configuration/test/common/{model.test.ts => configurationModel.test.ts} (98%) diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 75aaa4492f9..a9b23d8d74f 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -8,7 +8,7 @@ import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IConfigurationService, IConfigurationChangeEvent, Configuration, ConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IEditor, IEditorInput, IEditorOptions, IEditorService, IResourceInput, Position } from 'vs/platform/editor/common/editor'; import { ICommandService, ICommand, ICommandEvent, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -23,7 +23,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; import Event, { Emitter } from 'vs/base/common/event'; -import { DefaultConfigurationModel } from 'vs/platform/configuration/common/model'; +import { Configuration, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 43da9ac3cfb..74fba9962fd 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -5,14 +5,14 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as arrays from 'vs/base/common/arrays'; -import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; +import * as types from 'vs/base/common/types'; import URI from 'vs/base/common/uri'; -import { StrictResourceMap } from 'vs/base/common/map'; -import { Workspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import Event from 'vs/base/common/event'; -import { OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; export const IConfigurationService = createDecorator('configurationService'); @@ -66,7 +66,8 @@ export interface IConfigurationService { default: T, user: T, workspace: T, - workspaceFolder: T + workspaceFolder: T, + memory?: T, value: T, }; @@ -75,15 +76,42 @@ export interface IConfigurationService { user: string[]; workspace: string[]; workspaceFolder: string[]; + memory?: string[]; }; } -export function overrideIdentifierFromKey(key: string): string { - return key.substring(1, key.length - 1); +export interface IConfiguraionModel { + contents: T; + keys: string[]; + overrides: IOverrides[]; } -export function keyFromOverrideIdentifier(overrideIdentifier: string): string { - return `[${overrideIdentifier}]`; +export interface IOverrides { + contents: T; + identifiers: string[]; +} + +export interface IConfigurationData { + defaults: IConfiguraionModel; + user: IConfiguraionModel; + workspace: IConfiguraionModel; + folders: { [folder: string]: IConfiguraionModel }; +} + +export function compare(from: IConfiguraionModel, to: IConfiguraionModel): { added: string[], removed: string[], updated: string[] } { + const added = to.keys.filter(key => from.keys.indexOf(key) === -1); + const removed = from.keys.filter(key => to.keys.indexOf(key) === -1); + const updated = []; + + for (const key of from.keys) { + const value1 = getConfigurationValue(from.contents, key); + const value2 = getConfigurationValue(to.contents, key); + if (!objects.equals(value1, value2)) { + updated.push(key); + } + } + + return { added, removed, updated }; } export function toConfigurationUpdateEvent(udpated: string[], source: ConfigurationTarget, sourceConfig: any): IConfigurationChangeEvent { @@ -102,6 +130,45 @@ export function toConfigurationUpdateEvent(udpated: string[], source: Configurat return { keys, sections, overrideIdentifiers, source, sourceConfig, hasSectionChanged, hasKeyChanged }; } + +export function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void): any { + const root = Object.create(null); + + for (let key in properties) { + addToValueTree(root, key, properties[key], conflictReporter); + } + + return root; +} + +export function addToValueTree(settingsTreeRoot: any, key: string, value: any, conflictReporter: (message: string) => void): void { + const segments = key.split('.'); + const last = segments.pop(); + + let curr = settingsTreeRoot; + for (let i = 0; i < segments.length; i++) { + let s = segments[i]; + let obj = curr[s]; + switch (typeof obj) { + case 'undefined': + obj = curr[s] = Object.create(null); + break; + case 'object': + break; + default: + conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is ${JSON.stringify(obj)}`); + return; + } + curr = obj; + }; + + if (typeof curr === 'object') { + curr[last] = value; // workaround https://github.com/Microsoft/vscode/issues/13606 + } else { + conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`); + } +} + /** * A helper function to get the configuration value with a specific settings path (e.g. config.some.setting) */ @@ -137,245 +204,27 @@ export function merge(base: any, add: any, overwrite: boolean): void { }); } -export interface IConfiguraionModel { - contents: T; - keys: string[]; - overrides: IOverrides[]; +export function getConfigurationKeys(): string[] { + const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + return Object.keys(properties); } -export interface IOverrides { - contents: T; - identifiers: string[]; +export function getDefaultValues(): any { + const valueTreeRoot: any = Object.create(null); + const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + + for (let key in properties) { + let value = properties[key].default; + addToValueTree(valueTreeRoot, key, value, message => console.error(`Conflict in default settings: ${message}`)); + } + + return valueTreeRoot; } -export class ConfigurationModel implements IConfiguraionModel { - - constructor(protected _contents: T = {}, protected _keys: string[] = [], protected _overrides: IOverrides[] = []) { - } - - public get contents(): T { - return this._contents; - } - - public get overrides(): IOverrides[] { - return this._overrides; - } - - public get keys(): string[] { - return this._keys; - } - - public getContentsFor(section: string): V { - return objects.clone(this.contents[section]); - } - - public override(identifier: string): ConfigurationModel { - const result = new ConfigurationModel(); - const contents = objects.clone(this.contents); - if (this._overrides) { - for (const override of this._overrides) { - if (override.identifiers.indexOf(identifier) !== -1) { - merge(contents, override.contents, true); - } - } - } - result._contents = contents; - return result; - } - - public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { - const mergedModel = new ConfigurationModel(); - this.doMerge(mergedModel, this, overwrite); - this.doMerge(mergedModel, other, overwrite); - return mergedModel; - } - - protected doMerge(source: ConfigurationModel, target: ConfigurationModel, overwrite: boolean = true) { - merge(source.contents, objects.clone(target.contents), overwrite); - const overrides = objects.clone(source._overrides); - for (const override of target._overrides) { - const [sourceOverride] = overrides.filter(o => arrays.equals(o.identifiers, override.identifiers)); - if (sourceOverride) { - merge(sourceOverride.contents, override.contents, overwrite); - } else { - overrides.push(override); - } - } - source._overrides = overrides; - } +export function overrideIdentifierFromKey(key: string): string { + return key.substring(1, key.length - 1); } -export function compare(from: ConfigurationModel, to: ConfigurationModel): { added: string[], removed: string[], updated: string[] } { - const added = to.keys.filter(key => from.keys.indexOf(key) === -1); - const removed = from.keys.filter(key => to.keys.indexOf(key) === -1); - const updated = []; - - for (const key of from.keys) { - const value1 = getConfigurationValue(from.contents, key); - const value2 = getConfigurationValue(to.contents, key); - if (!objects.equals(value1, value2)) { - updated.push(key); - } - } - - return { added, removed, updated }; -} - -export interface IConfigurationData { - defaults: IConfiguraionModel; - user: IConfiguraionModel; - workspace: IConfiguraionModel; - folders: { [folder: string]: IConfiguraionModel }; -} - -export class Configuration { - - private _globalConfiguration: ConfigurationModel; - private _workspaceConsolidatedConfiguration: ConfigurationModel; - protected _foldersConsolidatedConfigurations: StrictResourceMap>; - - constructor(protected _defaults: ConfigurationModel, protected _user: ConfigurationModel, protected _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), protected folders: StrictResourceMap> = new StrictResourceMap>(), protected _workspace?: Workspace) { - this.merge(); - } - - get defaults(): ConfigurationModel { - return this._defaults; - } - - get user(): ConfigurationModel { - return this._user; - } - - get workspace(): ConfigurationModel { - return this._workspaceConfiguration; - } - - protected merge(): void { - this._globalConfiguration = new ConfigurationModel().merge(this._defaults).merge(this._user); - this._workspaceConsolidatedConfiguration = new ConfigurationModel().merge(this._globalConfiguration).merge(this._workspaceConfiguration); - this._foldersConsolidatedConfigurations = new StrictResourceMap>(); - for (const folder of this.folders.keys()) { - this.mergeFolder(folder); - } - } - - protected mergeFolder(folder: URI) { - this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); - } - - getValue(section: string = '', overrides: IConfigurationOverrides = {}): C { - const configModel = this.getConsolidateConfigurationModel(overrides); - return section ? configModel.getContentsFor(section) : configModel.contents; - } - - getValue2(key: string, overrides: IConfigurationOverrides = {}): any { - // make sure to clone the configuration so that the receiver does not tamper with the values - const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); - return objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)); - } - - lookup(key: string, overrides: IConfigurationOverrides = {}): { - default: C, - user: C, - workspace: C, - workspaceFolder: C - value: C, - } { - // make sure to clone the configuration so that the receiver does not tamper with the values - const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); - const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); - return { - default: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._defaults.override(overrides.overrideIdentifier).contents : this._defaults.contents, key)), - user: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._user.override(overrides.overrideIdentifier).contents : this._user.contents, key)), - workspace: objects.clone(this._workspace ? getConfigurationValue(overrides.overrideIdentifier ? this._workspaceConfiguration.override(overrides.overrideIdentifier).contents : this._workspaceConfiguration.contents, key) : void 0), //Check on workspace exists or not because _workspaceConfiguration is never null - workspaceFolder: objects.clone(folderConfigurationModel ? getConfigurationValue(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0), - value: objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)) - }; - } - - keys(): { - default: string[]; - user: string[]; - workspace: string[]; - workspaceFolder: string[]; - } { - const folderConfigurationModel = this.getFolderConfigurationModelForResource(); - return { - default: this._defaults.keys, - user: this._user.keys, - workspace: this._workspaceConfiguration.keys, - workspaceFolder: folderConfigurationModel ? folderConfigurationModel.keys : [] - }; - } - - private getConsolidateConfigurationModel(overrides: IConfigurationOverrides): ConfigurationModel { - let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides); - return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; - } - - private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides): ConfigurationModel { - if (!this._workspace) { - return this._globalConfiguration; - } - - if (!resource) { - return this._workspaceConsolidatedConfiguration; - } - - const root = this._workspace.getFolder(resource); - if (!root) { - return this._workspaceConsolidatedConfiguration; - } - - return this._foldersConsolidatedConfigurations.get(root.uri) || this._workspaceConsolidatedConfiguration; - } - - private getFolderConfigurationModelForResource(resource?: URI): ConfigurationModel { - if (!this._workspace || !resource) { - return null; - } - - const root = this._workspace.getFolder(resource); - return root ? this.folders.get(root.uri) : null; - } - - public toData(): IConfigurationData { - return { - defaults: { - contents: this._defaults.contents, - overrides: this._defaults.overrides, - keys: this._defaults.keys - }, - user: { - contents: this._user.contents, - overrides: this._user.overrides, - keys: this._user.keys - }, - workspace: { - contents: this._workspaceConfiguration.contents, - overrides: this._workspaceConfiguration.overrides, - keys: this._workspaceConfiguration.keys - }, - folders: this.folders.keys().reduce((result, folder) => { - const { contents, overrides, keys } = this.folders.get(folder); - result[folder.toString()] = { contents, overrides, keys }; - return result; - }, Object.create({})) - }; - } - - public static parse(data: IConfigurationData, workspace: Workspace): Configuration { - const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults); - const userConfiguration = Configuration.parseConfigurationModel(data.user); - const workspaceConfiguration = Configuration.parseConfigurationModel(data.workspace); - const folders: StrictResourceMap> = Object.keys(data.folders).reduce((result, key) => { - result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key])); - return result; - }, new StrictResourceMap>()); - return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders, workspace); - } - - private static parseConfigurationModel(model: IConfiguraionModel): ConfigurationModel { - return new ConfigurationModel(model.contents, model.keys, model.overrides); - } +export function keyFromOverrideIdentifier(overrideIdentifier: string): string { + return `[${overrideIdentifier}]`; } \ No newline at end of file diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts new file mode 100644 index 00000000000..4d619fbc13b --- /dev/null +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -0,0 +1,405 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as json from 'vs/base/common/json'; +import { StrictResourceMap } from 'vs/base/common/map'; +import * as arrays from 'vs/base/common/arrays'; +import * as objects from 'vs/base/common/objects'; +import URI from 'vs/base/common/uri'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfiguraionModel, merge, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys } from 'vs/platform/configuration/common/configuration'; +import { Workspace } from 'vs/platform/workspace/common/workspace'; + +export class ConfigurationModel implements IConfiguraionModel { + + constructor(protected _contents: T = {}, protected _keys: string[] = [], protected _overrides: IOverrides[] = []) { + } + + public get contents(): T { + return this._contents; + } + + public get overrides(): IOverrides[] { + return this._overrides; + } + + public get keys(): string[] { + return this._keys; + } + + public getContentsFor(section: string): V { + return objects.clone(this.contents[section]); + } + + public override(identifier: string): ConfigurationModel { + const result = new ConfigurationModel(); + const contents = objects.clone(this.contents); + if (this._overrides) { + for (const override of this._overrides) { + if (override.identifiers.indexOf(identifier) !== -1) { + merge(contents, override.contents, true); + } + } + } + result._contents = contents; + return result; + } + + public setValue(key: string, value: any) { + addToValueTree(this._contents, key, value, e => { throw new Error(e); }); + if (this._keys.indexOf(key) === -1) { + this._keys.push(key); + } + } + + public removeValue(key: string) { + // Remove key from the value tree + const index = this._keys.indexOf(key); + if (index !== -1) { + this._keys.splice(index, 1); + } + } + + public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { + const mergedModel = new ConfigurationModel(); + this.doMerge(mergedModel, this, overwrite); + this.doMerge(mergedModel, other, overwrite); + return mergedModel; + } + + protected doMerge(source: ConfigurationModel, target: ConfigurationModel, overwrite: boolean = true) { + merge(source.contents, objects.clone(target.contents), overwrite); + const overrides = objects.clone(source._overrides); + for (const override of target._overrides) { + const [sourceOverride] = overrides.filter(o => arrays.equals(o.identifiers, override.identifiers)); + if (sourceOverride) { + merge(sourceOverride.contents, override.contents, overwrite); + } else { + overrides.push(override); + } + } + source._overrides = overrides; + } +} + +export class DefaultConfigurationModel extends ConfigurationModel { + + constructor() { + super(getDefaultValues()); + this._keys = getConfigurationKeys(); + this._overrides = Object.keys(this._contents) + .filter(key => OVERRIDE_PROPERTY_PATTERN.test(key)) + .map(key => { + return >{ + identifiers: [overrideIdentifierFromKey(key).trim()], + contents: toValuesTree(this._contents[key], message => console.error(`Conflict in default settings file: ${message}`)) + }; + }); + } + + public get keys(): string[] { + return this._keys; + } +} + +interface Overrides extends IOverrides { + raw: any; +} + +export class CustomConfigurationModel extends ConfigurationModel { + + protected _parseErrors: any[] = []; + + constructor(content: string = '', private name: string = '') { + super(); + if (content) { + this.update(content); + } + } + + public get errors(): any[] { + return this._parseErrors; + } + + public update(content: string): void { + let parsed: T = {}; + let overrides: Overrides[] = []; + let currentProperty: string = null; + let currentParent: any = []; + let previousParents: any[] = []; + let parseErrors: json.ParseError[] = []; + + function onValue(value: any) { + if (Array.isArray(currentParent)) { + (currentParent).push(value); + } else if (currentProperty) { + currentParent[currentProperty] = value; + } + if (OVERRIDE_PROPERTY_PATTERN.test(currentProperty)) { + onOverrideSettingsValue(currentProperty, value); + } + } + + function onOverrideSettingsValue(property: string, value: any): void { + overrides.push({ + identifiers: [overrideIdentifierFromKey(property).trim()], + raw: value, + contents: null + }); + } + + let visitor: json.JSONVisitor = { + onObjectBegin: () => { + let object = {}; + onValue(object); + previousParents.push(currentParent); + currentParent = object; + currentProperty = null; + }, + onObjectProperty: (name: string) => { + currentProperty = name; + }, + onObjectEnd: () => { + currentParent = previousParents.pop(); + }, + onArrayBegin: () => { + let array: any[] = []; + onValue(array); + previousParents.push(currentParent); + currentParent = array; + currentProperty = null; + }, + onArrayEnd: () => { + currentParent = previousParents.pop(); + }, + onLiteralValue: onValue, + onError: (error: json.ParseErrorCode) => { + parseErrors.push({ error: error }); + } + }; + if (content) { + try { + json.visit(content, visitor); + parsed = currentParent[0] || {}; + } catch (e) { + console.error(`Error while parsing settings file ${this.name}: ${e}`); + this._parseErrors = [e]; + } + } + this.processRaw(parsed); + + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + this._overrides = overrides.map>(override => { + // Filter unknown and non-overridable properties + const raw = {}; + for (const key in override.raw) { + if (configurationProperties[key] && configurationProperties[key].overridable) { + raw[key] = override.raw[key]; + } + } + return { + identifiers: override.identifiers, + contents: toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)) + }; + }); + } + + protected processRaw(raw: T): void { + this._contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)); + this._keys = Object.keys(raw); + } +} + +export class Configuration { + + private _globalConfiguration: ConfigurationModel; + private _workspaceConsolidatedConfiguration: ConfigurationModel; + protected _foldersConsolidatedConfigurations: StrictResourceMap>; + protected _memoryConsolidatedConfigurations: StrictResourceMap>; + + constructor(protected _defaults: ConfigurationModel, + protected _user: ConfigurationModel, + protected _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), + protected folders: StrictResourceMap> = new StrictResourceMap>(), + protected _memoryConfiguration: ConfigurationModel = new ConfigurationModel(), + protected _memoryConfigurationByResource: StrictResourceMap> = new StrictResourceMap>(), + protected _workspace?: Workspace) { + this.merge(); + } + + get defaults(): ConfigurationModel { + return this._defaults; + } + + get user(): ConfigurationModel { + return this._user; + } + + get workspace(): ConfigurationModel { + return this._workspaceConfiguration; + } + + protected merge(): void { + this._globalConfiguration = new ConfigurationModel().merge(this._defaults).merge(this._user); + this._workspaceConsolidatedConfiguration = new ConfigurationModel().merge(this._globalConfiguration).merge(this._workspaceConfiguration); + this._foldersConsolidatedConfigurations = new StrictResourceMap>(); + for (const folder of this.folders.keys()) { + this.mergeFolder(folder); + } + } + + protected mergeFolder(folder: URI) { + this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); + } + + protected mergeMemory(folder: URI) { + this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); + } + + getValue(section: string = '', overrides: IConfigurationOverrides = {}): C { + const configModel = this.getConsolidateConfigurationModel(overrides); + return section ? configModel.getContentsFor(section) : configModel.contents; + } + + getValue2(key: string, overrides: IConfigurationOverrides = {}): any { + // make sure to clone the configuration so that the receiver does not tamper with the values + const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); + return objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)); + } + + updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void { + let memoryConfiguration: ConfigurationModel; + if (overrides.resource) { + let memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource); + if (!memoryConfiguration) { + memoryConfiguration = new ConfigurationModel(); + this._memoryConfigurationByResource.set(overrides.resource, memoryConfiguration); + } + } else { + memoryConfiguration = this._memoryConfiguration; + } + if (value === void 0) { + memoryConfiguration.removeValue(key); + } else { + memoryConfiguration.setValue(key, value); + } + } + + lookup(key: string, overrides: IConfigurationOverrides = {}): { + default: C, + user: C, + workspace: C, + workspaceFolder: C + memory?: C + value: C, + } { + // make sure to clone the configuration so that the receiver does not tamper with the values + const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); + const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); + const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; + return { + default: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._defaults.override(overrides.overrideIdentifier).contents : this._defaults.contents, key)), + user: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._user.override(overrides.overrideIdentifier).contents : this._user.contents, key)), + workspace: objects.clone(this._workspace ? getConfigurationValue(overrides.overrideIdentifier ? this._workspaceConfiguration.override(overrides.overrideIdentifier).contents : this._workspaceConfiguration.contents, key) : void 0), //Check on workspace exists or not because _workspaceConfiguration is never null + workspaceFolder: objects.clone(folderConfigurationModel ? getConfigurationValue(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0), + memory: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).contents : memoryConfigurationModel.contents, key)), + value: objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)) + }; + } + + keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + } { + const folderConfigurationModel = this.getFolderConfigurationModelForResource(); + return { + default: this._defaults.keys, + user: this._user.keys, + workspace: this._workspaceConfiguration.keys, + workspaceFolder: folderConfigurationModel ? folderConfigurationModel.keys : [] + }; + } + + private getConsolidateConfigurationModel(overrides: IConfigurationOverrides): ConfigurationModel { + let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides); + return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; + } + + private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides): ConfigurationModel { + if (!this._workspace) { + return this._globalConfiguration; + } + + if (!resource) { + return this._workspaceConsolidatedConfiguration.merge(this._memoryConfiguration); + } + + let consolidateConfiguration = this._workspaceConsolidatedConfiguration; + const root = this._workspace.getFolder(resource); + if (root) { + consolidateConfiguration = this._foldersConsolidatedConfigurations.get(root.uri) || this._workspaceConsolidatedConfiguration; + } + + const memoryConfigurationForResource = this._memoryConfigurationByResource.get(resource); + if (memoryConfigurationForResource) { + consolidateConfiguration = consolidateConfiguration.merge(memoryConfigurationForResource); + } + + return consolidateConfiguration; + } + + private getFolderConfigurationModelForResource(resource?: URI): ConfigurationModel { + if (!this._workspace || !resource) { + return null; + } + + const root = this._workspace.getFolder(resource); + return root ? this.folders.get(root.uri) : null; + } + + public toData(): IConfigurationData { + return { + defaults: { + contents: this._defaults.contents, + overrides: this._defaults.overrides, + keys: this._defaults.keys + }, + user: { + contents: this._user.contents, + overrides: this._user.overrides, + keys: this._user.keys + }, + workspace: { + contents: this._workspaceConfiguration.contents, + overrides: this._workspaceConfiguration.overrides, + keys: this._workspaceConfiguration.keys + }, + folders: this.folders.keys().reduce((result, folder) => { + const { contents, overrides, keys } = this.folders.get(folder); + result[folder.toString()] = { contents, overrides, keys }; + return result; + }, Object.create({})) + }; + } + + public static parse(data: IConfigurationData, workspace: Workspace): Configuration { + const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults); + const userConfiguration = Configuration.parseConfigurationModel(data.user); + const workspaceConfiguration = Configuration.parseConfigurationModel(data.workspace); + const folders: StrictResourceMap> = Object.keys(data.folders).reduce((result, key) => { + result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key])); + return result; + }, new StrictResourceMap>()); + return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders, new ConfigurationModel(), new StrictResourceMap>(), workspace); + } + + private static parseConfigurationModel(model: IConfiguraionModel): ConfigurationModel { + return new ConfigurationModel(model.contents, model.keys, model.overrides); + } +} \ No newline at end of file diff --git a/src/vs/platform/configuration/common/model.ts b/src/vs/platform/configuration/common/model.ts deleted file mode 100644 index 2eca51661ff..00000000000 --- a/src/vs/platform/configuration/common/model.ts +++ /dev/null @@ -1,194 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { Registry } from 'vs/platform/registry/common/platform'; -import * as json from 'vs/base/common/json'; -import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; -import { ConfigurationModel, IOverrides, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration'; - -export function getDefaultValues(): any { - const valueTreeRoot: any = Object.create(null); - const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - - for (let key in properties) { - let value = properties[key].default; - addToValueTree(valueTreeRoot, key, value, message => console.error(`Conflict in default settings: ${message}`)); - } - - return valueTreeRoot; -} - -export function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void): any { - const root = Object.create(null); - - for (let key in properties) { - addToValueTree(root, key, properties[key], conflictReporter); - } - - return root; -} - -function addToValueTree(settingsTreeRoot: any, key: string, value: any, conflictReporter: (message: string) => void): void { - const segments = key.split('.'); - const last = segments.pop(); - - let curr = settingsTreeRoot; - for (let i = 0; i < segments.length; i++) { - let s = segments[i]; - let obj = curr[s]; - switch (typeof obj) { - case 'undefined': - obj = curr[s] = Object.create(null); - break; - case 'object': - break; - default: - conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is ${JSON.stringify(obj)}`); - return; - } - curr = obj; - }; - - if (typeof curr === 'object') { - curr[last] = value; // workaround https://github.com/Microsoft/vscode/issues/13606 - } else { - conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`); - } -} - -export function getConfigurationKeys(): string[] { - const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - - return Object.keys(properties); -} - -export class DefaultConfigurationModel extends ConfigurationModel { - - constructor() { - super(getDefaultValues()); - this._keys = getConfigurationKeys(); - this._overrides = Object.keys(this._contents) - .filter(key => OVERRIDE_PROPERTY_PATTERN.test(key)) - .map(key => { - return >{ - identifiers: [overrideIdentifierFromKey(key).trim()], - contents: toValuesTree(this._contents[key], message => console.error(`Conflict in default settings file: ${message}`)) - }; - }); - } - - public get keys(): string[] { - return this._keys; - } -} - -interface Overrides extends IOverrides { - raw: any; -} - -export class CustomConfigurationModel extends ConfigurationModel { - - protected _parseErrors: any[] = []; - - constructor(content: string = '', private name: string = '') { - super(); - if (content) { - this.update(content); - } - } - - public get errors(): any[] { - return this._parseErrors; - } - - public update(content: string): void { - let parsed: T = {}; - let overrides: Overrides[] = []; - let currentProperty: string = null; - let currentParent: any = []; - let previousParents: any[] = []; - let parseErrors: json.ParseError[] = []; - - function onValue(value: any) { - if (Array.isArray(currentParent)) { - (currentParent).push(value); - } else if (currentProperty) { - currentParent[currentProperty] = value; - } - if (OVERRIDE_PROPERTY_PATTERN.test(currentProperty)) { - onOverrideSettingsValue(currentProperty, value); - } - } - - function onOverrideSettingsValue(property: string, value: any): void { - overrides.push({ - identifiers: [overrideIdentifierFromKey(property).trim()], - raw: value, - contents: null - }); - } - - let visitor: json.JSONVisitor = { - onObjectBegin: () => { - let object = {}; - onValue(object); - previousParents.push(currentParent); - currentParent = object; - currentProperty = null; - }, - onObjectProperty: (name: string) => { - currentProperty = name; - }, - onObjectEnd: () => { - currentParent = previousParents.pop(); - }, - onArrayBegin: () => { - let array: any[] = []; - onValue(array); - previousParents.push(currentParent); - currentParent = array; - currentProperty = null; - }, - onArrayEnd: () => { - currentParent = previousParents.pop(); - }, - onLiteralValue: onValue, - onError: (error: json.ParseErrorCode) => { - parseErrors.push({ error: error }); - } - }; - if (content) { - try { - json.visit(content, visitor); - parsed = currentParent[0] || {}; - } catch (e) { - console.error(`Error while parsing settings file ${this.name}: ${e}`); - this._parseErrors = [e]; - } - } - this.processRaw(parsed); - - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - this._overrides = overrides.map>(override => { - // Filter unknown and non-overridable properties - const raw = {}; - for (const key in override.raw) { - if (configurationProperties[key] && configurationProperties[key].overridable) { - raw[key] = override.raw[key]; - } - } - return { - identifiers: override.identifiers, - contents: toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)) - }; - }); - } - - protected processRaw(raw: T): void { - this._contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)); - this._keys = Object.keys(raw); - } -} \ No newline at end of file diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index 0d97199766b..6cfa5d53fb7 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -8,8 +8,8 @@ import { ConfigWatcher } from 'vs/base/node/config'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, toConfigurationUpdateEvent, ConfigurationModel, Configuration, compare } from 'vs/platform/configuration/common/configuration'; -import { CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/model'; +import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, toConfigurationUpdateEvent, compare } from 'vs/platform/configuration/common/configuration'; +import { CustomConfigurationModel, DefaultConfigurationModel, ConfigurationModel, Configuration } from 'vs/platform/configuration/common/configurationModels'; import Event, { Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { onUnexpectedError } from 'vs/base/common/errors'; diff --git a/src/vs/platform/configuration/test/common/configuration.model.test.ts b/src/vs/platform/configuration/test/common/configuration.model.test.ts index 42b52919b33..4b4ff1e3ed1 100644 --- a/src/vs/platform/configuration/test/common/configuration.model.test.ts +++ b/src/vs/platform/configuration/test/common/configuration.model.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { ConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; diff --git a/src/vs/platform/configuration/test/common/model.test.ts b/src/vs/platform/configuration/test/common/configurationModel.test.ts similarity index 98% rename from src/vs/platform/configuration/test/common/model.test.ts rename to src/vs/platform/configuration/test/common/configurationModel.test.ts index aa9597b6702..3cc4f519bc2 100644 --- a/src/vs/platform/configuration/test/common/model.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModel.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/model'; +import { CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index 5653f211b14..2410406e261 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -9,8 +9,7 @@ import { TernarySearchTree } from 'vs/base/common/map'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { EventEmitter } from 'vs/base/common/eventEmitter'; -import { getConfigurationKeys } from 'vs/platform/configuration/common/model'; -import { IConfigurationOverrides, IConfigurationService, getConfigurationValue } from 'vs/platform/configuration/common/configuration'; +import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue } from 'vs/platform/configuration/common/configuration'; export class TestConfigurationService extends EventEmitter implements IConfigurationService { public _serviceBrand: any; diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 9e5101340bf..1647e1736ac 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -11,7 +11,8 @@ import { WorkspaceConfiguration } from 'vscode'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostConfigurationShape, MainThreadConfigurationShape } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; -import { IConfigurationData, Configuration } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationData } from 'vs/platform/configuration/common/configuration'; +import { Configuration } from 'vs/platform/configuration/common/configurationModels'; import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; function lookUp(tree: any, key: string) { diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index b23dc396e7c..85afabd93a7 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -5,8 +5,8 @@ 'use strict'; import { clone, equals } from 'vs/base/common/objects'; -import { CustomConfigurationModel, toValuesTree } from 'vs/platform/configuration/common/model'; -import { ConfigurationModel, Configuration as BaseConfiguration, compare } from 'vs/platform/configuration/common/configuration'; +import { compare, toValuesTree } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel, Configuration as BaseConfiguration, CustomConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, IConfigurationPropertySchema, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; @@ -185,8 +185,15 @@ export class FolderConfigurationModel extends CustomConfigurationModel { export class Configuration extends BaseConfiguration { - constructor(defaults: ConfigurationModel, user: ConfigurationModel, workspaceConfiguration: ConfigurationModel, protected folders: StrictResourceMap>, workspace: Workspace) { - super(defaults, user, workspaceConfiguration, folders, workspace); + constructor( + defaults: ConfigurationModel, + user: ConfigurationModel, + workspaceConfiguration: ConfigurationModel, + protected folders: StrictResourceMap>, + memoryConfiguration: ConfigurationModel, + memoryConfigurationByResource: StrictResourceMap>, + workspace: Workspace) { + super(defaults, user, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource, workspace); } updateDefaultConfiguration(defaults: ConfigurationModel): void { @@ -199,7 +206,7 @@ export class Configuration extends BaseConfiguration { const { added, updated, removed } = compare(this._user, user); changedKeys = [...added, ...updated, ...removed]; if (changedKeys.length) { - const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._workspace); + const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._memoryConfiguration, this._memoryConfigurationByResource, this._workspace); this._user = user; this.merge(); @@ -215,7 +222,7 @@ export class Configuration extends BaseConfiguration { const { added, updated, removed } = compare(this._workspaceConfiguration, workspaceConfiguration); changedKeys = [...added, ...updated, ...removed]; if (changedKeys.length) { - const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._workspace); + const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._memoryConfiguration, this._memoryConfigurationByResource, this._workspace); this._workspaceConfiguration = workspaceConfiguration; this.merge(); @@ -234,7 +241,7 @@ export class Configuration extends BaseConfiguration { const { added, updated, removed } = compare(currentFolderConfiguration, configuration); changedKeys = [...added, ...updated, ...removed]; if (changedKeys.length) { - const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._workspace); + const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._memoryConfiguration, this._memoryConfigurationByResource, this._workspace); this.folders.set(resource, configuration); this.mergeFolder(resource); diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index f5b7cef1e68..4e4b99992ee 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -22,9 +22,9 @@ import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files import { isLinux } from 'vs/base/common/platform'; import { ConfigWatcher } from 'vs/base/node/config'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { CustomConfigurationModel } from 'vs/platform/configuration/common/model'; +import { CustomConfigurationModel, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { IConfigurationChangeEvent, ConfigurationTarget, toConfigurationUpdateEvent, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, toConfigurationUpdateEvent, ConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { ConfigurationService as GlobalConfigurationService, isConfigurationOverrides } from 'vs/platform/configuration/node/configurationService'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -152,13 +152,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat if (this.configurationEditingService) { const overrides = isConfigurationOverrides(arg3) ? arg3 : void 0; const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3); - if (target) { - if (target === ConfigurationTarget.MEMORY) { - return TPromise.as(null); - } else { - return this.writeConfigurationValue(key, value, target, overrides); - } + return this.writeConfigurationValue(key, value, target, overrides); } } return TPromise.as(null); @@ -176,6 +171,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat user: T, workspace: T, workspaceFolder: T, + memory?: T, value: T } { return this._configuration.lookup(key); @@ -329,7 +325,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat const folderConfigurationModels = new StrictResourceMap>(); folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); - this._configuration = new Configuration(this.baseConfigurationService.configuration.defaults, this.baseConfigurationService.configuration.user, workspaceConfiguration, folderConfigurationModels, this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: @Sandy Avoid passing null + this._configuration = new Configuration(this.baseConfigurationService.configuration.defaults, this.baseConfigurationService.configuration.user, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new StrictResourceMap>(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: @Sandy Avoid passing null // TODO: compare with old values?? const keys = this._configuration.keys(); @@ -465,6 +461,21 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat } private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides): TPromise { + if (target === ConfigurationTarget.DEFAULT) { + return TPromise.wrapError(new Error('Invalid configuration target')); + } + + let currentTargetValue = this.getTargetValue(key, target, overrides); + if (equals(currentTargetValue, value)) { + return TPromise.as(null); + } + + if (target === ConfigurationTarget.MEMORY) { + this._configuration.updateValue(key, value, overrides); + this.triggerConfigurationChange([key], target); + return TPromise.as(null); + } + return this.configurationEditingService.writeConfiguration(this.toEditableConfigurationTarget(target), { key, value }, { scopes: overrides }) .then(() => { switch (target) { @@ -528,6 +539,23 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat } } + private getTargetValue(key: string, target: ConfigurationTarget, overrides?: IConfigurationOverrides): any { + const inspect = this.inspect(key, overrides); + switch (target) { + case ConfigurationTarget.DEFAULT: + return inspect.default; + case ConfigurationTarget.USER: + return inspect.user; + case ConfigurationTarget.WORKSPACE: + return inspect.workspace; + case ConfigurationTarget.WORKSPACE_FOLDER: + return inspect.workspaceFolder; + case ConfigurationTarget.MEMORY: + return inspect.memory; + } + return void 0; + } + private getTargetConfiguration(target: ConfigurationTarget): any { switch (target) { case ConfigurationTarget.DEFAULT: diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index a935f3c9eed..54fa31a10d4 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -12,7 +12,7 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration import { MainThreadConfigurationShape } from 'vs/workbench/api/node/extHost.protocol'; import { TPromise } from 'vs/base/common/winjs.base'; import { ConfigurationTarget, ConfigurationEditingErrorCode, ConfigurationEditingError } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { ConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { TestThreadService } from './testThreadService'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; From 38f2f142b01dae11ddc56e28b720ad717c4fa836 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Oct 2017 11:48:02 +0200 Subject: [PATCH 10/28] Remove unnecessary generics in configuration area --- src/vs/code/electron-main/app.ts | 2 +- .../standalone/browser/simpleServices.ts | 4 +- .../configuration/common/configuration.ts | 22 ++--- .../common/configurationModels.ts | 96 +++++++++---------- .../node/configurationService.ts | 14 +-- .../test/common/configuration.model.test.ts | 20 ++-- src/vs/workbench/api/node/extHost.protocol.ts | 4 +- .../api/node/extHostConfiguration.ts | 6 +- .../common/configurationModels.ts | 76 +++++++-------- .../node/configurationService.ts | 50 +++++----- 10 files changed, 147 insertions(+), 147 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 789da519ee2..2efd0e0d351 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -76,7 +76,7 @@ export class CodeApplication { @ILogService private logService: ILogService, @IEnvironmentService private environmentService: IEnvironmentService, @ILifecycleService private lifecycleService: ILifecycleService, - @IConfigurationService private configurationService: ConfigurationService, + @IConfigurationService private configurationService: ConfigurationService, @IStorageService private storageService: IStorageService, @IHistoryMainService private historyService: IHistoryMainService ) { diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index a9b23d8d74f..3d84c45b5eb 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -442,13 +442,13 @@ export class SimpleConfigurationService implements IConfigurationService { private _onDidUpdateConfiguration = new Emitter(); public onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; - private _configuration: Configuration; + private _configuration: Configuration; constructor() { this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); } - private configuration(): Configuration { + private configuration(): Configuration { return this._configuration; } diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 74fba9962fd..b9cbfb0a599 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -80,25 +80,25 @@ export interface IConfigurationService { }; } -export interface IConfiguraionModel { - contents: T; +export interface IConfiguraionModel { + contents: any; keys: string[]; - overrides: IOverrides[]; + overrides: IOverrides[]; } -export interface IOverrides { - contents: T; +export interface IOverrides { + contents: any; identifiers: string[]; } -export interface IConfigurationData { - defaults: IConfiguraionModel; - user: IConfiguraionModel; - workspace: IConfiguraionModel; - folders: { [folder: string]: IConfiguraionModel }; +export interface IConfigurationData { + defaults: IConfiguraionModel; + user: IConfiguraionModel; + workspace: IConfiguraionModel; + folders: { [folder: string]: IConfiguraionModel }; } -export function compare(from: IConfiguraionModel, to: IConfiguraionModel): { added: string[], removed: string[], updated: string[] } { +export function compare(from: IConfiguraionModel, to: IConfiguraionModel): { added: string[], removed: string[], updated: string[] } { const added = to.keys.filter(key => from.keys.indexOf(key) === -1); const removed = from.keys.filter(key => to.keys.indexOf(key) === -1); const updated = []; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 4d619fbc13b..8a207935c4a 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -14,16 +14,16 @@ import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'v import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfiguraionModel, merge, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys } from 'vs/platform/configuration/common/configuration'; import { Workspace } from 'vs/platform/workspace/common/workspace'; -export class ConfigurationModel implements IConfiguraionModel { +export class ConfigurationModel implements IConfiguraionModel { - constructor(protected _contents: T = {}, protected _keys: string[] = [], protected _overrides: IOverrides[] = []) { + constructor(protected _contents: any = {}, protected _keys: string[] = [], protected _overrides: IOverrides[] = []) { } - public get contents(): T { + public get contents(): any { return this._contents; } - public get overrides(): IOverrides[] { + public get overrides(): IOverrides[] { return this._overrides; } @@ -35,8 +35,8 @@ export class ConfigurationModel implements IConfiguraionModel { return objects.clone(this.contents[section]); } - public override(identifier: string): ConfigurationModel { - const result = new ConfigurationModel(); + public override(identifier: string): ConfigurationModel { + const result = new ConfigurationModel(); const contents = objects.clone(this.contents); if (this._overrides) { for (const override of this._overrides) { @@ -64,14 +64,14 @@ export class ConfigurationModel implements IConfiguraionModel { } } - public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { - const mergedModel = new ConfigurationModel(); + public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { + const mergedModel = new ConfigurationModel(); this.doMerge(mergedModel, this, overwrite); this.doMerge(mergedModel, other, overwrite); return mergedModel; } - protected doMerge(source: ConfigurationModel, target: ConfigurationModel, overwrite: boolean = true) { + protected doMerge(source: ConfigurationModel, target: ConfigurationModel, overwrite: boolean = true) { merge(source.contents, objects.clone(target.contents), overwrite); const overrides = objects.clone(source._overrides); for (const override of target._overrides) { @@ -86,7 +86,7 @@ export class ConfigurationModel implements IConfiguraionModel { } } -export class DefaultConfigurationModel extends ConfigurationModel { +export class DefaultConfigurationModel extends ConfigurationModel { constructor() { super(getDefaultValues()); @@ -94,7 +94,7 @@ export class DefaultConfigurationModel extends ConfigurationModel { this._overrides = Object.keys(this._contents) .filter(key => OVERRIDE_PROPERTY_PATTERN.test(key)) .map(key => { - return >{ + return { identifiers: [overrideIdentifierFromKey(key).trim()], contents: toValuesTree(this._contents[key], message => console.error(`Conflict in default settings file: ${message}`)) }; @@ -106,11 +106,11 @@ export class DefaultConfigurationModel extends ConfigurationModel { } } -interface Overrides extends IOverrides { +interface Overrides extends IOverrides { raw: any; } -export class CustomConfigurationModel extends ConfigurationModel { +export class CustomConfigurationModel extends ConfigurationModel { protected _parseErrors: any[] = []; @@ -126,8 +126,8 @@ export class CustomConfigurationModel extends ConfigurationModel { } public update(content: string): void { - let parsed: T = {}; - let overrides: Overrides[] = []; + let parsed: any = {}; + let overrides: Overrides[] = []; let currentProperty: string = null; let currentParent: any = []; let previousParents: any[] = []; @@ -193,7 +193,7 @@ export class CustomConfigurationModel extends ConfigurationModel { this.processRaw(parsed); const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - this._overrides = overrides.map>(override => { + this._overrides = overrides.map(override => { // Filter unknown and non-overridable properties const raw = {}; for (const key in override.raw) { @@ -203,61 +203,61 @@ export class CustomConfigurationModel extends ConfigurationModel { } return { identifiers: override.identifiers, - contents: toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)) + contents: toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)) }; }); } - protected processRaw(raw: T): void { + protected processRaw(raw: any): void { this._contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)); this._keys = Object.keys(raw); } } -export class Configuration { +export class Configuration { - private _globalConfiguration: ConfigurationModel; - private _workspaceConsolidatedConfiguration: ConfigurationModel; - protected _foldersConsolidatedConfigurations: StrictResourceMap>; - protected _memoryConsolidatedConfigurations: StrictResourceMap>; + private _globalConfiguration: ConfigurationModel; + private _workspaceConsolidatedConfiguration: ConfigurationModel; + protected _foldersConsolidatedConfigurations: StrictResourceMap; + protected _memoryConsolidatedConfigurations: StrictResourceMap; - constructor(protected _defaults: ConfigurationModel, - protected _user: ConfigurationModel, - protected _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), - protected folders: StrictResourceMap> = new StrictResourceMap>(), - protected _memoryConfiguration: ConfigurationModel = new ConfigurationModel(), - protected _memoryConfigurationByResource: StrictResourceMap> = new StrictResourceMap>(), + constructor(protected _defaults: ConfigurationModel, + protected _user: ConfigurationModel, + protected _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), + protected folders: StrictResourceMap = new StrictResourceMap(), + protected _memoryConfiguration: ConfigurationModel = new ConfigurationModel(), + protected _memoryConfigurationByResource: StrictResourceMap = new StrictResourceMap(), protected _workspace?: Workspace) { this.merge(); } - get defaults(): ConfigurationModel { + get defaults(): ConfigurationModel { return this._defaults; } - get user(): ConfigurationModel { + get user(): ConfigurationModel { return this._user; } - get workspace(): ConfigurationModel { + get workspace(): ConfigurationModel { return this._workspaceConfiguration; } protected merge(): void { - this._globalConfiguration = new ConfigurationModel().merge(this._defaults).merge(this._user); - this._workspaceConsolidatedConfiguration = new ConfigurationModel().merge(this._globalConfiguration).merge(this._workspaceConfiguration); - this._foldersConsolidatedConfigurations = new StrictResourceMap>(); + this._globalConfiguration = new ConfigurationModel().merge(this._defaults).merge(this._user); + this._workspaceConsolidatedConfiguration = new ConfigurationModel().merge(this._globalConfiguration).merge(this._workspaceConfiguration); + this._foldersConsolidatedConfigurations = new StrictResourceMap(); for (const folder of this.folders.keys()) { this.mergeFolder(folder); } } protected mergeFolder(folder: URI) { - this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); + this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); } protected mergeMemory(folder: URI) { - this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); + this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); } getValue(section: string = '', overrides: IConfigurationOverrides = {}): C { @@ -272,7 +272,7 @@ export class Configuration { } updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void { - let memoryConfiguration: ConfigurationModel; + let memoryConfiguration: ConfigurationModel; if (overrides.resource) { let memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource); if (!memoryConfiguration) { @@ -326,12 +326,12 @@ export class Configuration { }; } - private getConsolidateConfigurationModel(overrides: IConfigurationOverrides): ConfigurationModel { + private getConsolidateConfigurationModel(overrides: IConfigurationOverrides): ConfigurationModel { let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides); - return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; + return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; } - private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides): ConfigurationModel { + private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides): ConfigurationModel { if (!this._workspace) { return this._globalConfiguration; } @@ -354,7 +354,7 @@ export class Configuration { return consolidateConfiguration; } - private getFolderConfigurationModelForResource(resource?: URI): ConfigurationModel { + private getFolderConfigurationModelForResource(resource?: URI): ConfigurationModel { if (!this._workspace || !resource) { return null; } @@ -363,7 +363,7 @@ export class Configuration { return root ? this.folders.get(root.uri) : null; } - public toData(): IConfigurationData { + public toData(): IConfigurationData { return { defaults: { contents: this._defaults.contents, @@ -388,18 +388,18 @@ export class Configuration { }; } - public static parse(data: IConfigurationData, workspace: Workspace): Configuration { + public static parse(data: IConfigurationData, workspace: Workspace): Configuration { const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults); const userConfiguration = Configuration.parseConfigurationModel(data.user); const workspaceConfiguration = Configuration.parseConfigurationModel(data.workspace); - const folders: StrictResourceMap> = Object.keys(data.folders).reduce((result, key) => { + const folders: StrictResourceMap = Object.keys(data.folders).reduce((result, key) => { result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key])); return result; - }, new StrictResourceMap>()); - return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders, new ConfigurationModel(), new StrictResourceMap>(), workspace); + }, new StrictResourceMap()); + return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders, new ConfigurationModel(), new StrictResourceMap(), workspace); } - private static parseConfigurationModel(model: IConfiguraionModel): ConfigurationModel { + private static parseConfigurationModel(model: IConfiguraionModel): ConfigurationModel { return new ConfigurationModel(model.contents, model.keys, model.overrides); } } \ No newline at end of file diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index 6cfa5d53fb7..d7b5f65f730 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -25,12 +25,12 @@ export function isConfigurationOverrides(thing: any): thing is IConfigurationOve && (!thing.resource || thing.resource instanceof URI); } -export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { +export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { _serviceBrand: any; - private _configuration: Configuration; - private userConfigModelWatcher: ConfigWatcher>; + private _configuration: Configuration; + private userConfigModelWatcher: ConfigWatcher; private _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; @@ -41,8 +41,8 @@ export class ConfigurationService extends Disposable implements IConfiguratio super(); this.userConfigModelWatcher = new ConfigWatcher(environmentService.appSettingsPath, { - changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new CustomConfigurationModel(null, environmentService.appSettingsPath), parse: (content: string, parseErrors: any[]) => { - const userConfigModel = new CustomConfigurationModel(content, environmentService.appSettingsPath); + changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new CustomConfigurationModel(null, environmentService.appSettingsPath), parse: (content: string, parseErrors: any[]) => { + const userConfigModel = new CustomConfigurationModel(content, environmentService.appSettingsPath); parseErrors = [...userConfigModel.errors]; return userConfigModel; } @@ -56,7 +56,7 @@ export class ConfigurationService extends Disposable implements IConfiguratio this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDidRegisterConfiguration(configurationProperties))); } - get configuration(): Configuration { + get configuration(): Configuration { return this._configuration; } @@ -126,7 +126,7 @@ export class ConfigurationService extends Disposable implements IConfiguratio } private reset(): void { - const defaults = new DefaultConfigurationModel(); + const defaults = new DefaultConfigurationModel(); const user = this.userConfigModelWatcher.getConfig(); this._configuration = new Configuration(defaults, user); } diff --git a/src/vs/platform/configuration/test/common/configuration.model.test.ts b/src/vs/platform/configuration/test/common/configuration.model.test.ts index 4b4ff1e3ed1..65555f8e9f2 100644 --- a/src/vs/platform/configuration/test/common/configuration.model.test.ts +++ b/src/vs/platform/configuration/test/common/configuration.model.test.ts @@ -29,8 +29,8 @@ suite('Configuration', () => { }); test('simple merge', () => { - let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); - let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); + let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); + let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); let result = base.merge(add); assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); }); @@ -43,24 +43,24 @@ suite('Configuration', () => { }); test('simple merge overrides', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': 2 } }]); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'b': 2 } }]); + let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': 2 } }]); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'b': 2 } }]); let result = base.merge(add); assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 } }]); }); test('recursive merge overrides', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } } }]); + let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } } }]); let result = base.merge(add); assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } } }]); }); test('merge ignore keys', () => { - let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); - let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); + let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); + let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); let result = base.merge(add); assert.deepEqual(result.keys, []); }); @@ -69,7 +69,7 @@ suite('Configuration', () => { let testObject = new ConfigurationModel({ 'a': 1 }); assert.deepEqual(testObject.getContentsFor('a'), 1); - testObject = new ConfigurationModel({ 'a': { 'b': 1 } }); + testObject = new ConfigurationModel({ 'a': { 'b': 1 } }); assert.deepEqual(testObject.getContentsFor('a'), { 'b': 1 }); }); @@ -80,7 +80,7 @@ suite('Configuration', () => { }); test('Test override gives all content merged with overrides', () => { - const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, [], [{ identifiers: ['b'], contents: { 'a': 2 } }]); + const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, [], [{ identifiers: ['b'], contents: { 'a': 2 } }]); assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1 }); }); diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 4016299feed..16f681bddca 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -74,7 +74,7 @@ export interface IInitData { environment: IEnvironment; workspace: IWorkspaceData; extensions: IExtensionDescription[]; - configuration: IConfigurationData; + configuration: IConfigurationData; telemetryInfo: ITelemetryInfo; } @@ -412,7 +412,7 @@ export interface ExtHostCommandsShape { } export interface ExtHostConfigurationShape { - $acceptConfigurationChanged(data: IConfigurationData): void; + $acceptConfigurationChanged(data: IConfigurationData): void; } export interface ExtHostDiagnosticsShape { diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 1647e1736ac..031763f0ff4 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -39,9 +39,9 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { private readonly _onDidChangeConfiguration = new Emitter(); private readonly _proxy: MainThreadConfigurationShape; private readonly _extHostWorkspace: ExtHostWorkspace; - private _configuration: Configuration; + private _configuration: Configuration; - constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationData) { + constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationData) { this._proxy = proxy; this._extHostWorkspace = extHostWorkspace; this._configuration = Configuration.parse(data, extHostWorkspace.workspace); @@ -51,7 +51,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { return this._onDidChangeConfiguration && this._onDidChangeConfiguration.event; } - $acceptConfigurationChanged(data: IConfigurationData) { + $acceptConfigurationChanged(data: IConfigurationData) { this._configuration = Configuration.parse(data, this._extHostWorkspace.workspace); this._onDidChangeConfiguration.fire(undefined); } diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 85afabd93a7..df0c1b196ee 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -15,14 +15,14 @@ import { Workspace } from 'vs/platform/workspace/common/workspace'; import { StrictResourceMap } from 'vs/base/common/map'; import URI from 'vs/base/common/uri'; -export class WorkspaceConfigurationModel extends CustomConfigurationModel { +export class WorkspaceConfigurationModel extends CustomConfigurationModel { - private _raw: T; + private _raw: any; private _folders: IStoredWorkspaceFolder[]; - private _worksapaceSettings: ConfigurationModel; - private _tasksConfiguration: ConfigurationModel; - private _launchConfiguration: ConfigurationModel; - private _workspaceConfiguration: ConfigurationModel; + private _worksapaceSettings: ConfigurationModel; + private _tasksConfiguration: ConfigurationModel; + private _launchConfiguration: ConfigurationModel; + private _workspaceConfiguration: ConfigurationModel; public update(content: string): void { super.update(content); @@ -34,11 +34,11 @@ export class WorkspaceConfigurationModel extends CustomConfigurationModel return this._folders; } - get workspaceConfiguration(): ConfigurationModel { + get workspaceConfiguration(): ConfigurationModel { return this._workspaceConfiguration; } - protected processRaw(raw: T): void { + protected processRaw(raw: any): void { this._raw = raw; this._folders = (this._raw['folders'] || []) as IStoredWorkspaceFolder[]; @@ -49,27 +49,27 @@ export class WorkspaceConfigurationModel extends CustomConfigurationModel super.processRaw(raw); } - private parseConfigurationModel(section: string): ConfigurationModel { + private parseConfigurationModel(section: string): ConfigurationModel { const rawSection = this._raw[section] || {}; const contents = toValuesTree(rawSection, message => console.error(`Conflict in section '${section}' of workspace configuration file ${message}`)); - return new ConfigurationModel(contents, Object.keys(rawSection)); + return new ConfigurationModel(contents, Object.keys(rawSection)); } - private consolidate(): ConfigurationModel { + private consolidate(): ConfigurationModel { const keys: string[] = [...this._worksapaceSettings.keys, ...this._tasksConfiguration.keys.map(key => `tasks.${key}`), ...this._launchConfiguration.keys.map(key => `launch.${key}`)]; - const mergedContents = new ConfigurationModel({}, keys) + const mergedContents = new ConfigurationModel({}, keys) .merge(this._worksapaceSettings) .merge(this._tasksConfiguration) .merge(this._launchConfiguration); - return new ConfigurationModel(mergedContents.contents, keys, mergedContents.overrides); + return new ConfigurationModel(mergedContents.contents, keys, mergedContents.overrides); } } -export class ScopedConfigurationModel extends CustomConfigurationModel { +export class ScopedConfigurationModel extends CustomConfigurationModel { constructor(content: string, name: string, public readonly scope: string) { super(null, name); @@ -85,14 +85,14 @@ export class ScopedConfigurationModel extends CustomConfigurationModel { } -export class FolderSettingsModel extends CustomConfigurationModel { +export class FolderSettingsModel extends CustomConfigurationModel { - private _raw: T; + private _raw: any; private _unsupportedKeys: string[]; - protected processRaw(raw: T): void { + protected processRaw(raw: any): void { this._raw = raw; - const processedRaw = {}; + const processedRaw = {}; this._unsupportedKeys = []; const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); for (let key in raw) { @@ -121,16 +121,16 @@ export class FolderSettingsModel extends CustomConfigurationModel { return !propertySchema.isExecutable; } - public createWorkspaceConfigurationModel(): ConfigurationModel { + public createWorkspaceConfigurationModel(): ConfigurationModel { return this.createScopedConfigurationModel(ConfigurationScope.WINDOW); } - public createFolderScopedConfigurationModel(): ConfigurationModel { + public createFolderScopedConfigurationModel(): ConfigurationModel { return this.createScopedConfigurationModel(ConfigurationScope.RESOURCE); } - private createScopedConfigurationModel(scope: ConfigurationScope): ConfigurationModel { - const workspaceRaw = {}; + private createScopedConfigurationModel(scope: ConfigurationScope): ConfigurationModel { + const workspaceRaw = {}; const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); for (let key in this._raw) { if (this.getScope(key, configurationProperties) === scope) { @@ -148,15 +148,15 @@ export class FolderSettingsModel extends CustomConfigurationModel { } } -export class FolderConfigurationModel extends CustomConfigurationModel { +export class FolderConfigurationModel extends CustomConfigurationModel { - constructor(public readonly workspaceSettingsConfig: FolderSettingsModel, private scopedConfigs: ScopedConfigurationModel[], private scope: ConfigurationScope) { + constructor(public readonly workspaceSettingsConfig: FolderSettingsModel, private scopedConfigs: ScopedConfigurationModel[], private scope: ConfigurationScope) { super(); this.consolidate(); } private consolidate(): void { - this._contents = {}; + this._contents = {}; this._overrides = []; this.doMerge(this, ConfigurationScope.WINDOW === this.scope ? this.workspaceSettingsConfig : this.workspaceSettingsConfig.createFolderScopedConfigurationModel()); @@ -183,25 +183,25 @@ export class FolderConfigurationModel extends CustomConfigurationModel { } } -export class Configuration extends BaseConfiguration { +export class Configuration extends BaseConfiguration { constructor( - defaults: ConfigurationModel, - user: ConfigurationModel, - workspaceConfiguration: ConfigurationModel, - protected folders: StrictResourceMap>, - memoryConfiguration: ConfigurationModel, - memoryConfigurationByResource: StrictResourceMap>, + defaults: ConfigurationModel, + user: ConfigurationModel, + workspaceConfiguration: ConfigurationModel, + protected folders: StrictResourceMap, + memoryConfiguration: ConfigurationModel, + memoryConfigurationByResource: StrictResourceMap, workspace: Workspace) { super(defaults, user, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource, workspace); } - updateDefaultConfiguration(defaults: ConfigurationModel): void { + updateDefaultConfiguration(defaults: ConfigurationModel): void { this._defaults = defaults; this.merge(); } - updateUserConfiguration(user: ConfigurationModel): string[] { + updateUserConfiguration(user: ConfigurationModel): string[] { let changedKeys = []; const { added, updated, removed } = compare(this._user, user); changedKeys = [...added, ...updated, ...removed]; @@ -217,7 +217,7 @@ export class Configuration extends BaseConfiguration { return []; } - updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): string[] { + updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): string[] { let changedKeys = []; const { added, updated, removed } = compare(this._workspaceConfiguration, workspaceConfiguration); changedKeys = [...added, ...updated, ...removed]; @@ -233,7 +233,7 @@ export class Configuration extends BaseConfiguration { return []; } - updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel): string[] { + updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel): string[] { const currentFolderConfiguration = this.folders.get(resource); if (currentFolderConfiguration) { @@ -269,7 +269,7 @@ export class Configuration extends BaseConfiguration { return keys; } - getFolderConfigurationModel(folder: URI): FolderConfigurationModel { - return >this.folders.get(folder); + getFolderConfigurationModel(folder: URI): FolderConfigurationModel { + return this.folders.get(folder); } } \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 4e4b99992ee..7e8884d88e7 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -57,10 +57,10 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat public _serviceBrand: any; private workspace: Workspace; - private _configuration: Configuration; - private baseConfigurationService: GlobalConfigurationService; + private _configuration: Configuration; + private baseConfigurationService: GlobalConfigurationService; private workspaceConfiguration: WorkspaceConfiguration; - private cachedFolderConfigs: StrictResourceMap>; + private cachedFolderConfigs: StrictResourceMap; protected readonly _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; @@ -315,17 +315,17 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat private loadConfiguration(): TPromise { // reset caches - this.cachedFolderConfigs = new StrictResourceMap>(); + this.cachedFolderConfigs = new StrictResourceMap(); const folders = this.workspace.folders; return this.loadFolderConfigurations(folders) .then((folderConfigurations) => { let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations); - const folderConfigurationModels = new StrictResourceMap>(); + const folderConfigurationModels = new StrictResourceMap(); folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); - this._configuration = new Configuration(this.baseConfigurationService.configuration.defaults, this.baseConfigurationService.configuration.user, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new StrictResourceMap>(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: @Sandy Avoid passing null + this._configuration = new Configuration(this.baseConfigurationService.configuration.defaults, this.baseConfigurationService.configuration.user, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new StrictResourceMap(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: @Sandy Avoid passing null // TODO: compare with old values?? const keys = this._configuration.keys(); @@ -333,14 +333,14 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat }); } - private getWorkspaceConfigurationModel(folderConfigurations: FolderConfigurationModel[]): ConfigurationModel { + private getWorkspaceConfigurationModel(folderConfigurations: FolderConfigurationModel[]): ConfigurationModel { switch (this.getWorkbenchState()) { case WorkbenchState.FOLDER: return folderConfigurations[0]; case WorkbenchState.WORKSPACE: return this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration; default: - return new ConfigurationModel(); + return new ConfigurationModel(); } } @@ -452,7 +452,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return TPromise.as(changedKeys); } - private loadFolderConfigurations(folders: IWorkspaceFolder[]): TPromise[]> { + private loadFolderConfigurations(folders: IWorkspaceFolder[]): TPromise { return TPromise.join([...folders.map(folder => { const folderConfiguration = new FolderConfiguration(folder.uri, this.workspaceSettingsRootFolder, this.getWorkbenchState() === WorkbenchState.WORKSPACE ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW); this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration)); @@ -588,7 +588,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat class WorkspaceConfiguration extends Disposable { private _workspaceConfigPath: URI; - private _workspaceConfigurationWatcher: ConfigWatcher>; + private _workspaceConfigurationWatcher: ConfigWatcher; private _workspaceConfigurationWatcherDisposables: IDisposable[] = []; private _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); @@ -618,7 +618,7 @@ class WorkspaceConfiguration extends Disposable { }); } - get workspaceConfigurationModel(): WorkspaceConfigurationModel { + get workspaceConfigurationModel(): WorkspaceConfigurationModel { return this._workspaceConfigurationWatcher ? this._workspaceConfigurationWatcher.getConfig() : new WorkspaceConfigurationModel(); } @@ -632,15 +632,15 @@ class WorkspaceConfiguration extends Disposable { } } -class FolderConfiguration extends Disposable { +class FolderConfiguration extends Disposable { private static RELOAD_CONFIGURATION_DELAY = 50; - private bulkFetchFromWorkspacePromise: TPromise; - private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise> }; + private bulkFetchFromWorkspacePromise: TPromise; + private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise }; private reloadConfigurationScheduler: RunOnceScheduler; - private reloadConfigurationEventEmitter: Emitter> = new Emitter>(); + private reloadConfigurationEventEmitter: Emitter = new Emitter(); constructor(private folder: URI, private configFolderRelativePath: string, private scope: ConfigurationScope) { super(); @@ -649,17 +649,17 @@ class FolderConfiguration extends Disposable { this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configuration => this.reloadConfigurationEventEmitter.fire(configuration), errors.onUnexpectedError), FolderConfiguration.RELOAD_CONFIGURATION_DELAY)); } - loadConfiguration(): TPromise> { + loadConfiguration(): TPromise { // Load workspace locals return this.loadWorkspaceConfigFiles().then(workspaceConfigFiles => { // Consolidate (support *.json files in the workspace settings folder) - const workspaceSettingsConfig = >workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new FolderSettingsModel(null); - const otherConfigModels = Object.keys(workspaceConfigFiles).filter(key => key !== WORKSPACE_CONFIG_DEFAULT_PATH).map(key => >workspaceConfigFiles[key]); - return new FolderConfigurationModel(workspaceSettingsConfig, otherConfigModels, this.scope); + const workspaceSettingsConfig = workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new FolderSettingsModel(null); + const otherConfigModels = Object.keys(workspaceConfigFiles).filter(key => key !== WORKSPACE_CONFIG_DEFAULT_PATH).map(key => workspaceConfigFiles[key]); + return new FolderConfigurationModel(workspaceSettingsConfig, otherConfigModels, this.scope); }); } - private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel }> { + private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel }> { // once: when invoked for the first time we fetch json files that contribute settings if (!this.bulkFetchFromWorkspacePromise) { this.bulkFetchFromWorkspacePromise = resolveStat(this.toResource(this.configFolderRelativePath)).then(stat => { @@ -686,7 +686,7 @@ class FolderConfiguration extends Disposable { return this.bulkFetchFromWorkspacePromise.then(() => TPromise.join(this.workspaceFilePathToConfiguration)); } - public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise> { + public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise { const events = event.changes; let affectedByChanges = false; @@ -744,18 +744,18 @@ class FolderConfiguration extends Disposable { }); } - private createConfigModel(content: IContent): ConfigurationModel { + private createConfigModel(content: IContent): ConfigurationModel { const path = this.toFolderRelativePath(content.resource); if (path === WORKSPACE_CONFIG_DEFAULT_PATH) { - return new FolderSettingsModel(content.value, content.resource.toString()); + return new FolderSettingsModel(content.value, content.resource.toString()); } else { const matches = /\/([^\.]*)*\.json/.exec(path); if (matches && matches[1]) { - return new ScopedConfigurationModel(content.value, content.resource.toString(), matches[1]); + return new ScopedConfigurationModel(content.value, content.resource.toString(), matches[1]); } } - return new CustomConfigurationModel(null); + return new CustomConfigurationModel(null); } private isWorkspaceConfigurationFile(folderRelativePath: string): boolean { From 60688ac08ee7f6e198a39c1a896bd1bf6800b032 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Oct 2017 11:55:21 +0200 Subject: [PATCH 11/28] Do not create a new empty model for merging --- .../platform/configuration/common/configurationModels.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 8a207935c4a..428b3a499a9 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -244,8 +244,8 @@ export class Configuration { } protected merge(): void { - this._globalConfiguration = new ConfigurationModel().merge(this._defaults).merge(this._user); - this._workspaceConsolidatedConfiguration = new ConfigurationModel().merge(this._globalConfiguration).merge(this._workspaceConfiguration); + this._globalConfiguration = this._defaults.merge(this._user); + this._workspaceConsolidatedConfiguration = this._globalConfiguration.merge(this._workspaceConfiguration); this._foldersConsolidatedConfigurations = new StrictResourceMap(); for (const folder of this.folders.keys()) { this.mergeFolder(folder); @@ -253,11 +253,11 @@ export class Configuration { } protected mergeFolder(folder: URI) { - this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); + this._foldersConsolidatedConfigurations.set(folder, this._workspaceConsolidatedConfiguration.merge(this.folders.get(folder))); } protected mergeMemory(folder: URI) { - this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); + this._foldersConsolidatedConfigurations.set(folder, this._workspaceConsolidatedConfiguration.merge(this.folders.get(folder))); } getValue(section: string = '', overrides: IConfigurationOverrides = {}): C { From 4765a8ab173c79d3117217eaa72b199be05ffcb3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Oct 2017 11:57:21 +0200 Subject: [PATCH 12/28] Configuration: Use proper names for getting sections and values --- src/vs/editor/standalone/browser/simpleServices.ts | 4 ++-- src/vs/platform/configuration/common/configurationModels.ts | 4 ++-- src/vs/platform/configuration/node/configurationService.ts | 6 +++--- src/vs/workbench/api/node/extHostConfiguration.ts | 4 ++-- .../services/configuration/common/configurationModels.ts | 6 +++--- .../services/configuration/node/configurationService.ts | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 3d84c45b5eb..91440df58d0 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -459,11 +459,11 @@ export class SimpleConfigurationService implements IConfigurationService { getConfiguration(arg1?: any, arg2?: any): any { const section = typeof arg1 === 'string' ? arg1 : void 0; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; - return this.configuration().getValue(section, overrides); + return this.configuration().getSection(section, overrides); } public getValue(key: string, options?: IConfigurationOverrides): C { - return this.configuration().getValue2(key, options); + return this.configuration().getValue(key, options); } public updateValue(key: string, value: any, arg3?: any, arg4?: any): TPromise { diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 428b3a499a9..a469c8d7571 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -260,12 +260,12 @@ export class Configuration { this._foldersConsolidatedConfigurations.set(folder, this._workspaceConsolidatedConfiguration.merge(this.folders.get(folder))); } - getValue(section: string = '', overrides: IConfigurationOverrides = {}): C { + getSection(section: string = '', overrides: IConfigurationOverrides = {}): C { const configModel = this.getConsolidateConfigurationModel(overrides); return section ? configModel.getContentsFor(section) : configModel.contents; } - getValue2(key: string, overrides: IConfigurationOverrides = {}): any { + getValue(key: string, overrides: IConfigurationOverrides = {}): any { // make sure to clone the configuration so that the receiver does not tamper with the values const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); return objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)); diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index d7b5f65f730..893af01e558 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -67,11 +67,11 @@ export class ConfigurationService extends Disposable implements IConfigurationSe getConfiguration(arg1?: any, arg2?: any): any { const section = typeof arg1 === 'string' ? arg1 : void 0; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; - return this.configuration.getValue(section, overrides); + return this.configuration.getSection(section, overrides); } getValue(key: string, overrides: IConfigurationOverrides): any { - return this.configuration.getValue2(key, overrides); + return this.configuration.getValue(key, overrides); } updateValue(key: string, value: any): TPromise @@ -113,7 +113,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe if (changedKeys.length) { const oldConfiguartion = this._configuration; this.reset(); - changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue2(key), this._configuration.getValue2(key))); + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this._configuration.getValue(key))); if (changedKeys.length) { this.trigger(changedKeys, ConfigurationTarget.USER); } diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 031763f0ff4..6d3aee7186e 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -58,8 +58,8 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { getConfiguration(section?: string, resource?: URI): WorkspaceConfiguration { const config = section - ? lookUp(this._configuration.getValue(null, { resource }), section) - : this._configuration.getValue(null, { resource }); + ? lookUp(this._configuration.getSection(null, { resource }), section) + : this._configuration.getSection(null, { resource }); function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget { if (arg === void 0 || arg === null) { diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index df0c1b196ee..1223ae638a7 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -211,7 +211,7 @@ export class Configuration extends BaseConfiguration { this._user = user; this.merge(); - changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue2(key), this.getValue2(key))); + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this.getValue(key))); return changedKeys; } return []; @@ -227,7 +227,7 @@ export class Configuration extends BaseConfiguration { this._workspaceConfiguration = workspaceConfiguration; this.merge(); - changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue2(key), this.getValue2(key))); + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this.getValue(key))); return changedKeys; } return []; @@ -246,7 +246,7 @@ export class Configuration extends BaseConfiguration { this.folders.set(resource, configuration); this.mergeFolder(resource); - changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue2(key, { resource }), this.getValue2(key, { resource }))); + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key, { resource }), this.getValue(key, { resource }))); return changedKeys; } return []; diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 7e8884d88e7..9a1c04fb4be 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -135,13 +135,13 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat getConfiguration(arg1?: any, arg2?: any): any { const section = typeof arg1 === 'string' ? arg1 : void 0; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; - const contents = this._configuration.getValue(section, overrides); + const contents = this._configuration.getSection(section, overrides); return typeof contents === 'object' ? { toJSON: () => this._configuration.toData(), ...contents } : contents; } getValue(key: string, overrides?: IConfigurationOverrides): T { - return this._configuration.getValue2(key, overrides); + return this._configuration.getValue(key, overrides); } updateValue(key: string, value: any): TPromise From cd4ef9fbacbb696a8e3589f0a8d210c906901ac4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Oct 2017 12:04:48 +0200 Subject: [PATCH 13/28] Use freeze instead of clone to prevent users to tamper data --- .../common/configurationModels.ts | 54 +++++++++---------- .../test/common/configuration.model.test.ts | 6 +-- .../test/common/configurationModel.test.ts | 12 ++--- .../test/common/configurationModels.test.ts | 2 +- 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index a469c8d7571..fcc05810bdb 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -31,22 +31,8 @@ export class ConfigurationModel implements IConfiguraionModel { return this._keys; } - public getContentsFor(section: string): V { - return objects.clone(this.contents[section]); - } - - public override(identifier: string): ConfigurationModel { - const result = new ConfigurationModel(); - const contents = objects.clone(this.contents); - if (this._overrides) { - for (const override of this._overrides) { - if (override.identifiers.indexOf(identifier) !== -1) { - merge(contents, override.contents, true); - } - } - } - result._contents = contents; - return result; + public getSectionContents(section: string): V { + return this.contents[section]; } public setValue(key: string, value: any) { @@ -64,6 +50,20 @@ export class ConfigurationModel implements IConfiguraionModel { } } + public override(identifier: string): ConfigurationModel { + const result = new ConfigurationModel(); + const contents = objects.clone(this.contents); + if (this._overrides) { + for (const override of this._overrides) { + if (override.identifiers.indexOf(identifier) !== -1) { + merge(contents, override.contents, true); + } + } + } + result._contents = contents; + return result; + } + public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { const mergedModel = new ConfigurationModel(); this.doMerge(mergedModel, this, overwrite); @@ -262,13 +262,12 @@ export class Configuration { getSection(section: string = '', overrides: IConfigurationOverrides = {}): C { const configModel = this.getConsolidateConfigurationModel(overrides); - return section ? configModel.getContentsFor(section) : configModel.contents; + return Object.freeze(section ? configModel.getSectionContents(section) : configModel.contents); } getValue(key: string, overrides: IConfigurationOverrides = {}): any { - // make sure to clone the configuration so that the receiver does not tamper with the values const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); - return objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)); + return Object.freeze(getConfigurationValue(consolidateConfigurationModel.contents, key)); } updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void { @@ -297,18 +296,17 @@ export class Configuration { memory?: C value: C, } { - // make sure to clone the configuration so that the receiver does not tamper with the values const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; - return { - default: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._defaults.override(overrides.overrideIdentifier).contents : this._defaults.contents, key)), - user: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._user.override(overrides.overrideIdentifier).contents : this._user.contents, key)), - workspace: objects.clone(this._workspace ? getConfigurationValue(overrides.overrideIdentifier ? this._workspaceConfiguration.override(overrides.overrideIdentifier).contents : this._workspaceConfiguration.contents, key) : void 0), //Check on workspace exists or not because _workspaceConfiguration is never null - workspaceFolder: objects.clone(folderConfigurationModel ? getConfigurationValue(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0), - memory: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).contents : memoryConfigurationModel.contents, key)), - value: objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)) - }; + return Object.freeze({ + default: getConfigurationValue(overrides.overrideIdentifier ? this._defaults.override(overrides.overrideIdentifier).contents : this._defaults.contents, key), + user: getConfigurationValue(overrides.overrideIdentifier ? this._user.override(overrides.overrideIdentifier).contents : this._user.contents, key), + workspace: this._workspace ? getConfigurationValue(overrides.overrideIdentifier ? this._workspaceConfiguration.override(overrides.overrideIdentifier).contents : this._workspaceConfiguration.contents, key) : void 0, //Check on workspace exists or not because _workspaceConfiguration is never null + workspaceFolder: folderConfigurationModel ? getConfigurationValue(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0, + memory: getConfigurationValue(overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).contents : memoryConfigurationModel.contents, key), + value: getConfigurationValue(consolidateConfigurationModel.contents, key) + }); } keys(): { diff --git a/src/vs/platform/configuration/test/common/configuration.model.test.ts b/src/vs/platform/configuration/test/common/configuration.model.test.ts index 65555f8e9f2..ea0266bd500 100644 --- a/src/vs/platform/configuration/test/common/configuration.model.test.ts +++ b/src/vs/platform/configuration/test/common/configuration.model.test.ts @@ -67,16 +67,16 @@ suite('Configuration', () => { test('Test contents while getting an existing property', () => { let testObject = new ConfigurationModel({ 'a': 1 }); - assert.deepEqual(testObject.getContentsFor('a'), 1); + assert.deepEqual(testObject.getSectionContents('a'), 1); testObject = new ConfigurationModel({ 'a': { 'b': 1 } }); - assert.deepEqual(testObject.getContentsFor('a'), { 'b': 1 }); + assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 }); }); test('Test contents are undefined for non existing properties', () => { const testObject = new ConfigurationModel({ awesome: true }); - assert.deepEqual(testObject.getContentsFor('unknownproperty'), undefined); + assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); }); test('Test override gives all content merged with overrides', () => { diff --git a/src/vs/platform/configuration/test/common/configurationModel.test.ts b/src/vs/platform/configuration/test/common/configurationModel.test.ts index 3cc4f519bc2..b79b8cbadcf 100644 --- a/src/vs/platform/configuration/test/common/configurationModel.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModel.test.ts @@ -61,10 +61,10 @@ suite('Configuration', () => { test('Test contents while getting an existing property', () => { let testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1 })); - assert.deepEqual(testObject.getContentsFor('a'), 1); + assert.deepEqual(testObject.getSectionContents('a'), 1); testObject = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); - assert.deepEqual(testObject.getContentsFor('a'), { 'b': 1 }); + assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 }); }); test('Test contents are undefined for non existing properties', () => { @@ -72,13 +72,13 @@ suite('Configuration', () => { awesome: true })); - assert.deepEqual(testObject.getContentsFor('unknownproperty'), undefined); + assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); }); test('Test contents are undefined for undefined config', () => { const testObject = new CustomConfigurationModel(null); - assert.deepEqual(testObject.getContentsFor('unknownproperty'), undefined); + assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); }); test('Test configWithOverrides gives all content merged with overrides', () => { @@ -125,7 +125,7 @@ suite('Configuration', () => { } } }); - assert.equal(true, new DefaultConfigurationModel().getContentsFor('a')); + assert.equal(true, new DefaultConfigurationModel().getSectionContents('a')); }); test('Test registering the language property', () => { @@ -142,7 +142,7 @@ suite('Configuration', () => { } } }); - assert.equal(undefined, new DefaultConfigurationModel().getContentsFor('[a]')); + assert.equal(undefined, new DefaultConfigurationModel().getSectionContents('[a]')); }); }); \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index a6755918258..14e684e145e 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -17,7 +17,7 @@ suite('ConfigurationService - Model', () => { const testObject = new FolderConfigurationModel(settingsConfig, [], ConfigurationScope.WINDOW); - assert.equal(testObject.getContentsFor('task'), undefined); + assert.equal(testObject.getSectionContents('task'), undefined); }); test('Test consolidate (settings and tasks)', () => { From fd70f31b703f27e8cdff476bb2a69e8ab6b75a53 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Oct 2017 12:31:27 +0200 Subject: [PATCH 14/28] Update workspace configuration with memory configuration --- .../configuration/common/configurationModels.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index fcc05810bdb..c77ec098128 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -219,7 +219,6 @@ export class Configuration { private _globalConfiguration: ConfigurationModel; private _workspaceConsolidatedConfiguration: ConfigurationModel; protected _foldersConsolidatedConfigurations: StrictResourceMap; - protected _memoryConsolidatedConfigurations: StrictResourceMap; constructor(protected _defaults: ConfigurationModel, protected _user: ConfigurationModel, @@ -245,18 +244,18 @@ export class Configuration { protected merge(): void { this._globalConfiguration = this._defaults.merge(this._user); - this._workspaceConsolidatedConfiguration = this._globalConfiguration.merge(this._workspaceConfiguration); + this.updateWorkspaceConsolidateConfiguration(); this._foldersConsolidatedConfigurations = new StrictResourceMap(); for (const folder of this.folders.keys()) { this.mergeFolder(folder); } } - protected mergeFolder(folder: URI) { - this._foldersConsolidatedConfigurations.set(folder, this._workspaceConsolidatedConfiguration.merge(this.folders.get(folder))); + private updateWorkspaceConsolidateConfiguration() { + this._workspaceConsolidatedConfiguration = this._globalConfiguration.merge(this._workspaceConfiguration).merge(this._memoryConfiguration); } - protected mergeMemory(folder: URI) { + protected mergeFolder(folder: URI) { this._foldersConsolidatedConfigurations.set(folder, this._workspaceConsolidatedConfiguration.merge(this.folders.get(folder))); } @@ -281,11 +280,16 @@ export class Configuration { } else { memoryConfiguration = this._memoryConfiguration; } + if (value === void 0) { memoryConfiguration.removeValue(key); } else { memoryConfiguration.setValue(key, value); } + + if (!overrides.resource) { + this.updateWorkspaceConsolidateConfiguration(); + } } lookup(key: string, overrides: IConfigurationOverrides = {}): { @@ -335,7 +339,7 @@ export class Configuration { } if (!resource) { - return this._workspaceConsolidatedConfiguration.merge(this._memoryConfiguration); + return this._workspaceConsolidatedConfiguration; } let consolidateConfiguration = this._workspaceConsolidatedConfiguration; From 193980e0baba336e63f892297942144bbc86844e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Oct 2017 12:48:00 +0200 Subject: [PATCH 15/28] Aquire instantiation service after workbench is intialized --- src/vs/workbench/electron-browser/workbench.ts | 2 ++ .../configuration/node/configurationService.ts | 16 +++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 59b300fc28b..128753dedd6 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -624,6 +624,8 @@ export class Workbench implements IPartService { Registry.as(EditorExtensions.EditorInputFactories).setInstantiationService(this.instantiationService); this.instantiationService.createInstance(DefaultConfigurationExportHelper); + + this.configurationService.setInstantiationService(this.getInstantiationService()); } private initSettings(): void { diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 9a1c04fb4be..6edf38685bb 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -7,6 +7,7 @@ import URI from 'vs/base/common/uri'; import * as paths from 'vs/base/common/paths'; import { TPromise } from 'vs/base/common/winjs.base'; +import * as assert from 'vs/base/common/assert'; import Event, { Emitter } from 'vs/base/common/event'; import { StrictResourceMap } from 'vs/base/common/map'; import * as errors from 'vs/base/common/errors'; @@ -149,14 +150,11 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat updateValue(key: string, value: any, target: ConfigurationTarget): TPromise updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise updateValue(key: string, value: any, arg3?: any, arg4?: any): TPromise { - if (this.configurationEditingService) { - const overrides = isConfigurationOverrides(arg3) ? arg3 : void 0; - const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3); - if (target) { - return this.writeConfigurationValue(key, value, target, overrides); - } - } - return TPromise.as(null); + assert.ok(this.configurationEditingService, 'Workbench is not initialized yet'); + const overrides = isConfigurationOverrides(arg3) ? arg3 : void 0; + const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3); + return target ? this.writeConfigurationValue(key, value, target, overrides) + : TPromise.as(null); } reloadConfiguration(folder?: IWorkspaceFolder, key?: string): TPromise { @@ -215,7 +213,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat .then(() => this.initializeConfiguration()); } - aquireDelayedServices(instantiationService: IInstantiationService): void { + setInstantiationService(instantiationService: IInstantiationService): void { this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService); } From 7088898d4487ebacb4d2cee79db992caf537a6f5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Oct 2017 17:32:44 +0200 Subject: [PATCH 16/28] Use a configuration event that can answer if a configuration has changed or not given a resource and override identifier. Requested configuration can be full key or section --- .../configuration/common/configuration.ts | 32 ++--- .../common/configurationModels.ts | 109 +++++++++++++++++- .../node/configurationService.ts | 6 +- .../parts/debug/browser/debugActionItems.ts | 2 +- .../watermark/electron-browser/watermark.ts | 2 +- .../common/configurationModels.ts | 70 +++++++---- .../node/configurationService.ts | 47 ++++---- 7 files changed, 193 insertions(+), 75 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index b9cbfb0a599..a2132efea12 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { TPromise } from 'vs/base/common/winjs.base'; -import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import * as types from 'vs/base/common/types'; import URI from 'vs/base/common/uri'; @@ -12,7 +11,7 @@ import Event from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; export const IConfigurationService = createDecorator('configurationService'); @@ -30,12 +29,12 @@ export enum ConfigurationTarget { } export interface IConfigurationChangeEvent { - keys: string[]; - sections: string[]; - overrideIdentifiers?: string[]; + affectedKeys: string[]; - hasSectionChanged(section: string): boolean; - hasKeyChanged(key: string): boolean; + affectsConfiugration(configuration: string): boolean; + affectsConfiugration(configuration: string, overrideIdentifier: string): boolean; + affectsConfiugration(configuration: string, resource: URI): boolean; + affectsConfiugration(configuration: string, overrideIdentifier: string, resource: URI): boolean; // Following data is used for telemetry source: ConfigurationTarget; @@ -114,23 +113,6 @@ export function compare(from: IConfiguraionModel, to: IConfiguraionModel): { add return { added, removed, updated }; } -export function toConfigurationUpdateEvent(udpated: string[], source: ConfigurationTarget, sourceConfig: any): IConfigurationChangeEvent { - const overrideIdentifiers = []; - const keys: string[] = []; - for (const key of udpated) { - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { - overrideIdentifiers.push(overrideIdentifierFromKey(key).trim()); - } else { - keys.push(key); - } - } - const sections = arrays.distinct(keys.map(key => key.split('.')[0])); - const hasSectionChanged = (section) => sections.indexOf(section) !== -1; - const hasKeyChanged = (key) => keys.indexOf(key) !== -1; - - return { keys, sections, overrideIdentifiers, source, sourceConfig, hasSectionChanged, hasKeyChanged }; -} - export function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void): any { const root = Object.create(null); @@ -227,4 +209,4 @@ export function overrideIdentifierFromKey(key: string): string { export function keyFromOverrideIdentifier(overrideIdentifier: string): string { return `[${overrideIdentifier}]`; -} \ No newline at end of file +} diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index c77ec098128..f86a10ee7d1 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -11,7 +11,7 @@ import * as objects from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; -import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfiguraionModel, merge, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys } from 'vs/platform/configuration/common/configuration'; +import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfiguraionModel, merge, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Workspace } from 'vs/platform/workspace/common/workspace'; export class ConfigurationModel implements IConfiguraionModel { @@ -42,6 +42,15 @@ export class ConfigurationModel implements IConfiguraionModel { } } + public setValueInOverrides(overrideIdentifier: string, key: string, value: any): void { + let override = this._overrides.filter(override => override.identifiers.indexOf(overrideIdentifier) !== -1)[0]; + if (!override) { + override = { identifiers: [overrideIdentifier], contents: {} }; + this._overrides.push(override); + } + addToValueTree(override.contents, key, value, e => { throw new Error(e); }); + } + public removeValue(key: string) { // Remove key from the value tree const index = this._keys.indexOf(key); @@ -272,7 +281,7 @@ export class Configuration { updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void { let memoryConfiguration: ConfigurationModel; if (overrides.resource) { - let memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource); + memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource); if (!memoryConfiguration) { memoryConfiguration = new ConfigurationModel(); this._memoryConfigurationByResource.set(overrides.resource, memoryConfiguration); @@ -404,4 +413,100 @@ export class Configuration { private static parseConfigurationModel(model: IConfiguraionModel): ConfigurationModel { return new ConfigurationModel(model.contents, model.keys, model.overrides); } +} + +export class ConfigurationChangeEvent implements IConfigurationChangeEvent { + + private changedConfiguration: ConfigurationModel = new ConfigurationModel(); + private changedConfigurationByResource: StrictResourceMap = new StrictResourceMap(); + private resources: URI[] = []; + + private _source: ConfigurationTarget; + private _sourceConfig: any; + + change(event: ConfigurationChangeEvent): ConfigurationChangeEvent + change(keys: string[], resource?: URI): ConfigurationChangeEvent + change(arg1: any, arg2?: any): ConfigurationChangeEvent { + if (arg1 instanceof ConfigurationChangeEvent) { + this.changedConfiguration = this.changedConfiguration.merge(arg1.changedConfiguration); + for (const resource of arg1.resources) { + let changedConfigurationByResource = this.getOrSetChangedConfigurationForResource(resource); + changedConfigurationByResource = changedConfigurationByResource.merge(arg1.changedConfigurationByResource.get(resource)); + this.changedConfigurationByResource.set(resource, changedConfigurationByResource); + } + } + return this.changeWithKeys(arg1, arg2); + } + + telemetryData(source: ConfigurationTarget, sourceConfig: any): ConfigurationChangeEvent { + this._source = source; + this._sourceConfig = sourceConfig; + return this; + } + + get affectedKeys(): string[] { + const keys = [...this.changedConfiguration.keys]; + this.changedConfigurationByResource.forEach(model => keys.push(...model.keys)); + return keys; + } + + get source(): ConfigurationTarget { + return this._source; + } + + get sourceConfig(): any { + return this._sourceConfig; + } + + affectsConfiugration(config: string): boolean + affectsConfiugration(config: string, overrideIdentifier: string): boolean + affectsConfiugration(config: string, resource: URI): boolean + affectsConfiugration(config: string, overrideIdentifier: string, resource: URI): boolean + affectsConfiugration(config: string, arg1?: any, arg2?: any): boolean { + let resource = arg1 instanceof URI ? arg1 : arg2 instanceof URI ? arg2 : void 0; + let overrideIdentifier = resource && arg1 !== resource ? arg1 : void 0; + let model = resource ? this.changedConfigurationByResource.get(resource) : this.changedConfiguration; + if (model) { + + if (overrideIdentifier) { + return model.overrides.some(override => override.identifiers.indexOf(overrideIdentifier) !== -1); + } + + let changedKeysTree = model.contents; + let requestedTree = toValuesTree({ [config]: true }, () => { }); + + let key; + while (typeof requestedTree === 'object' && (key = Object.keys(requestedTree)[0])) { // Only one key should present, since we added only one property + changedKeysTree = changedKeysTree[key]; + if (!changedKeysTree) { + return false; // Requested tree is not found + } + requestedTree = requestedTree[key]; + } + return true; + } + return false; + } + + private changeWithKeys(keys: string[], resource?: URI): ConfigurationChangeEvent { + let changedConfiguration = resource ? this.getOrSetChangedConfigurationForResource(resource) : this.changedConfiguration; + for (const key of keys) { + if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + changedConfiguration.setValueInOverrides(overrideIdentifierFromKey(key), 'key'/* any key */, true); + } else { + changedConfiguration.setValue(key, true); + } + } + return this; + } + + private getOrSetChangedConfigurationForResource(resource: URI): ConfigurationModel { + let changedConfigurationByResource = this.changedConfigurationByResource.get(resource); + if (!changedConfigurationByResource) { + changedConfigurationByResource = new ConfigurationModel(); + this.changedConfigurationByResource.set(resource, changedConfigurationByResource); + this.resources.push(resource); + } + return changedConfigurationByResource; + } } \ No newline at end of file diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index 893af01e558..035060db7de 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -8,8 +8,8 @@ import { ConfigWatcher } from 'vs/base/node/config'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, toConfigurationUpdateEvent, compare } from 'vs/platform/configuration/common/configuration'; -import { CustomConfigurationModel, DefaultConfigurationModel, ConfigurationModel, Configuration } from 'vs/platform/configuration/common/configurationModels'; +import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, compare } from 'vs/platform/configuration/common/configuration'; +import { CustomConfigurationModel, DefaultConfigurationModel, ConfigurationModel, Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import Event, { Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -132,7 +132,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe } private trigger(keys: string[], source: ConfigurationTarget): void { - this._onDidUpdateConfiguration.fire(toConfigurationUpdateEvent(keys, source, this.getTargetConfiguration(source))); + this._onDidUpdateConfiguration.fire(new ConfigurationChangeEvent().change(keys).telemetryData(source, this.getTargetConfiguration(source))); } private getTargetConfiguration(target: ConfigurationTarget): any { diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index 18b677b7f61..4cba52bfbcc 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -55,7 +55,7 @@ export class StartDebugActionItem extends EventEmitter implements IActionItem { private registerListeners(): void { this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => { - if (e.hasSectionChanged('launch')) { + if (e.affectsConfiugration('launch')) { this.updateOptions(); } })); diff --git a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts index effbdb62019..11538b93dad 100644 --- a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts +++ b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts @@ -127,7 +127,7 @@ export class WatermarkContribution implements IWorkbenchContribution { } }); this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => { - if (e.hasKeyChanged(WORKBENCH_TIPS_ENABLED_KEY)) { + if (e.affectsConfiugration(WORKBENCH_TIPS_ENABLED_KEY)) { const enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); if (enabled !== this.enabled) { this.enabled = enabled; diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 1223ae638a7..b4518c06965 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -5,8 +5,8 @@ 'use strict'; import { clone, equals } from 'vs/base/common/objects'; -import { compare, toValuesTree } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationModel, Configuration as BaseConfiguration, CustomConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { compare, toValuesTree, IConfigurationChangeEvent, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel, Configuration as BaseConfiguration, CustomConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, IConfigurationPropertySchema, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; @@ -201,10 +201,9 @@ export class Configuration extends BaseConfiguration { this.merge(); } - updateUserConfiguration(user: ConfigurationModel): string[] { - let changedKeys = []; + updateUserConfiguration(user: ConfigurationModel): ConfigurationChangeEvent { const { added, updated, removed } = compare(this._user, user); - changedKeys = [...added, ...updated, ...removed]; + let changedKeys = [...added, ...updated, ...removed]; if (changedKeys.length) { const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._memoryConfiguration, this._memoryConfigurationByResource, this._workspace); @@ -212,15 +211,13 @@ export class Configuration extends BaseConfiguration { this.merge(); changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this.getValue(key))); - return changedKeys; } - return []; + return new ConfigurationChangeEvent().change(changedKeys); } - updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): string[] { - let changedKeys = []; + updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): ConfigurationChangeEvent { const { added, updated, removed } = compare(this._workspaceConfiguration, workspaceConfiguration); - changedKeys = [...added, ...updated, ...removed]; + let changedKeys = [...added, ...updated, ...removed]; if (changedKeys.length) { const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._memoryConfiguration, this._memoryConfigurationByResource, this._workspace); @@ -228,18 +225,16 @@ export class Configuration extends BaseConfiguration { this.merge(); changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this.getValue(key))); - return changedKeys; } - return []; + return new ConfigurationChangeEvent().change(changedKeys); } - updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel): string[] { + updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel): ConfigurationChangeEvent { const currentFolderConfiguration = this.folders.get(resource); if (currentFolderConfiguration) { - let changedKeys = []; const { added, updated, removed } = compare(currentFolderConfiguration, configuration); - changedKeys = [...added, ...updated, ...removed]; + let changedKeys = [...added, ...updated, ...removed]; if (changedKeys.length) { const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._memoryConfiguration, this._memoryConfigurationByResource, this._workspace); @@ -247,29 +242,62 @@ export class Configuration extends BaseConfiguration { this.mergeFolder(resource); changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key, { resource }), this.getValue(key, { resource }))); - return changedKeys; } - return []; + return new ConfigurationChangeEvent().change(changedKeys, resource); } this.folders.set(resource, configuration); this.mergeFolder(resource); - return configuration.keys; + return new ConfigurationChangeEvent().change(configuration.keys, resource); } - deleteFolderConfiguration(folder: URI): string[] { + deleteFolderConfiguration(folder: URI): ConfigurationChangeEvent { if (this._workspace && this._workspace.folders.length > 0 && this._workspace.folders[0].uri.toString() === folder.toString()) { // Do not remove workspace configuration - return []; + return new ConfigurationChangeEvent(); } const keys = this.folders.get(folder).keys; this.folders.delete(folder); this._foldersConsolidatedConfigurations.delete(folder); - return keys; + return new ConfigurationChangeEvent().change(keys, folder); } getFolderConfigurationModel(folder: URI): FolderConfigurationModel { return this.folders.get(folder); } +} + +export class WorkspaceConfigurationChangeEvent implements IConfigurationChangeEvent { + + constructor(private configurationChangeEvent: ConfigurationChangeEvent, private workspace: Workspace) { + } + + get affectedKeys(): string[] { + return this.configurationChangeEvent.affectedKeys; + } + + get source(): ConfigurationTarget { + return this.configurationChangeEvent.source; + } + + get sourceConfig(): any { + return this.configurationChangeEvent.sourceConfig; + } + + affectsConfiugration(config: string, arg1?: any, arg2?: any): boolean { + if (this.configurationChangeEvent.affectsConfiugration(config, arg1, arg2)) { + return true; + } + + let resource = arg1 instanceof URI ? arg1 : arg2 instanceof URI ? arg2 : void 0; + if (resource) { + let workspaceFolder = this.workspace.getFolder(resource); + if (workspaceFolder) { + return this.configurationChangeEvent.affectsConfiugration(config, resource && arg1 !== resource ? arg1 : void 0, resource); + } + } + + return false; + } } \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 6edf38685bb..a57b41a33f6 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -23,9 +23,9 @@ import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files import { isLinux } from 'vs/base/common/platform'; import { ConfigWatcher } from 'vs/base/node/config'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { CustomConfigurationModel, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, toConfigurationUpdateEvent, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; -import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; +import { CustomConfigurationModel, ConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; +import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel, Configuration, WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { ConfigurationService as GlobalConfigurationService, isConfigurationOverrides } from 'vs/platform/configuration/node/configurationService'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -37,7 +37,6 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import product from 'vs/platform/node/product'; import pkg from 'vs/platform/node/package'; -import { distinct, flatten } from 'vs/base/common/arrays'; import { IConfigurationEditingService, ConfigurationTarget as EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; @@ -327,7 +326,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat // TODO: compare with old values?? const keys = this._configuration.keys(); - this.triggerConfigurationChange([...keys.default, ...keys.user, ...keys.workspace, ...keys.workspaceFolder], ConfigurationTarget.WORKSPACE); + this.triggerConfigurationChange(new ConfigurationChangeEvent().change([...keys.default, ...keys.user, ...keys.workspace, ...keys.workspaceFolder]), ConfigurationTarget.WORKSPACE); }); } @@ -363,7 +362,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat if (e.source === ConfigurationTarget.DEFAULT) { this.workspace.folders.forEach(folder => this._configuration.getFolderConfigurationModel(folder.uri).update()); this._configuration.updateDefaultConfiguration(this.baseConfigurationService.configuration.defaults); - this._onDidUpdateConfiguration.fire(e); + this.triggerConfigurationChange(new ConfigurationChangeEvent().change(e.affectedKeys), e.source); } else { let keys = this._configuration.updateUserConfiguration(this.baseConfigurationService.configuration.user); this.triggerConfigurationChange(keys, e.source); @@ -373,18 +372,18 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat private onWorkspaceConfigurationChanged(): TPromise { if (this.workspace && this.workspace.configuration && this._configuration) { - const changedWorkspaceKeys = this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration); + const workspaceConfigurationChangeEvent = this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration); let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.workspaceConfigurationModel.folders, URI.file(paths.dirname(this.workspace.configuration.fsPath))); const changes = this.compareFolders(this.workspace.folders, configuredFolders); if (changes.added.length || changes.removed.length || changes.changed.length) { this.workspace.folders = configuredFolders; return this.onFoldersChanged() - .then(changedFolderKeys => { - this.triggerConfigurationChange([...changedFolderKeys, ...changedWorkspaceKeys], ConfigurationTarget.WORKSPACE_FOLDER); + .then(foldersConfigurationChangeEvent => { + this.triggerConfigurationChange(foldersConfigurationChangeEvent.change(workspaceConfigurationChangeEvent), ConfigurationTarget.WORKSPACE_FOLDER); this._onDidChangeWorkspaceFolders.fire(changes); }); } else { - this.triggerConfigurationChange(changedWorkspaceKeys, ConfigurationTarget.WORKSPACE); + this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE); } } return TPromise.as(null); @@ -395,8 +394,11 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat // handle file event for each folder this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event) // Update folder configuration if handled - .then(folderConfiguration => folderConfiguration ? this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration) : [])) - ).then(changedKeys => this.triggerConfigurationChange(flatten(changedKeys), ConfigurationTarget.WORKSPACE_FOLDER)); + .then(folderConfiguration => folderConfiguration ? this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration) : new ConfigurationChangeEvent())) + ).then(changeEvents => { + const consolidateChangeEvent = changeEvents.reduce((consolidated, e) => consolidated.change(e), new ConfigurationChangeEvent()); + this.triggerConfigurationChange(consolidateChangeEvent, ConfigurationTarget.WORKSPACE_FOLDER); + }); } private onSingleFolderFileChanges(event: FileChangesEvent): TPromise { @@ -426,14 +428,14 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat }); } - private onFoldersChanged(): TPromise { - let changedKeys = []; + private onFoldersChanged(): TPromise { + let changeEvent = new ConfigurationChangeEvent(); // Remove the configurations of deleted folders for (const key of this.cachedFolderConfigs.keys()) { if (!this.workspace.folders.filter(folder => folder.uri.toString() === key.toString())[0]) { this.cachedFolderConfigs.delete(key); - changedKeys.push(...this._configuration.deleteFolderConfiguration(key)); + changeEvent = changeEvent.change(this._configuration.deleteFolderConfiguration(key)); } } @@ -442,12 +444,12 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return this.loadFolderConfigurations(toInitialize) .then(folderConfigurations => { folderConfigurations.forEach((folderConfiguration, index) => { - changedKeys.push(...this._configuration.updateFolderConfiguration(toInitialize[index].uri, folderConfiguration)); + changeEvent = changeEvent.change(this._configuration.updateFolderConfiguration(toInitialize[index].uri, folderConfiguration)); }); - return changedKeys; + return changeEvent; }); } - return TPromise.as(changedKeys); + return TPromise.as(changeEvent); } private loadFolderConfigurations(folders: IWorkspaceFolder[]): TPromise { @@ -470,7 +472,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat if (target === ConfigurationTarget.MEMORY) { this._configuration.updateValue(key, value, overrides); - this.triggerConfigurationChange([key], target); + this.triggerConfigurationChange(new ConfigurationChangeEvent().change(overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier)] : [key], overrides.resource), target); return TPromise.as(null); } @@ -531,9 +533,10 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat } } - private triggerConfigurationChange(keys: string[], target: ConfigurationTarget): void { - if (keys.length) { - this._onDidUpdateConfiguration.fire(toConfigurationUpdateEvent(distinct(keys), target, this.getTargetConfiguration(target))); + private triggerConfigurationChange(configurationEvent: ConfigurationChangeEvent, target: ConfigurationTarget): void { + if (configurationEvent.affectedKeys.length) { + configurationEvent.telemetryData(target, this.getTargetConfiguration(target)); + this._onDidUpdateConfiguration.fire(new WorkspaceConfigurationChangeEvent(configurationEvent, this.workspace)); } } From 0d3c0dcb4a19ceda197ca4a7c58118aa585c0126 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 13 Oct 2017 11:53:25 +0200 Subject: [PATCH 17/28] Use all keys configuration change event when configuration is initialized --- .../platform/configuration/common/configurationModels.ts | 8 ++++++++ .../services/configuration/node/configurationService.ts | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index f86a10ee7d1..68a3f544f61 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -415,6 +415,14 @@ export class Configuration { } } +export class AllKeysConfigurationChangeEvent implements IConfigurationChangeEvent { + + constructor(readonly affectedKeys: string[], readonly source: ConfigurationTarget, readonly sourceConfig: any) { } + + affectsConfiugration: () => true; + +} + export class ConfigurationChangeEvent implements IConfigurationChangeEvent { private changedConfiguration: ConfigurationModel = new ConfigurationModel(); diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index a57b41a33f6..3cccaa1e6b2 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -23,7 +23,7 @@ import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files import { isLinux } from 'vs/base/common/platform'; import { ConfigWatcher } from 'vs/base/node/config'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { CustomConfigurationModel, ConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { CustomConfigurationModel, ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel, Configuration, WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; @@ -326,7 +326,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat // TODO: compare with old values?? const keys = this._configuration.keys(); - this.triggerConfigurationChange(new ConfigurationChangeEvent().change([...keys.default, ...keys.user, ...keys.workspace, ...keys.workspaceFolder]), ConfigurationTarget.WORKSPACE); + this._onDidUpdateConfiguration.fire(new AllKeysConfigurationChangeEvent([...keys.default, ...keys.user, ...keys.workspace, ...keys.workspaceFolder], ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); }); } From 016cef69120bded64ed0e90cfd0bfa1596688ab2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 13 Oct 2017 12:23:41 +0200 Subject: [PATCH 18/28] Smart merge of override contents --- .../common/configurationModels.ts | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 68a3f544f61..7e5751fcdd4 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -60,17 +60,33 @@ export class ConfigurationModel implements IConfiguraionModel { } public override(identifier: string): ConfigurationModel { - const result = new ConfigurationModel(); - const contents = objects.clone(this.contents); - if (this._overrides) { - for (const override of this._overrides) { - if (override.identifiers.indexOf(identifier) !== -1) { - merge(contents, override.contents, true); + const overrideContents = this.getContentsForOverrideIdentifer(identifier); + + if (!overrideContents) { + // If there are no overrides, use base contents + return new ConfigurationModel(this._contents); + } + + let contents = {}; + for (const key of Object.keys(this._contents)) { + + let contentsForKey = this._contents[key]; + let overrideContentsForKey = overrideContents[key]; + + // If there are override contents for the key clone and merge otherwise use base contents + if (overrideContentsForKey) { + // Clone and merge only if base contents is of type object otherwise just override + if (typeof contentsForKey === 'object') { + contentsForKey = objects.clone(contents[key]); + merge(contentsForKey, overrideContentsForKey, true); + } else { + contentsForKey = overrideContentsForKey; } } + + contents[key] = contentsForKey; } - result._contents = contents; - return result; + return new ConfigurationModel(contents); } public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { @@ -93,6 +109,15 @@ export class ConfigurationModel implements IConfiguraionModel { } source._overrides = overrides; } + + private getContentsForOverrideIdentifer(identifier: string): any { + for (const override of this._overrides) { + if (override.identifiers.indexOf(identifier) !== -1) { + return override.contents; + } + } + return null; + } } export class DefaultConfigurationModel extends ConfigurationModel { From d17e3ddbc364e43fba80f526b0a3d140e0863bd9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 13 Oct 2017 17:20:15 +0200 Subject: [PATCH 19/28] Write tests for ConfigurationModel --- .../configuration/common/configuration.ts | 24 ++ .../common/configurationModels.ts | 51 ++- .../test/common/configuration.model.test.ts | 87 ---- .../test/common/configuration.test.ts | 89 +++- .../test/common/configurationModel.test.ts | 148 ------- .../test/common/configurationModels.test.ts | 399 ++++++++++++++++++ .../node/configurationService.ts | 4 +- 7 files changed, 546 insertions(+), 256 deletions(-) delete mode 100644 src/vs/platform/configuration/test/common/configuration.model.test.ts delete mode 100644 src/vs/platform/configuration/test/common/configurationModel.test.ts create mode 100644 src/vs/platform/configuration/test/common/configurationModels.test.ts diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index a2132efea12..8a1daa65929 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -151,6 +151,30 @@ export function addToValueTree(settingsTreeRoot: any, key: string, value: any, c } } +export function removeFromValueTree(valueTree: any, key: string): void { + const segments = key.split('.'); + doRemoveFromValueTree(valueTree, segments); +} + +function doRemoveFromValueTree(valueTree: any, segments: string[]): void { + const first = segments.shift(); + if (segments.length === 0) { + // Reached last segment + delete valueTree[first]; + return; + } + + if (Object.keys(valueTree).indexOf(first) !== -1) { + const value = valueTree[first]; + if (typeof value === 'object' && !Array.isArray(value)) { + doRemoveFromValueTree(value, segments); + if (Object.keys(value).length === 0) { + delete valueTree[first]; + } + } + } +} + /** * A helper function to get the configuration value with a specific settings path (e.g. config.some.setting) */ diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 7e5751fcdd4..7acaca42472 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -11,7 +11,7 @@ import * as objects from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; -import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfiguraionModel, merge, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfiguraionModel, merge, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget, removeFromValueTree } from 'vs/platform/configuration/common/configuration'; import { Workspace } from 'vs/platform/workspace/common/workspace'; export class ConfigurationModel implements IConfiguraionModel { @@ -36,9 +36,13 @@ export class ConfigurationModel implements IConfiguraionModel { } public setValue(key: string, value: any) { + this.addKey(key); addToValueTree(this._contents, key, value, e => { throw new Error(e); }); - if (this._keys.indexOf(key) === -1) { - this._keys.push(key); + } + + public removeValue(key: string): void { + if (this.removeKey(key)) { + removeFromValueTree(this._contents, key); } } @@ -51,33 +55,25 @@ export class ConfigurationModel implements IConfiguraionModel { addToValueTree(override.contents, key, value, e => { throw new Error(e); }); } - public removeValue(key: string) { - // Remove key from the value tree - const index = this._keys.indexOf(key); - if (index !== -1) { - this._keys.splice(index, 1); - } - } - public override(identifier: string): ConfigurationModel { const overrideContents = this.getContentsForOverrideIdentifer(identifier); - if (!overrideContents) { - // If there are no overrides, use base contents + if (!overrideContents || typeof overrideContents !== 'object' || !Object.keys(overrideContents).length) { + // If there are no valid overrides, use base contents return new ConfigurationModel(this._contents); } let contents = {}; - for (const key of Object.keys(this._contents)) { + for (const key of arrays.distinct([...Object.keys(this._contents), ...Object.keys(overrideContents)])) { let contentsForKey = this._contents[key]; let overrideContentsForKey = overrideContents[key]; - // If there are override contents for the key clone and merge otherwise use base contents + // If there are override contents for the key, clone and merge otherwise use base contents if (overrideContentsForKey) { - // Clone and merge only if base contents is of type object otherwise just override - if (typeof contentsForKey === 'object') { - contentsForKey = objects.clone(contents[key]); + // Clone and merge only if base contents and override contents are of type object otherwise just override + if (typeof contentsForKey === 'object' && typeof overrideContentsForKey === 'object') { + contentsForKey = objects.clone(contentsForKey); merge(contentsForKey, overrideContentsForKey, true); } else { contentsForKey = overrideContentsForKey; @@ -118,6 +114,25 @@ export class ConfigurationModel implements IConfiguraionModel { } return null; } + + private addKey(key: string): void { + let index = this._keys.length; + for (let i = 0; i < index; i++) { + if (key.indexOf(this._keys[i]) === 0) { + index = i; + } + } + this._keys.splice(index, 1, key); + } + + private removeKey(key: string): boolean { + let index = this._keys.indexOf(key); + if (index !== -1) { + this._keys.splice(index, 1); + return true; + } + return false; + } } export class DefaultConfigurationModel extends ConfigurationModel { diff --git a/src/vs/platform/configuration/test/common/configuration.model.test.ts b/src/vs/platform/configuration/test/common/configuration.model.test.ts deleted file mode 100644 index ea0266bd500..00000000000 --- a/src/vs/platform/configuration/test/common/configuration.model.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as assert from 'assert'; -import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; - -suite('Configuration', () => { - - suiteSetup(() => { - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': 'a', - 'order': 1, - 'title': 'a', - 'type': 'object', - 'properties': { - 'a': { - 'description': 'a', - 'type': 'boolean', - 'default': true, - 'overridable': true - } - } - }); - }); - - test('simple merge', () => { - let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); - let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); - }); - - test('recursive merge', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 } }); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - }); - - test('simple merge overrides', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': 2 } }]); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'b': 2 } }]); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 } }]); - }); - - test('recursive merge overrides', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } } }]); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } } }]); - }); - - test('merge ignore keys', () => { - let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); - let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); - let result = base.merge(add); - assert.deepEqual(result.keys, []); - }); - - test('Test contents while getting an existing property', () => { - let testObject = new ConfigurationModel({ 'a': 1 }); - assert.deepEqual(testObject.getSectionContents('a'), 1); - - testObject = new ConfigurationModel({ 'a': { 'b': 1 } }); - assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 }); - }); - - test('Test contents are undefined for non existing properties', () => { - const testObject = new ConfigurationModel({ awesome: true }); - - assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); - }); - - test('Test override gives all content merged with overrides', () => { - const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, [], [{ identifiers: ['b'], contents: { 'a': 2 } }]); - - assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1 }); - }); -}); \ No newline at end of file diff --git a/src/vs/platform/configuration/test/common/configuration.test.ts b/src/vs/platform/configuration/test/common/configuration.test.ts index 11024298132..456df853e01 100644 --- a/src/vs/platform/configuration/test/common/configuration.test.ts +++ b/src/vs/platform/configuration/test/common/configuration.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { merge } from 'vs/platform/configuration/common/configuration'; +import { merge, removeFromValueTree } from 'vs/platform/configuration/common/configuration'; suite('Configuration', () => { @@ -18,5 +18,92 @@ suite('Configuration', () => { assert.deepEqual(base, { 'a': 1, 'b': 2, 'c': 4 }); }); + test('removeFromValueTree: remove a non existing key', () => { + let target = { 'a': { 'b': 2 } }; + + removeFromValueTree(target, 'c'); + + assert.deepEqual(target, { 'a': { 'b': 2 } }); + }); + + test('removeFromValueTree: remove a multi segmented key from an object that has only sub sections of the key', () => { + let target = { 'a': { 'b': 2 } }; + + removeFromValueTree(target, 'a.b.c'); + + assert.deepEqual(target, { 'a': { 'b': 2 } }); + }); + + test('removeFromValueTree: remove a single segemented key', () => { + let target = { 'a': 1 }; + + removeFromValueTree(target, 'a'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a single segemented key when its value is undefined', () => { + let target = { 'a': void 0 }; + + removeFromValueTree(target, 'a'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a multi segemented key when its value is undefined', () => { + let target = { 'a': { 'b': 1 } }; + + removeFromValueTree(target, 'a.b'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a multi segemented key when its value is array', () => { + let target = { 'a': { 'b': [1] } }; + + removeFromValueTree(target, 'a.b'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a multi segemented key first segment value is array', () => { + let target = { 'a': [1] }; + + removeFromValueTree(target, 'a.0'); + + assert.deepEqual(target, { 'a': [1] }); + }); + + test('removeFromValueTree: remove when key is the first segmenet', () => { + let target = { 'a': { 'b': 1 } }; + + removeFromValueTree(target, 'a'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a multi segemented key when the first node has more values', () => { + let target = { 'a': { 'b': { 'c': 1 }, 'd': 1 } }; + + removeFromValueTree(target, 'a.b.c'); + + assert.deepEqual(target, { 'a': { 'd': 1 } }); + }); + + test('removeFromValueTree: remove a multi segemented key when in between node has more values', () => { + let target = { 'a': { 'b': { 'c': { 'd': 1 }, 'd': 1 } } }; + + removeFromValueTree(target, 'a.b.c.d'); + + assert.deepEqual(target, { 'a': { 'b': { 'd': 1 } } }); + }); + + test('removeFromValueTree: remove a multi segemented key when the last but one node has more values', () => { + let target = { 'a': { 'b': { 'c': 1, 'd': 1 } } }; + + removeFromValueTree(target, 'a.b.c'); + + assert.deepEqual(target, { 'a': { 'b': { 'd': 1 } } }); + }); }); \ No newline at end of file diff --git a/src/vs/platform/configuration/test/common/configurationModel.test.ts b/src/vs/platform/configuration/test/common/configurationModel.test.ts deleted file mode 100644 index b79b8cbadcf..00000000000 --- a/src/vs/platform/configuration/test/common/configurationModel.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as assert from 'assert'; -import { CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; - -suite('Configuration', () => { - - suiteSetup(() => { - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': 'a', - 'order': 1, - 'title': 'a', - 'type': 'object', - 'properties': { - 'a': { - 'description': 'a', - 'type': 'boolean', - 'default': true, - 'overridable': true - } - } - }); - }); - - test('simple merge using models', () => { - let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); - let add = new CustomConfigurationModel(JSON.stringify({ 'a': 3, 'c': 4 })); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); - }); - - test('simple merge with an undefined contents', () => { - let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); - let add = new CustomConfigurationModel(null); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); - - base = new CustomConfigurationModel(null); - add = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); - result = base.merge(add); - assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); - - base = new CustomConfigurationModel(null); - add = new CustomConfigurationModel(null); - result = base.merge(add); - assert.deepEqual(result.contents, {}); - }); - - test('Recursive merge using config models', () => { - let base = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); - let add = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 2 } })); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - }); - - test('Test contents while getting an existing property', () => { - let testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1 })); - assert.deepEqual(testObject.getSectionContents('a'), 1); - - testObject = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); - assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 }); - }); - - test('Test contents are undefined for non existing properties', () => { - const testObject = new CustomConfigurationModel(JSON.stringify({ - awesome: true - })); - - assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); - }); - - test('Test contents are undefined for undefined config', () => { - const testObject = new CustomConfigurationModel(null); - - assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); - }); - - test('Test configWithOverrides gives all content merged with overrides', () => { - const testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } })); - - assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } }); - }); - - test('Test configWithOverrides gives empty contents', () => { - const testObject = new CustomConfigurationModel(null); - - assert.deepEqual(testObject.override('b').contents, {}); - }); - - test('Test update with empty data', () => { - const testObject = new CustomConfigurationModel(); - testObject.update(''); - - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); - - testObject.update(null); - - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); - - testObject.update(undefined); - - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); - }); - - test('Test registering the same property again', () => { - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': 'a', - 'order': 1, - 'title': 'a', - 'type': 'object', - 'properties': { - 'a': { - 'description': 'a', - 'type': 'boolean', - 'default': false, - } - } - }); - assert.equal(true, new DefaultConfigurationModel().getSectionContents('a')); - }); - - test('Test registering the language property', () => { - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': '[a]', - 'order': 1, - 'title': 'a', - 'type': 'object', - 'properties': { - '[a]': { - 'description': 'a', - 'type': 'boolean', - 'default': false, - } - } - }); - assert.equal(undefined, new DefaultConfigurationModel().getSectionContents('[a]')); - }); - -}); \ No newline at end of file diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts new file mode 100644 index 00000000000..b4cc2a1d476 --- /dev/null +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -0,0 +1,399 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import { ConfigurationModel, CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; + +suite('ConfigurationModel', () => { + + test('setValue for a key that has no sections and not defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 } }, ['a.b']); + + testObject.setValue('f', 1); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'f']); + }); + + test('setValue for a key that has no sections and defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f']); + + testObject.setValue('f', 3); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'f': 3 }); + assert.deepEqual(testObject.keys, ['a.b', 'f']); + }); + + test('setValue for a key that has sections and not defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f']); + + testObject.setValue('b.c', 1); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'b': { 'c': 1 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'f', 'b.c']); + }); + + test('setValue for a key that has sections and defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 }, 'b': { 'c': 1 }, 'f': 1 }, ['a.b', 'b.c', 'f']); + + testObject.setValue('b.c', 3); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'b': { 'c': 3 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'b.c', 'f']); + }); + + test('setValue for a key that has sections and sub section not defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f']); + + testObject.setValue('a.c', 1); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1, 'c': 1 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'f', 'a.c']); + }); + + test('setValue for a key that has sections and sub section defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1, 'c': 1 }, 'f': 1 }, ['a.b', 'a.c', 'f']); + + testObject.setValue('a.c', 3); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1, 'c': 3 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'a.c', 'f']); + }); + + test('setValue for a key that has sections and last section is added', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': {} }, 'f': 1 }, ['a.b', 'f']); + + testObject.setValue('a.b.c', 1); + + assert.deepEqual(testObject.contents, { 'a': { 'b': { 'c': 1 } }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b.c', 'f']); + }); + + test('removeValue: remove a non existing key', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b']); + + testObject.removeValue('a.b.c'); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 2 } }); + assert.deepEqual(testObject.keys, ['a.b']); + }); + + test('removeValue: remove a single segemented key', () => { + let testObject = new ConfigurationModel({ 'a': 1 }, ['a']); + + testObject.removeValue('a'); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + }); + + test('removeValue: remove a multi segemented key', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 } }, ['a.b']); + + testObject.removeValue('a.b'); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + }); + + test('setValueInOverrides adds to overrides if does not exist', () => { + let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a']); + + testObject.setValueInOverrides('or', 'a', 2); + + assert.deepEqual(testObject.overrides[0].contents, { 'a': 2 }); + assert.deepEqual(testObject.override('or').contents, { 'a': 2, 'b': 1 }); + }); + + test('setValueInOverrides adds to overrides if exist', () => { + let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a'], [{ identifiers: ['or'], contents: { 'a': 2 } }]); + + testObject.setValueInOverrides('or', 'a', 3); + + assert.deepEqual(testObject.overrides[0].contents, { 'a': 3 }); + assert.deepEqual(testObject.override('or').contents, { 'a': 3, 'b': 1 }); + }); + + test('setValueInOverrides adds a nested key to overrides if exist', () => { + let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a'], [{ identifiers: ['or'], contents: { 'a': { 'c': 1 } } }]); + + testObject.setValueInOverrides('or', 'a.c', 2); + + assert.deepEqual(testObject.overrides[0].contents, { 'a': { 'c': 2 } }); + assert.deepEqual(testObject.override('or').contents, { 'a': { 'c': 2 }, 'b': 1 }); + }); + + test('setValueInOverrides adds new overrides if exist', () => { + let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a'], [{ identifiers: ['or1'], contents: { 'a': 2 } }]); + + testObject.setValueInOverrides('or2', 'b', 2); + + assert.deepEqual(testObject.overrides[0].contents, { 'a': 2 }); + assert.deepEqual(testObject.overrides[1].contents, { 'b': 2 }); + assert.deepEqual(testObject.override('or1').contents, { 'a': 2, 'b': 1 }); + assert.deepEqual(testObject.override('or2').contents, { 'a': 1, 'b': 2 }); + }); + + test('get overriding configuration model for an existing identifier', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': 1 }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); + }); + + test('get overriding configuration model for an identifier that does not exist', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': 1 }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); + + assert.deepEqual(testObject.override('xyz').contents, { 'a': { 'b': 1 }, 'f': 1 }); + }); + + test('get overriding configuration when one of the keys does not exist in base', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': 1 }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'g': 1 } }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1, 'g': 1 }); + }); + + test('get overriding configuration when one of the key in base is not of object type', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': 1 }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': { 'g': 1 } } }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': { 'g': 1 } }); + }); + + test('get overriding configuration when one of the key in overriding contents is not of object type', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': 1 } }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); + }); + + test('get overriding configuration if the value of overriding identifier is not object', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], + [{ identifiers: ['c'], contents: 'abc' }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); + }); + + test('get overriding configuration if the value of overriding identifier is an empty object', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], + [{ identifiers: ['c'], contents: {} }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); + }); + + test('simple merge', () => { + let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); + let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); + let result = base.merge(add); + + assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); + }); + + test('recursive merge', () => { + let base = new ConfigurationModel({ 'a': { 'b': 1 } }); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }); + let result = base.merge(add); + + assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); + assert.deepEqual(result.getSectionContents('a'), { 'b': 2 }); + }); + + test('simple merge overrides', () => { + let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': 2 } }]); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'b': 2 } }]); + let result = base.merge(add); + + assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); + assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 } }]); + assert.deepEqual(result.override('c').contents, { 'a': 2, 'b': 2 }); + }); + + test('recursive merge overrides', () => { + let base = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } } }]); + let result = base.merge(add); + + assert.deepEqual(result.contents, { 'a': { 'b': 2 }, 'f': 1 }); + assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } } }]); + assert.deepEqual(result.override('c').contents, { 'a': { 'b': 2, 'd': 1, 'e': 2 }, 'f': 1 }); + }); + + test('merge ignore keys', () => { + let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); + let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); + let result = base.merge(add); + assert.deepEqual(result.keys, []); + }); + + test('Test contents while getting an existing property', () => { + let testObject = new ConfigurationModel({ 'a': 1 }); + assert.deepEqual(testObject.getSectionContents('a'), 1); + + testObject = new ConfigurationModel({ 'a': { 'b': 1 } }); + assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 }); + }); + + test('Test contents are undefined for non existing properties', () => { + const testObject = new ConfigurationModel({ awesome: true }); + + assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); + }); + + test('Test override gives all content merged with overrides', () => { + const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, [], [{ identifiers: ['b'], contents: { 'a': 2 } }]); + + assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1 }); + }); +}); + +suite('CustomConfigurationModel', () => { + + suiteSetup(() => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'a', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + 'a': { + 'description': 'a', + 'type': 'boolean', + 'default': true, + 'overridable': true + } + } + }); + }); + + test('simple merge using models', () => { + let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); + let add = new CustomConfigurationModel(JSON.stringify({ 'a': 3, 'c': 4 })); + let result = base.merge(add); + assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); + }); + + test('simple merge with an undefined contents', () => { + let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); + let add = new CustomConfigurationModel(null); + let result = base.merge(add); + assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); + + base = new CustomConfigurationModel(null); + add = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); + result = base.merge(add); + assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); + + base = new CustomConfigurationModel(null); + add = new CustomConfigurationModel(null); + result = base.merge(add); + assert.deepEqual(result.contents, {}); + }); + + test('Recursive merge using config models', () => { + let base = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); + let add = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 2 } })); + let result = base.merge(add); + assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); + }); + + test('Test contents while getting an existing property', () => { + let testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1 })); + assert.deepEqual(testObject.getSectionContents('a'), 1); + + testObject = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); + assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 }); + }); + + test('Test contents are undefined for non existing properties', () => { + const testObject = new CustomConfigurationModel(JSON.stringify({ + awesome: true + })); + + assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); + }); + + test('Test contents are undefined for undefined config', () => { + const testObject = new CustomConfigurationModel(null); + + assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); + }); + + test('Test configWithOverrides gives all content merged with overrides', () => { + const testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } })); + + assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } }); + }); + + test('Test configWithOverrides gives empty contents', () => { + const testObject = new CustomConfigurationModel(null); + + assert.deepEqual(testObject.override('b').contents, {}); + }); + + test('Test update with empty data', () => { + const testObject = new CustomConfigurationModel(); + testObject.update(''); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + + testObject.update(null); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + + testObject.update(undefined); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + }); + + test('Test registering the same property again', () => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'a', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + 'a': { + 'description': 'a', + 'type': 'boolean', + 'default': false, + } + } + }); + assert.equal(true, new DefaultConfigurationModel().getSectionContents('a')); + }); + + test('Test registering the language property', () => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': '[a]', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + '[a]': { + 'description': 'a', + 'type': 'boolean', + 'default': false, + } + } + }); + assert.equal(undefined, new DefaultConfigurationModel().getSectionContents('[a]')); + }); + +}); \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 3cccaa1e6b2..f2ed3e415dc 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -322,8 +322,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat const folderConfigurationModels = new StrictResourceMap(); folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); - this._configuration = new Configuration(this.baseConfigurationService.configuration.defaults, this.baseConfigurationService.configuration.user, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new StrictResourceMap(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: @Sandy Avoid passing null - // TODO: compare with old values?? + this._configuration = new Configuration(this.baseConfigurationService.configuration.defaults, this.baseConfigurationService.configuration.user, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new StrictResourceMap(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: Sandy Avoid passing null + // TODO Sandy: compare with old values?? const keys = this._configuration.keys(); this._onDidUpdateConfiguration.fire(new AllKeysConfigurationChangeEvent([...keys.default, ...keys.user, ...keys.workspace, ...keys.workspaceFolder], ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); From 60a493cdc2ebaba842f229563842625054f0e6c2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Oct 2017 10:54:36 +0200 Subject: [PATCH 20/28] Tests for configuration change event --- .../configuration/common/configuration.ts | 5 +- .../common/configurationModels.ts | 93 ++++++++------ .../test/common/configurationModels.test.ts | 116 +++++++++++++++++- 3 files changed, 171 insertions(+), 43 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 8a1daa65929..16e5736bd3f 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -31,10 +31,7 @@ export enum ConfigurationTarget { export interface IConfigurationChangeEvent { affectedKeys: string[]; - affectsConfiugration(configuration: string): boolean; - affectsConfiugration(configuration: string, overrideIdentifier: string): boolean; - affectsConfiugration(configuration: string, resource: URI): boolean; - affectsConfiugration(configuration: string, overrideIdentifier: string, resource: URI): boolean; + affectsConfiugration(configuration: string, resource?: URI): boolean; // Following data is used for telemetry source: ConfigurationTarget; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 7acaca42472..be314db0f04 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -455,15 +455,47 @@ export class Configuration { } } -export class AllKeysConfigurationChangeEvent implements IConfigurationChangeEvent { +export class AbstractConfigurationChangeEvent { - constructor(readonly affectedKeys: string[], readonly source: ConfigurationTarget, readonly sourceConfig: any) { } + protected doesConfigurationContains(configuration: ConfigurationModel, config: string): boolean { + let changedKeysTree = configuration.contents; + let requestedTree = toValuesTree({ [config]: true }, () => { }); - affectsConfiugration: () => true; + let key; + while (typeof requestedTree === 'object' && (key = Object.keys(requestedTree)[0])) { // Only one key should present, since we added only one property + changedKeysTree = changedKeysTree[key]; + if (!changedKeysTree) { + return false; // Requested tree is not found + } + requestedTree = requestedTree[key]; + } + return true; + } + + protected updateKeys(configuration: ConfigurationModel, keys: string[], resource?: URI): void { + for (const key of keys) { + configuration.setValue(key, true); + } + } +} + +export class AllKeysConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent { + + private changedConfiguration: ConfigurationModel = null; + + constructor(readonly affectedKeys: string[], readonly source: ConfigurationTarget, readonly sourceConfig: any) { super(); } + + affectsConfiugration(config: string, resource?: URI): boolean { + if (!this.changedConfiguration) { + this.changedConfiguration = new ConfigurationModel(); + this.updateKeys(this.changedConfiguration, this.affectedKeys); + } + return this.doesConfigurationContains(this.changedConfiguration, config); + } } -export class ConfigurationChangeEvent implements IConfigurationChangeEvent { +export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent { private changedConfiguration: ConfigurationModel = new ConfigurationModel(); private changedConfigurationByResource: StrictResourceMap = new StrictResourceMap(); @@ -483,7 +515,8 @@ export class ConfigurationChangeEvent implements IConfigurationChangeEvent { this.changedConfigurationByResource.set(resource, changedConfigurationByResource); } } - return this.changeWithKeys(arg1, arg2); + this.changeWithKeys(arg1, arg2); + return this; } telemetryData(source: ConfigurationTarget, sourceConfig: any): ConfigurationChangeEvent { @@ -506,46 +539,30 @@ export class ConfigurationChangeEvent implements IConfigurationChangeEvent { return this._sourceConfig; } - affectsConfiugration(config: string): boolean - affectsConfiugration(config: string, overrideIdentifier: string): boolean - affectsConfiugration(config: string, resource: URI): boolean - affectsConfiugration(config: string, overrideIdentifier: string, resource: URI): boolean - affectsConfiugration(config: string, arg1?: any, arg2?: any): boolean { - let resource = arg1 instanceof URI ? arg1 : arg2 instanceof URI ? arg2 : void 0; - let overrideIdentifier = resource && arg1 !== resource ? arg1 : void 0; - let model = resource ? this.changedConfigurationByResource.get(resource) : this.changedConfiguration; - if (model) { + affectsConfiugration(config: string, resource?: URI): boolean { + let configurationModelsToSearch: ConfigurationModel[] = [this.changedConfiguration]; - if (overrideIdentifier) { - return model.overrides.some(override => override.identifiers.indexOf(overrideIdentifier) !== -1); + if (resource) { + let model = this.changedConfigurationByResource.get(resource); + if (model) { + configurationModelsToSearch.push(model); } - - let changedKeysTree = model.contents; - let requestedTree = toValuesTree({ [config]: true }, () => { }); - - let key; - while (typeof requestedTree === 'object' && (key = Object.keys(requestedTree)[0])) { // Only one key should present, since we added only one property - changedKeysTree = changedKeysTree[key]; - if (!changedKeysTree) { - return false; // Requested tree is not found - } - requestedTree = requestedTree[key]; - } - return true; + } else { + configurationModelsToSearch.push(...this.changedConfigurationByResource.values()); } + + for (const configuration of configurationModelsToSearch) { + if (this.doesConfigurationContains(configuration, config)) { + return true; + } + } + return false; } - private changeWithKeys(keys: string[], resource?: URI): ConfigurationChangeEvent { + private changeWithKeys(keys: string[], resource?: URI): void { let changedConfiguration = resource ? this.getOrSetChangedConfigurationForResource(resource) : this.changedConfiguration; - for (const key of keys) { - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { - changedConfiguration.setValueInOverrides(overrideIdentifierFromKey(key), 'key'/* any key */, true); - } else { - changedConfiguration.setValue(key, true); - } - } - return this; + this.updateKeys(changedConfiguration, keys); } private getOrSetChangedConfigurationForResource(resource: URI): ConfigurationModel { diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index b4cc2a1d476..294b9d0e69e 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -5,9 +5,11 @@ 'use strict'; import * as assert from 'assert'; -import { ConfigurationModel, CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModel, CustomConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; +import URI from 'vs/base/common/uri'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; suite('ConfigurationModel', () => { @@ -396,4 +398,116 @@ suite('CustomConfigurationModel', () => { assert.equal(undefined, new DefaultConfigurationModel().getSectionContents('[a]')); }); +}); + +suite('ConfigurationChangeEvent', () => { + + test('changeEvent affecting keys for all resources', () => { + let testObject = new ConfigurationChangeEvent(); + + testObject.change(['window.zoomLevel', 'workbench.editor.enablePreview', 'files', '[markdown]']); + + assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview', 'files', '[markdown]']); + assert.ok(testObject.affectsConfiugration('window.zoomLevel')); + assert.ok(testObject.affectsConfiugration('window')); + assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiugration('workbench.editor')); + assert.ok(testObject.affectsConfiugration('workbench')); + assert.ok(testObject.affectsConfiugration('files')); + assert.ok(!testObject.affectsConfiugration('files.exclude')); + assert.ok(testObject.affectsConfiugration('[markdown]')); + }); + + test('changeEvent affecting keys for resources', () => { + let testObject = new ConfigurationChangeEvent(); + + testObject.change(['window.title']); + testObject.change(['window.zoomLevel'], URI.file('file1')); + testObject.change(['workbench.editor.enablePreview'], URI.file('file2')); + testObject.change(['window.restoreFullscreen'], URI.file('file1')); + testObject.change(['window.restoreWindows'], URI.file('file2')); + + assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + + assert.ok(testObject.affectsConfiugration('window.zoomLevel')); + assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file('file1'))); + assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file('file2'))); + + assert.ok(testObject.affectsConfiugration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file2'))); + + assert.ok(testObject.affectsConfiugration('window.restoreWindows')); + assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file('file2'))); + assert.ok(!testObject.affectsConfiugration('window.restoreWindows', URI.file('file1'))); + + assert.ok(testObject.affectsConfiugration('window.title')); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('file2'))); + + assert.ok(testObject.affectsConfiugration('window')); + assert.ok(testObject.affectsConfiugration('window', URI.file('file1'))); + assert.ok(testObject.affectsConfiugration('window', URI.file('file2'))); + + assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('file2'))); + assert.ok(!testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('file1'))); + + assert.ok(testObject.affectsConfiugration('workbench.editor')); + assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file('file2'))); + assert.ok(!testObject.affectsConfiugration('workbench.editor', URI.file('file1'))); + + assert.ok(testObject.affectsConfiugration('workbench')); + assert.ok(testObject.affectsConfiugration('workbench', URI.file('file2'))); + assert.ok(!testObject.affectsConfiugration('workbench', URI.file('file1'))); + + assert.ok(!testObject.affectsConfiugration('files')); + assert.ok(!testObject.affectsConfiugration('files', URI.file('file1'))); + assert.ok(!testObject.affectsConfiugration('files', URI.file('file2'))); + }); + +}); + +suite('AllKeysConfigurationChangeEvent', () => { + + test('changeEvent affects keys for any resource', () => { + let testObject = new AllKeysConfigurationChangeEvent(['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows'], ConfigurationTarget.USER, null); + + assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + + assert.ok(testObject.affectsConfiugration('window.zoomLevel')); + assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file('file1'))); + assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file('file2'))); + + assert.ok(testObject.affectsConfiugration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file2'))); + + assert.ok(testObject.affectsConfiugration('window.restoreWindows')); + assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file('file2'))); + assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file('file1'))); + + assert.ok(testObject.affectsConfiugration('window.title')); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('file2'))); + + assert.ok(testObject.affectsConfiugration('window')); + assert.ok(testObject.affectsConfiugration('window', URI.file('file1'))); + assert.ok(testObject.affectsConfiugration('window', URI.file('file2'))); + + assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('file2'))); + assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('file1'))); + + assert.ok(testObject.affectsConfiugration('workbench.editor')); + assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file('file2'))); + assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file('file1'))); + + assert.ok(testObject.affectsConfiugration('workbench')); + assert.ok(testObject.affectsConfiugration('workbench', URI.file('file2'))); + assert.ok(testObject.affectsConfiugration('workbench', URI.file('file1'))); + + assert.ok(!testObject.affectsConfiugration('files')); + assert.ok(!testObject.affectsConfiugration('files', URI.file('file1'))); + }); }); \ No newline at end of file From 9927869cc86ba2307687a124b673dac86d17f7cc Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Oct 2017 10:56:27 +0200 Subject: [PATCH 21/28] Fix workspace configuration change event --- .../common/configurationModels.ts | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index b4518c06965..91366479302 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -5,7 +5,7 @@ 'use strict'; import { clone, equals } from 'vs/base/common/objects'; -import { compare, toValuesTree, IConfigurationChangeEvent, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { compare, toValuesTree, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel, Configuration as BaseConfiguration, CustomConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, IConfigurationPropertySchema, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -268,33 +268,21 @@ export class Configuration extends BaseConfiguration { } } -export class WorkspaceConfigurationChangeEvent implements IConfigurationChangeEvent { +export class WorkspaceConfigurationChangeEvent extends ConfigurationChangeEvent implements IConfigurationChangeEvent { - constructor(private configurationChangeEvent: ConfigurationChangeEvent, private workspace: Workspace) { + constructor(private workspace: Workspace) { + super(); } - get affectedKeys(): string[] { - return this.configurationChangeEvent.affectedKeys; - } - - get source(): ConfigurationTarget { - return this.configurationChangeEvent.source; - } - - get sourceConfig(): any { - return this.configurationChangeEvent.sourceConfig; - } - - affectsConfiugration(config: string, arg1?: any, arg2?: any): boolean { - if (this.configurationChangeEvent.affectsConfiugration(config, arg1, arg2)) { + affectsConfiugration(config: string, resource?: URI): boolean { + if (super.affectsConfiugration(config, resource)) { return true; } - let resource = arg1 instanceof URI ? arg1 : arg2 instanceof URI ? arg2 : void 0; if (resource) { let workspaceFolder = this.workspace.getFolder(resource); if (workspaceFolder) { - return this.configurationChangeEvent.affectsConfiugration(config, resource && arg1 !== resource ? arg1 : void 0, resource); + return super.affectsConfiugration(config, resource); } } From 1a87d359d7e0e10dd93f662287f30efba5a6161f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Oct 2017 11:12:26 +0200 Subject: [PATCH 22/28] tests for workspace configuration change event --- .../common/configurationModels.ts | 2 +- .../test/common/configurationModels.test.ts | 97 ++++++++++++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 91366479302..48506da5b83 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -282,7 +282,7 @@ export class WorkspaceConfigurationChangeEvent extends ConfigurationChangeEvent if (resource) { let workspaceFolder = this.workspace.getFolder(resource); if (workspaceFolder) { - return super.affectsConfiugration(config, resource); + return super.affectsConfiugration(config, workspaceFolder.uri); } } diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 14e684e145e..90091e4f0c1 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -5,8 +5,11 @@ 'use strict'; import * as assert from 'assert'; -import { FolderConfigurationModel, ScopedConfigurationModel, FolderSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels'; +import { join } from 'vs/base/common/paths'; +import { FolderConfigurationModel, ScopedConfigurationModel, FolderSettingsModel, WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import URI from 'vs/base/common/uri'; suite('ConfigurationService - Model', () => { @@ -94,4 +97,96 @@ suite('ConfigurationService - Model', () => { assert.deepEqual(new FolderConfigurationModel(settingsConfig, [launchConfig, tasksConfig], ConfigurationScope.WINDOW).contents, expected); assert.deepEqual(new FolderConfigurationModel(settingsConfig, [tasksConfig, launchConfig], ConfigurationScope.WINDOW).contents, expected); }); +}); + +suite('WorkspaceConfigurationChangeEvent', () => { + + test('changeEvent affecting workspace folders', () => { + let testObject = new WorkspaceConfigurationChangeEvent(new Workspace('id', 'name', + [new WorkspaceFolder({ index: 0, name: '1', uri: URI.file('folder1') }), + new WorkspaceFolder({ index: 1, name: '2', uri: URI.file('folder2') }), + new WorkspaceFolder({ index: 2, name: '3', uri: URI.file('folder3') })])); + + testObject.change(['window.title']); + testObject.change(['window.zoomLevel'], URI.file('folder1')); + testObject.change(['workbench.editor.enablePreview'], URI.file('folder2')); + testObject.change(['window.restoreFullscreen'], URI.file('folder1')); + testObject.change(['window.restoreWindows'], URI.file('folder2')); + + assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + + assert.ok(testObject.affectsConfiugration('window.zoomLevel')); + assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file('folder1'))); + assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file('file1'))); + assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file('file2'))); + assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file(join('folder3', 'file3')))); + + assert.ok(testObject.affectsConfiugration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file2'))); + assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file(join('folder3', 'file3')))); + + assert.ok(testObject.affectsConfiugration('window.restoreWindows')); + assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file('folder2'))); + assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiugration('window.restoreWindows', URI.file('file2'))); + assert.ok(!testObject.affectsConfiugration('window.restoreWindows', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiugration('window.restoreWindows', URI.file(join('folder3', 'file3')))); + + assert.ok(testObject.affectsConfiugration('window.title')); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('folder1'))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('folder2'))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('folder3'))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('file2'))); + assert.ok(testObject.affectsConfiugration('window.title', URI.file('file3'))); + + assert.ok(testObject.affectsConfiugration('window')); + assert.ok(testObject.affectsConfiugration('window', URI.file('folder1'))); + assert.ok(testObject.affectsConfiugration('window', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiugration('window', URI.file('folder2'))); + assert.ok(testObject.affectsConfiugration('window', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiugration('window', URI.file('folder3'))); + assert.ok(testObject.affectsConfiugration('window', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiugration('window', URI.file('file1'))); + assert.ok(testObject.affectsConfiugration('window', URI.file('file2'))); + assert.ok(testObject.affectsConfiugration('window', URI.file('file3'))); + + assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('folder2'))); + assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('folder3'))); + + assert.ok(testObject.affectsConfiugration('workbench.editor')); + assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file('folder2'))); + assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiugration('workbench.editor', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiugration('workbench.editor', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiugration('workbench.editor', URI.file('folder3'))); + + assert.ok(testObject.affectsConfiugration('workbench')); + assert.ok(testObject.affectsConfiugration('workbench', URI.file('folder2'))); + assert.ok(testObject.affectsConfiugration('workbench', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiugration('workbench', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiugration('workbench', URI.file('folder3'))); + + assert.ok(!testObject.affectsConfiugration('files')); + assert.ok(!testObject.affectsConfiugration('files', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiugration('files', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiugration('files', URI.file('folder2'))); + assert.ok(!testObject.affectsConfiugration('files', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiugration('files', URI.file('folder3'))); + assert.ok(!testObject.affectsConfiugration('files', URI.file(join('folder3', 'file3')))); + }); + }); \ No newline at end of file From 788c5c536b32f667419f9a1e458fb29e7e67e27f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Oct 2017 11:15:23 +0200 Subject: [PATCH 23/28] Correct mistyping --- .../configuration/common/configuration.ts | 2 +- .../common/configurationModels.ts | 4 +- .../test/common/configurationModels.test.ts | 122 ++++++++--------- .../parts/debug/browser/debugActionItems.ts | 2 +- .../watermark/electron-browser/watermark.ts | 2 +- .../common/configurationModels.ts | 6 +- .../test/common/configurationModels.test.ts | 128 +++++++++--------- 7 files changed, 133 insertions(+), 133 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 16e5736bd3f..5cbe7665027 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -31,7 +31,7 @@ export enum ConfigurationTarget { export interface IConfigurationChangeEvent { affectedKeys: string[]; - affectsConfiugration(configuration: string, resource?: URI): boolean; + affectsConfiguration(configuration: string, resource?: URI): boolean; // Following data is used for telemetry source: ConfigurationTarget; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index be314db0f04..a7e62338321 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -485,7 +485,7 @@ export class AllKeysConfigurationChangeEvent extends AbstractConfigurationChange constructor(readonly affectedKeys: string[], readonly source: ConfigurationTarget, readonly sourceConfig: any) { super(); } - affectsConfiugration(config: string, resource?: URI): boolean { + affectsConfiguration(config: string, resource?: URI): boolean { if (!this.changedConfiguration) { this.changedConfiguration = new ConfigurationModel(); this.updateKeys(this.changedConfiguration, this.affectedKeys); @@ -539,7 +539,7 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i return this._sourceConfig; } - affectsConfiugration(config: string, resource?: URI): boolean { + affectsConfiguration(config: string, resource?: URI): boolean { let configurationModelsToSearch: ConfigurationModel[] = [this.changedConfiguration]; if (resource) { diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 294b9d0e69e..598b7d6d616 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -408,14 +408,14 @@ suite('ConfigurationChangeEvent', () => { testObject.change(['window.zoomLevel', 'workbench.editor.enablePreview', 'files', '[markdown]']); assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview', 'files', '[markdown]']); - assert.ok(testObject.affectsConfiugration('window.zoomLevel')); - assert.ok(testObject.affectsConfiugration('window')); - assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview')); - assert.ok(testObject.affectsConfiugration('workbench.editor')); - assert.ok(testObject.affectsConfiugration('workbench')); - assert.ok(testObject.affectsConfiugration('files')); - assert.ok(!testObject.affectsConfiugration('files.exclude')); - assert.ok(testObject.affectsConfiugration('[markdown]')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files.exclude')); + assert.ok(testObject.affectsConfiguration('[markdown]')); }); test('changeEvent affecting keys for resources', () => { @@ -429,41 +429,41 @@ suite('ConfigurationChangeEvent', () => { assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); - assert.ok(testObject.affectsConfiugration('window.zoomLevel')); - assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file('file1'))); - assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('window.restoreFullscreen')); - assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file1'))); - assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('window.restoreWindows')); - assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file('file2'))); - assert.ok(!testObject.affectsConfiugration('window.restoreWindows', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.restoreWindows')); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window.title')); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('window')); - assert.ok(testObject.affectsConfiugration('window', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview')); - assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('file2'))); - assert.ok(!testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('workbench.editor')); - assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file('file2'))); - assert.ok(!testObject.affectsConfiugration('workbench.editor', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('workbench')); - assert.ok(testObject.affectsConfiugration('workbench', URI.file('file2'))); - assert.ok(!testObject.affectsConfiugration('workbench', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('workbench', URI.file('file1'))); - assert.ok(!testObject.affectsConfiugration('files')); - assert.ok(!testObject.affectsConfiugration('files', URI.file('file1'))); - assert.ok(!testObject.affectsConfiugration('files', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('files', URI.file('file2'))); }); }); @@ -475,39 +475,39 @@ suite('AllKeysConfigurationChangeEvent', () => { assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); - assert.ok(testObject.affectsConfiugration('window.zoomLevel')); - assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('window.restoreFullscreen')); - assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('window.restoreWindows')); - assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.restoreWindows')); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window.title')); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('window')); - assert.ok(testObject.affectsConfiugration('window', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview')); - assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('workbench.editor')); - assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('workbench')); - assert.ok(testObject.affectsConfiugration('workbench', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('workbench', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('workbench', URI.file('file1'))); - assert.ok(!testObject.affectsConfiugration('files')); - assert.ok(!testObject.affectsConfiugration('files', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', URI.file('file1'))); }); }); \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index 4cba52bfbcc..9467e227330 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -55,7 +55,7 @@ export class StartDebugActionItem extends EventEmitter implements IActionItem { private registerListeners(): void { this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => { - if (e.affectsConfiugration('launch')) { + if (e.affectsConfiguration('launch')) { this.updateOptions(); } })); diff --git a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts index 11538b93dad..ec6aa876b65 100644 --- a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts +++ b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts @@ -127,7 +127,7 @@ export class WatermarkContribution implements IWorkbenchContribution { } }); this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => { - if (e.affectsConfiugration(WORKBENCH_TIPS_ENABLED_KEY)) { + if (e.affectsConfiguration(WORKBENCH_TIPS_ENABLED_KEY)) { const enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); if (enabled !== this.enabled) { this.enabled = enabled; diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 48506da5b83..1fd4c79e89d 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -274,15 +274,15 @@ export class WorkspaceConfigurationChangeEvent extends ConfigurationChangeEvent super(); } - affectsConfiugration(config: string, resource?: URI): boolean { - if (super.affectsConfiugration(config, resource)) { + affectsConfiguration(config: string, resource?: URI): boolean { + if (super.affectsConfiguration(config, resource)) { return true; } if (resource) { let workspaceFolder = this.workspace.getFolder(resource); if (workspaceFolder) { - return super.affectsConfiugration(config, workspaceFolder.uri); + return super.affectsConfiguration(config, workspaceFolder.uri); } } diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 90091e4f0c1..58e25f17575 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -115,78 +115,78 @@ suite('WorkspaceConfigurationChangeEvent', () => { assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); - assert.ok(testObject.affectsConfiugration('window.zoomLevel')); - assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file('folder1'))); - assert.ok(testObject.affectsConfiugration('window.zoomLevel', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file('file1'))); - assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file('file2'))); - assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiugration('window.zoomLevel', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('folder1'))); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder3', 'file3')))); - assert.ok(testObject.affectsConfiugration('window.restoreFullscreen')); - assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file(join('folder1', 'file1')))); - assert.ok(testObject.affectsConfiugration('window.restoreFullscreen', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file1'))); - assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file('file2'))); - assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiugration('window.restoreFullscreen', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder3', 'file3')))); - assert.ok(testObject.affectsConfiugration('window.restoreWindows')); - assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file('folder2'))); - assert.ok(testObject.affectsConfiugration('window.restoreWindows', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiugration('window.restoreWindows', URI.file('file2'))); - assert.ok(!testObject.affectsConfiugration('window.restoreWindows', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiugration('window.restoreWindows', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiguration('window.restoreWindows')); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder3', 'file3')))); - assert.ok(testObject.affectsConfiugration('window.title')); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('folder1'))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file(join('folder1', 'file1')))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('folder2'))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file(join('folder2', 'file2')))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('folder3'))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file(join('folder3', 'file3')))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('window.title', URI.file('file3'))); + assert.ok(testObject.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder1'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder3'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file3'))); - assert.ok(testObject.affectsConfiugration('window')); - assert.ok(testObject.affectsConfiugration('window', URI.file('folder1'))); - assert.ok(testObject.affectsConfiugration('window', URI.file(join('folder1', 'file1')))); - assert.ok(testObject.affectsConfiugration('window', URI.file('folder2'))); - assert.ok(testObject.affectsConfiugration('window', URI.file(join('folder2', 'file2')))); - assert.ok(testObject.affectsConfiugration('window', URI.file('folder3'))); - assert.ok(testObject.affectsConfiugration('window', URI.file(join('folder3', 'file3')))); - assert.ok(testObject.affectsConfiugration('window', URI.file('file1'))); - assert.ok(testObject.affectsConfiugration('window', URI.file('file2'))); - assert.ok(testObject.affectsConfiugration('window', URI.file('file3'))); + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window', URI.file('folder1'))); + assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('window', URI.file('folder3'))); + assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file3'))); - assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview')); - assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('folder2'))); - assert.ok(testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiugration('workbench.editor.enablePreview', URI.file('folder3'))); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder3'))); - assert.ok(testObject.affectsConfiugration('workbench.editor')); - assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file('folder2'))); - assert.ok(testObject.affectsConfiugration('workbench.editor', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiugration('workbench.editor', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiugration('workbench.editor', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiugration('workbench.editor', URI.file('folder3'))); + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder3'))); - assert.ok(testObject.affectsConfiugration('workbench')); - assert.ok(testObject.affectsConfiugration('workbench', URI.file('folder2'))); - assert.ok(testObject.affectsConfiugration('workbench', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiugration('workbench', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiugration('workbench', URI.file('folder3'))); + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('workbench', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder3'))); - assert.ok(!testObject.affectsConfiugration('files')); - assert.ok(!testObject.affectsConfiugration('files', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiugration('files', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiugration('files', URI.file('folder2'))); - assert.ok(!testObject.affectsConfiugration('files', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiugration('files', URI.file('folder3'))); - assert.ok(!testObject.affectsConfiugration('files', URI.file(join('folder3', 'file3')))); + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('files', URI.file('folder2'))); + assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('files', URI.file('folder3'))); + assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder3', 'file3')))); }); }); \ No newline at end of file From 0d9e0253582be4c704ce3e684a1307ee3c3e8cce Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Oct 2017 11:20:11 +0200 Subject: [PATCH 24/28] Use wrapper pattern for Workspace Configuration Change Event --- .../common/configurationModels.ts | 23 ++++++++++++++----- .../test/common/configurationModels.test.ts | 19 +++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 1fd4c79e89d..8b9e5aa53fe 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -5,7 +5,7 @@ 'use strict'; import { clone, equals } from 'vs/base/common/objects'; -import { compare, toValuesTree, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { compare, toValuesTree, IConfigurationChangeEvent, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel, Configuration as BaseConfiguration, CustomConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, IConfigurationPropertySchema, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -268,21 +268,32 @@ export class Configuration extends BaseConfiguration { } } -export class WorkspaceConfigurationChangeEvent extends ConfigurationChangeEvent implements IConfigurationChangeEvent { +export class WorkspaceConfigurationChangeEvent implements IConfigurationChangeEvent { - constructor(private workspace: Workspace) { - super(); + constructor(private configurationChangeEvent: ConfigurationChangeEvent, private workspace: Workspace) { + } + + get affectedKeys(): string[] { + return this.configurationChangeEvent.affectedKeys; + } + + get source(): ConfigurationTarget { + return this.configurationChangeEvent.source; + } + + get sourceConfig(): any { + return this.configurationChangeEvent.sourceConfig; } affectsConfiguration(config: string, resource?: URI): boolean { - if (super.affectsConfiguration(config, resource)) { + if (this.configurationChangeEvent.affectsConfiguration(config, resource)) { return true; } if (resource) { let workspaceFolder = this.workspace.getFolder(resource); if (workspaceFolder) { - return super.affectsConfiguration(config, workspaceFolder.uri); + return this.configurationChangeEvent.affectsConfiguration(config, workspaceFolder.uri); } } diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 58e25f17575..781260d8502 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -10,6 +10,8 @@ import { FolderConfigurationModel, ScopedConfigurationModel, FolderSettingsModel import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import URI from 'vs/base/common/uri'; +import { ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; suite('ConfigurationService - Model', () => { @@ -102,18 +104,21 @@ suite('ConfigurationService - Model', () => { suite('WorkspaceConfigurationChangeEvent', () => { test('changeEvent affecting workspace folders', () => { - let testObject = new WorkspaceConfigurationChangeEvent(new Workspace('id', 'name', + let configurationChangeEvent = new ConfigurationChangeEvent(); + configurationChangeEvent.change(['window.title']); + configurationChangeEvent.change(['window.zoomLevel'], URI.file('folder1')); + configurationChangeEvent.change(['workbench.editor.enablePreview'], URI.file('folder2')); + configurationChangeEvent.change(['window.restoreFullscreen'], URI.file('folder1')); + configurationChangeEvent.change(['window.restoreWindows'], URI.file('folder2')); + configurationChangeEvent.telemetryData(ConfigurationTarget.WORKSPACE, {}); + + let testObject = new WorkspaceConfigurationChangeEvent(configurationChangeEvent, new Workspace('id', 'name', [new WorkspaceFolder({ index: 0, name: '1', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: '2', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: '3', uri: URI.file('folder3') })])); - testObject.change(['window.title']); - testObject.change(['window.zoomLevel'], URI.file('folder1')); - testObject.change(['workbench.editor.enablePreview'], URI.file('folder2')); - testObject.change(['window.restoreFullscreen'], URI.file('folder1')); - testObject.change(['window.restoreWindows'], URI.file('folder2')); - assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + assert.equal(testObject.source, ConfigurationTarget.WORKSPACE); assert.ok(testObject.affectsConfiguration('window.zoomLevel')); assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('folder1'))); From 996d90415e831d8682b6b6ee149b4aee3085097e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Oct 2017 11:52:50 +0200 Subject: [PATCH 25/28] Revert changes in preferences --- .../parts/preferences/browser/preferencesRenderers.ts | 5 ++--- .../parts/preferences/browser/preferencesService.ts | 4 +--- .../workbench/parts/preferences/common/preferencesModels.ts | 4 +--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts index 98996425277..6a06a153b61 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts @@ -561,9 +561,8 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr range, options: { stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'findMatch', - - }, + className: 'findMatch' + } }; } diff --git a/src/vs/workbench/parts/preferences/browser/preferencesService.ts b/src/vs/workbench/parts/preferences/browser/preferencesService.ts index 3a786ee208c..c3f74852cee 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesService.ts @@ -119,7 +119,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic .then(preferencesEditorModel => preferencesEditorModel ? preferencesEditorModel.content : null); } - // vsode://DefaultSettings/1 createPreferencesEditorModel(uri: URI): TPromise> { let promise = this.defaultPreferencesEditorModels.get(uri); if (promise) { @@ -251,7 +250,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: IEditorOptions, position?: EditorPosition): TPromise { - const openDefaultSettings = !!this.configurationService.lookup(DEFAULT_SETTINGS_EDITOR_SETTING).value; + const openDefaultSettings = !!this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING); return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { if (!options) { @@ -261,7 +260,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic } if (openDefaultSettings) { - // get a new URI for default settings here const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(configurationTarget)); const preferencesEditorInput = new PreferencesEditorInput(this.getPreferencesEditorInputName(configurationTarget, resource), editableSettingsEditorInput.getDescription(), defaultPreferencesEditorInput, editableSettingsEditorInput); this.lastOpenedSettingsInput = preferencesEditorInput; diff --git a/src/vs/workbench/parts/preferences/common/preferencesModels.ts b/src/vs/workbench/parts/preferences/common/preferencesModels.ts index 26e4c93d348..f17231a589b 100644 --- a/src/vs/workbench/parts/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/parts/preferences/common/preferencesModels.ts @@ -244,9 +244,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti constructor(reference: IReference, private _configurationTarget: ConfigurationTarget, @ITextFileService protected textFileService: ITextFileService) { super(); this.settingsModel = reference.object.textEditorModel; - this._register(this.onDispose(() => { - reference.dispose(); - })); + this._register(this.onDispose(() => reference.dispose())); this._register(this.settingsModel.onDidChangeContent(() => { this._settingsGroups = null; })); From 183035fb52a924557ef13502dc0777d062025c49 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Oct 2017 11:55:48 +0200 Subject: [PATCH 26/28] Fix compilation issues --- src/vs/workbench/parts/files/browser/fileActions.ts | 2 +- src/vs/workbench/parts/files/browser/views/explorerViewer.ts | 2 +- .../parts/preferences/common/preferencesContribution.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/files/browser/fileActions.ts b/src/vs/workbench/parts/files/browser/fileActions.ts index 6975b962704..5204850eaee 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -714,7 +714,7 @@ export class BaseDeleteFileAction extends BaseFileAction { let confirmPromise: TPromise; // Check if we need to ask for confirmation at all - if (this.skipConfirm || (this.useTrash && this.configurationService.lookup(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY).value === false)) { + if (this.skipConfirm || (this.useTrash && this.configurationService.getValue(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY) === false)) { confirmPromise = TPromise.as({ confirmed: true } as IConfirmationResult); } diff --git a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts index 01b9954702b..4878cedef0b 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts @@ -949,7 +949,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { let confirmPromise: TPromise; // Handle confirm setting - const confirmDragAndDrop = !isCopy && this.configurationService.lookup(FileDragAndDrop.CONFIRM_DND_SETTING_KEY).value; + const confirmDragAndDrop = !isCopy && this.configurationService.getValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY); if (confirmDragAndDrop) { confirmPromise = this.messageService.confirm({ message: nls.localize('confirmMove', "Are you sure you want to move '{0}'?", source.name), diff --git a/src/vs/workbench/parts/preferences/common/preferencesContribution.ts b/src/vs/workbench/parts/preferences/common/preferencesContribution.ts index 48e0ef167d2..2f91e713b0d 100644 --- a/src/vs/workbench/parts/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/parts/preferences/common/preferencesContribution.ts @@ -50,7 +50,7 @@ export class PreferencesContribution implements IWorkbenchContribution { this.editorOpeningListener = dispose(this.editorOpeningListener); // install editor opening listener unless user has disabled this - if (!!this.configurationService.lookup(DEFAULT_SETTINGS_EDITOR_SETTING).value) { + if (!!this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING)) { this.editorOpeningListener = this.editorGroupService.onEditorOpening(e => this.onEditorOpening(e)); } } @@ -60,7 +60,7 @@ export class PreferencesContribution implements IWorkbenchContribution { if ( !resource || resource.scheme !== 'file' || // require a file path opening !endsWith(resource.fsPath, 'settings.json') || // file must end in settings.json - !this.configurationService.lookup(DEFAULT_SETTINGS_EDITOR_SETTING).value // user has not disabled default settings editor + !this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING) // user has not disabled default settings editor ) { return; } From 3f1ffd14c8cafde90de8fb165954527932e1dadd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Oct 2017 11:57:22 +0200 Subject: [PATCH 27/28] Reload after editing --- .../configuration/node/configurationEditingService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index b264dbd9662..d13b326bfc2 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -94,7 +94,9 @@ export class ConfigurationEditingService implements IConfigurationEditingService private writeToBuffer(model: editorCommon.IModel, operation: IConfigurationEditOperation, save: boolean): TPromise { const edit = this.getEdits(model, operation)[0]; if (this.applyEditsToBuffer(edit, model) && save) { - return this.textFileService.save(operation.resource, { skipSaveParticipants: true /* programmatic change */ }); + return this.textFileService.save(operation.resource, { skipSaveParticipants: true /* programmatic change */ }) + // Reload the configuration so that we make sure all parties are updated + .then(() => this.configurationService.reloadConfiguration()); } return TPromise.as(null); } From 907de42b3e7c52dc7c34d0785b1f00c74da43fe9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Oct 2017 12:33:14 +0200 Subject: [PATCH 28/28] Fix tests --- src/vs/platform/configuration/common/configuration.ts | 7 +++++++ .../configuration/node/configurationService.ts | 10 +--------- .../test/common/testConfigurationService.ts | 5 +++-- .../configuration/node/configurationService.ts | 7 ++++--- .../test/node/configurationService.test.ts | 4 ++-- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 5cbe7665027..4ad222fdb0b 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -15,6 +15,13 @@ import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/co export const IConfigurationService = createDecorator('configurationService'); +export function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { + return thing + && typeof thing === 'object' + && (!thing.overrideIdentifier || typeof thing.overrideIdentifier === 'string') + && (!thing.resource || thing.resource instanceof URI); +} + export interface IConfigurationOverrides { overrideIdentifier?: string; resource?: URI; diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index 035060db7de..19f78c792ab 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -8,23 +8,15 @@ import { ConfigWatcher } from 'vs/base/node/config'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, compare } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, compare, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { CustomConfigurationModel, DefaultConfigurationModel, ConfigurationModel, Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import Event, { Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { onUnexpectedError } from 'vs/base/common/errors'; -import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { equals } from 'vs/base/common/objects'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -export function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { - return thing - && typeof thing === 'object' - && (!thing.overrideIdentifier || typeof thing.overrideIdentifier === 'string') - && (!thing.resource || thing.resource instanceof URI); -} - export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { _serviceBrand: any; diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index 2410406e261..6e0a81519b3 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -9,7 +9,7 @@ import { TernarySearchTree } from 'vs/base/common/map'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { EventEmitter } from 'vs/base/common/eventEmitter'; -import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue } from 'vs/platform/configuration/common/configuration'; +import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; export class TestConfigurationService extends EventEmitter implements IConfigurationService { public _serviceBrand: any; @@ -22,7 +22,8 @@ export class TestConfigurationService extends EventEmitter implements IConfigura return TPromise.as(this.getConfiguration()); } - public getConfiguration(section?: any, overrides?: any): C { + public getConfiguration(arg1?: any, arg2?: any): C { + const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; if (overrides && overrides.resource) { const configForResource = this.configurationByRoot.findSubstr(overrides.resource.fsPath); return configForResource || this.configuration; diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index f2ed3e415dc..b4902fe2e8e 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -24,10 +24,10 @@ import { isLinux } from 'vs/base/common/platform'; import { ConfigWatcher } from 'vs/base/node/config'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { CustomConfigurationModel, ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel, Configuration, WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; -import { ConfigurationService as GlobalConfigurationService, isConfigurationOverrides } from 'vs/platform/configuration/node/configurationService'; +import { ConfigurationService as GlobalConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationNode, IConfigurationRegistry, Extensions, ConfigurationScope, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry'; import { createHash } from 'crypto'; @@ -160,7 +160,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat if (folder) { return this.reloadWorkspaceFolderConfiguration(folder, key); } - return this.loadConfiguration(); + return this.reloadUserConfiguration() + .then(() => this.loadConfiguration()); } inspect(key: string, overrides?: IConfigurationOverrides): { diff --git a/src/vs/workbench/services/configuration/test/node/configurationService.test.ts b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts index bef7e75099f..5c6763edb10 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts @@ -178,7 +178,7 @@ suite('WorkspaceConfigurationService - Node', () => { return createService(workspaceDir, globalSettingsFile).then(service => { fs.writeFileSync(globalSettingsFile, '{ "testworkbench.editor.tabs": true }'); - service.reloadConfiguration(service.getWorkspace().folders[0]).then(() => { + service.reloadConfiguration().then(() => { const config = service.getConfiguration<{ testworkbench: { editor: { tabs: boolean } } }>(); assert.equal(config.testworkbench.editor.tabs, true); @@ -263,7 +263,7 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(globalSettingsFile, '{ "testworkbench.editor.icons": false, "testworkbench.other.setting": true }'); fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "testworkbench.editor.icons": true }'); - service.reloadWorkspaceConfiguration().then(() => { + service.reloadConfiguration().then(() => { const config = service.getConfiguration<{ testworkbench: { editor: { icons: boolean }, other: { setting: string } } }>(); assert.equal(config.testworkbench.editor.icons, true); assert.equal(config.testworkbench.other.setting, true);