Skip to content
Howard Melman edited this page May 10, 2022 · 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.

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

(embark-define-keymap embark-straight-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

  (embark-define-keymap embark-password-store-actions
    "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)))

(embark-define-keymap embark-kmacro-map
  "Actions on kmacros."
  ("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" consult-file-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.

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

(embark-define-keymap embark-consult-outline-map
  "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.

Clone this wiki locally