I write this in an editor called Kakoune. I started using it almost exactly two months ago as an experiment because I was running into issues with NeoVim: configuration errors/warnings even though (or because) I didn't change anything, incompatible dependencies causing issues, and a few minor other issues.
These types of issues aren't really showstoppers in many cases; the problem was really the feeling I had about them. I didn't feel like I wanted to fix any of them. It was the clearest signal I could get that maybe NeoVim isn't really for me anymore. Maybe I'd just changed and it was time to move on to something else.
NeoVim but less, and my own?
At first that "something else" was just NeoVim but with all my plugins (except the ones I made myself) deleted, starting over from scratch. I know from experience with many other things that this is really the ideal for me in most cases; I simply don't want to use other people's software if I can avoid it, and sometimes the feature that other people implement is best skipped entirely.
So I looked into what I would have to do to make this work, but there was really only one major showstopper that put the breaks on the entire idea. In the interest of not being unnecessarily inflammatory: I think Lua is a poor choice for a plugin language, and I think writing Lua for NeoVim is a particularly poor experience on top of that.
So what are the alternatives here, then?
What about Emacs?
Emacs? I think Emacs is a great concept, but once you've used any Lisp I don't know that it's possible to use elisp without gritting your teeth. It's especially grating to use elisp after using something more well designed like Racket, or basically any Scheme implementation.
Helix
Some time ago I heard about Helix, which was supposed to make editing more intuitive than Vim by placing movements before verbs. I briefly tried it once but found it confusing. I wasn't ready for that shift and it seemed pointless to me at the time.
I considered Helix this time around, but it became clear that I was going to have to rely on whatever the Helix people had decided to put in the editor if I wanted to use it. This is a non-starter, because I'm just opting in to whatever, with no real recourse to change, add or remove things.
Kakoune
Back when I tried Helix I also heard that it was inspired by an editor called Kakoune. It had the same type of confusing inversion of movement-verb-order, but it also had something a lot more interesting: It's made to call other programs. That part stuck out to me a lot more than any kind of editing quirk, because it suggested that you didn't really want or need any particular blessed scripting language to be prominent.
The Kakoune manual has a very clear and telling section in its "Writing scripts" section:
Interaction with external tools from a Kakoune session is supported through the use of scripts. Once loaded by the editor (either automatically or manually), they allow extending the functionalities provided by default through commands and hooks.
Their implementation should be kept as simple as possible, as they are not meant to be generic tools themselves but a mere API to actual software.
"a mere API to actual software". Your actual software is written in other languages, because Kakoune is not trying to have you create grand cathedrals in its scripting language, it's not trying to give you a programming language that you can solve anything in, it's just trying to optimize for handling the results of other programs.
Writing "actual software" is easy
Like most programmers who've been doing this for a while I've got a bunch of languages I'm very comfortable making things in, and solving just about any problem is really just a matter of thinking about the solution's architecture, the features and attributes I want the solution to have, and the tools that are needed to create that solution. In practice, for me, this means that I'm going to pick Odin as the implementation language about 19/20 times. This is easy, straight forward, and it's what I want to do in order to have things work. I can count on zero hands the amount of times I've wanted to use an editor's scripting language to solve a problem; it has always just been a necessity because things are designed that way.
How do I use Kakoune now?
I use zero external plugins, all of the functionality I use is either shipped with Kakoune or I've written it myself. I solve the exact problems I'm actually having, which means that I don't create kitchen sink solutions like writing my own LSP clients/servers in order to solve things, because it's not actually the thing I want. I often want something more specific, and I can often solve that much more easily.
Odin Documentation Lookup
From previous work (on my livestream), I had already created a documentation lookup program, and so I just integrated that into Kakoune with tab-completion for the Odin core library folders. This was very straight forward to integrate and solves almost all of my code discovery, which is a large part of what I used to use LSP for:
define-command doc-odin -override -docstring "Look up a Odin module" -params 1 %{
evaluate-commands %sh{
buffer_name=$(echo "*odin-lookup-$1*" | sed 's/\//-/g' )
echo fifo -name $buffer_name -- lookup "$ODIN_ROOT/$1"
echo set-option buffer="$buffer_name" filetype markdown
}
}
complete-command -menu doc-odin shell-script-candidates %{
$kak_config/scripts/shell-complete-odin-core.sh
}
#!/usr/bin/env bash
pushd $ODIN_ROOT/
fd -t d . core vendor
popd
A build runner
With NeoVim I had resorted to creating my own build-script runner where I could pick which build script I was going to run and get the results in-editor. I recreated this in Kakoune by simply reading a JSON-file from the project directory that specified which build command to run, then an error pattern to pick up, and plugging into the already existing make integration that Kakoune has to be able to jump to positions of errors. With some basic (and eventually some not so basic) knowledge of jq this was basically just stitching Kakoune + two shell scripts together, in order to get an actual nice build/edit/rebuild workflow:
define-command pick-build-command -docstring "Allows the user to choose a build command" \
-override %{
prompt -menu -shell-script-candidates %{
jq "keys[]" ./build-commands.json
} "Build command: " %{
evaluate-commands %sh{
$kak_config/scripts/set-build-command.sh "$kak_text"
}
}
}
The most interesting (and maybe somewhat unintuitive at first) thing about the above script is that it runs evaluate-commands with a %sh{ ... } block. This means that whatever the program we call outputs, we'll execute as Kakoune commands. Our set-build-command.sh script reflects this:
#!/usr/bin/env bash
BUILD_CONFIGURATION="$1"
BUILD_COMMAND=`jq ".$BUILD_CONFIGURATION.command" ./build-commands.json`
ERROR_PATTERN=$(jq --raw-output ".$BUILD_CONFIGURATION.error_pattern" ./build-commands.json)
echo "set-option global makecmd $BUILD_COMMAND"
echo "set-option global make_error_pattern \"$ERROR_PATTERN\""
rg for searching
I've created an rg command that will run rg with a specific pattern in the project, and trivially created a filetype called rg where pressing enter on a given result will open the matching line in the buffer:
define-command rg -override -params 1.. -docstring "rg from CWD" %{
fifo -name *rg* -- rg --line-number --column %arg{@}
set-option buffer=*rg* filetype rg
}
define-command rg-go-to-file-line -hidden -override -params 0 %{
execute-keys g h 3t: s \A^([^:\n]+):(\d+):(\d+)\z<ret>
edit %reg{1} %reg{2} %reg{3}
}
The most interesting part above is that you can really do some interesting things just by stitching Kakoune commands together. In this script for going to a matching line we execute the keys for going to the beginning of a line, selecting everything between the beginning and the third colon we find on a line, then selecting the filename, line and column into registers, then editing <filename> <line> <column>.
I have a similarly Kakoune-centric solution for toggling markdown checkboxes:
define-command markdown-toggle-checkbox -override -docstring "Toggles a markdown checkbox" %{
try %{
execute-keys -draft x 1s '\[( )\]'<ret> r x
} catch %{
try %{
execute-keys -draft x 1s '\[(x)\]'<ret> r <space>
} catch %{
}
}
}
The try/catch pairs here make it so that if the text we are selecting with 1s (select inside of selection, i.e. narrow down our selection) fails, we either do a fallback or if the fallback fails we do nothing at all.
Creating a command center, just because I can
I've also done things with Kakoune that weren't strictly necessary but just because it was so easy:
- Integrated the
ddgCLI tool in order to search DuckDuckGo and get the results in-editor - Integrated
w3mso I can just select a URL and show the web page rendered in a buffer - Used
mpcto control music playback from the editor
Most of these types of things are just either mappings that call nop %sh{ ... } to execute something, or fifo ... -- command in order to execute something and display the results, possibly while the program is still running.
A week is not a long time to learn and love an editor
All of the things I've mentioned above I did during my first week of using Kakoune. This is in part because I forced myself to use strictly only Kakoune, and so I had to solve some of these things (and I'm happier for it) but it's also because Kakoune is a much better platform than most for people who either already have their own solutions to things, or can create them.
I can't seem to replicate this in Emacs
Disclaimer: I'm not saying Emacs is bad, but it's ultimately worse for me than Kakoune
When I had created my command center in Kakoune I was beginning to think that maybe what I really appreciated about Kakoune was the nature of what I had created: My own command center. So I decided to give Emacs one more shot. I've used Emacs for 5+ years earlier, on and off, so Emacs isn't really new to me but I had also given it up before.
I gave Emacs one week of hardcore usage where I again forced myself to use only it, and at first I was dead set on using literally no external scripts to accomplish this. Pretty soon I was disabused of this notion and resigned myself to cloning emacs-bedrock. It's not bad, but I already felt like this was somewhat of a failed experiment because of it. Nevertheless, I continued and focused on trying to implement the actual things I was interested in for my command center.
Integration points
Emacs has entirely way too many integration points. The whole program is just a set of integration points. It's incredibly confusing to make your own features for Emacs because most of your time seems to be taken up just by endless searching for the right function and/or facility to use. It's like an editor-specific version of choice/analysis paralysis. Obviously I think you can get over this, but it's quite the hurdle.
This problem is made slightly better because of Emacs' amazing lookup features, but even so I was unable in most cases to reach a point where I was confident I had found the correct integration point for almost anything. Being able to search an endless sea of functions, variables and other things is neat, but it's not as useful as already having the answer because it's more or less obvious, which is what Emacs was competing with in this case.
Where does that leave me?
Kakoune spoiled me: The ability to run other programs easily, integrate them with almost any editor facility I can think of, and to spend most of my time just writing real programs not stuck in an editor... It's too good. I feel like I can't really go back to anything else at this point. I also didn't mention in this post that you can send commands to Kakoune in order to control it. This in itself isn't necessarily new, but it did make me think about the nature of Kakoune, what program it actually is. It's a facilitator of buffer contents more than anything else, a middle-man that handles
communication to and from programs.
Taking inspiration from Kakoune
With this fresh in my mind I created a coding agent for myself that communicates with registered frontend programs via a FIFO/socket, takes commands via a FIFO/socket and can (of course) be run and interacted with entirely in Kakoune.
The configuration language for the agent also looks suspiciously like KakScript, because it's neat and obviously I had to put the inspiration on display:
define-tool bash %description{{
Runs a command in bash
}} %parameters{
command string! The full command with arguments
} %command{{
bash -c "$hugin_parameter_command"
}}
define-tool tree %description{
Gives you the file tree starting at a base path.
} %parameters{
path string! The root path to get the file tree of
depth integer! The depth of the printed tree, set to 2 or 3
} %command{{
tree -L$hugin_parameter_depth "$hugin_parameter_path"
}}
define-tool pi-web-search %description{
Research a question on the web by spawning a sub-agent.
Provide both search terms and the precise question to answer; the sub-agent returns Markdown with source links and summaries.
Use this when research, current information, external documentation, comparisons or source-backed facts are needed.
} %parameters{
query string! Concise web search terms to use as the starting point for finding relevant sources
question string! The exact research question that is to be answered. Phrase it as a concrete question and include needed context, constraints and user intent. Summaries must be relevant to this question.
} %command{{
pi \
--print --no-session --no-tools --no-extensions \
--extension "/home/gonz/code/pi/gonz-pi/extensions/cursor-provider/" --no-skills \
--no-prompt-templates --no-context-files \
--model "cursor/composer-2.5" \
--append-system-prompt "/home/gonz/code/odin/hugin/trunk/pi-web-search-prompt.txt" \
"Search query: $hugin_parameter_query\nQuestion to answer: $question\nSearch the web, open relevant results, and return only the required Markdown report. each source summary must explain only what is relevant to answering the question."
}}
Other fun stuff
I also created an agent that was stuck in Kakoune and made a playlist of results running with different models.
Top comments (0)