Skip to content
Omar Antolín Camarena edited this page Apr 30, 2023 · 47 revisions

This page lists examples of additional actions that might be useful. Bind actions in the maps of embark-keymap-alist to make use of them within embark.

General actions

Search Google

The embark-general-map is generally available for most target types. The following action allows you to search for things on google, using your configured browser:

(defun my/embark-google-search (term)
    (interactive "sSearch Term: ")
    (browse-url
     (format "http://google.com/search?q=%s" term)))

;; inside use-package stanza, bind to general-map
:bind
    ...
    :map embark-general-map
    ("G" . my/embark-google-search)
    ...

Run the default action in another window

This one is slightly tricky to implement: there is an internal embark function called embark-default--action which computes the default, but it takes as argument the target type, which actions don't have access to. What we can do is have a dummy action that does nothing, with an around action hook that does all the work, since hooks do have access to the target type. The hook just needs to split the window, look up the default action and replace the dummy action with it.

(defun embark-default-action-in-other-window ()
  "Run the default embark action in another window."
  (interactive))

(cl-defun run-default-action-in-other-window
    (&rest rest &key run type &allow-other-keys)
  (let ((default-action (embark--default-action type)))
    (split-window-below) ; or your preferred way to split
    (funcall run :action default-action :type type rest)))

