oantolin / embark

Emacs Mini-Buffer Actions Rooted in Keymaps
GNU General Public License v3.0
877 stars 54 forks source link

+TITLE: Embark: Emacs Mini-Buffer Actions Rooted in Keymaps

+OPTIONS: d:nil

+EXPORT_FILE_NAME: embark.texi

+TEXINFO_DIR_CATEGORY: Emacs misc features

+TEXINFO_DIR_TITLE: Embark: (embark).

+TEXINFO_DIR_DESC: Emacs Mini-Buffer Actions Rooted in Keymaps

+html: GNU ELPA

+html: GNU-devel ELPA

+html: MELPA

+html: MELPA Stable

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:

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:

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 [[https://github.com/minad/marginalia][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 [[https://github.com/oantolin/embark/wiki/Default-Actions][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:

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:

** 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:

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=.

In Embark Collect or Embark Export buffers that were obtained by running =embark-collect= or =embark-export= from within a minibuffer completion session, =g= is bound to a command that 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.

*** Selecting some targets to make an ad hoc candidate set

The commands for working with sets of candidates just described, namely =embark-act-all=, =embark-export= and =embark-collect= by default work with all candidates defined in the current context. For example, in the minibuffer they operate on all currently completion candidates, or in a dired buffer they work on all marked files (or all files if none are marked). Embark also has a notion of /selection/, where you can accumulate an ad hoc list of targets for these commands to work on.

The selection is controlled by using the =embark-select= action, bound to =SPC= in =embark-general-map= so that it is always available (you can also give =embark-select= a global key binding if you wish; when called directly, not as an action for =embark-act=, it will select the first target at point). Calling this action on a target toggles its membership in the current buffer's Embark selection; that is, it adds it to selection if not selected and removes it from the selection if it was selected. Whenever the selection for a buffer is non-empty, the commands =embark-act-all=, =embark-export= and =embark-collect= will act on the selection.

To deselect all selected targets, you can use the =embark-select= action through =embark-act-all=, since this will run =embark-select= on each member of the current selection. Similarly if no targets are selected and you are in a minibuffer completion session, running =embark-select= from =embark-act-all= will select all the current completion candidates.

By default, whenever some targets are selected in the current buffer, a count of selected targets appears in the mode line. This can be turned off or customized through the =embark-selection-indicator= user option.

The selection functionality is supported in every 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, 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=.

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 [[https://github.com/minad/marginalia][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:

+begin_src emacs-lisp

(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)

;; Show the Embark target at point via Eldoc. You may adjust the
;; Eldoc strategy, if you want to see the documentation from
;; multiple providers. Beware that using this can be a little
;; jarring since the message shown in the minibuffer can be more
;; than one line, causing the modeline to move up and down:

;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)

: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))

+end_src

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

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 [[https://github.com/minad/vertico][Vertico]] or [[https://github.com/abo-abo/swiper][Ivy]].

If you are a [[https://emacs-helm.github.io/helm/][Helm]] or [[https://github.com/abo-abo/swiper][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.)

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:

Users of the popular [[https://github.com/justbur/emacs-which-key][which-key]] package may prefer to use the =embark-which-key-indicator= from the [[https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt][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=.

If you use [[https://github.com/minad/vertico][Vertico]], there is an even easier way to get a =which-key=-like display that also lets you use completion to narrow down the list of alternatives, described at the end of the next section.

** 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 selecting actions that way, you can configure embark to always prompt you for actions by setting the variable =embark-prompter= to =embark-completing-read-prompter=.

On the other hand, you may wish to continue using key bindings for the actions you perform most often, and to use completion only to explore what further actions are available or when you've forgotten a key binding. In that case, you may prefer to use the minimal indicator, which does not pop-up an =Embark Actions= buffer at all, and to use the =embark-help-key= whenever you need help. This unobtrusive setup is achieved with the following configuration:

+begin_src emacs-lisp

(setq embark-indicators '(embark-minimal-indicator ; default is embark-mixed-indicator embark-highlight-indicator embark-isearch-highlight-indicator))

+end_src

[[https://github.com/minad/vertico][Vertico]] users may wish to configure a grid display for the actions and key-bindings, reminiscent of the popular package [[https://github.com/justbur/emacs-which-key][which-key]], but, of course, enhanced by the use of completion to narrow the list of commands. In order to get the grid display, put the following in your Vertico configuration:

+begin_src emacs-lisp

(add-to-list 'vertico-multiform-categories '(embark-keybinding grid)) (vertico-multiform-mode)

+end_src

This will make the available keys be shown in a compact grid like in =which-key=. The =vertico-multiform-mode= also enables keys such as =M-V=, =M-G=, =M-B=, and =M-U= for manually switching between layouts in Vertico buffers.

*** Selecting commands via completion outside of Embark

If you like this completion interface for exploring key bindings for Embark actions, you may want to use it elsewhere in Emacs. You can use Embark's completion-based command prompter to list:

To use it for key bindings under a prefix (you can use this to replace the =which-key= package, for example), use this configuration:

+begin_src emacs-lisp

(setq prefix-help-command #'embark-prefix-help-command)

+end_src

Now, when you have started on a prefix sequence such as =C-x= or =C-c=, pressing =C-h= will bring up the Embark version of the built-in =prefix-help-command=, which will list the keys under that prefix and their bindings, and lets you select the one you wanted with completion, or by key binding if you press =embark-keymap-prompter-key=.

To list local or global key bindings, use the command =embark-bindings=. You can bind that to =C-h b=, which is the default key binding for the built-in =describe-bindings= command, which this command can replace. By default, =embark-bindings= lists local key bindings, typically those bound in the major mode keymap; to get global bindings as well, call it with a =C-u= prefix argument.

** 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:

+begin_src emacs-lisp

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

+end_src

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:

+begin_src emacs-lisp

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

+end_src

** 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 simplify Embark. Be sure to update your configuration; if you had something like:

+begin_src emacs-lisp

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

+end_src

you should replace it with:

+begin_src emacs-lisp

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

+end_src

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, after or around an action

Embark has three variables, =embark-pre-action-hooks=, =embark-post-action-hooks= and =embark-around-action-hooks=, which are alists associating commands to hooks that should run before or after or as around advice for the command when 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=, =embark-post-action-hooks= and =embark-around-action-hooks=.

For pre-action hooks:

For post-action hooks:

For around-action hooks:

** Creating your own keymaps

All internal keymaps are defined with the standard helper macro =defvar-keymap=. For example a simple version of the file action keymap could be defined as follows:

+BEGIN_SRC emacs-lisp

(defvar-keymap embark-file-map :doc "Example keymap with a few file actions" :parent embark-general-map "d" #'delete-file "r" #'rename-file "c" #'copy-file)

+END_SRC

These action keymaps are perfectly normal Emacs keymaps. You may want to inherit from the =embark-general-map= if you want to access the default Embark actions. Note that =embark-collect= and =embark-export= are also made available via =embark-general-map=.

** 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 [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html][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 [[https://github.com/minad/marginalia][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:

+begin_src emacs-lisp

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

+end_src

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:

+begin_src emacs-lisp

(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))

+end_src

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 [[https://github.com/minad/consult/][Consult]] package. With that function we can rewrite the command as follows:

+begin_src emacs-lisp

(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))

+end_src

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:

+begin_src emacs-lisp

(defvar-keymap embark-tab-actions :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." :parent embark-general-map "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))

+end_src

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:

    +begin_src emacs-lisp

    (push #'embark--confirm (alist-get 'tab-bar-close-tab-by-name embark-pre-action-hooks))

    +end_src

  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:

    +begin_src emacs-lisp

    (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)))

    +end_src

    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 a dotted list of the form ='(url URL-OF-THE-PAGE START . END)= where =START= and =END= are the buffer positions bounding the target, and are used by Embark to highlight it if you have =embark-highlight-indicator= included in the list =embark-indicators=. (There are a couple of other options for the return value of a target finder: the bounding positions are optional and a single target finder is allowed to return multiple targets; see the documentation for =embark-target-finders= for details.)

+begin_src emacs-lisp

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

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

+end_src

** 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:

+begin_src emacs-lisp

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

(keymap-set embark-symbol-map "X 4" #'example-action-function)

+end_src

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 cooperates well with the [[https://github.com/minad/marginalia][Marginalia]] and [[https://github.com/minad/consult][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:

** 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:

In both cases, pressing =g= will rerun the Consult command you had exported from and re-enter the input you had typed (which is similar to reverting but a little more flexible). 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:

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. Some examples are:

There are several packages that offer functionality similar to Embark's.

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

And some videos to watch:

Contributions to Embark are very welcome. There is a [[https://github.com/oantolin/embark/issues/95][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 [[https://github.com/oantolin/embark/issues][issue tracker]]. Any neat configuration tricks you find might be a good fit for the [[https://github.com/oantolin/embark/wiki][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.

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: