I noticed a pattern in some elisp modes I was putting together:

(let ((map (make-sparse-keymap)))
  (define-key map KEY 'FN)
  (setq FOO map))

so I wrote up the following macro

(defmacro def-sparse-map (name &rest key/fn-list)
  `(let ((map (make-sparse-keymap)))
     ,@(loop for (key fn) on key/fn-list by #'cddr
         collecting `(define-key map ,key ',fn))
     (setq ,name map)))

which lets me write

(def-sparse-map FOO

instead. All comments welcome, but some specific questions are

  • Can this be done more cleanly (and more generally, is it acceptable practice to use the ported CL functions when defining Elisp modes)?
  • Are there some issues I'm not seeing with that use of let/setq?
  • Is it worth it writing up an elisp with-gensyms to keep map from being bound externally?

and most importantly

  • Is there an Elisp primitive that does the same thing, or close to it?

General notes

  • Standard modes are not supposed to use cl. This, in practice, leads to more code duplication than it saves memory (a lot of third-party modes or user init files use cl anyway). So don't worry about using it unless you're really intent on having your package integrated into GNU Emacs.
  • Yes, using the map symbol in this way will interfere a use of map outside your macro. That's what gensym is for.

My approach

You don't need a complex macro here.

(defun inaimathi-make-keymap (&rest bindings)
  "Make a sparse keymap containing the specified bindings"
  (let ((map (make-sparse-keymap)))
    (while (consp bindings)
      (define-key map (car bindings) (car (cdr bindings)))
      (setq bindings (cdr (cdr bindings))))
(defmacro inaimathi-defmap (symbol docstring &rest bindings)
  "Define a keymap called SYMBOL, with a DOCSTRING.
Populate the keymap with BINDINGS by building it with `inaimathi-make-keymap'"
     (defvar ,symbol nil ,docstring)
     (setq map (inaimathi-make-keymap . ,bindings))))
(inaimathi-defmap some-map "Keymap for some mode."
  "\C-c\C-a" 'do-something
  "\C-c\C-z" 'do-something-else)

Let the macro define the keymap, whether or not you provide it the bindings at the time of creation.

(defmacro new-map (mapname &optional doc-string bindings)
  "Define keymap MAPNAME, with optional DOC-STRING and BINDINGS."
  (let ((map  (make-sparse-keymap)))
    (dolist (key.cmd  bindings)
      (define-key map (car key.cmd) (cdr key.cmd)))
    `(defvar ,mapname ',map ',doc-string)))

Use example:

(new-map my-map "My map." (("\C-cf" . forward-char)
                           ("\C-cb" . backward-char)))

Better: make adding the bindings to a map a separate function, add-bindings:

(defun add-bindings (keymap bindings)
  (dolist (key.cmd  bindings)
    (define-key keymap (car key.cmd) (cdr key.cmd))))

(defmacro new-map (mapname &optional doc-string bindings)
  "Define keymap MAPNAME, with optional DOC-STRING and BINDINGS."
  (let ((map  (make-sparse-keymap)))
    (add-bindings keymap bindings)
    `(defvar ,mapname ',map ',doc-string)))

That way a program can separate creating the map from adding bindings to it (e.g., any number of times).

Use example:

(new-map my-map "My map.")                               ; Create the map
(add-bindings my-map '(("\C-cf" . forward-char)          ; Add some bindings
                       ("\C-cb" . backward-char)))
(add-bindings my-map '(("\C-ca" . backwardward-sentence) ; Add more bindings
                       ("\C-ce" . forward-sentence)

Define removing some bindings the same way.

(defun remove-bindings (keymap keys)
  "Remove bindings for KEYS from KEYMAP."
  (dolist (key  keys) (define-key keymap key nil)))

Use example:

(remove-bindings my-map '("\C-cb"))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.