This article provides a quick reference for how to write Vim script for people who are already familiar with JavaScript. For the sake of brevity, I will refer to Vim script by another common name, “VimL,” throughout the rest of this article.
If you use Vim as your editor, and want to write your own plugins or just automate certain tasks, it’s worth learning how to write VimL. Vim’s own scripting language is guaranteed to work on every platform that Vim runs on. This guide is written for Vim 8.0 and above, and also for more recent NeoVim versions. This article will cover the following topics.
- The basic types VimL supports.
- Basic VimL syntax.
- Logical expressions in VimL.
- Working with Strings, Numbers, Lists, and Dictionaries.
- Type conversion and type checking.
- Basic statements and control flow.
- Writing basic functions in Vim.
- VimL variable scoping rules.
Throughout the following guide, references will be made to relevant sections of
the Vim documentation available via the help system. Type the :help
command in
Vim as provided to view that documentation. You can use the help system to look
up the documentation for any Vim function with :help function_name()
.
Types
Relevant help: :help variables
The best place to start with any language is with the basic types the language supports. The basic types you will be familiar with in JavaScript have the following equivalences in VimL.
JS type | Vim type |
---|---|
string |
String |
number |
Number OR Float |
boolean |
Boolean |
null |
v:null |
undefined |
No equivalent |
Object |
Dictionary |
Array |
List |
function |
Funcref |
JavaScript has one type for numbers, but VimL has two. Number
is the type
for integers, and Float
is the type for floating point numbers. Floating point
support is optional, but most modern platforms support it.
The Boolean
type is new in Vim 8.0, and was added to the language primarily to
support JSON serialization, as was the value v:null
. The newer Boolean
values are written with v:true
and v:false
. Numbers are usually used for
Boolean logic in VimL instead, so you should prefer to write 1
and 0
instead
of v:true
and v:false
.
There’s no need for undefined
in VimL, as accessing missing keys of objects
will result in exceptions being thrown.
Unlike JavaScript, there is no base Object
type from which other objects like
Array
and Function
are derived. There are no classes, though functions
can be bound to Dictionary
values. A Funcref
in Vim is a type of variable
which is a reference to a Vim function, which may or may not already be defined.
Basic Syntax
Relevant help: :help Command-line
, :help let
In VimL, each line of code represents a command, just as you can express with
:
in normal mode, or in “Ex” mode. (:help Ex-mode
)
Variables can be defined in Vim with the let
command. You can print
expressions with the echo
command. You can try the following in Vim from
normal mode.
:let foo = 'Hello World'
:echo foo
You should see the string “Hello World” printed to your echo line. You can save
the lines above exactly as they are in a .vim
file, and you can execute the
file by using :source %
, which will execute the current file as VimL code. The
:
for executing a command is redundant in “Ex” mode, so you can also write
your script like so.
let foo = 'Hello World'
echo foo
The near equivalent in JavaScript would be as follows.
var foo = 'Hello World'
console.log(foo)
Because every line in VimL code must be a valid Vim command, function calls must
either be part of an expression or called with the call
command.
let x = abs(1)
" You need to write `call` here.
call clearmatches()
Any Vim command can be abbreviated to the shortest string for a command name
which is still unique. For example, you can use :cal
in place of :call
, as
there’s no other command which starts with :cal
. It is recommended to always
write the full command name for a command in scripts to avoid confusion.
There are far too many Vim commands for this guide to name, but the essential commands for writing basic scripts will be named throughout this guide.
In JavaScript, logical lines of code are terminated with semicolons (;
), and
semicolons are automatically inserted in JavaScript code, meaning you don’t need
to write semicolons in most cases. In VimL, lines logically end where a line
ends in a file, and to split logical lines over further lines in a file, you
have to continue the line by adding \
to following lines. For example.
" Results in 3
let x = 1
\ + 2
In Vim, comments are all single-line comments denoted with a single "
character and some subsequent text. The following code in Javascript…
// This is a comment.
var x = 42
… can be written like so in VimL.
" This is a comment.
let x = 42
Comments cannot be made inside of expressions, say for large Dictionaries. This will be explained again below.
Logical expressions
Relevant help: :help expression-syntax
VimL supports the logical operations you might expect, with very similar syntax in most cases.
JavaScript | VimL |
---|---|
!x
|
!x
|
x || y
|
x || y
|
x && y
|
x && y
|
x < y
, etc. |
x < y
, etc. |
x == y
|
x ==# y
|
x === y
|
x is# y
|
x !== y
|
x isnot# y
|
x ? y : z
|
x ? y : z
|
Comparison operators will always result in either 0
or 1
, so you cannot
use expressions like possiblyNull || {}
in VimL, like you can in JavaScript.
VimL supports both case-sensitive and case-insensitive comparisons for String
values, and the behavior of the ==
and is
operators in Vim is
configurable. Because of this, it is advised to always write ==#
and is#
when working with String
values, or ==?
and is?
instead when you want
to perform a case-insensitive comparison. x is? y
in VimL is roughly
equivalent to String(x).toLowerCase() === String(y).toLowerCase()
in
JavaScript.
The ==
operator performs a deep comparison of values for List
and
Dictionary
objects, so the result of {'x': [1, 2]} == {'x': [1, 2]}
is 1
in VimL, and would be false
in JavaScript, where the operator has the meaning
of comparing the identity of two objects. is
in VimL performs a comparison
similar to ===
in JavaScript, and tests for two Dictionary
or List
objects
having the same identity.
==
can result in an exception being thrown for incompatible types, like List
and Number
, so there are fewer “wat” moments with strange comparisons in Vim
than in JavaScript. is
will never result in an exception being thrown. A rule
of them is to always use is#
or is?
for comparisons with strings, always use
is
for comparisons with numbers, and convert Floats to numbers whenever
possible or vice-versa, and use wither ==#
or is#
for testing for List
or
Dictionary
values.
String expressions
Relevant help: :help expr-string
There are two different kinds of strings in VimL, “strings” written with double
quotes ("
) and “literal strings” written with single quotes ('
). Unlike
JavaScript, your choice of quotes matters in Vim. Double quote strings support a
number of escape sequences, while single quoted strings do not support any
escape sequences, other than a special way of escaping single quotes by
inserting two single quote characters. (''
)
" Echoes: \n
echo '\n'
" Echoes a blank line.
echo "\n"
" Both echo: isn't
echo 'isn''t'
echo "isn't"
" Both echo: \
echo '\'
echo "\\"
" Echoes the character code for Ctrl+W
echo "\<C-w>"
Here are some essential String operations, and their equivalent forms.
JavaScript | VimL |
---|---|
str.length |
len(str) |
str[i] |
str[i] |
str[str.length - i] |
str[-i] |
str.slice(i) |
str[i:] |
str.slice(start, end) |
str[start : end - 1] |
str.slice(start, -1) |
str[start : -2] |
str.slice(-2, -1) |
str[-2 : -2] |
str + x |
str . x |
str.startsWith(x) |
str[:len(x) - 1] is# x |
str.indexOf(x) |
stridx(str, x) |
str.lastIndexOf(x) |
strridx(str, x) |
str.includes(x) |
stridx(str, x) >= 0 |
str.match(x) || []
|
matchlist(str, x) |
(str.match(x) || [])[0]
|
matchstr(str, x) |
str.match(x) != null |
str =~# x |
str.match(x) == null |
str !~# x |
str.repeat(x) |
repeat(str, x) |
str.replace('xyz', r) |
No direct equivalent |
str.replace(/xyz/, r) |
substitute(str, 'xyz', r, '') |
str.replace(/xyz/g, r) |
substitute(str, 'xyz', r, 'g') |
str.normalize(x) |
No direct equivalent |
str.trim() |
No direct equivalent |
Unlike List index lookups, looking up a index in a String which is out of range will return an empty String.
Regular expressions in Vim have very different syntax from JavaScript. Regular
expressions require too much discussion to describe in detail here, but
:help magic
will reveal most of the details you need to know.
Numeric expressions
Relevant help: :help expr-number
Numeric expressions are similar to what you might expect from JavaScript, and
C-like languages. Numbers can be expressed with a sequence of digits, as
hexadecimal values, as octals, and as binary values. Exponents can be used.
Numbers with a decimal place are interpreted as Float
values.
" 42 as a Number
echo 42
" 255 as a Number
echo 0xff
" 63 as a Number
echo 077
" 7 as a Number
echo 0b111
" 1.5 as a Float
echo 1.5
" 100 as a Float
echo 0.1e+3
Note that 0 + '077'
will become 63
, but str2nr('077')
will become 77
.
str2nr
accepts a second argument for the base of the number, which defaults to
base 10. Because base 10 is what you almost always want for leading zeros, you
should prefer to use str2nr
for converting String
values to Number
values.
Use str2float
to convert String
values to Float
values.
Here are some essential Number/Float operations, and their equivalent forms.
JavaScript | VimL |
---|---|
+x |
+x |
-x |
-x |
x + y |
x + y |
x - y |
x - y |
x * y |
x * y |
x / y |
x / y |
x ** y |
pow(x, y) |
x & y |
and(x, y) |
x | y
|
or(x, y) |
x ^ y |
xor(x, y) |
~x |
invert(x) |
x << y |
None |
x >> y |
None |
x >>> y |
None |
Math.max(x, y) |
max([x, y]) |
Math.min(x, y) |
min([x, y]) |
Math.round(x) |
round(x) |
Math.ceil(x) |
ceil(x) |
Math.floor(x) |
floor(x) |
Math.trunc(x) |
trunc(x) |
Math.sqrt(x) |
sqrt(x) |
Math.exp(x) |
exp(x) |
Math.log(x) |
log(x) |
Math.log10(x) |
log10(x) |
Math.log2(x) |
log(x) / log(2) |
Math.abs(x) |
abs(x) |
Math.acos(x) |
acos(x) |
Math.asin(x) |
asin(x) |
Math.atan(x) |
atan(x) |
Math.atan2(x, y) |
atan2(x, y) |
Math.cos(x) |
cos(x) |
Math.tan(x) |
tan(x) |
Note that 1 / 2
will result in 0
as a Number
. If you want a floating point
result, use a floating point number. 1 / 2.0
will result in 0.5
as a
Float
.
List expressions
Relevant help: :help Lists
Lists can be expressed like Arrays in JavaScript. [
and ]
open and close
lists.
echo [1, 2, 3]
You can continue long lists with the continuation character.
let x = [
\ 1,
\ 2,
\ 3,
\]
Here are some essential List operations, and their equivalent forms.
JavaScript | VimL |
---|---|
list.length |
len(list) |
list[i] |
get(list, i) |
list[list.length - i] |
get(list, -i) |
No equivalent | list[i] |
No equivalent | list[-i] |
list.slice() |
copy(list) |
list.slice(i) |
list[i:] |
list.slice(start, end) |
list[start : end - 1] |
list.slice(start, -1) |
list[start : -2] |
list.slice(-2, -1) |
list[-2 : -2] |
list.slice(0, 1) |
list[0 : 0] |
list.concat(x) |
list + x |
list.push(x) |
add(list, x) |
list.push(...x) |
extend(list, x) |
list.unshift(x) |
insert(list, x) |
list.splice(i, 0, x) |
insert(list, i, x) |
list.splice(i, 1) |
remove(list, i) |
list.splice(i, 2) |
remove(list, i, i + 1) |
list.splice(i, 0, x, y) |
extend(list, [x, y], i) |
list.pop() |
remove(list, -1) |
list.indexOf(x) |
index(list, x) |
list.indexOf(x, i) |
index(list, x, i) |
list.includes(x) |
index(list, x) >= 0 |
list.join(x) |
join(list, x) |
list.reverse() |
reverse(list) |
list.sort() |
sort(list) |
list.sort(CmpFunc) |
sort(list, CmpFunc) |
No direct equivalent | uniq(list) |
No direct equivalent | uniq(list, CmpFunc) |
No direct equivalent | list1 == list2 |
list1 === list2 |
list1 is list2 |
You can get items at index i
like in Javascript with list[i]
. Unlike
JavaScript, accessing an undefined or invalid index for a List results in an
exception being thrown. You can get an item at possibly undefined index or
a default value with get(list, i, default)
. The default value for default
itself is 0
.
Lists can be sliced with a slice operator [start : end]
. Unlike .slice()
in
JavaScript, end
specifies the index to end a slice at, not the index
before the end of a slice.
Note that the result of remove()
on a List with one index is the removed item
in Vim, but with two index values the result is a List with the items. This
is consistent with the behavior of the same function for Dictionary
values,
as you will see in the next section.
You can also perform a lowercase index lookup for strings too with
index(list, x, i, 1)
, which is roughly equivalent to
list.map(y => y.toLowerCase()).indexOf(x.toLowerCase(), i)
in JavaScript.
VimL provides one essential operation for Lists which JavaScript does not
provide directly, and that is the uniq()
function. uniq()
removes duplicate
entries from an already sorted list, with an optional comparison function just
like the function which can be given to sort()
. A copied de-duplicated list
with a comparison function can thus be computed like so.
uniq(sort(copy(list), function('CmpFunc')), function('CmpFunc'))
Dictionary expressions
Dictionary
values are like basic objects in JavaScript. One important
difference in the syntax is that unlike JavaScript, keys must always be quoted.
The following two examples are equivalent.
var some_string = 'z'
// An ES6 computed key is used here.
// {'x': 1, 'y': 2, 'z': 3}
var obj = {x: 1, 'y': 2, [some_string]: 3}
let some_string = 'z'
" {'x': 1, 'y': 2, 'z': 3}
let obj = {'x': 1, 'y': 2, some_string: 3}
Like JavaScript objects, Dictionary keys can only be String values.
Here are some essential Dictionary operations, and their equivalent forms.
JavaScript | VimL |
---|---|
obj.xyz |
obj.xyz |
obj.xyz = value |
let obj.xyz = value |
obj[key] |
get(obj, key) |
No equivalent | obj[key] |
obj[key] = value |
let obj[key] = value |
key in obj |
has_key(obj, key) |
delete obj1.xyz |
call remove(obj1, 'xyz') (Can throw errors) |
delete obj1[key] |
call remove(obj1, key) (Can throw errors) |
Object.keys(obj) |
keys(obj) |
Object.keys(obj).length |
len(obj) |
Object.values(obj) |
values(obj) |
Object.entries(obj) |
items(obj) |
Object.assign(obj1, obj2) |
extend(obj1, obj2) |
Object.freeze(obj) |
No equivalent |
Object.seal(obj) |
No equivalent |
No direct equivalent | obj1 == obj2 |
obj1 === obj2 |
obj1 is obj2 |
Removing a key which does not exist in a Dictionary in VimL will result in an error, whereas in JavaScript the operation will fail silently.
There are no equivalents for Object.freeze
, Object.seal
, or
Object.preventExtensions
in VimL, but you can use the lockvar
command to
temporarily prevent a Dictionary from being modified.
Type conversion
Relevant help: :help str2nr
, :help str2float
, :help string()
There are a few ways to convert between types in VimL, but the following methods are recommended for converting between types.
Types | Syntax |
---|---|
String -> Number |
str2nr(x) |
String -> Float |
str2float(x) |
Float -> Number |
float2nr(x) , float2nr(ceil(x)) , float2nr(floor(x)) |
Number -> Float |
floor(x) |
Number -> String |
string(x) |
Float -> String |
string(x) |
Boolean -> Number |
+x |
Number -> Boolean |
x ? v:true : v:false |
Type checking
Relevant help: :help type()
, :help v:t_TYPE
For performing type checking in Vim 8.0 and onwards, use the type()
function
and v:t_
constants for types. v:t_
variables do no exist in Vim versions
below Vim 8.0, where you can use type()
again with an example value instead.
See the following table.
Type | Expression |
---|---|
String |
type(x) is v:t_string |
Number |
type(x) is v:t_number |
Float |
type(x) is v:t_float |
Dictionary |
type(x) is v:t_dict |
List |
type(x) is v:t_list |
Funcref |
type(x) is v:t_func |
Boolean |
type(x) is v:t_bool |
v:null |
x is v:null |
The following table provides some examples of how to compare values with unknown
types with some values with known types. It is not safe to use ==
for
comparing any two values, as errors can be raised for incompatible values.
Types | Example |
---|---|
? == String |
x is# 'foo' |
? == Number |
x is 42 |
? == Float |
x is 42.0 |
? == Dictionary |
type(x) is v:t_dict && x ==# {'x': 'y'} |
? == List |
type(x) is v:t_list && x ==# [1, 2] |
? == Funcref |
x is function('type') |
? == Boolean |
x is v:true or x is v:false |
? == v:null |
x is v:null |
Basic Statements
Now you have all of the types and operators in mind, it’s time to discuss statements which form the basic building blocks of any programming language. Equivalent statements are listed below.
Conditions
An if statement written like so in JavaScript…
if (cond1) {
// expr1
} else if (cond2) {
// expr2
} else {
// expr3
}
… can be written like so in VimL.
if cond1
" command1
elseif cond2
" command2
else
" command3
endif
There are no switch
statements in VimL.
All values for conditions in Vim are first converted to Number
values, so
List
and Dictionary
objects are not valid values for conditions, as they
cannot be converted to numbers.
Loops
VimL supports while
loops like JavaScript. The following loop in JavaScript…
while (cond) {
// expr
}
… can be written like so in VimL.
while cond
" command
endwhile
There is no do ... while
loop in VimL.
The VimL for
loops loop over each element of a List
. A classic JavaScript
for loop like so…
for (var index = 0; index < length; index++) {
// expr
}
… can be written like so in VimL.
for index in range(length)
" command
endfor
A JavaScript for ... of
loop written like so…
for (var variable of iterable) {
// expr
}
… can be written like so in VimL.
for variable in iterable
" command
endfor
You can use the commands continue
and break
like you might expect in
JavaScript for all loops.
Exception handling
Relevant help: :help :try
, :help throw-variables
VimL supports try ... catch
somewhat similar to JavaScript, with some
important differences. In VimL, all exceptions are simply String
values,
and the catch
command supports regular expressions for looking for errors
that match the regular expression. You can throw an error as a String
with
the throw
command.
try
" Throw an error.
throw 'xxx foo xxx'
" Catch all errors with a message containing 'foo'
catch /foo/
call HandleException()
" finally is optional
finally
call AlwaysDoSomethingElse()
endtry
When an exception is thrown, the variable v:exception
will be set to the
exception’s message, and v:throwpoint
will be set describing where an
exception was thrown from.
Functions
Relevant help: :help function
, :help funcref()
, :help lambda
A JavaScript function like so…
function foo() {
return 42
}
… can be defined like so in VimL.
function! Foo() abort
return 42
endfunction
The !
option for the function command is referred to as a “bang,” which is
special syntax supported by some commands, with a different meaning for each
command. In the case of the function
command, it means that any current
function with the name Foo
should be replaced with the function being defined.
Without !
, Vim will raise an error about the function already being defined.
The abort
keyword for a function causes a function to exit on the first
command where an error is encountered. It is recommended to use the abort
keyword for all functions. Otherwise, Vim will continue to execute lines of code
in a function after an error is encountered, and strange things will happen.
Note that all function names must begin with a capital letter in VimL. This prevents users from being able to redefine standard Vim functions.
You can reference functions directly in JavaScript…
var func = foo
In VimL you have to create a Funcref
value with the function()
or
funcref()
functions.
" The variable again has to start with a capital letter.
let Func = function('Foo')
" Another way to reference a function.
let Func2 = funcref('Foo')
function()
and funcref()
are slightly different. function('Foo')
looks for
the Foo
function by name, so the reference will change if Foo
is redefined.
funcref('Foo')
gets a reference to an existing function, and the reference
will not change if Foo
is redefined.
Functions can also be defined with a s:
prefix for defining functions only
for use in a particular script.
" This function can only be called in the current script.
function! s:Foo() abort
return 42
endfunction
Variable scope
Relevant help: :help internal-variables
Variables in Vim can be set in various scopes. Variables are either scoped to the global scope or to a function by default. It is recommended to always state the scope of a variable explicitly, and the scope of a variable is sometimes required. All variables will have their scope explicitly set throughout the rest of this guide.
Scopes for variables are like so.
Scope | Explanation |
---|---|
g:x |
x in the global scope. |
l:x |
x in the local scope. |
a:x |
Function argument x . Read-only. |
s:x |
x in the script’s scope. Only accessible in the script. |
v:x |
A read-only special Vim variable x . |
b:x |
x in the current buffer. |
w:x |
x in the current window. |
t:x |
x in the current tab. |
b:
, w:
, and t:
variables are variables assigned to the scope of the
buffer, window, or tab. These scopes can be used for associating arbitrary
values with buffers, windows, or tabs. Vim never falls back on variables in one
scope after failing to find them in another.
Every variable scope is also itself a VimL variable, as a Dictionary
value.
Therefore all Dictionary
operations are valid for those variables. For
example, g:['x']
and g:x
are equivalent. get(g:, 'x', default)
can be used
to get a variable or a default. The following restrictions apply.
- Keys must also be valid variable names.
let g:['a b'] = 'c'
is illegal. - Read-only variables cannot be modified.
Funcref
keys must start with a capital letter.
Functions must reference their arguments as a:x
, and not x
.
function! Foo(x) abort
" Incorrect, `x` is equivalent to `g:x` here.
return x * 2
endfunction
" OK
function! Foo(x) abort
return a:x * 2
endfunction
All for now
Having read this article, you should now be familiar with all of the types of values in VimL, and the basic operations on those types. You should now be familiar with basic syntax and semantics in VimL. You should have a basic understanding of Vim scoping rules, and functions.
If you have been using Vim as your text editor for a while and writing JavaScript code, you might not have been familiar with the expressive power of VimL code. Many core concepts in JavaScript also appear in VimL, and the breadth of what can be accomplished in VimL might stretch further than you had previously imagined.
There is much, much more to discuss about VimL, so expect further articles to be
written at some point in the future. Important topics not discussed here include
defining custom commands, autoload
functions, autocmd
events, mapping keys
to custom functions, asynchronous programming similar to JavaScript’s execution
model, and much, much more. Look forward to more articles in the future, and I
hope you had fun reading this.