vim script - chimay/wheel Wiki


As a VimL programmer, the most used help file is :

:help eval.txt

Once there, you can use a plain search.

E.g., searching for buf.*() will give you buffer related functions.

You can also use the completion in command mode :

:help buf*(<tab>

will give you a list of candidates, like bufnr() or bufadd().

The :helpgrep command search in all help files and fill the quickfix list.

Each kind of VimL object has its own syntax :

Finally, the reference :

:help help
:help help-context


The command :let evaluate an expression and assign the result :

let list = range(5)
echo list

The command :eval evaluate an expression without assigning the result :

let list = range(5)
eval list->add(10)
echo list

The command :call calls a function, without assigning the result :

let list = range(5)
call add(list, 10)
echo list


Float to integer with float2nr() :

let float = 2.01
echo float2nr(float)

Integer to float with round() :

let int = 2
echo round(int)

String to integer with str2nr() :

let string = '   0002  '
echo str2nr(string)


Global variable

You can define and access global vars with the g: prefix :

let g:var = 'global var content'
echo g:var

Function local variables

Most of the time, variables used inside a function are local, even without the l: prefix :

let var = 1
let box = 2
fun! Locals ()
	let var = 3
	let l:box = 4
	echo 'var from inside : ' var
	echo 'l:box from inside : ' l:box
	echo 'box from inside : ' box
call Locals ()
echo 'var : ' var
echo 'box : ' box

The l: can sometimes be useful however, for instance to avoid confusion with a (neo)vim v: variable, like v:count.

Its almost for readability, for the editor seem to see the difference :

fun! VimVars ()
	let l:count = 3
	echo 'l:count : ' l:count
	echo 'count : ' count
	echo 'v:count : ' v:count

Buffer local variable

You can define and access buffer local vars with the b: prefix :

let b:var = 'buffer local var content'
echo b:var
buffer #
" throws an error
echo b:var

There are also window local w:var and tab local t:var.


let list = [3, 4, 5, 6, 7]
echo list[0]
echo list[2]
echo list[-1]
echo list[-2]
echo list[1:]
echo list[:-2]
echo list[1:3]


" empty
let dict = {}
echo dict
" with some keys & values
let dict = { 'one' : 'value one', 'two' : 'value 2'}
echo dict
" add keys & valuse
let = 'john'
let dict.hobbies = ['tennis', 'music']
" access values using keys
echo dict['hobbies']
" using key variable
let key = 'name'
echo dict[key]
let key = 'hobbies'
echo dict[key]
" list of keys
echo keys(dict)
" list of values
echo values(dict)


By default, list & dictionaries are accessed by reference :

" list
let list = [3, 4, 5, 6, 7]
let ref = list
let ref[2] = 100
" original is modified
echo list
echo ref
" dict
let dict = { 'one' : 'value one', 'two' : 'value 2'}
let shadow = dict
let = 'changed'
let shadow['two'] = 'again'
" original is modified
echo dict
echo shadow

You can explicitly make a copy :

let list = [3, 4, 5, 6, 7]
let snapshot = copy(list)
let snapshot[2] = 100
" no change in original
echo list
echo snapshot
" dict
let dict = { 'one' : 'value one', 'two' : 'value 2'}
let snapshot = copy(dict)
let = 'changed'
let snapshot['two'] = 'again'
" no change in original
echo dict
echo snapshot


In case of a nested list or dict, copy() only copy the first level :

let nested_list = [range(1,3), range(2, 10)]
let shallow = copy(nested_list)
echo nested_list
echo shallow
" modifying first level
let shallow[0] = ['new', 'list']
" no change in original
echo nested_list
echo shallow

The objects inside it are passed by reference :

let nested_list = [range(1,3), range(2, 10)]
let shallow = copy(nested_list)
echo nested_list
echo shallow
" modifying second level
let shallow[0][1] = 100
" the original is modified
echo nested_list
echo shallow

To avoid changing the original by mistake, use deepcopy() :

let nested_list = [range(1,3), range(2, 10)]
let deep = deepcopy(nested_list)
echo nested_list
echo deep
" modifying second level
let deep[0][1] = 100
" no change in original
echo nested_list
echo deep


let box = 3
echo box
" box -> constant
lockvar box
" throws error E741
let box = 4
echo box
" box -> variable
unlockvar box
" ok
let box = 5
echo box

Variable containing code

Ex command

With :execute :

let code = 'let var = range(5)'
execute code
echo var


With eval() :

let expression = "strftime('%H:%M')"
echo eval(expression)

Can be used to revert string() :

let list = range(10)
let str = string(list)
" returns 1 = string
echo type(str)
let new_list = eval(str)
" returns 3 = list
echo type(new_list)

Normal mode

With :normal :

let delta = 2
execute 'normal! ' . delta . "\<c-o>"

Pointer : variable name

Variable containing the name of another variable :

let var = 'hello'
let ptr = 'var'
echo {ptr}

File system


Test for a regular file :

if filereadable(my_file)
	call do_something (my_file)

Test for a directory :

if isdirectory(my_dir)
	call do_something (my_dir)

File name

Expand the tilde with expand() :

echo expand('~/Documents')

Modify the name with fnamemodify() :

" full path
echo fnamemodify('~/Documents/file', ':p')
echo fnamemodify('Documents/folder/file', ':p')
" directory part (head)
echo fnamemodify('~/Documents/file', ':h')
echo fnamemodify('Documents/folder/file', ':h')
" file part (tail)
echo fnamemodify('~/Documents/file', ':t')
echo fnamemodify('Documents/folder/file', ':t')
" without extension
echo fnamemodify('~/Documents/file.ext', ':r')
echo fnamemodify('Documents/folder/file.ext', ':r')
echo fnamemodify('~/Documents/file.tar.gz', ':r')
echo fnamemodify('~/Documents/file.tar.gz', ':r:r')
" extension
echo fnamemodify('~/Documents/file.ext', ':e')
echo fnamemodify('~/Documents/file.tar.gz', ':e')
echo fnamemodify('~/Documents/file.tar.gz', ':e:e')


List of files & dirs in current folder :

echo glob('*', v:false, v:true)

Recursive, all tree of files & dirs in current folder :

echo glob('**', v:false, v:true)

Note the double asterisk.

Tree of vim files :

echo glob('**/*.vim', v:false, v:true)


Number & name

Name from number :

echo bufname(1)

Number from name :

let name = bufname(1)
echo bufnr(name)

List of buffers

All buffers :

let buffers = getbufinfo()
for buf in buffers
	echo buf.number ':'

Listed buffers :

let buffers = getbufinfo({'buflisted' : 1})
for buf in buffers
	echo buf.number ':'


Current buffer

List of lines in current buffer :

" -- line number, output is a string
" current line
echo getline('.')
" last line
echo getline('$')
" -- range of line, output is a list
echo getline(1, 10)
echo getline('.', '$')

With setline(line, text), you can change a line.

With :put =var, you can add var content below the cursor.

With :put =list, you add an element of list per line.

Any buffer

" alternate buffer
let bufnum = '#'
" -- line number, output is a string
echo getbufline(bufnum, 5)
echo getbufline(bufnum, '$')
" -- range of line, output is a list
echo getbufline(bufnum, 1, 10)
echo getbufline(bufnum, 1, '$')
echo getbufline(bufnum, 1, '$')[4]

See also setbufline(), appendbufline().


Ex command output into a variable

With execute() :

let var = execute('tabs')
echo var
let list = split(var, "\n")
echo list

With :redir :

redir => var
redir END
let list = split(var, "\n")
echo list

Shell command output into a variable

With system*() :

let var = system('ls -l')
echo var
let list = systemlist('ls -l')
echo list

List content into a file

With writefile() :

let list = ['first line', 'second line', 'third line']
let file = expand('~/Documents/list-file')
" replace content
call writefile(list, file)
" add content
call writefile(list, file, 'a')

With :redir :

let list = ['first line', 'second line', 'third line']
let content = join(list, "\n")
let file = expand('~/Documents/content-file')
execute 'redir! >' file
silent! echo content
redir END
execute 'redir! >>' file
silent! echo content
redir END

Note the distinction between the function execute() and the command :execute



Variable containing a function name :

let fun = 'strftime'
echo {fun}('%H:%M')


Inside a function definition, arguments are accessed with a a: prefix :

fun! Fun (argument)
	return a:argument
echo Fun('hello')

You can call a function with a given argument list : call(function, argument-list) :

let list = range(5)
let element = 'new'
" synonym of add(list, element)
echo call('add', [list, element])
echo list

Optional arguments

Named optional argument

YOu can give a name to an optional argument :

fun! Fun (optional = 'default value')
	return a:optional
echo Fun ()
echo Fun ('another value')

Anonymous optional arguments

Optional arguments can be defined with the three dots syntax Fun(...). You can access them with these special variables :

Application : pass the optional arguments of a function to another one :

fun! Fun (...)
	let var = call('add', a:000)
	return var
let list = range(5)
echo Fun (list, 7)


fun! Fun (named = 'default', ...)
	return #{named: a:named, anonymous: a:000}
echo Fun ()
echo Fun ('value')
echo Fun ('value', 'one', 'two')


It is a good idea to use the autoload feature as much as possible. The definition of a function myfunction in a file myfile located in autoload/myfolder/ looks like :

fun! myfolder#myfile#myfunction ()

The autoload/myfolder has to be somewhere in your runtimepath.


Funcref :

let list = range(3)
let Fun = function('add')
echo Fun(list, 4)
echo list

With predefined argument(s) :

let list = range(3)
" add(list, element)
let Fun = function('add', [list])
echo Fun(4)
echo Fun(5)
echo list


let list = [2, 3, 5, 4, 1]
echo filter(list, {index, value -> value >= 4})
let list = [2, 3, 5, 1, -1]
echo map(list, {index, value -> index + value})


let number = -3.14
echo number->abs()
echo number->abs()->cos()
echo number->abs()->cos()->acos()

Lambda method :

let number = 3
echo number->{ x -> 2 * x }()
echo number->{ x, y -> x + y }(4)


fun! Fonctional(fn, value)
	fun! Function(arg) closure
		return a:fn(a:arg, a:value)
	return funcref('Function')

let list = range(10)
" add(list, element)
echo Fonctional(function('add'), 3)(list)
let F = Fonctional(function('add'), 5)
echo F(list)

Dict functions

Dict function :

fun! Fundict (arg) dict
	echo a:arg
" associate it with a dict
let dict = {'name' : 'John'}
let dict.fn = function('Fundict')
" call it
call dict.fn('Doe')
" function ref
let F = function('Fundict', dict)
call F('Smith')
" with empty arg
let G = function('Fundict', [], dict)
call G('Foo')
" with predefined arg
let H = function('Fundict', ['Bar'], dict)
call H()

Anonymous function :

let dict = {'name' : 'John'}
fun! dict.iam (arg) dict
	echo a:arg
call dict.iam('Will')

Tabs & windows


Each window has a :

Tab number

let current_tab = tabpagenr()
let last_tab = tabpagenr('$')

Buffer list of a tab

With tabpagebuflist() :

let last = tabpagenr('$')
for tab in range(1, last)
	echo tab ':' tabpagebuflist(tab)

Local window number

Get id :

let win_nr = winnr()
echo win_nr

Current local number in a given tab :

let tab_nr = 2
let win_nr = tabpagewinnr(tab_nr)
let last_win_nr = tabpagewinnr(tab_nr, '$')
let num_win_on_tab = last_win_nr

Go to it :

let win_nr = 2
execute win_nr 'wincmd w'

Global id

Get id :

let win_local_nr = 2
let tab_nr = 3
let win_id = win_getid(win_local_nr, tab_nr)
echo win_id
let current_win_id = win_getid()
echo current_win_id

Go to id :

let win_id = 1012
call win_gotoid(win_id)

Find a window

Find in which window(s) is displayed a buffer :

let win_list = win_findbuf(buf_nr)


Command maps

Of course, you can use :

" command
nnoremap <c-right> :tabnext<cr>
" with <c-u> to avoid things like :'<,'>
nnoremap <c-right> :<c-u>tabnext<cr>
" call function
nnoremap <c-right> :call goto_next_tab()<cr>
" from insert mode
inoremap <c-right> <esc>:tabnext<cr>i

but it’s often better to use <cmd> :

" command, no need to add <c-u>
nnoremap <c-right> <cmd>tabnext<cr>
" call function
nnoremap <c-right> <cmd>call goto_next_tab()<cr>
" from insert mode, no need to surround with <esc>...i
inoremap <c-right> <cmd>tabnext<cr>

Advantages :


An expression map is an indirect one : it calls the function given on the rhs to obtain the actual rhs of the map.

Example : if we define the function :

fun! TakeFive ()
	return '5j'

and the map :

nnoremap <expr> <F3> TakeFive()

hitting <F3> will move the cursor down five lines.


Define a plug :

noremap <plug>(myplugin-function) <cmd>call plugin#file#function()<cr>

and let the user use it for his own map :

map <user-key> <plug>(myplugin-function)

This last map must be recursive to work.



You can trigger a custom event, somewhere in your plugin :

doautocmd User MyPluginEvent

and let the user add whatever command he wants to it :

autocmd User MyPluginEvent echo 'hello there !'

Control execution

To avoid auto-execution, use noautocmd.

This one change window without triggering autocommand :

noautocmd call win_gotoid(window_id)

If needed, you can trigger them later by using doautocmd :

doautocmd WinEnter
doautocmd BufEnter

No ordinary write

Some buffers are special, and not related to a file, like the quickfix window. In that case, writing it does not mean a simple copy to the file system, but can involve a completely distinct operation. The BufWriteCmd event is there to modify the meaning of the :write command. These autocommands are often buffer local. Example :

autocmd BufWriteCmd <buffer> call FancyStuff ()

Writing the buffer where this autocommand is defined will call FancyStuff() instead of saving to disc.

Yank ring

First define a global var to hold the ring :

let g:yankring = []

then define a function to add yanks :

fun! Add2yankring ()
	let content = getreg('"')
	call insert(yanks, content)

With this autocommand, your function will be triggered at each yank :

autocmd TextYankPost * call Add2yankring()

Common typos

Let it be

Can you see this one ?

let bufnum = bufnr('%')
let filename = bufname(bufnum)
let filename = fnamemodify(filename, ':t')
lef filename = fnamemodify(filename, ':p')
echo filename

The filename var is not what you could expect. In a big file, you can easily miss it, and you could lose time to find it, because vim will throw no error. In the fourth line, it's a lef where it should be a let. The former is a shortcut for :left.

So, when things are not working as they should, always search for \<lef\> : either it is a typo for let, or it is intended and you want to type the full left instead.