(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