introduction
Welcome!
My goal with this article is to help you develop a comprehensive understanding of the Kakoune text editor: what it is, how it works, and why it has that design.
Although this is not exactly a tutorial, we build up these concepts gradually, and plenty concrete usage examples and demo videos are provided to supplement the discussion.
This piece is quite long, and densely loaded with information. I suggest treating it like a long-term learning resource you can chip away at, rather than trying to tackle it all in one session.
I hope this ends up being useful, both to new users and existing Kakoune enthusiasts. If you have any questions or comments about this post, please don’t hesitate to contact me.
This article was written without AI assistance. If you are interested in a more human internet, please consider subscribing to my RSS feed to be notified of future posts.
- What is kakoune?
- beyond vim
- What does subject : verb order even mean?
- kakoune’s inverted grammar
- orthogonality and atomic edits
- A deeper dive into keys & commands
- multiple selections
- registers & marks
- iterative learning
- a server-client editor
- editing with pipes
- command expansions
- writing plugins
- shell expansions
- conclusion
What is kakoune?
Kakoune (French: [kakun]) is a modal screen-based text editor created in 2011 by French programmer Maxime Coste 1, better known as
mawww. Having begun its life as a C++ rewrite of Vim, it has since evolved into a project with a strong identity and unique design goals.
A non-exhaustive overview of Kakoune’s most interesting features 2:
Multiple selections and multi-cursor editing are first-class(regex filtering, splitting, aligning, etc.).** Interactivity and experimentation are core design principles**.** Orthogonal design with powerful editing primitives**(selection rotation, indentation leveling, case manipulation…)** Client/Server architecture in which the windows of your session are managed by your terminal or window manager**(tmux, i3, niri…).** Highly extendable with an easy-to-learn hook & macro system**(unofficially called “Kakscript”).- Large focus on Unix philosophy; Kakoune not only integrates with, but relies on standard Unix tools.
note on helix
Kakoune has served as a major inspiration for the Helix editor. However, Helix has diverged on some key design points, and the two cannot be discussed interchangeably. Thus, Helix is outside the scope of this discussion. With that said, although much of this article won’t apply to Helix, some of it might, due to its shared DNA with Kakoune. Thus, Helix users may still find this discussion valuable.
beyond vim
Let’s consider “Vim motions” as an editing language. This “core” doesn’t need to be surrounded by Vim; there are many editors (VS Code, JetBrains…) that support Vim motions. Thus, a user that learns Vim motions in one tool can quickly adapt to another tool that supports them.
A side effect of this is that, because of Vim’s ubiquity, a lot of people have come to view “Vim-like bindings” as ** the standard** for keyboard-driven, modal editing. In other words, users expect editors to be mostly compatible with their existing “Vim binding muscle memory”. Some people won’t consider an editor that expects them to change their approach.
I think that’s a shame, because although I’m sure Vim is so popular for a reason, we can certainly do better. No design is immune to being iterated on and improved. Kakoune does this in an innovative way: by inverting the subject–verb order.
What does subject : verb order even mean?
When people say they expect “Vim bindings”, what they really mean isn’t specific
keys… sure, we’re used to hjkl
for movement, but we can get used to something else just as fast. What matters most is Vim’s modal text editing grammar.
It distinguishes between insert mode (regular typing) and normal mode: in which keys comprise words composed into sentences that describe text edits in the form verb followed by subject.
This structure closely mirrors English, so it feels natural for us to reason
about. For example, we might express “delete this word” as dw
: d
for
“delete” as the verb, and w
for “until the start of the next word” as the subject.
There’s no shortage of excellent writing on Vim’s text editing language. However, let me briefly challenge the intuitiveness of verb : subject for text editing.
How often have you found yourself in the following situation? You’re editing the middle of a sentence, and now you want to delete from the cursor up to a certain word. You know exactly how far to go; since you can clearly see the target with your eyes. The challenge: how to share this information with the editor?
Well, the optimal (Vim-like) command would be d<count>w
, where <count>
is a number key representing how many words you want to delete. However, how do you know that number? It’s trivial for 2-3 words, but how about something like 13? Sure, you could literally count them, but that’s going to take forever and take you out of the groove.
Another option is to guess. But sadly, your first (and only) visual indicator of how far you went is after the edit has been applied. So if you were off by a few words, you’ll have to undo and repeat the entire action with a different count.
Thankfully, Vim has an easy solution to the problem: visual mode! Simply press
v
, and now you can use w
and other motion keys to create a clearly visible selection (subject), after which you can press another key to execute the action (verb). By seeing exactly what you’re going to operate on before doing the action, you can reduce misfires greatly.
However, Vim’s visual mode isn’t the most ergonomic. There are different
versions of it (linewise, blockwise, …), and it’s a fully separate mode in
which key behaviors may differ in surprising ways. Furthermore, when you finally
press an “operator” key, the editor usually returns to normal mode — so if you
prefer visual editing, be prepared to press v
a lot.
Vim’s visual mode inverts the verb order from its normal mode. Subject first, then verb. The selection always comes before the action; as a consequence, you will see what you’re acting on before you act.
A friend of mine put it like this: Vim’s inclusion of visual mode is a reluctant admission that the verb-subject order isn’t good enough to cover all cases — but instead of adjusting course and adopting a better grammar, Vim decided to bolt support for it on top of the existing model, ballooning complexity to high heaven.3
kakoune’s inverted grammar
Luckily for the visual mode enjoyers of the world, there is a better solution out there. This is the exact situation where Kakoune’s editing language gets it chance to shine.
Imagine a normal mode that is visual mode. What if, instead of being relegated to a finicky sub-mode, visual selection-based editing was not only supported, but had the entire editing experience optimized around it?
Well, you don’t need to imagine it, because Kakoune is that subject-verb
native editor. For example, w
doesn’t just move the cursor forward to the next
word, it moves the entire selection area. Thus, the meaning of w
is “select the next word”. Here’s how that looks:
Of course, where this really shines is incrementally updating your selection.
Let’s return to the previous example of selecting. In Kakoune, the “shift”
modifier changes the meaning of many commands: instead of moving the
selection, it will extend it. So we can use W
to increase our selection, one word at a time.
Furthermore, most Kakoune keys support counts too, so you can press <num>W
if
you know the number already. And here’s the best part: if we overshoot, we don’t
have to undo and try again. We can just press B
as many times as we need to
decrease our selection. Once we’re happy with our selection, we can press any
key that operates (i.e. a verb), such as d
for “delete”.
well, actually
It’s not so much “the selection comes before” as it is “there’s always a selection”. A key that operates on a selection will always work, because even your “cursor” is just a single-character selection.
Although editing in Kakoune can feel like “make the selection, than do the
action”, there is no actual requirement to follow up a selection key with an
action key. You can spend as much time as you want perfecting your selection
before finally pressing a key like d
or y
to operate on it.
An often overlooked benefit of Kakoune’s verb-order-inversion is that it reduces the total number of modes by essentially merging Vim’s visual mode into normal mode. This eliminates some mental overhead and reduces total keystrokes (no more pressing stuff to always hop in and out of visual mode).
orthogonality and atomic edits
There’s another interesting consequence of Kakoune’s grammar inversion: it
completely eliminates the need for operator-pending mode. Basically, it’s not
fully accurate to say that d2w
is a normal mode command in Vim. The reason:
d
on its own can’t actually do anything. You haven’t yet specified what to delete!
So Vim enters a mode called “operator pending”, in which it waits for your
follow up: the subject! Once you’ve input 2w
, Vim can complete the sentence
d2w
and execute the edit.
In Kakoune, the operator (verb) always knows what to operate on: the selections,
at least one of which must always exist. This makes the behavior of normal
mode keys atomic. d
is defined as “delete the content of the selection”, and given the same buffer state (text & selections), it will always behave the same way.
This key difference from Vim makes composing actions into edits remarkably easy to improvise, because any key can follow any key. In essence, there are only two kinds of keys in Kakoune’s normal mode:
- Keys that change what is selected(move the selection, expand it, shrink it, etc.) - Keys that operate on what is selected(delete, copy, duplicate, replace, etc.)
This means edits aren’t grouped into “sentences” like d2w
or cib
. Since
they’re incremental, not only do you get visual feedback for each step, but
“logical edits” can also be partially undone with u
.
A deeper dive into keys & commands
Kakoune has two ways to receive instructions:
- executing keys
- evaluating commands
executing keys
Keystrokes are simple enough. Not simple as in trivial to learn — there’s a
lot of them,
listed here
— but simple to express in writing: we simply string keys together. Thus, the
sequence hhW
represents pressing h
twice, then W
(Shift + w
).
For some keys, we can use a special notation if needed:
- Modifier keys:
<a-X>
:Alt + X
<c-X>
:Control + X
<c-a-X>
:Control + Alt + X
-
etc…
-
Special keys:
<lt>, <gt>
: escaped<
and>
<esc>
: escape (as seen above)<ret>
: enter<tab>
,<space>
, etc…
spaces
Certain keys can be represented multiple ways. For example, you can write
<space>
explicitly, or simply put an actual space in the sequence. In the key
sequences written under the demo videos, I’m using <space>
to denote literal presses of the space bar. You can ignore the actual whitespaces, those are just there to make it easier to read and have no further meaning.
Why is this “key notation” important? Well, it’s canonical. Besides being a
more pleasant shorthand (<a-x>
is easier to write than Alt + x
all the
time), it gives us all a shared language to discuss and share Kakoune edits.
Additionally, Kakoune can actually parse these strings and execute them via the
execute-keys
command. Speaking of commands…
evaluating commands
Kakoune can evaluate a string as a set of commands. A sequence of such commands is often called Kakscript, though it’s not officially a scripting language. Much like a shell, its input is split into words, which are then interpreted as commands with arguments. There’s shell-like quoting, and commands can be separated by newlines or semicolons.
edit -existing src/main.c; set-option buffer indentwidth 4
write src/main.c.bak
Like a shell (wow! who would’ve guessed?) script, Kakoune can even read &
evaluate commands (Kakscript) from a file (.kak
extension by convention).
There’s even a command called evaluate-commads
(eval
for short) which, you
guessed it, pretty much does the same thing as eval
in the shell.
In case you’re unfamiliar, that means it takes its arguments as strings and
evaluates them as commands. Like execute-keys
, a number of helpful flags are
available, such as -buffer
, which runs those commands in a different buffer context. It also allows for nested evaluation, which becomes useful when working with expansions (discussed later).
kakscript quoting
Kakscript is mostly shell-like, since it has comments, word splitting, escaping
newlines with \
, and so on. However, it has some unorthodox quoting rules, briefly described here.
In short, Kakoune has two kinds of quote delimiters: balanced and matching.
The usual single '
and double "
quotation marks are matching:
"..."
'...'
You can also use a %
prefix to pick your own delimiter, which can be any non-alphabetic Unicode symbol. For example:
%|...|
%~...~
%ε...ε
Any matching delimiter can be doubled to escape it within the string.
Balanced delimiters are also %
-prefixed and follow slightly different rules. Only the four kinds of ASCII brackets are supported:
%{...}
%(...)
%[...]
%<...>
There can’t be escaped, but balanced pairs are allowed to be nested:
%{{}}
: contains literal{}
%(())
: contains literal()
%[[]]
: contains literal[]
%<<>>
: contains literal<>
Although these rules may seem like arbitrary complexity on the surface, they become really helpful when writing scripts with many nested layers of string evaluation.
As we’ll discover later, Kakscript often embeds POSIX shell, which also uses '
and "
for quoting. Using Kakoune’s unique quoting forms lets us write shell scripts without needing to constantly worry about escaping delimiters.
To read about Kakscript in more detail, see the manual. For a more in-depth discussion on its semantics, see my devlog on writing a Kakscript parser.
commands are keys
Of course, commands can be written and evaluated on the fly: pressing :
opens
a prompt into which you can type a command and press <ret>
to evaluate it. The same rules apply: comments, quoting, word-splitting all work the same.
Which leads us to an interesting observation…
I argue that commands are just a subset of keys, because any valid command can
be typed at the :
prompt. Thus, the command write src/main.c
is also the key
sequence :write src/main.c<ret>
. These are just two notations to express the same thing.
In fact, there’s nothing special about even :
. It’s just a key whose job is to
bring up the prompt, wait for input until <ret>
is pressed, then evaluate it.
The same if true of any key that uses a prompt, such as /
(search) and
<a-k>
(filter).
So, why does this matter? It means that commands can be evaluated in any
context where keys are valid, because there always exists some sequence of keys
to produce any command. This is true even in insert mode, wherein <a-;>
can be
used to escape to normal mode for one command: <a-;>:write src/main.c<ret>
Let’s illustrate why the “commands are keys” principle is so important with an
example. Consider the common case of binding a key to some action. In Kakoune,
we can use the map
command to map a key to a sequence. For example, let’s
make the tab key select the current line, but with whitespace trimmed:
map global normal <tab> x_
Let’s try a more common one: binding <c-s>
save the file. There’s a small
difference, though: there isn’t a key for saving, there’s only the write
command. Thankfully, that’s no problem for us, because as we observed earlier, any valid command can be expressed as a key sequence. Thus, to complete the binding:
map global normal <c-s> ':w<ret>'
Now, let’s consider the inverse: executing keys from a command context. It
turns out, this is possible too, thanks to the execute-keys
command (exec
for short). It’s a bit like map
in that it executes… well… you know, but
the execution happens immediately, and it also exposes some options for us to
tweak. To understand how this can be useful, let’s create another binding like
before. This time, we’ll make the =
key duplicate the current line:
map global normal '=' 'xyp'
This works, but there’s two problems: because those keys are being executed literally as if you had pressed them, we end up with side effects: our previous selection is lost, and our clipboard gets overwritten. What if we wanted to keep working with either of those after duplicating the line?
This is where execute-keys
becomes relevant. Beside just executing the key
sequence we give it (just like map
!), this command provides some options that can really help us out.
The first of these is -draft
, which executes keys in a “disposable context”.
This basically means that our selection state gets restored after the command
runs. Consider this example, where the key sequence glbcFOO<esc>
selects the
last word on the line and replaces it. You can see for yourself how running it
with without -draft
results in a different cursor position:
While using -draft
indeed keeps the original selection:
-save-regs
does something very similar, except for registers, which are like
Kakoune’s clipboards. (Yes, you can use multiple at once, and it’s as powerful
as you’d expect!) Registers all have names, and it happens that the default
register (the one stuff gets copied to when you press y
) is called "
(aka
dquote
). Thus, we can complete our improved line duplication command like so:
execute-keys -draft -save-regs '"' 'xyp'
Like before, we just need to write the above command in “key notation” to successfully bind it:
map global normal '=' ':execute-keys -draft -save-regs dquote xyp<ret>'
As you can see, the interplay between commands and keys has its uses for both interactive editing as well as scripting & macros. Even if you don’t plan to write any plugins, these tricks are bound to come in handy in your own configurations.
isolated mappings
Kakoune’s key mappings are isolated. If a mapped key refers to itself (e.g.
map global normal x <i-p>x
), the right-hand side will always use the canonical meaning of that key; recursion is never allowed.
Isolation also means that any keys bound by map
have no effect on
execute-keys
or subsequent map
commands. (execute-keys
does have a
-with-maps
option, but its use is generally discouraged).
A positive effect of this isolation is that key sequences are fully portable across Kakoune configurations, which enables the easy sharing of macros, custom commands, plugin implementations, etc.
For example, as a Colemak-DH user, I’ve mapped mnei
to hjkl
for navigation.
However, plugins are completely safe to use hjkl
internally; my custom mappings won’t interfere. Similarly, when we write our own plugins or custom hooks, we are expected to use the canonical keys as well.
multiple selections
As I mentioned earlier, multiple selections are a core editing feature in Kakoune. This is intuitively understood as multi-cursor editing; but since a cursor in Kakoune is just a selection of size 1, it’s important to think of it as multi-selection editing.
Commands that we understand as acting on “the” selection/cursor actually act on
all selections. It just happens that most of the time, we only have one
selection. But if we have multiple, pressing d
, for example, deletes all of them:
The concept is simple, but incredibly powerful. Multi-selections don’t necessarily empower us to make edits that are impossible in Vim-like editors… but they certainly make the whole experience a lot more ergonomic! Hang tight, you’ll see why in a moment.
So, how does one create multiple selections in the first place? One way is the
s
key, which lets us create them by matching a regex. In short, you first
select some area; then, you press s
, type a regex, and press enter. You’ll end up with all non-overlapping matches inside the original selection as new selections.
gjx s\d+<ret> s2<ret> r7
The most common use case for this is the classic “search and replace”. Let’s try
replacing every occurrence of “foo” with “bar” in this example. We begin by
pressing %
, which selects the entire buffer (think ggVG
in Vim, or <c-a>
in a “typical” IDE). We need to do this because keys like s
always operate on the selection; you must be explicit about saying “please search the whole buffer”.
So, pressing s
brings up a prompt, we type foo
, and after hitting enter, we
end up with each match selected. That’s the search part down, how about replace?
We do it the exact same way we’d replace a single selection: press c
(change),
which deletes the selection and enters insert mode. We can type our replacement,
bar
, and press escape to return to normal mode when we’re done.
% sfoo<ret> cbar<esc>
You may be thinking, big whoop, isn’t this basically the same as
:s/foo/bar/g<ret>
in Vim? Yes, but with a big difference: you get visual feedback for every step of the edit. Besides helping you catch regex mistakes earlier (you’ll clearly see if the wrong thing is selected), the main advantage of Kakoune’s approach is that it lets you branch into a different option at almost any step.
You don’t have to replace the matches; you could copy them (y
), make all
letters uppercase (<a->`
), pipe them to a program
(|
)… You can actually do anything, because as we learned earlier, multiple selections aren’t anything special; most keys you press just have their effects repeated across selections.
Thus, the power of multi-selection shines when making more complex edits. Let’s demonstrate that with some brief examples. Don’t worry about understanding each step, these are just to give you an idea of what’s possible. I’ll share the key sequences so you can try them out yourselves.
Here’s me turning each sentence of a passage into a bullet point:
%<a-I>s a<ret><esc> I-<space> <lt>
And how about swapping the contents of these if/else blocks:
<a-I>{ <a-)>
further reading
If you’re interested in more advanced examples, Dmitri Sabadie has an excellent write-up of solving some Vim puzzles in Kakoune.
registers & marks
We briefly mentioned registers in a previous section, but it’s worth investigating them a little deeper, since they’re a big part of editing with Kakoune. In case you’re not familiar with the concept, you can think of registers like Kakoune’s internal clipboard.
When we copy some text with y
, it gets put in the default register. p
pastes
text from that same default register. Like Vim, Kakoune lets us use the ASCII
letter keys as named registers. Double quote "
is the “register key”, so to
copy some text into register a
, we can press "ay
. To paste text from
register x
, we type "xp
. This can be done with any key that uses a register.
Note
This is also why "
or <dquote>
is the name of the default yank register!
You can already imagine how having multiple named clipboards is useful, but
registers really shine when we make use of Kakoune’s special registers. You
can see them all listed in :doc registers
, but let’s focus on the most useful
one: the mark register, called ^
or “caret”.
Other editors, like Vim, let you “mark” positions in the buffer. You can then “return” to a marked position easily. This feature can be lifesaving when navigating around a complicated codebase. Kakoune has marks too, and of course, they can be used like bookmarks as well. But Kakoune marks have a key property that make them incredibly powerful: they don’t just mark positions, they save selection descriptions to registers.
It’s a bit like creating a snapshot of your entire selection state and returning
to it later. We use Z
(uppercase) to save a mark, and z
(lowercase) to
restore it. Named registers can be used too, like "aZ
and "az
. Consider the following example, in which we’re about to do a search and replace but get distracted by something else. Using marks, we’re able to follow distractions to our heart’s content, since we can return to the previous editing state easily:
<a-i>p sfoo<ret> Z /TODO<ret> x2J sbaz<ret> cbar<esc>, 2kxd zcoof<esc>
We can also perform combinations on marks, which are accessible through
<a-z>
and <a-Z>
. The most common is a
for append. <a-z>a
selects the
mark plus our current selection without changing the mark, while <a-Z>a
appends the current selection to the mark.
Mark combinations are great for creating super precise selections
incrementally, without needing to conjure up some crazy regex to select it all
in one go. In this example, I use <a-Z>a
(append to register) to select a
bunch of words, then capitalize all of them with a single press of ~
:
wwZ jbb<a-Z>a 5w<a-Z>a 2j3b<a-Z>a glb<a-Z>a z~
We can also use the “union” mark combination (<a-z>u
) to select an area by
separately defining its start and end points. If we save a mark with Z
, select
something else, then press <a-z>u
, the entire area between those two points is selected. This is super handy for selecting a large chunk of text that doesn’t all fit on the screen at once:
xZ 20jvt 20jvt 6j x <a-z>u <a-;> d vv
iterative learning
Despite Kakoune’s high complexity, you can actually get up to speed with it pretty quickly. The truth is, most edits you’ll want to do can be expressed with a smaller number of basic keys that you can learn in a day. At this point, you’ll likely be somewhat inefficient, but nothing is stopping you from actually using Kakoune to complete your daily work at a reasonable pace.
The real learning experience comes with the gradual process of refinement as you discover more efficient ways to perform edits you already know. Kakoune is very well suited to being “discovered” in this way because the two main kinds of keys (change the selection, act on the selection) are simple to reason about. Once you understand Kakoune’s conceptual framework, integrating new keys and techniques into your workflow becomes a remarkably iterative process.
Let’s being with something called an “editing goal”. This is basically a description of the end result you want to arrive at. For example, “change this string’s quote delimiters”, or “extract these lines of code into a separate function”.
Most goals have multiple, equally valid ways to be achieved. When you are tasked with an editing goal you haven’t encountered before, a set of unknown paths become available to you. Your job is to “discover” one of those paths, based on the keys and techniques you already know. If you’ve got the basics down, a basic, but inefficient path should always be available to you.
Learning Kakoune is really about slowly integrating new techniques into your existing workflows to make them more efficient. Let’s illustrate this principle with an example. Suppose your goal is to swap the arguments in a Python function signature:
def foo(first, second):
pass
python
def foo(second, first):
pass
Let’s review the keys you’d need to discover the most basic editing path:
some basic keys
x
selects the whole line:
s
creates selections matching a regex:
<a-)>
rotates selections content (note the plural):
If you’ve learned the above keys, you can already form a simple game plan:
select as one selection per argument, then rotate. To execute this plan, you’d
press x
to select the line, select the parenthesis with s\(.+\)<ret>
, then
“reduce” your selection to just the arguments with s\w+<ret>
, followed by a
final <a-)>
.
x s\(.+\)<ret> s\w+<ret> <a-)>
That’s not exactly the most optimal way, but my point is, you can do it knowing only three keys. As you learn more keys, you can make edits like this more convenient.
For example, you may replace the s\w+<ret>
with the much simpler
S,<space><ret>
, which splits your selection on regex matches (in this case,
just the literal ,
):
x s\(.+\)<ret> H<a-;>L S,<space> <a-)>
The escaped parenthesis in that regex is kinda ugly, but luckily we can avoid
that too. By putting the cursor inside the parenthesis, we can select it all
with <a-i>)
, thus simplifying the edit:
www <a-i>) S,<space><ret> <a-)>
But what if we want to do this for multiple functions at once? Just select them, then select the parenthesis and the inner words as nested text objects:
% <a-I>) :exec <space> -itersel <space> <a-I>w <a-)>
Note
We have to actually type part of this out using :exec
because we need
-itersel
, which runs a key sequence once per selection. This is how we rotate the words within each line, as opposed to globally!
In other words, you really don’t need to know that many keys to get started with
Kakoune. All the keys you learn can be composed together in ways that make
sense, so you can learn new primitives at whatever pace suits you until you’ve
got them all. And in the meantime, if you really need something, :doc keys
serves as an excellent resource.
a server-client editor
Kakoune is designed with the Unix philosophy in mind: do one thing, and do it well. Thus, its focus lies entirely with being a competent code editor; anything that can be outsourced to external tools should be.
Like all modern editors, Kakoune can have multiple text buffers open for editing at once. However, unlike most of its peers, Kakoune does not implement any window management. A single client has no vertical or horizontal splits; you can only view one buffer at a time.
However, Kakoune does have a solution to this, and I think it’s pretty elegant. Notice how I said “client” earlier? That’s because Kakoune has a server–client architecture. When you first launch Kakoune, besides starting the interface you see, it also spins up a session with a unique ID… which can be used to attach new clients to this session!
Now, suppose we’ve started Kakoune with kak -s foobar
, which just sets a
custom session ID. If we want to see & edit two buffers side–by–side, we can
simply attach a new client to the session with kak -c foobar
, and change its active buffer.
This is what lets us outsource managing buffer windows to an actual window manager. A Kakoune client is a separate process. Thus, you can open new clients as tmux planes, separate terminal windows managed by your operating system, or however else you prefer to manage terminal applications.
Speaking of tmux, a strong parallel exists here. It could be helpful to think of
Kakoune like tmux, except it handles text buffers instead of terminal panes.
Tmux allows for detaching from a session, allowing it to exist in the background
and be reattached to later, even if you’ve closed the terminal window. Kakoune
also supports this detach-attach workflow. The daemonize-session
command prevents the session from terminating when the last client is closed, thus allowing your editing sessions to persist as long as the daemon isn’t killed.
You can also control Kakoune sessions from outside using kak -p <name>
. This
command reads commands from standard input and evaluates them in the context of
the session you named. For example, I can remotely kill the earlier session with
echo 'kill' | kak -p foobar
. This makes it easy to script Kakoune from anywhere on your system.
editing with pipes
Any Linux user will tell you that in a shell, the |
key is incredibly powerful. It can be used to compose commands together into pipelines by “piping” the output of one command into the input of another.
As it turns out, Kakoune lets us integrate this same concept into our text
editing with the |
key. In short, the content of our selections is piped to the standard input of the command of our choice; the output replaces the original text.
This capability has an important consequence: in Kakoune, you can always express a certain text transformation, because even if you can’t (or don’t want to) do it with Kakoune keys, you can do it with an external program.
I often select a paragraph and reflow it with |fmt<ret>
, or sort a list of
numbers with |sort -n<ret>
. You could also type some Python code, select it,
and “evaluate” it inline with |python<ret>
:
4w 2W _ y o<ret>print()<esc> hP x|python<ret>
But piping is most interesting when you write your own programs to augment some
aspect of your workflow. For example, I often find myself needing to “increment”
numbers and letters, so I wrote a small Python program and added it to my
$PATH
as char-inc
:
#!/usr/bin/env python3
import sys
def arg(i, default=1):
return int(sys.argv[i]) if i < len(sys.argv) else default
inp = sys.stdin.read()
count = arg(1)
fac = arg(2)
if count <= 0:
count = 1
out = []
for c in str(inp):
if c.isalnum():
out.append(chr(ord(c) + count * fac))
else:
out.append(c)
print("".join(out), end="")
bash
$ echo '1. a.' | char-inc
2. b.
$ echo '3. c.' | char-inc 2 -1 # optional <count> and <direction>
1. a.
Now I can refine my selection with regular Kakoune keys, then use my custom program to easily increment the selections. Here’s an example of me updating an ordered list:
xy %<a-s> gh F: |char-inc<ret> ggP bcqud<esc>
command expansions
Kakoune can expand certain constructs when evaluating commands. The results of
these expansions are treated as normal strings, so they can be passed as
parameters to other commands, substituted into longer strings, etc. This is
similar to parameter expansion in the shell, but it works a little differently,
because there are multiple kinds of expansions. For example, val
gives
access to read-only internal data, reg
lets us read
registers, and opt
can be used to read options, which
are (mostly) like variables. There are actually 7 expansion kinds in total…
though I won’t list them all here, you can read more about them in
:doc expansions
. Of all the expansion kinds, sh
(shell expansions) are by far the most powerful. We discuss them in a later section.
Expansions can only be done using the %
-quotes
described earlier. The kind goes after the %
sign,
and the name
goes between the delimiters: %kind{name}
. We can use the echo
command to experiment with them, since it just prints its arguments to the
status line. For example, we can see the current session ID with
echo %val{session}
, the content of register a
with echo %reg[a]
, the
currently detected filetype with echo %opt|filetype|
, and so on.
writing plugins
Besides being a joy to use interactively, Kakoune makes it easy to automate & script its behavior with macros, custom commands, hooks, key mapping, and more. Let’s learn about it by writing a simple plugin: an auto-formatter for Rust code.
Even a task that doesn’t seem like it, is just a task of automating actions
you can otherwise take interactively. For example, we can directly format a Rust
file with just a few keys: first, %
to select the entire buffer, then |
to
bring up a prompt for a shell command, typing rustfmt
and pressing enter.
This pipes the entire buffer into rustfmt
and replaces it with the output. In
Kakoune key notation, the action is: %|rustfmt<ret>
. This would get annoying
to do each time, though, so let’s use the map
command to bind this to the
equal key. We can add it to our kakrc
, which runs this command on session startup:
map global normal '=' '%|rustfmt<ret>'
So we’ve finished our first “level” of automation, which is binding a sequence
of keys (%|rustfmt<ret>
) to a single key (=
). Think of this binding as a direct translation of keystrokes. However, this feels like a waste of a key; it won’t be helpful in other filetypes, after all! Instead, we can define a custom command.
How about we create a command called rustfmt
which executes this key sequence
for us? There’s a difference in how map
and define-command
work. Where map
translates a key into a sequence of keystrokes, define-command
maps a command name to some Kakscript.
Because it expects commands and not keys, this won’t work:
define-command rustfmt %|rustfmt<ret>
Error: parse error: 1:26: unterminated string '%|...|'
Instead, we need to run these keystrokes from a command context. The
execute-keys
command, which we learned about earlier, is perfect for the job.
execute-keys -draft '%|rustfmt<ret>'
The -draft
switch runs it in a “disposable context”, meaning our current
selection state will be restored, and undo history won’t get clobbered. There’s
no need to use -save-regs
because the sequence isn’t doing any yanking. Nice!
Let’s put this command inside the body of rustfmt
:
define-command rustfmt %{
execute-keys -draft '%|rustfmt<ret>'
}
OK, so we’ve got a custom command, and we can use it interactively with
:rustfmt<ret>
. We could also bind it to the same key as before, if we want:
map global normal '=' ':rustfmt<ret>'
That’s nice, but this isn’t really auto-formatting, is it? Can we get this to
happen automatically for Rust code? Yes, with the hook
command! This command
lets us register other commands that will be evaluated when a certain hook
triggers. Available hooks and their parameters are documented in :doc hooks
.
In our case, we can use the BufWritePre
hook. This example triggers right
before buffers whose filename ends with .rs
are written to the disk; when
there’s a match, the rustfmt
command is executed.
hook global BufWritePre .*\.rs rustfmt
Hopefully this gave you an idea of how Kakoune’s hook & macro system can be used to automate behavior or implement certain functionality through some pretty simple components!
shell expansions
There’s one more piece to the Kakoune puzzle: shell expansions. They allow us to
spawn subshells that control Kakoune over standard output. In other words, we
can write shell scripts that “expand” within Kakscript. %sh{...}
in Kakoune is
analogous to "$(...)"
in shell.
For example, the following two blocks are functionally equivalent; it’s just that one of them spawns a subshell to arrive at the same result:
echo "foobar!"
evaluate-commands %sh{
printf 'echo "foobar!"\n'
}
Kakscript itself is very minimal and barely provides any programming
capabilities (there isn’t even an if/else
!), but the available commands are
capable enough when control flow and logic are handled by the shell, especially
since Kakoune’s state can be read from using $kak_
prefixed environment variables.
For example, here’s a small plugin that provides a command called dyn-theme
,
which can dynamically set a color scheme based on the time of day. Users can
also set the dyn_theme_enabled
and dyn_theme_default
options to control its behavior.
declare-option bool dyn_theme_enabled true
declare-option str dyn_theme_default "ashen"
define-command dyn-theme %{
colorscheme %sh{
if [ "$kak_opt_dyn_theme_enabled" = true ]; then
hour="$(date +%H")
if [ "$hour" -lt 18 ]; then
echo "ashen-light"
else
echo "ashen"
fi
else
echo "$kak_opt_dyn_theme_default"
fi
}
}
hook global KakBegin .* dyn-theme
If it’s currently 20:00, then the body of dyn-theme
expands to this:
colorscheme ashen
Setting the options before calling the command results in a different expansion:
set-option global dyn_theme_enabled false
set-option global dyn_theme_default gruvbox-dark
dyn-theme
Actually, there’s no requirement for the core logic to be written in shell. A “shell script” can be as simple as calling another program, so really, plugins can be written in any language. Although POSIX sh is most common because it’s simple and portable, Rust, Python, C and Perl are some other popular choices for Kakoune plugins.
Kakoune plugins are nothing more than configuration & commands that you share with others. As a consequence, you end up learning how to write plugins simply by customizing your own configuration. You’ll define lots of custom commands, use the hook system, and automate common actions with custom keybindings. These are all the same skills needed to write plugins.
Personally, I’ve published over a dozen individual Kakoune plugins, one of which has even been merged into Kakoune itself. It’s also quite common for plugin authors to “publish” plugins simply as part of their public dotfiles. I do this myself for plugins that don’t really warrant their own repository.
Overall, the topic of shell expansions & plugin development is an entirely different can of worms. I haven’t even touched on scopes, highlighters, and FIFOs. Delving further into it is out of scope for this article. My aim here is to share how this extension model works, and why it’s a good choice for Kakoune.
conclusion
Thank you for reading all the way until the end. Becoming a Kakoune “power user” is a challenging but incredibly rewarding path. I hope I was able to make it just a little bit easier for newcomers and experienced users alike. Writing this took considerable time and effort, and it’s by no means exhaustive — there’s still much left to discuss. You can expect much more Kakoune content in future posts.
acknowledgments
I’m grateful to the Kakoune community for their support and advice. Thanks in particular to Screwtape, possumvibes, raiguard, jdugan6240 and axlefublr for contributing their time & expertise to review my work.
further reading
Why Kakoune - The quest for a better code editorThe Vim-Inspired Editor with a Linguistic TwistKakoune, a Punk Rock Text EditorIntro to Kakoune highlightersIntro to Kakoune completionsUnderstanding Kakoune’s Command LanguageThe Kakoune Philosophy
automatically generating demo videos
All demo videos were generated automatically using VHS and a shell script. It’ll get its own blog post eventually; for now, you can see an example below.
Click to read #
First, I created a file demo1/in
, to which I wrote the input.
The quick brown fox jumped over the lazy dog.
Then, I created demo1/tape
, which tells VHS what keys to press once the input buffer is open. In this case, the instructions are very straightforward.
Sleep 500ms
Type@250ms "wwwwwwwwww"
Sleep 1s
I complete the process by running kak-vhs ex15
, which creates a file
ex15.webp
. The script handles starting Kakoune with the correct configuration,
opening the input file, executing the tape, and cleaning up after itself.
kak-vhs
is available here.