Lists And Key Sequences
In Emacs, a key sequence is special key binding that uses multiple
keys in a series. For instance, to store the position of a file in a
named register, you’d type:
C-x r SPC (the space key). You can
then jump back to that stored position with:
C-x r j. Since they
require more effort to type, a sequence is chosen for functions that
interrupt the normal editing flow.
We tend to bind similar functions to sequences with a common
prefix. Along with storing and copying text to registers with the
C-x r also contains all of the rectangle-related
Playing Music Sequence
Once upon a time, I started using the EMMS interface to play some
Internet music streams, but instead of feeble attempts to remember
cryptic URLs, I wrapped it in a
(defun play-jazz () "Start up some nice Jazz" (interactive) (emms-play-streamlist "http://thejazzgroove.com/itunes.pls"))
M-x play-jazz to kick off the tunes, and
M-x emms-stop to
pause. While one can not live by jazz alone, it seemed silly to
create a bunch of named functions for URL mappings.
On my Kinesis keyboard,
F9 is prominent, easy to reach, and my
go-to key for flow interrupting requests (for instance,
magit-status). Key sequences starting with
F9 m start
music streams, with
F9 m s stopping the music.
Below is my code to iterate over a list of letter combinations and an associated URL. First, define a key map:
(define-prefix-command 'personal-music-map) (global-set-key (kbd "<f9> m") 'personal-music-map)
dolist to iterate over a list of tuples so that
car returns the key and
cdr supplies the URL:
(dolist (station '(("a" . "http://stereoscenic.com/pls/pill-hi-mp3.pls") ;; Ambient ("t" . "http://www.1.fm/tunein/trance64k.pls") ;; Trance ("j" . "http://thejazzgroove.com/itunes.pls"))) ;; Jazz (lexical-let ((keystroke (car station)) (stream (cdr station))) (define-key personal-music-map (kbd keystroke) (lambda () (interactive) (emms-play-streamlist stream)))))
For each element in my list, I add to
define-key function), and I create an
emms-play-streamlist with the URL.
However, this requires an actual closure that Emacs Lisp doesn’t
grant for free. This is why I use
lexical-let instead of just a
While just an example, if you wish, see my EMMS emacs initialiation file.
Changing Color Themes
For another example of this pattern… I switch my Emacs color theme
depending on the time of day. Since I’m using the Powerline project,
changing the color scheme requires me to call
order to get the colors to apply to the mode line.
Another problem is that color themes don’t always specify the
org-block begin and ending lines. I defined both a dark and
light scheme, and wrap that in a cozy function blanket that works
for most themes.
To change a color theme, I call this function with the theme (as a function) and the org-block style (also as a function):
(defun ha/change-theme (theme org-block-style) "Changes the color scheme and reset the mode line." (funcall theme) (powerline-reset) (funcall org-block-style))
For an example, I can call it like:
(ha/change-theme 'color-theme-sanityinc-tomorrow-night 'org-src-color-blocks-dark)
Since I have few of these themes, I have a key sequence map, to follow a similar pattern:
(define-prefix-command 'personal-theme-map) (global-set-key (kbd "<f9> d") 'personal-theme-map)
My list now needs three items, so tuples are out, and regular lists
are in (just means I have to use
(dolist (atheme '(("d" 'color-theme-sanityinc-tomorrow-day 'org-src-color-blocks-light) ("l" 'color-theme-sanityinc-tomorrow-eighties 'org-src-color-blocks-dark) ("m" 'color-theme-sanityinc-tomorrow-bright 'org-src-color-blocks-dark) ("n" 'color-theme-sanityinc-tomorrow-night 'org-src-color-blocks-dark)) (lexical-let ((keystroke (car atheme)) (the-theme (cadr atheme)) (org-block (caadr atheme)) (define-key personal-theme-map (kbd keystroke) (lambda () (interactive) (ha/change-theme the-theme org-block))))))
Helper Sequence Function
Nothing makes a programmer itchy than duplicate code. Sure, both snippets are slightly different…but only slightly.
I would like to be able to define a key sequence series with:
- a name for the key-map (below this is
- an initial key prefix, like:
- a single function to call for each entry
- a list where each entry starts with a key, and the rest of the elements are given to the function
For instance, we could define a series of key sequences that call
message function with a silly message:
(define-sequence 'silly-map "<f9> s" 'message '(("b" "%s is a very %s" "Brian" "naughty boy") ("c" "Yes, this is indeed a cheese shop.") ("p" "%s. Beautiful %s" "Norwegian Blue" "plumage")))
While I would like to define this
define-sequence as a function, like:
(defun define-sequence (map-name prefix func seqs) "A silly attempt at defining symbols and key maps in a normal function. Silly, since this doesn't work." (define-prefix-command map-name) (global-set-key (kbd prefix) map-name) (dolist (el seqs) (lexical-let ((keystroke (car el)) (the-rest (cdr el))) (define-key map-name (kbd keystroke) (lambda () (interactive) (apply func the-rest))))))
For a moment, let’s pretend that this will work. Most of the code is
lifted from the examples aboves. The only real difference the call
apply that calls a function converting a list into functional
However, I can’t manipulate the symbol table so easily. Instead, I
need to define a macro to do this sort of work. With a
this macro mimics the function quite well:
(defmacro define-sequence (map-name prefix func seqs) "Define a collection of key sequences associated with MAP-NAME and begin with PREFIX that call a function, FUNC. The SEQS is a list where each element is a list that begins with a final key binding. The rest of the list is given as parameters to the function, FUNC." `(progn (define-prefix-command ,map-name) (global-set-key (kbd ,prefix) ,map-name) (dolist (el ,seqs) (lexical-let ((keystroke (car el)) (the-rest (cdr el))) (define-key ,map-name (kbd keystroke) (lambda () (interactive) (apply ,func the-rest)))))))
The following is the redefined code for my music playlist:
(define-sequence 'personal-music-map "<f9> m" 'emms-play-streamlist '(("a" "http://stereoscenic.com/pls/pill-hi-mp3.pls") ;; Ambient ("t" "http://www.1.fm/tunein/trance64k.pls") ;; Trance ("j" "http://thejazzgroove.com/itunes.pls"))) ;; Jazz
The example key sequences described above are groovy as long as you don’t have to call them a second time. I’m not quickly cycling through my music or color schemes, so the code above is good enough.
The Hydra Project is nice for key sequences that you may need to call multiple times as you can repeatedly press the last key to call the associated function again.
(require 'hydra) (defhydra hydra-font-size (global-map "<f9>") "text-scale" ("<up>" text-scale-increase "larger") ("<down>" text-scale-decrease "smaller"))
This code snippet lets me press
F9 and then either the up or down
arrow keys to increase or shrink the font size. If you are
interested (as it is really cool package), read Oleh Krehel’s
introduction to the Hydra project.
Thanks to this suggestion by apella, let’s rework my music list with Hydra:
(defhydra hydra-music-map (global-map "<f9> m" :body-pre (interactive) color :blue) "Start playing msuic from a stream" ("a" (emms-play-streamlist "http://stereoscenic.com/pls/pill-hi-mp3.pls") "ambient") ("t" (emms-play-streamlist "http://www.1.fm/tunein/trance64k.pls") "trance") ("j" (emms-play-streamlist "http://thejazzgroove.com/itunes.pls") "jazz"))
The biggest advantage of using Hydra instead of our function above,
is that after entering the prefix, in this case
F9 m, Hydra will
prompt you with the rest of the options.
Hrm. Perhaps this essay was little more than an introduction to Hydra. ;-)