Embrace the Chaos

How to Write a Code Editor from Scratch in 4 Months

By Gus Hogg-Blake on 29 January 2023

Over the past 21 months I’ve written a code editor from the ground up, including: a fast (~120fps) canvas-based editor component; multi-language syntax highlighting with Tree-sitter; snippets; a file browser; find-and-replace with regex and JavaScript expression support; and two completely new features: AST mode and CodePatterns.

In this post I will describe the development approach I’ve followed, which has allowed me to go from zero to a usable editor in 4 months; to daily-drivability in 7 months; and continues to make working on it easy and rewarding as it matures.

Embrace chaos

* A brief aside on algorithms: I find that I start most of my algorithms with while (true) and it usually sticks. It’s like firing up a little spinner that’s ready to do some kind of iterative process, but without the cognitive load of recursion or the friction of having to come up with a boundary condition before writing the body.

When you’ve got a while (true)* loop that searches a tree, it’s gonna throw you an infinite loop every now and then. Fortunately, the more times this happens the better you’ll get at lightweight debugging strategies, and the more familiar you’ll become with the algorithm and the data structures. Soon it won’t seem like a big deal to:

  • quickly add if (iterations === 1000) { debugger; } to the top of the loop;
  • step into the code;
  • walk through a few iterations to get a feel for the repeated pattern that the loop is stuck in;
  • figure out how to solve it.

You could meticulously unit-test each step of the algorithm and validate each assumption as you go, but between changing dependencies, evolving code and requirements, and the fact that you’re almost certainly making assumptions you’re not even aware of (recent example), I’ve found it better to just embrace the chaos and fix the bugs as they come up.

Unit test haphazardly

For nontrivial algorithms I like to start with unit tests and then use them as necessary for debugging later on (bringing them up to date if required). Don’t waste time keeping unit tests constantly up-to-date if the code is working. Once the code is both correct and well-factored, feel free to disable or remove the unit tests so they don’t clutter your test runs with errors after subsequent refactorings.

Use it early

A unique property of writing a code editor is that after a very short bootstrapping period, the thing you’re writing will also be the main tool you’re using to write it. This means two things:

  • you quickly get to a point where you’ve written your own code editor and are using it to write itself, which is a very satisfying achievement; and

  • you always know exactly what to work on (see below).

You always know what to work on

If you’re using the code editor to write itself, the most pressing bugs and missing features will make themselves apparent to you for free. Once you’ve manually indented the fifth line in a row after expecting it to auto-indent, your brain will tell you exactly what it’s tired of! Drop everything and work on that. It’s hard to overstate the value of (almost) never having to think about what you should be working on next.

Refactor when it seems like a good idea

Refactoring can be done early or late; it doesn’t really matter. I’d been using Edita for over a year when I finally got round to making the basic Selection and Cursor objects full classes; prior to that they’d been namespaced utility functions and anonymous objects. Similarly with Tree-sitter-related classes and utils — it was a long time before this:

treeSitterPointToCursor(nodeUtils.endPosition(nodeA)).isBefore(treeSitterPointToCursor(nodeUtils.startPosition(nodeB)))

became this:

nodeA.end.isBefore(nodeB.start)

It’s a great feeling when the code clicks into place in a new, leaner shape — but don’t let that distract you from working on what’s important.

Refactor fearlessly (even without unit tests)

Don’t worry about breaking things. If the new structuring you’ve thought of is better, get stuck in and change the code. You will introduce bugs this way, but because you’re using the editor every day to write itself, you’ll find them quickly (in fact, almost by definition, you’ll tend to find them more quickly the more important they are) and your policy of always working on whatever needs fixing right now will make sure they’re fixed promptly.

An added bonus is that breaking things now and then will randomly show you parts of the code you haven’t seen in a while, so you can give them some attention if they need it.

Keep hacking

Sticking to this approach has served me well and resulted in an editor that frustrates me much less frequently than my old one did, even though it was built and maintained by a large team and on top of an existing open-source core.

Randomly deleting unit tests may seem like insanity to some of you, and in certain contexts it probably is — but writing your own editor is a particular endeavour, and if you embark on it I encourage you to do whatever feels right to you, refactor as you please, and embrace the chaos. Happy hacking!