Skip to content

Latest commit

 

History

History
289 lines (207 loc) · 12 KB

HOW-TO-LAYER.md

File metadata and controls

289 lines (207 loc) · 12 KB

Pre-warning: This readme might be outdated. To get a good grasp on how powerful layers are, check out the core layer which implements all of protons core functionality.

How to contribute a Layer

Layers are the core packages of proton. Each layer is a bundle of sub-packages, keybindings, keymaps and settings. All together that makes one layer that you put on top of your editor.

Creating a layer

Creating a layer is very simple. Here is how your base skeleton could look like (don't worry, I'll go into detail in a bit):

(ns proton.layers.{{your-layer}}.core
  (:use [proton.layers.base :only [init-layer! get-initial-config et-keybindings get-packages get-keymaps]]))

(defmethod get-initial-config :{{your-layer}}
  []
  [])

(defmethod init-layer! :{{your-layer}}
  [_ {:keys [config layers]}]
  (println "hello world"))

(defmethod get-keybindings :{{your-layer}}
  []
  {})

(defmethod get-packages :{{your-layer}}
  []
  [])

(defmethod get-keymaps :{{your-layer}}
  []
  [])

(defmethod describe-mode :{{your-layer}}
  []
  {})

namespace

Proton is written in clojurescript. Let's walk through the code here:

(ns proton.layers.{{your-layer}}.core
  (:use [proton.layers.base :only [get-keybindings get-packages get-keymaps]]))

This is the namespace declaration. In the case of the git layer, this would be proton.layers.git.core.

Inside here, we import init-layer!, get-initial-config, get-keybindings, get-packages and get-keymaps. These are multimethods that are present in every layer.

the layer lifecycle

When your layer is loaded, it will always get asked which config it wants to set (or needs) before anything else. Proton will call get-initial-config and expects you to return a vector of vectors. This allows you to

  • Create your own config keys that your layer uses later like showTabBar or shouldPrintHelloWorld
  • Overwrite atom environment configurations

Point 1 is very useful if you want to give the user the option to configure your layer. ["myLayer.printHelloWorld" true] is creating this config key inside atom.

Point 2 is very straight-forward. Just check what config key you want overwrite and specify them like this: ["editor.something" true]

Once all config has been retrieved, proton will proceed to call init-layer! with the entire layer and user configuration as second parameter. This includes:

  • Configuration the user has specified in his / her .proton file
  • Configuration fragments that a layer specified (like myLayer.printHelloWorld)

If you want to know if you should print hello world now, you could do something like this:

(defmethod init-layer! :core
  [_ {:keys [config layers]}]
  (let [config-map (into (hash-map) config)]
    (if (config-map "myLayer.printHelloWorld")
     (println "Hello World!"))))

packages

your layer can depend on any number of packages. To specify what the layer needs to operate, just return a vector with all the packages you want from get-packages. In the following example, we say that the git layer depends on the atom package git-plus:

(defmethod get-packages :git
  []
  [:git-plus])

Our method declares that it needs no arguments ([]) and returns a vector ([]).

package init hook

You can define init function associated with specific package which will be executed when package become enabled. You should use defmethod init-package dispatched by vector [:layer-name :package-name]. Let see example from :lang/javascript layer.

(defmethod init-package [:lang/javascript :linter] []
  (keymap/set-proton-keys-for-mode :javascript-major-mode
    {:L {:category "linters"
         :e {:fx (fn [] (toggle-linter :eslint)) :title "use eslint"}
         :j {:fx (fn [] (toggle-linter :jshint)) :title "use jshint"}}}))

(defmethod init-package [:lang/javascript :linter-jshint] []
  (mode/define-package-mode :linter-jshint
    {:mode-keybindings
      {:L {:f {:action "FixMyJS" :title "fix jshint errors"}}}})
  (mode/link-modes :javascript-major-mode (mode/package-mode-name :linter-jshint)))

(defmethod init-package [:lang/javascript :linter-eslint] []
  (mode/define-package-mode :linter-eslint
    {:mode-keybindings
      {:L {:f {:action "linter-eslint:fix-file" :target actions/get-active-editor :title "fix eslint errors"}}}})
  (mode/link-modes :javascript-major-mode (mode/package-mode-name :linter-eslint)))

Here we define 3 init methods for linter, linter-eslint and linter-jshint packages. When linter package enabled we define mode key binding SPC m L e and SPC m L j to switch between jshint and eslint linters.

...

(defmethod init-package [:lang/javascript :linter] []
  (keymap/set-proton-keys-for-mode :javascript-major-mode
    {:L {:category "linters"
         :e {:fx (fn [] (toggle-linter :eslint)) :title "use eslint"}
         :j {:fx (fn [] (toggle-linter :jshint)) :title "use jshint"}}}))
...

By convention we define separate package modes for linter-jshint and linter-eslint packages since we want to use specific key bindings depends on active package.

....

(mode/define-package-mode :linter-jshint
  {:mode-keybindings
    {:L {:f {:action "FixMyJS" :title "fix jshint errors"}}}})

....

(mode/define-package-mode :linter-eslint
  {:mode-keybindings
    {:L {:f {:action "linter-eslint:fix-file" :target actions/get-active-editor :title "fix eslint errors"}}}})

