You’re using Avy wrong.
Too harsh? Let me rephrase: you’re barely using Avy. Still too broad? Okay, the noninflammatory version: Avy, the Emacs package to jump around the screen, lends itself to efficient composable usage that’s obscured by default.
Featured Content Ads
add advertising hereWithout burying the lede any further, here’s a demo that uses a single Avy command (avy-goto-char-timer
) to do various things in multiple buffers and windows, all without manually moving the cursor:
Copy text, kill lines or regions, move text around, mark text, bring up help buffers, look up definitions, search google, check my spelling, the list goes on. I emphasize this again: Avy defines dozens of jump commands, but I’m only using one. This post breaks this down in detail so you can create your own version of this, but more importantly tries to explain why this is a neat idea.
This is the first of two parts in a series on Avy, an Emacs package for jumping around efficiently with the keyboard. Part 1 is about about supercharging built-in customization to do anything with Avy, or some approximation thereof. Part 2 will be a more technical (elisp-y) dive into writing more complex features for your individual needs. If you are interested in the short elisp snippets in this document, they are collated into a single file here.
Filter → Select → Act
We see the same pattern repeated in most interactions with Emacs whose primary purpose isn’t typing text. To perform a task, we Filter, Select and Act in sequence:
Featured Content Ads
add advertising hereFilter: Winnow a large pile of candidates to a smaller number, usually by typing in some text. These candidates can be anything, see below for examples.
Select: Specify a candidate as the one you need, usually with visual confirmation. If you filter a list down to one candidate, it’s automatically selected.
Act: Run the task with this candidate as an argument.
- Want to open a file? Invoke a command, then type in text to filter a list of completions, select a completion, find-file.
- Switch buffers? Type to filter, select a buffer, switch.
- Autocomplete a symbol in code? Type a few characters to narrow a pop-up list, choose a candidate, confirm.
As ever, this model is a simplification. Helm, Ivy, Dired & co let you select multiple candidates to act on, for instance. We will put this qualification aside while we explore this idea.
Featured Content Ads
add advertising hereThis observation leads to several interesting ideas. For one, the Filter → Select → Act process is often thought of as a single operation. Decoupling the filtering, selection and action phases (in code and in our heads) frees us to consider a flock of possibilities. Here’s minibuffer interaction:
The possibilities are, respectively: different completion styles (matching by regexps, flex, orderless), different selection interfaces (Icomplete, Ivy, Helm, Vertico, Selectrum and more by the day) and different action dispatchers (Embark, Helm/Ivy actions)1. A similar mini-Cambrian explosion has happened in the space of shell utilities since fzf arrived on the scene. In Emacs the minibuffer is where we’re used to selecting things, so it’s natural to think of these examples.
But this usage pattern extends to more than just minibuffer interaction. We often do things in regular buffers that involve the Filter → Select → Act pattern. In fact, you can look at most interactions with Emacs (whose focus isn’t typing text) this way: when you call a goto-definition command with point–the text cursor–in some text, the filtering stage is skipped, the selection is made for you by a function from the thing-at-pt
library, and a preset action is called. How about a mouse interaction? scroll through a window full of icons, move the mouse cursor to an icon and click on it. These completely redundant examples prime us for the more interesting cases. Consider Isearch:
When you type (after C-s
), you automatically filter buffer text and select the nearest match. You can select each subsequent match in turn, or jump to the first or last match. The Act here is the process of moving the cursor to the match location, but it can be one of a few things, like running occur
or query-replace
on the search string. Many Isearch commands simultaneously filter, select and act, so we’re fitting a square peg in a round hole here2.
If you’ve spent any time using Isearch, you can appreciate the tradeoff involved in dividing a task into these three independently configurable phases. Lumping two or all three into a single operation makes Isearch a wicked fast keyboard interaction. When I use Isearch my brain is usually trying to catch up to my fingers. On the other hand, the advantage of modularity is expressive power. The three phase process is slower on the whole, but we can do a whole lot more by plugging in different pieces into each of the Filter , Select and Act slots. To see this, you have only to consider how many disparate tasks the minibuffer handles, and in how many different ways!
But back to Isearch: what can we do to decouple the three stages here? Not much without modifying its guts. It’s all elisp, so that’s not a tall order. For example, Protesilaos Stavrou adds many intuitive actions (such as marking candidates) to Isearch in this video. But it turns out we don’t need to modify Isearch, because Avy exists, has a killer Filter feature, and it separates the three stages like a champ. This makes for some very intriguing possibilities.
Wait, what’s Avy?
Avy is authored by the prolific abo-abo (Oleh Krehel), who also wrote Ivy, Counsel, Ace-Window, Hydra, Swiper and many other mainstays of the Emacs package ecosystem that you’ve probably used. If you’re reading this, chances are you already know (and probably use) Avy. So here’s a very short version from the documentation:
avy is a GNU Emacs package for jumping to visible text using a char-based decision tree.
You can call an Avy command and type in some text. Any match for this text on the frame (or all Emacs frames if desired) becomes a selection candidate, with some hint characters overlaid on top. Here I type in “an” and all strings in the frame that match it are highlighted:
Typing in one of the hints then jumps the cursor to that location. Here I jump to this sentence from another window:
Play by play
avy-goto-char-timer
g
here.
Typical Avy usage looks something like this: Jump to a location that matches your text (across all windows), then jump back with pop-global-mark
(C-x C-SPC
). In a later section I go into more detail on jumping back and forth with Avy. Here is a demo of this process where I jump twice with Avy and then jump back in sequence:
Play by play
avy-goto-char-timer
pop-global-mark
(C-x C-SPC
) to jump back to the previous location.pop-global-mark
(C-x C-SPC
) again to jump back to the previous location.
At least that’s the official description. You can peruse the README for more information, but what I find mystifying is that…
…Avy’s documentation leaves out the best part
Avy handles filtering automatically and the selection is made through a char-based decision tree. Here’s how it fits into our three part interaction model.
Filter,
Before you call Avy every text character on your screen is a potential candidate for selection. The possibilites are all laid out for you, and there are too many of them!
You filter the candidate pool with Avy similar to how you would in the minibuffer, by typing text. This reduces the size of the pool to those that match your input. Avy provides dozens of filtering styles. It can: only consider candidates above/below point, only beginnings of words, only the current window (or frame), only whitespace, only beginnings of lines, only headings in Org files, the list goes on.
This is a crazy list to keep track of.
Filtering commands in Avy
Unit: Char
Unit: Word or Symbol
Unit: Line or other
avy-goto-char-timer
avy-goto-word-0
avy-goto-line
avy-goto-char
avy-goto-subword-0
avy-goto-end-of-line
avy-goto-char-2
avy-goto-symbol-1-above
avy-goto-whitespace-end
avy-goto-char-in-line
avy-goto-word-0-below
avy-org-goto-heading-timer
avy-goto-char-2-below
avy-goto-word-1-below
avy-goto-whitespace-end-above
avy-goto-char-2-above
avy-goto-symbol-1-below
avy-goto-line-above
avy-goto-word-1-above
avy-goto-whitespace-end-below
avy-goto-symbol-1
avy-goto-line-below
avy-goto-word-or-subword-1
avy-goto-subword-1
avy-goto-word-1
avy-goto-word-0-above
Filtering in Avy is independent of the selection method (as it should be), but there is a dizzying collection of filtering methods. I assume the idea is that the user will pick a couple of commands that they need most often and commit only those to memory.
Here’s the problem: We want to use our mental bandwidth for the problem we’re trying to solve with the text editor, not the editor itself. Conscious decision-making is expensive and distracting. As of now we need to decide on the fly between Isearch and Avy to find and act on things. If you use a fancy search tool like Swiper, Helm-swoop or Consult-line, you now have three options. Having a bunch of Avy commands on top is a recipe for mental gridlock. To that end, we just pick the most adaptable, flexible and general-purpose Avy command (avy-goto-char-timer
) for everything.
(global-set-key (kbd "M-j") 'avy-goto-char-timer)
Further below I make the case that you don’t need to make even this decision, you can always use Isearch and switch to Avy as needed.
To be clear, this decision cost has to be balanced against the cost of frequent busywork and chronic context switching that Avy helps avoid. There is a case to be made for adapting Avy’s flexible filtering options to our needs, and the number of packages that offer Avy-based candidate filtering (for everything from linting errors to buffer selection) attests to this. We will examine this in depth in Part II.
But in this piece we are interested in a different, much less explored aspect of Avy.
Select,
Every selection method that Avy offers involves typing characters that map to screen locations. This is quite different from Isearch, where you call isearch-repeat-forward
(C-s
, possibly with a numerical prefix argument) or the minibuffer, where you navigate a completions buffer or list with C-n
and C-p
. Avy’s selection method is generally faster because it minimizes, by design, the length of the character sequences it uses, and we have ten fingers that can access ∼40 keys in O(1) time. The fact that we’re often looking directly where we mean to jump means we don’t need to parse an entire screen of gibberish. Unfortunately for this article, this means using Avy is much more intuitive than looking at screenshots or watching demos.
This excellent design leaves us with little reason to tinker with the selection phase: it’s sufficiently modular and accommodating of different filter and act stages. You can customize avy-style
if you want to change the set or positions of characters used for selection. Here is an example of using simple words to select candidates:
We will pay more attention to the selection operation in part II as well.
Act!
This brings us to the focus of this article. The stated purpose of Avy, jumping to things, makes it sound like a (contextually) faster Isearch. But jumping is only one of many possibilities. Avy provides a “dispatch list”, a collection of actions you can perform on a candidate, and they are all treated on equal footing with the jump action. You can show these commands any time you use Avy with ?
:
This means we are free to leverage Avy’s unique filtering and selection method to whatever action we want to carry out at any location on the screen. Our interaction model now ends in a block that looks something like this:
Additionally, Avy also defines a few commands that run different actions, like copying text from anywhere on screen:
Kill | Copy | Move |
---|---|---|
avy-kill-ring-save-whole-line | avy-copy-line | avy-move-line |
avy-kill-ring-save-region | avy-copy-region | avy-move-region |
avy-kill-region | avy-transpose-lines-in-region | |
avy-kill-whole-line | avy-org-refile-as-child |
The problem with this approach is that it doesn’t scale. Each of these commands defines a full Filter → Select → Act process, and we quickly run out of headspace and keyboard room if we want any kind of versatility or coverage. They’re also not dynamic enough: you’re locked into the pipeline and cannot change your mind once you start the command.
Folks love Vim’s editing model for a reason: it’s a mini-language where knowing M actions (verbs) and N cursor motions gives you M × N composite operations. This (M + N) → (M × N) ratio pays off quadratically with the effort you put into learning verbs and motions in Vim. Easymotion, which is Vim’s version of Avy3, has part of this composability built-in as a result. We seek to bring some of this to Avy, and (because this is Emacs) do a lot more than combining motions with verbs. We won’t need to step into Avy’s source code for this, it has all the hooks we need already.
Avy actions
The basic usage for running an arbitrary action with Avy is as follows:
- Call an Avy command. Any command will do, I stick to
avy-goto-char-timer
. - Filter: Type in some text to shrink the candidate pool from the entire screen to a few locations.
- Act: Specify the action you want to run. You can pull up the dispatch help with
?
, although you won’t have to if you set it up right, see Remembering to Avy. - Select: Pick one of the candidates to run the action on.
Here are some things I frequently do with Avy. Note that you can do this on any text in your frame, not just the active window!
First, taking the annoyance out of some common editing actions with Avy. If you use Vim and Easymotion, you get the first few actions below for free:
For clarity, I set Avy to desaturate the screen and to “pulse” the line during a few of these actions. These are not enabled by default. I also slowed down the operations by adding a delay to make them easier to follow. In actual usage these are instantaneous.
The keys Avy uses to dispatch actions on candidates are specified in We will also need to ensure that these keys don’t coincide with the ones Avy uses as selection hints on screen. Consider customizing
A note about these demos
avy-dispatch-alist
.
avy-keys
for this.
Kill a candidate word, sexp or line
Killing words or s-expressions is built-in. I added an action to kill a line. In this demo I quickly squash some typos and delete a comment, then remove some code in a different window:
Play by play
avy-goto-char-timer
.avy-action-kill
with k
avy-Goto-Char-Timer
.avy-action-kill-whole-line
with K
.
(defun avy-action-kill-whole-line (pt)
(save-excursion
(goto-char pt)
(kill-whole-line))
(select-window
(cdr
(ring-ref avy-ring 0)))
t)
(setf (alist-get ?k avy-dispatch-alist) 'avy-action-kill-stay
(alist-get ?K avy-dispatch-alist) 'avy-action-kill-whole-line)
Yank a candidate word, sexp or line
Copy to the kill-ring or copy to point in your buffer. In this demo I copy some text from a man page into my file:
Play by play
avy-goto-char-timer
.avy-action-yank
, bound to y
.avy-goto-char-timer
.avy-action-yank-whole-line
, bound to Y
.just-one-space
, bound to M-SPC
by default.
(defun avy-action-copy-whole-line (pt)
(save-excursion
(goto-char pt)
(cl-destructuring-bind (start . end)
(bounds-of-thing-at-point 'line)
(copy-region-as-kill start end)))
(select-window
(cdr
(ring-ref avy-ring 0)))
t)
(defun avy-action-yank-whole-line (pt)
(avy-action-copy-whole-line pt)
(save-excursion (yank))
t)
(setf (alist-get ?y avy-dispatch-alist) 'avy-action-yank
(alist-get ?w avy-dispatch-alist) 'avy-action-copy
(alist-get ?W avy-dispatch-alist) 'avy-action-copy-whole-line
(alist-get ?Y avy-dispatch-alist) 'avy-action-yank-whole-line)
Note that Avy actually defines separate commands for this: avy-copy-line
and avy-copy-region
to copy lines and regions from anywhere in the frame. These are a little faster since they have the action stage baked into the function call. You might be better served by these. But we want to avoid the mental burden of remembering too many top level commands, so we work in two simpler stages: call avy-goto-char-timer
(to filter and select) and then dispatch on our selected candidate as we see fit.
Move a candidate word, sexp or line
Avy calls this “teleport”, I call it “transpose”, either way it’s bound to t
. In this demo I move text around the buffer… without moving (much) around the buffer:
Play by play
avy-goto-char-timer
.t
to run avy-action-teleport
avy-goto-char-timer
. This is the only match for the input “down”, so Avy jumps there automatically. You could also just isearch-backwards
here.avy-goto-char-timer
.T
to run =avy-action-teleport-line~.
(defun avy-action-teleport-whole-line (pt)
(avy-action-kill-whole-line pt)
(save-excursion (yank)) t)
(setf (alist-get ?t avy-dispatch-alist) 'avy-action-teleport
(alist-get ?T avy-dispatch-alist) 'avy-action-teleport-whole-line)
Zap to a candidate position
This is built-in and bound to z
by default:
Play by play
avy-goto-char-timer
z
to run avy-action-zap
.
Mark a candidate word or sexp
Also built in, m
by default. This isn’t different from jumping to the candidate using Avy and calling mark-sexp
, but it is more convenient:
Play by play
avy-goto-char-timer
.m
to run avy-action-mark
.("~/.local/share")
.(data_directory...
and RotatingFileHandler
Mark the region from point to a candidate
Avy sets the mark before it jumps, so you could use C-x C-x
to activate the region, but this saves you the trouble.
Play by play
avy-goto-char-timer
.SPC
to run avy-action-mark-to-char
.avy-goto-char-timer
.SPC
to run avy-action-mark-to-char
.
(defun avy-action-mark-to-char (pt)
(activate-mark)
(goto-char pt))
(setf (alist-get ? avy-dispatch-alist) 'avy-action-mark-to-char)
Next, some contextual actions automagicked by Avy:
ispell a candidate word
This is built-in, bound to i
by default.
Play by play
avy-goto-char-timer
(or any other Avy jump command)avy-action-ispell
, set to i
by default.ispell-word
on the selection.avy-goto-char-timer
again.avy-action-ispell
ispell-word
again, and “teh” can be corrected.
You can replace avy-action-ispell
(built-in) with a version that automatically picks the top correction for a word, automating the process:
(defun avy-action-flyspell (pt)
(save-excursion
(goto-char pt)
(when (require 'flyspell nil t)
(flyspell-auto-correct-word)))
(select-window
(cdr (ring-ref avy-ring 0)))
t)
;; Bind to semicolon (flyspell uses C-;)
(setf (alist-get ?; avy-dispatch-alist) 'avy-action-flyspell)
Define a word
I use the dictionary package for Emacs, and I’m lazy about it:
Play by play
avy-goto-char-timer
(or any other Avy jump command)avy-action-define
, set to =
herescroll-other-window
(C-M-v
) to scroll the dictionary window.avy-goto-char-timer
again.avy-action-define
;Replace your package manager or preferred dict package
(package-install 'dictionary)
(defun dictionary-search-dwim (&optional arg)
"Search for definition of word at point. If region is active,
search for contents of region instead. If called with a prefix
argument, query for word to search."
(interactive "P")
(if arg
(dictionary-search nil)
(if (use-region-p)
(dictionary-search (buffer-substring-no-properties
(region-beginning)
(region-end)))
(if (thing-at-point 'word)
(dictionary-lookup-definition)
(dictionary-search-dwim '(4))))))
(defun avy-action-define (pt)
(save-excursion
(goto-char pt)
(dictionary-search-dwim))
(select-window
(cdr (ring-ref avy-ring 0)))
t)
(setf (alist-get ?= avy-dispatch-alist) 'dictionary-search-dwim)
Look up the documentation for a symbol
Play by play
- Call
avy-goto-char-timer
- Type in text to filter with, in this case “pc”.
- Press
H
to runavy-action-helpful
. - Select a candidate phrase, in this case “pcase-lambda”. This pulls up a documentation buffer for this symbol.
scroll-other-window
withC-M-v
to scroll the help buffer.- call
avy-goto-char-timer
- Type in text to filter with, in this case “ma”.
- Press
H
to runavy-action-helpful
. - Select a candidate phrase, in this case “macroexp-parse-body”. Note that this is matched in the other (help) window. This pulls up the documentation for this symbol.
- Repeat steps 5-9 to find the documentation of another symbol, in this case
memq
.
;Replace with your package manager or help library of choice
(package-install 'helpful)
(defun avy-action-helpful (pt)
(save-excursion
(goto-char pt)
(helpful-at-point))
(select-window
(cdr (ring-ref avy-ring 0)))
t)
(setf (alist-get ?H avy-dispatch-alist) 'avy-action-helpful)
Google search for a word or sexp4
You’ll need an Emacs feature that can search Google for you. There are several. I use a CLI program named Tuxi for this, and it’s pretty handy:
Play by play
avy-goto-char-timer
(or any other Avy jump command)avy-action-tuxi
, set to C-=
hereavy-goto-char-timer
again.avy-action-tuxi
Now: We could continue populating avy-dispatch-alist
with functions to do increasingly arcane contextual actions, but let’s take a step back. We want a list of easily callable actions on pieces of semantically classified buffer text… now where have we seen something like this before?
Avy + Embark: Any action, anywhere
Avy and Embark plug into each other like LEGO blocks. Here are a couple of examples:
Highlight occurrences
In this demo I highlight some keywords in a busy LaTeX document, then visit the bibliography entry of a citation key with Avy and Embark, without ever manually moving the cursor:
Play by play
avy-goto-char-timer
(or any other Avy jump command)avy-action-embark
with o
.embark-toggle-highlight
action with H
.avy-goto-char-timer
again.avy-action-embark
with o
.e
by the bibtex-actions package.
Run a skein through Emacs’ help systems
In this demo I explore my way through a package with Avy and Embark, threading help, apropos and customization buffers, again without manually moving the cursor.
Play by play
avy-goto-char-timer
.avy-action-embark
with o
.h
, which makes Embark run describe-symbol
on the match. This opens up a help buffer for the function. (Note: we bound a help command to Avy earlier, we could have used that.)C-M-v
(scroll-other-window
) to scroll the help buffer.avy-goto-char-timer
again.avy-action-embark
with o
.embark-cycle
to change the target from a file (named “project-x”) to a library (named “project-x”)h
, which makes Embark run finder-commentary
on the project-x library. This opens a buffer with some commentary.apropos-library
action with a
in Embark. This opens an apropos buffer.customize-variable
action with u
in Embark. This opens a customization buffer for the variable project-x-local-identifier
.
A division of responsibility
We save ourselves a lot of redundancy and reuse muscle memory here. Avy provides its unique means of filtering and Embark does what it does best, run actions! The intermediate job of candidate selection is shared between Avy and Embark: Avy specifies the general location of the candidate, and Embark figures out the semantic unit at that position on which to act. The fact that the Filter → Select → Act process is helpfully chunked this way by Avy makes the elisp required to integrate the two completely trivial5:
(defun avy-action-embark (pt)
(unwind-protect
(save-excursion
(goto-char pt)
(embark-act))
(select-window
(cdr (ring-ref avy-ring 0))))
t)
(setf (alist-get ?. avy-dispatch-alist) 'avy-action-embark)
Note that if you don’t like the candidate that Embark picks as the unit to act on, you can call embark-cycle
to cycle through the other targets.
All that, and we didn’t even move the point.6
Avy + Isearch: Seek, then jump
Isearch and Avy have different strengths: Avy jumps quickly to any visible element in any window, Isearch to any matching candidate in this buffer. Avy is faster when you want to cover some distance in a jump, Isearch when you’re moving a small distance or a very large one. Avy is useful when your eyes are already on a target, Isearch when you’re looking for one. But you don’t have you choose. You can handily combine the two by restricting Avy’s candidate pool to Isearch candidates: now you can start Isearch and finish with Avy:
(define-key isearch-mode-map (kbd "M-j") 'avy-isearch)
Again, consciously deciding which of the two commands to call every time is a bad idea. It’s not a bad idea to always Isearch and switch to Avy when necessary:
Play by play
C-s
. In the video I switched to isearch-regexp
with M-r
.C-s
, recentering the screen with C-l
if necessary.avy-goto-char-timer
. The candidate pool limits to the Isearch matches.
At least, that’s the usual pitch.
For us, however, “jump” in that description is replaced with “act”. We can act on any visible Isearch candidate with one of the above actions. Kill text between two isearch matches? Copy the previous line that contains a word to the current location? Check. Essentially we filter with Isearch and select and Act with Avy, indirectly decoupling Filter from the other two actions in Isearch.
When Avy is too clever
This usage pattern has a failure mode. When there’s a single match, Avy jumps to the location and does not offer any actions. Oops.
While it’s possible to force Avy to show a selection char/hint for a single match, the default DWIM behavior is usually desirable. There are two options:
-
Filter candidates conservatively, for example by typing in a single character. Using
avy-goto-char
oravy-goto-char-2
will almost always result in more than one match, preventing this problem. If you use one of the timer-based Avy commands, you can vary how much text to filter by on the fly. -
Carry out the action the old-fashioned way after jumping, then jump back by popping the mark Avy sets. You can do this with the default
set-mark-command
(C-u C-SPC
)7. You can do this for most commands that cause the point to jump, including Isearch. Vim users have the jumplist, accessed withC-o
, and the changelist, accessed withg;
.In this demo I jump twice with Avy to edit text and then chain jump my way back to where I started:
Play by play
- Call
avy-goto-char-timer
and jump to a candidate (or end up there by accident) - Make your edits (or not).
- Call
set-mark-command
with a prefix arg (C-u C-SPC
) to jump back. You can chain these jumps.
- Call
What’s the Point, anyway
This section is for the pedants.
I’ve been using “point” and “cursor” interchangeably in this article. Yes, I’m aware of the distinction.
One of the illustrated advantages of using Avy to filter and select text to run actions on is that you can do it without moving the cursor. But as the above code snippets make clear with their save-excursion
blocks, we do move the point, mostly just invisibly. The point is where the “gap” in Emacs’ gap-buffer data structure is located, so Emacs commands are all oriented around acting on the point, and usually more efficient when doing so.
Yes: it’s much faster to run an Avy action on some text in a different window than it is to call other-window
, then Isearch to the text, run an action and switch back. But to me, the Point (har) is primarily a useful abstraction for writing elisp. The real advantage of Avy is in how it lets me think about the contents of the entire frame in the powerful Filter → select → Act paradigm. The fact that you can do this without the mental context switch involved in expressly navigating around the window or frame is a bonus.
Remembering to Avy
In some ways, using Avy to jump around the screen is like using a mouse. You could make the case, quite successfully, that the mouse is faster here and thus preferable. This unfavorable comparison evaporates when you add your dispatch actions into the mix. Yanking a line from another window or running goto-definition on a symbol on the other edge of the screen is much faster with Avy than with the mouse selection/right-click business. And this is without taking into account the disruptive effect of frequent context switching, the reason to prefer all keyboard navigation (or all mouse, when possible) in the first place.
The smell test
However, using Avy actions is a new way of interacting with text on your screen even if you already use Avy to jump around. To remember to use Avy actions or find new ones, I look for “smells” in my day-to-day Emacs usage:
- Switching windows multiple times to land my cursor on some text
- Isearching through more than three matches to jump to the right one
- Moving the point a long distance to run a lookup command
- Activating the mark manually (
C-SPC
) all the time - Jumping to locations to delete single words
Buried in keymaps
The other sense of “remembering to Avy” is that piling a new abstraction onto simple text editing means you have to learn a new keymap. Emacs already has too many of those!
This is true. But the effort is greatly mitigated by a choice of keys that is sensible to you. In the above code snippets, I made choices that mimic my Emacs’ keybindings (which are close to the default ones) so I don’t have to remember anything new:
Action | Avy keybinding | Emacs keybinding | Emacs Default? |
---|---|---|---|
Kill | k , K (line) |
C-k |
Yes |
Copy | w , W (line) |
M-w |
Yes |
Yank | y , Y (line) |
C-y |
Yes |
Transpose | t , T (line) |
C-t , M-t etc |
Yes |
Zap | z |
M-z |
Yes |
Flyspell | ; |
C-; |
Yes |
Mark | m |
m in special buffers |
Yes |
Activate region | SPC |
C-SPC |
Yes |
Dictionary | = |
C-h = |
No |
Google search | C-= |
C-h C-= |
No |
Embark | o |
C-o |
No |
You can go beyond the mnemonic and simply reuse the same keybindings you use in regular editing, trading off a slightly longer key sequence for maximally reusing your muscle memory. If you’re an Embark user, you don’t even need the above keys, just one to call embark-act
.
The missing pieces
There are two common editing actions that still require manually moving the point elsewhere, perhaps to another window:
-
Searching or jumping to the contents of other windows beyond the confines of the screen. This has a simple solution:
Isearching in other windows
(defun isearch-forward-other-window (prefix) "Function to isearch-forward in other-window." (interactive "P") (unless (one-window-p) (save-excursion (let ((next (if prefix -1 1))) (other-window next) (isearch-forward) (other-window (- next)))))) (defun isearch-backward-other-window (prefix) "Function to isearch-backward in other-window." (interactive "P") (unless (one-window-p) (save-excursion (let ((next (if prefix 1 -1))) (other-window next) (isearch-backward) (other-window (- next)))))) (define-key global-map (kbd "C-M-s") 'isearch-forward-other-window) (define-key global-map (kbd "C-M-r") 'isearch-backward-other-window)
In keeping with
C-M-v
to scroll the other window, you can Isearch the other window withC-M-s
without switching to it8. If you’re feeling adventurous, replace(other-window next)
in the above functions with(ace-window)
.You can call Avy from Isearch as before, to run actions on essentially any text in the other-window’s buffer.
-
Copying regions of text. This has an Avy-based solution:
avy-copy-region
. I promised at the beginning, however, that you would only need to call one Avy command. For now you would do this the boring way, using Avy only to make shorter jumps or using Isearch+Avy. The more elispy solution will have to wait until part II of this series.
This post primarily concerned itself with the Act part as it connects with the ideas in the the previous one about ways to use Embark. But Avy is composed of modular pieces that makes it suitable for a wide variety of Filter → Select applications as well. In part II of this series, we will dig into the Avy API and see how to create unique commands.