s/Neovim/Helix/g

マリウス ·

20 min read Original article ↗

I have recently replaced Neovim with Helix globally on my computer as well as most of the other systems that I use. This is a write-up of my experience with the new editor so far.

A while ago I decided that I’d like to challenge my muscle memory (and by that my brain) a little by pushing it out of its comfort zone. Last time I did that was back in 2024 with the switch from my endgame M60 to the Corne v3 and ultimately to the Kunai, which required me to re-learn to type, in a way. I remember how dreadfully inconvenient the experience had been in the first days/weeks, but over two years down the line I’m glad I made the move, and I feel like it has definitely improved my typing and my abilities, let alone my wrist posture.

This time, I decided to challenge myself to switch away from the editor that I migrated to five years ago, Neovim, to Helix, the post-modern text editor. I had wanted to do this for a long time, and it just so happened that yet another troubling story unfolded within the Neovim community that had once again put the unprofessionalism of the core team on full display. And after years of the project being infiltrated and virtually taken over by bullshitters content creators, I decided that it was about time to move along and start this challenge.

Options

Despite many people in the community channel seemingly being loyal members of the Church of Emacs, I didn’t want to go back to an editor that required a GUI, because I enjoy the ability to fire up the exact same editor on any (remote) host (via Mosh) and feel at home right away. After all that was one of the reasons that pushed me to leave the impeccable editor that is Sublime Text.

Note: I know that there’s a TUI version of Emacs, but from what I read online, as well as in the community channel, it is inferior to GUI Emacs and people only use it when they have no other choice. For remote editing it appears that TRAMP is the way to go in Emacs.

Anyhow, with Emacs being predominantly GUI-based, it didn’t quite check the boxes for me. I wanted something that is primarily a text editor and not an almost complete operating system. In addition, while I was never particularly fond of Neovim’s Lua, I’m even less so of Lisp, which would have added another layer of reluctance to the whole challenge.

After scouring the web for a capable alternative to Vim that would ideally be written in Zig, I ended up with two options, neither of which is Zig-based though: Kakoune and Helix.

Kakoune seemed like a very good candidate for something that is similar in spirit to Vim but offers a handful of differentiating features that would make it an interesting pick. However, because it is so similar to Vim, it also has similar requirements with regard to its configuration, making the ramp-up curve relatively steep. For example, just like Vim/Neovim requires you to spend a few hours/days/weeks to properly and thoroughly configure language servers, Kakoune, too, ships those as an external plugin that runs alongside the kak process and is in fact not even written on top of the same C++ stack that Kakoune uses, but in Rust. Kakoune is practically the same assembly of little bits and pieces that Vim/Neovim had always been, which comes with the same benefits, like endless extensibility, but also with the exact same drawbacks with regard to configuration complexity, breaking changes/incompatibilities, and a noticeable hit in terms of efficiency. My final Neovim config wasn’t as bad as what setups like LazyVim, LunarVim, SpaceVim, et al. usually deliver, but as soon as I was working with large files I could definitely feel the editor and, more precisely, its plugins, putting some load on my system.

Helix it is then!

Hence I found the value proposition of Helix very interesting. One editor, one binary, with complex features like language-specific autocompletion and syntax highlighting built right into the core. Not only does this mean that the efficiency aspect might be noticeably better (spoiler: it is!), but also that it doesn’t take me hours/days/weeks to set up what I would call absolute basics in 2026. The only true downside of Helix seemed to be that it’s a Rust project, meaning that compiling takes almost the same time as building the Linux kernel, and that it might be yet another project that’s one wrong pronoun shy of falling apart.

Nevertheless, I caved in to the curiosity and decided to try Helix.

The good (a.k.a. what works but just needs getting used to)

