(2023-07-29) Switching from (Neo)Vim to... my own text editor
-------------------------------------------------------------
Right now, I kinda feel like a Jedi having built his own lightsaber.
Yes, I got used to writing some tooling myself before, but those mostly were
simple scripts (or not so simple, like FrugalVox or Bopher-NG) and not a
friggin' fullscreen text editor for VT100-compatible terminals on POSIX
systems written in under 777 SLOC of pure ANSI C that I'd switch to since
day one I considered it more or less stable and complete. This is not _the_
largest project I have written in pure C (I reckon Equi is the largest so
far), but it surely is the most important for me personally. Because I use a
text editor literally every day I use any of my personal computers. And
writing own text editor for fun is one thing (and it surely has been fun),
but writing it to replace Vim/NeoVim/Vis/busybox vi/etc as the main tool for
daily usage is totally different. And there really was a lot to consider, as
well as a lot to sacrifice. But let's begin from the beginning.
During my week of exploration (two posts before), I also stumbled upon sta.li
and Oasis Linux. I wasn't able to fully build either of them but I became
more interested in all the lightweight permissive-licensed software that
could be built statically. Before that, I also had discovered Zig project
(that itself is based on an LLVM derivative) and its zig cc subcommand that
allows easy cross-compilation of C code into a bunch of different
architectures. Among the targets, there was static linking with musl libc.
So, I started taking whatever pieces of MIT-licensed, BSD-licensed or public
domain software I could and building them statically against musl (and glibc
whenever musl was impossible). I was generally satisfied with the resulting
binaries size, with one notable exception: text editors. Statically built
Vim binary, for instance, weighs 3433600 bytes, and Vis weighs 644288 bytes.
Really? An entire programming language runtime (Lua 5.3) weighs 363088
bytes! Anyway, this was the first time it hit me that, unless I find a
decent lightweight editor, I must create one myself.
For some more time though, I continued searching. My first options were to
separate busybox or toybox vi, but either of those is too cumbersome to use
and doesn't even offer line wrapping. And having to use horizontal scrolling
really makes me puke. There also were some microEMACS derivatives like mg,
but an editor that requires a double combo to exit simply cannot earn my
trust. Also, even mg was full of sheer nonsense and functionality I never
found myself using. I also found a very ancient (ca. 1991) public domain
editor called ue with puny codebase around 345 SLOC of C, but it physically
had been unable to handle any terminal size other than 80x25 and didn't
handle it very well overall, the code required several fixes just to be able
to compile it with anything at my disposal, and on top of all that, it used
Ctrl+SDEX for arrows, and other keybinding were no less unusable. I liked
the idea of such small codebase though, I just needed something more
practical. This is where I gave up searching and finally started my own
design, only setting a single hard limit: no more than 1000 SLOC of C.
At first, I wanted to make my editor a vi clone. Yep, that silly. I planned
to only implement a subset of POSIX vi that I actually was using day to day.
Then, I understood two simple things: first, even Busybox or Toybox
implementations of vi greatly surpass 1000 SLOC, second, why recreate
someone else's experience if I can tailor the entire application to my own?
Key chords are awful, modality is not very obvious, but what else is left? I
spent a good day or two thinking just about the control scheme I want to
implement. And I settled on the semi-modal controls: it's like chords but
you have a prefix sequence (I call it "modstring" in the docs) instead of
having to keep a modifier key like Ctrl pressed. The modstring I chose
doesn't conflict with any other application: it is double Escape key press.
I found it a no-brainer to get used to. Pressing Esc Esc w to save the text
file is faster than pressing Esc :w Return in vi. It's fascinating how far
you can go when you ditch all the dogmas imposed onto you over all these
years.
After the control scheme had been defined, the process went on much quicker.
Besides the usual routine work about terminal I/O and memory management,
another techincal challenge arose: unlike most "lightweight" alternatives, I
wanted my editor to be fully Unicode-aware as I also write poetry, mostly in
Ukrainian. That's why I decided to not perform any internal codepoint
decoding but just store every UTF-8 character in a 32-bit integer as a
sequence of 1 to 4 bytes, little-endian. Why little-endian? Because they are
much easier to output to the terminal or a file with a single loop with
shifts. And when the shift result is zero, you know that the character
ended, because no valid UTF-8 sequence can contain a null byte. It's a
simple but elegant solution that made a firm distinction between a byte and
a character, and all my further functions operated on characters in 4-byte
integer boxes instead. And for the lower part of ASCII (<128) these
operations aren't different from the usual char type anyway.
Several days had also been spent on implementing and perfecting line wraps,
scrolling and cursor positioning. I had to make a sacrifice though and not
implement whole-word wrapping, as this part already was complicated enough.
As I write code and poetry much more often than I write posts like this
(which are then auto-wrapped with the gmi2txt.sh script), I don't mind not
having whole-word wrapping at all. After this had been done, I fixed the
file loading process and a bazillion of other small things, implemented
useful features like bracket matching and external shell runner and also
added the icing on top of the cake: an in-app help screen that can be called
with Esc Esc h. This way, you can learn this editor even if you have a
single C file or a static binary without the readme.
Given all that, I decided to call this editor just what it represents: nne,
no-nonsense editor. You can see in action by building it from my SourceHut
repo here: [1]. Not only is it below 1000 SLOC but managed to get it under
the limit of 777 (at the time of writing, it's 774 or so). And I have fully
switched to it myself and writing this very post within it. Also, it is
released into public domain, no compromises. Just like public domain
deserves oksh and SQLite, it also deserves a decent lightweight text editor.
Of course it's missing a huge number of features (such as line end
conversion), but that's because I don't really need them in my daily routine
(e.g. if I need to convert the endings, I use dos2unix first). As such, I
consider it feature-complete and will only focus on bugfixes and
optimizations from now on. Although it already is quite fast and small: the
musl-linked static binary for x86_64 currently weighs just 68880 bytes
(compare it to Vis or mg, uh-huh).
Of course, nne is highly opinionated. It doesn't have a way of changing the
tabwidth without recompiling, it auto-replaces all tabs with spaces (to type
a literal tabulation character, you have to press Esc Esc Tab), you can't
turn off autoindentation but you don't have any syntax highlighting (I
explained why in my previous post) or visual line numbering (only in the
status bar). It doesn't even have a real undo, only the modcombo Esc Esc u
to discard unsaved changes. I explained most of these aspects in nne's
README and its FAQ, but this vision is something you just have to accept if
you want to feel comfortable with nne. And if you don't... again, it's
public domain, it's very small (for ANSI C and this set of features),
well-commented and comprehensible code, feel free to make any changes to
make it more suitable to your personal workflow.
For me though, being finally untied from both keychord and modal paradigms,
from any visual overhead such as colors and line number columns and, most
importantly, from the feature creep that I would never use, made me feel
much freer than I was before. I really hope this particular lightsaber makes
a nice addition to my statically built collection and will stay in use as
long as it can.
--- Luxferre ---
[1]:
https://git.sr.ht/~luxferre/nne