Modern Vim plugins and complex configuration use Vim9 script; it is far faster and less quirky than legacy Vim script.
Legacy Vim script is interpreted line by line and full of quirks (let, a: argument prefixes, l: scopes, ==# for case-sensitive comparison). Vim 9 added Vim9 script, enabled by putting vim9script as the first line of a file (or using def functions anywhere).
Main differences:
- Variables are declared with
var(orconst/final) and optionally typed:var count: number = 0. Assignment needs nolet. - Functions are written with
def ... enddef, have typed parameters and return types, and are compiled to bytecode on first call, often 10x+ faster than legacy functions. - Comments start with
#, not". - Comparisons behave like other languages:
==does not depend on theignorecaseoption. - Line continuation needs no leading backslash; expressions can span lines.
- Script-local by default;
exportandimportgive real modules.
Type errors are caught at compile time, so a broken function fails as soon as it is defined or first called, with a clear message, rather than half-way through running.
Neovim did not adopt Vim9; it uses Lua for the same purpose. Inside Vim, legacy script still works, and both can call each other. For new plugin code or anything with loops over many lines, write Vim9.
Example
vim9script
# ~/.vim/plugin/wordcount.vim
def CountWord(word: string): number
var n = 0
for line in getline(1, '$')
n += count(line, word)
endfor
return n
enddef
command -nargs=1 CountWord echo $'{<q-args>}: {CountWord(<q-args>)}'
# :CountWord TODO -> TODO: 7Remember
- Start the file with vim9script; use var, def/enddef and # comments
- def functions are compiled and type-checked, much faster
- Neovim uses Lua instead; Vim9 is Vim-only
Watch out for
- Mixing legacy syntax (let, a:arg, " comments) inside a vim9script file; it errors.