Skip to content

New minibuffer target example - tab-bar tabs

As an example let us take a look at the tab bars. 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.

  1. 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:

    emacs-lisp
    (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:

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

    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:

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

    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.

  2. 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:

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

    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:

      emacs-lisp
      (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:

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

      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.