Files
atom/src/text-editor-registry.js
2016-07-12 14:19:35 -07:00

210 lines
6.8 KiB
JavaScript

/** @babel */
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'],
['editor.showIndentGuide', 'setShowIndentGuide'],
['editor.softWrap', 'setSoftWrapped'],
['editor.softWrapHangingIndent', 'setSoftWrapIndentLength'],
['editor.softWrapAtPreferredLineLength', 'setSoftWrapAtPreferredLineLength'],
['editor.preferredLineLength', 'setPreferredLineLength'],
['editor.backUpBeforeSaving', 'setBackUpBeforeSaving'],
['editor.autoIndent', 'setAutoIndent'],
['editor.autoIndentOnPaste', 'setAutoIndentOnPaste'],
['editor.scrollPastEnd', 'setScrollPastEnd'],
['editor.undoGroupingInterval', 'setUndoGroupingInterval'],
['editor.nonWordCharacters', 'setNonWordCharacters'],
]
// Experimental: This global registry tracks registered `TextEditors`.
//
// If you want to add functionality to a wider set of text editors than just
// those appearing within workspace panes, use `atom.textEditors.observe` to
// invoke a callback for all current and future registered text editors.
//
// If you want packages to be able to add functionality to your non-pane text
// editors (such as a search field in a custom user interface element), register
// them for observation via `atom.textEditors.add`. **Important:** When you're
// done using your editor, be sure to call `dispose` on the returned disposable
// to avoid leaking editors.
export default class TextEditorRegistry {
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()
this.scopedSettingsDelegate = new ScopedSettingsDelegate(config)
}
destroy () {
this.subscriptions.dispose()
this.editorsWithMaintainedConfig = null
}
// Register a `TextEditor`.
//
// * `editor` The editor to register.
//
// Returns a {Disposable} on which `.dispose()` can be called to remove the
// added editor. To avoid any memory leaks this should be called when the
// editor is destroyed.
add (editor) {
this.editors.add(editor)
editor.registered = true
this.emitter.emit("did-add-editor", editor)
return new Disposable(() => this.remove(editor))
}
// Remove a `TextEditor`.
//
// * `editor` The editor to remove.
//
// Returns a {Boolean} indicating whether the editor was successfully removed.
remove (editor) {
var removed = this.editors.delete(editor)
editor.registered = false
return removed
}
// Invoke the given callback with all the current and future registered
// `TextEditors`.
//
// * `callback` {Function} to be called with current and future text editors.
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
observe (callback) {
this.editors.forEach(callback)
return this.emitter.on("did-add-editor", callback)
}
maintainGrammar (editor) {
}
maintainConfig (editor) {
this.editorsWithMaintainedConfig.add(editor)
this.subscribeToSettingsForEditorScope(editor)
editor.setScopedSettingsDelegate(this.scopedSettingsDelegate)
const configOptions = {scope: editor.getRootScopeDescriptor()}
for (const [settingKey, setterName] of EDITOR_SETTER_NAMES_BY_SETTING_KEY) {
editor[setterName](this.config.get(settingKey, configOptions))
}
const updateTabTypes = () => {
editor.setSoftTabs(shouldEditorUseSoftTabs(
editor,
this.config.get('editor.tabType', configOptions),
this.config.get('editor.softTabs', configOptions)
))
}
updateTabTypes()
this.subscriptions.add(editor.onDidTokenize(updateTabTypes))
}
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().isEqual(scopeDescriptor)) {
editor[setterName](newValue)
}
})
})
)
}
const updateTabTypes = () => {
const tabType = this.config.get('editor.tabType', configOptions)
const softTabs = this.config.get('editor.softTabs', configOptions)
this.editorsWithMaintainedConfig.forEach(editor => {
if (editor.getRootScopeDescriptor().isEqual(scopeDescriptor)) {
editor.setSoftTabs(shouldEditorUseSoftTabs(editor, tabType, softTabs))
}
})
}
this.subscriptions.add(
this.config.onDidChange('editor.tabType', configOptions, updateTabTypes),
this.config.onDidChange('editor.softTabs', configOptions, updateTabTypes)
)
}
}
}
function shouldEditorUseSoftTabs (editor, tabType, softTabs) {
switch (tabType) {
case 'hard':
return false
case 'soft':
return true
case 'auto':
switch (editor.usesSoftTabs()) {
case true:
return true
case false:
return false
default:
return softTabs
}
}
}
class ScopedSettingsDelegate {
constructor (config) {
this.config = config
}
getNonWordCharacters(scope) {
return this.config.get('editor.nonWordCharacters', {scope: scope})
}
getIncreaseIndentPattern (scope) {
return this.config.get('editor.increaseIndentPattern', {scope: scope})
}
getDecreaseIndentPattern (scope) {
return this.config.get('editor.decreaseIndentPattern', {scope: scope})
}
getDecreaseNextIndentPattern (scope) {
return this.config.get('editor.decreaseNextIndentPattern', {scope: scope})
}
getFoldEndPattern (scope) {
return this.config.get('editor.foldEndPattern', {scope: scope})
}
getCommentStrings (scope) {
const commentStartEntries = this.config.getAll('editor.commentStart', {scope})
const commentEndEntries = this.config.getAll('editor.commentEnd', {scope})
const commentStartEntry = commentStartEntries[0]
const commentEndEntry = commentEndEntries.find(entry => {
return entry.scopeSelector === commentStartEntry.scopeSelector
})
return {
commentStartString: commentStartEntry && commentStartEntry.value,
commentEndString: commentEndEntry && commentEndEntry.value,
}
}
}