Skip to content
/ embark Public
forked from oantolin/embark

Emacs Mini-Buffer Actions Rooted in Keymaps

License

Notifications You must be signed in to change notification settings

Ypot/embark

 
 

Repository files navigation

Embark: Emacs Mini-Buffer Actions Rooted in Keymaps

GNU ELPA GNU-devel ELPA MELPA MELPA Stable

Overview

Embark makes it easy to choose a command to run based on what is near point, both during a minibuffer completion session (in a way familiar to Helm or Counsel users) and in normal buffers. Bind the command embark-act to a key and it acts like prefix-key for a keymap of actions (commands) relevant to the target around point. With point on an URL in a buffer you can open the URL in a browser or eww or download the file it points to. If while switching buffers you spot an old one, you can kill it right there and continue to select another. Embark comes preconfigured with over a hundred actions for common types of targets such as files, buffers, identifiers, s-expressions, sentences; and it is easy to add more actions and more target types. Embark can also collect all the candidates in a minibuffer to an occur-like buffer or export them to a buffer in a major-mode specific to the type of candidates, such as dired for a set of files, ibuffer for a set of buffers, or customize for a set of variables.

Acting on targets

You can think of embark-act as a keyboard-based version of a right-click contextual menu. The embark-act command (which you should bind to a convenient key), acts as a prefix for a keymap offering you relevant actions to use on a target determined by the context:

  • In the minibuffer, the target is the current top completion candidate.
  • In the *Completions* buffer the target is the completion at point.
  • In a regular buffer, the target is the region if active, or else the file, symbol, URL, s-expression or defun at point.

Multiple targets can be present at the same location and you can cycle between them by repeating the embark-act key binding. The type of actions offered depend on the type of the target. Here is a sample of a few of the actions offered in the default configuration:

  • For files you get offered actions like deleting, copying, renaming, visiting in another window, running a shell command on the file, etc.
  • For buffers the actions include switching to or killing the buffer.
  • For package names the actions include installing, removing or visiting the homepage.
  • For Emacs Lisp symbols the actions include finding the definition, looking up documentation, evaluating (which for a variable immediately shows the value, but for a function lets you pass it some arguments first). There are some actions specific to variables, such as setting the value directly or though the customize system, and some actions specific to commands, such as binding it to a key.

By default when you use embark-act if you don’t immediately select an action, after a short delay Embark will pop up a buffer showing a list of actions and their corresponding key bindings. If you are using embark-act outside the minibuffer, Embark will also highlight the current target. These behaviors are configurable via the variable embark-indicators. Instead of selecting an action via its key binding, you can select it by name with completion by typing C-h after embark-act.

Everything is easily configurable: determining the current target, classifying it, and deciding which actions are offered for each type in the classification. The above introduction just mentions part of the default configuration.

Configuring which actions are offered for a type is particularly easy and requires no programming: the variable embark-keymap-alist associates target types with variables containing keymaps, and those keymaps containing bindings for the actions. (To examine the available categories and their associated keymaps, you can use C-h v embark-keymap-alist or customize that variable.) For example, in the default configuration the type file is associated with the symbol embark-file-map. That symbol names a keymap with single-letter key bindings for common Emacs file commands, for instance c is bound to copy-file. This means that if you are in the minibuffer after running a command that prompts for a file, such as find-file or rename-file, you can copy a file by running embark-act and then pressing c.

These action keymaps are very convenient but not strictly necessary when using embark-act: you can use any command that reads from the minibuffer as an action and the target of the action will be inserted at the first minibuffer prompt. After running embark-act all of your key bindings and even execute-extended-command can be used to run a command. For example, if you want to replace all occurrences of the symbol at point, just use M-% as the action, there is no need to bind query-replace in one of Embark’s keymaps. Also, those action keymaps are normal Emacs keymaps and you should feel free to bind in them whatever commands you find useful as actions and want to be available through convenient bindings.

The actions in embark-general-map are available no matter what type of completion you are in the middle of. By default this includes bindings to save the current candidate in the kill ring and to insert the current candidate in the previously selected buffer (the buffer that was current when you executed a command that opened up the minibuffer).

Emacs’s minibuffer completion system includes metadata indicating the category of what is being completed. For example, find-file’s metadata indicates a category of file and switch-to-buffer’s metadata indicates a category of buffer. Embark has the related notion of the type of a target for actions, and by default when category metadata is present it is taken to be the type of minibuffer completion candidates when used as targets. Emacs commands often do not set useful category metadata so the Marginalia package, which supplies this missing metadata, is highly recommended for use with Embark.

Embark’s default configuration has actions for the following target types: files, buffers, symbols, packages, URLs, bookmarks, and as a somewhat special case, actions for when the region is active. You can read about the default actions and their key bindings on the GitHub project wiki.

The default action on a target

