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.