Both of the things that I had to unlearn first come from the same design decision, namely that Helix puts the selection before the action, so that every motion produces a selection and every command works on whatever is selected at that moment. In Vim I press d and the editor waits for a motion that tells it how much to delete, while in Helix d deletes what is already selected, and because a resting cursor is a selection of exactly one character, d cuts a single character. There is no dd, because d is not an operator that takes a motion. Instead, removing a line is x to select it and d to delete it.

The same principle explains why the cursor moves to the next line when I press l at the end of the current one. Helix treats a buffer as one sequence of characters instead of a list of lines, and moving right asks for the next grapheme boundary in that sequence, which after the last character of a line is the line break itself, and after that the first character of the line below. Vim stops at the last character unless whichwrap is configured otherwise, but Helix has no equivalent option to switch the behavior off.

What also follows from the selection-first model is that multiple cursors are built-in. % selects the entire file, s prompts for a regular expression and turns every match inside the current selection into its own cursor, C copies the selection to the next line, A-s splits a selection on line breaks and , reverts back to a single cursor. In Neovim I would have renamed something using :%s/old/new/g, but in Helix it is %, s, the pattern, and then c and the new text, with every occurrence visible and editable while I type. Frankly I didn’t mind the way Neovim did it, but Helix’ approach is definitely more visual.

Speaking of which, Helix ships by default with another visual feature that makes discoverability a lot better. In Neovim I had to manually configure the which-key plugin for that, but in Helix pressing a prefix key automatically shows an infobox listing every continuation together with a description of what it does. Also, : completes typable commands and shows their documentation and their flags while I type, and :tutor opens an interactive introduction that ships inside the binary.

The list of things that work without configuration in Helix is frankly longer than I expected. The languages.toml that ships with 25.07 defines 342 languages, pulls in 303 tree-sitter grammars and preconfigures language servers for 189 of those languages. Helix also supports .editorconfig files. Syntax highlighting, autocompletion, diagnostics, fuzzy pickers, a git diff gutter and a debug adapter client are all there without a single line of configuration, where my Neovim setup needed three dozen plugs (that would break at least once a quarter) to support roughly the same feature set.

And it’s not like Helix’ configuration is some interpreted Lua glued on top of Vimscript. It is a set of easy to read and understand TOML files, and consists of a config.toml, a languages.toml and, optionally, a <theme-name>.toml.

The Neovim setup I linked above was 35 plugins and 1,787 lines of Lua across 21 files, plus another 678 lines for my Netrunner theme, which, reading it, makes it even more absurd. What replaced it is a 65-line config.toml, a languages.toml that includes everything upstream has plus a few overrides, and a 264-line netrunner.toml, which is my own theme. And let’s not forget that each of those 35 Neovim plug repositories is maintained by someone else and can change at any time, and each of them could hit me with malware at any moment.

The config.toml contains the general editor configuration, as well as the custom keybindings. Those can be nested, they can call typable commands, and when they start with @ they replay a key sequence, which means that the simple but stubborn muscle memory can be satisfied, at least in some cases:

V = ["extend_line_below", "select_mode"]
G = "@ge"

Note: Commands and key sequences sadly cannot be mixed, which means that a binding like V = ["extend_line_below", "@ge", "select_mode"] is not possible.

Whatever feature the core does not implement is left to the shell, for which | can be used to pipe the selection through a command and replace it with the output. Similarly, ! can be used to insert the output of a command before the selection, and $ can be used to only keep the selections for which a command exits with a zero status.

sort can sort a block of lines, column can align a table, and fmt can reflow a paragraph. And as with most command line tools, you can use C-z to suspend the editor and type fg to bring it back.

Speaking of muscle memory: There is a fork called Evil Helix that restores the Vim keymap for people who want the new core without having to re-learn the motions and commands. While I understand the appeal, installing it would have defeated the point of the exercise for me. However, if you’re on the lookout for an alternative editor to escape the rollercoaster that the Vim ecosystems have become, this might be the thing for you.

Efficiency

To put the efficiency claim that I mentioned earlier into actual numbers, I built Markdown files by concatenating the last 180 posts of this site and cutting the result down to 100 KB, 1 MB, 5 MB and 25 MB, the largest of which is 543,015 lines. I ran Neovim with my old configuration, with all 35 plugins and the 70 tree-sitter parsers compiled. Both editors ran on the same 8-vCPU/16GB RAM KVM virtual machine that I used to simulate a resource-strapped environment on my Intel Core Ultra X7 368H. Both used a terminal window of 50 by 200 characters, and every number is the median of three runs after a warm-up. First screen describes the point at which the initial paint completed, and redraws done is when the editor stopped writing to the terminal.

FileEditorFirst screenRedraws donePeak RSSCPU
100 KBHelix0.47s0.85s38 MB0.5s
100 KBNeovim0.13s0.73s65 MB0.8s
1 MBHelix0.62s1.00s76 MB0.6s
1 MBNeovim0.12s1.31s117 MB1.4s
5 MBHelix0.63s1.01s85 MB0.6s
5 MBNeovim0.14s0.85s300 MB2.9s
25 MBHelix0.68s1.06s111 MB0.7s
25 MBNeovim0.12s2.13s329 MB4.1s

As you can see, Neovim paints its first full screen roughly five times faster, in 0.12s against 0.62s on the 1 MB file, because Helix appears to read and parse the whole file before it draws anything. Neovim, instead, draws immediately and keeps working in the background. So much for my assumption that the Rust binary would win the efficiency duel, huh?

Well, not so fast, cowboy! As you can see in the next columns everything after that initial load/first paint goes the opposite way. Opening the 5 MB file costs Neovim 300 MB of resident memory and 2.9 seconds of CPU, against 85 MB and 0.6 seconds for Helix, and at 25 MB it is 329 MB and 4.1 seconds against 111 MB and 0.7 seconds. Helix stays close to flat across a 250-fold range in file size, while Neovim’s cost grows with every step.

As for the syntax parser, both editors have a deadline and drop highlighting when it is missed, which I measured by counting the distinct foreground colors on a rendered screen whose content is identical in every file. Helix highlights reliably up to about 1.02 MB, intermittently up to 1.12 MB and not at all above 1.13 MB, while Neovim is solid at 2 to 3 MB but stops at 4 MB. That is also why Neovim finishes redrawing the 5 MB file faster than the 1 MB one, since past its own deadline there is a lot less left to do.

On the 100 KB file, where both editors highlight the entire screen, Helix needs 38 MB against 65 MB and 0.5 seconds of CPU against 0.8. Jumping to the last line redraws consistently in about 43 ms in Helix, and between 21 and 111 ms in Neovim, so neither of them is really sluggish at that, but Neovim can, at times, take almost three times the time to redraw. My spelllang of six languages in Neovim, which I had assumed to be one of the expensive parts, doesn’t really cost anything, as Neovim with and without spell checking is within the presented numbers.

Single line of death

Many lines are one problem, however a single line of extreme width is a very different one, and it is one of the things that would normally bring Vim down to its knees. I joined the same 180 posts into one line of 5,201,084 characters, 5 MB in total, and ran the same session in both editors: search for keyboard, step through the matches, replace all of them with trackball, jump to the last character with A and to the first one with I, and then insert single characters at the very beginning. Every number is the time from the keystroke until the editor stopped drawing and stopped using CPU.

OperationHelixNeovim
Open the file0.65s5.76s
Search for the pattern0.08s0.24s
Jump to the next match0.01s0.09s
Replace all 471 matches5.68s1.70s
A, insert at the last character5.11s7.91s
I, insert at the first character0.59s1.01s
Insert one character at the beginning0.26snever finished
Peak RSS while searching and replacing112 MB354 MB
Peak RSS while inserting at the start114 MB9.1 GB