Embark has a notion of default action for a target:

  • If the target is a minibuffer completion candidate, then the default action is whatever command opened the minibuffer in the first place. For example if you run kill-buffer, then the default action will be to kill buffers.
  • If the target comes from a regular buffer (i.e., not a minibuffer), then the default action is whatever is bound to RET in the keymap of actions for that type of target. For example, in Embark’s default configuration for a URL found at point the default action is browse-url, because RET is bound to browse-url in the embark-url-map keymap.

To run the default action you can press RET after running embark-act. Note that if there are several different targets at a given location, each has its own default action, so first cycle to the target you want and then press RET to run the corresponding default action.

There is also embark-dwim which runs the default action for the first target found. It’s pretty handy in non-minibuffer buffers: with Embark’s default configuration it will:

  • Open the file at point.
  • Open the URL at point in a web browser (using the browse-url command).
  • Compose a new email to the email address at point.
  • In an Emacs Lisp buffer, if point is on an opening parenthesis or right after a closing one, it will evaluate the corresponding expression.
  • Go to the definition of an Emacs Lisp function, variable or macro at point.
  • Find the file corresponding to an Emacs Lisp library at point.

Working with sets of possible targets

Besides acting individually on targets, Embark lets you work collectively on a set of target candidates. For example, while you are in the minibuffer the candidates are simply the possible completions of your input. Embark provides three main commands to work on candidate sets:

  • The embark-act-all command runs the same action on each of the current candidates. It is just like using embark-act on each candidate in turn. (Because you can easily act on many more candidates than you meant to, by default Embark asks you to confirm uses of embark-act-all; you can turn this off by setting the user option embark-confirm-act-all to nil.)
  • The embark-collect command produces a buffer listing all the current candidates, for you to peruse and run actions on at your leisure. The candidates are displayed as a list showing additional annotations.

    The Embark Collect buffer is “dired-like”: you can mark and unmark candidates with m and u, you can unmark all marked candidates with U or toggle the marks with t. In an Embark Collect buffer embark-act-all is bound to A and will act on all currently marked candidates if there any, and will act on all candidates if none are marked.

  • The embark-export command tries to open a buffer in an appropriate major mode for the set of candidates. If the candidates are files export produces a Dired buffer; if they are buffers, you get an Ibuffer buffer; and if they are packages you get a buffer in package menu mode.

    If you use the grepping commands from the Consult package, consult-grep, consult-git-grep or consult-ripgrep, then you should install the embark-consult package, which adds support for exporting a list of grep results to an honest grep-mode buffer, on which you can even use wgrep if you wish.

When in doubt choosing between exporting and collecting, a good rule of thumb is to always prefer embark-export since when an exporter to a special major mode is available for a given type of target, it will be more featureful than an Embark collect buffer, and if no such exporter is configured the embark-export command falls back to the generic embark-collect.

These commands are always available as “actions” (although they do not act on just the current target but on all candidates) for embark-act and are bound to A, S (for “snapshot”), and E, respectively, in embark-general-map. This means that you do not have to bind your own key bindings for these (although you can, of course!), just a key binding for embark-act.

Reverting an Embark Collect or Embark Export buffer has slightly unusual behavior if the buffer was obtained by running embark-collect or embark-export from within a minibuffer completion session. In that case reverting just restarts the completion session, that is, the command that opened the minibuffer is run again and the minibuffer contents restored. You can then interact normally with the command, perhaps editing the minibuffer contents, and, if you wish, you can rerun embark-collect or embark-export to get an updated buffer.

embark-live a live-updating variant of embark-collect

Finally, there is also an embark-live variant of the embark-collect command which automatically updates the collection after each change in the source buffer. Users of a completion UI that automatically updates and displays the candidate list (such as Vertico, Icomplete, Selectrum, Fido-mode, or MCT) will probably not want to use embark-live from the minibuffer as they will then have two live updating displays of the completion candidates!

A more likely use of embark-live is to be called from a regular buffer to display a sort of live updating “table of contents” for the buffer. This depends on having appropriate candidate collectors configured in embark-candidate-collectors. There are not many in Embark’s default configuration, but you can try this experiment: open a dired buffer in a directory that has very many files, mark a few, and run embark-live. You’ll get an Embark Collect buffer containing only the marked files, which updates as you mark or unmark files in dired. To make embark-live genuinely useful other candidate collectors are required. The embark-consult package (documented near the end of this manual) contains a few: one for imenu items and one for outline headings as used by outline-minor-mode. Those collectors really do give embark-live a table-of-contents feel.

Switching to a different command without losing what you’ve typed

