www.howardism.org
Babblings of an aging geek in love with the Absurd, his family, and his own hubris.... oh, and Lisp.

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 prefix, C-x r also contains all of the rectangle-related sequences.

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 play-jazz function:

(defun play-jazz ()
  "Start up some nice Jazz"
  (interactive)
  (emms-play-streamlist "http://thejazzgroove.com/itunes.pls"))

Type 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, F9 g issues a 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)

Second, use 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 personal-music-map (using the define-key function), and I create an interactive function that calls 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 regular let. 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 powerline-reset in 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 car, cadr, and caadr):

(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 silly-map
  • an initial key prefix, like: F9 s
  • 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 the 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 to apply that calls a function converting a list into functional arguments.

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 backquote, 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 Hydra

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.

For instance:

(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. ;-)