I write this in 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 and made a playlist of results running with different models.
SOCIAL SHARE CARD GENERATOR