Embark also has the embark-become command which is useful for when you run a command, start typing at the minibuffer and realize you meant a different command. The most common case for me is that I run switch-to-buffer, start typing a buffer name and realize I haven’t opened the file I had in mind yet! I’ll use this situation as a running example to illustrate embark-become. When this happens I can, of course, press C-g and then run find-file and open the file, but this requires retyping the portion of the file name you already typed. This process can be streamlined with embark-become: while still in the switch-to-buffer you can run embark-become and effectively make the switch-to-buffer command become find-file for this run.

You can bind embark-become to a key in minibuffer-local-map, but it is also available as an action under the letter B (uppercase), so you don’t need a binding if you already have one for embark-act. So, assuming I have embark-act bound to, say, C-., once I realize I haven’t open the file I can type C-. B C-x C-f to have switch-to-buffer become find-file without losing what I have already typed in the minibuffer.

But for even more convenience, embark-become offers shorter key bindings for commands you are likely to want the current command to become. When you use embark-become it looks for the current command in all keymaps named in the list embark-become-keymaps and then activates all keymaps that contain it. For example, the default value of embark-become-keymaps contains a keymap embark-become-file+buffer-map with bindings for several commands related to files and buffers, in particular, it binds switch-to-buffer to b and find-file to f. So when I accidentally try to switch to a buffer for a file I haven’t opened yet, embark-become finds that the command I ran, switch-to-buffer, is in the keymap embark-become-file+buffer-map, so it activates that keymap (and any others that also contain a binding for switch-to-buffer). The end result is that I can type C-. B f to switch to find-file.

Quick start

The easiest way to install Embark is from GNU ELPA, just run M-x package-install RET embark RET. (It is also available on MELPA.) It is highly recommended to also install Marginalia (also available on GNU ELPA), so that Embark can offer you preconfigured actions in more contexts. For use-package users, the following is a very reasonable starting configuration:

(use-package marginalia
  :ensure t
  :config
  (marginalia-mode))

