w0rp zone

Blog

Vim Script for the JavaScripter

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 final, 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()</code> No direct equivalent

Unlike List index lookups, looking up a index in a String which is out of range will return an empty String.

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.

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 &#124; 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] obj[key]
obj[key] = value let obj[key] = value
delete obj1.xyz call remove(obj1, 'xyz')
delete obj1[key] call remove(obj1, key)
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.

  1. Keys must also be valid variable names. let g:['a b'] = 'c' is illegal.
  2. Read-only variables cannot be modified.
  3. 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.

Post Your Comment

Need help with comment formatting?

Okay, here's a cheat sheet for you. Click here to get rid of this.

Choose a help topic below. Raw HTML for basic formatting allowed, and other HTML will be escaped. Other Markdown syntax is valid, but probably not relevant.

*italic* **bold**
_italic_ __bold__
1. Item #1
2. Item #2

* Unordered list item
* Unordered list item
Three backticks are your friend here: ```square = lambda x: x * x```

Also in a block like so:

```python
def square(x):
  return x * x
```

Comments

Definitely the most succinct and to the point vimscript intro I've read, thanks.

Excellent reference! Thank you so much for sharing.