From c656d5e1d70c253ceb37d802d19bc60ac4709227 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 9 Nov 2017 11:28:00 +0100 Subject: [PATCH] Reduce calling clone --- src/vs/base/common/objects.ts | 23 + .../standalone/browser/simpleServices.ts | 2 +- .../configuration/common/configuration.ts | 22 +- .../common/configurationModels.ts | 528 ++++++++++-------- .../common/configurationRegistry.ts | 19 +- .../configuration/node/configuration.ts | 44 ++ .../node/configurationService.ts | 26 +- .../test/common/configurationModels.test.ts | 132 ++--- .../api/node/extHostConfiguration.ts | 2 +- src/vs/workbench/electron-browser/main.ts | 6 +- .../configuration/common/configuration.ts | 10 +- .../common/configurationExtensionPoint.ts | 57 +- .../common/configurationModels.ts | 310 +++++----- .../configuration/node/configuration.ts | 106 +++- .../node/configurationEditingService.ts | 4 +- .../node/configurationService.ts | 82 +-- .../test/common/configurationModels.test.ts | 190 ++++--- .../node/configurationEditingService.test.ts | 4 +- .../test/node/configurationService.test.ts | 8 +- 19 files changed, 845 insertions(+), 730 deletions(-) create mode 100644 src/vs/platform/configuration/node/configuration.ts diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 308c65519a6..04063697114 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -41,6 +41,29 @@ export function deepClone(obj: T): T { return result; } +export function deepFreeze(obj: T): T { + + if (!obj || typeof obj !== 'object') { + return obj; + } + + // Retrieve the property names defined on obj + var propNames = Object.getOwnPropertyNames(obj); + + // Freeze properties before freezing self + propNames.forEach(function (name) { + var prop = obj[name]; + + // Freeze prop if it is an object + if (typeof prop === 'object' && prop !== null) { + deepFreeze(prop); + } + }); + + // Freeze self (no-op if already frozen) + return Object.freeze(obj); +} + const hasOwnProperty = Object.prototype.hasOwnProperty; export function cloneAndChange(obj: any, changer: (orig: any) => any): any { diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 43cf5921f94..821db839dce 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -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 { Configuration, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { Configuration, DefaultConfigurationModel, ConfigurationModel } 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 6ebb51245d6..ffa86c0267a 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -11,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 } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; import { StrictResourceMap } from 'vs/base/common/map'; export const IConfigurationService = createDecorator('configurationService'); @@ -124,6 +124,26 @@ export function compare(from: IConfigurationModel, to: IConfigurationModel): { a return { added, removed, updated }; } +export function toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] { + const overrides: IOverrides[] = []; + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + for (const key of Object.keys(raw)) { + if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + const overrideRaw = {}; + for (const keyInOverrideRaw in raw[key]) { + if (configurationProperties[keyInOverrideRaw] && configurationProperties[keyInOverrideRaw].overridable) { + overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]; + } + } + overrides.push({ + identifiers: [overrideIdentifierFromKey(key).trim()], + contents: toValuesTree(overrideRaw, conflictReporter) + }); + } + } + return overrides; +} + export function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void): any { const root = Object.create(null); diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 6ae3de36627..4f613e1ae30 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -7,66 +7,52 @@ 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 types from 'vs/base/common/types'; 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, IConfigurationModel, merge, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget, removeFromValueTree } from 'vs/platform/configuration/common/configuration'; +import { OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget, removeFromValueTree, toOverrides } from 'vs/platform/configuration/common/configuration'; import { Workspace } from 'vs/platform/workspace/common/workspace'; export class ConfigurationModel implements IConfigurationModel { - constructor(protected _contents: any = {}, protected _keys: string[] = [], protected _overrides: IOverrides[] = []) { + private isFrozen: boolean = false; + + constructor( + private _contents: any = {}, + private _keys: string[] = [], + private _overrides: IOverrides[] = [] + ) { } - public get contents(): any { - return this._contents; + get contents(): any { + return this.checkAndFreeze(this._contents); } - public get overrides(): IOverrides[] { - return this._overrides; + get overrides(): IOverrides[] { + return this.checkAndFreeze(this._overrides); } - public get keys(): string[] { - return this._keys; + get keys(): string[] { + return this.checkAndFreeze(this._keys); } - public getSectionContents(section: string): V { + getSectionContents(section: string): V { return this.contents[section]; } - public setValue(key: string, value: any) { - this.addKey(key); - addToValueTree(this._contents, key, value, e => { throw new Error(e); }); - } - - public removeValue(key: string): void { - if (this.removeKey(key)) { - removeFromValueTree(this._contents, key); - } - } - - 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 override(identifier: string): ConfigurationModel { + override(identifier: string): ConfigurationModel { const overrideContents = this.getContentsForOverrideIdentifer(identifier); if (!overrideContents || typeof overrideContents !== 'object' || !Object.keys(overrideContents).length) { - // If there are no valid overrides, use base contents - return new ConfigurationModel(this._contents); + // If there are no valid overrides, return self + return this; } let contents = {}; - for (const key of arrays.distinct([...Object.keys(this._contents), ...Object.keys(overrideContents)])) { + for (const key of arrays.distinct([...Object.keys(this.contents), ...Object.keys(overrideContents)])) { - let contentsForKey = this._contents[key]; + let contentsForKey = this.contents[key]; let overrideContentsForKey = overrideContents[key]; // If there are override contents for the key, clone and merge otherwise use base contents @@ -74,7 +60,7 @@ export class ConfigurationModel implements IConfigurationModel { // 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); + this.mergeContents(contentsForKey, overrideContentsForKey); } else { contentsForKey = overrideContentsForKey; } @@ -82,33 +68,61 @@ export class ConfigurationModel implements IConfigurationModel { contents[key] = contentsForKey; } + return new ConfigurationModel(contents); } - public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { - const mergedModel = new ConfigurationModel(); - this.doMerge(mergedModel, this, overwrite); - this.doMerge(mergedModel, other, overwrite); - return mergedModel; - } + merge(...others: ConfigurationModel[]): ConfigurationModel { + const contents = objects.clone(this.contents); + const overrides = objects.clone(this.overrides); + const keys = [...this.keys]; - 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); + for (const other of others) { + this.mergeContents(contents, other.contents); + + for (const otherOverride of other.overrides) { + const [override] = overrides.filter(o => arrays.equals(o.identifiers, otherOverride.identifiers)); + if (override) { + this.mergeContents(override.contents, otherOverride.contents); + } else { + overrides.push(otherOverride); + } + } + for (const key of other.keys) { + if (keys.indexOf(key) === -1) { + keys.push(key); + } } } - source._overrides = overrides; - source._keys = arrays.distinct([...source._keys, ...target.keys]); + return new ConfigurationModel(contents, keys, overrides); + } + + freeze(): ConfigurationModel { + this.isFrozen = true; + return this; + } + + private mergeContents(source: any, target: any): void { + for (const key of Object.keys(target)) { + if (key in source) { + if (types.isObject(source[key]) && types.isObject(target[key])) { + this.mergeContents(source[key], target[key]); + continue; + } + } + source[key] = objects.clone(target[key]); + } + } + + private checkAndFreeze(data: T): T { + if (this.isFrozen && !Object.isFrozen(data)) { + return objects.deepFreeze(data); + } + return data; } private getContentsForOverrideIdentifer(identifier: string): any { - for (const override of this._overrides) { + for (const override of this.overrides) { if (override.identifiers.indexOf(identifier) !== -1) { return override.contents; } @@ -116,25 +130,6 @@ export class ConfigurationModel implements IConfigurationModel { 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; - } - toJSON(): IConfigurationModel { return { contents: this.contents, @@ -142,50 +137,90 @@ export class ConfigurationModel implements IConfigurationModel { keys: this.keys }; } + + // Update methods + + public setValue(key: string, value: any) { + this.addKey(key); + addToValueTree(this.contents, key, value, e => { throw new Error(e); }); + } + + public removeValue(key: string): void { + if (this.removeKey(key)) { + removeFromValueTree(this.contents, key); + } + } + + 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); }); + } + + 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 { constructor() { - super(getDefaultValues()); - this._keys = getConfigurationKeys(); - this._overrides = Object.keys(this._contents) - .filter(key => OVERRIDE_PROPERTY_PATTERN.test(key)) - .map(key => { - return { + const contents = getDefaultValues(); + const keys = getConfigurationKeys(); + const overrides: IOverrides[] = []; + for (const key of Object.keys(contents)) { + if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + overrides.push({ 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); + contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)) + }); + } } + super(contents, keys, overrides); + } +} + +export class ConfigurationModelParser { + + private _configurationModel: ConfigurationModel = null; + private _parseErrors: any[] = []; + + constructor(protected readonly _name: string) { } + + get configurationModel(): ConfigurationModel { + return this._configurationModel || new ConfigurationModel(); } - public get errors(): any[] { + get errors(): any[] { return this._parseErrors; } - public update(content: string): void { - let parsed: any = {}; - let overrides: Overrides[] = []; + public parse(content: string): void { + const raw = this.parseContent(content); + const configurationModel = this.parseRaw(raw); + this._configurationModel = new ConfigurationModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides); + } + + protected parseContent(content: string): any { + let raw: any = {}; let currentProperty: string = null; let currentParent: any = []; let previousParents: any[] = []; @@ -197,17 +232,6 @@ export class CustomConfigurationModel extends ConfigurationModel { } 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 = { @@ -242,88 +266,46 @@ export class CustomConfigurationModel extends ConfigurationModel { if (content) { try { json.visit(content, visitor); - parsed = currentParent[0] || {}; + raw = currentParent[0] || {}; } catch (e) { - console.error(`Error while parsing settings file ${this.name}: ${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}`)) - }; - }); + return raw; } - protected processRaw(raw: any): void { - this._contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)); - this._keys = Object.keys(raw); + protected parseRaw(raw: any): IConfigurationModel { + const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); + const keys = Object.keys(raw); + const overrides: IOverrides[] = toOverrides(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); + return { contents, keys, overrides }; } } export class Configuration { - private _globalConfiguration: ConfigurationModel; - private _workspaceConsolidatedConfiguration: ConfigurationModel; - protected _foldersConsolidatedConfigurations: StrictResourceMap; + private _workspaceConsolidatedConfiguration: ConfigurationModel = null; + private _foldersConsolidatedConfigurations: 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()) { - 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 = this._defaults.merge(this._user); - this.updateWorkspaceConsolidateConfiguration(); - this._foldersConsolidatedConfigurations = new StrictResourceMap(); - for (const folder of this.folders.keys()) { - this.mergeFolder(folder); - } - } - - private updateWorkspaceConsolidateConfiguration() { - this._workspaceConsolidatedConfiguration = this._globalConfiguration.merge(this._workspaceConfiguration).merge(this._memoryConfiguration); - } - - protected mergeFolder(folder: URI) { - this._foldersConsolidatedConfigurations.set(folder, this._workspaceConsolidatedConfiguration.merge(this.folders.get(folder))); + constructor( + private _defaultConfiguration: ConfigurationModel, + private _userConfiguration: ConfigurationModel, + private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), + private _folderConfigurations: StrictResourceMap = new StrictResourceMap(), + private _memoryConfiguration: ConfigurationModel = new ConfigurationModel(), + private _memoryConfigurationByResource: StrictResourceMap = new StrictResourceMap()) { } getSection(section: string = '', overrides: IConfigurationOverrides, workspace: Workspace): C { const configModel = this.getConsolidateConfigurationModel(overrides, workspace); - return objects.clone(section ? configModel.getSectionContents(section) : configModel.contents); + return section ? configModel.getSectionContents(section) : configModel.contents; } getValue(key: string, overrides: IConfigurationOverrides, workspace: Workspace): any { const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); - return objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)); + return getConfigurationValue(consolidateConfigurationModel.contents, key); } updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void { @@ -345,7 +327,7 @@ export class Configuration { } if (!overrides.resource) { - this.updateWorkspaceConsolidateConfiguration(); + this._workspaceConsolidatedConfiguration = null; } } @@ -360,14 +342,14 @@ export class Configuration { const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource, workspace); const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; - return objects.clone({ - 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), + return { + default: getConfigurationValue(overrides.overrideIdentifier ? this._defaultConfiguration.override(overrides.overrideIdentifier).contents : this._defaultConfiguration.contents, key), + user: getConfigurationValue(overrides.overrideIdentifier ? this._userConfiguration.override(overrides.overrideIdentifier).contents : this._userConfiguration.contents, key), workspace: 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(workspace: Workspace): { @@ -377,12 +359,64 @@ export class Configuration { workspaceFolder: string[]; } { const folderConfigurationModel = this.getFolderConfigurationModelForResource(null, workspace); - return objects.clone({ - default: this._defaults.keys, - user: this._user.keys, + return { + default: this._defaultConfiguration.keys, + user: this._userConfiguration.keys, workspace: this._workspaceConfiguration.keys, workspaceFolder: folderConfigurationModel ? folderConfigurationModel.keys : [] - }); + }; + } + + updateDefaultConfiguration(defaultConfiguration: ConfigurationModel): void { + this._defaultConfiguration = defaultConfiguration; + this._workspaceConsolidatedConfiguration = null; + this._foldersConsolidatedConfigurations.clear(); + } + + updateUserConfiguration(userConfiguration: ConfigurationModel): void { + this._userConfiguration = userConfiguration; + this._workspaceConsolidatedConfiguration = null; + this._foldersConsolidatedConfigurations.clear(); + } + + updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): void { + this._workspaceConfiguration = workspaceConfiguration; + this._workspaceConsolidatedConfiguration = null; + this._foldersConsolidatedConfigurations.clear(); + } + + updateFolderConfiguration(resource: URI, configuration: ConfigurationModel): void { + this._folderConfigurations.set(resource, configuration); + this._foldersConsolidatedConfigurations.delete(resource); + } + + deleteFolderConfiguration(resource: URI): void { + this.folders.delete(resource); + this._foldersConsolidatedConfigurations.delete(resource); + } + + get defaults(): ConfigurationModel { + return this._defaultConfiguration; + } + + get user(): ConfigurationModel { + return this._userConfiguration; + } + + get workspace(): ConfigurationModel { + return this._workspaceConfiguration; + } + + protected get folders(): StrictResourceMap { + return this._folderConfigurations; + } + + private get memory(): ConfigurationModel { + return this._memoryConfiguration; + } + + private get memoryByResource(): StrictResourceMap { + return this._memoryConfigurationByResource; } private getConsolidateConfigurationModel(overrides: IConfigurationOverrides, workspace: Workspace): ConfigurationModel { @@ -391,62 +425,91 @@ export class Configuration { } private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace): ConfigurationModel { - if (!workspace) { - return this._globalConfiguration; - } + let consolidateConfiguration = this.getWorkspaceConsolidatedConfiguration(); - if (!resource) { - return this._workspaceConsolidatedConfiguration; - } - - let consolidateConfiguration = this._workspaceConsolidatedConfiguration; - const root = 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); + if (workspace && resource) { + const root = workspace.getFolder(resource); + if (root) { + consolidateConfiguration = this.getFolderConsolidatedConfiguration(root.uri) || consolidateConfiguration; + } + const memoryConfigurationForResource = this._memoryConfigurationByResource.get(resource); + if (memoryConfigurationForResource) { + consolidateConfiguration = consolidateConfiguration.merge(memoryConfigurationForResource); + } } return consolidateConfiguration; } - private getFolderConfigurationModelForResource(resource: URI, workspace: Workspace): ConfigurationModel { - if (!workspace || !resource) { - return null; + private getWorkspaceConsolidatedConfiguration(): ConfigurationModel { + if (!this._workspaceConsolidatedConfiguration) { + this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this._userConfiguration).merge(this._workspaceConfiguration).merge(this._memoryConfiguration).freeze(); } - - const root = workspace.getFolder(resource); - return root ? this.folders.get(root.uri) : null; + return this._workspaceConsolidatedConfiguration; } - public toData(): IConfigurationData { + private getFolderConsolidatedConfiguration(folder: URI): ConfigurationModel { + let folderConsolidatedConfiguration = this._foldersConsolidatedConfigurations.get(folder); + if (!folderConsolidatedConfiguration) { + folderConsolidatedConfiguration = this.getWorkspaceConsolidatedConfiguration().merge(this._folderConfigurations.get(folder)).freeze(); + this._foldersConsolidatedConfigurations.set(folder, folderConsolidatedConfiguration); + } + return folderConsolidatedConfiguration; + } + + private getFolderConfigurationModelForResource(resource: URI, workspace: Workspace): ConfigurationModel { + if (workspace && resource) { + const root = workspace.getFolder(resource); + if (root) { + return this._folderConfigurations.get(root.uri); + } + } + return null; + } + + toData(): IConfigurationData { return { defaults: { - contents: this._defaults.contents, - overrides: this._defaults.overrides, - keys: this._defaults.keys + contents: this._defaultConfiguration.contents, + overrides: this._defaultConfiguration.overrides, + keys: this._defaultConfiguration.keys }, user: { - contents: this._user.contents, - overrides: this._user.overrides, - keys: this._user.keys + contents: this._userConfiguration.contents, + overrides: this._userConfiguration.overrides, + keys: this._userConfiguration.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); + folders: this._folderConfigurations.keys().reduce((result, folder) => { + const { contents, overrides, keys } = this._folderConfigurations.get(folder); result[folder.toString()] = { contents, overrides, keys }; return result; }, Object.create({})) }; } + allKeys(workspace: Workspace): string[] { + let keys = this.keys(workspace); + let all = [...keys.default]; + const addKeys = (keys) => { + for (const key of keys) { + if (all.indexOf(key) === -1) { + all.push(key); + } + } + }; + addKeys(keys.user); + addKeys(keys.workspace); + for (const resource of this.folders.keys()) { + addKeys(this.folders.get(resource).keys); + } + return all; + } + public static parse(data: IConfigurationData): Configuration { const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults); const userConfiguration = Configuration.parseConfigurationModel(data.user); @@ -455,11 +518,11 @@ export class Configuration { 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()); + return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders); } private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel { - return new ConfigurationModel(model.contents, model.keys, model.overrides); + return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze(); } } @@ -487,29 +550,6 @@ export class AbstractConfigurationChangeEvent { } } -export class AllKeysConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent { - - private _changedConfiguration: ConfigurationModel = null; - - constructor(readonly affectedKeys: string[], readonly source: ConfigurationTarget, readonly sourceConfig: any) { super(); } - - get changedConfiguration(): ConfigurationModel { - if (!this._changedConfiguration) { - this._changedConfiguration = new ConfigurationModel(); - this.updateKeys(this._changedConfiguration, this.affectedKeys); - } - return this._changedConfiguration; - } - - get changedConfigurationByResource(): StrictResourceMap { - return new StrictResourceMap(); - } - - affectsConfiguration(config: string, resource?: URI): boolean { - return this.doesConfigurationContains(this.changedConfiguration, config); - } -} - export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent { private _source: ConfigurationTarget; @@ -554,7 +594,7 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i get affectedKeys(): string[] { const keys = [...this._changedConfiguration.keys]; this._changedConfigurationByResource.forEach(model => keys.push(...model.keys)); - return keys; + return arrays.distinct(keys); } get source(): ConfigurationTarget { diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index f0eb0575667..383fce19bc8 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -27,7 +27,7 @@ export interface IConfigurationRegistry { /** * Register multiple configurations to the registry. */ - registerConfigurations(configurations: IConfigurationNode[], validate?: boolean): void; + registerConfigurations(configurations: IConfigurationNode[], defaultConfigurations: IDefaultConfigurationExtension[], validate?: boolean): void; /** * Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values. @@ -35,8 +35,6 @@ export interface IConfigurationRegistry { */ notifyConfigurationSchemaUpdated(configuration: IConfigurationNode): void; - registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void; - /** * Event that fires whenver a configuration has been * registered. @@ -116,10 +114,15 @@ class ConfigurationRegistry implements IConfigurationRegistry { } public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void { - this.registerConfigurations([configuration], validate); + this.registerConfigurations([configuration], [], validate); } - public registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void { + public registerConfigurations(configurations: IConfigurationNode[], defaultConfigurations: IDefaultConfigurationExtension[], validate: boolean = true): void { + const configurationNode = this.toConfiguration(defaultConfigurations); + if (configurationNode) { + configurations.push(configurationNode); + } + const properties: string[] = []; configurations.forEach(configuration => { properties.push(...this.validateAndRegisterProperties(configuration, validate)); // fills in defaults @@ -140,7 +143,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updateOverridePropertyPatternKey(); } - public registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void { + private toConfiguration(defaultConfigurations: IDefaultConfigurationExtension[]): IConfigurationNode { const configurationNode: IConfigurationNode = { id: 'defaultOverrides', title: nls.localize('defaultConfigurations.title', "Default Configuration Overrides"), @@ -159,9 +162,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } } - if (Object.keys(configurationNode.properties).length) { - this.registerConfiguration(configurationNode, false); - } + return Object.keys(configurationNode.properties).length ? configurationNode : null; } private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false): string[] { diff --git a/src/vs/platform/configuration/node/configuration.ts b/src/vs/platform/configuration/node/configuration.ts new file mode 100644 index 00000000000..2fd2a77c115 --- /dev/null +++ b/src/vs/platform/configuration/node/configuration.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigWatcher } from 'vs/base/node/config'; +import Event, { Emitter } from 'vs/base/common/event'; +import { TPromise } from 'vs/base/common/winjs.base'; + +export class UserConfiguration extends Disposable { + + private userConfigModelWatcher: ConfigWatcher; + + private _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + + constructor(settingsPath: string) { + super(); + this.userConfigModelWatcher = new ConfigWatcher(settingsPath, { + changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new ConfigurationModelParser(settingsPath), parse: (content: string, parseErrors: any[]) => { + const userConfigModelParser = new ConfigurationModelParser(settingsPath); + userConfigModelParser.parse(content); + parseErrors = [...userConfigModelParser.errors]; + return userConfigModelParser; + } + }); + this._register(this.userConfigModelWatcher); + + // Listeners + this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this._onDidChangeConfiguration.fire(this.configurationModel))); + } + + get configurationModel(): ConfigurationModel { + return this.userConfigModelWatcher.getConfig().configurationModel; + } + + reload(): TPromise { + return new TPromise(c => this.userConfigModelWatcher.reload(() => c(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 baa6c14f8d0..d13e0ed5d26 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -4,25 +4,24 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -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, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration'; -import { CustomConfigurationModel, DefaultConfigurationModel, ConfigurationModel, Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { DefaultConfigurationModel, 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 { TPromise } from 'vs/base/common/winjs.base'; import { equals } from 'vs/base/common/objects'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { UserConfiguration } from 'vs/platform/configuration/node/configuration'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { _serviceBrand: any; private _configuration: Configuration; - private userConfigModelWatcher: ConfigWatcher; + private userConfiguration: UserConfiguration; private _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; @@ -32,19 +31,12 @@ export class ConfigurationService extends Disposable implements IConfigurationSe ) { 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); - parseErrors = [...userConfigModel.errors]; - return userConfigModel; - } - }); - this._register(this.userConfigModelWatcher); + this.userConfiguration = this._register(new UserConfiguration(environmentService.appSettingsPath)); this.reset(); // Listeners - this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this.onDidUpdateConfigModel())); + this._register(this.userConfiguration.onDidChangeConfiguration(() => this.onDidChangeUserConfiguration())); this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDidRegisterConfiguration(configurationProperties))); } @@ -99,12 +91,12 @@ export class ConfigurationService extends Disposable implements IConfigurationSe reloadConfiguration(folder?: IWorkspaceFolder): TPromise { return folder ? TPromise.as(null) : - new TPromise((c, e) => this.userConfigModelWatcher.reload(() => c(this.onDidUpdateConfigModel()))); + this.userConfiguration.reload().then(() => this.onDidChangeUserConfiguration()); } - private onDidUpdateConfigModel(): void { + private onDidChangeUserConfiguration(): void { let changedKeys = []; - const { added, updated, removed } = compare(this._configuration.user, this.userConfigModelWatcher.getConfig()); + const { added, updated, removed } = compare(this._configuration.user, this.userConfiguration.configurationModel); changedKeys = [...added, ...updated, ...removed]; if (changedKeys.length) { const oldConfiguartion = this._configuration; @@ -123,7 +115,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe private reset(): void { const defaults = new DefaultConfigurationModel(); - const user = this.userConfigModelWatcher.getConfig(); + const user = this.userConfiguration.configurationModel; this._configuration = new Configuration(defaults, user); } diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 0bf2c7ea154..6de854a9778 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -5,11 +5,10 @@ 'use strict'; import * as assert from 'assert'; -import { ConfigurationModel, CustomConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, ConfigurationModelParser } 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', () => { @@ -279,86 +278,97 @@ suite('CustomConfigurationModel', () => { }); 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); + let base = new ConfigurationModelParser('base'); + base.parse(JSON.stringify({ 'a': 1, 'b': 2 })); + + let add = new ConfigurationModelParser('add'); + add.parse(JSON.stringify({ 'a': 3, 'c': 4 })); + + let result = base.configurationModel.merge(add.configurationModel); 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); + let base = new ConfigurationModelParser('base'); + base.parse(JSON.stringify({ 'a': 1, 'b': 2 })); + let add = new ConfigurationModelParser('add'); + let result = base.configurationModel.merge(add.configurationModel); 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); + base = new ConfigurationModelParser('base'); + add = new ConfigurationModelParser('add'); + add.parse(JSON.stringify({ 'a': 1, 'b': 2 })); + result = base.configurationModel.merge(add.configurationModel); assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); - base = new CustomConfigurationModel(null); - add = new CustomConfigurationModel(null); - result = base.merge(add); + base = new ConfigurationModelParser('base'); + add = new ConfigurationModelParser('add'); + result = base.configurationModel.merge(add.configurationModel); 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); + let base = new ConfigurationModelParser('base'); + base.parse(JSON.stringify({ 'a': { 'b': 1 } })); + let add = new ConfigurationModelParser('add'); + add.parse(JSON.stringify({ 'a': { 'b': 2 } })); + let result = base.configurationModel.merge(add.configurationModel); 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); + let testObject = new ConfigurationModelParser('test'); + testObject.parse(JSON.stringify({ 'a': 1 })); + assert.deepEqual(testObject.configurationModel.getSectionContents('a'), 1); - testObject = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); - assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 }); + testObject.parse(JSON.stringify({ 'a': { 'b': 1 } })); + assert.deepEqual(testObject.configurationModel.getSectionContents('a'), { 'b': 1 }); }); test('Test contents are undefined for non existing properties', () => { - const testObject = new CustomConfigurationModel(JSON.stringify({ + const testObject = new ConfigurationModelParser('test'); + testObject.parse(JSON.stringify({ awesome: true })); - assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); + assert.deepEqual(testObject.configurationModel.getSectionContents('unknownproperty'), undefined); }); test('Test contents are undefined for undefined config', () => { - const testObject = new CustomConfigurationModel(null); + const testObject = new ConfigurationModelParser('test'); - assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); + assert.deepEqual(testObject.configurationModel.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 } })); + const testObject = new ConfigurationModelParser('test'); + testObject.parse(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } })); - assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } }); + assert.deepEqual(testObject.configurationModel.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } }); }); test('Test configWithOverrides gives empty contents', () => { - const testObject = new CustomConfigurationModel(null); + const testObject = new ConfigurationModelParser('test'); - assert.deepEqual(testObject.override('b').contents, {}); + assert.deepEqual(testObject.configurationModel.override('b').contents, {}); }); test('Test update with empty data', () => { - const testObject = new CustomConfigurationModel(); - testObject.update(''); + const testObject = new ConfigurationModelParser('test'); + testObject.parse(''); - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); + assert.deepEqual(testObject.configurationModel.contents, {}); + assert.deepEqual(testObject.configurationModel.keys, []); - testObject.update(null); + testObject.parse(null); - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); + assert.deepEqual(testObject.configurationModel.contents, {}); + assert.deepEqual(testObject.configurationModel.keys, []); - testObject.update(undefined); + testObject.parse(undefined); - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); + assert.deepEqual(testObject.configurationModel.contents, {}); + assert.deepEqual(testObject.configurationModel.keys, []); }); test('Test registering the same property again', () => { @@ -503,48 +513,4 @@ suite('ConfigurationChangeEvent', () => { assert.ok(actual.affectsConfiguration('[markdown]', 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.affectsConfiguration('window.zoomLevel')); - assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window.zoomLevel', 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.affectsConfiguration('window.restoreWindows')); - assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); - assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file1'))); - - 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.affectsConfiguration('window')); - assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); - - 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.affectsConfiguration('workbench.editor')); - assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2'))); - assert.ok(testObject.affectsConfiguration('workbench.editor', 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.affectsConfiguration('files')); - assert.ok(!testObject.affectsConfiguration('files', URI.file('file1'))); - }); }); \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index e7fa2a2066c..44833b91369 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -12,7 +12,7 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; import { IConfigurationData, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { Configuration, ConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { Configuration, ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; import { StrictResourceMap } from 'vs/base/common/map'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 7b3740aff84..577105b0535 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -75,7 +75,7 @@ function openWorkbench(configuration: IWindowConfiguration): TPromise { // Since the configuration service is one of the core services that is used in so many places, we initialize it // right before startup of the workbench shell to have its data ready for consumers - return createAndInitializeWorkspaceService(configuration, environmentService, mainServices.get(IWorkspacesService)).then(workspaceService => { + return createAndInitializeWorkspaceService(configuration, environmentService).then(workspaceService => { const timerService = new TimerService((window).MonacoEnvironment.timers as IInitData, workspaceService.getWorkbenchState() === WorkbenchState.EMPTY); const storageService = createStorageService(workspaceService, environmentService); @@ -107,9 +107,9 @@ function openWorkbench(configuration: IWindowConfiguration): TPromise { }); } -function createAndInitializeWorkspaceService(configuration: IWindowConfiguration, environmentService: EnvironmentService, workspacesService: IWorkspacesService): TPromise { +function createAndInitializeWorkspaceService(configuration: IWindowConfiguration, environmentService: EnvironmentService): TPromise { return validateSingleFolderPath(configuration).then(() => { - const workspaceService = new WorkspaceService(environmentService, workspacesService); + const workspaceService = new WorkspaceService(environmentService); return workspaceService.initialize(configuration.workspace || configuration.folderPath || configuration).then(() => workspaceService, error => workspaceService); }); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index d6f6475490b..4ca66967670 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -6,9 +6,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export const CONFIG_DEFAULT_NAME = 'settings'; -export const WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME = '.vscode'; -export const WORKSPACE_CONFIG_DEFAULT_PATH = `${WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME}/${CONFIG_DEFAULT_NAME}.json`; +export const FOLDER_CONFIG_FOLDER_NAME = '.vscode'; +export const FOLDER_SETTINGS_NAME = 'settings'; +export const FOLDER_SETTINGS_PATH = `${FOLDER_CONFIG_FOLDER_NAME}/${FOLDER_SETTINGS_NAME}.json`; export const IWorkspaceConfigurationService = createDecorator('configurationService'); @@ -28,5 +28,5 @@ export const TASKS_CONFIGURATION_KEY = 'tasks'; export const LAUNCH_CONFIGURATION_KEY = 'launch'; export const WORKSPACE_STANDALONE_CONFIGURATIONS = Object.create(null); -WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME}/tasks.json`; -WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY] = `${WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME}/launch.json`; \ No newline at end of file +WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/tasks.json`; +WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/launch.json`; \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts index 5525cc06c01..193e8b1e9e1 100644 --- a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts @@ -52,9 +52,36 @@ const configurationEntrySchema: IJSONSchema = { } }; +let registeredDefaultConfigurations: IDefaultConfigurationExtension[] = []; + +// 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 => { + registeredDefaultConfigurations = extensions.map(extension => { + const id = extension.description.id; + const name = extension.description.name; + const defaults = objects.clone(extension.value); + return { + id, name, defaults + }; + }); +}); +// END VSCode extension point `configurationDefaults` + // BEGIN VSCode extension point `configuration` -const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configuration', [], { +const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configuration', [defaultConfigurationExtPoint], { description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'), oneOf: [ configurationEntrySchema, @@ -89,36 +116,10 @@ configurationExtPoint.setHandler(extensions => { value.forEach(v => handleConfiguration(v, id, extension)); } } - configurationRegistry.registerConfigurations(configurations, false); + configurationRegistry.registerConfigurations(configurations, registeredDefaultConfigurations, 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, extension: IExtensionPointUser): void { let properties = configuration.properties; if (properties) { diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index be16b2d69c6..0c4276fbff5 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -4,190 +4,130 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { clone, equals } from 'vs/base/common/objects'; -import { compare, toValuesTree, IConfigurationChangeEvent, ConfigurationTarget, IConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationModel, Configuration as BaseConfiguration, CustomConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { equals } from 'vs/base/common/objects'; +import { compare, toValuesTree, IConfigurationChangeEvent, ConfigurationTarget, IConfigurationModel, IConfigurationOverrides, IOverrides } from 'vs/platform/configuration/common/configuration'; +import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationChangeEvent, ConfigurationModel, AbstractConfigurationChangeEvent } 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'; 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'; -import { distinct } from 'vs/base/common/arrays'; -export class WorkspaceConfigurationModel extends CustomConfigurationModel { +export class WorkspaceSettingsModel extends ConfigurationModel { - private _raw: any; - private _folders: IStoredWorkspaceFolder[]; - private _worksapaceSettings: WorkspaceSettingsModel; + private _unsupportedKeys: string[]; - public update(content: string): void { - super.update(content); - this._folders = (this._raw['folders'] || []) as IStoredWorkspaceFolder[]; - this._worksapaceSettings = new WorkspaceSettingsModel(this._raw['settings'] || {}); + constructor(contents: any, keys: string[], overrides: IOverrides[], unsupportedKeys: string[]) { + super(contents, keys, overrides); + this._unsupportedKeys = unsupportedKeys; + } + + public get unsupportedKeys(): string[] { + return this._unsupportedKeys; + } + +} + +export class WorkspaceConfigurationModelParser extends ConfigurationModelParser { + + private _folders: IStoredWorkspaceFolder[] = []; + private _workspaceSettingsModelParser: FolderSettingsModelParser; + + constructor(name: string) { + super(name); + this._workspaceSettingsModelParser = new FolderSettingsModelParser(name); } get folders(): IStoredWorkspaceFolder[] { return this._folders; } - get workspaceConfiguration(): ConfigurationModel { - return this._worksapaceSettings || new WorkspaceSettingsModel({}); - } - get workspaceSettingsModel(): WorkspaceSettingsModel { - return this._worksapaceSettings || new WorkspaceSettingsModel({}); + return this._workspaceSettingsModelParser.folderSettingsModel; } - protected processRaw(raw: any): void { - this._raw = raw; - super.processRaw(raw); + reprocessWorkspaceSettings(): void { + this._workspaceSettingsModelParser.reprocess(); } + protected parseRaw(raw: any): IConfigurationModel { + this._folders = (raw['folders'] || []) as IStoredWorkspaceFolder[]; + this._workspaceSettingsModelParser.parse(raw['settings']); + return super.parseRaw(raw); + } +} + +export class StandaloneConfigurationModelParser extends ConfigurationModelParser { + + constructor(name: string, private readonly scope: string) { + super(name); + } + + protected parseRaw(raw: any): IConfigurationModel { + const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); + const scopedContents = Object.create(null); + scopedContents[this.scope] = contents; + const keys = Object.keys(raw).map(key => `${this.scope}.${key}`); + return { contents: scopedContents, keys, overrides: [] }; + } } -export class WorkspaceSettingsModel extends ConfigurationModel { +export class FolderSettingsModelParser extends ConfigurationModelParser { private _raw: any; - private _unsupportedKeys: string[]; + private _workspaceSettingsModel: WorkspaceSettingsModel; - constructor(raw: any) { - super(); - this._raw = raw; - this.update(); + constructor(name: string, private configurationScope?: ConfigurationScope) { + super(name); } - public get unsupportedKeys(): string[] { - return this._unsupportedKeys || []; + parse(content: string | any): void { + this._raw = typeof content === 'string' ? this.parseContent(content) : content; + this.parseWorkspaceSettings(this._raw); } - update(): void { - const { unsupportedKeys, contents } = processWorkspaceSettings(this._raw); - this._unsupportedKeys = unsupportedKeys; - this._contents = toValuesTree(contents, message => console.error(`Conflict in workspace settings file: ${message}`)); - this._keys = Object.keys(contents); - } -} - -export class ScopedConfigurationModel extends CustomConfigurationModel { - - constructor(content: string, name: string, public readonly scope: string) { - super(null, name); - this.update(content); + get configurationModel(): ConfigurationModel { + return this._workspaceSettingsModel || new WorkspaceSettingsModel({}, [], [], []); } - public update(content: string): void { - super.update(content); - const contents = Object.create(null); - contents[this.scope] = this.contents; - this._contents = contents; + get folderSettingsModel(): WorkspaceSettingsModel { + return this.configurationModel; } -} - -function processWorkspaceSettings(content: any): { unsupportedKeys: string[], contents: any } { - const isNotExecutable = (key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): boolean => { - const propertySchema = configurationProperties[key]; - if (!propertySchema) { - return true; // Unknown propertis are ignored from checks - } - return !propertySchema.isExecutable; - }; - - const unsupportedKeys = []; - const contents = {}; - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - for (let key in content) { - if (isNotExecutable(key, configurationProperties)) { - contents[key] = content[key]; - } else { - unsupportedKeys.push(key); - } - } - return { contents, unsupportedKeys }; -} - -export class FolderSettingsModel extends CustomConfigurationModel { - - private _raw: any; - private _unsupportedKeys: string[]; - - protected processRaw(raw: any): void { - this._raw = raw; - const { unsupportedKeys, contents } = processWorkspaceSettings(raw); - this._unsupportedKeys = unsupportedKeys; - return super.processRaw(contents); + reprocess(): void { + this.parse(this._raw); } - public reprocess(): void { - this.processRaw(this._raw); - } - - public get unsupportedKeys(): string[] { - return this._unsupportedKeys || []; - } - - public createWorkspaceConfigurationModel(): ConfigurationModel { - return this.createScopedConfigurationModel(ConfigurationScope.WINDOW); - } - - public createFolderScopedConfigurationModel(): ConfigurationModel { - return this.createScopedConfigurationModel(ConfigurationScope.RESOURCE); - } - - private createScopedConfigurationModel(scope: ConfigurationScope): ConfigurationModel { - const workspaceRaw = {}; + private parseWorkspaceSettings(rawSettings: any): void { + const unsupportedKeys = []; + const rawWorkspaceSettings = {}; const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - for (let key in this._raw) { - if (this.getScope(key, configurationProperties) === scope) { - workspaceRaw[key] = this._raw[key]; + for (let key in rawSettings) { + if (this.isNotExecutable(key, configurationProperties)) { + if (this.configurationScope === void 0 || this.getScope(key, configurationProperties) === this.configurationScope) { + rawWorkspaceSettings[key] = rawSettings[key]; + } + } else { + unsupportedKeys.push(key); } } - const workspaceContents = toValuesTree(workspaceRaw, message => console.error(`Conflict in workspace settings file: ${message}`)); - const workspaceKeys = Object.keys(workspaceRaw); - return new ConfigurationModel(workspaceContents, workspaceKeys, clone(this._overrides)); + const configurationModel = this.parseRaw(rawWorkspaceSettings); + this._workspaceSettingsModel = new WorkspaceSettingsModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides, unsupportedKeys); } private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope { const propertySchema = configurationProperties[key]; return propertySchema ? propertySchema.scope : ConfigurationScope.WINDOW; } -} -export class FolderConfigurationModel extends CustomConfigurationModel { - - constructor(public readonly workspaceSettingsConfig: FolderSettingsModel, private scopedConfigs: ScopedConfigurationModel[], private scope: ConfigurationScope) { - super(); - this.consolidate(); - } - - private consolidate(): void { - this._contents = {}; - this._overrides = []; - - this.doMerge(this, ConfigurationScope.WINDOW === this.scope ? this.workspaceSettingsConfig : this.workspaceSettingsConfig.createFolderScopedConfigurationModel()); - for (const configModel of this.scopedConfigs) { - this.doMerge(this, configModel); + private isNotExecutable(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): boolean { + const propertySchema = configurationProperties[key]; + if (!propertySchema) { + return true; // Unknown propertis are ignored from checks } - } - - public get keys(): string[] { - const keys: string[] = [...this.workspaceSettingsConfig.keys]; - this.scopedConfigs.forEach(scopedConfigModel => { - Object.keys(WORKSPACE_STANDALONE_CONFIGURATIONS).forEach(scope => { - if (scopedConfigModel.scope === scope) { - keys.push(...scopedConfigModel.keys.map(key => `${scope}.${key}`)); - } - }); - }); - return keys; - } - - public update(): void { - this.workspaceSettingsConfig.reprocess(); - this.consolidate(); + return !propertySchema.isExecutable; } } @@ -197,7 +137,7 @@ export class Configuration extends BaseConfiguration { defaults: ConfigurationModel, user: ConfigurationModel, workspaceConfiguration: ConfigurationModel, - protected folders: StrictResourceMap, + folders: StrictResourceMap, memoryConfiguration: ConfigurationModel, memoryConfigurationByResource: StrictResourceMap, private readonly _workspace: Workspace) { @@ -232,77 +172,55 @@ export class Configuration extends BaseConfiguration { return super.keys(this._workspace); } - updateDefaultConfiguration(defaults: ConfigurationModel): void { - this._defaults = defaults; - this.merge(); - } - - updateUserConfiguration(user: ConfigurationModel): ConfigurationChangeEvent { - const { added, updated, removed } = compare(this._user, user); + compareAndUpdateUserConfiguration(user: ConfigurationModel): ConfigurationChangeEvent { + const { added, updated, removed } = compare(this.user, user); 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); - - this._user = user; - this.merge(); - - changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this.getValue(key))); + const oldValues = changedKeys.map(key => this.getValue(key)); + super.updateUserConfiguration(user); + changedKeys = changedKeys.filter((key, index) => !equals(oldValues[index], this.getValue(key))); } return new ConfigurationChangeEvent().change(changedKeys); } - updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): ConfigurationChangeEvent { - const { added, updated, removed } = compare(this._workspaceConfiguration, workspaceConfiguration); + compareAndUpdateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): ConfigurationChangeEvent { + const { added, updated, removed } = compare(this.workspace, workspaceConfiguration); 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); - - this._workspaceConfiguration = workspaceConfiguration; - this.merge(); - - changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this.getValue(key))); + const oldValues = changedKeys.map(key => this.getValue(key)); + super.updateWorkspaceConfiguration(workspaceConfiguration); + changedKeys = changedKeys.filter((key, index) => !equals(oldValues[index], this.getValue(key))); } return new ConfigurationChangeEvent().change(changedKeys); } - updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel): ConfigurationChangeEvent { + compareAndUpdateFolderConfiguration(resource: URI, folderConfiguration: ConfigurationModel): ConfigurationChangeEvent { const currentFolderConfiguration = this.folders.get(resource); - if (currentFolderConfiguration) { - const { added, updated, removed } = compare(currentFolderConfiguration, configuration); + const { added, updated, removed } = compare(currentFolderConfiguration, folderConfiguration); 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); - - this.folders.set(resource, configuration); - this.mergeFolder(resource); - - changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key, { resource }), this.getValue(key, { resource }))); + const oldValues = changedKeys.map(key => this.getValue(key, { resource })); + super.updateFolderConfiguration(resource, folderConfiguration); + changedKeys = changedKeys.filter((key, index) => !equals(oldValues[index], this.getValue(key, { resource }))); } return new ConfigurationChangeEvent().change(changedKeys, resource); + } else { + super.updateFolderConfiguration(resource, folderConfiguration); + return new ConfigurationChangeEvent().change(folderConfiguration.keys, resource); } - - this.folders.set(resource, configuration); - this.mergeFolder(resource); - return new ConfigurationChangeEvent().change(configuration.keys, resource); } - deleteFolderConfiguration(folder: URI): ConfigurationChangeEvent { + compareAndDeleteFolderConfiguration(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 new ConfigurationChangeEvent(); } - const keys = this.folders.get(folder).keys; - this.folders.delete(folder); - this._foldersConsolidatedConfigurations.delete(folder); + super.deleteFolderConfiguration(folder); return new ConfigurationChangeEvent().change(keys, folder); } - getFolderConfigurationModel(folder: URI): FolderConfigurationModel { - return this.folders.get(folder); - } - compare(other: Configuration): string[] { let from = other.allKeys(); let to = this.allKeys(); @@ -323,12 +241,34 @@ export class Configuration extends BaseConfiguration { } allKeys(): string[] { - let keys = this.keys(); - let all = [...keys.default, ...keys.user, ...keys.workspace]; - for (const resource of this.folders.keys()) { - all.push(...this.folders.get(resource).keys); + return super.allKeys(this._workspace); + } +} + +export class AllKeysConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent { + + private _changedConfiguration: ConfigurationModel = null; + + constructor(private _configuration: Configuration, readonly source: ConfigurationTarget, readonly sourceConfig: any) { super(); } + + get changedConfiguration(): ConfigurationModel { + if (!this._changedConfiguration) { + this._changedConfiguration = new ConfigurationModel(); + this.updateKeys(this._changedConfiguration, this.affectedKeys); } - return distinct(all); + return this._changedConfiguration; + } + + get changedConfigurationByResource(): StrictResourceMap { + return new StrictResourceMap(); + } + + get affectedKeys(): string[] { + return this._configuration.allKeys(); + } + + affectsConfiguration(config: string, resource?: URI): boolean { + return this.doesConfigurationContains(this.changedConfiguration, config); } } diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configuration.ts index 3692195001f..16b437d3e78 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configuration.ts @@ -15,13 +15,14 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { isLinux } from 'vs/base/common/platform'; import { ConfigWatcher } from 'vs/base/node/config'; -import { CustomConfigurationModel, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; -import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel, WorkspaceSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels'; -import { WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; -import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; +import { WorkspaceConfigurationModelParser, FolderSettingsModelParser, StandaloneConfigurationModelParser, WorkspaceSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels'; +import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspace, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import * as extfs from 'vs/base/node/extfs'; import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; +import { WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; // node.hs helper functions @@ -73,7 +74,7 @@ function resolveStat(resource: URI): TPromise { export class WorkspaceConfiguration extends Disposable { private _workspaceConfigPath: URI; - private _workspaceConfigurationWatcher: ConfigWatcher; + private _workspaceConfigurationWatcher: ConfigWatcher; private _workspaceConfigurationWatcherDisposables: IDisposable[] = []; private _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); @@ -88,12 +89,15 @@ export class WorkspaceConfiguration extends Disposable { this.stopListeningToWatcher(); return new TPromise((c, e) => { + const defaultConfig = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath); + defaultConfig.parse(JSON.stringify({ folders: [] } as IStoredWorkspace, null, '\t')); this._workspaceConfigurationWatcher = new ConfigWatcher(this._workspaceConfigPath.fsPath, { changeBufferDelay: 300, onError: error => errors.onUnexpectedError(error), - defaultConfig: new WorkspaceConfigurationModel(JSON.stringify({ folders: [] } as IStoredWorkspace, null, '\t'), this._workspaceConfigPath.fsPath), + defaultConfig, parse: (content: string, parseErrors: any[]) => { - const workspaceConfigurationModel = new WorkspaceConfigurationModel(content, this._workspaceConfigPath.fsPath); + const workspaceConfigurationModel = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath); + workspaceConfigurationModel.parse(content); parseErrors = [...workspaceConfigurationModel.errors]; return workspaceConfigurationModel; }, initCallback: () => c(null) @@ -102,8 +106,8 @@ export class WorkspaceConfiguration extends Disposable { }); } - private get workspaceConfigurationModel(): WorkspaceConfigurationModel { - return this._workspaceConfigurationWatcher ? this._workspaceConfigurationWatcher.getConfig() : new WorkspaceConfigurationModel(); + private get workspaceConfigurationModelParser(): WorkspaceConfigurationModelParser { + return this._workspaceConfigurationWatcher ? this._workspaceConfigurationWatcher.getConfig() : new WorkspaceConfigurationModelParser(this._workspaceConfigPath ? this._workspaceConfigPath.fsPath : ''); } reload(): TPromise { @@ -115,7 +119,7 @@ export class WorkspaceConfiguration extends Disposable { } getFolders(): IStoredWorkspaceFolder[] { - return this.workspaceConfigurationModel.folders; + return this.workspaceConfigurationModelParser.folders; } setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: JSONEditingService): TPromise { @@ -124,11 +128,16 @@ export class WorkspaceConfiguration extends Disposable { } getConfiguration(): ConfigurationModel { - return this.workspaceConfigurationModel.workspaceConfiguration; + return this.workspaceConfigurationModelParser.workspaceSettingsModel; } getWorkspaceSettings(): WorkspaceSettingsModel { - return this.workspaceConfigurationModel.workspaceSettingsModel; + return this.workspaceConfigurationModelParser.workspaceSettingsModel; + } + + reprocessWorkspaceSettings(): ConfigurationModel { + this.workspaceConfigurationModelParser.reprocessWorkspaceSettings(); + return this.getConfiguration(); } private listenToWatcher() { @@ -151,29 +160,64 @@ export class FolderConfiguration extends Disposable { private static RELOAD_CONFIGURATION_DELAY = 50; private bulkFetchFromWorkspacePromise: TPromise; - private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise }; + private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise }; + + private _folderSettingsModelParser: FolderSettingsModelParser; + private _standAloneConfigurations: ConfigurationModel[] = []; + private _cache: ConfigurationModel = new ConfigurationModel(); private reloadConfigurationScheduler: RunOnceScheduler; - private reloadConfigurationEventEmitter: Emitter = new Emitter(); + private reloadConfigurationEventEmitter: Emitter = new Emitter(); - constructor(private folder: URI, private configFolderRelativePath: string, private scope: ConfigurationScope) { + constructor(private folder: URI, private configFolderRelativePath: string, workbenchState: WorkbenchState) { super(); + this._folderSettingsModelParser = new FolderSettingsModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? ConfigurationScope.RESOURCE : void 0); this.workspaceFilePathToConfiguration = Object.create(null); 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 => { + this._standAloneConfigurations = Object.keys(workspaceConfigFiles).filter(key => key !== FOLDER_SETTINGS_PATH).map(key => workspaceConfigFiles[key].configurationModel); // 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); + this.consolidate(); + return this._cache; }); } - private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel }> { + reprocess(): ConfigurationModel { + const oldKeys = this.getUnsupportedKeys(); + this._folderSettingsModelParser.reprocess(); + const newKeys = this.getUnsupportedKeys(); + if (this.hasKeysChanged(oldKeys, newKeys)) { + this.consolidate(); + } + return this._cache; + } + + getUnsupportedKeys(): string[] { + return this._folderSettingsModelParser.folderSettingsModel.unsupportedKeys; + } + + private hasKeysChanged(oldKeys: string[], newKeys: string[]): boolean { + if (oldKeys.length !== newKeys.length) { + return true; + } + for (const key of oldKeys) { + if (newKeys.indexOf(key) === -1) { + return true; + } + } + return false; + } + + private consolidate(): void { + this._cache = this._folderSettingsModelParser.folderSettingsModel.merge(...this._standAloneConfigurations); + } + + private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModelParser }> { // 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 => { @@ -191,7 +235,7 @@ export class FolderConfiguration extends Disposable { }).map(stat => stat.resource)); }, err => [] /* never fail this call */) .then((contents: IContent[]) => { - contents.forEach(content => this.workspaceFilePathToConfiguration[this.toFolderRelativePath(content.resource)] = TPromise.as(this.createConfigModel(content))); + contents.forEach(content => this.workspaceFilePathToConfiguration[this.toFolderRelativePath(content.resource)] = TPromise.as(this.createConfigurationModelParser(content))); }, errors.onUnexpectedError); } @@ -200,7 +244,7 @@ export 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; @@ -237,7 +281,7 @@ export class FolderConfiguration extends Disposable { break; case FileChangeType.UPDATED: case FileChangeType.ADDED: - this.workspaceFilePathToConfiguration[workspacePath] = resolveContent(resource).then(content => this.createConfigModel(content), errors.onUnexpectedError); + this.workspaceFilePathToConfiguration[workspacePath] = resolveContent(resource).then(content => this.createConfigurationModelParser(content), errors.onUnexpectedError); affectedByChanges = true; } } @@ -258,22 +302,24 @@ export class FolderConfiguration extends Disposable { }); } - private createConfigModel(content: IContent): ConfigurationModel { + private createConfigurationModelParser(content: IContent): ConfigurationModelParser { const path = this.toFolderRelativePath(content.resource); - if (path === WORKSPACE_CONFIG_DEFAULT_PATH) { - return new FolderSettingsModel(content.value, content.resource.toString()); + if (path === FOLDER_SETTINGS_PATH) { + this._folderSettingsModelParser.parse(content.value); + return this._folderSettingsModelParser; } else { const matches = /\/([^\.]*)*\.json/.exec(path); if (matches && matches[1]) { - return new ScopedConfigurationModel(content.value, content.resource.toString(), matches[1]); + const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(content.resource.toString(), matches[1]); + standAloneConfigurationModelParser.parse(content.value); + return standAloneConfigurationModelParser; } } - - return new CustomConfigurationModel(null); + return new ConfigurationModelParser(null); } private isWorkspaceConfigurationFile(folderRelativePath: string): boolean { - return [WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY], WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY]].some(p => p === folderRelativePath); + return [FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY], WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY]].some(p => p === folderRelativePath); } private toResource(folderRelativePath: string): URI { diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index 1deacb7b526..482dbeedeb0 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -24,7 +24,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier, ConfigurationTarget } 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 { FOLDER_SETTINGS_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 { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -462,7 +462,7 @@ export class ConfigurationEditingService { return { key, jsonPath, value: config.value, resource: URI.file(this.environmentService.appSettingsPath), target }; } - const resource = this.getConfigurationFileResource(target, WORKSPACE_CONFIG_DEFAULT_PATH, overrides.resource); + const resource = this.getConfigurationFileResource(target, FOLDER_SETTINGS_PATH, overrides.resource); if (workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath) { jsonPath = ['settings', ...jsonPath]; } diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 11cd1329ab5..2d3c1a8a0a9 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -20,15 +20,14 @@ import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, import { FileChangesEvent } from 'vs/platform/files/common/files'; import { isLinux } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration'; -import { FolderConfigurationModel, Configuration, WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; -import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; -import { ConfigurationService as GlobalConfigurationService } from 'vs/platform/configuration/node/configurationService'; +import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; +import { IWorkspaceConfigurationService, FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, ConfigurationScope, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry'; import { createHash } from 'crypto'; -import { getWorkspaceLabel, IWorkspacesService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; +import { getWorkspaceLabel, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -40,6 +39,7 @@ import { JSONEditingService } from 'vs/workbench/services/configuration/node/jso import { Schemas } from 'vs/base/common/network'; import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces'; import { distinct } from 'vs/base/common/arrays'; +import { UserConfiguration } from 'vs/platform/configuration/node/configuration'; export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService { @@ -47,7 +47,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat private workspace: Workspace; private _configuration: Configuration; - private baseConfigurationService: GlobalConfigurationService; + private defaultConfiguration: DefaultConfigurationModel; + private userConfiguration: UserConfiguration; private workspaceConfiguration: WorkspaceConfiguration; private cachedFolderConfigs: StrictResourceMap; @@ -68,16 +69,17 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat private configurationEditingService: ConfigurationEditingService; private jsonEditingService: JSONEditingService; - // @ts-ignore unused injected service - constructor(private environmentService: IEnvironmentService, private workspacesService: IWorkspacesService, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) { + constructor(private environmentService: IEnvironmentService, private workspaceSettingsRootFolder: string = FOLDER_CONFIG_FOLDER_NAME) { super(); + this.defaultConfiguration = new DefaultConfigurationModel(); + this.userConfiguration = this._register(new UserConfiguration(environmentService.appSettingsPath)); this.workspaceConfiguration = this._register(new WorkspaceConfiguration()); + this._register(this.userConfiguration.onDidChangeConfiguration(() => this.onUserConfigurationChanged())); this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged())); - this.baseConfigurationService = this._register(new GlobalConfigurationService(environmentService)); - this._register(this.baseConfigurationService.onDidChangeConfiguration(e => this.onBaseConfigurationChanged(e))); this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(e => this.registerConfigurationSchemas())); + this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties))); this.workspaceEditingQueue = new Queue(); } @@ -282,7 +284,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat getUnsupportedWorkspaceKeys(): string[] { const unsupportedWorkspaceKeys = [...this.workspaceConfiguration.getWorkspaceSettings().unsupportedKeys]; for (const folder of this.workspace.folders) { - unsupportedWorkspaceKeys.push(...this._configuration.getFolderConfigurationModel(folder.uri).workspaceSettingsConfig.unsupportedKeys); + unsupportedWorkspaceKeys.push(...this.cachedFolderConfigs.get(folder.uri).getUnsupportedKeys()); } return distinct(unsupportedWorkspaceKeys); } @@ -403,7 +405,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat } private reloadUserConfiguration(key?: string): TPromise { - return this.baseConfigurationService.reloadConfiguration(); + return this.userConfiguration.reload(); } private reloadWorkspaceConfiguration(key?: string): TPromise { @@ -430,22 +432,22 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat .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)); const currentConfiguration = this._configuration; - 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.defaultConfiguration, this.userConfiguration.configurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new StrictResourceMap(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: Sandy Avoid passing null if (currentConfiguration) { const changedKeys = this._configuration.compare(currentConfiguration); this.triggerConfigurationChange(new ConfigurationChangeEvent().change(changedKeys), ConfigurationTarget.WORKSPACE); } else { - this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration.allKeys(), ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); + this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); } }); } - private getWorkspaceConfigurationModel(folderConfigurations: FolderConfigurationModel[]): ConfigurationModel { + private getWorkspaceConfigurationModel(folderConfigurations: ConfigurationModel[]): ConfigurationModel { switch (this.getWorkbenchState()) { case WorkbenchState.FOLDER: return folderConfigurations[0]; @@ -456,6 +458,17 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat } } + private onDefaultConfigurationChanged(keys: string[]): void { + this.defaultConfiguration = new DefaultConfigurationModel(); + this.registerConfigurationSchemas(); + if (this.workspace && this._configuration) { + this._configuration.updateDefaultConfiguration(this.defaultConfiguration); + this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reprocessWorkspaceSettings()); + this.workspace.folders.forEach(folder => this._configuration.updateFolderConfiguration(folder.uri, this.cachedFolderConfigs.get(folder.uri).reprocess())); + this.triggerConfigurationChange(new ConfigurationChangeEvent().change(keys), ConfigurationTarget.DEFAULT); + } + } + private registerConfigurationSchemas(): void { if (this.workspace) { const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); @@ -472,23 +485,14 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat } } - private onBaseConfigurationChanged(e: IConfigurationChangeEvent): void { - if (this.workspace && this._configuration) { - if (e.source === ConfigurationTarget.DEFAULT) { - this.workspaceConfiguration.getWorkspaceSettings().update(); - this.workspace.folders.forEach(folder => this._configuration.getFolderConfigurationModel(folder.uri).update()); - this._configuration.updateDefaultConfiguration(this.baseConfigurationService.configuration.defaults); - this.triggerConfigurationChange(new ConfigurationChangeEvent().change(e.affectedKeys), e.source); - } else { - let keys = this._configuration.updateUserConfiguration(this.baseConfigurationService.configuration.user); - this.triggerConfigurationChange(keys, e.source); - } - } + private onUserConfigurationChanged(): void { + let keys = this._configuration.compareAndUpdateUserConfiguration(this.userConfiguration.configurationModel); + this.triggerConfigurationChange(keys, ConfigurationTarget.USER); } private onWorkspaceConfigurationChanged(): TPromise { if (this.workspace && this.workspace.configuration && this._configuration) { - const workspaceConfigurationChangeEvent = this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration()); + const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration()); let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), 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) { @@ -510,7 +514,7 @@ 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) : new ConfigurationChangeEvent())) + .then(folderConfiguration => folderConfiguration ? this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration) : new ConfigurationChangeEvent())) ).then(changeEvents => { const consolidateChangeEvent = changeEvents.reduce((consolidated, e) => consolidated.change(e), new ConfigurationChangeEvent()); this.triggerConfigurationChange(consolidateChangeEvent, ConfigurationTarget.WORKSPACE_FOLDER); @@ -523,8 +527,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat .then(folderConfiguration => { if (folderConfiguration) { // File change handled - this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration); - const workspaceChangedKeys = this._configuration.updateWorkspaceConfiguration(folderConfiguration); + this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); + const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration); this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE); } }); @@ -534,9 +538,9 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat this.disposeFolderConfiguration(folder); return this.loadFolderConfigurations([folder]) .then(([folderConfiguration]) => { - const folderChangedKeys = this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration); + const folderChangedKeys = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); if (this.getWorkbenchState() === WorkbenchState.FOLDER) { - const workspaceChangedKeys = this._configuration.updateWorkspaceConfiguration(folderConfiguration); + const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration); this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE); } else { this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER); @@ -551,7 +555,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat for (const key of this.cachedFolderConfigs.keys()) { if (!this.workspace.folders.filter(folder => folder.uri.toString() === key.toString())[0]) { this.cachedFolderConfigs.delete(key); - changeEvent = changeEvent.change(this._configuration.deleteFolderConfiguration(key)); + changeEvent = changeEvent.change(this._configuration.compareAndDeleteFolderConfiguration(key)); } } @@ -560,7 +564,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return this.loadFolderConfigurations(toInitialize) .then(folderConfigurations => { folderConfigurations.forEach((folderConfiguration, index) => { - changeEvent = changeEvent.change(this._configuration.updateFolderConfiguration(toInitialize[index].uri, folderConfiguration)); + changeEvent = changeEvent.change(this._configuration.compareAndUpdateFolderConfiguration(toInitialize[index].uri, folderConfiguration)); }); return changeEvent; }); @@ -568,9 +572,9 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return TPromise.as(changeEvent); } - 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); + const folderConfiguration = new FolderConfiguration(folder.uri, this.workspaceSettingsRootFolder, this.getWorkbenchState()); this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration)); return folderConfiguration.loadConfiguration(); })]); 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 781260d8502..34c3fc5d42a 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -6,99 +6,93 @@ import * as assert from 'assert'; 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 { Registry } from 'vs/platform/registry/common/platform'; +import { FolderSettingsModelParser, WorkspaceConfigurationChangeEvent, StandaloneConfigurationModelParser, AllKeysConfigurationChangeEvent, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; 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 { ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { StrictResourceMap } from 'vs/base/common/map'; -suite('ConfigurationService - Model', () => { +suite('FolderSettingsModelParser', () => { - test('Test scoped configs are undefined', () => { - const settingsConfig = new FolderSettingsModel(JSON.stringify({ - awesome: true - })); - - const testObject = new FolderConfigurationModel(settingsConfig, [], ConfigurationScope.WINDOW); - - assert.equal(testObject.getSectionContents('task'), undefined); + suiteSetup(() => { + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': 'FolderSettingsModelParser_1', + 'type': 'object', + 'properties': { + 'FolderSettingsModelParser.window': { + 'type': 'string', + 'default': 'isSet' + }, + 'FolderSettingsModelParser.resource': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.RESOURCE + }, + 'FolderSettingsModelParser.executable': { + 'type': 'string', + 'default': 'isSet', + isExecutable: true + } + } + }); }); - test('Test consolidate (settings and tasks)', () => { - const settingsConfig = new FolderSettingsModel(JSON.stringify({ - awesome: true - })); + test('parse all folder settings', () => { + const testObject = new FolderSettingsModelParser('settings'); - const tasksConfig = new ScopedConfigurationModel(JSON.stringify({ - awesome: false - }), '', 'tasks'); + testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.executable': 'executable' })); - const expected = { - awesome: true, - tasks: { - awesome: false - } - }; - - assert.deepEqual(new FolderConfigurationModel(settingsConfig, [tasksConfig], ConfigurationScope.WINDOW).contents, expected); + assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'window': 'window', 'resource': 'resource' } }); }); - test('Test consolidate (settings and launch)', () => { - const settingsConfig = new FolderSettingsModel(JSON.stringify({ - awesome: true - })); + test('parse resource folder settings', () => { + const testObject = new FolderSettingsModelParser('settings', ConfigurationScope.RESOURCE); - const launchConfig = new ScopedConfigurationModel(JSON.stringify({ - awesome: false - }), '', 'launch'); + testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.executable': 'executable' })); - const expected = { - awesome: true, - launch: { - awesome: false - } - }; - - assert.deepEqual(new FolderConfigurationModel(settingsConfig, [launchConfig], ConfigurationScope.WINDOW).contents, expected); + assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource' } }); }); - test('Test consolidate (settings and launch and tasks) - launch/tasks wins over settings file', () => { - const settingsConfig = new FolderSettingsModel(JSON.stringify({ - awesome: true, - launch: { - launchConfig: 'defined', - otherLaunchConfig: 'alsoDefined' - }, - tasks: { - taskConfig: 'defined', - otherTaskConfig: 'alsoDefined' + test('reprocess folder settings excludes executable', () => { + const testObject = new FolderSettingsModelParser('settings'); + + testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherExecutable': 'executable' })); + + assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource', 'anotherExecutable': 'executable' } }); + + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': 'FolderSettingsModelParser_2', + 'type': 'object', + 'properties': { + 'FolderSettingsModelParser.anotherExecutable': { + 'type': 'string', + 'default': 'isSet', + isExecutable: true + } } - })); + }); - const tasksConfig = new ScopedConfigurationModel(JSON.stringify({ - taskConfig: 'overwritten', - }), '', 'tasks'); - - const launchConfig = new ScopedConfigurationModel(JSON.stringify({ - launchConfig: 'overwritten', - }), '', 'launch'); - - const expected = { - awesome: true, - launch: { - launchConfig: 'overwritten', - otherLaunchConfig: 'alsoDefined' - }, - tasks: { - taskConfig: 'overwritten', - otherTaskConfig: 'alsoDefined' - } - }; - - assert.deepEqual(new FolderConfigurationModel(settingsConfig, [launchConfig, tasksConfig], ConfigurationScope.WINDOW).contents, expected); - assert.deepEqual(new FolderConfigurationModel(settingsConfig, [tasksConfig, launchConfig], ConfigurationScope.WINDOW).contents, expected); + testObject.reprocess(); + assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource' } }); }); + +}); + +suite('StandaloneConfigurationModelParser', () => { + + test('parse tasks stand alone configuration model', () => { + const testObject = new StandaloneConfigurationModelParser('tasks', 'tasks'); + + testObject.parse(JSON.stringify({ 'version': '1.1.1', 'tasks': [] })); + + assert.deepEqual(testObject.configurationModel.contents, { 'tasks': { 'version': '1.1.1', 'tasks': [] } }); + }); + }); suite('WorkspaceConfigurationChangeEvent', () => { @@ -194,4 +188,50 @@ suite('WorkspaceConfigurationChangeEvent', () => { assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder3', 'file3')))); }); +}); + +suite('AllKeysConfigurationChangeEvent', () => { + + test('changeEvent affects keys for any resource', () => { + const configuraiton = new Configuration(new ConfigurationModel({}, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']), + new ConfigurationModel(), new ConfigurationModel(), new StrictResourceMap(), new ConfigurationModel(), new StrictResourceMap(), null); + let testObject = new AllKeysConfigurationChangeEvent(configuraiton, ConfigurationTarget.USER, null); + + assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + + 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.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); + + 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.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.title', 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.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.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('workbench.editor', 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.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', URI.file('file1'))); + }); }); \ 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 67997f3b92c..908016a8cee 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts @@ -33,7 +33,6 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { IChoiceService } from 'vs/platform/message/common/message'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -105,8 +104,7 @@ suite('ConfigurationEditingService', () => { instantiationService = workbenchInstantiationService(); const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); instantiationService.stub(IEnvironmentService, environmentService); - const workspacesService = instantiationService.stub(IWorkspacesService, {}); - const workspaceService = new WorkspaceService(environmentService, workspacesService); + const workspaceService = new WorkspaceService(environmentService); instantiationService.stub(IWorkspaceContextService, workspaceService); return workspaceService.initialize(noWorkspace ? {} as IWindowConfiguration : workspaceDir).then(() => { instantiationService.stub(IConfigurationService, workspaceService); 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 444f2793249..f5a36943297 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts @@ -102,7 +102,7 @@ suite('WorkspaceContextService - Folder', () => { workspaceResource = folderDir; const globalSettingsFile = path.join(parentDir, 'settings.json'); const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); - workspaceContextService = new WorkspaceService(environmentService, null); + workspaceContextService = new WorkspaceService(environmentService); return (workspaceContextService).initialize(folderDir); }); }); @@ -158,7 +158,7 @@ suite('WorkspaceContextService - Workspace', () => { parentResource = parentDir; const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json')); - const workspaceService = new WorkspaceService(environmentService, null); + const workspaceService = new WorkspaceService(environmentService); const instantiationService = workbenchInstantiationService(); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -322,7 +322,7 @@ suite('WorkspaceConfigurationService - Folder', () => { const instantiationService = workbenchInstantiationService(); const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); - const workspaceService = new WorkspaceService(environmentService, null); + const workspaceService = new WorkspaceService(environmentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IEnvironmentService, environmentService); @@ -580,7 +580,7 @@ suite('WorkspaceConfigurationService - Update (Multiroot)', () => { parentResource = parentDir; const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json')); - const workspaceService = new WorkspaceService(environmentService, null); + const workspaceService = new WorkspaceService(environmentService); const instantiationService = workbenchInstantiationService(); instantiationService.stub(IWorkspaceContextService, workspaceService);