Chapter 4 : Behind Atom

Less than 1 minute

STOP

This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

Behind Atom

Now that we've written a number of packages and themes, let's take minute to take a closer look at some of the ways that Atom works in greater depth. Here we'll go into more of a deep dive on individual internal APIs and systems of Atom, even looking at some Atom source to see how things are really getting done.

Configuration API

Reading Config Settings

If you are writing a package that you want to make configurable, you'll need to read config settings via the atom.config global. You can read the current value of a namespaced config key with atom.config.get:

// read a value with `config.get`
if (atom.config.get("editor.showInvisibles")) {
	this.showInvisibles();
}

Or you can subscribe via atom.config.observe to track changes from any view object.

const {View} = require('space-pen')

class MyView extends View {
  function attached() {
    this.fontSizeObserveSubscription =
      atom.config.observe('editor.fontSize', (newValue, {previous}) => {
        this.adjustFontSize(newValue)
      })
  }

  function detached() {
    this.fontSizeObserveSubscription.dispose()
  }
}

The atom.config.observe method will call the given callback immediately with the current value for the specified key path, and it will also call it in the future whenever the value of that key path changes. If you only want to invoke the callback the next time the value changes, use atom.config.onDidChange instead.

Subscription methods return Disposableopen in new window objects that can be used to unsubscribe. Note in the example above how we save the subscription to the @fontSizeObserveSubscription instance variable and dispose of it when the view is detached. To group multiple subscriptions together, you can add them all to a CompositeDisposableopen in new window that you dispose when the view is detached.

Writing Config Settings

The atom.config database is populated on startup from ~/.atom/config.cson%USERPROFILE%\.atom\config.cson, but you can programmatically write to it with atom.config.set:

// basic key update
atom.config.set("core.showInvisibles", true);

If you're exposing package configuration via specific key paths, you'll want to associate them with a schema in your package's main module. Read more about schemas in the Config API documentationopen in new window.

Keymaps In-Depth

Structure of a Keymap File

Keymap files are encoded as JSON or CSON files containing nested hashes. They work much like style sheets, but instead of applying style properties to elements matching the selector, they specify the meaning of keystrokes on elements matching the selector. Here is an example of some bindings that apply when keystrokes pass through atom-text-editor elements:

Beneath the first selector are several keybindings, mapping specific key combinations to commands. When an element with the atom-text-editor class is focused and Alt+BackspaceCtrl+Backspace is pressed, a custom DOM event called editor:delete-to-beginning-of-word is emitted on the atom-text-editor element.

The second selector group also targets editors, but only if they don't have the mini attribute. In this example, the commands for code folding don't really make sense on mini-editors, so the selector restricts them to regular editors.

Key Combinations

Key combinations express one or more keys combined with optional modifier keys. For example: ctrl-w v, or cmd-shift-up. A key combination is composed of the following symbols, separated by a -. A key sequence can be expressed as key combinations separated by spaces.

TypeExamples
Character literalsa 4 $
Modifier keyscmd ctrl alt shift
Special keysenter escape backspace delete tab home end pageup pagedown left right up down space
Commands

Commands are custom DOM events that are triggered when a key combination or sequence matches a binding. This allows user interface code to listen for named commands without specifying the specific keybinding that triggers it. For example, the following code creates a command to insert the current date in an editor:

atom.commands.add("atom-text-editor", {
	"user:insert-date": function (event) {
		const editor = this.getModel();
		return editor.insertText(new Date().toLocaleString());
	},
});

atom.commands refers to the global CommandRegistry instance where all commands are set and consequently picked up by the command palette.

When you are looking to bind new keys, it is often useful to use the Command Palette (Cmd+Shift+PCtrl+Shift+P) to discover what commands are being listened for in a given focus context. Commands are "humanized" following a simple algorithm, so a command like editor:fold-current-row would appear as "Editor: Fold Current Row".

"Composed" Commands

A common question is, "How do I make a single keybinding execute two or more commands?" There isn't any direct support for this in Atom, but it can be achieved by creating a custom command that performs the multiple actions you desire and then creating a keybinding for that command. For example, let's say I want to create a "composed" command that performs a Select Line followed by Cut. You could add the following to your init.coffee:

atom.commands.add("atom-text-editor", "custom:cut-line", function () {
	const editor = this.getModel();
	editor.selectLinesContainingCursors();
	editor.cutSelectedText();
});

Then let's say we want to map this custom command to alt-ctrl-z, you could add the following to your keymap:

'atom-text-editor':
  'alt-ctrl-z': 'custom:cut-line'
Specificity and Cascade Order

As is the case with CSS applying styles, when multiple bindings match for a single element, the conflict is resolved by choosing the most specific selector. If two matching selectors have the same specificity, the binding for the selector appearing later in the cascade takes precedence.

Currently, there's no way to specify selector ordering within a single keymap, because JSON objects do not preserve order. We handle cases where selector ordering is critical by breaking the keymap into separate files, such as snippets-1.cson and snippets-2.cson.

Selectors and Custom Packages

If a keybinding should only apply to a specific grammar, you can limit bindings to that grammar using the data-grammar attribute on the atom-text-editor element:

"atom-text-editor[data-grammar='source example']":
  'ctrl-.': 'custom:custom-command'

While selectors can be applied to the entire editor by what grammar is associated with it, they cannot be applied to scopes defined within the grammar or to sub-elements of atom-text-editor.

Removing Bindings

When the keymap system encounters a binding with the unset! directive as its command, it will treat the current element as if it had no key bindings matching the current keystroke sequence and continue searching from its parent. For example, the following code removes the keybinding for a in the Tree View, which is normally used to trigger the tree-view:add-file command:

'.tree-view':
  'a': 'unset!'

Keybinding Resolver

But if some element above the Tree View had a keybinding for a, that keybinding would still execute even when the focus is inside the Tree View.

When the keymap system encounters a binding with the abort! directive as its command, it will stop searching for a keybinding. For example, the following code removes the keybinding for Cmd+OCtrl+O when the selection is inside an editor pane:

::: codetabs#keymaps-in-depth