-
-
Notifications
You must be signed in to change notification settings - Fork 59
Additional Actions
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.
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)
...
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
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!
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)
You can bind these in embark-file-map
.
(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)))
(defun embark-magit-status (file)
"Run `magit-status` on repo containing the embark target."
(interactive "GFile: ")
(magit-status (locate-dominating-file file ".git")))
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: ")))))
(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))))))
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:
- Run it!, by far the most useful action.
- 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. - Name it, which prompts for a symbol and create a new command you can run with
M-x
. - 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]
, seekmacro-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))
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.
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))
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.
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.
See also the Consult Wiki and the Vertico Wiki!