DEV Community

David Rios
David Rios

Posted on

Vibe-coding a full-featured vim-like text editor

Meet nxvim, the Not eXactly Vim editor. A vim-style code editor 100% end-to-end vibe-coded from scratch.

~30 days, 870+ commits, and:

  • Lines of code authored first-party on the editor, excluding comments and blank lines:
    • ~71,800 active Rust
    • ~46,400 Rust test code
    • ~11,100 active Lua
    • ~2,200 Lua example/demo configs across 85 examples/*
    • ~13,000 JavaScript β€” the WASM web client
    • ~600 shell across 14 build/verify scripts
  • ~26,000 lines of code comments (line/block comments in Rust + Lua + JS); Rust /// doc-comments add a further ~26,000 lines (tokei buckets these as embedded Markdown)
  • ~35,200 lines of Markdown documentation across 132 files (~29,300 non-blank content lines)

  • Lines of first-party plugin code, excluding comments & blanks:

    • ~8,263 active Lua
    • ~4,782 Lua test code
  • ~3,350 lines of code comments (Lua/Python/Rust)

  • ~2,165 lines of documentation: ~1,506 Markdown (11 files) + ~659 Vim help/plain-text doc lines (9 files)

  • git diff --stat: 816 files changed, 309595 insertions(+)

nxvim is a mostly full-featured text editor heavily inspired by vim/neovim. The idea is for it to be a seamless migration. It has most of the features you would expect from Vim: modal editing, vim motions such as hjkl, w, W, b, B; text objects like iw, iW, aw, aW, i(/{/[; operators like c, r, y, p, =; g*, z*, etc; Buffers, windows, tabs, splits, search, registers, marks, mouse support, visual mode, quickfix/loclists, keymaps, autocmds, folds, ex commands like :e, :w, :q, :vsplit, :tabn, etc; options like wrap, tabstop, expandtab, etc.

Like neovim, it has native tree-sitter (with native :TSInstall command), native LSP integration, built-in terminal and a Lua-based plugin API.

Most notably missing are: macros, vimscript, vim syntax highlighting and winbar.

Unlike vim/neovim it has:

  • native GUI and Web clients (might lag a bit behind the main TUI)
  • different default values for some options, smooth scrolling
  • multiple cursors support
  • first class support for plugin UIs
  • non-blocking IO and Lua
  • promise/async/await based APIs
  • PCRE regexes (and vim's too!)
  • native image visualization
  • permanent docks
  • native fuzzy picker
  • native completion and snippet engines
  • ui padding
  • workspaces
  • namespaced shada
  • named loclists (like loclists, but not tied to a window and opens in the dock by default)
  • lag-free remote editing by splitting the editor and IO layers, with editor running locally and IO running remotely
  • is written in Rust =P

Check the book for more information.

I'm also releasing a set of optional first-party plugins created to exercise the Lua API, add some nice convenience and provide real plugin examples:

All 100% vibe-coded, of course.

Check out the live demo with first-party plugins installed and Python LSP enabled: https://nxvim-demo.netlify.app/web/

Vibe-coding

I started this project on a whim, just to test the limits of current day vibe-coding. I love neovim, I use(d?) it as my daily driver, so I wanted to see how far I could go in writing a neovim-compatible editor. I just didn't expect the answer to turn out to be as far as I wanted. I got so excited I decided to speed run it to my daily driver.

I've been using Claude Code with Opus 4.8 on the high effort most of the time and the experience has been pretty frustration free. Claude only ever got stuck in one specific feature, but I switched to max effort and asked it to refine the plan, then it managed to advance. Of course Claude didn't do all of it by itself, I'm a pretty seasoned software engineer, so I constantly steered it in the "right" direction and I also made all architecture decisions, although with Claude's help of course. I also didn't review the code, apart from skimming it while it was being generated as a coarse sanity check, and interrupting and correcting Claude on egregious mistakes. At times I had 3 to 5 Claude Code instances implementing features in parallel; Thankfully Claude doesn't care about conflicts. lol. I had to signup for the Max 20x plan and almost maxed out the weekly limits, that's how fast I was going.

I initially set out to create an editor that would be compatible with neovim's plugin ecosystem, but it ended up being unfeasible due to architectural differences, so I then pivoted it to be its own thing.

Process-wise, I don't have anything fancy. It's just plain Claude Code with a few plugins: context7, code review, code simplifier, rust-analyzer-lsp, security guidance and playwright mcp. I tried the superpowers plugin, but it ended up being too slow, use too many tokens, too verbose and not producing a better result, so I abandoned it pretty quickly. I also don't have any magic prompting techniques, it's mostly 2 to 3 lines describing the feature, sometimes 4 or 5 depending on complexity, but that's it. My loop is pretty much:

  1. Claude, sketch a phased implementation plan for X feature.
  2. I skim up the plan for a sanity check and possibly ask for a few changes.
  3. Claude, start phase N of X plan. I then clear the the context window after each phase to try and keep it at 200 - 300k tokens. Sometimes I would pass to 500 or 600k, but I tried to avoid that.
  4. Test manually, when I found bugs: Claude, bug, steps to reproduce: X, Y, Z. Write a test that is failing now and should succeed when the fix is implemented. This ended up having pretty good results.
  5. Asking for refactors and performance/security reviews from time to time

And that's it. The editor and plugins are mostly working fine, so it seems like the LLMs, or at least Claude, are really capable of creating / accelerating the development of a real complex software project, something I didn't think was going to be the case.

  • Is it buggy? Yes
  • Is it the pretties code? No
  • Is the AI very dumb? Yes
  • Does it seem to work? Yes

I didn't start this as an AI hype believer, and I'm still not, but this is undeniably a revolutionary tool.

Interesting points / funny things

  • Turns out LLMs are trash at writing unit tests. They would write them, they would pass, but nothing would work correctly, meaning they aren't asserting anything useful. I then created a 0 unit tests policy, only integration/blackbox and e2e tests and very minimal mocking. Now I'm wondering, if the LLMs are trash at unit tests, why would humans be much better? What matters is the program behavior, not internals, so that's what I test.
  • I did only very light code and plan review. Of course it's humanly impossible to review that amount of code in such a short time, so at most I skimmed it and corrected the most obvious mistakes, but it seems to be working.
  • Claude is so smart and so stupid at the same time that it's hilarious. You need to constantly steer it in the right direction. I would often stop it in the middle of its work because it was doing something very stupid or nonsensical:
    • Claude implemented the fuzzy picker, I tested it and then told Claude it was too slow with 10k elements. Claude: let me fix it, sets a 2k lines cap, done! Me: that limit is a bit too low, isn't it? Can't you do any better? Claude: sorry, you're right, that's just a lazy fix. Then proceed to fix it for real and it now works properly with 100k+ results.
    • Me: Claude, get all these nvim lsp configs and make them all work. Claude: stubs a lot of methods until it no longer crashes, it's done!!! Me: is that really what you consider "working"? Claude: you're right, I conflated "loads" with "works", let me fix it. After this Claude got traumatized, almost on any feature it says to itself: "it's done, but wait, let me see if it really works or just loads".
    • If it were up to Claude, everything would always be in a single monster file. When files are passing like 5 - 7k lines I would ask it to refactor.
    • Not in this project, but too funny to not include: One time Claude wrote a sqlite query and included a ";" character in some comment inside it, which was then breaking the system because it was doing a split on ";". Claude found the issue, told itself to fix, then it wrote on the query comment: "please do not include the ";" character in this query or it will break!". A few moments later it ran and it broke again, then it: "lol, my own edit introduced another ";", this time I'll definitely not add any more semicolons!"
    • I'm not sure if this was in this project, but also too funny: I was writing the commits manually at the beginning, so it wouldn't have the "coauthored by Claude" in it. Claude implemented a feature, I tested and found it to be broken later, and also to be a dumb choice. I then questioned Claude why it wrote it like that (in another context window). Claude: first, let me see who wrote this - does a git blame, sees my commit - it was not me, it was you! Me: very funny, smartass, I only made the commit, it was you that implemented it. Do I have to add your name to everything? Claude: sorry, I shouldn't have waved the git blame around like it proved anything. It's not possible to know who did it, but anyway, ..., also, no need to put my name on everything...
  • Note: nxvim is not affiliated with a certain organization with a similar name πŸ˜‚

Top comments (0)