(setf (alist-get 'embark-default-action-in-other-window
                 embark-around-action-hooks)
      '(run-default-action-in-other-window))

(define-key embark-general-map "O" #'embark-default-action-in-other-window) ; or whatever key you prefer

Identifier/symbol actions

The symbol-overlay package is useful to create persistent symbol/identifier overlay highlights. Embark already comes with embark-toggle-highlight for this purpose, but symbol-overlay has a few more capabilities, like renaming the marked identifier and jumping between them. symbol-overlay realizes this feature with a local keymap bound to the overlays.

(define-key embark-identifier-map "y" #'symbol-overlay-put)

In some programming languages like Rust or Ruby identifier styles are mixed. Sometimes it can be useful to cycle between the styles using the string-inflection package.

(define-key embark-identifier-map "-" #'string-inflection-cycle)
(add-to-list 'embark-repeat-actions #'string-inflection-cycle) ;; repeatable action!

Heading actions

The titlecase package helps with capitalizing English headlines. It can be useful to bind the command titlecase-line in the embark-heading-map. Furthermore there is a titlecase-region command which could go to the embark-region-map.

(define-key embark-heading-map "T" #'titlecase-line)
(define-key embark-region-map "T" #'titlecase-region)

File actions

You can bind these in embark-file-map.

Attaching file to an email message

(autoload 'gnus-dired-attach "gnus-dired")

(defun embark-attach-file (file)
  "Attach FILE to an  email message.
The message to which FILE is attached is chosen as for `gnus-dired-attach`,
that is: if no message buffers are found a new email is started; if some
message buffer exist you are asked whether you want to start a new email
anyway, if you say no and there is only one message buffer the attachements
are place there, otherwise you are prompted for a message buffer."
  (interactive "fAttach: ")
  (gnus-dired-attach (list file)))

Magit status of repo containing a given file

(defun embark-magit-status (file)
  "Run `magit-status` on repo containing the embark target."
  (interactive "GFile: ")
  (magit-status (locate-dominating-file file ".git")))

Very large files and sudo edit

Don't forget you have to install them as well as quelpa-use-package to use :quelpa keyword for package installation

(use-package embark
  :bind
  (:map
   minibuffer-local-completion-map
   ("M-o" . embark-act)
   :map embark-file-map
   ("s" . sudo-edit)
   ("l" . vlf))
  :quelpa
  (embark :repo "oantolin/embark" :fetcher github))

Simpler sudo edit which doesn't cover all the edge cases:

(defun +embark-sudo-edit ()
  (interactive)
  (find-file (concat "/sudo:root@localhost:" 
                     (expand-file-name (read-file-name "Find file as root: ")))))

Package actions using straight.el

(defvar-keymap embark-straight-map
  :parent embark-general-map
  "u" #'straight-visit-package-website
  "r" #'straight-get-recipe
  "i" #'straight-use-package
  "c" #'straight-check-package
  "F" #'straight-pull-package
  "f" #'straight-fetch-package
  "p" #'straight-push-package
  "n" #'straight-normalize-package
  "m" #'straight-merge-package)

(add-to-list 'embark-keymap-alist '(straight . embark-straight-map))

(add-to-list 'marginalia-prompt-categories '("recipe\\|package" . straight))

Password-store actions using password-store.el

  (defvar-keymap embark-password-store-actions
    :doc "Keymap for actions for password-store."
    "c" #'password-store-copy
    "f" #'password-store-copy-field
    "i" #'password-store-insert
    "I" #'password-store-generate
    "r" #'password-store-rename
    "e" #'password-store-edit
    "k" #'password-store-remove
    "U" #'password-store-url)

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

  ;; Either add a prompt classifier or overwrite password-store--completing-read
  (add-to-list 'marginalia-prompt-categories '("Password entry" . password-store))

Instead of adding the marginalia-prompt-categories classifier, it is also possible to overwrite the completing-read function of password-store.el directly.

(defun password-store--completing-read ()
  "Read a password entry in the minibuffer, with completion."
  (completing-read
   "Password entry: "
   (let ((passwords (password-store-list)))
     (lambda (string pred action)
       (if (eq action 'metadata)
           '(metadata (category . password-store))
         (complete-with-action action passwords string pred))))))

Actions for textual representations of keyboard macros

The GNU Hyperbole package is full of interesting ideas, one of which is to provide a convenient way to execute readable written representations of keyboard macros found in text buffers. Hyperbole's notation surrounds these in braces, so, for example {M-< hello! C-M-f} will go to the beginning of the buffer, insert the string "hello!" and then move forward one s-expression. We can teach Embark to recognize this notation and to offer several actions for one of these textual keyboard macros:

  1. Run it!, by far the most useful action.
  2. Save it on the keyboard macro ring, which means it becomes the current keyboard macro and can be executed with C-x e or <f4> in the default bindings.
  3. Name it, which prompts for a symbol and create a new command you can run with M-x.
  4. Bind it, which prompts for a key sequence and binds it to the keyboard macro (this has some convenient special treatment for keybindings of the form C-x C-k [0-9A-Z] , see kmacro-bind-to-key for details).

Here's the code:

(defun embark-kmacro-target ()
  "Target a textual kmacro in braces."
  (save-excursion
    (let ((beg (progn (skip-chars-backward "^{}\n") (point)))
          (end (progn (skip-chars-forward "^{}\n") (point))))
      (when (and (eq (char-before beg) ?{) (eq (char-after end) ?}))
        `(kmacro ,(buffer-substring-no-properties beg end)
                 . (,(1- beg) . ,(1+ end)))))))

(add-to-list 'embark-target-finders 'embark-kmacro-target)

(defun embark-kmacro-run (arg kmacro)
  (interactive "p\nsKmacro: ")
  (kmacro-call-macro arg t nil (kbd kmacro)))

(defun embark-kmacro-save (kmacro)
  (interactive "sKmacro: ")
  (kmacro-push-ring)
  (setq last-kbd-macro (kbd kmacro)))

(defun embark-kmacro-name (kmacro name)
  (interactive "sKmacro: \nSName: ")
  (let ((last-kbd-macro (kbd kmacro)))
    (kmacro-name-last-macro name)))

(defun embark-kmacro-bind (kmacro)
  (interactive "sKmacro: \n")
  (let ((last-kbd-macro (kbd kmacro)))
    (kmacro-bind-to-key nil)))

(defvar-keymap embark-kmacro-map
  :doc "Actions on kmacros."
  :parent embark-general-map
  "RET" #'embark-kmacro-run
  "s" #'embark-kmacro-save
  "n" #'embark-kmacro-name
  "b" #'embark-kmacro-bind)

(add-to-list 'embark-keymap-alist '(kmacro . embark-kmacro-map))

Use Embark like a leader-key

You can use embark-act as a leader key to perform actions on the current buffer or file:

(defun embark-target-this-buffer-file ()
  (cons 'this-buffer-file (or (buffer-file-name) (buffer-name))))

(add-to-list 'embark-target-finders #'embark-target-this-buffer-file 'append)

(add-to-list 'embark-keymap-alist '(this-buffer-file . this-buffer-file-map))

(embark-define-keymap this-buffer-file-map
      "Commands to act on current file or buffer."
      ("l" load-file)
      ("b" byte-compile-file)
      ("S" sudo-find-file)
      ("r" rename-file-and-buffer)
      ("d" diff-buffer-with-file)
      ("=" ediff-buffers)
      ("C-=" ediff-files)
      ("!" shell-command)
      ("&" async-shell-command)
      ("x" embark-open-externally)
      ("c" copy-file)
      ("k" kill-buffer)
      ("z" bury-buffer)
      ("|" embark-shell-command-on-buffer)
      ("g" revert-buffer))

Now running embark-act anywhere in a buffer should present these actions on that buffer. However, depending on the cursor position this-buffer-file-map may be shadowed by other embark keymaps, such as for actions on expressions or defuns. To get around this you can either call embark-cycle or define a new function:

(defun embark-act-on-buffer-file (&optional arg)
  (interactive "P")
  (let ((embark-target-finders '(embark-target-this-buffer-file)))
    (embark-act arg)))

(global-set-key (kbd "C-c o") 'embark-act-on-buffer-file)

Now embark-act-on-buffer-file functions like a leader-key for file/buffer actions. Note: Emacs 28 added a ctl-x-x-map for buffer actions with more ideas for bindings here.

Narrow to heading in consult-outline

This adds an action in consult-outline to narrow to the selected heading and expand it.

(defun my/consult-outline-narrow-heading (heading)
  "Narrow to and expand HEADING."
  (embark-consult-goto-location heading)
  (outline-mark-subtree)
  (and
   (use-region-p)
   (narrow-to-region (region-beginning) (region-end))
   (deactivate-mark))
  (outline-show-subtree))

(defvar-keymap embark-consult-outline-map
  :doc "Keymap for embark actions in `consult-outline'."
  "r" #'my/consult-outline-narrow-heading)

(defun with-embark-consult-outline-map (fn &rest args)
  "Let-bind `embark-keymap-alist' to include `consult-location'."
  (let ((embark-keymap-alist
	   (cons '(consult-location . embark-consult-outline-map) embark-keymap-alist)))
    (apply fn args)))

(advice-add 'consult-outline :around #'with-embark-consult-outline-map)

For those who use outshine-mode, a simpler definition of the narrowing function:

(defun my/consult-outline-narrow-heading (heading)
    "Narrow to and expand HEADING."
    (embark-consult-goto-location heading)
    (outshine-narrow-to-subtree)
    (outline-show-subtree))

Elisp expression/variable actions

Sometimes it's useful to embark-act on the result of an Elisp expression, not on the expression itself. For example one might have code like this:

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))

By default there is no easy way to evaluate the inner expression and invoke embark-act on it to open the actual file. The following action provides exactly this:

(defun embark-act-with-eval (expression)
  "Evaluate EXPRESSION and call `embark-act' on the result."
  (interactive "sExpression: ")
  (with-temp-buffer
    (insert (eval (read expression)))
    (embark-act)))

Bind it in embark-variable-map and embark-expression-map for best results.

Pull target into minibuffer after a command has started

It is possible to write an action to, in effect, reverse embark-act's target-action ordering.

As a concrete example, say you are looking at the Magit status view for a repo, and you decide that you want to check out a particular commit by its hash. So you hit b b, running magit-checkout, which wants to read the hash value in the minibuffer. Then you realize that you have the hash already written out in another open buffer. You switch to that buffer and run embark-act on the hash. The magit-checkout prompt is still active and Embark has the value you want. How do you put them together?

The new action needs to take the target and insert it into the minibuffer that's being used for magit-checkout's prompt. This turns out to be quite simple. The key is that after the Embark action has its input any minibuffer it used is gone, so its body can access the existing active minibuffer. Here is a suggested implementation by @minad:

(defun embark-inject (str)
  (interactive "sString: ")
  (let ((win (active-minibuffer-window)))
    (unless win
      (user-error "No active minibuffer"))
    (select-window win)
    (delete-minibuffer-contents)
    (insert str)))

(define-key embark-general-map "I" #'embark-inject)

This could also be written as a non-interactive function.

Of course, with Embark's built-in actions you could use embark-copy-as-kill (bound to w in embark-general-map) and then do a normal yank into the active minibuffer, although this will leave the target in your kill ring.