Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom string replacement: ambiguous example in README #338

Open
marczz opened this issue Apr 14, 2022 · 6 comments
Open

custom string replacement: ambiguous example in README #338

marczz opened this issue Apr 14, 2022 · 6 comments

Comments

@marczz
Copy link

marczz commented Apr 14, 2022

I could not achieve custom string replacement as shown in the custom string replacement section

Consider the following keymap:

(defvar testmap
 (make-sparse-keymap))
(define-key  testmap [?a] '(lambda () (interactive) (prin1 "key a in testmap")))
;;(define-key global-map [?:] 'self-insert-command)
(define-key global-map [?\C-:] testmap)

With this test which key gives for the top level "C-:" : "C-: -> +prefix", and for "C-:" itself
"a -> lambda".

Lets try to improve that:

(define-key  testmap [?a] '("a key" . (lambda () (interactive) (prin1 "key a in testmap"))))

Now on "C-:" which-key display "a -> a key".

Now for the keymap, if I follow the custom string replacement section I try:

(define-key global-map [?\C-:] '("test map" . (testmap)))

I get when entering "C-:" (wrong-type-argument commandp (testmap))
of course if I replace the cdr by testmap it is also wrong because testmap is a variable
whose value is not a command, but a keymap.

I have few ways of solving this either one of the following works as expected:

(define-key global-map [?\C-:] (cons "test map"  testmap))
(fset 'testmap testmap)
(define-key global-map [?\C-:] '("test map" . testmap))

But the documentation seems as least ambiguous on what is meant by (keymap)

@dankessler
Copy link

I got stuck in a very similar place as you, and ultimately found the explanation here. I too wonder if the documentation could be amended to show an example where a description is provided and a key is bound to a non-trivial keymap.

Suppose for example I have already constructed a keymap, and it is referred to by the symbol some-prefix-map. Then, if I understand correctly, the examples could read

(define-key some-map "f" '("foo" . command-foo))
(define-key some-map "b" (cons "bar-prefix" some-prefix-map))

and

(define-key some-map "f" 'long-command-name-foo)
(define-key some-map "b" some-prefix-map)
(which-key-add-keymap-based-replacements some-map
  "f" '("foo" . long-command-name-foo)
  "b" (cons "bar-prefix" some-prefix-map))

@dankessler
Copy link

dankessler commented Apr 14, 2022

At the same time, I still can't quite wrap my head around how this is working on a deep level. My understanding is that the correct usage is to pass a BINDING that is a cons whose car is a descriptive string and whose cdr is a keymap (literally a keymap, not a symbol referring to a keymap). My understanding is that BINDING should get processed according to this logic, and to my naive eyes it looks the cons cell as passed would fall under the "anything else" category (the cons is a list, but its car is neither the symbol keymap nor lambda).

@marczz
Copy link
Author

marczz commented Apr 15, 2022

The elisp manual is not so clear about define-key, it only says

The argument BINDING can be any Lisp object, but only certain types are meaningful. (For a list of meaningful types, see *note Key Lookup::.)

If it was so, as you say, we should find the cons under the "anything else" category, and it does not fit there.

But if you look at the docstring of define-key, in the description of (define-key KEYMAP KEY DEF) a list of possible values for DEF is given among which you find:

a cons (STRING . DEFN), meaning that DEFN is the definition (DEFN should be a valid definition in its own right),

We just use that and so for DEFN we have the choice between a command, a string treated as a keyboard macro, a keymap, a symbol which stands for its function definition.
In my first example and in your examples the CDR of the cons is a keymap.
In my second example I use a symbol, but the value slot cannot be used directly, because the symbol stands for its function definition.
So I fill also the function slot with (fset 'testmap testmap).

@justbur
Copy link
Owner

justbur commented Apr 19, 2022

A keymap is a list with the symbol keymap as its CAR (doc). Therefore, (testmap) is not a keymap. That's one issue. By the way you can use make-sparse-keymap if you like (doc).

You also have to pay attention to what the DEF is evaluating to. In the case of commands, you want the DEF to evaluate to a symbol pointing to a command. In the case of keymaps, you want the DEF to evaluate to the underlying keymap list/object, not to a symbol pointing to the keymap (doc).

Some examples might help

This works, because the DEF evaluates to a keymap object.

(define-key parent-map "k" '(keymap))

This doesn't work, because the DEF evaluates to a symbol pointing to a keymap object.

(setq testmap '(keymap))
(define-key parent-map "k" 'testmap)

This works again. Note the quotation.

(setq testmap '(keymap))
(define-key parent-map "k" testmap)

This doesn't work, because (testmap) is not a keymap object (it is not a list beginning with the keymap symbol).

(define-key parent-map "k" '(testmap))

Now, adding the CONS form to name the definition follows the same rules. You just have to make sure that the CDR evaluates to a keymap as before. This will work

(define-key parent-map "k" '("name" . (keymap)))

This will not

(define-key parent-map "k" '("name" . '(keymap)))

This will not work, because DEF evaluates to ("name" . testmap) and testmap is not a keymap object (it is a symbol pointing to one).

(setq testmap '(keymap))
(define-key parent-map "k" '("name" . testmap))

This will work again, because cons evaluates both of its arguments before constructing the new CONS form.

(setq testmap '(keymap))
(define-key parent-map "k" (cons "name" testmap))

justbur added a commit that referenced this issue Apr 19, 2022
@marczz
Copy link
Author

marczz commented Apr 19, 2022

Thank you Justin, the added example in 1692a1e will help to understand the syntax.
The way of understanding examples is subjective, and your mileage may vary, but I still find confusing the wording of

(define-key some-map "b" '("bar-prefix" . (keymap)))

What is the meaning of keymap here? If it is the literal symbol keymap that constitute the car of any keymap we should rather write:

 (define-key some-map "b" '("bar-prefix" . (keymap ...)))

If it designates any keymap the parentheses are not relevant because the keymap is yet a cons, so the parentheses are included in the keymap.
In this case i would rather write it:

   (define-key some-map "b" '("bar-prefix" . <some keymap>))

The use of angle brackets to designate an arbitrary parameter is a posix convention, with an alternative of words separated by underlines, usually in italic.

But the main thing is by writing <some keymap> or some_keymap or an-other-map like you have used some-map the user understand that it is a generic slot to be filled with the corresponding object.

This may be quite pernickety, and the added example should dispel any confusion

@marczz
Copy link
Author

marczz commented Apr 19, 2022

In the examples that I gave in my initial post, I omitted define-prefix-command which is equivalent to the use of fset but more elegant, with it you would write:

 (define-prefix-command 'testmap)
 (define-key global-map [?\C-:] '("test map" . testmap))

Such an example might be added in the README.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants