Vim 9

Tue Jun 28 2022

Vim 9 released today and it's a game changer.

If you aren't a familiar with Vim, it's a powerful text-based text editor that's been around for ages. It's a direct descendent of Vi, which is a descendent of Ex, which is a descendant of Ed, the first text editor ever created. It's often still used on most Unix systems as the default text editor after a brand new install.

I've been using Neovim (a Vim successor) full time since early 2020 and it has begun to dominate my entire workflow, improving my efficiency all around. Of course, when infrequent updates to Vim propagate to release, they always to catch my eye.

Here are the notable updates in Vim 9 worth mentioning.

New Autocommands

Vim 9 introduces a few new autocommands:

CompleteDonePre       after Insert mode completion done, before clearing info
DirChangedPre         before the working directory will change
InsertLeavePre        just before leaving Insert mode
ModeChanged           after changing the mode
SigUSR1               after the SIGUSR1 signal has been detected
WinClosed             after closing a window
WinScrolled           after scrolling or resizing a window
VimSuspend            when suspending Vim
VimResume             when Vim is resumed after being suspended

I'm fairly certain that everyone who uses Vim has written their own hacky functions to get mode updates. Now it can be done with ModeChanged:

def g:MyFunction(mode: string)
  " callback here!

augroup mode_change
  au ModeChanged * call g:MyFunction(mode())
augroup end

New Operators

Vim now has bit shifts and nullish coalescence!

>>                    bitwise right shift
<<                    bitwise left shift
??                    falsy operator

It's mostly syntactic sugar:

str1 ? str1 : strFallback

" becomes

str1 ?? strFallback

However, the falsy operator is potentially more efficient than the original ternary operator since "str1" is evaluated twice in the vimscript ternary.

Bug Fixes

Bug fixes are always good - especially the documentation ones.

See the patches


With the introduction of Vim 9, Bram announced a new age of Vim internal tooling which immediately captured my attention: Vim9script. This new scripting language is meant to replace the original Vimscript with a cleaner modern alternative to make plugin writing easier than ever. It also has associated performance benefits: "An increase in execution speed of 10 to 100 times can be expected."

I use vimscript for all of my configuration files and I want to highlight a few of my favorite changes that will greatly clean up my code.

Reloading a Vim 9 script clears functions and variables by default.

This is huge.

When vimscript is normally reloaded, old definitions are not erased by default. This makes debugging variables and functions a pain because random callback functions you thought were cleared are actually still defined and causing conflicts.

If you write this script:

fu SomeFunc()
  echo "hello"

fu CriticalFunction()
  call SomeFunc()
  " ...
  call SomeFunc()

And then rename a function but not all of its calls:

fu SomeFunc2()
  echo "hello"

fu CriticalFunction()
  call SomeFunc2()
  " ...
  call SomeFunc() " this line never changed

This might still be considered valid scripting since "SomeFunc" is still defined somewhere. With Vim 9, Vim9scripts will clear all old definitions.

Comments are now denoted by # instead of ".

You used to have to carefully place your comments to avoid confusion. Here's an example:

set viminfo="" " disable viminfo

What quotes are for the string literal? And what quotes are for the comment?

On the other hand, Vim9script is looking pretty good.

set viminfo="" # disable viminfo

Using backslashes for continuation are hardly ever needed.

This change makes a multiline declarations less of a pain:

let g:coc_global_extensions = [
  \ 'coc-clangd',
  \ 'coc-css',
  \ 'coc-go',
  \ 'coc-json',
  \ 'coc-tsserver',
  \ ]

# becomes

let g:coc_global_extensions = [

It makes indented blocks way more readable:

call termopen(
  \s:shell_name, {
    \'on_exit': 'Terminal_exit',
    \'cwd': g:cwd,

# becomes

call termopen(
  shell_name, {
    'on_exit': 'Terminal_exit',
    'cwd': g:cwd,

Functions now have a new definition syntax, argument types, and no longer need to reference the "a:" dictionary.

Working with function arguments in Vim has always been confusing. You either had to refer to them by their argument number ("a:1") or by name in the "a:" dictionary ("a:x"). There's never been any type safety in vimscript as far as I'm aware.

The new definition syntax is cleaner and supposedly faster since each of these functions are type-checked and precompiled before execution.

fu! s:popup_new(x, y, w, h)
  let l:col = float2nr(&columns * a:x + 2)
  let l:row = float2nr(&lines * a:y + 1)
  let l:width = float2nr(&columns * a:w - 4)
  let l:height = float2nr(&lines * a:h - 3)

# becomes

def popup_new(x: number, y: number, w: number, h:number)
  let l:col = float2nr(&columns * x + 2)
  let l:row = float2nr(&lines * y + 1)
  let l:width = float2nr(&columns * w - 4)
  let l:height = float2nr(&lines * h - 3)

Final Thoughts

Vim 9's new scripting language is like the transition from Javascript 2015 to ES8. It's becoming a super weird highly processed tooling language. And I have a love-hate relationship with it.

All in all, Vim is moving in a good direction in the future. We're finally moving away from the Vi days while maintaining a small yet powerful editing tool. If you're curious to read the full changes for yourself, try updating to Vim 9 or reading the help page with ":h vim9".