Seven months searching for an editor

VS Code was my first and only editor. But its performance bothered me: sometimes it uses an enormous amount of CPU and memory. So I started looking for something faster.

My requirements were modest: preview for PDF and images, a terminal, and Git in the editor.

Terminal editors are fast, but blind

The first path led into the terminal. Speed is not a problem there. Preview is: no image, no PDF, no rendered Markdown.

So I tried to retrofit it. In Fresh, the plugins are too limited for that. In LazyVim it works, but not everywhere. Not every terminal cooperates, and it wasn't stable either: LazyVim crashed on image previews in the explorer. Above all of it hung the suspicion that sooner or later I would hit a ceiling built into the terminal itself.

Swap the shell instead of patching the plugin

That turned into my first own approach, and it reverses the task. If the preview doesn't fit into the terminal, then the terminal editor has to go into a window.

Two attempts went in this direction. neoview embeds Neovim via nvim-rs in a UI built with gpui, the framework Zed is built with. The UI talks to Neovim over msgpack-rpc. freshview does the same with the editor Fresh, only more tightly: Fresh runs as a library in the same process, egui provides the window, egui_ratatui translates in between, and mupdf draws PDF and images in floating windows next to it.

The next attempts, flexed and editor-framework, are no longer wrappers but scaffolds on gpui in which nothing is core and everything is a plugin. In editor-framework the plugins are written in Lua and can be reloaded without a rebuild.

Why Rust was dropped

Zed is a good editor, but without PDF preview. And Zed is written in Rust.

Rust was too slow to build for me. neoview and freshview had their difficulties, sluggish rendering here, a crash when closing the PDF preview there. That might have been fixable, or maybe not. Something else decided it: with these compile times I wasn't making progress fast enough. And gpui is Rust, so it went too.

What I was looking for was another fast language that could compete with Zed. Go seemed dated, V too little known, and Zig was just breaking through as a competitor to Rust. So Zig, and there builds are pleasantly faster for me.

Eleven attempts, eight UIs

Each row is its own repo, in the order in which they were created. Some lived for a day, some for a week. All eleven fall within five weeks between early March and early April 2026.

StartedAttemptLanguageUIReason for stopping
March 2neoviewRustgpuiBuild times
March 2freshviewRustegui, egui_ratatuiBuild times
March 8flexedRustgpuiBuild times
March 9editor-frameworkRustgpui, mluaBuild times
March 14slint-rust-editorRustSlintBuild times
March 15mojo-nuklear-editorMojo, C bridgeNuklearMojo unsuitable for writing editors
March 17slint-editorMojoSlint via PythonSlint only reachable via Python
March 22v-gui-editorVV's GUI frameworkScrolling broke after adding explorer and tabs
March 25zed-cloneZigdvuiCrash when scrolling, large files slow
March 26sokol-nanovg-zig-editorZigsokol, NanoVGFont gone after switching to Vulkan, sokol's game loop cost CPU
April 2qt-zigedZigQt 6 via libqt6zigRemained a technical demonstration

On April 3, zid began. There has been no new attempt since.

What came of it

The editor is called zid and is written in Zig. It draws via wgpu, with the renderer locked to the Vulkan backend. Layout is done by Clay. Text rendering is my own: glyph atlas and GPU renderer on top of FreeType.

The editor itself is my own: input, editing commands, search, line wrapping, line numbers, key bindings. Underneath lies third-party code, and deliberately so. Text storage is handled by flow-core, the core library of the editor Flow Control, with buffer, cursor and selection types. Syntax highlighting comes from the same house via tree-sitter. The terminal is Ghostty's emulation, Markdown is rendered by zigdown, PDF is drawn by mupdf.

That is a different cut than in neoview and freshview. There, the whole editor was third-party and only the shell was my own. In zid it is the other way around: the editor is my own, and the third-party parts are the building blocks underneath that have nothing to do with editing.

Rules belong in the code, not in the prompt

zid has a built-in agent that works against a local language model, llama-server with Qwen3-4B. An agent in the editor needs tools, and tools need limits. The obvious way is to write the limits into the system prompt. With a model of this size, though, a prompt line is only a request, and it costs space in a context of 8192 tokens on every request. That is why the limits live in the code. There, however, they have to be airtight.

An example. Anyone who wants to overwrite an existing file gets a confirmation dialog. The model bypassed it on the first try: instead of write_file it sent a replace_text whose search text was the entire file. Formally not an overwrite, in practice exactly that.

Since then, replace_text also counts as an overwrite as soon as it replaces half the file or more:

/// replace_text mit `old` = (fast) ganzer Datei ist ein verkapptes Überschreiben und
/// braucht dieselbe Bestätigung wie write_file auf eine bestehende Datei.
pub fn replaceCountsAsRewrite(file_len: usize, old_len: usize) bool {
    if (file_len == 0) return false;
    return old_len * 2 >= file_len;
}

The same applies to paths. Instead of telling the model to stay inside the project, a function resolves the path and returns nothing if it lies outside. A path that leads out of the project via .. or as an absolute path then cannot produce any file operation at all.

Rules like these are pure functions, and pure functions can be tested. Both have unit tests. On top of that there is an end-to-end script that runs the editor headless against the real model and checks the rejection path: request an overwrite, wait for the dialog, reject, file unchanged, and a path outside the project gets refused. A prompt line you can only hope for.

Conversely, the same holds for capabilities. The command tool gets its choices generated from the editor's command enumeration. Every menu and every keyboard shortcut is thus automatically reachable for the agent, without a second list having to be maintained anywhere.

The question of which window a file opened by the agent appears in is also not an instruction to the model, but a function with a unit test. The system prompt has shrunk to three sentences: the role, the convention that paths are meant relative to the project, and the request to answer briefly in the user's language after tool results. Everything else lives in the code, where it is checked, instead of in the prompt, where it is requested.

What remains open

There are limits that stay in place deliberately. The editor loads exactly one font face, which is why the Markdown view shows bold and italic through colors instead of real font faces. Points like these are recorded as decisions in the project documentation, not as open tasks.

Where it stands today

Of the three requirements from the introduction, two are met: PDF and images open in a tab, the terminal is built in. Git is only halfway there. The explorer shows the status of every file, diff and blame are still missing. Added along the way were things that weren't on the list: Markdown preview, slides from Markdown with export to PDF, go to definition via a language server, quick open and command palette, and the agent. It is built for Linux and Windows.

The source code is open: github.com/gstrainovic/zid. The eleven attempts before it are public as well, each under its name from the table.

How the agent operates and checks the editor without a window is covered in the next article: An AI agent tests my editor without a window.