Start moving config observation out of editor into editor registry

Signed-off-by: Nathan Sobo <nathan@github.com>
This commit is contained in:
Max Brunsfeld
2016-07-08 17:41:59 -07:00
committed by Nathan Sobo
parent e9650f611f
commit eec1b70967
4 changed files with 196 additions and 22 deletions

View File

@@ -1,17 +1,31 @@
/** @babel */
import {TextBuffer} from 'atom'
import TextEditorRegistry from '../src/text-editor-registry'
import TextEditor from '../src/text-editor'
describe('TextEditorRegistry', function () {
let registry, editor
beforeEach(function () {
registry = new TextEditorRegistry()
registry = new TextEditorRegistry({
config: atom.config
})
editor = new TextEditor({
buffer: new TextBuffer({filePath: 'test.js'}),
config: atom.config,
clipboard: atom.clipboard,
grammarRegistry: atom.grammars
})
})
afterEach(function () {
registry.destroy()
})
describe('.add', function () {
it('adds an editor to the list of registered editors', function () {
editor = {}
registry.add(editor)
expect(editor.registered).toBe(true)
expect(registry.editors.size).toBe(1)
@@ -19,7 +33,6 @@ describe('TextEditorRegistry', function () {
})
it('returns a Disposable that can unregister the editor', function () {
editor = {}
const disposable = registry.add(editor)
expect(registry.editors.size).toBe(1)
disposable.dispose()
@@ -46,4 +59,92 @@ describe('TextEditorRegistry', function () {
expect(spy.calls.length).toBe(2)
})
})
describe('.maintainGrammar', function () {
it('assigns a grammar to the editor based on its path', async function () {
await atom.packages.activatePackage('language-javascript')
await atom.packages.activatePackage('language-c')
registry.maintainGrammar(editor)
expect(editor.getGrammar().name).toBe('JavaScript')
editor.getBuffer().setPath('test.c')
expect(editor.getGrammar().name).toBe('C')
})
it('updates the editor\'s grammar when a more appropriate grammar is added for its path', async function () {
expect(editor.getGrammar().name).toBe('Null Grammar')
registry.maintainGrammar(editor)
await atom.packages.activatePackage('language-javascript')
expect(editor.getGrammar().name).toBe('JavaScript')
});
})
describe('.maintainConfig(editor)', function () {
it('sets the encoding based on the config', function () {
editor.setEncoding('utf8')
expect(editor.getEncoding()).toBe('utf8')
atom.config.set('core.fileEncoding', 'utf16le')
registry.maintainConfig(editor)
expect(editor.getEncoding()).toBe('utf16le')
atom.config.set('core.fileEncoding', 'utf8')
expect(editor.getEncoding()).toBe('utf8')
});
it('sets the tab length based on the config', function () {
editor.setTabLength(4)
expect(editor.getTabLength()).toBe(4)
atom.config.set('editor.tabLength', 8)
registry.maintainConfig(editor)
expect(editor.getTabLength()).toBe(8)
atom.config.set('editor.tabLength', 4)
expect(editor.getTabLength()).toBe(4)
});
it('enables or disables atomic soft tabs based on the config', function () {
editor.setAtomicSoftTabs(true)
expect(editor.hasAtomicSoftTabs()).toBe(true)
atom.config.set('editor.atomicSoftTabs', false)
registry.maintainConfig(editor)
expect(editor.hasAtomicSoftTabs()).toBe(false)
atom.config.set('editor.atomicSoftTabs', true)
expect(editor.hasAtomicSoftTabs()).toBe(true)
});
it('enables or disables invisible based on the config', function () {
editor.setShowInvisibles(true)
expect(editor.doesShowInvisibles()).toBe(true)
atom.config.set('editor.showInvisibles', false)
registry.maintainConfig(editor)
expect(editor.doesShowInvisibles()).toBe(false)
atom.config.set('editor.showInvisibles', true)
expect(editor.doesShowInvisibles()).toBe(true)
});
it('sets the invisibles based on the config', function () {
editor.setShowInvisibles(true)
atom.config.set('editor.showInvisibles', true)
const invisibles1 = {'tab': 'a', 'cr': false, eol: false, space: false}
const invisibles2 = {'tab': 'b', 'cr': false, eol: false, space: false}
editor.setInvisibles(invisibles1)
expect(editor.getInvisibles()).toEqual(invisibles1)
atom.config.set('editor.invisibles', invisibles2)
registry.maintainConfig(editor)
expect(editor.getInvisibles()).toEqual(invisibles2)
atom.config.set('editor.invisibles', invisibles1)
expect(editor.getInvisibles()).toEqual(invisibles1)
});
})
})

View File

@@ -195,7 +195,7 @@ class AtomEnvironment extends Model
})
@themes.workspace = @workspace
@textEditors = new TextEditorRegistry
@textEditors = new TextEditorRegistry({@config})
@autoUpdater = new AutoUpdateManager({@applicationDelegate})
@config.load()

View File

@@ -1,6 +1,14 @@
/** @babel */
import {Emitter, Disposable} from "event-kit"
import {Emitter, Disposable, CompositeDisposable} from "event-kit"
const EDITOR_SETTER_NAMES_BY_SETTING_KEY = [
['core.fileEncoding', 'setEncoding'],
['editor.atomicSoftTabs', 'setAtomicSoftTabs'],
['editor.showInvisibles', 'setShowInvisibles'],
['editor.tabLength', 'setTabLength'],
['editor.invisibles', 'setInvisibles'],
]
// Experimental: This global registry tracks registered `TextEditors`.
//
@@ -14,9 +22,18 @@ import {Emitter, Disposable} from "event-kit"
// done using your editor, be sure to call `dispose` on the returned disposable
// to avoid leaking editors.
export default class TextEditorRegistry {
constructor () {
constructor ({config}) {
this.config = config
this.subscriptions = new CompositeDisposable()
this.editors = new Set()
this.emitter = new Emitter()
this.scopesWithConfigSubscriptions = new Set()
this.editorsWithMaintainedConfig = new Set()
}
destroy () {
this.subscriptions.dispose()
this.editorsWithMaintainedConfig = null
}
// Register a `TextEditor`.
@@ -55,4 +72,40 @@ export default class TextEditorRegistry {
this.editors.forEach(callback)
return this.emitter.on("did-add-editor", callback)
}
maintainGrammar (editor) {
}
maintainConfig (editor) {
this.editorsWithMaintainedConfig.add(editor)
this.subscribeToSettingsForEditorScope(editor)
const configOptions = {scope: editor.getRootScopeDescriptor()}
for (const [settingKey, setterName] of EDITOR_SETTER_NAMES_BY_SETTING_KEY) {
editor[setterName](atom.config.get(settingKey, configOptions))
}
}
subscribeToSettingsForEditorScope (editor) {
const scopeDescriptor = editor.getRootScopeDescriptor()
const scopeChain = scopeDescriptor.getScopeChain()
if (!this.scopesWithConfigSubscriptions.has(scopeChain)) {
this.scopesWithConfigSubscriptions.add(scopeChain)
const configOptions = {scope: scopeDescriptor}
for (const [settingKey, setterName] of EDITOR_SETTER_NAMES_BY_SETTING_KEY) {
this.subscriptions.add(
this.config.onDidChange(settingKey, configOptions, ({newValue}) => {
this.editorsWithMaintainedConfig.forEach(editor => {
if (editor.getRootScopeDescriptor().getScopeChain() === scopeChain) {
editor[setterName](newValue)
}
})
})
)
}
}
}
}

View File

@@ -77,6 +77,8 @@ class TextEditor extends Model
height: null
width: null
registered: false
atomicSoftTabs: true
invisibles: null
Object.defineProperty @prototype, "element",
get: -> @getElement()
@@ -176,8 +178,6 @@ class TextEditor extends Model
@languageMode = new LanguageMode(this, @config)
@setEncoding(@config.get('core.fileEncoding', scope: @getRootScopeDescriptor()))
@gutterContainer = new GutterContainer(this)
@lineNumberGutter = @gutterContainer.addGutter
name: 'line-number'
@@ -231,10 +231,7 @@ class TextEditor extends Model
@scopedConfigSubscriptions = subscriptions = new CompositeDisposable
scopeDescriptor = @getRootScopeDescriptor()
subscriptions.add @config.onDidChange 'editor.atomicSoftTabs', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
subscriptions.add @config.onDidChange 'editor.tabLength', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
subscriptions.add @config.onDidChange 'editor.invisibles', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
subscriptions.add @config.onDidChange 'editor.showInvisibles', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
subscriptions.add @config.onDidChange 'editor.showIndentGuide', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
subscriptions.add @config.onDidChange 'editor.softWrap', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
subscriptions.add @config.onDidChange 'editor.softWrapHangingIndent', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
@@ -2704,17 +2701,24 @@ class TextEditor extends Model
# * `softTabs` A {Boolean}
setSoftTabs: (@softTabs) -> @softTabs
# Returns a {Boolean} indicating whether atomic soft tabs are enabled for this editor.
hasAtomicSoftTabs: -> @atomicSoftTabs
# Enable or disable atomic soft tabs for this editor.
#
# * `atomicSoftTabs` A {Boolean}
setAtomicSoftTabs: (atomicSoftTabs) ->
return if atomicSoftTabs is @atomicSoftTabs
@atomicSoftTabs = atomicSoftTabs
@resetDisplayLayer()
# Essential: Toggle soft tabs for this editor
toggleSoftTabs: -> @setSoftTabs(not @getSoftTabs())
# Essential: Get the on-screen length of tab characters.
#
# Returns a {Number}.
getTabLength: ->
if @tabLength?
@tabLength
else
@config.get('editor.tabLength', scope: @getRootScopeDescriptor())
getTabLength: -> @tabLength
# Essential: Set the on-screen length of tab characters. Setting this to a
# {Number} This will override the `editor.tabLength` setting.
@@ -2728,19 +2732,35 @@ class TextEditor extends Model
@tokenizedBuffer.setTabLength(@tabLength)
@resetDisplayLayer()
setIgnoreInvisibles: (ignoreInvisibles) ->
return if ignoreInvisibles is @ignoreInvisibles
# Returns a {Boolean} indicating whether atomic soft tabs are enabled for this editor.
doesShowInvisibles: -> @showInvisibles
@ignoreInvisibles = ignoreInvisibles
# Enable or disable invisible character substitution for this editor.
#
# * `showInvisibles` A {Boolean}
setShowInvisibles: (showInvisibles) ->
return if showInvisibles is @showInvisibles
@showInvisibles = showInvisibles
@resetDisplayLayer()
# Returns an {Object} representing the current invisible character
# substitutions for this editor. See {::setInvisibles}.
getInvisibles: ->
scopeDescriptor = @getRootScopeDescriptor()
if @config.get('editor.showInvisibles', scope: scopeDescriptor) and not @ignoreInvisibles and @showInvisibles
@config.get('editor.invisibles', scope: scopeDescriptor)
if not @mini and @showInvisibles and @invisibles?
@invisibles
else
{}
# Set the invisible character substitutions for this editor.
#
# * `invisibles` An {Object} whose keys are names of invisible characters
# and whose values are 1-character {Strings}s to display in place of those
# invisble characters
setInvisibles: (invisibles) ->
return if invisibles is @invisibles
@invisibles = invisibles
@resetDisplayLayer()
# Extended: Determine if the buffer uses hard or soft tabs.
#
# Returns `true` if the first non-comment line with leading whitespace starts