(use-package embark
  :ensure t

  :bind
  (("C-." . embark-act)         ;; pick some comfortable binding
   ("C-;" . embark-dwim)        ;; good alternative: M-.
   ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'

  :init

  ;; Optionally replace the key help with a completing-read interface
  (setq prefix-help-command #'embark-prefix-help-command)

  :config

  ;; Hide the mode line of the Embark live/completions buffers
  (add-to-list 'display-buffer-alist
               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 nil
                 (window-parameters (mode-line-format . none)))))

;; Consult users will also want the embark-consult package.
(use-package embark-consult
  :ensure t ; only need to install it, embark loads it after consult if found
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

About the suggested key bindings for embark-act and embark-dwim:

  • Those key bindings are unlikely to work in the terminal, but terminal users are probably well aware of this and will know to select different bindings.
  • The suggested C-. binding is used by default in (at least some installations of) GNOME to input emojis, and Emacs doesn’t even get a chance to respond to the binding. You can select a different key binding for embark-act or use ibus-setup to change the shortcut for emoji insertion (Emacs 29 will likely use C-x 8 e e, in case you want to set the same one system-wide).
  • The suggested alternative of M-. for embark-dwim is bound by default to xref-find-definitions. That is a very useful command but overwriting it with embark-dwim is sensible since in Embark’s default configuration, embark-dwim will also find the definition of the identifier at point. (Note that xref-find-definitions with a prefix argument prompts you for an identifier, embark-dwim does not cover this case).

Other Embark commands such as embark-act-all, embark-become, embark-collect, and embark-export can be run through embark-act as actions bound to A, B, S (for “snapshot”), and E respectively, and thus don’t really need a dedicated key binding, but feel free to bind them directly if you so wish. If you do choose to bind them directly, you’ll probably want to bind them in minibuffer-local-map, since they are most useful in the minibuffer (in fact, embark-become only works in the minibuffer).

The command embark-dwim executes the default action at point. Another good keybinding for embark-dwim is M-. since embark-dwim acts like xref-find-definitions on the symbol at point. C-. can be seen as a right-click context menu at point and M-. acts like left-click. The keybindings are mnemonic, both act at the point (.).

Embark needs to know what your minibuffer completion system considers to be the list of candidates and which one is the current candidate. Embark works out of the box if you use Emacs’s default tab completion, the built-in icomplete-mode or fido-mode, or the third-party packages Vertico, Selectrum or Ivy.

If you are a Helm or Ivy user you are unlikely to want Embark since those packages include comprehensive functionality for acting on minibuffer completion candidates. (Embark does come with Ivy integration despite this.)

Advanced configuration

Showing information about available targets and actions

By default, if you run embark-act and do not immediately select an action, after a short delay Embark will pop up a buffer called *Embark Actions* containing a list of available actions with their key bindings. You can scroll that buffer with the mouse of with the usual commands scroll-other-window and scroll-other-window-down (bound by default to C-M-v and C-M-S-v).

That functionality is provided by the embark-mixed-indicator, but Embark has other indicators that can provide information about the target and its type, what other targets you can cycle to, and which actions have key bindings in the action map for the current type of target. Any number of indicators can be active at once and the user option embark-indicators should be set to a list of the desired indicators.

Embark comes with the following indicators:

  • embark-minimal-indicator: shows a messages in the echo area or minibuffer prompt showing the current target and the types of all targets starting with the current one; this one is on by default.
  • embark-highlight-indicator: highlights the target at point; also on by default.
  • embark-verbose-indicator: displays a table of actions and their key bindings in a buffer; this is not on by default, in favor of the mixed indicator described next.
  • embark-mixed-indicator: starts out by behaving as the minimal indicator but after a short delay acts as the verbose indicator; this is on by default.
  • embark-isearch-highlight-indicator: this only does something when the current target is the symbol at point, in which case it lazily highlights all occurrences of that symbol in the current buffer, like isearch; also on by default.

Users of the popular which-key package may prefer to use the embark-which-key-indicator from the Embark wiki. Just copy its definition from the wiki into your configuration and customize the embark-indicators user option to exclude the mixed and verbose indicators and to include embark-which-key-indicator.

Selecting commands via completions instead of key bindings

As an alternative to reading the list of actions in the verbose or mixed indicators (see the previous section for a description of these), you can press the embark-help-key, which is C-h by default (but you may prefer ? to free up C-h for use as a prefix) after running embark-act. Pressing the help key will prompt you for the name of an action with completion (but feel free to enter a command that is not among the offered candidates!), and will also remind you of the key bindings. You can press embark-keymap-prompter-key, which is @ by default, at the prompt and then one of the key bindings to enter the name of the corresponding action.

You may think that with the *Embark Actions* buffer popping up to remind you of the key bindings you’d never want to use completion to select an action by name, but personally I find that typing a small portion of the action name to narrow down the list of candidates feels significantly faster than visually scanning the entire list of actions.

If you find you prefer entering actions that way, you can configure embark to always prompt you for actions by setting the variable embark-prompter to embark-completing-read-prompter.

Quitting the minibuffer after an action

By default, if you call embark-act from the minibuffer it quits the minibuffer after performing the action. You can change this by setting the user option embark-quit-after-action to nil. Having embark-act not quit the minibuffer can be useful to turn commands into little “thing managers”. For example, you can use find-file as a little file manager or describe-package as a little package manager: you can run those commands, perform a series of actions, and then quit the command.

If you want to control the quitting behavior in a fine-grained manner depending on the action, you can set embark-quit-after-action to an alist, associating commands to either t for quitting or nil for not quitting. When using an alist, you can use the special key t to specify the default behavior. For example, to specify that by default actions should not quit the minibuffer but that using kill-buffer as an action should quit, you can use the following configuration:

(setq embark-quit-after-action '((kill-buffer . t) (t . nil)))

The variable embark-quit-after-action only specifies a default, that is, it only controls whether or not embark-act quits the minibuffer when you call it without a prefix argument, and you can select the opposite behavior to what the variable says by calling embark-act with C-u. Also note that both the variable embark-quit-after-action and C-u have no effect when you call embark-act outside the minibuffer.

If you find yourself using the quitting and non-quitting variants of embark-act about equally often, independently of the action, you may prefer to simply have separate commands for them instead of a single command that you call with C-u half the time. You could, for example, keep the default exiting behavior of embark-act and define a non-quitting version as follows:

(defun embark-act-noquit ()
  "Run action but don't quit the minibuffer afterwards."
  (interactive)
  (let ((embark-quit-after-action nil))
    (embark-act)))

Running some setup after injecting the target

You can customize what happens after the target is inserted at the minibuffer prompt of an action. There are embark-target-injection-hooks, that are run by default after injecting the target into the minibuffer. The variable embark-target-injection-hooks is an alist associating commands to their setup hooks. There are two special keys: if no setup hook is specified for a given action, the hook associated to t is run; and the hook associated to :always is run regardless of the action. (This variable used to have the less explicit name of embark-setup-action-hooks, so please update your configuration.)

For example, consider using shell-command as an action during file completion. It would be useful to insert a space before the target file name and to leave the point at the beginning, so you can immediately type the shell command to run on that file. That’s why in Embark’s default configuration there is an entry in embark-target-injection-hooks associating shell-command to a hook that includes embark--shell-prep, a simple helper function that quotes all the spaces in the file name, inserts an extra space at the beginning of the line and leaves point to the left of it.

Now, the preparation that embark--shell-prep does would be useless if Embark did what it normally does after it inserts the target of the action at the minibuffer prompt, which is to “press RET” for you, accepting the target as is; if Embark did that for shell-command you wouldn’t get a chance to type in the command to execute! That is why in Embark’s default configuration the entry for shell-command in embark-target-injection-hooks also contains the function embark--allow-edit.

Embark used to have a dedicated variable embark-allow-edit-actions to which you could add commands for which Embark should forgo pressing RET for you after inserting the target. Since its effect can also be achieved via the general embark-target-injection-hooks mechanism, that variable has been removed to simply Embark. Be sure to update your configuration; if you had something like:

(add-to-list 'embark-allow-edit-actions 'my-command)

you should replace it with:

(push 'embark--allow-edit
      (alist-get 'my-command embark-target-injection-hooks))

Also note that while you could abuse embark--allow-edit so that you have to confirm “dangerous” actions such as delete-file, it is better to implement confirmation by adding the embark--confirm function to the appropriate entry of a different hook alist, namely, embark-pre-action-hooks.

Besides embark--allow-edit, Embark comes with another function that is of general utility in action setup hooks: embark--ignore-target. Use it for commands that do prompt you in the minibuffer but for which inserting the target would be inappropriate. This is not a common situation but does occasionally arise. For example it is used by default for shell-command-on-region: that command is used as an action for region targets, and it prompts you for a shell command; you typically do not want the target, that is the contents of the region, to be entered at that prompt!

Running hooks before or after an action

Embark has two variables, embark-pre-action-hooks and embark-post-action-hooks, which are alists associating commands to hooks that should run before or after the command is used as an action. As with, embark-target-injection-hooks, there are two special keys for the alists: t designates the default hook to run when no specific hook is specified for a command; and the hook associated to :always runs regardless.

The default values of those variables are fairly extensive, adding creature comforts to make running actions a smooth experience. Embark comes with several functions intended to be added to these hooks, and used in the default values of embark-pre-action-hooks and embark-post-action-hooks.

For pre-action hooks:

embark--confirm
Prompt the user for confirmation before executing the action. This is used be default for commands deemed “dangerous”, or, more accurately, hard to undo, such as delete-file and kill-buffer.
embark--mark-target
Mark the target as an active region. Most targets at point outside the minibuffer report which region of the buffer they correspond to (this is the information used by embark-highlight-indicator to know what portion of the buffer to highlight); this function marks that region. It is useful as a pre action hook for commands that expect a region to be marked, for example, it is used by default for indent-region so that it works on s-expression targets, or for fill-region so that it works on paragraph targets.
embark--unmark-target
Unmark the active region. Use this for commands you want to act on the region contents but without the region being active. The default configuration uses this function as a pre-action hook for occur and query-replace, for example, so that you can use them as actions with region targets to search the whole buffer for the text contained in the region. Without this pre-action hook using occur as an action for a region target would be pointless: it would search for the the region contents in the region, (typically, due to the details of regexps) finding only one match!
embark--beginning-of-target
Move to the beginning of the target (for targets that report bounds). This is used by default for backward motion commands such as backward-sexp, so that they don’t accidentally leave you on the current target.
embark--end-of-target
Move to the end of the target. This is used similarly to the previous function, but also for commands that act on the last s-expression like eval-last-sexp. This allow you to act on an s-expression from anywhere inside it and still use eval-last-sexp as an action.
embark--narrow-to-target
Narrow buffer to current target. Use this as a pre-action hook to localize the effect of actions that don’t already work on just the region. In the default configuration it is used for repunctuate-sentences.
embark--xref-push-markers
Push the current location on the xref marker stack. Use this for commands that take you somewhere and for which you’d like to be able to come back to where you were using xref-pop-marker-stack. This is used by default for find-library.
embark--cd
Run the action with default-directory set to the directory associated to the current target. The target should be of type file, buffer, bookmark or library, and the associated directory is what you’d expect in each case.

For post-action hooks:

embark--restart
Restart the command currently prompting in the minibuffer, so that the list of completion candidates is updated. This is useful as a post action hook for commands that delete or rename a completion candidate; for example the default value of embark-post-action-hooks uses it for delete-file, kill-buffer, rename-file, rename-buffer, etc.

Creating your own keymaps

All internal keymaps are defined with a helper macro embark-define-keymap that you can use to define your own keymaps, whether they are for new categories in embark-keymap-alist or for any other purpose! For example a simple version of the file action keymap could be defined as follows:

(embark-define-keymap embark-file-map
  "Example keymap with a few file actions"
  ("d" delete-file)
  ("r" rename-file)
  ("c" copy-file))

Remember also that these action keymaps are perfectly normal Emacs keymaps, and do not need to be created with this helper macro. You can use the built-in define-key, or your favorite external package such as bind-key or general.el to manage them.

Defining actions for new categories of targets

It is easy to configure Embark to provide actions for new types of targets, either in the minibuffer or outside it. I present below two very detailed examples of how to do this. At several points I’ll explain more than one way to proceed, typically with the easiest option first. I include the alternative options since there will be similar situations where the easiest option is not available.

New minibuffer target example - tab-bar tabs

As an example, take the new tab bars from Emacs 27. I’ll explain how to configure Embark to offer tab-specific actions when you use the tab-bar-mode commands that mention tabs by name. The configuration explained here is now built-in to Embark (and Marginalia), but it’s still a good self-contained example. In order to setup up tab actions you would need to: (1) make sure Embark knows those commands deal with tabs, (2) define a keymap for tab actions and configure Embark so it knows that’s the keymap you want.

Telling Embark about commands that prompt for tabs by name

For step (1), it would be great if the tab-bar-mode commands reported the completion category tab when asking you for a tab with completion. (All built-in Emacs commands that prompt for file names, for example, do have metadata indicating that they want a file.) They do not, unfortunately, and I will describe a couple of ways to deal with this.

Maybe the easiest thing is to configure Marginalia to enhance those commands. All of the tab-bar-*-tab-by-name commands have the words “tab by name” in the minibuffer prompt, so you can use:

(add-to-list 'marginalia-prompt-categories '("tab by name" . tab))

That’s it! But in case you are ever in a situation where you don’t already have commands that prompt for the targets you want, I’ll describe how writing your own command with appropriate category metadata looks:

(defun my-select-tab-by-name (tab)
  (interactive
   (list
    (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab)))
                                (tab-bar-tabs))
                        (user-error "No tabs found"))))
      (completing-read
       "Tabs: "
       (lambda (string predicate action)
         (if (eq action 'metadata)
             '(metadata (category . tab))
           (complete-with-action
            action tab-list string predicate)))))))
  (tab-bar-select-tab-by-name tab))

