Why Neovim is Better than Vim

I know Vim better than most. Vim was my first real text editor.[1] I used it for years. I helped write the Floobits plugin for Vim. I’ve delved into Vim’s source code to figure out its workings. I even helped write a patch (though it was rejected). Considering these credentials, I hope you’ll accept that I know what I’m talking about.

It may come as a shock when I say: The only good part of Vim is its user interface.

Every other aspect of Vim is irredeemable. The codebase is atrocious. The plugin API is cumbersome and restrictive. The dev community is apathetic. The benevolent dictator is averse to change. There is no chance of fixing these problems.

I wish it were otherwise, but it isn’t.

Considering the degree of these criticisms, I should back them up with specific examples.

The Plugin API

Vim’s plugin API is just plain bad. First, all plugin code runs synchronously. That means if any plugin’s code is executing, Vim’s UI is frozen. This makes many types of plugins difficult or impossible to implement. Linters have to finish in milliseconds or risk annoying the user. External commands (such as make) can’t be cancelled, and they must finish before the user can resume editing.

Another annoyance is that writing plugins requires knowledge of Vim’s special language: vimscript. This is true even if you’re using a Vim compiled with support for other languages. Yes, +python gives you access to Python’s libraries and syntax. But your code will be littered with calls to vim.eval() and vim.command(). Here’s an example:

import vim

# Show current directory in Vim
cwd = vim.eval('getcwd()')
vim.command(':Explore %s | redraw' % cwd)

You might notice that issues could arise from failing to properly escape variables in calls to eval() and command(). You’d be right. It’s not uncommon for special character inputs to cause Vim plugins to crash or misbehave.

The Codebase

I started programming in C almost 20 years ago. Vim is, without question, the worst C codebase I have seen. Copy-pasted but subtly changed code abounds. Indentation is haphazard. Lines contain tabs mixed with spaces. Source files are huge. There are almost 25,000 lines in eval.c. That file contains over 500 #ifdefs and references globals defined in the 2,000 line globals.h.

Some of Vim’s source code isn’t even valid text. It’s not ASCII or UTF-8. The venerable file can’t figure out the encoding.

ggreer@carbon:~/code/vim% file -I src/digraph.c 
src/digraph.c: text/x-c; charset=unknown-8bit

Thankfully, eval.c is pure ASCII.

Many of Vim’s #ifdefs are for platforms that became irrelevant decades ago: BeOS, VMS, Amiga, Mac OS Classic, IRIX. These preprocessor statements may seem innocuous, but they slow development and inhibit new features. Also, Vim doesn’t even work on most of these platforms anymore. It’s just that nobody has an ancient system with which to test Vim. Neovim developers analyzed many of the preprocessor statements and found a significant number that could never be included in a working Vim.

Complexity stemming from cross-platform support may be excusable, but even something as simple as reading keyboard input is a nightmare in Vim. Stepping through with a debugger will result in call stacks such as inchar() in getchar.c calling ui_inchar() in ui.c, which calls mch_inchar() in os_unix.c, which calls WaitForChar(), which calls RealWaitForChar(). This call stack can be completely different on different platforms. It also differs when running in command line versus GUI mode.

Figuring out Vim’s control flow is harrowing. Even when you hit paydirt in RealWaitForChar(), the code is extremely hard to follow. Here’s a snippet. You can view the whole function at my Vim Hall of WTF.

# if defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H)
    /* Remember at what time we started, so that we know how much longer we
     * should wait after being interrupted. */
#  define USE_START_TV
    struct timeval  start_tv;

    if (msec > 0 && (
        xterm_Shell != (Widget)0
#   if defined(USE_XSMP) || defined(FEAT_MZSCHEME)
#   endif
#  endif
#  ifdef USE_XSMP
        xsmp_icefd != -1
#   endif
#  endif
    (mzthreads_allowed() && p_mzq > 0)
#  endif
    gettimeofday(&start_tv, NULL);
# endif

That if statement’s conditions span 17 lines and 4 different #ifdefs. All to call gettimeofday(). Amusingly, even the body of that statement has a bug: times returned by gettimeofday() are not guaranteed to increase. User intervention or ntpd can cause the system clock to go back in time. The correct solution is to use a monotonically increasing time function, such Linux’s clock_gettime() or OS X’s mach_absolute_time().

The Developer Community

Matt and I worked for months to add asynchronous functionality to Vim. From that experience, I have few good things to say about Vim’s dev community. In fact, out of all the developer communities I’ve encountered, Vim’s is the most hostile to change. Anything that isn’t a bug fix is frowned upon.

Patches are often criticized for ridiculous reasons. After we posted our patch to the Vim-dev mailing list, the first reply was:

NOTE: Don’t use ANSI style function declarations. A few people still have to use a compiler that doesn’t support it.

Seriously? C89 is a quarter-century old. The number of people stuck on older compilers can be counted on one hand. This is a non-concern. Still, I acquiesced. It was easier to make the change than argue with the critic.

The rest of that thread is me being as civil as possible, despite discouragement at every turn. The replies might as well be a paint-by-numbers guide on how to alienate new contributors.

On a more general note: After reading random posts on the Vim-dev mailing list, I get the impression that the developer community is fragmented. Some want Vim to be similar to Sublime Text: A flexible, extensible text editor for developers. Some (including BDFL Bram Moolenaar) are afraid of Vim becoming an IDE.

The Overly Cautious Dictator for Life

Speaking of Bram Moolenaar: His merge criteria are inscrutable. Some patches he ignores. Some, he attacks. Others, he merges.

Take a look again at the thread where Matt and I submitted our patch. We did our best to cater to Bram’s every whim, but it was a waste of time. Had he immediately told us to give up, it would have been a better outcome for all involved. Instead, we were given hope and strung along, working on a patch that had no chance of getting merged.

The Alternative

A couple of months after my disillusionment with Vim, Thiago de Arruda submitted a similar patch. It was likewise rejected. But unlike me, Thiago didn’t give up. He started NeoVim and created a Bountysource for it.

Neovim is exactly what it claims to be. It fixes every issue I have with Vim: The plugin API. The codebase. The community. The BDFL.

Neovim’s plugin API is backwards-compatible with Vim, but it also allows asynchronous execution. Users have already made plugins that Vim can never have. For example, Neomake allows async linters. That feature alone is worth making the switch for.

Neovim’s codebase is a substantial improvement. They’ve replaced much of the hacky, platform-specific code with libuv. They’ve fixed the problems with indentation, style, and bad file encodings. They’ve removed old code for ancient, unused platforms. They’ve drastically increased test quality and coverage. There’s still much to be done, but the difference is already worlds better.

Neovim’s development community is excellent. They respond to issues. They merge pull requests. They give quality feedback. And most importantly, they’re nice to newbies.

In fact, they’re nice to everyone. The main dev team holds no enmity toward Bram Moolenaar. They recognize Vim’s failings, but they don’t feel the need to criticize it.

The only thing Neovim is missing is a tagged stable release. But there’s no need to wait. Right now you can clone Neovim, compile it, and have an editor that works with all your existing plugins.

If you are a Vim user, I strongly recommend switching to Neovim. It’s the Vim you’re used to, but with plugins you never knew you wanted.

Thanks to both Bjorn Tipling and Matt Kaniaris for their help with this post.

  1. Edit and pico don’t count.

When commenting, remember: Is it true? Is it necessary? Is it kind?