Over the winter of 2020, during the time where I had planned to be working on a port of differential dataflow, I instead wrote a text editor which I now use for all my writing and coding.
This was perhaps not the most productive use of my time, but it wasn't wasted either:
It's a huge project to write a text editor that people will actually use, but it turns out to be fairly easy to write a text editor that only one person will use. Far from being a toy project, this is actually the editor I use day-to-day. Yet the stable branch is only ~5kloc and I expect that it will still be under 10kloc by the time I've replaced all the features of emacs that I actually used.
Good performance is motivating. When the tool that I spend the majority of my day interacting with can handle typing in a 30kloc file without dropping frames, can scroll smoothly at 60fps, can search for files without waiting several seconds for the fuzzy finder to catch up, can open new windows fast enough that I can just keep typing and all the keypresses end up in the new window, can show completions instantly instead of gating them behind an idle delay ... I want to spend more time coding. Every time some random emacs hiccup interrupts my muscle memory and makes me context switch from thinking about my code to thinking about my editor, that just adds a little bit of frustration to my day and over time it erodes my desire to come back.
It's the first realistic project I've written in zig. The tradeoffs embedded in zig (no guarantees of memory safety, no type-checking of un-instantiated generic code) are unlikely to cause problems in smaller projects so I needed to see some real code in action to discover whether zig is a good idea or not.
I followed a list of design principles, some of which are strongly counter to the prevailing tech-blogger wisdom on how to write good software, which I think are worth putting to the test publicly.
- Only solve problems that I actually have eg ascii-only rendering because I don't have any non-ascii files on my hard-drive
- Hardcode everything rather than wasting complexity on configuration or extensibility
- Avoid callbacks, function pointers and polymorphism - optimize for being able to follow the entire control flow with go-to-definition
- Make control flow tree-like where possible - things lower down in the hierarchy are not allowed to call back into things higher up in the hierarchy
- Centralize decisions in one place rather than relying on the order of side-effect in multiple places (eg appending callbacks onto a list at runtime)
- Do the dumbest possible thing until brute force has been proved not to work
- Plan performance for specific numbers eg expect code files to be <=2mb (my work journal is ~1.5mb, the zig compiler has several files ~300kb, codemirror output is ~400kb)
- It turns out I need to write a lot more blog posts this year, and this is excellent blog fodder :)
The editor is called 'focus' and lives here. This series of posts will just trail the development for now, but I hope once the codebase settles down to tidy these up and make them into a mini-book showing how to build a text editor from scratch.
Fun fact - this was initially the repo for my pinephone experiments. I was writing my own tiny GUI library, it needed a text input widget, things escalated.