mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Merge pull request #4802 from atom/mb-reduce-config-saves
Don't write the config to disk so darn often
This commit is contained in:
@@ -9,6 +9,8 @@ describe "Config", ->
|
||||
|
||||
beforeEach ->
|
||||
dotAtomPath = temp.path('dot-atom-dir')
|
||||
atom.config.configDirPath = dotAtomPath
|
||||
atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
|
||||
|
||||
describe ".get(keyPath, {scope, sources, excludeSources})", ->
|
||||
it "allows a key path's value to be read", ->
|
||||
@@ -124,15 +126,21 @@ describe "Config", ->
|
||||
expect(atom.config.set("foo.bar.baz", 42)).toBe true
|
||||
expect(atom.config.get("foo.bar.baz")).toBe 42
|
||||
|
||||
it "updates observers and saves when a key path is set", ->
|
||||
observeHandler = jasmine.createSpy "observeHandler"
|
||||
atom.config.observe "foo.bar.baz", observeHandler
|
||||
observeHandler.reset()
|
||||
|
||||
it "saves the user's config to disk after it stops changing", ->
|
||||
atom.config.set("foo.bar.baz", 42)
|
||||
|
||||
advanceClock(50)
|
||||
expect(atom.config.save).not.toHaveBeenCalled()
|
||||
atom.config.set("foo.bar.baz", 43)
|
||||
advanceClock(50)
|
||||
expect(atom.config.save).not.toHaveBeenCalled()
|
||||
atom.config.set("foo.bar.baz", 44)
|
||||
advanceClock(150)
|
||||
expect(atom.config.save).toHaveBeenCalled()
|
||||
expect(observeHandler).toHaveBeenCalledWith 42
|
||||
|
||||
it "does not save when a non-default 'source' is given", ->
|
||||
atom.config.set("foo.bar.baz", 42, source: 'some-other-source', scopeSelector: '.a')
|
||||
advanceClock(500)
|
||||
expect(atom.config.save).not.toHaveBeenCalled()
|
||||
|
||||
it "does not allow a 'source' option without a 'scopeSelector'", ->
|
||||
expect(-> atom.config.set("foo", 1, source: [".source.ruby"])).toThrow()
|
||||
@@ -215,6 +223,7 @@ describe "Config", ->
|
||||
atom.config.save.reset()
|
||||
|
||||
atom.config.unset('a.c')
|
||||
advanceClock(500)
|
||||
expect(atom.config.save.callCount).toBe 1
|
||||
|
||||
describe "when a 'source' and no 'scopeSelector' is given", ->
|
||||
@@ -252,6 +261,7 @@ describe "Config", ->
|
||||
atom.config.save.reset()
|
||||
|
||||
atom.config.unset('foo.bar.baz', scopeSelector: '.source.coffee')
|
||||
advanceClock(150)
|
||||
expect(atom.config.save.callCount).toBe 1
|
||||
|
||||
it "allows removing settings for a specific source and scope selector", ->
|
||||
@@ -285,26 +295,32 @@ describe "Config", ->
|
||||
|
||||
it "removes the scoped value when it was the only set value on the object", ->
|
||||
spyOn(CSON, 'writeFileSync')
|
||||
jasmine.unspy atom.config, 'save'
|
||||
atom.config.save.andCallThrough()
|
||||
|
||||
atom.config.setDefaults("foo", bar: baz: 10)
|
||||
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
|
||||
atom.config.set('foo.bar.ok', 20, scopeSelector: '.source.coffee')
|
||||
CSON.writeFileSync.reset()
|
||||
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 55
|
||||
|
||||
advanceClock(150)
|
||||
CSON.writeFileSync.reset()
|
||||
|
||||
atom.config.unset('foo.bar.baz', scopeSelector: '.source.coffee')
|
||||
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 10
|
||||
expect(atom.config.get('foo.bar.ok', scope: ['.source.coffee'])).toBe 20
|
||||
|
||||
advanceClock(150)
|
||||
expect(CSON.writeFileSync).toHaveBeenCalled()
|
||||
properties = CSON.writeFileSync.mostRecentCall.args[1]
|
||||
expect(properties['.coffee.source']).toEqual
|
||||
foo:
|
||||
bar:
|
||||
ok: 20
|
||||
|
||||
CSON.writeFileSync.reset()
|
||||
|
||||
atom.config.unset('foo.bar.ok', scopeSelector: '.source.coffee')
|
||||
|
||||
advanceClock(150)
|
||||
expect(CSON.writeFileSync).toHaveBeenCalled()
|
||||
properties = CSON.writeFileSync.mostRecentCall.args[1]
|
||||
expect(properties['.coffee.source']).toBeUndefined()
|
||||
@@ -573,7 +589,6 @@ describe "Config", ->
|
||||
|
||||
describe "when ~/.atom/config.json exists", ->
|
||||
it "writes any non-default properties to ~/.atom/config.json", ->
|
||||
atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.json")
|
||||
atom.config.set("a.b.c", 1)
|
||||
atom.config.set("a.b.d", 2)
|
||||
atom.config.set("x.y.z", 3)
|
||||
@@ -582,13 +597,12 @@ describe "Config", ->
|
||||
CSON.writeFileSync.reset()
|
||||
atom.config.save()
|
||||
|
||||
expect(CSON.writeFileSync.argsForCall[0][0]).toBe path.join(atom.config.configDirPath, "atom.config.json")
|
||||
expect(CSON.writeFileSync.argsForCall[0][0]).toBe atom.config.configFilePath
|
||||
writtenConfig = CSON.writeFileSync.argsForCall[0][1]
|
||||
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", ->
|
||||
atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
|
||||
atom.config.set("a.b.c", 1)
|
||||
atom.config.set("a.b.d", 2)
|
||||
atom.config.set("x.y.z", 3)
|
||||
@@ -624,8 +638,6 @@ describe "Config", ->
|
||||
|
||||
describe ".loadUserConfig()", ->
|
||||
beforeEach ->
|
||||
atom.config.configDirPath = dotAtomPath
|
||||
atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
|
||||
expect(fs.existsSync(atom.config.configDirPath)).toBeFalsy()
|
||||
atom.config.setSchema 'foo',
|
||||
type: 'object'
|
||||
@@ -714,6 +726,15 @@ describe "Config", ->
|
||||
describe ".observeUserConfig()", ->
|
||||
updatedHandler = null
|
||||
|
||||
writeConfigFile = (data) ->
|
||||
previousSetTimeoutCallCount = setTimeout.callCount
|
||||
runs ->
|
||||
fs.writeFileSync(atom.config.configFilePath, data)
|
||||
waitsFor "debounced config file load", ->
|
||||
setTimeout.callCount > previousSetTimeoutCallCount
|
||||
runs ->
|
||||
advanceClock(1000)
|
||||
|
||||
beforeEach ->
|
||||
atom.config.setSchema 'foo',
|
||||
type: 'object'
|
||||
@@ -729,8 +750,6 @@ describe "Config", ->
|
||||
type: 'integer'
|
||||
default: 12
|
||||
|
||||
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, """
|
||||
global:
|
||||
@@ -752,7 +771,7 @@ describe "Config", ->
|
||||
|
||||
describe "when the config file changes to contain valid cson", ->
|
||||
it "updates the config data", ->
|
||||
fs.writeFileSync(atom.config.configFilePath, "foo: { bar: 'quux', baz: 'bar'}")
|
||||
writeConfigFile("foo: { bar: 'quux', baz: 'bar'}")
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
runs ->
|
||||
expect(atom.config.get('foo.bar')).toBe 'quux'
|
||||
@@ -761,8 +780,9 @@ describe "Config", ->
|
||||
it "does not fire a change event for paths that did not change", ->
|
||||
atom.config.onDidChange 'foo.bar', noChangeSpy = jasmine.createSpy()
|
||||
|
||||
fs.writeFileSync(atom.config.configFilePath, "foo: { bar: 'baz', baz: 'ok'}")
|
||||
writeConfigFile("foo: { bar: 'baz', baz: 'ok'}")
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(noChangeSpy).not.toHaveBeenCalled()
|
||||
expect(atom.config.get('foo.bar')).toBe 'baz'
|
||||
@@ -774,7 +794,8 @@ describe "Config", ->
|
||||
type: 'array'
|
||||
items:
|
||||
type: 'string'
|
||||
fs.writeFileSync(atom.config.configFilePath, "foo: { bar: ['baz', 'ok']}")
|
||||
|
||||
writeConfigFile("foo: { bar: ['baz', 'ok']}")
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
runs -> updatedHandler.reset()
|
||||
|
||||
@@ -782,8 +803,9 @@ describe "Config", ->
|
||||
noChangeSpy = jasmine.createSpy()
|
||||
atom.config.onDidChange('foo.bar', noChangeSpy)
|
||||
|
||||
fs.writeFileSync(atom.config.configFilePath, "foo: { bar: ['baz', 'ok'], baz: 'another'}")
|
||||
writeConfigFile("foo: { bar: ['baz', 'ok'], baz: 'another'}")
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(noChangeSpy).not.toHaveBeenCalled()
|
||||
expect(atom.config.get('foo.bar')).toEqual ['baz', 'ok']
|
||||
@@ -794,12 +816,13 @@ describe "Config", ->
|
||||
scopedSpy = jasmine.createSpy()
|
||||
atom.config.onDidChange('foo.scoped', scope: ['.source.ruby'], scopedSpy)
|
||||
|
||||
fs.writeFileSync atom.config.configFilePath, """
|
||||
writeConfigFile """
|
||||
global:
|
||||
foo:
|
||||
scoped: false
|
||||
"""
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(scopedSpy).toHaveBeenCalled()
|
||||
expect(atom.config.get('foo.scoped', scope: ['.source.ruby'])).toBe false
|
||||
@@ -808,7 +831,7 @@ describe "Config", ->
|
||||
noChangeSpy = jasmine.createSpy()
|
||||
atom.config.onDidChange('foo.scoped', scope: ['.source.ruby'], noChangeSpy)
|
||||
|
||||
fs.writeFileSync atom.config.configFilePath, """
|
||||
writeConfigFile """
|
||||
global:
|
||||
foo:
|
||||
bar: 'baz'
|
||||
@@ -817,6 +840,7 @@ describe "Config", ->
|
||||
scoped: true
|
||||
"""
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(noChangeSpy).not.toHaveBeenCalled()
|
||||
expect(atom.config.get('foo.bar', scope: ['.source.ruby'])).toBe 'baz'
|
||||
@@ -824,7 +848,7 @@ describe "Config", ->
|
||||
|
||||
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'}")
|
||||
writeConfigFile("foo: { baz: 'new'}")
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
runs ->
|
||||
expect(atom.config.get('foo.bar')).toBe 'def'
|
||||
@@ -832,19 +856,20 @@ describe "Config", ->
|
||||
|
||||
describe "when the config file changes to be empty", ->
|
||||
beforeEach ->
|
||||
fs.writeFileSync(atom.config.configFilePath, "")
|
||||
writeConfigFile("")
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
|
||||
it "resets all settings back to the defaults", ->
|
||||
expect(updatedHandler.callCount).toBe 1
|
||||
expect(atom.config.get('foo.bar')).toBe 'def'
|
||||
atom.config.set("hair", "blonde") # trigger a save
|
||||
advanceClock(500)
|
||||
expect(atom.config.save).toHaveBeenCalled()
|
||||
|
||||
describe "when the config file subsequently changes again to contain configuration", ->
|
||||
beforeEach ->
|
||||
updatedHandler.reset()
|
||||
fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'newVal'")
|
||||
writeConfigFile("foo: bar: 'newVal'")
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
|
||||
it "sets the setting to the value specified in the config file", ->
|
||||
@@ -853,7 +878,7 @@ describe "Config", ->
|
||||
describe "when the config file changes to contain invalid cson", ->
|
||||
beforeEach ->
|
||||
spyOn(console, 'error')
|
||||
fs.writeFileSync(atom.config.configFilePath, "}}}")
|
||||
writeConfigFile("}}}")
|
||||
waitsFor "error to be logged", -> console.error.callCount > 0
|
||||
|
||||
it "logs a warning and does not update config data", ->
|
||||
@@ -864,11 +889,12 @@ describe "Config", ->
|
||||
|
||||
describe "when the config file subsequently changes again to contain valid cson", ->
|
||||
beforeEach ->
|
||||
fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'newVal'")
|
||||
writeConfigFile("foo: bar: 'newVal'")
|
||||
waitsFor 'update event', -> updatedHandler.callCount > 0
|
||||
|
||||
it "updates the config data and resumes saving", ->
|
||||
atom.config.set("hair", "blonde")
|
||||
advanceClock(500)
|
||||
expect(atom.config.save).toHaveBeenCalled()
|
||||
|
||||
describe ".initializeConfigDirectory()", ->
|
||||
|
||||
@@ -84,6 +84,10 @@ beforeEach ->
|
||||
atom.workspaceViewParentSelector = '#jasmine-content'
|
||||
|
||||
window.resetTimeouts()
|
||||
spyOn(_._, "now").andCallFake -> window.now
|
||||
spyOn(window, "setTimeout").andCallFake window.fakeSetTimeout
|
||||
spyOn(window, "clearTimeout").andCallFake window.fakeClearTimeout
|
||||
|
||||
atom.packages.packageStates = {}
|
||||
|
||||
serializedWindowState = null
|
||||
@@ -102,9 +106,9 @@ beforeEach ->
|
||||
spyOn(atom.menu, 'sendToBrowserProcess')
|
||||
|
||||
# reset config before each spec; don't load or save from/to `config.json`
|
||||
spyOn(Config::, 'load')
|
||||
spyOn(Config::, 'save')
|
||||
config = new Config({resourcePath, configDirPath: atom.getConfigDirPath()})
|
||||
spyOn(config, 'load')
|
||||
spyOn(config, 'save')
|
||||
atom.config = config
|
||||
atom.loadConfig()
|
||||
config.set "core.destroyEmptyPanes", false
|
||||
@@ -114,6 +118,7 @@ beforeEach ->
|
||||
config.set "core.disabledPackages", ["package-that-throws-an-exception",
|
||||
"package-with-broken-package-json", "package-with-broken-keymap"]
|
||||
config.set "editor.useShadowDOM", true
|
||||
advanceClock(1000)
|
||||
config.load.reset()
|
||||
config.save.reset()
|
||||
|
||||
@@ -121,8 +126,6 @@ beforeEach ->
|
||||
TextEditorElement::setUpdatedSynchronously(true)
|
||||
|
||||
spyOn(atom, "setRepresentedFilename")
|
||||
spyOn(window, "setTimeout").andCallFake window.fakeSetTimeout
|
||||
spyOn(window, "clearTimeout").andCallFake window.fakeClearTimeout
|
||||
spyOn(pathwatcher.File.prototype, "detectResurrectionAfterDelay").andCallFake -> @detectResurrection()
|
||||
spyOn(TextEditor.prototype, "shouldPromptToSave").andReturn false
|
||||
|
||||
|
||||
@@ -755,7 +755,6 @@ describe "TextEditorComponent", ->
|
||||
expect(cursorNode.offsetWidth).toBe charWidth
|
||||
|
||||
it "blinks cursors when they aren't moving", ->
|
||||
spyOn(_._, 'now').andCallFake -> window.now # Ensure _.debounce is based on our fake spec timeline
|
||||
cursorsNode = componentNode.querySelector('.cursors')
|
||||
|
||||
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
||||
@@ -2048,8 +2047,6 @@ describe "TextEditorComponent", ->
|
||||
expect(component.mouseWheelScreenRow).toBe null
|
||||
|
||||
it "clears the mouseWheelScreenRow after a delay even if the event does not cause scrolling", ->
|
||||
spyOn(_._, 'now').andCallFake -> window.now # Ensure _.debounce is based on our fake spec timeline
|
||||
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
|
||||
lineNode = componentNode.querySelector('.line')
|
||||
|
||||
@@ -317,8 +317,9 @@ class Config
|
||||
@configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson'])
|
||||
@configFilePath ?= path.join(@configDirPath, 'config.cson')
|
||||
@transactDepth = 0
|
||||
@prioritiesBySource = {}
|
||||
@prioritiesBySource[@getUserConfigPath()] = 1000
|
||||
|
||||
@debouncedSave = _.debounce(@save, 100)
|
||||
@debouncedLoad = _.debounce(@loadUserConfig, 100)
|
||||
|
||||
###
|
||||
Section: Config Subscription
|
||||
@@ -570,6 +571,7 @@ class Config
|
||||
[keyPath, value, options] = arguments
|
||||
scopeSelector = options?.scopeSelector
|
||||
source = options?.source
|
||||
shouldSave = options?.save ? true
|
||||
|
||||
if source and not scopeSelector
|
||||
throw new Error("::set with a 'source' and no 'sourceSelector' is not yet implemented!")
|
||||
@@ -587,7 +589,7 @@ class Config
|
||||
else
|
||||
@setRawValue(keyPath, value)
|
||||
|
||||
@save() unless @configFileHasErrors or options?.save is false
|
||||
@debouncedSave() if source is @getUserConfigPath() and shouldSave and not @configFileHasErrors
|
||||
true
|
||||
|
||||
# Essential: Restore the setting at `keyPath` to its default value.
|
||||
@@ -616,17 +618,16 @@ class Config
|
||||
@scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector)
|
||||
_.setValueForKeyPath(settings, keyPath, undefined)
|
||||
settings = withoutEmptyObjects(settings)
|
||||
@set(null, settings, {scopeSelector, source, priority: @prioritiesBySource[source]}) if settings?
|
||||
@save() unless @configFileHasErrors
|
||||
@set(null, settings, {scopeSelector, source, priority: @priorityForSource(source)}) if settings?
|
||||
@debouncedSave()
|
||||
else
|
||||
@scopedSettingsStore.removePropertiesForSource(source)
|
||||
@scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector)
|
||||
@emitChangeEvent()
|
||||
else
|
||||
@scopedSettingsStore.removePropertiesForSource(source)
|
||||
if keyPath?
|
||||
@set(keyPath, _.valueForKeyPath(@defaultSettings, keyPath))
|
||||
|
||||
|
||||
# Extended: Get an {Array} of all of the `source` {String}s with which
|
||||
# settings have been added via {::set}.
|
||||
getSources: ->
|
||||
@@ -820,7 +821,7 @@ class Config
|
||||
observeUserConfig: ->
|
||||
try
|
||||
@watchSubscription ?= pathWatcher.watch @configFilePath, (eventType) =>
|
||||
@loadUserConfig() if eventType is 'change' and @watchSubscription?
|
||||
@debouncedLoad() if eventType is 'change' and @watchSubscription?
|
||||
catch error
|
||||
@notifyFailure('Failed to watch user config', error)
|
||||
|
||||
@@ -963,12 +964,19 @@ class Config
|
||||
Section: Private Scoped Settings
|
||||
###
|
||||
|
||||
priorityForSource: (source) ->
|
||||
if source is @getUserConfigPath()
|
||||
1000
|
||||
else
|
||||
0
|
||||
|
||||
emitChangeEvent: ->
|
||||
@emitter.emit 'did-change' unless @transactDepth > 0
|
||||
|
||||
resetUserScopedSettings: (newScopedSettings) ->
|
||||
@scopedSettingsStore.removePropertiesForSource(@getUserConfigPath())
|
||||
@scopedSettingsStore.addProperties(@getUserConfigPath(), newScopedSettings, priority: @prioritiesBySource[@getUserConfigPath()])
|
||||
source = @getUserConfigPath()
|
||||
@scopedSettingsStore.removePropertiesForSource(source)
|
||||
@scopedSettingsStore.addProperties(source, newScopedSettings, priority: @priorityForSource(source))
|
||||
@emitChangeEvent()
|
||||
|
||||
addScopedSettings: (source, selector, value, options) ->
|
||||
@@ -989,7 +997,7 @@ class Config
|
||||
|
||||
settingsBySelector = {}
|
||||
settingsBySelector[selector] = value
|
||||
@scopedSettingsStore.addProperties(source, settingsBySelector, priority: @prioritiesBySource[source])
|
||||
@scopedSettingsStore.addProperties(source, settingsBySelector, priority: @priorityForSource(source))
|
||||
@emitChangeEvent()
|
||||
|
||||
getRawScopedValue: (scopeDescriptor, keyPath, options) ->
|
||||
|
||||
Reference in New Issue
Block a user