i wish i have to never do this again this is so frustrating.
but here we are. i wrote a text editor. in Zig. ported from kilo, the tiny C editor by antirez.
why even do this
kilo is about 1000 lines of C. it does syntax highlighting, search, cursor movement, all directly emitting VT100 escape sequences. no ncurses. no libraries. just raw bytes to stdout.
i thought porting it to Zig would be a weekend project. i was wrong. the Zig compiler has opinions. many opinions. and it will not let you get away with anything.
what i learned
terminal escape sequences are hell
every terminal emulates VT100 differently. cursor positioning, colors, hiding the cursor, showing it again. all of it is just "please do this thing" followed by "maybe it worked."
zig's type system is a strict teacher
in C you can just cast things around and hope for the best. in Zig, the compiler asks you very politely
to explain yourself. @intCast everywhere. error unions that you must handle. optional types
that force you to deal with null properly.
it's annoying. it's also correct. every single time.
memory management is not a joke
the editor allocates memory for rows, for the render buffer, for the highlight array. every allocation needs a matching free. Zig doesn't have a garbage collector. neither does C, but at least in C you can pretend memory leaks don't exist until your program eats 4GB of RAM.
in Zig you get defer. it's beautiful and it's terrifying because it means you actually have
to think about cleanup.
syntax highlighting is surprisingly complex
it starts simple. "if character is digit, color it red." then you add strings. then comments. then multi-line comments that span across rows. then you realize you need to track whether the previous row ended inside a comment. then keywords. then escaped quotes inside strings.
by the end of it you've written a parser and you didn't even realize it.
short learning notes
things i wish someone told me before i started
[1] editorUpdateSyntax is the most important function in the entire
editor. it's where all the highlighting logic lives. the order of checks matters a lot. comments before
strings before numbers before keywords.
[2] zig's std.mem.eql is your best friend for string comparison. forget
strncmp. just use it.
[3] the hl_open_comment flag on each row is the key to multi-line comment
highlighting. when a row's comment state changes, you have to propagate that change to all subsequent
rows. it's recursive and it's beautiful and it made me want to cry.
[4] nonprintable characters will mess up your rendering if you don't handle them. Ctrl-A
through Ctrl-Z should display as ^A through ^Z with inverted colors. otherwise
your terminal beeps at you every time you press a key.
[5] the current_color optimization in editorDrawRows saves a
lot of unnecessary escape sequences. instead of writing \x1b[39m before every single normal
character, track the current color and only emit when it changes.
[6] search highlighting needs to save and restore the hl array. otherwise the blue
highlights from your search query stay forever. static variables and malloc/free
do the job, even if it feels ugly.
[7] the keyword system uses a pipe | character at the end of secondary
keywords to distinguish them from primary keywords. "int|" vs "if". it's a
hack but it works and it's clever.
[8] apparently every thing inserted or deleted needs the whole row remade and shit , never thought so much work went into just writing text ngl.
the zig specifics that got me
@intFromEnum to convert enums to integers. @intCast to convert between integer
types. @as(usize, @intCast(...)) every time you need an array index. the compiler will
catch overflow bugs that would silently corrupt memory in C.
error unions mean you can't just ignore failures. try or catch. no middle
ground. it's exhausting and it's the right way to write software.
optional types (?[]u8) replace nullable pointers. orelse replaces null
checks. it's cleaner but it means you can't just pretend null doesn't exist.
could i do it again?
could i build this from scratch without a guide? probably. but it'd take longer ( maybe 2-3 months). this time i leaned on the kilo reference. next time i'd move faster, but also next time i'm not doing it.
would i do it again
no. absolutely not. never.
but i'm glad i did it once. building a text editor from scratch teaches you things you didn't know you didn't know. how the terminal works. how memory works. how parsing works. how even the simplest programs are actually deeply complex systems of interacting parts.
and zig, for all its strictness, is a good language for this kind of thing. it makes you think about every byte, every allocation, every error. you come out the other side a better programmer, even if you want to throw your laptop into the sea by the end.
i wish i have to never do this again this is so frustrating.
but hey, at least now i have a text editor.
the code is at kilo-zig on github. it compiles. it runs. it highlights syntax. i'll probably rewrite it in an easier language (nim or golang maybe) or just fork something smaller. either way, the agent-driven editor is coming next.