Elisp newbie-style
11 Feb 2015I've been cleaning up my .emacs lately, with the intention to put my whole config on Github. Today, I'll show you a useful function for describing the current buffer's key bindings. The function will generate something like this (with all letters, of course):
;; (global-set-key (kbd "a") 'self-insert-command)
;; ...
;; (global-set-key (kbd "C-b") 'backward-char)
;; ...
;; (global-set-key (kbd "M-c") 'subword-capitalize)
;; ...
;; (global-set-key (kbd "C-M-d") 'down-list)
;; ...
;; (global-set-key (kbd "η") 'save-and-switch-buffer)
;; ...
;; (global-set-key (kbd "C-θ") 'ins-single-quotes)
;; ...
;; (global-set-key (kbd "M-ι") 'nil)
;; ...
;; (global-set-key (kbd "C-M-κ") 'nil)
First, I'll show you how I wrote it down as a newbie, and then today's corrections with some remarks.
Old style
Please don't try this at home:
;;;###autoload
(defun keys-latin ()
(loop for c from ?a to ?z
collect (string c)))
;;;###autoload
(defun keys-greek ()
(loop for c from ?α to ?ω
collect (string c)))
(require 'dash)
;;;###autoload
(defun keys-describe-prefix (letters prefix)
(->> letters
(mapcar (lambda (letter) (concat prefix letter)))
(mapcar (lambda (key)
(cons key
(prin1-to-string (key-binding (kbd key))))))
(mapcar (lambda (binding)
(concat
";; (global-set-key (kbd \""
(car binding)
"\") '"
(cdr binding)
")\n")))
(apply #'concat)))
;;;###autoload
(defun keys-describe-prefixes ()
(interactive)
(with-output-to-temp-buffer "*Bindings*"
(mapcar
(lambda (f-letters)
(mapcar (lambda (prefix)
(princ (keys-describe-prefix f-letters prefix))
(princ "\n\n"))
'("" "C-" "M-" "C-M-")))
(list (keys-latin) (keys-greek)))))
Corrections
Redundant autoloads
Since the entry point of the whole thing is keys-describe-prefixes
, only it needs to be autoloaded.
Once an autoloaded function is called, it will load the whole buffer. So if e.g. keys-latin
is
not being used anywhere else outside this file, it doesn't need an autoload.
Redundant functions
keys-latin
and keys-greek
are actually very small and not used anywhere else. It might be better
to just inline them into keys-describe-prefixes
.
Redundant libraries
Here, dash
is required just for the ->>
macro, which can actually
be obtained from a core library subr-x
as thread-last
.
But even then, it's just better to unwind the whole thing. After that, it becomes clear that
the three consecutive mapcar
s could be folded into a single mapcar
with the help of a let
binding.
After the fold, it starts to look silly, since I'm cons
ing just to
take a car
and cdr
later:
(defun keys-describe-prefix (letters prefix)
(apply #'concat
(mapcar
(lambda (letter)
(let* ((key (concat prefix letter))
(binding
(cons key
(prin1-to-string
(key-binding (kbd key))))))
(concat
";; (global-set-key (kbd \""
(car binding)
"\") '"
(cdr binding)
")\n")))
letters)))
Here's a simplification, removing binding
:
(defun keys-describe-prefix (letters prefix)
(apply #'concat
(mapcar
(lambda (letter)
(let ((key (concat prefix letter)))
(concat
";; (global-set-key (kbd \""
key
"\") '"
(prin1-to-string
(key-binding (kbd key)))
")\n")))
letters)))
I guess that I didn't know about format
function back then, and the fact that "%S"
key is equivalent
to prin1-to-string
:
(defun keys-describe-prefix (letters prefix)
(apply #'concat
(mapcar
(lambda (letter)
(let ((key (concat prefix letter)))
(format ";; (global-set-key (kbd \"%s\") '%S)\n"
key
(key-binding (kbd key)))))
letters)))
Next, the combination (apply #'concat (mapcar ...))
is already implemented in C as mapconcat
:
(defun keys-describe-prefix (letters prefix)
(mapconcat
(lambda (letter)
(let ((key (concat prefix letter)))
(format ";; (global-set-key (kbd \"%s\") '%S)"
key
(key-binding (kbd key)))))
letters
"\n"))
In the end, keys-describe-prefix
turned out to be so small that I could just inline it into
keys-describe-prefixes
.
Final version
Note that here I also replaced mapcar
with dolist
:
;;;###autoload
(defun keys-describe-prefixes ()
(interactive)
(with-output-to-temp-buffer "*Bindings*"
(dolist (letter-group (list
(cl-loop for c from ?a to ?z
collect (string c))
(cl-loop for c from ?α to ?ω
collect (string c))))
(dolist (prefix '("" "C-" "M-" "C-M-"))
(princ (mapconcat
(lambda (letter)
(let ((key (concat prefix letter)))
(format ";; (global-set-key (kbd \"%s\") '%S)"
key
(key-binding (kbd key)))))
letter-group
"\n"))
(princ "\n\n")))))
I hope that this sort of analysis can be useful for people starting to learn Elisp. And if you have corrections for the final version, do let me know, I don't mind being schooled, as long as I get better in the end.
The output of keys-describe-prefixes
can be used to learn some
bindings that you didn't know about, and also as a template to
redefine some bindings that you don't need.