2

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 ('«')."
  (interactive)
  (if (equal (char-before (point)) #x3c);;code for <
      (progn
    (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 ('»')."
  (interactive)
  (if (equal (char-before (point)) #x3e);;code for >
      (progn
    (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 '<'."
  (interactive)
  (progn (insert-char #x3c 1 t)))

(defun quoted-insert-right-angle-bracket ()
  "Insert the quoted char '>'."
  (interactive)
  (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
‘../init.el’.

It is bound to C - <.

(quoted-insert-left-angle-bracket)

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.

Drew
  • 75,699
  • 9
  • 109
  • 225

1 Answers1

2

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 '22 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 "<". – Gilles 'SO- stop being evil' Feb 25 '22 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 '22 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 – Alessandro Bertulli Feb 27 '22 at 14:47