Key Specs

Jwno has its own mini-language to specify key sequences, called key sepcs (yeah I know, how creative). They're written as strings enclosed by quotes. The most basic key spec specifies a single key:

"Enter"

Which means press and then release the Enter key on your keyboard. From that, you can add modifier keys:

"Win + Shift + Enter"

It means hold the Win key and the Shift key, then press and release the Enter key. Ctrl and Alt modifier keys work too, and you can use - to separate key names instead of +. For example:

"Ctrl - Alt - Enter"

Similarly, this means hold Ctrl and Alt, then press and release Enter. All white-space characters are optional when specifying a single key combo, i.e. the keys you press at the same time. Take these key specs as an example:

"Win+Shift+Enter"
"Ctrl-Alt-Enter"

They are equivalent to the two key specs with white-spaces we've seen above.

Multi-Level Keymaps

One of the most powerful features from Jwno's key binding system is that, you can specify multiple key combos in a sequence, and it will automatically define multi-level keymaps for you (akin to Emacs key bindings):

"Win + Enter  A  B"

This means press and release Win + Enter first, then the A key, and finally the B key. You can use multi-level keymaps to group similar commands together, and give them the same "prefix". For example, in the example config, all the infrequently used window commands begin with Win + W.

Actually Defining a Key Binding

To associate a key spec with a command to execute, call the :define-key method from a keymap object, and you can optionally write some description:

(def keymap (:new-keymap (in jwno/context :key-manager)))  # Create a new keymap
(:define-key keymap "Win + Shift + Q" :quit
             "Tell Jwno to exit")
(:define-key keymap "Win + W  D" :describe-window
             "Show some info about the current window")

Transient Keymaps

Another most powerful thing about Jwno's key binding system is that, it has an internal stack for keymaps. Only the top keymap in the stack takes effect, and we call it the transient keymap. You can use the special commands :push-keymap and :pop-keymap to manipulate the stack. The commands are special because they operate directly in the UI thread, and you can't use the :call-command method from the command manager to call them.

For example, we can define a simple transient keymap only for moving windows around:

(def yank-mode-keymap
  (let [keymap (:new-keymap (in jwno/context :key-manager))]
    (:define-key keymap "Down"  [:move-window :down])
    (:define-key keymap "Up"    [:move-window :up])
    (:define-key keymap "Left"  [:move-window :left])
    (:define-key keymap "Right" [:move-window :right])
    (:define-key keymap "Esc"   :pop-keymap)
    keymap))

And then enable it when we press Win + K:

(:define-key root-keymap "Win + K" [:push-keymap yank-mode-keymap]
             "Yank mode")

When yank-mode-keymap is in effect, you can use the arrow keys without any modifier keys to move your windows. The keymap will remain in effect until you press the Esc key, which calls the :pop-keymap command.

Differences Between Multi-Level Keymaps and Transient Keymaps

Well, they both look like some "namespacing" mechanism, in that you can enable the next level of key bindings by pressing a certain key combo. But multi-level keymaps will always reset to the root keymap immediately after a command is triggered or an undefined key is pressed, while transient keymaps will stick around until you pop it from the stack.

Yeah, seriously, transient keymaps stick around. You should know by now how bad I am at naming things.

Setting the Root Keymap

We call the keymap where your top-level key bindings are defined the root keymap. You can invoke those key bindings directly, without any prefix. For example, to make awesome-keymap your root keymap:

(:set-keymap (in jwno/context :key-manager) awesome-keymap)

Modifications to keymaps will not take effect before the :set-keymap method is called again, e.g.:

(:set-keymap (in jwno/context :key-manager) awesome-keymap)

# We defined a new key binding after calling `:set-keymap`:
(:define-key awesome-keymap "Win + Ctrl + Shift + Alt + B" :brew-coffee)

# But it won't work until we properly set the keymap again:
(:set-keymap (in jwno/context :key-manager) awesome-keymap)

Jwno has no default key bindings, so you have to call the :set-keymap method with the keymap you defined, or it won't respond to any key events.

Next Step

See Also