vim script - chimay/wheel Wiki

Help

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

Expression

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

Numbers

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)

Variables

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
endfun
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
endfun

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.

List

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]

Dictionary

" empty
let dict = {}
echo dict
" with some keys & values
let dict = { 'one' : 'value one', 'two' : 'value 2'}
echo dict
" add keys & valuse
let dict.name = 'john'
let dict.hobbies = ['tennis', 'music']
" access values using keys
echo dict.name
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)

Copy

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 shadow.one = '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 snapshot.one = 'changed'
let snapshot['two'] = 'again'
" no change in original
echo dict
echo snapshot

Nested

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

Constants

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

Expression

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

Test for a regular file :

if filereadable(my_file)
	call do_something (my_file)
endif

Test for a directory :

if isdirectory(my_dir)
	call do_something (my_dir)
endif

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')

Globbing

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)

Buffers

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 ':' buf.name
endfor

Listed buffers :

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

Lines

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().

Output

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
tabs
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

Functions

Name

Variable containing a function name :

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

Arguments

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

fun! Fun (argument)
	return a:argument
endfun
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
endfun
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
endfun
let list = range(5)
echo Fun (list, 7)

Mixed

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

Autoload

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 ()
	...
endfun

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

References

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

Lambda

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})

Method

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)

Closure

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

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 self.name a:arg
endfun
" 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 self.name a:arg
endfun
call dict.iam('Will')

Tabs & windows

Introduction

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)
endfor

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)

Mappings

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 :

Expression

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'
endfun

and the map :

nnoremap <expr> <F3> TakeFive()

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

Plugs

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.

Autocommands

Custom

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)
endfun

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.