Embark is a package for Emacs that turns whatever
is at point (a file, URL, region, symbol) into a target with a context menu of
actions, invoked with embark-act (I bind it to C-.). Think of it like right-click
with the mouse over an item. It's very handy when you start doing something,
e.g. finding a file to open it, but wish to execute a different action over it instead.
In this HOWTO I present some custom embark actions I have added to my Emacs configuration:
- Storing Org links to files, dirs, URLs and Magit commits
- Open Magit and terminals on directories
- Sending things to an LLM
- A few tweaks to use jinx and helpful.
Each section shows the code and the keybinding I define.
Prerequisites
I assume use-package (built into Emacs 29+) and embark installed and bound:
(use-package embark
:bind (("C-." . embark-act))
:config [...]
)
Every snippet below goes inside that block's :config. The pattern is always the same:
define a command that receives the target through its interactive spec, then
keymap-set it into the right embark-*-map.
Storing Org links to files and URLs
When using org-mode one calls M-x org-store-link to save the location of the thing at
point, and then insert a link to it somewhere else using C-c C-l (org-insert-link).
embark-org (auto-loaded with embark) binds org-store-link on C-. l for Org
headings, agenda items and consult-org-heading candidates. However I often wanted to
save links to local files or directories, or to an URL.
I defined two actions to call org-stored-link, so now I can save these links easily
and insert them in my Org files with C-c C-l:
(defvar org-stored-links)
(defun my-embark-org-store-file-link (file)
"Store an Org link to FILE in `org-stored-links'."
(interactive "fStore Org link to file: ")
(push (list (concat "file:" (expand-file-name file))
(file-name-nondirectory file))
org-stored-links)
(message "Stored Org link to %s" file))
(defun my-embark-org-store-url-link (url)
"Store an Org link to URL in `org-stored-links'."
(interactive "sStore Org link to URL: ")
(push (list url nil) org-stored-links)
(message "Stored Org link to %s" url))
(keymap-set embark-file-map "l" #'my-embark-org-store-file-link)
(keymap-set embark-url-map "l" #'my-embark-org-store-url-link)
Note that l in embark-file-map displaces the default load-file binding.
Acting on Magit commits
I use orgit to have links to git commits in my org
files. The usual flow is to open a commit in detail, then call M-x orgit-store-link to
save the link to it. With Embark I can avoid opening the commit, for example while
browsing the log.
I define a new target type, magit-commit, for the commit at point in any Magit buffer
(log, status, refs, revision), found with the public magit-commit-at-point. Then the
actions: RET shows the commit, C-. l stores an orgit-rev: link, and C-. w copies
the hash (from embark-general-map).
;; Registered near the front of `embark-target-finders' so the commit
;; target beats the generic identifier target on hashes (mirrors
;; embark-org's approach).
(defun my-embark-target-magit-commit ()
"Target the commit at point in a Magit buffer."
(when (derived-mode-p 'magit-mode)
(when-let* ((rev (magit-commit-at-point)))
(if-let* ((bounds (bounds-of-thing-at-point 'git-revision)))
`(magit-commit ,rev ,(car bounds) . ,(cdr bounds))
(cons 'magit-commit rev)))))
(defun my-embark-org-store-commit-link (rev)
"Store an Org link to commit REV via orgit."
(interactive (list (or (magit-commit-at-point)
(magit-read-branch-or-commit
"Store Org link to commit"))))
(unless (require 'orgit nil t)
(user-error "Storing Org links to commits requires the orgit package"))
(save-window-excursion
;; `magit-show-commit' selects the revision window, so
;; `org-store-link' runs in the revision buffer and orgit
;; stores an orgit-rev link.
(magit-show-commit rev)
(org-store-link nil t)))
(defvar-keymap my-embark-magit-commit-map
:doc "Keymap for Embark actions on Magit commits."
:parent embark-general-map
"RET" #'magit-show-commit
"l" #'my-embark-org-store-commit-link)
(let ((tail (memq 'embark-target-active-region embark-target-finders)))
(cl-pushnew 'my-embark-target-magit-commit (cdr tail)))
(add-to-list 'embark-keymap-alist
'(magit-commit my-embark-magit-commit-map))
The embark-vc package also
targets commits, but finds them via a margin-hash regexp and is
forge-centric; this target is section-based, so it works anywhere
magit-commit-at-point does, and focuses on Org links.
Git and terminal actions on files and directories
C-. g runs magit-status for the directory; this also works on project roots in
project-switch-project, because Embark turns those candidates into directory
targets.
C-. T opens a file's history with
git-timemachine
C-. t opens a terminal in the directory:
(defun my-embark-magit-status (dir)
"Run `magit-status' for the repository containing DIR."
(interactive "DMagit status: ")
(magit-status dir))
(defun my-embark-git-timemachine (file)
"Visit FILE and browse its history with `git-timemachine'."
(interactive "fFile: ")
(find-file file)
(git-timemachine))
(defun my-embark-ghostel (dir)
"Open a Ghostel terminal in DIR."
(interactive "DTerminal in directory: ")
(let ((default-directory (if (file-directory-p dir)
(file-name-as-directory dir)
(file-name-directory (expand-file-name dir)))))
(ghostel)))
(keymap-set embark-file-map "g" #'my-embark-magit-status)
(keymap-set embark-file-map "T" #'my-embark-git-timemachine)
(keymap-set embark-file-map "t" #'my-embark-ghostel)
Swap ghostel for eshell, vterm or whichever terminal you use.
LLM actions with gptel
I use a global C-c a prefix for AI actions through
gptel: r rewrite, a add to context, s send,
R security review. I mimic the same keys in the embark map, plus C-. a on a file to
add it to the gptel context, and C-. a on a Flymake diagnostic asks the LLM to explain
it:
;; Displaces `align' in `embark-region-map' (`align-regexp' stays on "A").
(defvar-keymap my-embark-gptel-map
:doc "Keymap for Embark actions sending the region to gptel."
"r" #'gptel-rewrite
"a" #'gptel-add
"s" #'gptel-send
"R" #'my-gptel-review-malicious-code) ; my own review command
(fset 'my-embark-gptel-map my-embark-gptel-map)
(keymap-set embark-region-map "a" 'my-embark-gptel-map)
(keymap-set embark-file-map "a" #'gptel-add-file)
(defun my-embark-gptel-explain-diagnostic ()
"Ask gptel to explain the Flymake diagnostic at point."
(interactive)
(require 'gptel)
(if-let* ((diag (car (flymake-diagnostics (point)))))
(let* ((text (format
"Explain this diagnostic in %s and suggest a fix.\n\n\
Diagnostic (%s): %s\n\nCode:\n%s"
(buffer-name)
(flymake-diagnostic-type diag)
(flymake-diagnostic-text diag)
(buffer-substring-no-properties
(flymake-diagnostic-beg diag)
(flymake-diagnostic-end diag))))
(buffer (get-buffer-create "*gptel-diagnostic*"))
(marker (with-current-buffer buffer
(erase-buffer)
(org-mode)
(point-marker))))
(pop-to-buffer buffer)
(gptel-request text
:system "You are a concise programming assistant."
:stream t
:buffer buffer
:position marker))
(user-error "No Flymake diagnostic at point")))
(keymap-set embark-flymake-map "a" #'my-embark-gptel-explain-diagnostic)
Smaller quality-of-life tweaks
Three one-liners: spell-correct with jinx instead of
ispell; open helpful buffers instead of
describe-*; and find references with
elisp-refs:
(keymap-set embark-identifier-map "$" #'jinx-correct)
(keymap-set embark-symbol-map "h" #'helpful-symbol)
(keymap-set embark-variable-map "h" #'helpful-variable)
(keymap-set embark-function-map "h" #'helpful-callable)
(keymap-set embark-command-map "h" #'helpful-command)
(keymap-set embark-symbol-map "R" #'elisp-refs-symbol)
Cheat sheet
| Target | Keys | Action |
|---|---|---|
| File or URL | C-. l |
Store Org link for C-c C-l in org-mode |
| Magit commit | C-. RET / l / w |
Show / store orgit link / copy hash |
| Directory | C-. g |
magit-status (also project roots) |
| File | C-. T |
git-timemachine |
| Directory | C-. t |
Terminal in directory |
| Region | C-. a r/a/s/R |
gptel rewrite / add / send / review |
| File | C-. a |
Add file to gptel context |
| Flymake diagnostic | C-. a |
Explain and fix with gptel |
| Identifier | C-. $ |
jinx-correct |
| Symbol | C-. h / C-. R |
helpful / find references |
Closing
Embark is a truly useful package because it is original in its functionality, it really adds something new to the Emacs experience. Even if you don't find any of these examples directly useful to you, may they serve as inspiration to craft your own.