As you can see, the built-in support for setting the category meta-datum is not very easy to use or pretty to look at. To help with this I recommend the consult--read function from the excellent Consult package. With that function we can rewrite the command as follows:

(defun my-select-tab-by-name (tab)
  (interactive
   (list
    (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab)))
                                (tab-bar-tabs))
                        (user-error "No tabs found"))))
      (consult--read tab-list
                     :prompt "Tabs: "
                     :category 'tab))))
  (tab-bar-select-tab-by-name tab))

Much nicer! No matter how you define the my-select-tab-by-name command, the first approach with Marginalia and prompt detection has the following advantages: you get the tab category for all the tab-bar-*-bar-by-name commands at once, also, you enhance built-in commands, instead of defining new ones.

Defining and configuring a keymap for tab actions

Let’s say we want to offer select, rename and close actions for tabs (in addition to Embark general actions, such as saving the tab name to the kill-ring, which you get for free). Then this will do:

(embark-define-keymap embark-tab-actions
  "Keymap for actions for tab-bar tabs (when mentioned by name)."
  ("s" tab-bar-select-tab-by-name)
  ("r" tab-bar-rename-tab-by-name)
  ("k" tab-bar-close-tab-by-name))

(add-to-list 'embark-keymap-alist '(tab . embark-tab-actions))

