Embark and my extras (Emacs)
Raw link: https://www.youtube.com/watch?v=uoP9ZYdNCHg
In this video I provide an overview of Embark’s features. I discuss
everything from the basic concepts of acting on targets, to how you can
extend it as your front-end for completion candidates. There is also a
demonstration of embark-become
and how that can be used to make your
minibuffer-centric workflows more efficient.
The text of the presentation is copied below (org-mode
notation).
Refer to my Emacs configuration file (“dotemacs”) for the implementation
details: https://protesilaos.com/emacs/dotemacs. And check the Git
repositories of the projects I covered:
#+TITLE: Emacs: Embark and my extras
#+AUTHOR: Protesilaos Stavrou · protesilaos.com
* Brief introduction to Embark
Embark provides a unified framework of regular Emacs keymaps which let
you carry out /contextually relevant actions/ on *targets* through a common
*point of entry*, typically a /prefix key/.
+ "Actions" are standard Emacs commands, such as =describe-symbol= or some
interactive command you have defined that reads an argument from the
minibuffer.
+ "Targets" are semantically sensitive constructs, such as the symbol at
point, a URL, a file name, the active region, or the current
completion candidate in the minibuffer (or the completions'
buffer---more on that in the next section). Embark has so-called
"classifiers" which help it determine the category that the target
belongs to.
+ The "contextually relevant [actions]" are defined in keymaps whose
scope matches the category of the target. So =embark-file-map= holds
all key and command assossiations for when Embark recognises a file
name as its target. =embark-region-map= is for actions pertaining to
the active region; =embark-buffer-map= for buffer names that you access
through, say, =switch-to-buffer= (=C-x b=). And so on.
+ As for the "point of entry" or "prefix key", it is an Embark command,
such as =embark-act=, =embark-act-noexit=, or =embark-become=. Those
activate the appropriate keymap, thus granting you access to the
relevant commands.
Embark can act on individual targets (e.g. the region) or sets of
targets (e.g. the list of minibuffer completion candidates).
* Embark collections and how to cycle completions
Here we will be discussing Embark's ability to act on a set of targets.
Our specific case is to instruct it (via a hook), to automatically
gather the completion candidates of the minibuffer and put them in a
live-updating buffer. In other words: a front-end to the minibuffer's
underlying completion mechanisms.
Let's test this with =switch-to-buffer= (=C-x b=) and some input that we
provide. The "Embark Collect" buffer pops up and shows us what we are
currently matching. We can then produce a snapshot or export this set
to an appropriate major-mode (=ibuffer-mode= in this case).
I received a lot of questions about my workflow with the Embark
completions buffer. The idea was:
+ How do you select an item when you narrow to a short list?
+ Do you manually switch from the minibuffer to the completions?
The short answer is that I have written some extensions that handle this
"candidate cycling".
The long answer is best illustrated by an example (the following is a
natural cycling behaviour):
+ =C-n= in the minibuffer takes us to the top of the completions' buffer.
+ =C-p= in the minibuffer moves to the completions' bottom.
+ =C-n= inside the completions' buffer moves the line normally or, when at
the end, switches to the minibuffer.
+ =C-p= inside the completions' buffer also moves the line, though in the
opposite direction, and when at the top it switches to the minibuffer.
* Perform default action while cycling
A common workflow with Embark is to produce a snapshot of the
minibuffer's collection you have narrowed to and then inspect that
buffer.
Let's try this with =M-x describe-keymap RET embark= (I bind that help to
=C-h K=). Then we produce a snapshot with =embark-act= (you have a key
binding for that) and =S= for =embark-collect-snapshot=.
In this buffer we can move up and down normally and hit =RET= when we want
to perform the default action which, in this case, is to get a help
buffer for the symbol at point.
With my =C-M-n= or =C-M-p= we essentially combine =C-n= or =C-p= into a single
motion. This is useful when we want to continue from one line to the
next, such as by inspecting the help buffer of each of those embark
keymaps that we got the snapshot for.
[ this is a concept I got from Ivy's own version of operating on sets of
targets ]
* Manual previews for Consult commands (consult.el)
Those specific "move+act" motions allow me to get manual previews for
all =consult.el= commands, even though I use the default minibuffer.
Otherwise I would need to use some other library to cycle candidates,
like =icomplete= or =selectrum=.
So here is an example with =consult-line= and purposeful manual previews:
+ Search a file for a pattern
+ Cycle the Embark candidates
+ Use =C-M-j= to "preview" the line at point or, =C-M-n= / =C-M-p= to preview
the next/previous one and move the point there (the latter two accept
a numeric argument)
The benefit of this workflow is that I can display a preview only when I
want to and, most importantly, I do it from inside the Embark buffer
instead of the minibuffer (which is why I can avoid Icomplete or
Selectrum).
NOTE: consult can be configured to display previews manually or on a
case-by-case basis, though I feel you only benefit from that if you are
using it with Icomplete or Selectrum.
* A look at ~embark-become~ and cross-package integration
One of my goals with extending Embark for my personal needs is to have
some fluidity or seamlessness while performing minibuffer-centric
actions. This can be achieved with the =embark-become= command: it lets
you re-use the current minibuffer's input in another minibuffer command.
The default =embark-become= lets you switch contexts between =find-file=
(=C-x C-f=) and =switch-to-buffer= (=C-x b=). Start either of those actions,
type something, and the invoke =embark-become= to switch to the other (you
should bind =embark-become= to some key---see my dotemacs).
The design of Embark is based on the principle of scoping actions inside
keymaps. Each of those keymaps applies to a context that Embark can
interpret by reading the category of what is being completed against or
what the target at point is. Long story short: we can bind our own
actions to keys in each of those contexts and/or we can define our own
keymaps (general or specific) to extend the default options.
What I have here is a work in progress, but consider those two scenaria
with Consult commands for (1) grep and find, (2) outline and line
search:
1. We can invoke the relevant grep command and then decide that what we
were actually looking for is a find operation. So we rely on
=embark-become= to take us from one place to the next without losing
what we have already inserted in the minibuffer.
2. Same principle for =consult-outline= and =consult-line=, where we may be
searching for a pattern that exists in a heading only to realise that
we wanted to query for all lines instead. =embark-become= to the
rescue!
* Further information
Refer to my "dotemacs" for the complete setup:
<https://protesilaos.com/emacs/dotemacs>.
And check the Git repositories of the projects:
+ <https://github.com/oantolin/embark>
+ <https://github.com/minad/consult>