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

Switch to new prefix declaration using which-key #2876

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions doc/DOCUMENTATION.org
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- [[#setting-configuration-layers-variables][Setting configuration layers variables]]
- [[#excluding-packages][Excluding packages]]
- [[#hooks][Hooks]]
- [[#binding-keys][Binding keys]]
- [[#custom-variables][Custom variables]]
- [[#main-principles][Main principles]]
- [[#evil][Evil]]
Expand Down Expand Up @@ -542,6 +543,58 @@ configuration at the beginning and end of =Spacemacs= loading process.
loading.
- =dotspacemacs/config= is triggered at the very end of =Spacemacs= loading.

*** Binding keys
Key sequences are bound to commands in Emacs in various keymaps. The most basic
map is the global-map. Setting a key binding the global-map uses the function
=global-set-key= as follows (to the command =forward-char= in this case).

#+begin_src emacs-lisp
(global-set-key (kbd "C-]") 'forward-char)
#+end_src

The =kbd= macro accepts a string describing a key sequence. The global-map is
often shadowed by other maps. For example, evil-mode defines keymaps that target
states (or modes in vim terminology). Here is an example that creates the same
binding as above but only in insert state (=define-key= is a built-in function.
Evil-mode has its own functions for defining keys).

#+begin_src emacs-lisp
(define-key evil-insert-state-map (kbd "C-]") 'forward-char)
#+end_src

Perhaps most importantly for spacemacs is the use of the evil-leader package,
which binds keys to the evil-leader keymap. This is where most of the spacemacs
bindings live. There are two related commands from this package which are used
as follows.

#+begin_src emacs-lisp
(evil-leader/set-key "C-]" 'forward-char)
(evil-leader/set-key-for-mode 'emacs-lisp-mode "C-]" 'forward-char)
#+end_src

These functions use a macro like =kbd= to translate the key sequences for you.
The second function, =evil-leader/set-key-for-mode=, binds the key only in the
specified mode. The second key binding would not be in effect in =org-mode= for
example.

Finally, one should be aware of prefix keys. Essentially, all keymaps can be
nested. Nested keymaps are used extensively in spacemacs, and in vanilla Emacs
for that matter. For example, ~SPC a~ points to key bindings for "applications",
like ~SPC ac~ for =calc-dispatch=. Nesting bindings is easy.

#+begin_src emacs-lisp
(spacemacs/declare-prefix "]" "bracket-prefix")
(evil-leader/set-key "]]" 'double-bracket-command)
#+end_src
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I read this I found it a bit confusing. When it says "keymaps can be nested", I thought that I can have a keymap that maps a to something and b to something, and then I can take that keymap and nest it behind SPC whatever, and the result would be bindings for SPC whatever a and SPC whatever b.

Later you use "nesting bindings". Maybe that way of putting it should be used everywhere, or maybe we can just talk about common prefixes and how command execution doesn't happen until a complete key sequence has been input.

Anyway, it was a very good read!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it's a little awkward as it is. Is this better?

Finally, one should be aware of prefix keys, which are just key bindings that point to another keymap. Essentially, all keymaps can be nested, and nested keymaps are used extensively in spacemacs (and in vanilla Emacs for that matter). For example, SPC a points to a set of key bindings for "applications", like SPC ac for =calc-dispatch=. Internally, SPC a is simply bound to another keymap, making it a prefix key. Creating prefix keys is easy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Still doesn't feel quite right. It says "Creating prefix keys is easy.". I'd say it's so easy that you're always just doing that implicitly, and never explicitly. You're not going "I'm going to create a prefix key." and write some code. You just create a key binding for more than one key. The focus is still the function the key sequence is bound to. The prefix just works automatically.

Maybe the name spacemacs/declare-prefix makes it sound a little "I first need to declare this thing, then I can use it." – like a variable in certain programming languages. But in fact declare-prefix just sets a which-key name for the prefix that helps documentation. The prefix itself works automatically, and you don't have to declare it.

"nesting keymaps" or "nesting bindings" sounds like this to me:

(keymap/prefix "]" "brackety things"
  ("]" 'double-bracket-command
   ")" 'bracket-paren-command
   "-" 'bracket-dash-command))

But that's not what it is. Or am I missing something about prefixes?

Maybe something like this (if you use it, feel free to change at will up to the point where it's unrecognizable :)):

"You can also bind commands to sequences of keys. This is used extensively in spacemacs to group related key bindings together. You can define a name for any key sequence with spacemacs/declare-prefix. This name will be used in the popup window that shows possible continuations of the current key sequence."

I actually just looked at spacemacs/declare-prefix, and I think it does a lot more than setting a name for the prefix to be used by which-key. I'm new to emacs and elisp. It calls define-prefix-command. Hmm, does a key sequence binding work only if all prefixes in the binding have been defined with define-prefix-command? Maybe the problem here is that I don't really understand the thing that is being documented :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbertheau Why don't you open up a pr for this, so we can discuss it there? I'm happy to provide more explanation in the docs about what's going on. I just wasn't sure where to draw the line so to speak.

You're right that declaring the prefix only serves to name the prefix and is unnecessary for the functionality. That's why I was saying it's easy.

spacemacs/declare-prefix was recently changed to support the new way of naming prefixes, which is entirely done through which-key, but it also supports the old way, which I hope to phase out eventually. The old way actually renamed the keymap's symbol (which is why the name had to be unique, which was causing problems, etc) and was more of a hack to be honest. The new way just tells which-key that this key binding is a prefix and declares a name for it. Again, I'm happy to provide more detail, and please feel free to rework the explanation in a new pr since this has been closed already.

Editted for typo


The first line declares ~SPC ]~ to be a prefix and the second binds the key
sequence ~SPC ]]~ to the corresponding command. The first line is actually
unnecessary to create the prefix, but it will give your new prefix a name that
key-discovery tools can use (e.g., which-key).

There is much more to say about bindings keys, but these are the basics. Keys
can be bound in your =~/.spacemacs= file or in individual layers.

*** Custom variables
Custom variables configuration from =M-x customize-group= which are
automatically saved by Emacs are stored at the end of your =~/.spacemacs= file.
Expand Down
40 changes: 27 additions & 13 deletions spacemacs/funcs.el
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,38 @@
"Declare a prefix PREFIX. PREFIX is a string describing a key
sequence. NAME is a symbol name used as the prefix command.
LONG-NAME if given is stored in `spacemacs/prefix-command-alist'."
(let ((command (intern (concat spacemacs/prefix-command-string name)))
(full-prefix-vim (listify-key-sequence (kbd (concat dotspacemacs-leader-key " " prefix))))
(full-prefix-emacs (listify-key-sequence (kbd (concat dotspacemacs-emacs-leader-key " " prefix)))))
(let* ((command (intern (concat spacemacs/prefix-command-string name)))
(full-prefix (concat dotspacemacs-leader-key " " prefix))
(full-prefix-emacs (concat dotspacemacs-emacs-leader-key " " prefix))
(full-prefix-lst (listify-key-sequence (kbd full-prefix)))
(full-prefix-emacs-lst (listify-key-sequence
(kbd full-prefix-emacs))))
;; define the prefix command only if it does not already exist
(unless long-name (setq long-name name))
(unless (lookup-key evil-leader--default-map prefix)
(define-prefix-command command)
(evil-leader/set-key prefix command)
(push (cons full-prefix-vim long-name) spacemacs/prefix-titles)
(push (cons full-prefix-emacs long-name) spacemacs/prefix-titles))))

(defun spacemacs/declare-prefix-for-mode (mode prefix name)
(if (fboundp 'which-key-declare-prefixes)
(which-key-declare-prefixes
full-prefix-emacs (cons name long-name)
full-prefix (cons name long-name))
(unless (lookup-key evil-leader--default-map prefix)
(define-prefix-command command)
(evil-leader/set-key prefix command)
(push (cons full-prefix-lst long-name) spacemacs/prefix-titles)
(push (cons full-prefix-emacs-lst long-name) spacemacs/prefix-titles)))))

(defun spacemacs/declare-prefix-for-mode (mode prefix name &optional long-name)
"Declare a prefix PREFIX. MODE is the mode in which this prefix command should
be added. PREFIX is a string describing a key sequence. NAME is a symbol name
used as the prefix command."
(let ((command (intern (concat spacemacs/prefix-command-string name))))
(define-prefix-command command)
(evil-leader/set-key-for-mode mode prefix command)))
(let ((command (intern (concat spacemacs/prefix-command-string name)))
(full-prefix (concat dotspacemacs-leader-key " " prefix))
(full-prefix-emacs (concat dotspacemacs-emacs-leader-key " " prefix)))
(unless long-name (setq long-name name))
(if (fboundp 'which-key-declare-prefixes-for-mode)
(which-key-declare-prefixes-for-mode mode
full-prefix-emacs (cons name long-name)
full-prefix (cons name long-name))
(define-prefix-command command)
(evil-leader/set-key-for-mode mode prefix command))))

(defun spacemacs/activate-major-mode-leader ()
"Bind major mode key map to `dotspacemacs-major-mode-leader-key'."
Expand Down
29 changes: 19 additions & 10 deletions spacemacs/packages.el
Original file line number Diff line number Diff line change
Expand Up @@ -3699,18 +3699,27 @@ one of `l' or `r'."
(which-key-add-key-based-replacements
(concat leader-key " m") "major mode commands"
(concat leader-key " " dotspacemacs-command-key) "M-x"))
(if (fboundp 'which-key-declare-prefixes)
(which-key-declare-prefixes
dotspacemacs-leader-key '("root" . "Spacemacs root")
dotspacemacs-emacs-leader-key '("root" . "Spacemacs root")
(concat dotspacemacs-leader-key " m")
'("major-mode-cmd" . "Major mode commands")
(concat dotspacemacs-emacs-leader-key " m")
'("major-mode-cmd" . "Major mode commands"))
;; no need to use this after everyone updates which-key
(setq which-key-prefix-title-alist
`((,(listify-key-sequence
(kbd (concat dotspacemacs-leader-key " m"))) . "Major mode commands")
(,(listify-key-sequence
(kbd (concat dotspacemacs-emacs-leader-key " m"))) . "Major mode commands")
(,(listify-key-sequence
(kbd dotspacemacs-leader-key)) . "Spacemacs root")
(,(listify-key-sequence
(kbd dotspacemacs-emacs-leader-key)) . "Spacemacs root")))
(nconc which-key-prefix-title-alist spacemacs/prefix-titles))
;; disable special key handling for spacemacs, since it can be
;; disorienting if you don't understand it
(setq which-key-prefix-title-alist
`((,(listify-key-sequence
(kbd (concat dotspacemacs-leader-key " m"))) . "Major mode commands")
(,(listify-key-sequence
(kbd (concat dotspacemacs-emacs-leader-key " m"))) . "Major mode commands")
(,(listify-key-sequence
(kbd dotspacemacs-leader-key)) . "Spacemacs root")
(,(listify-key-sequence
(kbd dotspacemacs-emacs-leader-key)) . "Spacemacs root")))
(nconc which-key-prefix-title-alist spacemacs/prefix-titles)
(setq which-key-special-keys nil
which-key-use-C-h-for-paging t
which-key-echo-keystrokes 0.02
Expand Down