What if after using this for a while you feel closing the tab without confirmation is dangerous? You have a couple of options:

  1. You can keep using the tab-bar-close-tab-by-name command, but have Embark ask you for confirmation:
    (push #'embark--confirm
          (alist-get 'tab-bar-close-tab-by-name
                     embark-pre-action-hooks))
        
  2. You can write your own command that prompts for confirmation and use that instead of tab-bar-close-tab-by-name in the above keymap:
    (defun my-confirm-close-tab-by-name (tab)
      (interactive "sTab to close: ")
      (when (y-or-n-p (format "Close tab '%s'? " tab))
        (tab-bar-close-tab-by-name tab)))
        

    Notice that this is a command you can also use directly from M-x independently of Embark. Using it from M-x leaves something to be desired, though, since you don’t get completion for the tab names. You can fix this if you wish as described in the previous section.

New target example in regular buffers - short Wikipedia links

Say you want to teach Embark to treat text of the form wikipedia:Garry_Kasparov in any regular buffer as a link to Wikipedia, with actions to open the Wikipedia page in eww or an external browser or to save the URL of the page in the kill-ring. We can take advantage of the actions that Embark has preconfigured for URLs, so all we need to do is teach Embark that wikipedia:Garry_Kasparov stands for the URL https://en.wikipedia.org/wiki/Garry_Kasparov.

You can be as fancy as you want with the recognized syntax. Here, to keep the example simple, I’ll assume the link matches the regexp wikipedia:[[:alnum:]_]+. We will write a function that looks for a match surrounding point, and returns an improper list of the form '(url actual-url-of-the-page beg . end) where beg and end are the buffer positions where the target starts and ends, and are used by Embark to highlight the target (if you have embark-highlight-indicator included in the list embark-indicators).