For linter-jshint we define package mode :linter-jshint using proton.lib.mode/define-package-mode method and appropriate mode-keybinding SPC m L f bind to action FixMyJS. Also we say that we want to use linter-jshint mode along with javascript-major-mode to use our key bindings when javascript-major-mode is activated. We used proton.lib.mode/link-modes method:

(mode/link-modes :javascript-major-mode (mode/package-mode-name :linter-jshint)))
; `proton.lib.mode/package-mode-name` used by convention since we auto generate
; mode name for packages.

Proton handles package state and execute init-package hook on package activation and also when package disalbed will take care to remove associated mode and mode keybindings.

For our example, when we press SPC m L j linter-jshint will be activated and linter-eslint will be disabled. init-package [:lang/javascript :linter-jshint] will be executed and linter-jshint mode activated. In the same time linter-eslint mode and associated key bindings will be removed.

NOTE: (defmethod init-package [:layer-name :package-name]) is recommended to define settings and key bindings associated with package inside your layer.

keybindings

for proton to "see" your keybindings, you'll have to implement the multimethod get-keybindings. In the case of the git layer, this could look like this:

(defmethod get-keybindings :git
  []
  {})

Keybindings are proton keybindings, not atom keybindings. They will only work inside a proton command chain and not affect anything else from the editor.

A keybinding tree could for example look like this:

{:g {:category "git"
       :a {:action "git-plus:add"}
       :S {:action "git-plus:status"}
       :s {:action "git-plus:stage-files"}
       :P {:action "git-plus:push"}
       :c {:action "git-plus:commit"}}}

What we did here is, we created a new category called git and mapped it under root g which is getting resolved by using <spc> g.

Inside :g we define a couple of actions. <spc> g s for example will dispatch the command git-plus:stage-files inside the atom workspace by default.

If your command needs to get executed somewhere else, you can specify a :target key:

:S {:action "git-plus:status"
    :target (fn [atom] (.-body js/document))}

:target is a function that is getting executed with the atom environment as parameter. Return a DOM node where you want the command to be executed on.

In the example above, <spc> g S would now execute git-plus:status on document.body.

You can also specify :title if you want something else than :action to show up in protons key-helper.

Alternatively if you want to execute a certain action that doesn't exist as a plugin action, proton also allows you to specify a full function that is getting executed when ever the user executes your keybinding. This would look something like this:

:p {:title "print me"
    :fx (fn [] (.log js/console "hello world!"))}

Now <spc> g p will execute (.log js/console "hello world")! Pretty nifty, huh? Since we don't have a action here, you have to specify :title, otherwise this will not work. :fx currently does not take parameter.

keymaps

keymaps and keybindings are very similar but operate differently:

  • keybindings are being used inside a proton command chain and only there
  • keymaps are atom keymaps that operate in the entire editor

Here's how you can distinguish them:

  • Do you want something to happen when the user hits <spc> x f? That's a keybinding
  • Do you want something to happen when the user hits j inside the tree-view? That's a keymap.

Keymaps are being defined as a vector of maps. Each map has :selector which limits where the keymap will be usable, and :keymap which defines the actual keybindings:

(defmethod get-keymaps :git
  []
  [{:selector ".tree-view" :keymap [["escape" "tree-view:toggle"]]}])

Here we return a keymap with :selector .tree-view, meaning that whatever comes inside :keymap will only work when the user is inside the .tree-view context.

The keymap specifies that once the user hits escape inside that .tree-view, dispatch tree-view:toggle.

Custom functions and custom targets are not supported yet.

mode keybindings

you can specify keybindings related to special file types and grammars using describe-mode function. This keybindings will be available by <spc> m orc short alias ,. For example:

(defmethod describe-mode :lang/clojure []
  {:mode-name :clojure
   :atom-grammars ["Clojure"]
   :file-extensions [#"\.proton$"]
   :mode-keybindings
   {:t {:category "toggles"
        :p {:action "parinfer:toggleMode" :title "Toggle Parinfer Mode"}}}})

What we did here is, created new mode called :clojure which will be activated when opened file grammar is "Clojure" or filename ends with .proton. Also we define keybinding within :mode-keybindings keyword to toggle parinfer mode by t p keys. Now when we open clojure file or .proton and hit <spc> m we'll see special keymaps available for our mode. Now we can toggle parinfer mode using <spc> m t p. Also all mode keybindings can be executed by short alias , instead of <spc> m, in our example , t p and <spc> m t p execute same action.

mode options

To define mode you should use (defmethod describe-mode :{{your-layer}} [] {}). For now following options are available.

  • :mode-name - keyword to define name of the mode. This option is required.
  • :atom-grammars - vector of strings or single string name of grammar to capture, e.g. "Clojure", ["GitHub Flawored Markdown"]
  • :atom-scope - vector of strings or single string name of the grammar scope to capture, e.g. "source.clojure", ["source.gfm"]
  • :file-extensions - vector of regular expressions to detect filename extension, e.g. [#".cljs$", #".clj"]
  • :mode-keybindings - map description of keybindings used for this mode. Format is the same as for get-keybindings function.
  • :init - function to initialize on mode activation.

Testing your layer

Once you are done, add your layer inside proton.core:

[proton.layers.base :as layerbase]
[proton.layers.core.core :as core-layer]
[proton.layers.git.core :as git-layer]
[proton.layers.clojure.core :as clojure-layer]

Now just include it inside your ~/.proton config file and on the next start proton should load it.