Skip to content

Additional Configuration

Omar Antolín Camarena edited this page May 20, 2023 · 80 revisions

This page lists example of additional configuration that might be useful to use with embark.

Make h display help for the thing at point in more modes

(Or more generally: remap commands to make the same action bindings useful in more contexts.)

Embark has h bound to display-local-help in the action map for identifiers. So you can remap display-local-help to a command that's appropriate to each major mode. The command you remap to should be of one of two types: it can provide help for the identifier at point, or it can prompt for an identifier and show help for it.

For example in shell-related modes (either for editing scripts or for running a shell), you can remap display-local-help to man:

(with-eval-after-load 'shell
  (define-key shell-mode-map [remap display-local-help] #'man))
(with-eval-after-load 'sh-script
  (define-key sh-mode-map [remap display-local-help] #'man))
(with-eval-after-load 'esh-mode
  (define-key eshell-mode-map [remap display-local-help] #'man))

For eshell, the h action will still use describe-symbol on Emacs Lisp symbols.

Other examples:

  • In cperl-mode remapping to cperl-perldoc.
  • In Slime or SLY remapping to slime-describe-symbol or sly-describe-symbol. In these modes you probably also will want further remappings:
(with-eval-after-load 'sly
  (let ((map sly-editing-mode-map))
    (define-key map [remap display-local-help] #'sly-describe-symbol)
    (define-key map [remap embark-pp-eval-defun] #'sly-compile-defun)
    (define-key map [remap pp-macroexpand-expression] #'sly-expand-1)
    (define-key map [remap pp-eval-expression] #'sly-interactive-eval)
    (define-key map [remap xref-find-definitions] #'sly-edit-definition))

This makes those Embark Emacs Lisp bindings work on Common Lisp code too.

(The slime version should be totally analogous, but I haven't tested it.)

Use which-key like a key menu prompt

The built-in embark-verbose-indicator displays actions in a buffer along with their keybindings and the first line of their docstrings. Users desiring a more compact display can use which-key instead with the following configuration:

(defun embark-which-key-indicator ()
  "An embark indicator that displays keymaps using which-key.
The which-key help message will show the type and value of the
current target followed by an ellipsis if there are further
targets."
  (lambda (&optional keymap targets prefix)
    (if (null keymap)
        (which-key--hide-popup-ignore-command)
      (which-key--show-keymap
       (if (eq (plist-get (car targets) :type) 'embark-become)
           "Become"
         (format "Act on %s '%s'%s"
                 (plist-get (car targets) :type)
                 (embark--truncate-target (plist-get (car targets) :target))
                 (if (cdr targets) "" "")))
       (if prefix
           (pcase (lookup-key keymap prefix 'accept-default)
             ((and (pred keymapp) km) km)
             (_ (key-binding prefix 'accept-default)))
         keymap)
       nil nil t (lambda (binding)
                   (not (string-suffix-p "-argument" (cdr binding))))))))

(setq embark-indicators
  '(embark-which-key-indicator
    embark-highlight-indicator
    embark-isearch-highlight-indicator))

(defun embark-hide-which-key-indicator (fn &rest args)
  "Hide the which-key indicator immediately when using the completing-read prompter."
  (which-key--hide-popup-ignore-command)
  (let ((embark-indicators
         (remq #'embark-which-key-indicator embark-indicators)))
      (apply fn args)))

(advice-add #'embark-completing-read-prompter
            :around #'embark-hide-which-key-indicator)

Automatically resizing auto-updating Embark Collect buffers to fit their contents

This produces an effect similar to (setq resize-mini-windows t) for the minibuffer.

(add-hook 'embark-collect-post-revert-hook
          (defun resize-embark-collect-window (&rest _)
            (when (memq embark-collect--kind '(:live :completions))
              (fit-window-to-buffer (get-buffer-window)
                                    (floor (frame-height) 2) 1))))

Showing Embark actions keys in keycast-mode

Because of the way embark-act works, @tarsius's excellent keycast package never finds out that Embark actions are commands nor what keys you pressed to run them. The following configuration fixes that:

(defun store-action-key+cmd (cmd)
  (force-mode-line-update t)
  (setq this-command cmd
        keycast--this-command-keys (this-single-command-keys)
        keycast--this-command-desc cmd))

(advice-add 'embark-keymap-prompter :filter-return #'store-action-key+cmd)

;; version of keycast--update that accepts (and ignores) parameters
(defun force-keycast-update (&rest _) (keycast--update))

(advice-add 'embark-act :before #'force-keycast-update)

Associate commands with actions

Ivy allows to associate commands with specific actions. The classification hooks embark provides allow you to easily define actions which are useful for a wider range of contexts. But you can also define ad hoc actions and actions which are specific to their command by temporarily setting embark-keymap-alist. You can also use this variable to setup your own mappings for commands with custom actions by using something like this:

(defvar my-action-map-alist nil)

(add-hook 'minibuffer-setup-hook
          (defun my-setup-actions ()
            (when-let ((map (cdr (assoc this-command my-action-map-alist))))
              (setq-local embark-keymap-alist `((t . ,map))))))

Now you can associate specific commands to specific keymaps in my-action-map-alist. For example:

(defvar my-actions
  (let ((map (make-sparse-keymap)))
    (define-key map "d"
      (defun example-action (target)
        (interactive "sTarget: ")
        (message "Do something with %s" target)))
    map))

(defun example-command ()
  (interactive)
  (completing-read "Check: " '("this" "out")))

(add-to-list 'my-action-map-alist '(example-command . my-actions))

Switch between candidates and actions like in Helm

Use the tab key (change according to preference) to switch back and forth between the list of minibuffer candidates and the list of embark actions on the selected candidate, like in Helm. Works best with a completion system that shows candidates in a vertical list, like vertico, selectrum, icomplete-vertical etc.

(defun with-minibuffer-keymap (keymap)
  (lambda (fn &rest args)
    (minibuffer-with-setup-hook
        (lambda ()
          (use-local-map
           (make-composed-keymap keymap (current-local-map))))
      (apply fn args))))

(defvar embark-completing-read-prompter-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "<tab>") 'abort-recursive-edit)
    map))

(advice-add 'embark-completing-read-prompter :around
            (with-minibuffer-keymap embark-completing-read-prompter-map))
(define-key vertico-map (kbd "<tab>") 'embark-act-with-completing-read)

  (defun embark-act-with-completing-read (&optional arg)
    (interactive "P")
    (let* ((embark-prompter 'embark-completing-read-prompter)
           (act (propertize "Act" 'face 'highlight))
           (embark-indicator (lambda (_keymap targets) nil)))
      (embark-act arg)))

Replace vertico-map above with your completion system of choice's active minibuffer keymap. The default is minibuffer-local-completion-map.

Show the current Embark target types in the modeline

Similar to which-function-mode.

(defvar embark--target-mode-timer nil)
(defvar embark--target-mode-string "")

(defun embark--target-mode-update ()
  (setq embark--target-mode-string
        (if-let (targets (embark--targets))
            (format "[%s%s] "
                    (propertize (symbol-name (plist-get (car targets) :type)) 'face 'bold)
                    (mapconcat (lambda (x) (format ", %s" (plist-get x :type)))
                               (cdr targets)
                               ""))
          "")))

(define-minor-mode embark-target-mode
  "Shows the current targets in the modeline."
  :global t
  (setq mode-line-misc-info (assq-delete-all 'embark-target-mode mode-line-misc-info))
  (when embark--target-mode-timer
    (cancel-timer embark--target-mode-timer)
    (setq embark--target-mode-timer nil))
  (when embark-target-mode
    (push '(embark-target-mode (:eval embark--target-mode-string)) mode-line-misc-info)
    (setq embark--target-mode-timer
          (run-with-idle-timer 0.1 t #'embark--target-mode-update))))