(defun my-short-wikipedia-link ()
  "Target a link at point of the form wikipedia:Page_Name."
  (save-excursion
    (let* ((beg (progn (skip-chars-backward "[:alnum:]_:") (point)))
           (end (progn (skip-chars-forward "[:alnum:]_:") (point)))
           (str (buffer-substring-no-properties beg end)))
      (save-match-data
        (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str)
          `(url 
            ,(format "https://en.wikipedia.org/wiki/%s"
                     (match-string 1 str))
            ,beg . ,end))))))

(add-to-list 'embark-target-finders 'my-short-wikipedia-link)

How does Embark call the actions?

Embark actions are normal Emacs commands, that is, functions with an interactive specification. In order to execute an action, Embark calls the command with call-interactively, so the command reads user input exactly as if run directly by the user. For example the command may open a minibuffer and read a string (read-from-minibuffer) or open a completion interface (completing-read). If this happens, Embark takes the target string and inserts it automatically into the minibuffer, simulating user input this way. After inserting the string, Embark exits the minibuffer, submitting the input. (The immediate minibuffer exit can be disabled for specific actions in order to allow editing the input; this is done by adding the embark--allow-edit function to the appropriate entry of embark-target-injection-hooks). Embark inserts the target string at the first minibuffer opened by the action command, and if the command happens to prompt the user for input more than once, the user still interacts with the second and further prompts in the normal fashion. Note that if a command does not prompt the user for input in the minibuffer, Embark still allows you to use it as an action, but of course, never inserts the target anywhere. (There are plenty of examples in the default configuration of commands that do not prompt the user bound to keys in the action maps, most of the region actions, for instance.)

This is how Embark manages to reuse normal commands as actions. The mechanism allows you to use as Embark actions commands that were not written with Embark in mind (and indeed almost all actions that are bound by default in Embark’s action keymaps are standard Emacs commands). It also allows you to write new custom actions in such a way that they are useful even without Embark.

Staring from version 28.1, Emacs has a variable y-or-n-p-use-read-key, which when set to t causes y-or-n-p to use read-key instead of read-from-minibuffer. Setting y-or-n-p-use-read-key to t is recommended for Embark users because it keeps Embark from attempting to insert the target at a y-or-n-p prompt, which would almost never be sensible. Also consider this as a warning to structure your own action commands so that if they use y-or-n-p, they do so only after the prompting for the target.

Here is a simple example illustrating the various ways of reading input from the user mentioned above. Bind the following commands to the embark-symbol-map to be used as actions, then put the point on some symbol and run them with embark-act:

(defun example-action-command1 ()
  (interactive)
  (message "The input was `%s'." (read-from-minibuffer "Input: ")))

(defun example-action-command2 (arg input1 input2)
  (interactive "P\nsInput 1: \nsInput 2: ")
  (message "The first input %swas `%s', and the second was `%s'."
           (if arg "truly " "")
           input1
           input2))

(defun example-action-command3 ()
  (interactive)
  (message "Your selection was `%s'."
           (completing-read "Select: " '("E" "M" "B" "A" "R" "K"))))

(defun example-action-command4 ()
  (interactive)
  (message "I don't prompt you for input and thus ignore the target!"))

(define-key embark-symbol-map "X1" #'example-action-command1)
(define-key embark-symbol-map "X2" #'example-action-command2)
(define-key embark-symbol-map "X3" #'example-action-command3)
(define-key embark-symbol-map "X4" #'example-action-command4)

Also note that if you are using the key bindings to call actions, you can pass prefix arguments to actions in the normal way. For example, you can use C-u X2 with the above demonstration actions to make the message printed by example-action-command2 more emphatic. This ability to pass prefix arguments to actions is useful for some actions in the default configuration, such as embark-shell-command-on-buffer.

Non-interactive functions as actions

Alternatively, Embark does support one other type of action: a non-interactive function of a single argument. The target is passed as argument to the function. For example:

(defun example-action-function (target)
  (message "The target was `%s'." target))

(define-key embark-symbol-map "X4" #'example-action-function)

Note that normally binding non-interactive functions in a keymap is useless, since when attempting to run them using the key binding you get an error message similar to “Wrong type argument: commandp, example-action-function”. In general it is more flexible to write any new Embark actions as commands, that is, as interactive functions, because that way you can also run them directly, without Embark. But there are a couple of reasons to use non-interactive functions as actions:

  1. You may already have the function lying around, and it is convenient to simply reuse it.
  2. For command actions the targets can only be simple string, with no text properties. For certain advanced uses you may want the action to receive a string with some text properties, or even a non-string target.

Embark, Marginalia and Consult

Embark cooperates well with the Marginalia and Consult packages. Neither of those packages is a dependency of Embark, but both are highly recommended companions to Embark, for opposite reasons: Marginalia greatly enhances Embark’s usefulness, while Embark can help enhance Consult.

In the remainder of this section I’ll explain what exactly Marginalia does for Embark, and what Embark can do for Consult.

Marginalia

Embark comes with actions for symbols (commands, functions, variables with actions such as finding the definition, looking up the documentation, evaluating, etc.) in the embark-symbol-map keymap, and for packages (actions like install, delete, browse url, etc.) in the embark-package-keymap.

Unfortunately Embark does not automatically offers you these keymaps when relevant, because many built-in Emacs commands don’t report accurate category metadata. For example, a command like describe-package, which reads a package name from the minibuffer, does not have metadata indicating this fact.

In an earlier Embark version, there were functions to supply this missing metadata, but they have been moved to Marginalia, which augments many Emacs command to report accurate category metadata. Simply activating marginalia-mode allows Embark to offer you the package and symbol actions when appropriate again. Candidate annotations in the Embark collect buffer are also provided by the Marginalia package:

  • If you install Marginalia and activate marginalia-mode, Embark Collect buffers will use the Marginalia annotations automatically.
  • If you don’t install Marginalia, you will see only the annotations that come with Emacs (such as key bindings in M-x, or the unicode characters in C-x 8 RET).

Consult

The excellent Consult package provides many commands that use minibuffer completion, via the completing-read function; plenty of its commands can be considered enhanced versions of built-in Emacs commands, and some are completely new functionality. One common enhancement provided in all commands for which it makes sense is preview functionality, for example consult-buffer will show you a quick preview of a buffer before you actually switch to it.

If you use both Consult and Embark you should install the embark-consult package which provides integration between the two. It provides exporters for several Consult commands and also tweaks the behavior of many Consult commands when used as actions with embark-act in subtle ways that you may not even notice, but make for a smoother experience. You need only install it to get these benefits: Embark will automatically load it after Consult if found.

The embark-consult package provides the following exporters:

  • You can use embark-export from consult-line, consult-outline, or consult-mark to obtain an occur-mode buffer. As with the built-in occur command you use that buffer to jump to a match and after that, you can then use next-error and previous-error to navigate to other matches. You can also press e to activate occur-edit-mode and edit the matches in place!
  • You can export from any of the Consult asynchronous search commands, consult-grep, consult-git-grep, or consult-ripgrep to get a grep-mode buffer. Here too you can use next-error and previous-error to navigate among matches, and, if you install the wgrep package, you can use it to edit the matches in place.

In both cases, pressing g to revert the exported buffer will rerun the Consult command you had exported from and re-enter the input you had typed. You can then proceed to re-export if that’s what you want, but you can also edit the input changing the search terms or simply cancel if you see you are done with that search.

The embark-consult also contains some candidates collectors that allow you to run embark-live to get a live-updating table of contents for your buffer:

  • embark-consult-outline-candidates produces the outline headings of the current buffer, using consult-outline.
  • embark-consult-imenu-candidates produces the imenu items of the current buffer, using consult-imenu.
  • embark-consult-imenu-or-outline-candidates is a simple combination of the two previous functions: it produces imenu items in buffers deriving from prog-mode and otherwise outline headings.

The way to configure embark-live (or embark-collect and embark-export for that matter) to use one of these function is to add it at the end of the embark-candidate-collectors list. The embark-consult package by default adds the last one, which seems to be the most sensible default.

Besides those exporters and candidate collectors, the embark-consult package provides many subtle tweaks and small integrations between Embark and Consult. For example, if you run embark-collect from any of the the consult-yank family of commands, you’ll see the Embark Collect buffers has full multi-line kill-ring entries with zebra stripes, so you can easily tell where they start and end.

Some examples of little tweaks provided by embark-consult to the behavior of Consult commands when used as Embark actions are:

  • The asynchronous search commands will start in the directory associated to the Embark target if that target is a file, buffer, bookmark or Emacs Lisp library.
    • For all other target types, a Consult search command (asynchronous or not) will search for the text of the target but leave the minibuffer open so you can interact with the Consult command.
  • consult-imenu will search for the target and take you directly to the location if it matches a unique imenu entry, otherwise it will leave the minibuffer open so you can navigate among the matches.

Resources

If you want to learn more about how others have used Embark here are some links to read:

And some videos to watch:

Contributions

Contributions to Embark are very welcome. There is a wish list for actions, target finders, candidate collectors and exporters. For other ideas you have for Embark, feel free to open an issue on the issue tracker. Any neat configuration tricks you find might be a good fit for the wiki.

Code contributions are very welcome too, but since Embark is now on GNU ELPA, copyright assignment to the FSF is required before you can contribute code.

Acknowledgments

While I, Omar Antolín Camarena, have written most of the Embark code and remain very stubborn about some of the design decisions, Embark has received substantial help from a number of other people which this document has neglected to mention for far too long. In particular, Daniel Mendler has been absolutely invaluable, implementing several important features, and providing a lot of useful advice.

Code contributions:

Advice and useful discussions:

About

Emacs Mini-Buffer Actions Rooted in Keymaps

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Emacs Lisp 100.0%