Helix opens that file almost nine times faster and finds the pattern quicker. Neovim, however, beats Helix in the replacement task by a wide margin, because :%s/../../g is a single operation while Helix builds 471 cursors and then edits every one of them.

Inserting a single character at the beginning of that line costs Helix about a quarter of a second. Neovim pegs one core at 100 percent and allocates roughly 25 MB of memory per second while drawing absolutely nothing, and it kept doing that for the two minutes after which I cut the measurement off. By that time the session was at 9.1 GB of resident memory, but the character still hadn’t made it into the buffer.

To be fair to Neovim, this one is not its direct fault, though. The exact same file and the exact same keystroke in nvim --clean inserts the character in well under a second and drops back to zero CPU at 150 MB, so what melts down here is my configuration, which is most likely the combination of spell checking and tree-sitter on a line that is five million characters wide.

The bad (a.k.a. missing quality of life)

If you think that what I wrote about Helix so far reads too good to be true, and that there’s definitely a catch somewhere, then your intuition still works.

For example, one thing I run into every day is the front matter of the posts on this site. In Neovim, a small plugin rewrote the lastmod field of the Markdown file on every write, and :HugoHelperDateIsNow set the date of a post I had just created. Helix, on the other hand, does not have a built-in feature like that, nor does it offer a user-facing API to custom build this.

The only thing that runs on write is the formatter that is configured for the language, which takes the buffer on standard input and writes it back on standard output. A front matter updater therefore has to be a filter over the entire document, and it has to coexist with whatever formatter is already configured for Markdown. Inserting a timestamp by hand is !date -Iseconds, which is fine for the date of a new post, but definitely more cumbersome for a lastmod that is supposed to be maintained without me thinking about it.

Another example concerns the keybindings. A binding made of two subsequent combinations works, so a [keys.normal."C-q"] section that contains "C-q" = ":quit-all!" gives me a C-q C-q chord. Pressing the first half pops up the infobox that shows the second half. What doesn’t work, however, is having the same key bound on its own and as the prefix of a longer sequence, which is what I had in Neovim:

vim.keymap.set("n", "<C-q>", ":qa<CR>", { silent = true, desc = "Quit" })
vim.keymap.set(
  "n",
  "<C-q><C-q>",
  ":qa!<CR>",
  { silent = true, desc = "Force quit" }
)

The keymap’s nodes can be either commands or submaps, but Helix doesn’t support a timeout that would disambiguate the two the way Vim’s timeoutlen does. Hence a key is either a binding or a prefix, and <C-q> for quitting next to <C-q><C-q> for quitting without saving is sadly not something one can express in Helix.

A handful of smaller things from Vim have no equivalent either. There are no marks, and the “Marks” issue has been open since September 2021. Getting back to a position therefore requires a search or the jumplist.

There is also no persistent undo, which means that closing a buffer discards its history. Two attempts at adding this feature were closed without being merged. There is also no session restore, and . repeats only the last insert, not the last change the way it does in Vim, so a delete or a replace can’t be repeated with it. What’s left is A-. to repeat the last motion and g. to jump to the last modification.

Spell checking is another thing that’s missing entirely. Vim and Neovim ship :set spell with dictionaries, but Helix has nothing of the sort, so the only way to get my typos underlined is via a language server such as codebook or harper-ls, configured per language in languages.toml.

Then there’s the Git support, which is basically just the diff gutter. Helix marks added, modified and deleted lines against HEAD by default and [g and ]g jump between the changes, however there is no blame and no staging of individual hunks. C-z and fg or a second pane can cover for that, but for people who would like to avoid the context switch for adding files or committing, an integration of the most basic Git functions would make sense.

