diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index c42add20c..21c327533 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -200,7 +200,7 @@ describe "Config", -> expect(CSON.writeFileSync.argsForCall[0][0]).toBe(path.join(atom.config.configDirPath, "atom.config.json")) writtenConfig = CSON.writeFileSync.argsForCall[0][1] - expect(writtenConfig).toBe atom.config.settings + expect(writtenConfig).toEqual global: atom.config.settings describe "when ~/.atom/config.json doesn't exist", -> it "writes any non-default properties to ~/.atom/config.cson", -> @@ -214,9 +214,29 @@ describe "Config", -> atom.config.save() expect(CSON.writeFileSync.argsForCall[0][0]).toBe(path.join(atom.config.configDirPath, "atom.config.cson")) - CoffeeScript = require 'coffee-script' writtenConfig = CSON.writeFileSync.argsForCall[0][1] - expect(writtenConfig).toEqual atom.config.settings + expect(writtenConfig).toEqual global: atom.config.settings + + describe "when scoped settings are defined", -> + it 'writes out explicitly set config settings', -> + atom.config.set('.source.ruby', 'foo.bar', 'ruby') + atom.config.set('.source.ruby', 'foo.omg', 'wow') + atom.config.set('.source.coffee', 'foo.bar', 'coffee') + + CSON.writeFileSync.reset() + atom.config.save() + + writtenConfig = CSON.writeFileSync.argsForCall[0][1] + expect(writtenConfig).toEqualJson + global: + atom.config.settings + '*.ruby.source': + foo: + bar: 'ruby' + omg: 'wow' + '*.coffee.source': + foo: + bar: 'coffee' describe ".setDefaults(keyPath, defaults)", -> it "assigns any previously-unassigned keys to the object at the key path", -> @@ -356,6 +376,23 @@ describe "Config", -> afterEach -> fs.removeSync(dotAtomPath) + describe "when the config file contains scoped settings", -> + beforeEach -> + fs.writeFileSync atom.config.configFilePath, """ + global: + foo: + bar: 'baz' + + '.source.ruby': + foo: + bar: 'more-specific' + """ + atom.config.loadUserConfig() + + it "updates the config data based on the file contents", -> + expect(atom.config.get("foo.bar")).toBe 'baz' + expect(atom.config.get(['.source.ruby'], "foo.bar")).toBe 'more-specific' + describe "when the config file contains valid cson", -> beforeEach -> fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'baz'") @@ -430,7 +467,15 @@ describe "Config", -> atom.config.configDirPath = dotAtomPath atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson") expect(fs.existsSync(atom.config.configDirPath)).toBeFalsy() - fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'baz'") + fs.writeFileSync atom.config.configFilePath, """ + global: + foo: + bar: 'baz' + scoped: false + '.source.ruby': + foo: + scoped: true + """ atom.config.loadUserConfig() atom.config.observeUserConfig() updatedHandler = jasmine.createSpy("updatedHandler") @@ -474,6 +519,38 @@ describe "Config", -> expect(atom.config.get('foo.bar')).toEqual ['baz', 'ok'] expect(atom.config.get('foo.omg')).toBe 'another' + describe 'when scoped settings are used', -> + it "fires a change event for scoped settings that are removed", -> + atom.config.onDidChange ['.source.ruby'], 'foo.scoped', scopedSpy = jasmine.createSpy() + + fs.writeFileSync atom.config.configFilePath, """ + global: + foo: + scoped: false + """ + waitsFor 'update event', -> updatedHandler.callCount > 0 + runs -> + expect(scopedSpy).toHaveBeenCalled() + expect(atom.config.get(['.source.ruby'], 'foo.scoped')).toBe false + + it "does not fire a change event for paths that did not change", -> + atom.config.onDidChange ['.source.ruby'], 'foo.scoped', noChangeSpy = jasmine.createSpy() + + fs.writeFileSync atom.config.configFilePath, """ + global: + foo: + bar: 'baz' + '.source.ruby': + foo: + scoped: true + """ + waitsFor 'update event', -> updatedHandler.callCount > 0 + runs -> + expect(noChangeSpy).not.toHaveBeenCalled() + expect(atom.config.get(['.source.ruby'], 'foo.bar')).toBe 'baz' + expect(atom.config.get(['.source.ruby'], 'foo.scoped')).toBe true + + describe "when the config file changes to omit a setting with a default", -> it "resets the setting back to the default", -> fs.writeFileSync(atom.config.configFilePath, "foo: { baz: 'new'}") @@ -866,9 +943,9 @@ describe "Config", -> describe "scoped settings", -> describe ".get(scopeDescriptor, keyPath)", -> it "returns the property with the most specific scope selector", -> - 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) + atom.config.addScopedSettings("config", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + atom.config.addScopedSettings("config", ".source .string.quoted.double", foo: bar: baz: 22) + atom.config.addScopedSettings("config", ".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 +953,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.addScopedSettings(".source.coffee .string.quoted.single", foo: bar: baz: 42) - atom.config.addScopedSettings(".source.coffee .string.quoted.double", foo: bar: baz: 22) + atom.config.addScopedSettings("config", ".source.coffee .string.quoted.single", foo: bar: baz: 42) + atom.config.addScopedSettings("config", ".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 @@ -889,9 +966,9 @@ describe "Config", -> describe ".set(scope, keyPath, value)", -> it "sets the value and overrides the others", -> - 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) + atom.config.addScopedSettings("config", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + atom.config.addScopedSettings("config", ".source .string.quoted.double", foo: bar: baz: 22) + atom.config.addScopedSettings("config", ".source", foo: bar: baz: 11) expect(atom.config.get([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 42 @@ -917,11 +994,11 @@ describe "Config", -> expect(changeSpy).toHaveBeenCalledWith(12) changeSpy.reset() - disposable1 = atom.config.addScopedSettings(".source .string.quoted.double", foo: bar: baz: 22) + disposable1 = atom.config.addScopedSettings("a", ".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) + disposable2 = atom.config.addScopedSettings("b", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) expect(changeSpy).toHaveBeenCalledWith(42) changeSpy.reset() @@ -946,11 +1023,11 @@ describe "Config", -> expect(changeSpy).toHaveBeenCalledWith({oldValue: undefined, newValue: 12, keyPath}) changeSpy.reset() - disposable1 = atom.config.addScopedSettings(".source .string.quoted.double", foo: bar: baz: 22) + disposable1 = atom.config.addScopedSettings("a", ".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) + disposable2 = atom.config.addScopedSettings("b", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 42, keyPath}) changeSpy.reset() diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index e483d25a5..0396f4673 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -2,6 +2,7 @@ require '../src/window' atom.initialize() atom.restoreWindowDimensions() +require 'jasmine-json' require '../vendor/jasmine-jquery' path = require 'path' _ = require 'underscore-plus' diff --git a/src/config.coffee b/src/config.coffee index bb45a09ec..5ccb835c5 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -1,7 +1,7 @@ _ = require 'underscore-plus' fs = require 'fs-plus' EmitterMixin = require('emissary').Emitter -{Disposable, Emitter} = require 'event-kit' +{CompositeDisposable, Disposable, Emitter} = require 'event-kit' CSON = require 'season' path = require 'path' async = require 'async' @@ -312,6 +312,7 @@ class Config @defaultSettings = {} @settings = {} @scopedSettingsStore = new ScopedPropertyStore + @usersScopedSettings = new CompositeDisposable @configFileHasErrors = false @configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson']) @configFilePath ?= path.join(@configDirPath, 'config.cson') @@ -658,7 +659,7 @@ class Config try userConfig = CSON.readFileSync(@configFilePath) - @setAll(userConfig) + @resetUserSettings(userConfig) @configFileHasErrors = false catch error @configFileHasErrors = true @@ -678,18 +679,30 @@ class Config @watchSubscription = null save: -> - CSON.writeFileSync(@configFilePath, @settings) + allSettings = global: @settings + + for settingsObject in @scopedSettingsStore.propertiesForSource('user-config') + for selector, properties of settingsObject + allSettings[selector] = properties + + CSON.writeFileSync(@configFilePath, allSettings) ### Section: Private methods managing global settings ### - setAll: (newSettings) -> + resetUserSettings: (newSettings) -> unless isPlainObject(newSettings) @settings = {} @emitter.emit 'did-change' return + if newSettings.global? + scopedSettings = newSettings + newSettings = newSettings.global + delete scopedSettings.global + @resetUserScopedSettings(scopedSettings) + unsetUnspecifiedValues = (keyPath, value) => if isPlainObject(value) keys = if keyPath? then keyPath.split('.') else [] @@ -782,12 +795,13 @@ class Config Section: Private Scoped Settings ### - addScopedSettings: (name, selector, value) -> - if arguments.length < 3 - value = selector - selector = name - name = null + resetUserScopedSettings: (newScopedSettings) -> + @usersScopedSettings?.dispose() + @usersScopedSettings = new CompositeDisposable + @usersScopedSettings.add @scopedSettingsStore.addProperties('user-config', newScopedSettings) + @emitter.emit 'did-change' + addScopedSettings: (name, selector, value) -> settingsBySelector = {} settingsBySelector[selector] = value disposable = @scopedSettingsStore.addProperties(name, settingsBySelector) @@ -801,7 +815,11 @@ class Config newValue = {} _.setValueForKeyPath(newValue, keyPath, value) value = newValue - @addScopedSettings(null, selector, value) + + settingsBySelector = {} + settingsBySelector[selector] = value + @usersScopedSettings.add @scopedSettingsStore.addProperties('user-config', settingsBySelector) + @emitter.emit 'did-change' getRawScopedValue: (scopeDescriptor, keyPath) -> scopeChain = scopeDescriptor diff --git a/src/syntax.coffee b/src/syntax.coffee index 84bb2e0bb..8c04a424a 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -40,6 +40,7 @@ class Syntax extends GrammarRegistry atom.config.scopedSettingsStore addProperties: (args...) -> + args.unshift(null) if args.length == 2 deprecate 'Consider using atom.config.set() instead. A direct (but private) replacement is available at atom.config.addScopedSettings().' atom.config.addScopedSettings(args...)