I wrote a couple of functions to automatically convert sequences like "<<" in «. (Yeah, I know that input methods can do the same, but I don't want the extra bindings: I'm planning to use a lot of apostrophes, and I don't want them to be joined). So far, I have my functions:

(defun electric-left-angle ()
  "Insert a left angle bracket ('<') and, if doubles, converts it to an open guillemet ('«')."
  (if (equal (char-before (point)) #x3c);;code for <
    (delete-char -1) ;;TODO see doc
    (insert-char #x00ab 1 t)) ;;code for «
    (insert-char #x3c 1 t)))

(defun electric-right-angle ()
  "Insert a right angle bracket ('>') and, if double, converts it to a close guillemet ('»')."
  (if (equal (char-before (point)) #x3e);;code for >
    (delete-char -1) ;;TODO see doc
    (insert-char #x00bb 1 t)) ;;code for »
    (insert-char #x3e 1 t)))

which works fine.

Then I thought "What if I want to insert a literal sequence <<? So I also created these:

(defun quoted-insert-left-angle-bracket ()
  "Insert the quoted char '<'."
  (progn (insert-char #x3c 1 t)))

(defun quoted-insert-right-angle-bracket ()
  "Insert the quoted char '>'."
  (progn (insert-char #x3e 1 t)))

and these works fine too.

The problem arises when I try to bind these functions in a custom minor mode:

(define-minor-mode narrative-mode
  "A minor mode that overrides the angle brackets insertion, changing them to guillemets.

Binds '<' and '>' to specific functions, which converts \"<<\" to '«' and \">>\" to '»'. Normal angle brackets can be inserted with C-< and C->."
  :lighter " narr"
  :keymap '(((kbd "<") . electric-left-angle)
        ((kbd ">") . electric-right-angle)
        ((kbd "C-<") . quoted-insert-left-angle-bracket)
        ((kbd "C->") . quoted-insert-right-angle-bracket)))

When I activate this mode, pressing < < does indeed insert «. But pressing C + < reports C-< is undefined in the echo area. Why?

Further information

Notice that investigating with C-h k C-< gives C-< is undefined, but C-h f quoted-insert-left-angle gives

quoted-insert-left-angle-bracket is an interactive Lisp function in

It is bound to C - <.


Insert the quoted char ’<’.

(notice the unusual odd space in "C - <".

Moreover, assigning the function globally with (global-set-key (kbd "C-<") #'quoted-insert-left-angle-bracket) works fine, so I suppose it's a problem of how I bind the functions in the minor mode.

I'm using GNU Emacs 27.2 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.27, cairo version 1.17.4) of 2021-03-26.

1 Answer 1


I would actually have expected < and > not to work either. What you need to pass to :keymap is a list where each element is a cons pair of a key and a binding. A “key” is something you can pass to define-key, i.e. an event type. What you're passing as the key is (kbd "<"), which is a 2-element list whose car is kbd. This is not a listed event type, so I would have expected an error.

It just happens that what you passed as the key is a Lisp expression which, when evaluated, returns a valid event type. But that's not valid here. You need to pass the value of that expression.

A simple solution (untested) is to use a backquote and unquote the expressions you want to evaluate. And of course for something simple like (kbd "<"), you don't need kbd at all.

  :keymap `(("<" . electric-left-angle)
            (">" . electric-right-angle)
            (,(kbd "C-<") . quoted-insert-left-angle-bracket)
            (,(kbd "C->") . quoted-insert-right-angle-bracket)))
  • "What you're passing as the key is (kbd "<"), which is a 2-element list whose car is kbd. This is not a listed event type, so I would have expected an error." But the same applies to (kbd "<"), no? That too needs to be evaluated.
    – Drew
    Feb 25, 2022 at 22:18
  • @Drew Yes, I don't understand why it's working. But it's not evaluating (kbd "<") as a function call: you can pass (foo "<") and it “works” too in that it's equivalent to "<". Feb 25, 2022 at 22:32
  • Yes, I agree. Your answer is correct. But the "tolerance" (if that's what it is) of (kbd "<") is confusing, and can lead (did lead) to the confusion about (kbd "C-<"). (And I think "something you can pass to define-key" is itself misleading. This needs to be worded in some unambiguous way - in the doc, for example. It's possible to "pass" (kbd "C->") to define-key - it all depends on how one interprets "passing" something to a function.
    – Drew
    Feb 25, 2022 at 22:34
  • This worked out, thanks to both. Tried in my config, (kbd "<") evaluates to "<", while (kbd "C-<") evaluates to [67108924]. Maybe is this the cause of the difference? Single-char sequences evaluates to themselves, making them effectively suitable to be placed in the bindings list Feb 27, 2022 at 14:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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