Speaking of files, the file picker gained a directory-browsing mode in 25.07 (#11285), but a persistent tree sidebar of the neo-tree kind still doesn’t exist in Helix. I know that you’re not supposed to navigate via the sidebar and that fuzzy-finding and buffers are theoretically superior, but I also know how a big project structure can sometimes become increasingly difficult to efficiently fuzzy-find through.

Lastly, the plugin system. There is no plugin system. Steel, the Scheme dialect that might become one, has been a draft pull request (#8675) since October 2023, and it has 491 commits and 334 comments without being merged. It appears that in various forks it is already in use, which makes it even weirder. But that is essentially why there are no user-defined commands, no scripting and no ecosystem. However, it is also why nothing in my configuration has broken since the day I wrote it. Both things are consequences of the lack of plugins, and I am not sure yet which of the two I value more.

The ugly (a.k.a. missing essentials)

Now to some more essential things that I would argue are crucial for many engineers and developers, but that are nowhere to be found in Helix.

Amongst the most important features is continuous hard wrap. Helix does soft-wrap, at text-width when soft-wrap.wrap-at-text-width is set, and it does hard-wrap on demand with :reflow, which rewraps the selected lines to the width given as an argument or to the configured text-width. What it doesn’t do is wrap while I type, the way textwidth=80 together with formatoptions+=t does in Vim, and the issue has been open since April 2022. Selecting a paragraph and calling :reflow from a keybinding works but it is obviously very cumbersome. What I have settled on for now is an on-save formatter such as deno fmt or prettier --prose-wrap always, which however means that the formatter rewraps the whole file including the paragraphs that I never touched, which turns a three-line edit into a diff over an entire file.

Another essential that’s missing is the file watcher. Helix doesn’t watch the files it has open. When a buffer changes on disk, after a git checkout, a rebase, a formatter that ran in a different window or a sync over Syncthing, the editor keeps showing the old content until I run :reload or :reload-all. It does at least reject the write with file modified by an external process, use :w! to overwrite, so nothing gets overwritten by accident, however all of the reloading is manual, while Neovim covers this with autoread and checktime. The pull request that implements file watching was opened in October 2025 and is still open.

Then, there is the overtype mode, which also doesn’t exist at all. In Vim, R replaces characters as I type until I press escape, which is how a fixed-width table, a box drawing or a column-aligned configuration file gets edited without pushing everything to the right. Helix uses r to replace every character of the selection with a single one and R to paste over the selection from a register. However, neither of them is sticky. The request has been open since February 2023 and a pull request implementing it since July 2025, and until that is merged, the workaround is selecting exactly as many characters as I am about to type and then replacing them.

Speaking of selections, a line-wise selection mode is missing as well. Vim’s V selects whole lines and j and k then extend the selection in both directions, while Helix has x to extend the selection one line downward, X to snap whatever is selected to whole lines and A-x to shrink it back to them, none of which extends upward. Binding V = ["extend_line_below", "select_mode"] covers most of it, however extending upward still means entering select mode, moving with k and pressing X to put the selection back on line boundaries.

Lastly, code folding doesn’t exist, and it is the one complaint I came across most often while reading through the issue tracker, although to be fair I personally don’t use this feature as extensively, so it’s not as much of a big deal for me. The issue has been open since March 2022 and has 48 comments, and a pull request has been open since October 2025. Vim has been folding since version 6.0 in 2001 and Neovim added tree-sitter-based folds on top of that, while Helix offers neither automatic nor manual folds, which for navigating a long file leaves the jumplist, the symbol picker and scrolling.

Verdict

With all that said, I’m going to stick to Helix for the time being, despite its bad and ugly parts. It is an overall solid piece of software, it has a relatively big community around it, and it is available on every platform that I care about. On top of that, it uses a fraction of the memory and CPU of the Neovim setup that I had in place before. It is also far easier to build on my Gentoo machine than Neovim.

However, I do hope that Helix will continue to evolve into something that is able to offer slightly more flexibility than it does today, without compromising too much on its approach of delivering an editor with batteries included. I, too, hope that we’re not going to witness similarly poor stewardship as has been the case with Neovim, and that the Helix project can continue living somewhat in the shadows, overlooked by most content creators.