Start here
Everyday use
- Moving
- Entering INSERT state
- Editing
- Selecting — objects, rectangles, banks, the mark run
- Wrapping a selection in a pair
- Pairing as you type — closing a delimiter for you
- The Enter key
- Search and replace
- Editing every match at once: =f=
- Typing characters your keyboard lacks
- Other languages
- Suppressed keys
Making it yours
- Your own keys — one key, the leader, taking a key away
- If a key is not doing what you want
- Modes where DONKEY steps aside
- Modes DONKEY supports rather than takes over —
hjkl, and the mode keeps the rest - The cursor
- The clipboard
- Configuring the Enter key
- Choosing your own pairs
- Pairing: every setting — per mode, per delimiter, and who does it
- Keys a major mode types with
- Why those two modes ship excluded — the reasoning, for readers who want it
- The digraph chart
- A complete configuration
When something is not right
Reference
DONKEY gives Emacs two states. In INSERT state Emacs is exactly the
Emacs you know. In NORMAL state the letter keys run editing commands
instead of typing letters — j moves down, d deletes, y copies.
C-g takes you from INSERT to NORMAL; i takes you back.
It is an addition, not a replacement. Nothing is taken away to make
room: every C-x and C-c sequence, M-x, C-h, isearch, the arrow
keys and every Meta binding work in both states exactly as they always
did. No DONKEY keymap binds a Meta key or the ESC prefix at all.
If you have used Vim or Helix, the letters will feel familiar. If you
have not, the only thing you must know is the C-g / i pair; the
rest you can learn a key at a time, and M-x donkey-tutor will walk
you through it in a buffer you can practise in.
INSERT state changes exactly one key: C-g returns to NORMAL state.
If a package has something open under the cursor when you press it — a
completion list, a snippet with fields, extra cursors — that package’s
own quit runs first, so the one press closes what was open and returns
you to NORMAL state.
NORMAL state differs in four ways, and only four:
- Letters run commands instead of typing.
- Digits are not counts —
C-u 3is, as in stock Emacs. RETdoes nothing in a buffer you are editing, and is handed back to the mode in buffers you are not.BACKSPACEandDELETEdo nothing.
Everything else falls through: C-a, C-e, C-k, C-w, M-w,
C-y, C-s, C-SPC, C-/, M-f, M-b, M-^, C-x …, C-c …,
M-x, C-h …, TAB, C-u, arrows, Home/End, PageUp/PageDown.
A test walks every key of DONKEY’s keymaps against a plain Emacs buffer, so a fifth difference cannot appear without somebody noticing, and another walks that list above and checks each key really does resolve to what it resolves to with DONKEY off.
A key one of your packages binds works as it always did too, unless NORMAL state binds that key as well — there it is NORMAL state’s, and the package’s key is waiting for you in INSERT state. If a Key Is Not Doing What You Want is the chapter for that, and it has three ways out.
Some buffers are not documents. In a terminal, a shell, or a language
REPL the keys belong to the program on the other end, so DONKEY stays
in INSERT passthrough there and NORMAL state cannot be reached at all.
The modeline says DONKEY[E] instead of DONKEY[I] so you can see it
without pressing anything. You can add your own modes to that list —
see Modes Where DONKEY Steps Aside.
DONKEY is one file with no dependencies beyond Emacs 29.1.
git clone https://github.com/yardquit/donkey ~/src/donkey
;; Where the clone lives. One place, read by both `:load-path' and
;; the `:init' block under it.
(defvar my-donkey-dir (expand-file-name "~/src/donkey"))
(use-package donkey
:load-path my-donkey-dir
:init
;; A package archive byte-compiles what it installs. A clone does
;; not, and nothing in DONKEY compiles itself, so compile it here --
;; and again whenever donkey.el turns out to be newer than the .elc
;; beside it, which is what a `git pull' leaves behind.
(let* ((src (expand-file-name "donkey.el" my-donkey-dir))
(elc (expand-file-name "donkey.elc" my-donkey-dir))
(recompiled nil))
(if (not (file-exists-p src))
(message "DONKEY: no donkey.el under %s -- check my-donkey-dir"
my-donkey-dir)
(when (file-newer-than-file-p src elc)
(require 'bytecomp)
(let ((start (current-time)))
(setq recompiled
(and (ignore-errors (byte-compile-file src))
(float-time (time-since start))))))
(let ((compiled (and (file-exists-p elc)
(not (file-newer-than-file-p src elc)))))
(load (if compiled elc src) nil t)
(message "DONKEY %s loaded %s%s"
(donkey-version)
(if compiled "compiled" "from source (interpreted)")
(if recompiled
(format ", recompiled in %.2fs" recompiled)
"")))))
:config
;; Whatever DONKEY does not bind is yours; see "Making it yours".
(keymap-set donkey-normal-mode-map "F" #'delete-other-windows)
(keymap-set donkey-leader-map "b" '("switch buffer" . switch-to-buffer))
(donkey-mode 1))
:load-path on its own would be enough to load DONKEY: use-package
ends with (require 'donkey), and require would find donkey.el
there. It would also run interpreted, and DONKEY works from
post-command-hook, so that cost is paid on every keystroke.
So :init picks the file instead of leaving the choice to require.
That is what the load call is for. Emacs prefers donkey.elc to
donkey.el whenever both are on the path, which makes a stale .elc
the copy require takes — and a later (load "donkey.el") cannot undo
it. Choosing explicitly sidesteps that, and by the time use-package
runs its own require, donkey is already in features and the
require does nothing.
Both halves read my-donkey-dir, so there is one path to edit and no
way for the two to drift apart. Pointing :load-path at one directory
and the compile step at another is the mistake this arrangement exists
to prevent: DONKEY would still load, from the :load-path copy and
interpreted, behind a warning about the other.
The echo area says which copy you got — loaded compiled, or loaded
from source (interpreted) — with the version, and the time the
recompile took when there was one.
If the compile fails, DONKEY loads from source and says so. That is
deliberate: a file that will not compile must not stop Emacs from
starting. It also means the reason is not shown, so run M-x
byte-compile-file on donkey.el when you want to see it.
One thing to know if you run the test suite over a clone: four tests
mock forward-char, forward-line and delete-region, which
byte-code open-codes, so delete donkey.elc before running them.
M-x donkey-mode toggles DONKEY everywhere. Turning it off clears
every trace of it from every buffer and gives you stock Emacs back.
M-x donkey-mode. The modeline showsDONKEY[N]: you are in NORMAL state.- Press
i. The modeline showsDONKEY[I]. Type a sentence — this is ordinary Emacs. - Press
C-g. Back toDONKEY[N]. h j k lmove left, down, up, right.wandbmove by words.vstarts a selection,jandlgrow it,ycopies it,ppastes.ddeletes the character under the cursor, or the selection if you have one.uundoes.C-x C-ssaves, as always.
That is enough to work. ? shows every key DONKEY binds, in a buffer
you can read.
M-x donkey-tutor opens a buffer you learn in by editing it, the way
M-x help-with-tutorial and vimtutor do.
Sixteen lessons, ending with the five that go beyond a single cursor — wrapping a selection, the characters the keyboard does not have, editing every match at once and choosing what it holds, and cursors in a column:
- Moving around (including
Sfor undoing a mis-keyed jump) - Counts
- Typing
- Deleting and changing
- Selecting things (words, sentences, delimiters, levels)
- Whole lines
- Copy and paste
- Banking, which is DONKEY’s own idea
- Columns (rectangles)
- Changing your mind (undo, and the exchange key)
- When two selections disagree
- Wrapping a selection
- Characters your keyboard does not have (digraphs)
- Editing every match at once (Split mode)
- Choosing what a split holds (selections, banks, regexps)
- Cursors in a column
The keys it names are your keys: the lessons are written with
substitute-command-keys escapes, so anyone who has rebound something
is taught the binding they actually have rather than the default.
Every key stands out in the face Emacs’s own help gives keys, the names
of the states in donkey-tutor-state, and the arrow before each line
to practice on in donkey-tutor-arrow; customize either face to change
its color.
Running M-x donkey-tutor again returns you to the buffer as you left
it. Kill the buffer to start over — nothing is written to disk.
| Key | Does |
|---|---|
h | left one character |
j | down one line |
k | up one line |
l | right one character |
w | forward one word |
b | back one word |
W | forward one balanced expression |
B | back one balanced expression |
g g | beginning of the buffer |
G | end of the buffer |
g e | end of the buffer (Helix’s spelling) |
g h | beginning of the line |
g l | end of the line |
: | go to a line by number |
z z | recentre the screen |
On a line too long for the window, j and k move by what you can
see: the next and previous screen row, keeping the column, as Emacs’s
own C-n and C-p do. J and K move by whole lines instead, to the
start of the next or previous line however far it wraps.
Inside a whole-line selection (V, below) J and K grow and shrink
it by a line. j and k move the cursor inside it, and the next J or
K squares it up to whole lines again.
Digits type digits in stock Emacs and DONKEY leaves that alone, so a
count is C-u and the number, exactly as everywhere else in Emacs:
C-u 5 j— down five linesC-u 3 d— delete three charactersC-u 10 w— forward ten words
C-u 0 j and a negative count do the sensible thing: zero is a bare
press, and a negative count reverses the direction where that means
something.
S walks back through the places you have been. DONKEY keeps a ring of
the last donkey-position-ring-max positions (10 by default); pressing
S repeatedly steps back through them, and any other command starts
the ring collecting again from where you are.
(setq donkey-position-ring-max 20)
| Key | Where you end up |
|---|---|
i | before the cursor |
a | after the cursor |
I | at the first non-blank character of the line |
A | at the end of the line |
o | on a new line below |
O | on a new line above |
c | replacing the selection, or the character at point |
C-g brings you back from any of them.
| Key | Does |
|---|---|
d | delete the selection, or the character under the cursor |
x | the same as d (Vim’s spelling; d is Helix’s) |
D | delete to the end of the line |
c | change: delete, then INSERT state |
y | copy the selection |
p | paste |
P | paste as a rectangle |
u | undo |
U | redo — including undo-tree’s own redo, where that is on |
C | comment or uncomment |
g j | join this line and the next |
> | indent the selection, or this line |
g q | fill the selection |
g Q | fill the paragraph |
. | repeat the last command |
% | select the whole buffer |
d is deliberately not an operator the way Vim’s is: there is no
d w or d d. Select first, or use D for the rest of the line.
Two smaller rules worth knowing, because they are choices rather than accidents:
- Deleting a single character does not go on the kill ring. Single characters are typo fixes, not cuts; the kill ring keeps what you deliberately cut.
- What a selection replaces is always recoverable — from the kill ring or from undo, and each command’s documentation says which.
v sets the mark and starts a selection that any motion extends. v
again re-anchors it where the cursor is; C-g cancels it.
V selects the line the cursor is on, and J and K grow it a line
at a time (see Long lines above); C-u 3 V selects three lines at once.
V again lets go.
The highlight stops at the end of the last line, but y, d and p
take that line’s newline as well, so lines go and come back whole. So
do Emacs’s own C-w, M-w and C-x r s, and anything else that asks
Emacs for the region’s text.
C-x C-x trades the selection’s ends, as o does in Vim: J and K
then move the end the cursor went to. C-SPC lets the line selection
go and starts a plain one at the cursor.
A line is the line the screen shows. A folded Org or outline heading
is one line, so V takes it with the body it hides and J and K
step over it in one press, the way m l banks it and Emacs’s
kill-whole-line kills it.
A command that reads the region’s two ends for itself sees it stop
before the last newline: M-x keep-lines leaves the last line out,
M-| hands its command the lines without their last newline, and
C-x n n and M-x append-to-buffer leave that newline behind. For
those, select with v from the start of the first line to the start of
the line after — g h v j j for two lines.
| Key | Selects |
|---|---|
m w | the word at point |
m W | the symbol at point |
m b | the word before point |
m B | the symbol before point |
m s | the sentence |
m S | the sentence before |
m p | the paragraph |
m P | the paragraph before |
m i | inside a pair — quotes, brackets, anything |
m a | the pair as well as what it holds |
m I | inside the balanced expression |
m A | the balanced expression as well |
m v | a rectangle |
m i and m a ask which pair: press the delimiter after them, and
either half will do — m i ( and m i ) mean the same thing. With
the cursor already on a delimiter they use that one without asking.
donkey-mark-inner=/=donkey-mark-outer support ( [ { < " ' ` ‘ ’ “ ” « » ‹ › = * ~ | \ / : + _ $
by default — 21 pairs, kept in donkey-mark-pair-delimiters. Where a
character both opens and closes, the same key does both jobs. Adding
your own is one line, and what m i can select, a key can wrap.
Press m w twice and you get two words, not the same word twice. The
same holds for m W, m s and m p, and it keeps going on each
further press until the buffer runs out — so m s m s m s is another
way to write C-u 3 m s.
The backward keys grow backward, and continue the same run: starting
on “that” in This buffer is for text that is not saved, m w m w m b
selects “text that is” — two words forward, one back.
Pressed fresh, with no run to continue, a backward key selects exactly what its forward partner would. The pair differ only in the direction further presses grow.
Objects mix freely, each press adding one object of its own kind at its
own end: m w m s grows a word selection forward to the end of its
sentence, and m s m b takes the sentence plus the word before it.
M is the m prefix held down for you. In a mark run the bare letters
w W b B s S do what their m-prefixed versions do, so M w b
selects what m w m w m b selects, and the run keeps growing with
every letter.
M arrives already holding a word — the word m w would have marked
— because that is what nearly every run starts from. So M d takes
the word under the cursor, M w is two words, M b is the word and
the one before it.
One sentence covers the whole design: =M= is =m w=, and each letter after it is one more =m=-prefixed press.
Inside a run:
| Keys | Do |
|---|---|
w W b B s S | mark and grow, as the m versions do |
m w, m p, m P … | still work, and continue the run — paragraphs are reached this way |
h j k l, g h, g l, g g, g e, J, K | continue a visible run by character, line and buffer |
u, U | step back and forward through the run’s history |
. | repeat |
* | swap which end of the selection grows |
p, P | stay the paste keys — M p replaces the marked word |
d, y, x, c | act on the run’s selection, as everywhere else |
v, V | refused: neither can take a selection the run owns |
M | end the run |
| anything else | a beep, and the run survives it |
Counts work: M C-u 3 w is four words, exactly as m w C-u 3 m w is.
A reminder in the echo area stays for as long as the run does. It names
only the keys whose subject the run changes — w moves by a word in
NORMAL state and marks one here, which nobody could guess — and leaves
out the keys that keep their meaning. The full list is above, in ?,
and in the tutor.
A run follows you between buffers and frames rather than dying when you leave; see A mark run follows you between buffers.
m v starts a rectangle. P pastes one back; over a V selection it
replaces the lines with the block’s rows, one row to a line.
Rectangles work with the wrap keys and with banking, below.
c on a rectangle empties the block on every row and puts a cursor on
each, in INSERT state, so what you type appears on every row at once —
the cursors of t, below. C-g comes back to the cursors and C-g
again ends them. A row too short to reach the block is padded out to
its column, and the block goes to the rectangle store, where P pastes
it back. A block one row high is a plain change.
A rectangle costs a motion the same however tall it is: only the rows a window could show are highlighted, and a middle-click in another program is answered with the rectangle when it asks, rather than Emacs copying every row after every key. What the middle-click pastes is unchanged: the rectangle as it stands, as it was left, or what a change took out of it.
Sometimes the lines you want are not next to each other. m l banks
the current selection — puts it aside, visibly highlighted — and you
can go and bank another somewhere else. The banked lines together are
what the next command acts on.
| Key | Does |
|---|---|
m l | bank the selection |
m u | take back the last bank |
m U | take back the whole run of banks |
m DEL | clear every bank |
Banked lines survive C-g: they are a store you put something in on
purpose, like the kill ring, and only the key that means discard
discards them. A line you open or paste beside a banked line is not
banked with it, and a change of major mode or a revert of the file
keeps the bank.
With a selection, press a delimiter and the selection is wrapped in it. Nineteen keys do this out of the box —
" $ ' ( ) * + / < = [ \ ] _ ` { | } ~
— and so do the curved and angled quotes « » ‘ ’ “ ” ‹ ›,
which you can reach with a digraph even if your keyboard cannot type
them. Either half of a pair does the same job: ) wraps in ( and )
just as ( does.
Press the same delimiter with the pair already around the selection and it comes off again. Which one you get is decided by what you selected: select what the pair holds and the pair comes off; select the pair as well and another goes on around it.
Characters your keyboard cannot reach are available too — see Typing characters your keyboard lacks — and rectangles can be wrapped line by line.
Nothing, in a buffer you can edit — the same as any other suppressed
key. In a buffer you cannot edit, the press goes back to the major
mode: the Org agenda keeps + and <, and a help buffer, an Occur
list and a compilation log keep <. A wrap key is borrowed only for
as long as a
selection lasts, and a read-only buffer is one where nothing could be
typed anyway, so both questions have the same answer.
A mode command that would type is refused even there.
- A read-only buffer, or read-only text where the pair would land, is refused before anything changes, and your selection is left standing.
- A count is ignored: one delimiter press, one wrap.
Press a delimiter on a selection that already sits inside that delimiter and the three engines part company:
| Your selection | DONKEY | electric-pair-mode | Smartparens |
|---|---|---|---|
word in say "word" now | say word now | say ""word"" now | say ""word"" now |
word in (setq x "word") | (setq x word) | (setq x ""word"") | (setq x "\"word\"") |
DONKEY takes the pair off — the press that puts a pair on is the press
that removes it, and nothing is escaped in any mode. Smartparens
escapes, but only inside a string, where the syntax needs it.
electric-pair-mode does neither: it nests, unescaped, and leaves you
with source that will not read.
So the two engines are not two ways of doing one thing. DONKEY’s own
wrap also keeps apart two things a pairing package ties together:
whether ' pairs while you type is your package’s business, and
whether ' wraps a selection is DONKEY’s. Turn the first off — many
people do in Lisp and Org, where ' and ` are quote characters —
and the second goes on working in every mode.
When you do want your package’s answer, including its escaping, M-x
donkey-toggle-wrap-engine is the short way: flip, press the delimiter,
flip back, without leaving NORMAL state.
DONKEY can close a delimiter for you while you type, so that pressing
{ gives you {} with the cursor between the halves. It is off, and
stays off unless you ask for it:
(donkey-pair-mode 1)
That is deliberate. INSERT state is otherwise entirely Emacs’s — C-g
is the only key DONKEY holds there — and a package that started
changing how you type on upgrade would be taking a liberty.
With it on, as you type:
- an opening delimiter writes its closing half and leaves the cursor between the two;
- the closing half, typed where that character already stands, steps over it rather than doubling it;
- in overwrite mode (the
<insert>key) nothing pairs: a delimiter replaces the character under the cursor, as it does in plain Emacs; DELbetween the halves of an empty pair takes both, andDELanywhere else, or in a buffer you cannot edit such as Dired, is whatever your major mode bound it to;- a count types and pairs nothing:
C-u 3 (gives you three of them.
The last two are DONKEY’s own answers. Where electric-pair-mode is
doing the pairing, DEL and counts are Emacs’s, and it pairs once
under a count rather than not at all. Pairing: every setting says
who does the job when, and what each one does differently.
They come out of donkey-mark-pair-delimiters, the same table m i
selects from and the wrap keys use. Add a pair there and you can type
it as well as select and wrap it:
(setopt donkey-mark-pair-delimiters
(cons '(?# . ?#) donkey-mark-pair-delimiters))
Now # closes itself. There is no second list to keep in step.
donkey-pair-delimiters decides how much of that table is used:
| Value | Means |
|---|---|
safe | the table, less the ordinary punctuation (default) |
all | every pair in the table |
| a list | exactly those characters |
nil | nothing pairs, with the mode still on |
;; the default: the table, less the ordinary punctuation
(setopt donkey-pair-delimiters 'safe)
;; everything the table knows, punctuation included
(setopt donkey-pair-delimiters 'all)
;; exactly these and nothing else
(setopt donkey-pair-delimiters '(?\( ?\[ ?\{ ?\"))
;; the mode on, and nothing pairing
(setopt donkey-pair-delimiters nil)
A list replaces the set rather than adding to it, so name every character you want, not only the extra one.
Here is the whole shipped table under safe, so you can see at a
glance what will close itself and what will not:
| Opener | safe | Why |
|---|---|---|
( [ { | yes | brackets, and nothing else |
" | yes | the string quote |
‘ “ | yes | typographic quotes, typed on purpose |
« ‹ | yes | guillemets, typed on purpose |
< | no | less-than, far more often |
' | no | the apostrophe in “don’t” |
` | no | the quote in Lisp |
= * ~ | \ / : + _ $ | no | ordinary punctuation |
Any pair you add to donkey-mark-pair-delimiters yourself is in: the
list above is what SHIPS, and safe only ever removes from it. The
# in the worked configuration below pairs for exactly that reason.
Under all the bottom row comes back, so a colon typed in prose gives
you two of them and an apostrophe fires in the middle of “don’t”.
You do not have to choose, and you do not have to configure anything. DONKEY works out who should do the job.
| What is on | Who does the pairing |
|---|---|
| neither | DONKEY |
electric-pair-mode | Emacs, as it did before |
smartparens-mode | Smartparens |
Where Emacs is doing it, DONKEY hands Emacs the delimiters, so a pair you added to the table is one Emacs pairs too. Where Smartparens is on, DONKEY stays out of the way entirely.
To see who has the job in the buffer you are in, run M-x
donkey-debug-platform and read its pairing line.
That is the whole of everyday use. Pairing: every setting has the
rest: holding one character back from safe, turning one on or off
in a single major mode, and what each package does differently.
In a buffer you are editing — code, prose, org — RET does nothing in
NORMAL state. That is deliberate: NORMAL state does not type, and
RET is the last key that should put a newline in a buffer you are
reading.
In a buffer you are not editing, RET is handed back to the mode:
org-agenda-switch-to in the Org agenda, compile-goto-error in a
compilation log, man-follow in a man page. In Org and Markdown it
follows the link at point, toggles the checkbox, or flips the TODO —
see Enter Rules to change or extend that.
(In Dired, Magit, Info and the other modes DONKEY steps aside from,
RET is the mode’s for a simpler reason: DONKEY is not there at all.
See Modes Where DONKEY Steps Aside.)
| Key | Runs |
|---|---|
r r | replace-regexp |
r q | query-replace |
isearch is untouched: C-s and C-r work in both states, as does
everything else Emacs binds to a control or meta key.
f (donkey-split) asks for a regexp and holds every match in the
selection. It changes nothing by itself — it selects, and then waits
for you to say what to do:
| Key | What it does |
|---|---|
i | type before every match |
a | type after every match |
c | empty every match, then type |
d | delete every match |
w, or a pair | wrap every match, or take the pair off |
C-g | end it |
A cursor is drawn at every match, in the shape and place of the real
one, so you can see where each of them is. While you type, what you
write appears at every match at once.
Backspace, retype, keep going: it is ordinary INSERT state, and C-g
ends it keeping what you typed, exactly as C-g does anywhere else.
There is no limit on how many matches a split holds. Past a couple of
thousand, a keystroke writes the matches on screen at once and the
rest as fast as they can be written, between your keystrokes; scroll
and what you scroll onto is written first, and C-g waits for the
last of them, as saving the file does. Once the split has ended, one u takes back everything
it wrote, at every match, and U puts it back; what c, d or a
wrap did is one undo step too.
A wrap leaves the split standing, so pairs nest and a verb can still
follow — f ( [ a wraps twice and then types after. The other
verbs end it.
What c and d remove reaches the kill ring as one kill: a single
copy when the matches agree, so p pastes what was there rather than a
column of copies, and every match on a line of its own when they
differ.
| Selection | Searched |
|---|---|
| none | the current line |
v, V, % | the selection |
m v | inside the block |
C-u then f | with m v, each row’s whole line |
banked (m l) | the banked lines, plus a live v |
any, %foo | the whole buffer, the bank left alone |
There is no whole-buffer default on purpose: press % first and the
buffer is a selection like any other, or type the regexp with % in
front — f %foo RET — and the whole buffer is searched whatever is
selected. Either way you reach it by choosing it. The % is not part
of the search; \% or [%] at the start searches for a percent sign.
A match hidden from view — inside a folded Org subtree or outline
heading, or in any other invisible text — is left out, so nothing is
written where you cannot see it, and the reminder says how many were
left out. Folded text is never changed by f, whatever
search-invisible says. To change it, use r r (replace-regexp) or
r q (query-replace); for r q, unfold first, so you can see each
match you answer y or n to.
Banked lines are how the split reaches lines that are not next to each
other. Each run of banked lines is searched up to the end of its last
line and no further, so ^ and $ stay on the lines you banked. A v
selection made after banking is searched with the bank, exactly as
selected; the line the cursor happens to be on is not added. Opening
the split spends the bank, as y and d do, so C-g on the split
ends it without bringing the bank back; if nothing matches, the bank
stays. A live m v block is searched instead of the bank and leaves it
alone.
Two things follow from searching inside a block. A match to the right
of the block is not found — widen with C-u if you want the rows
rather than the columns. And $ and ^ match a real line end and line
start, so in a block that stops mid-line they match nothing; with v
or with no selection they work as you would expect.
The matches do not have to agree. [0-9]+ over 1, 22 and 333
holds three different texts: i and a then type at the start or the
end of each, so what you type is added and none of them is replaced;
c empties every one before you type; d deletes them.
Case is ignored the way r r (replace-regexp) ignores it: a regexp
with no capital letter matches any case, so todo finds TODO, Todo
and todo, and one with a capital is exact. case-fold-search and
search-upper-case decide it, as they do for every search in Emacs.
It is Emacs’s own regexp, the one r r, r q, C-M-s and
M-x occur read, and five spellings differ from what other tools use:
\s-is whitespace;\s+here matches nothing at all.\S-is anything but whitespace;\S+here matches every character.[0-9]is a digit;\dhere is the letterd.\(a\|b\)isaorb; plain parentheses match themselves.x\{3\}isxxx; plain braces match themselves.
M-x re-builder highlights what a regexp matches as you type it.
Align three assignments, then end each line:
v j j g l f = RET c := v j j g l f $ RET a ;
Comment out a region, by holding the start of every line:
v j j j f ^ RET i # SPC
Matches that touch are refused — a over aaa, or a single space over
two — because text typed where two of them meet would belong to both.
An empty regexp is refused too.
Where the matches differ, the split types only at their edges: moving into one ends it, since there is no one text to edit in all of them. Editing inside matches that differ is what a multiple-cursors package is for; this is not one.
A deletion just past a match is made at every match. Backspace and
C-d, with a count or without, take as many characters at each, and
so does typing in overwrite mode, which writes over the character
after every match alike; a command that deletes as far as the text goes — M-d, C-k, M-\ —
does so only where the same text stands past every match. Where it
does not, the split ends and says so, and the other matches keep what
they hold.
A split belongs to the buffer it was made in. Text changed where it
cannot follow — from an indirect buffer sharing the text, such as one
C-x 4 c or Org makes, or by a program that turns the change hooks
off — ends the writing and says so. Nothing is written over that
change; what was typed stays, and u then takes back each edit as
Emacs recorded it rather than every match at once. A clone made while
a split is live does not carry the split with it.
t (donkey-split-add-cursor) puts a second cursor on the line below,
at the same column; each t after adds one more below the last, and a
count adds that many. T (donkey-split-add-cursor-above) adds above
instead, and DEL takes back the cursor added last. A line shorter
than the column gets its cursor at its end, and a line hidden in a
fold gets none: the line below is the one j goes to. When a motion
leaves the cursors in different columns, = lines them up under the
real one again; with selections it wraps them, as any delimiter does.
With lines selected, t puts a cursor on every one of them at once:
V j j j j t gives five cursors, each at its line’s start, and a v
selection or an m v block gives each line a cursor at point’s column.
The real cursor stays on point’s line, or goes to the last line
selected where the selection stops at the start of point’s line: g h
v j j t is two lines and two cursors. The other cursors are drawn in
the real cursor’s shape, a box or a bar as the state changes, and
always in the text’s own color rather than the real cursor’s; a
background given to the face donkey-split-cursor-face is used
instead. Every cursor then does what the real one does:
| Key | At every cursor |
|---|---|
h l w b W B g h g l | move |
v then a motion | select from where the cursor was |
V | select the cursor’s line |
m w, m s, m i, and the other m | select that object |
i a I A c | type; C-g comes back to the cursors |
d x, D, y, p | delete, kill, copy, paste |
| a pair | wrap each selection, as in Normal |
M, then w b s h l g h | a mark run at every cursor |
t T, DEL | add a cursor below, above; drop one |
= | line the cursors up under the real one |
m l, m u | bank or unbank each cursor’s line |
M-u M-l M-c | case of each selection, or the word |
o O | open a line at each cursor and type |
> C | indent, comment each cursor’s line |
. | the cursors’ last command, again |
u U | undo, redo — the cursors stay |
f | search each cursor’s selection or line |
C-g | let go of the selections, then end |
With nothing selected, a types after the character under each
cursor and i before it, as they do with one cursor; a cursor at its
line’s end types there rather than on the next line. With a selection,
a types after the whole of it.
d, D, y and p keep the cursors. What they take reaches the kill
ring as one kill, one line per cursor, and p gives each cursor its
own line back when the kill has as many lines as there are cursors —
so m w y, a move, and p copies each cursor’s word to where that
cursor went. Whole lines taken with V are the lines as V y at one
cursor takes them, and V p replaces each cursor’s line the way it
replaces one. A key m i would ask for is asked once.
M at the cursors is the mark run at every cursor: each selects its
word, or keeps what it had selected, and the run’s keys grow and
adjust every selection at once — M w d takes two words from every
line, and u and U step every cursor’s selection back and forward
as they step one. The run’s keys that would leave the line, j k J
K g g g e, beep and keep the run. Any other key ends
the run, and a key the cursors answer does its job at every cursor.
The case keys are Emacs’s own, and a key you gave upcase-region or
downcase-region in Normal state works at the cursors too. An edit at
the cursors is one change: u takes it back everywhere at once, and
an edit that fails at one cursor is taken back at all of them, the
cursors and their selections left as they were. z z
and C-l move the view and keep the cursors.
Every key but the ones that type runs once at every cursor: a motion
or a selection takes about ten microseconds a cursor, so ten thousand
cursors answer in a tenth of a second and a hundred thousand in a
second or two. Typing is written at the cursors as it is at the
matches of f, however many. t refuses to make more than
donkey-split-cursor-limit (10000), and says so; set it higher, or to
nil for no limit, or set donkey-split-cursor-limit-ask to be asked
instead. c on a rectangle taller than the limit asks for its text in
the minibuffer and writes it on every row at once. f has no limit.
What a key changes at the cursors is one undo step, whatever their
number: u takes it back at every cursor and U puts it back.
Three limits keep the cursors out of each other’s way. A cursor never
leaves its own line: a motion that would cross stops at the line’s
edge. Cursors that come to share a line — after V d, for one — become
one. And a command that fails at one cursor is undone at all of them.
Any key not in the table, j and k included, ends the cursors and
does its ordinary job.
Every step a split takes — a wrap, a deletion, the writing between i
and C-g, a key at the cursors — is one undo entry, and u takes it
back everywhere at once. The entry holds a position for every place
and the text that changed, so a step is about 8 bytes a place plus
the text: 100 KB at ten thousand places, 0.8 MB at a hundred
thousand. Emacs keeps history up to undo-limit, 160 KB by default,
and always keeps the newest step whole, so at those sizes the history
is a step or two. DONKEY counts its entries at their true size, where
Emacs’s own count would take each for 32 bytes and let the history
grow until a garbage collection threw all of it away at once.
To keep more, raise the limits. The cost is the memory the kept
records take, and a garbage collection that is longer by about a
quarter of a millisecond per megabyte kept — some 25 ms more with a
hundred megabytes of history; typing, the split’s keys and undo itself
are no slower. undo-strong-limit must stay above undo-limit, and a
single step larger than undo-outer-limit (24 MB by default) is not
recorded at all, and the split says so.
(setopt undo-limit (* 100 1024 1024) ; history kept, per buffer
undo-strong-limit (* 120 1024 1024) ; must stay above undo-limit
undo-outer-limit (* 200 1024 1024)) ; the largest single step recordedSPC i & types one character from a two-key digraph — SPC i & a '
gives á, SPC i & - > gives →. With a selection, the digraph wraps it.
For more than one, turn the input method on with SPC i . and type
& followed by the two keys as you go; SPC i - turns it off again.
M-x donkey-digraph opens a chart of every digraph, sorted with the
twelve most useful first.
donkey-input-methods puts your own input methods under SPC i:
(setq donkey-input-methods
'(("s" "swedish" "swedish-postfix")
("n" "norwegian" "norwegian-postfix")
("p" "polish" "polish-slash")))
That gives you SPC i s, SPC i n and SPC i p. DONKEY’s own
entries under SPC i sit on symbols — &, . and - — precisely so
that every letter stays free for a language of yours.
Emacs’s own way in, C-x RET C-\ and M-x set-input-method, works
exactly as it always did.
These do nothing in NORMAL state, on purpose:
| Key | Why |
|---|---|
BACKSPACE, DELETE (all four key names) | silenced — pressed from habit, and a beep on every stray press is noise nobody can act on |
, - ; | refused with “is undefined”: a punctuation key pressed in NORMAL state is a question, and this answers it |
They are bound to something harmless rather than left unbound, and that is the point: an unbound key falls through to the major mode, and some major modes type with punctuation. The rest of the punctuation is not on this list because it wraps — see Wrapping a selection.
NORMAL state answers for every key from ! to ~. One DONKEY does not
bind, and has not lent to a wrap pair, is refused with “is undefined”
rather than reaching the major mode.
That is what lets a key mean one thing everywhere. Thirty-two printable
keys used to fall through, and what they did depended on the mode: the
suppression is a remap of self-insert-command and the commands like
it, which catches a mode that types with a NAMED command and cannot
catch one that does not. idlwave-mode binds & to an anonymous
command that inserts an ampersand, and & typed into a buffer you were
navigating, whatever that list said.
The two rows above are listed separately because those keys are bound
in DONKEY’s own keymap. The rest are answered from underneath it, which
is why ? does not list them: they are not keys DONKEY chose to do
something with, they are what is left.
Everything below goes in your init file. DONKEY has to be loaded first,
so wrap anything that touches its keymaps or options in
with-eval-after-load, or put it in a use-package :config block.
(with-eval-after-load 'donkey
(keymap-set donkey-normal-mode-map "F" #'delete-other-windows))
That key is yours in every NORMAL-state buffer.
Rather than spending single letters, put your keys under SPC. Write
each as ("name" . command) and the name shows up in ? and in
which-key, with nothing depending on which-key being installed:
(with-eval-after-load 'donkey
(keymap-set donkey-leader-map "b" '("switch buffer" . switch-to-buffer))
(keymap-set donkey-leader-map "f" '("find file" . find-file))
(keymap-set donkey-leader-map "g" '("magit" . magit-status))
;; a prefix of your own, with its own leaves
(keymap-set donkey-leader-map "w h" '("window left" . windmove-left))
(keymap-set donkey-leader-map "w l" '("window right" . windmove-right)))
SPC b then runs switch-to-buffer, and ? shows the row:
SPC b switch-to-buffer switch buffer
Every letter under SPC is yours. DONKEY keeps only symbols there —
SPC i for input methods — so nothing of yours can collide with
something added later.
A prefix with no name shows as +prefix in the which-key popup, which
tells a reader nothing. Name it the same way a leaf is named — by
binding its keymap as ("name" . keymap).
Either define the prefix whole and name it as you bind it:
(defvar my-open-map
(define-keymap
"g" '("magit" . magit-status)
"m" '("ement" . ement-connect)))
(with-eval-after-load 'donkey
(keymap-set donkey-leader-map "o" (cons "open/apps" my-open-map)))
— or let keymap-set make the prefix for you, and put a name on it
afterwards:
(with-eval-after-load 'donkey
(keymap-set donkey-leader-map "t l" #'display-line-numbers-mode)
(keymap-set donkey-leader-map "t w" #'whitespace-mode)
(keymap-set donkey-leader-map "t"
(cons "toggle" (keymap-lookup donkey-leader-map "t"))))
Either way the popup then shows +open/apps and +toggle where it
would otherwise show +prefix. Keys added under the prefix afterwards
keep the name, and naming it a second time renames it. The name
travels in the binding rather than in another package’s table, which
is why none of this needs which-key installed: DONKEY’s own SPC i is
named this way and shows as +input-method.
To name a prefix you did not bind — one belonging to another package — which-key’s own table is the way, and it is the only way:
(which-key-add-key-based-replacements
"SPC a" "org/agenda"
"SPC d" "denote")
Name a prefix both ways and the table wins: it rewrites the name the
keymap offered, so the one in the binding is the one you stop seeing.
Nothing warns you, which is reason enough to pick one place and keep
every prefix there. DONKEY’s own SPC i is no exception —
(with-eval-after-load 'donkey
(which-key-add-key-based-replacements "SPC i" "input/information"))
— renames it in the popup, and the keys under it go on doing what they did.
Whichever way you set it, a prefix name is for the popup. The ?
chart lists every key under a prefix, each with its own name, and
gives the prefix itself no row.
That is a deliberate silence rather than a gap. ? reads the keymap,
so the only name it could ever show is one bound into it — and a chart
that named the prefixes you bound while saying nothing about the ones
you named in which-key’s table would be telling half the story, in a
way that reads as a bug rather than a rule. The popup has all of them.
A key in NORMAL state is DONKEY’s, one you bound, or nothing: whatever the major mode puts on a key, NORMAL state never lets it through, so a key pressed by mistake does nothing you did not ask for. Take one of DONKEY’s keys away and it does nothing:
(with-eval-after-load 'donkey
(keymap-unset donkey-normal-mode-map "x")) ; x does nothing now
With or without keymap-unset’s REMOVE argument, the key is refused
rather than handed to the buffer. That holds for every printable key,
SPC, RET and the delete keys, the keys NORMAL state answers for.
The rest behave as they always did: C-x, C-c, M-x, the arrows
and TAB reach Emacs and the major mode, which is how TAB folds a
headline in Org.
To give a key a command, bind the command, and it is a key you chose. For one mode only, remap DONKEY’s command in that mode’s own map; If a Key Is Not Doing What You Want shows how.
NORMAL state gives the letters to DONKEY. Most of the time that is the point, and now and then it takes a key you wanted for something else.
First: it still works in INSERT state. Press i and the key is the
package’s again, exactly as it was.
If you want it in NORMAL state too, there are three ways, in order of how much they change.
Say you use org-noter, and in its document window you would rather
i wrote a note than started INSERT state:
(with-eval-after-load 'org-noter
(define-key org-noter-doc-mode-map
[remap donkey-insert-here] #'org-noter-insert-note))
Now i writes a note in an org-noter document buffer and is INSERT
state everywhere else. You do not have to remember which buffer you
are in — the mode does.
The pattern is always the same: take the DONKEY command you want out
of the way, wrap it in [remap ...], bind that in the mode’s own map.
M-x donkey-check-bindings lists it afterwards as a key that mode
remaps, which is its way of saying “somebody meant this”.
Three things it cannot do:
- It moves a command, not a key.
dandxare bothdonkey-delete, so remapping that moves both; every wrap key isdonkey-wrap-region, so remapping that moves all nineteen. - It cannot give a key back to a prefix — a key that opens a further
menu of keys, as AUCTeX’s
`does. - It needs a command to name. “Whatever that mode would have done” is not something you can write here.
For those, exclude the mode.
See Modes Where DONKEY Steps Aside, just below. One line, and the mode has all of its keys back.
If it is your key rather than the mode’s, bind it in
donkey-normal-mode-map as above — that wins everywhere.
Dired has its own alphabet. So does Ibuffer, Magit, Info. Those keys are the whole of what the mode is, and a modal editor that lands on top of them has taken away the thing you opened the buffer to use.
The old answer was to exclude the mode, which gave the keys back but
took hjkl with them. A support mode is the middle: DONKEY keeps
h, j, k and l, and the mode keeps everything else.
DONKEY[S] h j k l are DONKEY's
a b c … are the mode's, all of them
Every buffer a program made for you is a support mode. DONKEY does
not need to be told which: a mode derived from special-mode — Emacs’s
own word for it — or a read-only buffer, and not a mode you write in.
That last clause matters: a source file opened read-only is still a
file you read as a writer, and DONKEY keeps its keys there.
What a section adds is what h and l mean where the mode has
somewhere to go. Forty-one sections ship; twenty-six of them give h
or l a meaning:
| Mode | h | l |
|---|---|---|
dired-mode | up a directory | open the file or enter the directory |
ibuffer-mode | — | visit the buffer |
speedbar-mode | up a directory | open the file or enter the directory |
Man-mode | previous section | next section |
woman-mode | — | follow |
apropos-mode | — | follow |
image-mode | previous file | next file |
doc-view-mode | previous page | next page |
tar-mode | — | extract the entry |
archive-mode | — | extract the entry |
Custom-mode | up to the parent group | — |
occur-mode | — | go to the occurrence |
compilation-mode | — | go to the error (covers grep-mode) |
emacs-lisp-compilation-mode | — | go to the error |
package-menu-mode | — | describe the package |
Buffer-menu-mode | — | visit the buffer |
org-agenda-mode | a week earlier | a week later |
bookmark-bmenu-mode | — | jump to the bookmark |
vc-dir-mode | — | visit the file |
proced-mode | — | refine by the field at point |
profiler-report-mode | — | expand or collapse the entry |
xref--xref-buffer-mode | — | go to the reference |
finder-mode | — | select |
flymake-diagnostics-buffer-mode | — | go to the diagnostic |
ebrowse-member-mode | — | go to the member definition |
calendar-mode | a day earlier | a day later |
Where no section says otherwise, h and l are backward-char and
forward-char, and j and k always move by a line.
Two of them are there only because the rule misses them: Custom-mode
and org-agenda-mode are neither derived from special-mode nor
read-only, and a section makes a mode a support mode whatever the rule
thinks.
Info-mode is not in the table. It has an alphabet of its own that
hjkl cannot be fitted around without taking something a reader needs,
so it is excluded instead and DONKEY holds no key there at all.
SPC is the leader in a support mode exactly as it is in NORMAL state,
and it is the same keymap: whatever you hang under SPC reaches
Dired, help, Man and the rest without being named a second time.
(with-eval-after-load 'donkey
(define-key (lookup-key donkey-normal-mode-map " ") "b"
#'switch-to-buffer))
That SPC b now works in every buffer, whichever state it is in.
What the mode had on SPC is not carried anywhere, and almost nothing
is lost by that. Twenty-five of the forty-one put scroll-up-command
on SPC and scroll-down-command on S-SPC; C-v and M-v are
those two commands in every Emacs buffer, and S-SPC is left alone, so
page up still works where it always did. image-mode and
doc-view-mode are counted among them — the pair is their own rather
than Emacs’s, but C-v and M-v still do it. Eight put a line motion
on SPC, which is what j does. so-long-mode binds it to nothing
but a space.
Three more put a command that already has another key: speedbar-mode
toggles a tree node, which +, = and - also do; finder-mode put
finder-select, which its own section puts on l; and calendar-mode
put scroll-other-window, which is C-M-v.
That leaves four modes losing something with no obvious home:
org-agenda-mode, where SPC showed the entry and scrolled it;
flymake-diagnostics-buffer-mode, where it showed the diagnostic
without leaving the list; and ebrowse-tree-mode and
ebrowse-member-mode, where it viewed the class or the member without
leaving the tree. A section takes SPC back for a mode that needs it:
(with-eval-after-load 'donkey
(setopt donkey-support-modes
(cons '(org-agenda-mode (?\s . org-agenda-show-and-scroll-up)
(?h . org-agenda-earlier)
(?l . org-agenda-later))
donkey-support-modes)))
h and l are written out again because this section replaces the
shipped one rather than adding to it, and a section answers with
nothing from any later one. Leaving them out would take the agenda’s
week-earlier and week-later keys away while giving SPC back.
Where h, j, k or l displaces something the mode had, the section
usually gives it the shift of the key that took it:
| Mode | Key | Runs |
|---|---|---|
dired-mode | J K | go to file, kill lines |
ibuffer-mode | J K | jump to buffer, kill lines |
speedbar-mode | K | kill the buffer on the line |
help-mode | H L R | back, forward, revert |
eww-mode | L | back a page |
dictionary-mode | H L | previous, match words |
shortdoc-mode | C | copy the function as kill |
log-view-mode | T | toggle the mark on the entry |
bookmark-bmenu-mode | L | load a bookmark file |
ert-results-mode | J | jump between summary and result |
calendar-mode | H | the holidays here |
Nineteen sections name the prose package instead of pairs:
Man-mode, woman-mode, help-mode, apropos-mode, shortdoc-mode,
dictionary-mode, messages-buffer-mode, debugger-mode,
vc-annotate-mode, log-view-mode, emacs-authors-mode,
tags-table-mode, url-cookie-mode, ebrowse-tree-mode,
so-long-mode, ebrowse-member-mode, compilation-mode and
emacs-lisp-compilation-mode, and DONKEY’s own bindings chart.
A package is a named list of sequences a section can ask for in one
word, and prose is the reading half of NORMAL state: the motions and
word keys, the g jumps and G, the selection and mark-run keys, y
to copy, and ?. These are buffers full of text to read and copy with
links to follow, and none of it types, so the mode loses nothing it was
using.
One key it does cost. prose puts g g, g e, g h and g l under
g, which makes g a prefix — and g is revert-buffer or
recompile in most of these buffers. Where that matters the section
carries it on R: R reverts in help-mode and the four reading
modes, and recompiles in a compilation buffer. so-long-mode binds
nothing of its own, so it carries nothing.
A compilation buffer takes the package because there is little else in
it: n and p still walk the errors, l and RET still go to one,
and only g moved. grep-mode and emacs-lisp-compilation-mode are
the same buffer with a different producer — the elisp one is written
before compilation-mode in the table, because a section answers with
nothing from any later one and it has to carry its own l.
Enter is the exception a package makes: it is never taken from a mode that has its own use for it, because in a buffer a program made for you that is the action key. Where the mode has nothing on Enter, the package takes it like any other key.
In Dired that means j and k walk the listing, l opens what is
under the cursor and h goes back up — and d, x, m, g, w,
q and the other forty are Dired’s, exactly as they are without
DONKEY.
Four keys are taken, so four commands lose their key. Two of them were never really lost:
| Mode | Key | Was | Still reachable |
|---|---|---|---|
dired-mode | h | describe-mode | yes — C-h m |
dired-mode | l | dired-do-redisplay | ≈ g, which reverts |
ibuffer-mode | h | (unbound) | nothing was there |
ibuffer-mode | l | ibuffer-redisplay | g, which is the same command |
The other four had no second key, so the section gives them one — the shifted form of the key that took them:
| Mode | Key | Runs |
|---|---|---|
dired-mode | J | dired-goto-file |
dired-mode | K | dired-do-kill-lines |
ibuffer-mode | J | ibuffer-jump-to-buffer |
ibuffer-mode | K | ibuffer-do-kill-lines |
J and K were unbound in both modes, so nothing was displaced in
turn. This is the section doing it, not DONKEY editing the mode’s
keymap: what DONKEY does in a buffer is all in one table you can read.
Not by care — by measurement. Across every prog-mode, text-mode
and conf-mode derivative in a stock Emacs, and again across a
configuration with the usual language and markup packages installed,
exactly one binds a plain letter: org-mode, which binds all
fifty-two to org-self-insert-command.
That is typing, and typing is what INSERT state is for. So there is no
command to relocate in a writing mode, because none of them put one on
a letter to begin with — they use C-c and the modifiers. The promise
that DONKEY owns the letters where you write costs those modes
nothing.
One thing is decided by a rule: is this a buffer a program made for
me? Emacs answers it — special-mode or read-only, and not a writing
mode — and DONKEY takes the answer.
Nothing else is. Which keys DONKEY holds is fixed at h j k l; what
h and l run is read off a section, never worked out from the
mode’s keymap; and every other key is the mode’s because no section
named it. Nothing changes when a mode gains a binding next year.
donkey-support-mode-exceptions is where the rule is wrong.
occur-edit-mode is the case it ships for: it derives from
occur-mode, so it looks like a program’s buffer, but it is the mode
e puts an Occur buffer in so the matches can be edited — a buffer you
type in, where NORMAL state belongs.
Adding a section is how you change what h and l do:
(with-eval-after-load 'donkey
(setopt donkey-support-modes
(append '((pdf-view-mode (?h . pdf-view-previous-page)
(?l . pdf-view-next-page))
(elfeed-search-mode (?l . elfeed-search-show-entry)))
donkey-support-modes)))
j and k are never written down — they are DONKEY’s in every
support mode, and a section that names one is ignored. Where a section
does not name h or l they are backward-char and forward-char.
A mode matches by derivation as well as by name, and the first section it matches answers, so list a specific mode before the general one. A command you do not have is passed over rather than bound.
The support map is an emulation map, which Emacs consults before a
major mode’s own keymap. That is what makes the four motion keys
reliable — and it means a binding you put in the mode’s map with
:bind (:map dired-mode-map ...) is silently ignored on any key DONKEY
holds. In Dired those are h, j, k, l, SPC, J and K;
everything else reaches your binding as normal.
There are three levers, smallest first. Each is shown twice — in DONKEY’s block and in the affected package’s — because a mode’s settings are usually easiest to find where the rest of that mode’s configuration already lives.
Any key but j and k, including SPC. In DONKEY’s :config, beside
the (donkey-mode 1) from Installing:
(setopt donkey-support-modes
(cons '(dired-mode (?h . dired-up-directory)
(?l . dired-open-file)
(?y . dired-copy-filename-as-kill)
(?J . dired-goto-file)
(?K . dired-do-kill-lines))
donkey-support-modes))
or in Dired’s own block:
(use-package dired
:config
(with-eval-after-load 'donkey
(setopt donkey-support-modes
(cons '(dired-mode (?h . dired-up-directory)
(?l . dired-open-file)
(?y . dired-copy-filename-as-kill)
(?J . dired-goto-file)
(?K . dired-do-kill-lines))
donkey-support-modes))))
cons, never a literal list: setopt with a list of your own replaces
the table and takes the other forty sections with it. Yours is written
before the shipped entry, so it is the one that answers.
And because it is the one that answers, it answers with nothing from
the shipped one — so carry every pair you still want. That is why J
and K are written out above: Dired’s section puts dired-goto-file
and dired-do-kill-lines there, and a replacement that left them out
would take away the only keys those two have.
j and k are the floor and no section can take them — but a remap of
the command they run is consulted even from the mode’s own map, which
is how j reaches dired-next-line in the first place. This is the
one lever that names no DONKEY symbol, so it is safe anywhere and reads
most naturally in Dired’s block:
(use-package dired
:bind (:map dired-mode-map
("<remap> <next-line>" . my-down)
("<remap> <previous-line>" . my-up)
("<remap> <dired-find-file>" . dired-open-file)))
The same thing written in DONKEY’s :config instead:
(with-eval-after-load 'dired
(keymap-set dired-mode-map "<remap> <next-line>" #'my-down)
(keymap-set dired-mode-map "<remap> <previous-line>" #'my-up)
(keymap-set dired-mode-map "<remap> <dired-find-file>" #'dired-open-file))
Three things to know about a remap:
- Remap the command, not the key. The key stays DONKEY’s, which is
why
C-h kstill tells the truth about it. - Remap the command DONKEY’s map holds, not the one the mode already
reached.
jisnext-linethere, and Dired’s own remap turns that intodired-next-line; a remap ofdired-next-linewould be a second hop, and Emacs does not take it. - A remap follows the command everywhere it is reachable. Remapping
dired-find-filemovesl,RETandetogether. That is usually what you want — it is howdired-openworks — but it is not a way to change one key.
Where no single key is the problem — you know Ibuffer’s own keys and
want all of them — put the mode on donkey-excluded-modes. In DONKEY’s
:config:
(setopt donkey-excluded-modes
(cons 'ibuffer-mode donkey-excluded-modes))
or in Ibuffer’s own block:
(use-package ibuffer
:config
;; with-eval-after-load matters here and did not in the remap
;; above: if Ibuffer loads before DONKEY, the option is a void
;; variable, use-package swallows the error, and the mode stays
;; =[S]= with nothing said about it.
(with-eval-after-load 'donkey
(setopt donkey-excluded-modes
(cons 'ibuffer-mode donkey-excluded-modes))))
The modeline then reads DONKEY[E], and every Ibuffer key is
Ibuffer’s again — j is ibuffer-jump-to-buffer, k is
ibuffer-do-kill-lines.
donkey-support-mode-exceptions is the other exception and a different
thing: it gives the buffer back to NORMAL state, for a
program-looking buffer you type in, which is what occur-edit-mode
ships for. It is the wrong lever for a list you drive. Put Ibuffer on
it and DONKEY owns every letter: the modeline reads DONKEY[I], and
m, t and D stop marking and deleting.
Both mean Normal state is off in this buffer. [E] is
the excluded list — DONKEY holds no key at all, not even hjkl.
[S] is a section — DONKEY holds four.
A mode on both lists is excluded. If you have dired-mode in your own
donkey-excluded-modes, that still wins, and you will see [E] rather
than [S] until you take it off.
In a mode on donkey-excluded-modes, DONKEY is out of the way
completely: every key is the mode’s, NORMAL state cannot be reached by
any route, C-g means what it means in stock Emacs, and the modeline
says DONKEY[E].
Thirteen modes ship on the list. Add your own like this:
(with-eval-after-load 'donkey
(dolist (mode '(pdf-view-mode eww-mode elfeed-search-mode))
(add-to-list 'donkey-excluded-modes mode)))
Naming a parent mode covers its children, which saves a lot of typing:
magit-mode alone covers the status, log, diff, revision, refs,
reflog, stash and process buffers, and the forge ones too.
wdired is the exception worth knowing: it is not built on
dired-mode, so excluding dired leaves it alone — which is what you
want, since a wdired buffer is one you are editing. C-x C-q takes you
there in NORMAL state, like any other buffer you are editing; press
i to type a new name.
Editing donkey-excluded-modes reaches buffers that are already open,
both ways: add a mode and its open buffers step aside on the next
command, remove one and they get NORMAL state back. INSERT state you
asked for yourself is never taken away by either.
Three kinds of mode, for three different reasons.
| Mode | Why |
|---|---|
comint-mode | every key goes to a subprocess. Covers its derivatives: shell-mode, ielm-mode, inferior-python-mode, sql-interactive-mode, geiser-repl-mode, inf-ruby-mode, gud-mode and the rest |
term-mode | a full terminal — character at a time, raw mode |
vterm-mode | the same, faster |
eshell-mode | its own command line and input loop |
eat-mode | a terminal that derives from none of the above |
mistty-mode | the same, wrapping a shell |
slime-repl-mode | Common Lisp REPL, not a comint derivative |
cider-repl-mode | Clojure REPL, the same |
racket-repl-mode | Racket REPL, the same |
haskell-interactive-mode | GHCi REPL, the same |
The first four cover a great many modes by derivation. The six after them derive from none of the four and had to be named one at a time.
| Mode | What NORMAL state would take |
|---|---|
magit-mode | commit, log, branch, diff, push, stash. Covers every magit and forge buffer |
git-rebase-mode | pick, reword, drop, exec, break — an interactive rebase cannot be finished without them |
Nothing could have been damaged in any of them: they are read-only, so
every editing key is refused with Buffer is read-only. What went
missing was the mode, not your work.
| Mode | Why |
|---|---|
Info-mode | n, p, u, l, m, i, d, t, s, SPC and DEL are the whole of how you move through a manual |
Info is the one mode where the support mode answer was tried
and lost. Four ways of fitting h j k l around its alphabet were
measured, and each cost something a reader needs: the node keys, the
menu, the history, or the scroll. It is excluded instead, so DONKEY
holds no key there at all — not even the four.
j and k. Magit remaps next-line, so while NORMAL state is on
those two keys move by magit sections — and with the mode excluded they
go back to what the mode itself binds, which is magit-delete-thing.
If you want the motion back, two lines:
(Dired used to be on this list for the same reason. It is a support
mode now, which keeps j and k walking the listing without taking
anything else, so nothing has to be put back by hand there.)
(with-eval-after-load 'magit
(keymap-set magit-mode-map "j" #'magit-next-line)
(keymap-set magit-mode-map "k" #'magit-previous-line))
Deliberately, because a support mode is the better answer
there: help-mode, occur-mode, compilation-mode, grep-mode,
Man-mode and org-agenda-mode keep their own alphabet while DONKEY
keeps the four motion keys and the leader. They were NORMAL state
until 1.12.0, which is what made excluding them look necessary.
Deliberately, because they are buffers you are editing: wdired-mode,
git-commit-mode, diff-mode.
No longer on it, because they are support modes now: dired-mode and
ibuffer-mode. They keep every key they ever had except hjkl, which
is what excluding them was really for.
Not on it because they do not need to be: elfeed-search-mode,
deft-mode, vundo-mode, pdf-view-mode, the docker and
kubernetes UIs. A mode DONKEY has never heard of still gets an
answer — see below — so excluding one is a choice rather than a
repair. Add any of them as above if you want DONKEY out of the way
entirely, and please open an issue so the next person does not have
to. (eww-mode and ranger-mode came off this list: both are support
modes now.)
Nothing has to be on either list for DONKEY to behave. A mode you
write in — any prog-mode or text-mode derivative, so rust-mode,
go-mode, markdown-mode, yaml-mode and the rest — is NORMAL
state, and its letters are DONKEY’s. A buffer a program made —
anything derived from special-mode, or read-only — is a support mode
on the floor alone: h, j, k, l and the leader, with every
other key the mode’s. kubernetes-mode, log4e-mode and
tablist-mode land there today without a line of configuration.
So most packages get support for free. The sections DONKEY ships are
for stock Emacs, where the maintainer can test them; a section for a
third-party mode is added on request. If one you use would be better
with h and l meaning something, open an issue saying which mode
and which two commands.
That makes this a list rather than a rule, and it stops where the reading stopped. Why Those Two Modes Ship Excluded sets out the reasoning and how to undo any of it.
To see the list in force: M-x eval-expression donkey-excluded-modes.
DONKEY changes the cursor shape with the state, so you can see which one you are in without reading the modeline.
(setq donkey-cursor-normal 'box) ; NORMAL state
(setq donkey-cursor-insert '(bar . 2)) ; INSERT state
(setq donkey-cursor-support 'box) ; a support mode, =[S]=
(setq donkey-cursor-excluded '(bar . 2)) ; an excluded mode, =[E]=
Any of them accepts what Emacs’s cursor-type accepts: box, bar,
hbar, hollow, or a cons like (bar . 2) for a bar two pixels
wide. Set one to nil to leave the cursor alone in that state.
The last two are the same shapes as the first two, on purpose. A
support mode takes NORMAL’s box because the keys there are NORMAL’s —
h, j, k and l move, and nothing you press becomes text. An
excluded mode takes INSERT’s bar because most of them are terminals
and REPLs, where what you type goes to the program on the other end.
So out of the box the cursor answers is this buffer taking my
letters? rather than which of the four states you are in, and the
modeline is still where you read the difference between [S], [E]
and [I]. Give either its own shape if you would rather see all
four.
In a graphical frame Emacs draws the cursor itself. In a terminal the shape has to be asked for with an escape sequence (DECSCUSR), and not every terminal understands it. DONKEY sends it only where it is known to work, and refuses terminals by name:
(setq donkey-decscusr-denied-terminals '("dumb" "linux"))
Those two are the default: dumb supports no escape sequences at all,
and the Linux console changes its cursor by a different mechanism.
Matching is by prefix on what tty-type reports.
When Emacs exits or is suspended, or an emacsclient -t frame closes,
the terminal is sent its own cursor shape back, so the shell you
return to does not keep DONKEY’s. Coming back from a suspension sends
the state’s shape again.
If your terminal prints stray characters when the state changes, add its type:
(with-eval-after-load 'donkey
(donkey-add-denylist-entry "eterm"))
M-x donkey-add-denylist-entry and M-x donkey-remove-denylist-entry
do the same interactively, and remember the change.
M-x donkey-debug-platform prints what DONKEY thinks it is running in
— window system, terminal type, whether the cursor escape is allowed,
which clipboard tool was found — which is the first thing to paste
into a bug report.
y and p use the system clipboard where there is one, falling back
to the kill ring. p reads the clipboard once per press and puts
another program’s copy on the kill ring once, however often it is
pasted, so M-y after a paste reaches the kill before it. On most
systems this needs no configuration at all:
DONKEY looks for wl-copy, xclip, xsel, pbcopy or Windows
PowerShell and uses the first that fits.
M-x donkey-debug-platform shows which one it found.
A Wayland session with emacs --daemon and a mix of emacsclient -c
and emacsclient -nw frames has a known trap that is not DONKEY’s:
wl-paste can deadlock when Emacs owns the clipboard itself. If you
run that combination, route terminal clipboard reads through a live
graphical frame:
(use-package xclip
:config
(setq xclip-method 'wl-copy) ; autodetection picks xsel first
(xclip-mode 1))
In Normal state RET tries these in order and stops at the first that
answers:
- An Org buffer — the rules below: follow the link, toggle the checkbox, flip the TODO.
- A Markdown buffer — follow the link at point.
- A mode not listed in
donkey-editing-modes— whatever the mode itself bindsRETto, asked at the moment you press it. A command that would type or break a line is refused, soRETstays inert rather than inserting a newline in a buffer you are reading.
Where none of them answers, RET does nothing. That is every buffer
of a mode in the list, away from what the Org and Markdown steps act
on:
;; the default
(setq donkey-editing-modes
'(prog-mode text-mode org-mode fundamental-mode
conf-mode markdown-mode gfm-mode))
Derived modes are covered, so those seven names cover nearly every
buffer you actually edit. Org and Markdown are in it for what their
own steps leave over: RET on an Org checkbox still toggles it. Add a
mode to make RET inert there; remove one to let RET reach whatever
that mode binds.
;; (donkey-add-enter-rule ELEMENT-TYPE PROPERTY COMMAND...)
(donkey-add-enter-rule item :checkbox org-toggle-checkbox)
(donkey-add-enter-rule headline :todo-type donkey-org-todo)
(donkey-add-enter-rule link nil org-open-at-point)
Those three are the defaults. A rule names an Org element type, a property to test (or nil for “any”), and one or more commands tried in order until one is bound and callable — so a rule can name a command from a package you may not have installed.
Rules added later are tried first, which lets you override a default without removing it. Within one press, the element at point is tried against every rule before the element that contains it, and so on outward, so the most specific rule wins.
Turn the defaults off entirely:
(setq donkey-default-enter-rules-enabled nil)
Element types you can write rules for are Org’s own: headline,
item, link, table-row, src-block, quote-block, example-block,
footnote-reference, timestamp, inline-src-block and the rest of
org-element’s vocabulary.
The pairs m i and m a can select are the pairs a key can wrap —
one table, read by both, so a pair added once is added everywhere:
;; add a pair of your own
(with-eval-after-load 'donkey
(setopt donkey-mark-pair-delimiters
(cons '(?# . ?#) donkey-mark-pair-delimiters)))
setopt matters here: it runs the setter that claims the key. With
plain setq or add-to-list, the key is claimed at the first idle
moment after startup instead — which is still before you can press it,
but M-x donkey-refresh-wrap-keys is the way to ask by hand if you
change the table later.
To wrap with only some of them:
(setq donkey-wrap-delimiters '(?\( ?\[ ?\" ?*)) ; these four only
all, the default, means every pair in the table.
If a character you name already runs a DONKEY command, that command
keeps the key and the character simply does not wrap. : goes to a
line and > indents, so neither wraps, and DONKEY says so if you ask:
DONKEY: the wrap delimiter : is donkey-goto-line, so it does not wrap
Either rebind the key yourself or pick another character.
If you use smartparens, electric-pair-mode or similar and would rather it did the wrapping:
(setq donkey-wrap-region-engine 'pairing-package)
DONKEY then hands the keypress to self-insert-command with the mark
still active and lets your package’s hook do the work. The trade: what
wraps and how is then your package’s business, including whether
pressing the same delimiter again takes the pair off. M-x
donkey-toggle-wrap-engine flips between the two so you can feel the
difference.
In a buffer where nothing but donkey-pair-mode is pairing, DONKEY’s
own pairing is the package, so the wrap there is DONKEY’s own, taking
the pair off included, and the toggle says so.
Everyday use is Pairing as you type: turn the mode on, and choose the delimiters. This is everything else it can be told.
This is the part worth reading, because DONKEY is usually not the one doing it. Emacs comes first:
| In this buffer | Who pairs | What DONKEY does |
|---|---|---|
electric-pair-mode on | Emacs | hands Emacs its delimiters |
smartparens-mode on | smartparens | nothing at all |
| neither | DONKEY | pairs, from its own table |
So if you already use electric-pair-mode, turning this on does not
replace it or compete with it. Emacs goes on pairing exactly as it
did, and DONKEY simply tells it about the delimiters it did not know —
which is the job smartparens was doing for you. Drop smartparens, keep
electric-pair-mode, turn this on, and your own pairs keep working.
And if you prefer smartparens, DONKEY stays out of the way entirely, the same way it does about wrapping.
M-x donkey-debug-platform says which of the three you are looking at:
Pair typing: on, electric-pair-mode pairs (9 delimiters, 6 handed to it)
Worth knowing before you turn anything on: electric-pair-mode on its
own already closes the common brackets nearly everywhere.
| Mode | ( | [ | { | " | < | ' |
|---|---|---|---|---|---|---|
| text | yes | yes | yes | yes | no | no |
| org | yes | yes | yes | yes | yes | no |
| emacs-lisp | yes | yes | no | yes | no | no |
| python, c, js | yes | yes | yes | yes | no | yes |
| html | yes | yes | yes | yes | yes | yes |
| latex | yes | yes | yes | yes | no | no |
So what DONKEY adds is not the brackets. It is the pairs Emacs has no
way to know about: #, the guillemets, and whatever else you put in
donkey-mark-pair-delimiters.
Look at the < column above. It closes itself in Org and in HTML and
in nothing else — and that is nothing to do with DONKEY, which never
pairs < in any mode. Emacs decides by the syntax table, and Org
gives < bracket syntax because of <<targets>>, timestamps and the
<s templates.
This is the trade you make by leaving Emacs in charge, and it is worth saying plainly: where =electric-pair-mode= is on, what pairs in which buffer is Emacs’s answer, mode by mode. DONKEY hands it the extra delimiters and Emacs settles the rest.
Only in the third arrangement — no electric-pair-mode, no
smartparens, DONKEY doing the pairing — do you get one answer
everywhere: the same delimiters behaving the same way in a Lisp buffer
and in a letter, which is what DONKEY’s wrap keys already do.
Neither is wrong. They are different promises, and which you get depends on what else is on.
Emacs asks two questions, in this order.
First, is the character named outright? electric-pair-pairs is a
plain list, and what it names pairs regardless of anything else. It
ships holding the double quote and the two typographic quote pairs.
That is why " closes itself even in text-mode, where the syntax
table calls it mere punctuation.
Second, what does this mode call it? Failing the list, Emacs asks the major mode’s syntax table for the character’s class, and pairs it only if the class is open bracket or string quote. Everything else — punctuation, symbol, word — is not a pair and never closes itself.
So the answer is per mode because syntax tables are per mode:
| Character | text | org | emacs-lisp | python | html | latex |
|---|---|---|---|---|---|---|
( [ | open | open | open | open | open | open |
{ | open | open | symbol | open | open | open |
" | punct | string | string | string | string | punct |
< | symbol | open | symbol | punct | open | symbol |
' | word | word | quote | string | string | word |
Read that table and the behaviour above stops being a surprise:
{fails to pair in Emacs Lisp alone, because Lisp does nothing with braces, so they are ordinary symbol characters there.<pairs in Org and HTML alone, because only those two call it a bracket — Org for<<targets>>and timestamps, HTML for tags."pairs everywhere, including the two modes that call it punctuation, because the named list wins over the table.
It is stable: the table above is identical on Emacs 29.1, 30.1, 30.2 and 31.1, and DONKEY has a test that reads it out of this README and checks it against the running Emacs.
What the other two do instead. Smartparens asks neither question: it
keeps explicit pair definitions, globally and per major mode, which is
how it can offer Org’s / and === or Ruby’s module … end — none of
which any syntax table could express. DONKEY asks neither either: it
reads donkey-mark-pair-delimiters the same way in every buffer,
which is what makes its own pairing identical in a Lisp file and a
letter, and precisely what it gives up when it hands the delimiters to
Emacs instead.
Turning DONKEY’s mode on is the same line whichever you want. What decides the arrangement is which other pairing mode is running.
To keep Emacs in charge and let DONKEY supply the extra delimiters — the usual choice, and the one that leaves your editor as it is:
(electric-pair-mode 1)
(donkey-pair-mode 1)
To have DONKEY pair everything, with one answer in every buffer, turn
the other two off. electric-pair-mode is already off in stock Emacs,
so usually only the first line applies:
(smartparens-global-mode -1) ; or drop the package
(electric-pair-mode -1) ; off by default anyway
(donkey-pair-mode 1)
To keep smartparens, do nothing: DONKEY stands down wherever it is on,
whether or not donkey-pair-mode is enabled.
M-x donkey-debug-platform says which of the three you ended up with,
for the buffer you ran it from.
Every no in the safe table under Pairing as you type is a setting
rather than a rule. The thirteen characters those rows name are
donkey-pair-safe-exclusions, and you
add to it when a pair you put in the table is one you never want to
TYPE. A letter is the case that matters: add (?X . ?X) so that m i
X can select between two of them, and X would otherwise close
itself, so typing faX would give you faXX.
(setopt donkey-pair-safe-exclusions
(cons ?X donkey-pair-safe-exclusions))
X stays in the table and stays a wrap key. It simply stops pairing
as you type. To drop a delimiter in one major mode rather than
everywhere, that is donkey-pair-delimiter-exceptions instead.
DONKEY’s own pairing reads both variables at every press, so a change
is live at once. Emacs’s pairing is told the set rather than asking
for it, so M-x donkey-pair-refresh hands it over again after a plain
setq or add-to-list. setopt and M-x customize-variable need no
help.
Turning the mode off gives Emacs back exactly the pairs DONKEY added,
and leaves alone any you put in electric-pair-pairs yourself.
donkey-pair-excluded-modes is its own list, so a mode can keep NORMAL
state and stop pairing:
(setopt donkey-pair-excluded-modes '(org-mode))
Nothing pairs where DONKEY steps aside altogether — a terminal or a
REPL on donkey-excluded-modes — whatever that list says.
To drop one delimiter in one mode rather than all of them, name it in
donkey-pair-delimiter-exceptions. The shipped entries keep ', `
and # from pairing in Lisp buffers, where #' is a function quote:
(setopt donkey-pair-delimiter-exceptions
'((emacs-lisp-mode ?\' ?\` ?#)))
This one reaches DONKEY’s own pairing only. Where Emacs is doing the
work it is handed the delimiters and decides the rest itself; if you
want a delimiter inhibited there, electric-pair-inhibit-predicate is
Emacs’s own way to say so.
donkey-pair-delimiter-inclusions is the other direction, and HTML is
what it is for. The less-than sign is less-than in prose, which is why
safe leaves it out, but almost every line of an HTML buffer sits
between one and its closer:
(setopt donkey-pair-delimiter-inclusions '((html-mode ?<)))
Typing < then script now gives you <script>, and typing > where
one already stands steps over it. The character pairs in that mode and
in the modes deriving from it, mhtml-mode among them, and in no other
buffer.
It adds to the list in force rather than replacing it, so brackets and
quotes go on pairing there. Where a mode is named in both lists for the
same character the exception wins, and a character
donkey-mark-pair-delimiters does not carry is not something any list
can add.
Emacs does not need telling this, because html-mode gives < the
syntax of an opening bracket and > that of a closing one. That is why
electric-pair-mode pairs them there with no configuration, and why
DONKEY needs the line: DONKEY pairs from your lists, which are the same
in every buffer, rather than from the mode’s syntax table. Modes whose
syntax table does NOT say it, nxml-mode among them, want the line
just as much.
Nothing pairs in NORMAL state, because nothing types there: a delimiter key wraps the selection instead, which is the section above.
DONKEY’s own pairing does not read the buffer’s syntax to decide
whether a quote you typed opens or closes. The rule is simpler and the
same in every buffer: if that character already stands after the
cursor, step over it. So typing " immediately before an existing "
steps over that one rather than opening a new pair. electric-pair-mode
asks the syntax table and gets that case right — one more reason to let
it keep the job where you have it.
If you use smartparens, one call improves C-g inside its overlays,
especially in a terminal:
(with-eval-after-load 'smartparens
(donkey-setup-smartparens))
NORMAL state suppresses typing by remapping self-insert-command,
which catches every key a mode types through Emacs’s normal path — and
that is most of them, Org included.
A mode that binds a key directly to an insert command of its own is
a different matter: org-mode puts org-force-self-insert on |,
cc-mode puts electric commands on #, * and /, AUCTeX puts its
own on $, \ and ^, python-mode (the MELPA one) puts
py-electric-comment on #, tagedit puts one on !, and
fortran-mode, pascal-mode and tcl-mode each have their own.
Twenty-one such commands are refused by name:
You will meet this as a key that types in NORMAL state when it should
not. To name the command it ran, press it, then C-h l
(view-lossage): the last line names the key and the command beside
it.
;; forward-char ;; mymode-electric-quote
M-: last-command answers the same question in one line. With the
name in hand:
(with-eval-after-load 'donkey
(add-to-list 'donkey-self-insert-commands 'mymode-electric-quote)
(donkey-refresh-suppressed-commands))
donkey-refresh-suppressed-commands is only needed when you use
setq or add-to-list; through Customize the list re-installs itself.
This is a list and not a rule: a mode with a typing command nobody has met yet will type. If you find one, add it and open an issue — then the next reader does not have to find it twice.
For readers who want the reasoning rather than the setting. Nothing here is needed to use DONKEY — it is here so the default can be argued with rather than taken on trust, and so that undoing it is a decision rather than a guess.
Dired is the clearest case, because its keymap is the biggest. Press a key there with NORMAL state on and one of four things happens.
! # & 0 1 2 3 4 5 6 7 8 9 E H L N Q R T X Y Z ^ e f n q s t
These are the keys Dired binds that DONKEY’s keymap does not. They used to reach Dired; NORMAL state now answers every printable key itself, so they are refused with “is undefined” instead — see Suppressed keys.
It makes the argument below stronger rather than weaker. The mode’s own keys are one more thing NORMAL state costs in a buffer a program made, not one fewer, which is the whole reason such buffers are not in NORMAL state to begin with.
j, k and u. Dired remaps next-line, previous-line and undo,
and a remap catches the command whichever key ran it — so DONKEY’s
motion keys arrive at dired-next-line and dired-previous-line.
This one is a happy accident worth knowing, because it is better than what Dired binds those keys to itself:
| Key | Dired’s own binding | What you get with NORMAL state on |
|---|---|---|
j | dired-goto-file | dired-next-line |
k | dired-do-kill-lines | dired-previous-line |
u | dired-unmark | dired-undo |
The wrap keys — $ ( + < = ~ — and RET. A wrap key is
borrowed only while a selection could use it, and a buffer you cannot
type into gets it back the moment you press it. What C-h k reports
and what the key runs are different things here:
+ binding: donkey-wrap-region pressing runs: dired-create-directory < binding: donkey-wrap-region pressing runs: dired-prev-dirline RET binding: donkey-enter-dwim pressing runs: dired-find-file
Everything else. In Dired that is a c d i m o p v w x y, A C D G I M
O P S U W, SPC . - >, and the * and % mark prefixes.
Read that last row again and the argument makes itself. m marks, d
flags, x executes, C copies, D deletes, and * and % are both
of the mark prefixes. Those are not incidental keys — they are what
Dired is. A file manager you cannot mark, flag or execute in is not a
file manager with some keys moved; it is a file manager you cannot use.
Worth saying plainly, because it is the first thing a reader worries about. Every one of these buffers is read-only, so every editing key was refused before it did anything:
d → error: Buffer is read-only x → error: Buffer is read-only c → error: Buffer is read-only
What went missing was the mode, not your work. This is a usability default and not a safety fix.
Two wrinkles you may have met and wondered about:
iandaput you in INSERT state in a buffer you cannot type into — no error, no message, justDONKEY[I]in the modeline.C-gcomes back out.oandDmove point before they fail, so a mistyped key can lose your place in a long listing.
| Mode | Its letters are | So NORMAL state answered with |
|---|---|---|
magit-mode | commit, log, branch, diff, push, stash | change, forward-char, backward-word |
git-rebase-mode | pick, reword, drop, exec, break | yank, delete, change |
git-rebase-mode is the sharpest of them: seven of its twelve verbs
were gone, pick and drop among them, so an interactive rebase could
not be finished with the keys that finish it.
Two of the six are parents, which matters more than it looks.
magit-mode covers the status buffer, the log, the diff, the revision,
refs, reflog, stash, cherry, log-select, process and every forge
buffer. Naming magit-status-mode instead covers exactly one of them —
the rest are its siblings, not its children, since they all derive
from magit-mode directly.
tabulated-list-mode is the same idea one level up. A tabulated list
is a control panel by construction: rows you act on, single letters as
verbs, always read-only. One entry covers the package menu, the buffer
menu, and the tabulated UIs that other packages build on it —
pdf-tools’s tablist, docker and tmr among them.
This one surprises people, so it is worth being explicit. The modeline
says DONKEY[E] exactly when DONKEY has stepped aside — it is never
showing you something that is not true. But the mode you are looking at
may not be the mode that is on the list, because a parent covers its
children:
| Buffer’s major mode | On the list? | What matched it |
|---|---|---|
magit-log-mode | no | magit-mode |
shell-mode | no | comint-mode |
package-menu-mode | no | tabulated-list-mode |
magit-mode | yes | itself |
So M-x eval-expression donkey-excluded-modes can quite correctly
fail to mention the mode you are standing in. Rather than work it out,
ask:
M-x donkey-check-bindings
DONKEY: Normal state is off here: magit-log-mode derives from
magit-mode, on donkey-excluded-modes
It names the entry that decided, whether that is the mode itself or an ancestor of it, and says the same for an exception that put NORMAL state back. In an ordinary buffer it says nothing about state at all — nothing decided it there.
A consequence worth knowing before you try it: you cannot take a child
off the list while its parent is on it. Removing magit-log-mode does
nothing at all, because it was never there and magit-mode still
matches:
;; does nothing — magit-log-mode is covered by magit-mode
(setopt donkey-excluded-modes (remq 'magit-log-mode donkey-excluded-modes))
The way to do it is donkey-excluded-mode-exceptions, which is read
first and wins:
;; DONKEY out of the way in Magit, except in a log buffer
(with-eval-after-load 'donkey
(setopt donkey-excluded-mode-exceptions '(magit-log-mode)))
It matches the way donkey-excluded-modes does — an exact mode, or a
parent of one — so naming a parent there exempts everything under it.
It is empty by default and costs nothing while it is.
The older way, if you would rather keep it all in one list, is to take the parent off and name the children you still want:
;; NORMAL state in the log, DONKEY out of the way everywhere else in Magit
(with-eval-after-load 'donkey
(setopt donkey-excluded-modes
(append (remq 'magit-mode donkey-excluded-modes)
'(magit-status-mode magit-diff-mode magit-refs-mode
magit-process-mode magit-stash-mode))))
magit-revision-mode is not in that list because it derives from
magit-diff-mode and follows it — the same rule, one level down.
Three reasons, in the order they matter.
If DONKEY ships ON in Magit, you press c to commit, something else
happens, and nothing anywhere says why. The keys do not fail — they do
other things, quietly and plausibly. There is no thread to pull, and
the bug that gets filed is filed against Magit.
If DONKEY ships OFF in Magit and you wanted NORMAL state there, you
notice at once, the modeline tells you why (DONKEY[E]), and the fix
is one line, below.
Both defaults are wrong for somebody. Only one of them is wrong out loud.
The tempting rule is “step aside in every read-only buffer”, or in
every special-mode derivative. Measured across the read-only modes,
the numbers split rather than converge. The application half hide most
of the mode’s keys. The help-like half hide a handful and gain DONKEY’s
motion in exchange, which is the better trade there — so help-mode,
occur-mode, compilation-mode, grep-mode, Man-mode and
org-agenda-mode are read-only and deliberately not on the list. A
rule that caught the six would have caught those as well.
The honest cost of a list is that it has to admit where it stops. A
mode nobody has met yet is not on it: eww-mode, elfeed-search-mode,
deft-mode, vundo-mode, ranger-mode and pdf-view-mode are all
reasonable additions that nobody has asked for. Add them as
Modes Where DONKEY Steps Aside shows, and please
open an issue so the next reader does not have to.
;; NORMAL state back in Magit, everything else left alone
(with-eval-after-load 'donkey
(setopt donkey-excluded-modes (remq 'magit-mode donkey-excluded-modes)))
remq rather than delq: delq edits the list in place whenever what
it removes is not the first element, and the list it would be editing
is the shipped default.
If what you wanted was only Magit’s or Dired’s line motion back, that is a smaller change than un-excluding the whole mode — see What you give up by excluding magit and dired.
M-x donkey-digraph opens the chart of every two-key sequence. If the
rows look cramped or too airy in your font:
(setq donkey-digraph-line-spacing 0.3) ; a third of a row's height
A whole number is pixels; a fraction is that share of the row, so the spacing keeps its proportion if you scale the text.
Every option DONKEY has, in one use-package block, with the shipped
value shown for each. A line marked ; default sets what you already
have — it is there so you can see the option exists and what shape its
value takes, not because you need it. Read this as a menu and delete
what you do not want.
setopt rather than setq throughout. Four of these options rebuild
something through a setter when they change — the two wrap options, the
pair table and the suppressed commands — and setopt runs it the way
Customize would, where setq would leave the keymap as it was.
donkey-input-methods looks like a fifth and is not: the SPC i keymap
is rebuilt from a variable watcher, which plain setq also trips.
Using setopt for everything means never having to know which is which.
;; The clone and the compile step, the same block as [[#installing][Installing]].
;; From a package archive instead: drop my-donkey-dir, :load-path
;; and :init, and use :ensure t.
(defvar my-donkey-dir (expand-file-name "~/src/donkey"))
(use-package donkey
:load-path my-donkey-dir
:init
;; A package archive byte-compiles what it installs. A clone does
;; not, and nothing in DONKEY compiles itself, so compile it here --
;; and again whenever donkey.el turns out to be newer than the .elc
;; beside it, which is what a `git pull' leaves behind.
(let* ((src (expand-file-name "donkey.el" my-donkey-dir))
(elc (expand-file-name "donkey.elc" my-donkey-dir))
(recompiled nil))
(if (not (file-exists-p src))
(message "DONKEY: no donkey.el under %s -- check my-donkey-dir"
my-donkey-dir)
(when (file-newer-than-file-p src elc)
(require 'bytecomp)
(let ((start (current-time)))
(setq recompiled
(and (ignore-errors (byte-compile-file src))
(float-time (time-since start))))))
(let ((compiled (and (file-exists-p elc)
(not (file-newer-than-file-p src elc)))))
(load (if compiled elc src) nil t)
(message "DONKEY %s loaded %s%s"
(donkey-version)
(if compiled "compiled" "from source (interpreted)")
(if recompiled
(format ", recompiled in %.2fs" recompiled)
"")))))
:config
;;; Where DONKEY steps aside
;; Thirteen modes ship on the list: the terminals and REPLs, where
;; NORMAL state would break the program on the other end, the
;; applications whose single letters are their commands, and Info,
;; whose alphabet would not fit around h j k l. Add yours -- this
;; adds to the list rather than replacing it.
(dolist (mode '(pdf-view-mode elfeed-search-mode))
(add-to-list 'donkey-excluded-modes mode))
;; An exception is read first and wins, which is how a child comes
;; out from under a listed parent: magit-log-mode is not on the
;; list, magit-mode is, so removing it there would do nothing.
(setopt donkey-excluded-mode-exceptions nil) ; default
;;; Modes DONKEY supports rather than takes over
;; Every buffer a program made for you is a support mode, decided
;; by the rule in donkey--program-buffer-p. A section adds what h
;; and l mean there; j and k are never written down. Forty-one
;; ship -- see the chapter above for the whole table.
;;
;; CONS rather than a literal list: setopt with a list of your own
;; REPLACES the table and takes the other forty with it. The
;; first section a mode matches answers, so one written here wins
;; over the shipped one -- which is how to disagree with a section
;; without excluding the mode.
(setopt donkey-support-modes
(cons '(dired-mode (?h . dired-up-directory)
(?l . dired-open-file)
(?y . dired-copy-filename-as-kill)
(?J . dired-goto-file)
(?K . dired-do-kill-lines))
donkey-support-modes))
;; Where the rule is wrong the other way: a buffer that looks like
;; a program's but is one you type in. This is NORMAL state, not
;; [E] -- DONKEY holds every letter there again -- so it is the
;; wrong lever for a list you drive.
(setopt donkey-support-mode-exceptions '(occur-edit-mode)) ; default
;; A bare symbol in a section names a key PACKAGE: the set of keys
;; that package holds, written once and taken by naming it. One
;; ships, `prose', the reading set of 45 keys. Your own goes on
;; the front the same way a section does:
;; (setopt donkey-key-packages
;; (cons '(mine "h" "l" "RET") donkey-key-packages))
;;; The Enter key
;; Where RET does nothing in NORMAL state. Everywhere else it is
;; handed back to the mode.
(setopt donkey-editing-modes
'(prog-mode text-mode org-mode fundamental-mode
conf-mode markdown-mode gfm-mode)) ; default
;; The three built-in Org rules: checkbox, TODO, link.
(setopt donkey-default-enter-rules-enabled t) ; default
;; Your own, one element type at a time:
;; (donkey-add-enter-rule link nil org-open-at-point)
;;; The cursor
(setopt donkey-cursor-normal 'box) ; default
(setopt donkey-cursor-insert '(bar . 2)) ; default
;; A support mode takes NORMAL's shape and an excluded mode takes
;; INSERT's, so the cursor says whether the buffer is taking your
;; letters. Give either its own to tell all four states apart.
(setopt donkey-cursor-support 'box) ; default
(setopt donkey-cursor-excluded '(bar . 2)) ; default
(setopt donkey-decscusr-denied-terminals
'("dumb" "linux")) ; default
;;; Cursors in a column
;; T refuses to make more cursors than this; nil for no limit.
(setopt donkey-split-cursor-limit 10000) ; default
;; Ask past the limit instead of refusing.
(setopt donkey-split-cursor-limit-ask nil) ; default
;;; Undo history
;; Emacs's own limits, not DONKEY's. A split's step is one undo
;; entry of about 8 bytes a place plus the text, so Emacs's 160 KB
;; keeps a step or two at ten thousand places; these keep about a
;; thousand there, or a hundred and thirty at a hundred thousand.
(setopt undo-limit (* 100 1024 1024))
(setopt undo-strong-limit (* 120 1024 1024))
(setopt undo-outer-limit (* 200 1024 1024))
;;; Selecting and wrapping
;; Twenty-one pairs ship. What m i and m a select, the keys wrap,
;; so a pair added here is a wrap key too.
(setopt donkey-mark-pair-delimiters
(cons '(?# . ?#) donkey-mark-pair-delimiters))
;; Which of those pairs get a key: all of them, or name characters.
(setopt donkey-wrap-delimiters 'all) ; default
;; Who puts the pair on: DONKEY itself, or smartparens, or
;; electric-pair-mode.
(setopt donkey-wrap-region-engine 'donkey) ; default
;;; Closing a delimiter as you type
;; Off unless asked for. With electric-pair-mode on, Emacs goes on
;; doing the pairing and DONKEY only hands it the delimiters; with
;; smartparens on, DONKEY does nothing at all; with neither, DONKEY
;; pairs. M-x donkey-debug-platform says which you have.
;; (donkey-pair-mode 1)
;; How much of the pair table types: `safe' is that table less the
;; ordinary punctuation, `all' is every pair in it, and a list is
;; exactly those characters and no others.
(setopt donkey-pair-delimiters 'safe) ; default
;; (setopt donkey-pair-delimiters 'all)
;; (setopt donkey-pair-delimiters '(?\( ?\[ ?\{ ?\"))
;; What `safe' leaves out: characters that are ordinary text far
;; more often than they are delimiters. Add to it when a pair you
;; put in the table is one you never want to type -- a letter, say,
;; added so that m i can select between two of them.
(setopt donkey-pair-safe-exclusions
'(?< ?\' ?\` ?= ?* ?~ ?\| ?\\ ?/ ?: ?+ ?_ ?$)) ; default
;; Modes where typing does not pair, DONKEY's own list.
(setopt donkey-pair-excluded-modes nil) ; default
;; One delimiter dropped in one mode. The shipped entries keep the
;; Lisp quote characters and # from pairing in Lisp buffers.
;; (setopt donkey-pair-delimiter-exceptions
;; '((emacs-lisp-mode ?\' ?\` ?#)))
;; One delimiter added in one mode, the other direction. < is
;; punctuation in prose, which is why `safe' leaves it out, and a
;; delimiter in HTML.
(setopt donkey-pair-delimiter-inclusions nil) ; default
;; (setopt donkey-pair-delimiter-inclusions '((html-mode ?<)))
;; Stay out of smartparens' way where it is on.
(setopt donkey-pair-stand-down t) ; default
;; DONKEY's own pairing reads both variables at every press. Emacs
;; is TOLD the set instead, so after a plain setq or add-to-list on
;; either of them, hand it over again:
;; M-x donkey-pair-refresh
;; How far S remembers.
(setopt donkey-position-ring-max 10) ; default
;;; Keys a major mode types with
;; Twenty-one commands are refused by name. To add one you have
;; met, press the key and C-h l names it:
;; (setopt donkey-self-insert-commands
;; (cons 'mymode-electric-quote donkey-self-insert-commands))
;;; Other languages
;; Each entry is (KEY LABEL INPUT-METHOD), reached under SPC i.
(setopt donkey-input-methods nil) ; default
;; '(("s" "swedish" "swedish-postfix")
;; ("p" "polish" "polish-slash"))
;;; The digraph chart
(setopt donkey-digraph-line-spacing 0) ; default
;;; What DONKEY says for itself
;; The one line at first idle when something has taken its keys.
;; M-x donkey-check-bindings asks for the whole answer any time.
(setopt donkey-report-binding-changes t) ; default
;;; Keys of your own
;; A single letter, yours in every NORMAL-state buffer.
(keymap-set donkey-normal-mode-map "F" #'delete-other-windows)
;; Under the SPC leader, where (NAME . COMMAND) carries the name
;; into ? and into which-key.
(keymap-set donkey-leader-map "b" '("switch buffer" . switch-to-buffer))
(keymap-set donkey-leader-map "g" '("magit" . magit-status))
;;; One key back to its own mode
;; Remapping the DONKEY command in that mode's own map: i writes a
;; note in an org-noter document buffer and is INSERT state
;; everywhere else, with nothing to remember.
(with-eval-after-load 'org-noter
(define-key org-noter-doc-mode-map
[remap donkey-insert-here] #'org-noter-insert-note))
;;; Hooks
;; donkey-normal-mode-hook and donkey-insert-mode-hook run when
;; their state is switched ON *and* when it is switched OFF, and
;; one state change fires both of them -- so a function here reads
;; the variable rather than assuming. This keeps the current line
;; highlighted while you are reading and drops it while you type:
(add-hook 'donkey-normal-mode-hook
(lambda () (hl-line-mode (if donkey-normal-mode 1 -1))))
;; donkey-mode-hook runs when the global mode itself is toggled,
;; for setup that needs DONKEY to be there already, and
;; donkey-pair-mode-hook when the pairing is switched on or off.
;;; And on
(donkey-mode 1))
C-h k then the key, as always in Emacs: it tells you what that key
runs in this buffer, whoever bound it.
? in NORMAL state opens DONKEY’s own chart — every key it binds,
grouped by prefix, with the mark run’s keys listed too because they
live in a transient map that C-h b cannot show. Command names in it
are buttons; click one for its documentation.
M-x donkey-check-bindings compares the keys DONKEY bound when it
loaded with the keys as they stand, and answers for the current buffer
as well:
DONKEY: every key is as DONKEY left it; 2 wrap delimiters cannot take their key -- see the message log
The lines it can print, in plain terms:
; is consult-outline now, was donkey-goto-line— you, or a package, bound that key. Nothing is broken; you are being told so you are not surprised later.D runs org-kill-line in this org-mode buffer, which remaps kill-line— the mode has its own better version of the same command and DONKEY’s key gets it. Counted separately from keys that changed hands, because nothing was lost.the wrap delimiter : is donkey-goto-line, so it does not wrap— you asked for:as a wrap pair, but that key already runs a DONKEY command. Pick another character, or rebind the key.the wrap delimiter # has no key yet; M-x donkey-refresh-wrap-keys— you added a pair after startup. Run that command.
DONKEY says the quieter half of this once, a moment after startup, and nothing at all when nothing differs. Turn even that off with:
(setq donkey-report-binding-changes nil)
Prints what DONKEY sees: window system, terminal type, whether the cursor escape sequence is allowed here and why, which clipboard tool was found, and the same binding report. The first thing to paste into a bug report.
The symptom: you press C-g to leave INSERT state and nothing
happens — or Quit flashes — and the second or third press works.
More often on large frames and prose-heavy buffers.
This is not a DONKEY binding misfiring, and no package can intercept
it, because the press never becomes a keypress at all. C-g is also
Emacs’s interrupt character: arriving while Emacs is running Lisp it
is consumed interrupting that work, below every keymap and every hook.
The moment right after an edit is exactly when Emacs tends to be
running Lisp — refontifying the window, re-spellchecking it, running
checkers.
To confirm: reproduce it, then press C-h l (view-lossage). A press
eaten as an interrupt is simply absent from the log — you pressed
three times, the log shows one.
What helps is shrinking the busy window:
;; redisplay skips fontification while input is pending, so a fast
;; keypress is read as a key instead of interrupting jit-lock
(setq redisplay-skip-fontification-on-input t)
;; if you run jinx: move its recheck out from under your fingers
;; (the default 0.2 is squarely where a quick C-g lands)
(setq jinx-delay 0.8)
The same applies to any checker with an idle delay: raise it past your reaction time.
DONKEY recovers where recovery is possible: a quit that unwinds to the
command loop while INSERT state is on is given the meaning the press
had, so the exit runs instead of Quit being printed. It cannot catch
a quit that redisplay or a timer swallows before the command loop sees
it — which is what the settings above are for.
That is deliberate. A mark run is a transient keymap, and Emacs keeps those per terminal — two graphical frames on one display are one terminal — so a plain transient map would be armed in the other frame too, and any command outside the run would end it.
DONKEY puts the run down instead: leave its buffer, or let its frame
lose focus, and the run is stored with its selection and its u=/=U
history; come back, and it is armed again exactly as it was.
Everywhere in between, every key is itself.
Three things to know:
- The selection must still be active when you come back. A run whose
selection is gone is forgotten, like one you ended with
C-g. - A command that stays in the run’s buffer but is not one of the
run’s still ends it:
xdeletes the selection, and the run is over. - Clicking into a frame to focus it is a mouse command in that buffer and ends the run. Switch frames with the keyboard, or click once and start the run again.
The symptom: you switch frames with a desktop shortcut, press a key,
and the echo area says s-w is undefined; every key does the same
until you press C-g or a modifier once.
The s- is the tell — the keys are arriving with Super held. The
shortcut that moved the focus held Super, its press reached the new
frame as part of the focus change, and its release went to the
compositor, so GDK keeps Super down until it sees the key again.
DONKEY never sees those keys at all: s-w is not w. C-h l shows
them with the modifier you did not press.
What to do: tap Super once after switching, or switch frames with a click or a shortcut that releases its modifier first. Terminal frames do not have the problem.
h j k l | left, down, up, right |
w b | forward, back one word |
W B | forward, back one balanced expression |
J K | down, up one whole line |
S | back through where you have been |
: | go to line |
% | select the whole buffer |
z z | recentre |
g g | beginning of buffer | g e | end of buffer |
g h | beginning of line | g l | end of line |
g j | join lines | g q | fill region |
g Q | fill paragraph | G | end of buffer |
i | before point | a | after point |
I | line start | A | line end |
o | line below | O | line above |
c | change |
d x | delete selection or character | D | to end of line |
c | change | C | comment |
y | copy | p | paste |
P | paste rectangle | > | indent |
u | undo | U | redo |
. | repeat |
v | selection | V | visual line / line selection |
M | mark run | m v | rectangle |
m w | word | m W | symbol |
m b | word backward | m B | symbol backward |
m s | sentence | m S | sentence backward |
m p | paragraph | m P | paragraph backward |
m i | inside a pair | m a | pair and contents |
m I | inside a sexp | m A | sexp and contents |
m l | bank selection | m u | unbank last |
m U | unbank the run | m DEL | clear the bank |
r r | replace-regexp |
r q | query-replace |
z z | recentre |
SPC | your leader |
SPC i & | insert one digraph |
SPC i . | digraph input method |
SPC i - | input method off |
Nineteen keys — " $ ' ( ) * + / < = [ \ ] _
` { | } ~ — plus « » ‘ ’ “ ” ‹ ›. With a
selection they wrap it; press again with the pair around the selection
and it comes off.
, - ; answer “is undefined”. BACKSPACE and DELETE (under all
four of their key names) do nothing at all.
80 commands. These have no key and are reached with M-x:
| Command | Does |
|---|---|
donkey-mode | turn DONKEY on or off everywhere |
donkey-normal-mode | NORMAL state in this buffer |
donkey-insert-mode | INSERT state in this buffer |
donkey-enter-normal | enter NORMAL state |
donkey-tutor | the tutor buffer |
donkey-digraph | the digraph chart |
donkey-check-bindings | what has taken DONKEY’s keys |
donkey-debug-platform | what DONKEY sees on this machine |
donkey-version | the loaded version |
donkey-org-scratch | an *org-scratch* buffer |
donkey-org-todo | toggle a headline’s TODO state |
donkey-switch-other-buffer | back to the previous buffer |
donkey-mark-run-adopt | adopt the current selection into a mark run |
donkey-refresh-wrap-keys | claim wrap keys after changing the pair table |
donkey-refresh-suppressed-commands | re-apply donkey-self-insert-commands |
donkey-toggle-wrap-engine | switch between DONKEY and your pairing package |
donkey-pair-mode | close a delimiter as you type it |
donkey-pair-refresh | hand the delimiters to electric-pair-mode again |
donkey-setup-smartparens | improve C-g inside smartparens overlays |
donkey-add-denylist-entry | stop sending cursor escapes to a terminal |
donkey-remove-denylist-entry | undo that |
The rest are bound to keys. In full, so that every command can be found by name — one row per key, and every row is checked against the keymap by the test suite:
| Key | Command |
|---|---|
m l | donkey-bank-selection |
c | donkey-change |
m <delete> | donkey-clear-banked-selection |
m <deletechar> | donkey-clear-banked-selection |
m DEL | donkey-clear-banked-selection |
C | donkey-comment-dwim |
y | donkey-copy |
d | donkey-delete |
x | donkey-delete |
? | donkey-describe-bindings |
SPC i - | donkey-disable-input-method |
<enter> | donkey-enter-dwim |
RET | donkey-enter-dwim |
: | donkey-goto-line |
> | donkey-indent-region-or-line |
SPC i . | donkey-input-method-digraphs |
a | donkey-insert-after |
I | donkey-insert-beginning-of-line |
SPC i & | donkey-insert-digraph |
A | donkey-insert-end-of-line |
i | donkey-insert-here |
g j | donkey-join-line |
S | donkey-jump-back |
m i | donkey-mark-inner |
m a | donkey-mark-outer |
m p | donkey-mark-paragraph |
m P | donkey-mark-paragraph-backward |
M | donkey-mark-run-toggle |
m s | donkey-mark-sentence |
m S | donkey-mark-sentence-backward |
m I | donkey-mark-sexp-inner |
m A | donkey-mark-sexp-outer |
m W | donkey-mark-symbol |
m B | donkey-mark-symbol-backward |
% | donkey-mark-whole-buffer |
m w | donkey-mark-word |
m b | donkey-mark-word-backward |
O | donkey-open-above |
o | donkey-open-below |
m v | donkey-rectangle-mark-mode |
U | donkey-redo |
v | donkey-set-mark |
m u | donkey-unbank-line |
m U | donkey-unbank-section |
V | donkey-visual-line-toggle |
J | donkey-visual-next-line |
K | donkey-visual-previous-line |
" | donkey-wrap-region |
$ | donkey-wrap-region |
' | donkey-wrap-region |
( | donkey-wrap-region |
) | donkey-wrap-region |
* | donkey-wrap-region |
+ | donkey-wrap-region |
/ | donkey-wrap-region |
< | donkey-wrap-region |
= | donkey-wrap-region |
[ | donkey-wrap-region |
\ | donkey-wrap-region |
] | donkey-wrap-region |
_ | donkey-wrap-region |
` | donkey-wrap-region |
{ | donkey-wrap-region |
| | | donkey-wrap-region |
} | donkey-wrap-region |
~ | donkey-wrap-region |
« | donkey-wrap-region |
» | donkey-wrap-region |
‘ | donkey-wrap-region |
’ | donkey-wrap-region |
“ | donkey-wrap-region |
” | donkey-wrap-region |
‹ | donkey-wrap-region |
› | donkey-wrap-region |
p | donkey-yank |
P | donkey-yank-rectangle |
Inside a mark run the letters mean something else. Listed the other way round, because these are true of the run and not of NORMAL state:
| Command | Key in a run |
|---|---|
donkey-mark-run-buffer-end | G |
donkey-mark-run-buffer-end | g e |
donkey-mark-run-buffer-start | g g |
donkey-mark-run-cancel | M |
donkey-mark-run-down | j |
donkey-mark-run-exchange | * |
donkey-mark-run-left | h |
donkey-mark-run-line-backward | K |
donkey-mark-run-line-end | g l |
donkey-mark-run-line-forward | J |
donkey-mark-run-line-start | g h |
donkey-mark-run-refuse | V |
donkey-mark-run-refuse | v |
donkey-mark-run-right | l |
donkey-mark-run-step-back | u |
donkey-mark-run-step-forward | U |
donkey-mark-run-up | k |
w W b B s S inside a run mark and grow, running the same
commands their m-prefixed versions run in NORMAL state.
? shows the same list inside Emacs, with the mark run’s keys as well,
and M-x apropos-command donkey- finds any of them.
| Option | Default | What it does |
|---|---|---|
donkey-excluded-modes | 13 modes, see above | where DONKEY steps aside completely |
donkey-support-modes | 41 sections, see above | what h and l do in a support mode |
donkey-key-packages | 1 package, prose | the keys a section takes by naming it |
donkey-support-mode-exceptions | occur-edit-mode | program-looking buffers you type in |
donkey-excluded-mode-exceptions | nil | modes that stay on under an excluded parent |
donkey-editing-modes | 7 broad modes | where RET does nothing |
donkey-position-ring-max | 10 | how far S remembers |
donkey-cursor-normal | box | cursor in NORMAL state |
donkey-cursor-insert | (bar . 2) | cursor in INSERT state |
donkey-cursor-support | box | cursor in a support mode, [S] |
donkey-cursor-excluded | (bar . 2) | cursor in an excluded mode, [E] |
donkey-decscusr-denied-terminals | ("dumb" "linux") | terminals that get no cursor escapes |
donkey-split-cursor-limit | 10000 | most cursors t makes; nil for no limit |
donkey-split-cursor-limit-ask | nil | ask past the limit instead of refusing |
donkey-mark-pair-delimiters | 21 pairs | what m i=/=m a select and the keys wrap |
donkey-wrap-delimiters | all | which of those pairs get a key |
donkey-wrap-region-engine | donkey | who puts the pair on: DONKEY or your pairing package |
donkey-pair-mode | nil | close a delimiter as you type it; opt-in |
donkey-pair-delimiters | safe | which delimiters close themselves as you type |
donkey-pair-safe-exclusions | 13 characters | what safe leaves out of the typing set |
donkey-pair-excluded-modes | nil | modes where typing does not pair |
donkey-pair-delimiter-exceptions | 4 modes | one delimiter dropped in one mode |
donkey-pair-delimiter-inclusions | nil | one delimiter added in one mode |
donkey-pair-stand-down | t | stay out of smartparens’ way |
donkey-self-insert-commands | 21 commands | mode commands that type, refused in NORMAL state |
donkey-input-methods | nil | your input methods under SPC i |
donkey-default-enter-rules-enabled | t | install the three built-in Enter rules |
donkey-report-binding-changes | t | say once at startup what has taken DONKEY’s keys |
donkey-digraph-line-spacing | 0 | air under each row of the digraph chart |
M-x customize-group donkey shows them all with their
documentation.
M-x donkey-version prints the version running in this session.
CHANGELOG.org in the repository is written for people using the
package rather than from the commit log.
In a bug report, please include the output of M-x
donkey-debug-platform and, for anything about a key, C-h l
(view-lossage) taken right after reproducing it.
CI runs on every push to master and every pull request; the same
checks by hand:
# the full suite
emacs -Q --batch -L . -L donkey-testing \
$(for f in donkey-testing/*-test.el; do printf ' -l %s' "$f"; done) \
-f ert-run-tests-batch-and-exit
# byte-compile, warnings as errors
emacs -Q --batch --eval '(setq byte-compile-error-on-warn t)' \
-f batch-byte-compile donkey.el
# documentation conventions
emacs -Q --batch --eval "(progn (require 'checkdoc) (checkdoc-file \"donkey.el\"))"
# MELPA packaging conventions (needs package-lint)
emacs -Q --batch --eval "(package-initialize)" \
-l package-lint -f package-lint-batch-and-exit donkey.el
Delete =donkey.elc= before running the suite. With a compiled file
present -L . loads it in preference to the source, and byte-code
open-codes delete-region, forward-char and forward-line, which
defeats the mocks several tests use; seven tests then fail for reasons
unrelated to any change. CI keeps compilation in a separate job for
exactly this reason.
The test files are deliberately not compiled with warnings as errors: they carry unused lambda arguments (mock signatures that must match the real command) and references to optional third-party symbols.
| Version | What it is |
|---|---|
29.1 | the minimum in Package-Requires |
30.1 | the previous stable series |
31.1 | the current stable release |
release-snapshot | the branch upstream is preparing its next release from |
A fifth job builds Emacs master and is allowed to fail, so churn in a
version nobody runs yet does not turn the build red. The single-version
jobs — each test file alone, Smartparens, shuffled order, the terminal
frame, and lint — run on 31.1: they test properties of the suite
rather than of Emacs.