From 8533286114f73f16146e80c450c9c26cc2ed3d0e Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 15:18:12 -0700 Subject: [PATCH 01/28] Move internal things into an internal section --- src/config.coffee | 54 +++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index 19acf887e..6c192298e 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -484,7 +484,7 @@ class Config deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.' ### - Section: Private + Section: Internal to Core ### pushAtKeyPath: (keyPath, value) -> @@ -505,6 +505,34 @@ class Config @set(keyPath, arrayValue) result + setSchema: (keyPath, schema) -> + unless isPlainObject(schema) + throw new Error("Error loading schema for #{keyPath}: schemas can only be objects!") + + unless typeof schema.type? + throw new Error("Error loading schema for #{keyPath}: schema objects must have a type attribute") + + rootSchema = @schema + if keyPath + for key in keyPath.split('.') + rootSchema.type = 'object' + rootSchema.properties ?= {} + properties = rootSchema.properties + properties[key] ?= {} + rootSchema = properties[key] + + _.extend rootSchema, schema + @setDefaults(keyPath, @extractDefaultsFromSchema(schema)) + + load: -> + @initializeConfigDirectory() + @loadUserConfig() + @observeUserConfig() + + ### + Section: Private + ### + initializeConfigDirectory: (done) -> return if fs.existsSync(@configDirPath) @@ -521,11 +549,6 @@ class Config queue.push({sourcePath, destinationPath}) fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true) - load: -> - @initializeConfigDirectory() - @loadUserConfig() - @observeUserConfig() - loadUserConfig: -> unless fs.existsSync(@configFilePath) fs.makeTreeSync(path.dirname(@configFilePath)) @@ -615,25 +638,6 @@ class Config catch e console.warn("'#{keyPath}' could not set the default. Attempted default: #{JSON.stringify(defaults)}; Schema: #{JSON.stringify(@getSchema(keyPath))}") - setSchema: (keyPath, schema) -> - unless isPlainObject(schema) - throw new Error("Error loading schema for #{keyPath}: schemas can only be objects!") - - unless typeof schema.type? - throw new Error("Error loading schema for #{keyPath}: schema objects must have a type attribute") - - rootSchema = @schema - if keyPath - for key in keyPath.split('.') - rootSchema.type = 'object' - rootSchema.properties ?= {} - properties = rootSchema.properties - properties[key] ?= {} - rootSchema = properties[key] - - _.extend rootSchema, schema - @setDefaults(keyPath, @extractDefaultsFromSchema(schema)) - extractDefaultsFromSchema: (schema) -> if schema.default? schema.default From 356f4bec7c1d421f96860bbff01d5c9712fc1343 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 16:31:22 -0700 Subject: [PATCH 02/28] Basic scoped settings in Config works --- spec/config-spec.coffee | 46 ++++++++++++++++++++++ src/config.coffee | 85 ++++++++++++++++++++++++++++++++++------- 2 files changed, 117 insertions(+), 14 deletions(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 0c9251054..b7675e29f 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -862,3 +862,49 @@ describe "Config", -> expect(atom.config.set('foo.bar.arr', ['two', 'three'])).toBe true expect(atom.config.get('foo.bar.arr')).toEqual ['two', 'three'] + + describe "scoped settings", -> + describe ".get(scopeDescriptor, keyPath)", -> + it "returns the property with the most specific scope selector", -> + atom.config.addScopedDefaults(".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + atom.config.addScopedDefaults(".source .string.quoted.double", foo: bar: baz: 22) + atom.config.addScopedDefaults(".source", foo: bar: baz: 11) + + expect(atom.config.get([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 42 + expect(atom.config.get([".source.js", ".string.quoted.double.js"], "foo.bar.baz")).toBe 22 + expect(atom.config.get([".source.js", ".variable.assignment.js"], "foo.bar.baz")).toBe 11 + expect(atom.config.get([".text"], "foo.bar.baz")).toBeUndefined() + + it "favors the most recently added properties in the event of a specificity tie", -> + atom.config.addScopedDefaults(".source.coffee .string.quoted.single", foo: bar: baz: 42) + atom.config.addScopedDefaults(".source.coffee .string.quoted.double", foo: bar: baz: 22) + + expect(atom.config.get([".source.coffee", ".string.quoted.single"], "foo.bar.baz")).toBe 42 + expect(atom.config.get([".source.coffee", ".string.quoted.single.double"], "foo.bar.baz")).toBe 22 + + describe 'when there are global defaults', -> + beforeEach -> + atom.config.setDefaults("foo", hasDefault: 'ok') + + it 'falls back to the global when there is no scoped property specified', -> + expect(atom.config.get([".source.coffee", ".string.quoted.single"], "foo.hasDefault")).toBe 'ok' + + describe ".set(scope, keyPath, value)", -> + it "sets the value and overrides the others", -> + atom.config.addScopedDefaults(".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + atom.config.addScopedDefaults(".source .string.quoted.double", foo: bar: baz: 22) + atom.config.addScopedDefaults(".source", foo: bar: baz: 11) + + expect(atom.config.get([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 42 + + expect(atom.config.set(".source.coffee .string.quoted.double.coffee", "foo.bar.baz", 100)).toBe true + expect(atom.config.get([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 100 + + describe ".removeScopedSettingsForName(name)", -> + it "allows properties to be removed by name", -> + atom.config.addScopedDefaults("a", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + atom.config.addScopedDefaults("b", ".source .string.quoted.double", foo: bar: baz: 22) + + atom.config.removeScopedSettingsForName("b") + expect(atom.config.get([".source.js", ".string.quoted.double.js"], "foo.bar.baz")).toBeUndefined() + expect(atom.config.get([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 42 diff --git a/src/config.coffee b/src/config.coffee index 6c192298e..fe2723762 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -8,6 +8,8 @@ async = require 'async' pathWatcher = require 'pathwatcher' {deprecate} = require 'grim' +ScopedPropertyStore = require 'scoped-property-store' + # Essential: Used to access all of Atom's configuration details. # # An instance of this class is always available as the `atom.config` global. @@ -307,6 +309,7 @@ class Config properties: {} @defaultSettings = {} @settings = {} + @scopedSettingsStore = new ScopedPropertyStore @configFileHasErrors = false @configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson']) @configFilePath ?= path.join(@configDirPath, 'config.cson') @@ -368,20 +371,16 @@ class Config # # Returns the value from Atom's default settings, the user's configuration # file in the type specified by the configuration schema. - get: (keyPath) -> - value = _.valueForKeyPath(@settings, keyPath) - defaultValue = _.valueForKeyPath(@defaultSettings, keyPath) + get: (scopeDescriptor, keyPath) -> + if arguments.length == 1 + keyPath = scopeDescriptor + scopeDescriptor = undefined - if value? - value = _.deepClone(value) - valueIsObject = _.isObject(value) and not _.isArray(value) - defaultValueIsObject = _.isObject(defaultValue) and not _.isArray(defaultValue) - if valueIsObject and defaultValueIsObject - _.defaults(value, defaultValue) - else - value = _.deepClone(defaultValue) + if scopeDescriptor? + value = @getRawScopedValue(scopeDescriptor, keyPath) + return value if value? - value + @getRawValue(keyPath) # Essential: Sets the value for a configuration setting. # @@ -394,14 +393,23 @@ class Config # Returns a {Boolean} # * `true` if the value was set. # * `false` if the value was not able to be coerced to the type specified in the setting's schema. - set: (keyPath, value) -> + set: (scope, keyPath, value) -> unless value == undefined try value = @makeValueConformToSchema(keyPath, value) catch e return false - @setRawValue(keyPath, value) + if arguments.length < 3 + value = keyPath + keyPath = scope + scope = undefined + + if scope? + @addRawScopedValue(scope, keyPath, value) + else + @setRawValue(keyPath, value) + @save() unless @configFileHasErrors true @@ -578,6 +586,18 @@ class Config save: -> CSON.writeFileSync(@configFilePath, @settings) + getRawValue: (keyPath) -> + value = _.valueForKeyPath(@settings, keyPath) + defaultValue = _.valueForKeyPath(@defaultSettings, keyPath) + + if value? + value = _.deepClone(value) + _.defaults(value, defaultValue) if isPlainObject(defaultValue) and isPlainObject(defaultValue) + else + value = _.deepClone(defaultValue) + + value + setRawValue: (keyPath, value) -> defaultValue = _.valueForKeyPath(@defaultSettings, keyPath) value = undefined if _.isEqual(defaultValue, value) @@ -651,6 +671,43 @@ class Config value = @constructor.executeSchemaEnforcers(keyPath, value, schema) if schema = @getSchema(keyPath) value + ### + Section: Private Scoped Settings + ### + + addScopedDefaults: (name, selector, value) -> + if arguments.length < 3 + value = selector + selector = name + name = null + + name = "#{name ? ''}+default" + settingsBySelector = {} + settingsBySelector[selector] = value + @scopedSettingsStore.addProperties(name, settingsBySelector) + + addRawScopedValue: (selector, keyPath, value) -> + if keyPath? + newValue = {} + _.setValueForKeyPath(newValue, keyPath, value) + value = newValue + + settingsBySelector = {} + settingsBySelector[selector] = value + @scopedSettingsStore.addProperties(name, settingsBySelector) + + getRawScopedValue: (scopeDescriptor, keyPath) -> + scopeChain = scopeDescriptor + .map (scope) -> + scope = ".#{scope}" unless scope[0] is '.' + scope + .join(' ') + @scopedSettingsStore.getPropertyValue(scopeChain, keyPath) + + removeScopedSettingsForName: (name) -> + @scopedSettingsStore.removeProperties(name) + @scopedSettingsStore.removeProperties("#{name}+default") + # Base schema enforcers. These will coerce raw input into the specified type, # and will throw an error when the value cannot be coerced. Throwing the error # will indicate that the value should not be set. From 38e889b7d879f1f34928c95300af7ffbc40e56d7 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 16:34:51 -0700 Subject: [PATCH 03/28] Reorganize private methods into section --- src/config.coffee | 72 +++++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index fe2723762..dc714f598 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -492,7 +492,7 @@ class Config deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.' ### - Section: Internal to Core + Section: Internal methods used by core ### pushAtKeyPath: (keyPath, value) -> @@ -538,7 +538,7 @@ class Config @observeUserConfig() ### - Section: Private + Section: Private methods managing the user's config file ### initializeConfigDirectory: (done) -> @@ -586,6 +586,42 @@ class Config save: -> CSON.writeFileSync(@configFilePath, @settings) + ### + Section: Private methods managing global settings + ### + + setAll: (newSettings) -> + unless isPlainObject(newSettings) + @settings = {} + @emitter.emit 'did-change' + return + + unsetUnspecifiedValues = (keyPath, value) => + if isPlainObject(value) + keys = if keyPath? then keyPath.split('.') else [] + for key, childValue of value + continue unless value.hasOwnProperty(key) + unsetUnspecifiedValues(keys.concat([key]).join('.'), childValue) + else + @setRawValue(keyPath, undefined) unless _.valueForKeyPath(newSettings, keyPath)? + return + + @setRecursive(null, newSettings) + unsetUnspecifiedValues(null, @settings) + + setRecursive: (keyPath, value) -> + if isPlainObject(value) + keys = if keyPath? then keyPath.split('.') else [] + for key, childValue of value + continue unless value.hasOwnProperty(key) + @setRecursive(keys.concat([key]).join('.'), childValue) + else + try + value = @makeValueConformToSchema(keyPath, value) + @setRawValue(keyPath, value) + catch e + console.warn("'#{keyPath}' could not be set. Attempted value: #{JSON.stringify(value)}; Schema: #{JSON.stringify(@getSchema(keyPath))}") + getRawValue: (keyPath) -> value = _.valueForKeyPath(@settings, keyPath) defaultValue = _.valueForKeyPath(@defaultSettings, keyPath) @@ -613,38 +649,6 @@ class Config newValue = @get(keyPath) @emitter.emit 'did-change', {oldValue, newValue, keyPath} unless _.isEqual(newValue, oldValue) - setRecursive: (keyPath, value) -> - if isPlainObject(value) - keys = if keyPath? then keyPath.split('.') else [] - for key, childValue of value - continue unless value.hasOwnProperty(key) - @setRecursive(keys.concat([key]).join('.'), childValue) - else - try - value = @makeValueConformToSchema(keyPath, value) - @setRawValue(keyPath, value) - catch e - console.warn("'#{keyPath}' could not be set. Attempted value: #{JSON.stringify(value)}; Schema: #{JSON.stringify(@getSchema(keyPath))}") - - setAll: (newSettings) -> - unless isPlainObject(newSettings) - @settings = {} - @emitter.emit 'did-change' - return - - unsetUnspecifiedValues = (keyPath, value) => - if isPlainObject(value) - keys = if keyPath? then keyPath.split('.') else [] - for key, childValue of value - continue unless value.hasOwnProperty(key) - unsetUnspecifiedValues(keys.concat([key]).join('.'), childValue) - else - @setRawValue(keyPath, undefined) unless _.valueForKeyPath(newSettings, keyPath)? - return - - @setRecursive(null, newSettings) - unsetUnspecifiedValues(null, @settings) - setDefaults: (keyPath, defaults) -> if defaults? and isPlainObject(defaults) keys = if keyPath? then keyPath.split('.') else [] From 778d9fafc5a1efc21dcd998b3eeaf227a253fca3 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 16:44:10 -0700 Subject: [PATCH 04/28] fix value and default value object checks --- src/config.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.coffee b/src/config.coffee index dc714f598..1d7550c32 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -628,7 +628,7 @@ class Config if value? value = _.deepClone(value) - _.defaults(value, defaultValue) if isPlainObject(defaultValue) and isPlainObject(defaultValue) + _.defaults(value, defaultValue) if isPlainObject(value) and isPlainObject(defaultValue) else value = _.deepClone(defaultValue) From 7a5054027ebdd4d10b01219c71025f5229ffa7df Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 16:44:26 -0700 Subject: [PATCH 05/28] Shift the args before coercing the value --- src/config.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index 1d7550c32..f1e207190 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -394,17 +394,17 @@ class Config # * `true` if the value was set. # * `false` if the value was not able to be coerced to the type specified in the setting's schema. set: (scope, keyPath, value) -> + if arguments.length < 3 + value = keyPath + keyPath = scope + scope = undefined + unless value == undefined try value = @makeValueConformToSchema(keyPath, value) catch e return false - if arguments.length < 3 - value = keyPath - keyPath = scope - scope = undefined - if scope? @addRawScopedValue(scope, keyPath, value) else From f61a7d0c6204699f8a94257f6326f95d5b7fc43e Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 16:52:00 -0700 Subject: [PATCH 06/28] Remove unused method --- src/syntax.coffee | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/syntax.coffee b/src/syntax.coffee index 09af37c7c..29a33ea1e 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -82,6 +82,3 @@ class Syntax extends GrammarRegistry .join(' ') @propertyStore.getProperties(scopeChain, keyPath) - - cssSelectorFromScopeSelector: (scopeSelector) -> - new ScopeSelector(scopeSelector).toCssSelector() From dd05c6cec1a19a3b84bd40b5185c89c0f65efb00 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 17:05:04 -0700 Subject: [PATCH 07/28] Syntax calls into atom.config for scoped properties --- src/config.coffee | 12 ++++++++++++ src/syntax.coffee | 39 +++++---------------------------------- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index f1e207190..2d26060b7 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -708,10 +708,22 @@ class Config .join(' ') @scopedSettingsStore.getPropertyValue(scopeChain, keyPath) + # TODO: figure out how to remove this. Only language mode uses it for one thing. + settingsForScopeDescriptor: (scopeDescriptor, keyPath) -> + scopeChain = scopeDescriptor + .map (scope) -> + scope = ".#{scope}" unless scope[0] is '.' + scope + .join(' ') + @scopedSettingsStore.getProperties(scopeChain, keyPath) + removeScopedSettingsForName: (name) -> @scopedSettingsStore.removeProperties(name) @scopedSettingsStore.removeProperties("#{name}+default") + clearScopedSettings: -> + @scopedSettingsStore = new ScopedPropertyStore + # Base schema enforcers. These will coerce raw input into the specified type, # and will throw an error when the value cannot be coerced. Throwing the error # will indicate that the value should not be set. diff --git a/src/syntax.coffee b/src/syntax.coffee index 29a33ea1e..18f55db97 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -28,7 +28,6 @@ class Syntax extends GrammarRegistry constructor: -> super(maxTokensPerLine: 100) - @propertyStore = new ScopedPropertyStore serialize: -> {deserializer: @constructor.name, @grammarOverridesByPath} @@ -41,44 +40,16 @@ class Syntax extends GrammarRegistry @propertyStore.propertySets addProperties: (args...) -> - name = args.shift() if args.length > 2 - [selector, properties] = args - propertiesBySelector = {} - propertiesBySelector[selector] = properties - @propertyStore.addProperties(name, propertiesBySelector) + atom.config.addScopedDefaults(args...) removeProperties: (name) -> - @propertyStore.removeProperties(name) + atom.config.removeScopedSettingsForName(name) clearProperties: -> - @propertyStore = new ScopedPropertyStore + atom.config.clearScopedSettings() - # Public: Get a property for the given scope and key path. - # - # ## Examples - # - # ```coffee - # comment = atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart') - # console.log(comment) # '# ' - # ``` - # - # * `scope` An {Array} of {String} scopes. - # * `keyPath` A {String} key path. - # - # Returns a {String} property value or undefined. getProperty: (scope, keyPath) -> - scopeChain = scope - .map (scope) -> - scope = ".#{scope}" unless scope[0] is '.' - scope - .join(' ') - @propertyStore.getPropertyValue(scopeChain, keyPath) + atom.config.getRawScopedValue(scope, keyPath) propertiesForScope: (scope, keyPath) -> - scopeChain = scope - .map (scope) -> - scope = ".#{scope}" unless scope[0] is '.' - scope - .join(' ') - - @propertyStore.getProperties(scopeChain, keyPath) + atom.config.settingsForScopeDescriptor(scope, keyPath) From 21feab322fcba73bcb2420d6cf35762be642b9c0 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 17:07:58 -0700 Subject: [PATCH 08/28] Add deprecations to the syntax scoped property methods --- src/syntax.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/syntax.coffee b/src/syntax.coffee index 18f55db97..511ba7df8 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -40,16 +40,21 @@ class Syntax extends GrammarRegistry @propertyStore.propertySets addProperties: (args...) -> + deprecate 'Consider using atom.config.set() instead. A direct (but private) replacement is available at atom.config.addScopedDefaults().' atom.config.addScopedDefaults(args...) removeProperties: (name) -> + deprecate 'A direct (but private) replacement is available at atom.config.removeScopedSettingsForName().' atom.config.removeScopedSettingsForName(name) clearProperties: -> + deprecate 'A direct (but private) replacement is available at atom.config.clearScopedSettings().' atom.config.clearScopedSettings() getProperty: (scope, keyPath) -> + deprecate 'A direct (but private) replacement is available at atom.config.getRawScopedValue().' atom.config.getRawScopedValue(scope, keyPath) propertiesForScope: (scope, keyPath) -> + deprecate 'A direct (but private) replacement is available at atom.config.settingsForScopeDescriptor().' atom.config.settingsForScopeDescriptor(scope, keyPath) From 9a957fe0a466d69cde60d89fe99104c2e4350486 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 18:11:40 -0700 Subject: [PATCH 09/28] Fix specs for settings view. --- src/syntax.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/syntax.coffee b/src/syntax.coffee index 511ba7df8..10f9cda98 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -35,9 +35,9 @@ class Syntax extends GrammarRegistry createToken: (value, scopes) -> new Token({value, scopes}) # Deprecated: Used by settings-view to display snippets for packages - @::accessor 'scopedProperties', -> - deprecate("Use Syntax::getProperty instead") - @propertyStore.propertySets + @::accessor 'propertyStore', -> + deprecate("Do not use this. Use a public method on Config") + atom.config.scopedSettingsStore addProperties: (args...) -> deprecate 'Consider using atom.config.set() instead. A direct (but private) replacement is available at atom.config.addScopedDefaults().' From d72b179b3b7def4f777f215f32937e2a614335c7 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 18:11:52 -0700 Subject: [PATCH 10/28] Use config in spec helper --- spec/spec-helper.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 68c382d47..6004189c0 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -82,7 +82,7 @@ beforeEach -> spyOn(atom, 'saveSync') atom.syntax.clearGrammarOverrides() - atom.syntax.clearProperties() + atom.config.clearScopedSettings() spy = spyOn(atom.packages, 'resolvePackagePath').andCallFake (packageName) -> if specPackageName and packageName is specPackageName From 82990cfc778309a2ab59ff86f2afa3e6fb3601ed Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 1 Oct 2014 18:14:16 -0700 Subject: [PATCH 11/28] rename method --- src/config.coffee | 2 +- src/syntax.coffee | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index 2d26060b7..8b07e01cb 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -709,7 +709,7 @@ class Config @scopedSettingsStore.getPropertyValue(scopeChain, keyPath) # TODO: figure out how to remove this. Only language mode uses it for one thing. - settingsForScopeDescriptor: (scopeDescriptor, keyPath) -> + scopedSettingsForScopeDescriptor: (scopeDescriptor, keyPath) -> scopeChain = scopeDescriptor .map (scope) -> scope = ".#{scope}" unless scope[0] is '.' diff --git a/src/syntax.coffee b/src/syntax.coffee index 10f9cda98..7a7b8feb8 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -56,5 +56,5 @@ class Syntax extends GrammarRegistry atom.config.getRawScopedValue(scope, keyPath) propertiesForScope: (scope, keyPath) -> - deprecate 'A direct (but private) replacement is available at atom.config.settingsForScopeDescriptor().' - atom.config.settingsForScopeDescriptor(scope, keyPath) + deprecate 'A direct (but private) replacement is available at atom.config.scopedSettingsForScopeDescriptor().' + atom.config.scopedSettingsForScopeDescriptor(scope, keyPath) From e2ac19c17f98865cbefb648b8e3e6f79c3c71fc7 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 10:31:32 -0700 Subject: [PATCH 12/28] Use config rather than syntax for scoped properties --- spec/package-manager-spec.coffee | 12 ++++++------ src/language-mode.coffee | 2 +- src/scoped-properties.coffee | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 697c5b732..cccb91b62 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -313,7 +313,7 @@ describe "PackageManager", -> atom.packages.activatePackage("package-with-scoped-properties") runs -> - expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' + expect(atom.config.get ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' describe "converted textmate packages", -> it "loads the package's grammars", -> @@ -326,13 +326,13 @@ describe "PackageManager", -> expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby" it "loads the translated scoped properties", -> - expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined() + expect(atom.config.get(['.source.ruby'], 'editor.commentStart')).toBeUndefined() waitsForPromise -> atom.packages.activatePackage('language-ruby') runs -> - expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBe '# ' + expect(atom.config.get(['.source.ruby'], 'editor.commentStart')).toBe '# ' describe "::deactivatePackage(id)", -> describe "atom packages", -> @@ -436,9 +436,9 @@ describe "PackageManager", -> atom.packages.activatePackage("package-with-scoped-properties") runs -> - expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' + expect(atom.config.get ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' atom.packages.deactivatePackage("package-with-scoped-properties") - expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBeUndefined() + expect(atom.config.get ['.source.omg'], 'editor.increaseIndentPattern').toBeUndefined() describe "textmate packages", -> it "removes the package's grammars", -> @@ -458,7 +458,7 @@ describe "PackageManager", -> runs -> atom.packages.deactivatePackage('language-ruby') - expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined() + expect(atom.config.get(['.source.ruby'], 'editor.commentStart')).toBeUndefined() describe "::activate()", -> packageActivator = null diff --git a/src/language-mode.coffee b/src/language-mode.coffee index b1327a2b4..6e1e79d2f 100644 --- a/src/language-mode.coffee +++ b/src/language-mode.coffee @@ -312,7 +312,7 @@ class LanguageMode @editor.setIndentationForBufferRow(bufferRow, desiredIndentLevel) getRegexForProperty: (scopes, property) -> - if pattern = atom.syntax.getProperty(scopes, property) + if pattern = atom.config.get(scopes, property) new OnigRegExp(pattern) increaseIndentRegexForScopes: (scopes) -> diff --git a/src/scoped-properties.coffee b/src/scoped-properties.coffee index 47d239a5a..d649a8be9 100644 --- a/src/scoped-properties.coffee +++ b/src/scoped-properties.coffee @@ -13,7 +13,7 @@ class ScopedProperties activate: -> for selector, properties of @scopedProperties - atom.syntax.addProperties(@path, selector, properties) + atom.config.addScopedDefaults(@path, selector, properties) deactivate: -> - atom.syntax.removeProperties(@path) + atom.config.removeScopedSettingsForName(@path) From a8fad6a0fbc81533ad884b4b5e0f7b4b149d811b Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 13:49:30 -0700 Subject: [PATCH 13/28] Use disposables for removing properties --- spec/package-manager-spec.coffee | 3 +++ src/config.coffee | 4 ---- src/scoped-properties.coffee | 7 +++++-- src/syntax.coffee | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index cccb91b62..bb2f70c27 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -335,6 +335,9 @@ describe "PackageManager", -> expect(atom.config.get(['.source.ruby'], 'editor.commentStart')).toBe '# ' describe "::deactivatePackage(id)", -> + afterEach -> + atom.packages.unloadPackages() + describe "atom packages", -> it "calls `deactivate` on the package's main module if activate was successful", -> pack = null diff --git a/src/config.coffee b/src/config.coffee index 8b07e01cb..685a354f0 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -717,10 +717,6 @@ class Config .join(' ') @scopedSettingsStore.getProperties(scopeChain, keyPath) - removeScopedSettingsForName: (name) -> - @scopedSettingsStore.removeProperties(name) - @scopedSettingsStore.removeProperties("#{name}+default") - clearScopedSettings: -> @scopedSettingsStore = new ScopedPropertyStore diff --git a/src/scoped-properties.coffee b/src/scoped-properties.coffee index d649a8be9..7e9aa60c2 100644 --- a/src/scoped-properties.coffee +++ b/src/scoped-properties.coffee @@ -1,4 +1,5 @@ CSON = require 'season' +{CompositeDisposable} = require 'event-kit' module.exports = class ScopedProperties @@ -10,10 +11,12 @@ class ScopedProperties callback(null, new ScopedProperties(scopedPropertiesPath, scopedProperties)) constructor: (@path, @scopedProperties) -> + @propertyDisposable = new CompositeDisposable activate: -> for selector, properties of @scopedProperties - atom.config.addScopedDefaults(@path, selector, properties) + @propertyDisposable.add atom.config.addScopedDefaults(@path, selector, properties) + return deactivate: -> - atom.config.removeScopedSettingsForName(@path) + @propertyDisposable.dispose() diff --git a/src/syntax.coffee b/src/syntax.coffee index 7a7b8feb8..c472ee45d 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -44,8 +44,8 @@ class Syntax extends GrammarRegistry atom.config.addScopedDefaults(args...) removeProperties: (name) -> - deprecate 'A direct (but private) replacement is available at atom.config.removeScopedSettingsForName().' - atom.config.removeScopedSettingsForName(name) + deprecate 'atom.config.addScopedDefaults() now returns a disposable you can call .dispose() on' + atom.config.scopedSettingsStore.removeProperties(name) clearProperties: -> deprecate 'A direct (but private) replacement is available at atom.config.clearScopedSettings().' From b1f8c6a6e89a3ff499dbb8ae3a37823fe88ee925 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 13:56:27 -0700 Subject: [PATCH 14/28] Remove special method for language mode --- src/config.coffee | 9 --------- src/language-mode.coffee | 8 ++++---- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index 685a354f0..d0db72711 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -708,15 +708,6 @@ class Config .join(' ') @scopedSettingsStore.getPropertyValue(scopeChain, keyPath) - # TODO: figure out how to remove this. Only language mode uses it for one thing. - scopedSettingsForScopeDescriptor: (scopeDescriptor, keyPath) -> - scopeChain = scopeDescriptor - .map (scope) -> - scope = ".#{scope}" unless scope[0] is '.' - scope - .join(' ') - @scopedSettingsStore.getProperties(scopeChain, keyPath) - clearScopedSettings: -> @scopedSettingsStore = new ScopedPropertyStore diff --git a/src/language-mode.coffee b/src/language-mode.coffee index 6e1e79d2f..89d9130c8 100644 --- a/src/language-mode.coffee +++ b/src/language-mode.coffee @@ -30,11 +30,11 @@ class LanguageMode # Returns an {Array} of the commented {Ranges}. toggleLineCommentsForBufferRows: (start, end) -> scopes = @editor.scopesForBufferPosition([start, 0]) - properties = atom.syntax.propertiesForScope(scopes, "editor.commentStart")[0] - return unless properties + properties = atom.config.get(scopes, "editor") + return unless properties.commentStart - commentStartString = _.valueForKeyPath(properties, "editor.commentStart") - commentEndString = _.valueForKeyPath(properties, "editor.commentEnd") + commentStartString = properties.commentStart + commentEndString = properties.commentEnd return unless commentStartString From d47dbede29f8e765165a3c68144781ab23a009a8 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 14:01:35 -0700 Subject: [PATCH 15/28] Fix specs --- spec/config-spec.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index b7675e29f..21bb681d4 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -902,9 +902,9 @@ describe "Config", -> describe ".removeScopedSettingsForName(name)", -> it "allows properties to be removed by name", -> - atom.config.addScopedDefaults("a", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) - atom.config.addScopedDefaults("b", ".source .string.quoted.double", foo: bar: baz: 22) + disposable1 = atom.config.addScopedDefaults("a", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + disposable2 = atom.config.addScopedDefaults("b", ".source .string.quoted.double", foo: bar: baz: 22) - atom.config.removeScopedSettingsForName("b") + disposable2.dispose() expect(atom.config.get([".source.js", ".string.quoted.double.js"], "foo.bar.baz")).toBeUndefined() expect(atom.config.get([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 42 From aedf02a3e33dc783743c3b31d3094c8f4bc4d55d Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 14:16:36 -0700 Subject: [PATCH 16/28] Remove +default junk --- src/config.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/config.coffee b/src/config.coffee index d0db72711..f0a24bb49 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -685,7 +685,6 @@ class Config selector = name name = null - name = "#{name ? ''}+default" settingsBySelector = {} settingsBySelector[selector] = value @scopedSettingsStore.addProperties(name, settingsBySelector) From 3732bdf1e9d1ae8d338a69e4f7a2d7777bc72fef Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 14:34:02 -0700 Subject: [PATCH 17/28] Ugh, add settingsForScopeDescriptor back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s used by language mode and autocomplete for different things --- src/config.coffee | 11 +++++++++++ src/language-mode.coffee | 8 ++++---- src/syntax.coffee | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index f0a24bb49..0fb61f286 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -707,6 +707,17 @@ class Config .join(' ') @scopedSettingsStore.getPropertyValue(scopeChain, keyPath) + # TODO: figure out how to change / remove this. The return value is awkward. + # * language mode uses it for one thing. + # * autocomplete uses it for editor.completions + settingsForScopeDescriptor: (scopeDescriptor, keyPath) -> + scopeChain = scopeDescriptor + .map (scope) -> + scope = ".#{scope}" unless scope[0] is '.' + scope + .join(' ') + @scopedSettingsStore.getProperties(scopeChain, keyPath) + clearScopedSettings: -> @scopedSettingsStore = new ScopedPropertyStore diff --git a/src/language-mode.coffee b/src/language-mode.coffee index 89d9130c8..32112309e 100644 --- a/src/language-mode.coffee +++ b/src/language-mode.coffee @@ -30,11 +30,11 @@ class LanguageMode # Returns an {Array} of the commented {Ranges}. toggleLineCommentsForBufferRows: (start, end) -> scopes = @editor.scopesForBufferPosition([start, 0]) - properties = atom.config.get(scopes, "editor") - return unless properties.commentStart + properties = atom.config.settingsForScopeDescriptor(scopes, "editor.commentStart")[0] + return unless properties - commentStartString = properties.commentStart - commentEndString = properties.commentEnd + commentStartString = _.valueForKeyPath(properties, 'editor.commentStart') + commentEndString = _.valueForKeyPath(properties, 'editor.commentEnd') return unless commentStartString diff --git a/src/syntax.coffee b/src/syntax.coffee index c472ee45d..852b48656 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -57,4 +57,4 @@ class Syntax extends GrammarRegistry propertiesForScope: (scope, keyPath) -> deprecate 'A direct (but private) replacement is available at atom.config.scopedSettingsForScopeDescriptor().' - atom.config.scopedSettingsForScopeDescriptor(scope, keyPath) + atom.config.settingsForScopeDescriptor(scope, keyPath) From e5d67bb2ff6fa789d69aa20628ceccdf6a8df597 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 14:43:14 -0700 Subject: [PATCH 18/28] Can remove the clearing of scoped properties Config is being created on each spec run! --- spec/spec-helper.coffee | 1 - src/config.coffee | 5 +---- src/syntax.coffee | 4 ---- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 6004189c0..7f99d9c2b 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -82,7 +82,6 @@ beforeEach -> spyOn(atom, 'saveSync') atom.syntax.clearGrammarOverrides() - atom.config.clearScopedSettings() spy = spyOn(atom.packages, 'resolvePackagePath').andCallFake (packageName) -> if specPackageName and packageName is specPackageName diff --git a/src/config.coffee b/src/config.coffee index 0fb61f286..f751ce63a 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -707,7 +707,7 @@ class Config .join(' ') @scopedSettingsStore.getPropertyValue(scopeChain, keyPath) - # TODO: figure out how to change / remove this. The return value is awkward. + # TODO: figure out how to change / remove this. The return value is awkward. # * language mode uses it for one thing. # * autocomplete uses it for editor.completions settingsForScopeDescriptor: (scopeDescriptor, keyPath) -> @@ -718,9 +718,6 @@ class Config .join(' ') @scopedSettingsStore.getProperties(scopeChain, keyPath) - clearScopedSettings: -> - @scopedSettingsStore = new ScopedPropertyStore - # Base schema enforcers. These will coerce raw input into the specified type, # and will throw an error when the value cannot be coerced. Throwing the error # will indicate that the value should not be set. diff --git a/src/syntax.coffee b/src/syntax.coffee index 852b48656..02f3e1c5d 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -47,10 +47,6 @@ class Syntax extends GrammarRegistry deprecate 'atom.config.addScopedDefaults() now returns a disposable you can call .dispose() on' atom.config.scopedSettingsStore.removeProperties(name) - clearProperties: -> - deprecate 'A direct (but private) replacement is available at atom.config.clearScopedSettings().' - atom.config.clearScopedSettings() - getProperty: (scope, keyPath) -> deprecate 'A direct (but private) replacement is available at atom.config.getRawScopedValue().' atom.config.getRawScopedValue(scope, keyPath) From 899929a1ce91150745b6c1a2de723b2a01ff05c9 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 14:53:22 -0700 Subject: [PATCH 19/28] addScopedDefaults -> addScopedSettings --- spec/config-spec.coffee | 20 ++++++++++---------- src/config.coffee | 2 +- src/scoped-properties.coffee | 2 +- src/syntax.coffee | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 21bb681d4..b4d363d2c 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -866,9 +866,9 @@ describe "Config", -> describe "scoped settings", -> describe ".get(scopeDescriptor, keyPath)", -> it "returns the property with the most specific scope selector", -> - atom.config.addScopedDefaults(".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) - atom.config.addScopedDefaults(".source .string.quoted.double", foo: bar: baz: 22) - atom.config.addScopedDefaults(".source", foo: bar: baz: 11) + atom.config.addScopedSettings(".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + atom.config.addScopedSettings(".source .string.quoted.double", foo: bar: baz: 22) + atom.config.addScopedSettings(".source", foo: bar: baz: 11) expect(atom.config.get([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 42 expect(atom.config.get([".source.js", ".string.quoted.double.js"], "foo.bar.baz")).toBe 22 @@ -876,8 +876,8 @@ describe "Config", -> expect(atom.config.get([".text"], "foo.bar.baz")).toBeUndefined() it "favors the most recently added properties in the event of a specificity tie", -> - atom.config.addScopedDefaults(".source.coffee .string.quoted.single", foo: bar: baz: 42) - atom.config.addScopedDefaults(".source.coffee .string.quoted.double", foo: bar: baz: 22) + atom.config.addScopedSettings(".source.coffee .string.quoted.single", foo: bar: baz: 42) + atom.config.addScopedSettings(".source.coffee .string.quoted.double", foo: bar: baz: 22) expect(atom.config.get([".source.coffee", ".string.quoted.single"], "foo.bar.baz")).toBe 42 expect(atom.config.get([".source.coffee", ".string.quoted.single.double"], "foo.bar.baz")).toBe 22 @@ -891,9 +891,9 @@ describe "Config", -> describe ".set(scope, keyPath, value)", -> it "sets the value and overrides the others", -> - atom.config.addScopedDefaults(".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) - atom.config.addScopedDefaults(".source .string.quoted.double", foo: bar: baz: 22) - atom.config.addScopedDefaults(".source", foo: bar: baz: 11) + atom.config.addScopedSettings(".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + atom.config.addScopedSettings(".source .string.quoted.double", foo: bar: baz: 22) + atom.config.addScopedSettings(".source", foo: bar: baz: 11) expect(atom.config.get([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 42 @@ -902,8 +902,8 @@ describe "Config", -> describe ".removeScopedSettingsForName(name)", -> it "allows properties to be removed by name", -> - disposable1 = atom.config.addScopedDefaults("a", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) - disposable2 = atom.config.addScopedDefaults("b", ".source .string.quoted.double", foo: bar: baz: 22) + disposable1 = atom.config.addScopedSettings("a", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + disposable2 = atom.config.addScopedSettings("b", ".source .string.quoted.double", foo: bar: baz: 22) disposable2.dispose() expect(atom.config.get([".source.js", ".string.quoted.double.js"], "foo.bar.baz")).toBeUndefined() diff --git a/src/config.coffee b/src/config.coffee index f751ce63a..4c7709572 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -679,7 +679,7 @@ class Config Section: Private Scoped Settings ### - addScopedDefaults: (name, selector, value) -> + addScopedSettings: (name, selector, value) -> if arguments.length < 3 value = selector selector = name diff --git a/src/scoped-properties.coffee b/src/scoped-properties.coffee index 7e9aa60c2..dc910a6be 100644 --- a/src/scoped-properties.coffee +++ b/src/scoped-properties.coffee @@ -15,7 +15,7 @@ class ScopedProperties activate: -> for selector, properties of @scopedProperties - @propertyDisposable.add atom.config.addScopedDefaults(@path, selector, properties) + @propertyDisposable.add atom.config.addScopedSettings(@path, selector, properties) return deactivate: -> diff --git a/src/syntax.coffee b/src/syntax.coffee index 02f3e1c5d..84bb2e0bb 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -40,11 +40,11 @@ class Syntax extends GrammarRegistry atom.config.scopedSettingsStore addProperties: (args...) -> - deprecate 'Consider using atom.config.set() instead. A direct (but private) replacement is available at atom.config.addScopedDefaults().' - atom.config.addScopedDefaults(args...) + deprecate 'Consider using atom.config.set() instead. A direct (but private) replacement is available at atom.config.addScopedSettings().' + atom.config.addScopedSettings(args...) removeProperties: (name) -> - deprecate 'atom.config.addScopedDefaults() now returns a disposable you can call .dispose() on' + deprecate 'atom.config.addScopedSettings() now returns a disposable you can call .dispose() on' atom.config.scopedSettingsStore.removeProperties(name) getProperty: (scope, keyPath) -> From f8a3ae61043adc7912e50bee102210f6567bab2f Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 15:20:29 -0700 Subject: [PATCH 20/28] Pull observing out into special methods for global config --- src/config.coffee | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index 4c7709572..64d792d1a 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -337,9 +337,7 @@ class Config message = "`callNow` was set to false. Use ::onDidChange instead. Note that ::onDidChange calls back with different arguments." if options.callNow == false deprecate "Config::observe no longer supports options. #{message}" - callback(_.clone(@get(keyPath))) unless options.callNow == false - @emitter.on 'did-change', (event) -> - callback(event.newValue) if keyPath? and keyPath.indexOf(event?.keyPath) is 0 + @observeKeyPath(keyPath, options ? {}, callback) # Essential: Add a listener for changes to a given key path. If `keyPath` is # not specified, your callback will be called on changes to any key. @@ -358,8 +356,7 @@ class Config callback = keyPath keyPath = undefined - @emitter.on 'did-change', (event) -> - callback(event) if not keyPath? or (keyPath? and keyPath.indexOf(event?.keyPath) is 0) + @onDidChangeKeyPath(keyPath, callback) ### Section: Managing Settings @@ -643,6 +640,15 @@ class Config newValue = @get(keyPath) @emitter.emit 'did-change', {oldValue, newValue, keyPath} unless _.isEqual(newValue, oldValue) + observeKeyPath: (keyPath, options, callback) -> + callback(_.clone(@get(keyPath))) unless options.callNow == false + @emitter.on 'did-change', (event) -> + callback(event.newValue) if keyPath? and keyPath.indexOf(event?.keyPath) is 0 + + onDidChangeKeyPath: (keyPath, callback) -> + @emitter.on 'did-change', (event) -> + callback(event) if not keyPath? or (keyPath? and keyPath.indexOf(event?.keyPath) is 0) + setRawDefault: (keyPath, value) -> oldValue = _.clone(@get(keyPath)) _.setValueForKeyPath(@defaultSettings, keyPath, value) From f724c7fca88edb5bf183ab7b382ec1b9a8cf615f Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 16:17:08 -0700 Subject: [PATCH 21/28] Implement observing on scoped properties --- spec/config-spec.coffee | 59 ++++++++++++++++++++++++++++++++ src/config.coffee | 74 +++++++++++++++++++++++++++++++---------- 2 files changed, 116 insertions(+), 17 deletions(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index b4d363d2c..473b029c7 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -908,3 +908,62 @@ describe "Config", -> disposable2.dispose() expect(atom.config.get([".source.js", ".string.quoted.double.js"], "foo.bar.baz")).toBeUndefined() expect(atom.config.get([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 42 + + describe ".observe(scopeDescriptor, keyPath)", -> + it 'calls the supplied callback when the value at the descriptor/keypath changes', -> + atom.config.observe [".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz", changeSpy = jasmine.createSpy() + expect(changeSpy).toHaveBeenCalledWith(undefined) + changeSpy.reset() + + atom.config.set("foo.bar.baz", 12) + expect(changeSpy).toHaveBeenCalledWith(12) + changeSpy.reset() + + disposable1 = atom.config.addScopedSettings(".source .string.quoted.double", foo: bar: baz: 22) + expect(changeSpy).toHaveBeenCalledWith(22) + changeSpy.reset() + + disposable2 = atom.config.addScopedSettings("a", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + expect(changeSpy).toHaveBeenCalledWith(42) + changeSpy.reset() + + disposable2.dispose() + expect(changeSpy).toHaveBeenCalledWith(22) + changeSpy.reset() + + disposable1.dispose() + expect(changeSpy).toHaveBeenCalledWith(12) + changeSpy.reset() + + atom.config.set("foo.bar.baz", undefined) + expect(changeSpy).toHaveBeenCalledWith(undefined) + changeSpy.reset() + + describe ".onDidChange(scopeDescriptor, keyPath)", -> + it 'calls the supplied callback when the value at the descriptor/keypath changes', -> + keyPath = "foo.bar.baz" + atom.config.onDidChange [".source.coffee", ".string.quoted.double.coffee"], keyPath, changeSpy = jasmine.createSpy() + + atom.config.set("foo.bar.baz", 12) + expect(changeSpy).toHaveBeenCalledWith({oldValue: undefined, newValue: 12, keyPath}) + changeSpy.reset() + + disposable1 = atom.config.addScopedSettings(".source .string.quoted.double", foo: bar: baz: 22) + expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: 22, keyPath}) + changeSpy.reset() + + disposable2 = atom.config.addScopedSettings("a", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 42, keyPath}) + changeSpy.reset() + + disposable2.dispose() + expect(changeSpy).toHaveBeenCalledWith({oldValue: 42, newValue: 22, keyPath}) + changeSpy.reset() + + disposable1.dispose() + expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 12, keyPath}) + changeSpy.reset() + + atom.config.set("foo.bar.baz", undefined) + expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: undefined, keyPath}) + changeSpy.reset() diff --git a/src/config.coffee b/src/config.coffee index 64d792d1a..a185f2060 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -1,7 +1,7 @@ _ = require 'underscore-plus' fs = require 'fs-plus' EmitterMixin = require('emissary').Emitter -{Emitter} = require 'event-kit' +{Disposable, Emitter} = require 'event-kit' CSON = require 'season' path = require 'path' async = require 'async' @@ -328,16 +328,29 @@ class Config # # Returns a {Disposable} with the following keys on which you can call # `.dispose()` to unsubscribe. - observe: (keyPath, options={}, callback) -> - if _.isFunction(options) - callback = options - options = {} - else + observe: (scopeDescriptor, keyPath, options, callback) -> + args = Array::slice.call(arguments) + if args.length is 2 + # observe(keyPath, callback) + [keyPath, callback, scopeDescriptor, options] = args + else if args.length is 3 and Array.isArray(scopeDescriptor) + # observe(scopeDescriptor, keyPath, callback) + [scopeDescriptor, keyPath, callback, options] = args + else if args.length is 3 and _.isString(scopeDescriptor) and _.isObject(keyPath) + # observe(keyPath, options, callback) # Deprecated! + [keyPath, options, callback, scopeDescriptor] = args + message = "" message = "`callNow` was set to false. Use ::onDidChange instead. Note that ::onDidChange calls back with different arguments." if options.callNow == false - deprecate "Config::observe no longer supports options. #{message}" + deprecate "Config::observe no longer supports options; see https://atom.io/docs/api/latest/Config. #{message}" + else + console.warn 'An unsupported form of Config::observe is being used. See https://atom.io/docs/api/latest/Config for details' + return - @observeKeyPath(keyPath, options ? {}, callback) + if scopeDescriptor? + @observeScopedKeyPath(scopeDescriptor, keyPath, callback) + else + @observeKeyPath(keyPath, options ? {}, callback) # Essential: Add a listener for changes to a given key path. If `keyPath` is # not specified, your callback will be called on changes to any key. @@ -351,12 +364,17 @@ class Config # # Returns a {Disposable} with the following keys on which you can call # `.dispose()` to unsubscribe. - onDidChange: (keyPath, callback) -> + onDidChange: (scopeDescriptor, keyPath, callback) -> + args = Array::slice.call(arguments) if arguments.length is 1 - callback = keyPath - keyPath = undefined + [callback, scopeDescriptor, keyPath] = args + else if arguments.length is 2 + [keyPath, callback, scopeDescriptor] = args - @onDidChangeKeyPath(keyPath, callback) + if scopeDescriptor? + @onDidChangeScopedKeyPath(scopeDescriptor, keyPath, callback) + else + @onDidChangeKeyPath(keyPath, callback) ### Section: Managing Settings @@ -693,17 +711,18 @@ class Config settingsBySelector = {} settingsBySelector[selector] = value - @scopedSettingsStore.addProperties(name, settingsBySelector) + disposable = @scopedSettingsStore.addProperties(name, settingsBySelector) + @emitter.emit 'did-change' + new Disposable => + disposable.dispose() + @emitter.emit 'did-change' addRawScopedValue: (selector, keyPath, value) -> if keyPath? newValue = {} _.setValueForKeyPath(newValue, keyPath, value) value = newValue - - settingsBySelector = {} - settingsBySelector[selector] = value - @scopedSettingsStore.addProperties(name, settingsBySelector) + @addScopedSettings(null, selector, value) getRawScopedValue: (scopeDescriptor, keyPath) -> scopeChain = scopeDescriptor @@ -713,6 +732,27 @@ class Config .join(' ') @scopedSettingsStore.getPropertyValue(scopeChain, keyPath) + observeScopedKeyPath: (scopeDescriptor, keyPath, callback) -> + oldValue = @get(scopeDescriptor, keyPath) + + callback(oldValue) + + didChange = => + newValue = @get(scopeDescriptor, keyPath) + callback(newValue) unless _.isEqual(oldValue, newValue) + oldValue = newValue + + @emitter.on 'did-change', didChange + + onDidChangeScopedKeyPath: (scopeDescriptor, keyPath, callback) -> + oldValue = @get(scopeDescriptor, keyPath) + didChange = => + newValue = @get(scopeDescriptor, keyPath) + callback({oldValue, newValue, keyPath}) unless _.isEqual(oldValue, newValue) + oldValue = newValue + + @emitter.on 'did-change', didChange + # TODO: figure out how to change / remove this. The return value is awkward. # * language mode uses it for one thing. # * autocomplete uses it for editor.completions From 2475e1a9a602351a0df78badb5e276d175febd8c Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 17:20:10 -0700 Subject: [PATCH 22/28] :memo: Update docs for scoped settings --- src/config.coffee | 87 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index a185f2060..80501e768 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -221,7 +221,7 @@ ScopedPropertyStore = require 'scoped-property-store' # # All types support an `enum` key. The enum key lets you specify all values # that the config setting can possibly be. `enum` _must_ be an array of values -# of your specified type. +# of your specified type. Schema: # # ```coffee # config: @@ -231,6 +231,8 @@ ScopedPropertyStore = require 'scoped-property-store' # enum: [2, 4, 6, 8] # ``` # +# Usage: +# # ```coffee # atom.config.set('my-package.someSetting', '2') # atom.config.get('my-package.someSetting') # -> 2 @@ -322,6 +324,17 @@ class Config # than {::onDidChange} in that it will immediately call your callback with the # current value of the config entry. # + # ### Examples + # + # You might want to be notified when the themes change. We'll watch + # `core.themes` for changes + # + # ```coffee + # atom.config.observe 'core.themes', (value) -> + # # do stuff with value + # ``` + # + # * `scopeDescriptor` (optional) {Array} of {String}s. See {::get} for examples # * `keyPath` {String} name of the key to observe # * `callback` {Function} to call when the value of the key changes. # * `value` the new value of the key @@ -355,7 +368,9 @@ class Config # Essential: Add a listener for changes to a given key path. If `keyPath` is # not specified, your callback will be called on changes to any key. # - # * `keyPath` (optional) {String} name of the key to observe + # * `scopeDescriptor` (optional) {Array} of {String}s. See {::get} for examples + # * `keyPath` (optional) {String} name of the key to observe. Must be + # specified if `scopeDescriptor` is specified. # * `callback` {Function} to call when the value of the key changes. # * `event` {Object} # * `newValue` the new value of the key @@ -382,6 +397,37 @@ class Config # Essential: Retrieves the setting for the given key. # + # ### Examples + # + # You might want to know what themes are enabled, so check `core.themes` + # + # ```coffee + # atom.config.get('core.themes') + # ``` + # + # With scope descriptors you can get settings within a specific editor + # scope. For example, you might want to know `editor.tabLength` for ruby + # files. + # + # ```coffee + # atom.config.get(['source.ruby'], 'editor.tabLength') # => 2 + # ``` + # + # This setting in ruby files might be different than the global tabLength setting + # + # ```coffee + # atom.config.get('editor.tabLength') # => 4 + # atom.config.get(['source.ruby'], 'editor.tabLength') # => 2 + # ``` + # + # Additionally, you can get the setting at the specific cursor position. + # + # ```coffee + # scopeDescriptor = @editor.scopesForBufferPosition(@editor.getCursorBufferPosition()) + # atom.config.get(scopeDescriptor, 'editor.tabLength') # => 2 + # ``` + # + # * `scopeDescriptor` (optional) {Array} of {String}s. # * `keyPath` The {String} name of the key to retrieve. # # Returns the value from Atom's default settings, the user's configuration @@ -401,6 +447,32 @@ class Config # # This value is stored in Atom's internal configuration file. # + # ### Examples + # + # You might want to change the themes programmatically: + # + # ```coffee + # atom.config.set('core.themes', ['atom-light-ui', 'atom-light-syntax']) + # ``` + # + # You can also set scoped settings. For example, you might want change the + # `editor.tabLength` only for ruby files. + # + # ```coffee + # atom.config.get('editor.tabLength') # => 4 + # atom.config.get(['source.ruby'], 'editor.tabLength') # => 4 + # atom.config.get(['source.js'], 'editor.tabLength') # => 4 + # + # # Set ruby to 2 + # atom.config.set('source.ruby', 'editor.tabLength', 2) # => true + # + # # Notice it's only set to 2 in the case of ruby + # atom.config.get('editor.tabLength') # => 4 + # atom.config.get(['source.ruby'], 'editor.tabLength') # => 2 + # atom.config.get(['source.js'], 'editor.tabLength') # => 4 + # ``` + # + # * `scope` (optional) {String}. eg. '.source.ruby' # * `keyPath` The {String} name of the key. # * `value` The value of the setting. Passing `undefined` will revert the # setting to the default value. @@ -428,7 +500,7 @@ class Config @save() unless @configFileHasErrors true - # Extended: Restore the key path to its default value. + # Extended: Restore the global setting at `keyPath` to its default value. # # * `keyPath` The {String} name of the key. # @@ -437,7 +509,7 @@ class Config @set(keyPath, _.valueForKeyPath(@defaultSettings, keyPath)) @get(keyPath) - # Extended: Get the default value of the key path. _Please note_ that in most + # Extended: Get the global default value of the key path. _Please note_ that in most # cases calling this is not necessary! {::get} returns the default value when # a custom value is not specified. # @@ -448,7 +520,7 @@ class Config defaultValue = _.valueForKeyPath(@defaultSettings, keyPath) _.deepClone(defaultValue) - # Extended: Is the key path value its default value? + # Extended: Is the value at `keyPath` its default value? # # * `keyPath` The {String} name of the key. # @@ -457,7 +529,7 @@ class Config isDefault: (keyPath) -> not _.valueForKeyPath(@settings, keyPath)? - # Extended: Retrieve the schema for a specific key path. The shema will tell + # Extended: Retrieve the schema for a specific key path. The schema will tell # you what type the keyPath expects, and other metadata about the config # option. # @@ -473,7 +545,8 @@ class Config schema = schema.properties[key] schema - # Extended: Returns a new {Object} containing all of settings and defaults. + # Extended: Returns a new {Object} containing all of the global settings and + # defaults. This does not include scoped settings. getSettings: -> _.deepExtend(@settings, @defaultSettings) From 16fd53c1235289ae68548c33378da31c3e8b9031 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 17:32:30 -0700 Subject: [PATCH 23/28] Add schemas for scoped configs --- src/config-schema.coffee | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 4f7779dda..822bd6135 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -38,6 +38,19 @@ module.exports = editor: type: 'object' properties: + # These settings are used in scoped fashion only. No defaults. + commentStart: + type: 'string' + commentEnd: + type: 'string' + increaseIndentPattern: + type: 'string' + decreaseIndentPattern: + type: 'string' + foldEndPattern: + type: 'string' + + # These can be used as globals or scoped, thus defaults. fontFamily: type: 'string' default: '' From 27da0669f32e440d9e5a3a4ac7c624ccf7d28f7d Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 2 Oct 2014 18:04:38 -0700 Subject: [PATCH 24/28] Moar :memo: --- src/config-schema.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 822bd6135..08bf8e347 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -1,7 +1,8 @@ path = require 'path' fs = require 'fs-plus' -# This is loaded by atom.coffee +# This is loaded by atom.coffee. See https://atom.io/docs/api/latest/Config for +# more information about config schemas. module.exports = core: type: 'object' From a711e908d560af71542a63a16ff18bed4ce0d558 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 3 Oct 2014 10:45:37 -0700 Subject: [PATCH 25/28] :lipstick: --- spec/config-spec.coffee | 4 +--- src/config.coffee | 2 +- src/language-mode.coffee | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 473b029c7..c42add20c 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -883,10 +883,8 @@ describe "Config", -> expect(atom.config.get([".source.coffee", ".string.quoted.single.double"], "foo.bar.baz")).toBe 22 describe 'when there are global defaults', -> - beforeEach -> - atom.config.setDefaults("foo", hasDefault: 'ok') - it 'falls back to the global when there is no scoped property specified', -> + atom.config.setDefaults("foo", hasDefault: 'ok') expect(atom.config.get([".source.coffee", ".string.quoted.single"], "foo.hasDefault")).toBe 'ok' describe ".set(scope, keyPath, value)", -> diff --git a/src/config.coffee b/src/config.coffee index 80501e768..cdf2aede5 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -357,7 +357,7 @@ class Config message = "`callNow` was set to false. Use ::onDidChange instead. Note that ::onDidChange calls back with different arguments." if options.callNow == false deprecate "Config::observe no longer supports options; see https://atom.io/docs/api/latest/Config. #{message}" else - console.warn 'An unsupported form of Config::observe is being used. See https://atom.io/docs/api/latest/Config for details' + console.error 'An unsupported form of Config::observe is being used. See https://atom.io/docs/api/latest/Config for details' return if scopeDescriptor? diff --git a/src/language-mode.coffee b/src/language-mode.coffee index 32112309e..34d7f3032 100644 --- a/src/language-mode.coffee +++ b/src/language-mode.coffee @@ -30,7 +30,7 @@ class LanguageMode # Returns an {Array} of the commented {Ranges}. toggleLineCommentsForBufferRows: (start, end) -> scopes = @editor.scopesForBufferPosition([start, 0]) - properties = atom.config.settingsForScopeDescriptor(scopes, "editor.commentStart")[0] + properties = atom.config.settingsForScopeDescriptor(scopes, 'editor.commentStart')[0] return unless properties commentStartString = _.valueForKeyPath(properties, 'editor.commentStart') From 47d5b46a1d792e1637c8e0424a448b580bb6f687 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 3 Oct 2014 11:42:45 -0700 Subject: [PATCH 26/28] Fix warnings from schema incorrectness --- src/config-schema.coffee | 10 +++++----- src/config.coffee | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 08bf8e347..a400888be 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -41,15 +41,15 @@ module.exports = properties: # These settings are used in scoped fashion only. No defaults. commentStart: - type: 'string' + type: ['string', 'null'] commentEnd: - type: 'string' + type: ['string', 'null'] increaseIndentPattern: - type: 'string' + type: ['string', 'null'] decreaseIndentPattern: - type: 'string' + type: ['string', 'null'] foldEndPattern: - type: 'string' + type: ['string', 'null'] # These can be used as globals or scoped, thus defaults. fontFamily: diff --git a/src/config.coffee b/src/config.coffee index cdf2aede5..ffffa263d 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -882,7 +882,7 @@ Config.addSchemaEnforcers 'null': # null sort of isnt supported. It will just unset in this case coerce: (keyPath, value, schema) -> - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be null") unless value == null + throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be null") unless value in [undefined, null] value 'object': From 4e3c8406ee70b6544a3e52c99fb484362bbec225 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 3 Oct 2014 11:57:35 -0700 Subject: [PATCH 27/28] Clean up docs --- src/config.coffee | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index ffffa263d..c14180b25 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -334,7 +334,9 @@ class Config # # do stuff with value # ``` # - # * `scopeDescriptor` (optional) {Array} of {String}s. See {::get} for examples + # * `scopeDescriptor` (optional) {Array} of {String}s describing a path from + # the root of the syntax tree to a token. Get one by calling + # {TextEditor::scopesAtCursor}. See {::get} for examples. # * `keyPath` {String} name of the key to observe # * `callback` {Function} to call when the value of the key changes. # * `value` the new value of the key @@ -368,7 +370,9 @@ class Config # Essential: Add a listener for changes to a given key path. If `keyPath` is # not specified, your callback will be called on changes to any key. # - # * `scopeDescriptor` (optional) {Array} of {String}s. See {::get} for examples + # * `scopeDescriptor` (optional) {Array} of {String}s describing a path from + # the root of the syntax tree to a token. Get one by calling + # {TextEditor::scopesAtCursor}. See {::get} for examples. # * `keyPath` (optional) {String} name of the key to observe. Must be # specified if `scopeDescriptor` is specified. # * `callback` {Function} to call when the value of the key changes. @@ -423,11 +427,13 @@ class Config # Additionally, you can get the setting at the specific cursor position. # # ```coffee - # scopeDescriptor = @editor.scopesForBufferPosition(@editor.getCursorBufferPosition()) + # scopeDescriptor = @editor.scopesAtCursor() # atom.config.get(scopeDescriptor, 'editor.tabLength') # => 2 # ``` # - # * `scopeDescriptor` (optional) {Array} of {String}s. + # * `scopeDescriptor` (optional) {Array} of {String}s describing a path from + # the root of the syntax tree to a token. Get one by calling + # {TextEditor::scopesAtCursor} # * `keyPath` The {String} name of the key to retrieve. # # Returns the value from Atom's default settings, the user's configuration From 062fa29895a79e9158fe973a4d8856cd8f62c877 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 3 Oct 2014 11:57:50 -0700 Subject: [PATCH 28/28] addRawScopedValue -> setRawScopedValue --- src/config.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index c14180b25..bb45a09ec 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -499,7 +499,7 @@ class Config return false if scope? - @addRawScopedValue(scope, keyPath, value) + @setRawScopedValue(scope, keyPath, value) else @setRawValue(keyPath, value) @@ -796,7 +796,7 @@ class Config disposable.dispose() @emitter.emit 'did-change' - addRawScopedValue: (selector, keyPath, value) -> + setRawScopedValue: (selector, keyPath, value) -> if keyPath? newValue = {} _.setValueForKeyPath(newValue, keyPath, value)