All Articles

Vim script notes

vim script

I’ve longed for a tool which would help me in manual investigation of server logs. I couldn’t find any thus I created a small Vim plugin which highlights text in the log. Getting to the state of having the plugin forced me to learn basics of the Vim scripting language.
Here I’m going to summarize few findings.

Starting with the Vim script

There are many articles about Vim script. I found nice in particular these two series:

How to load and run a function

Ok, I want to write my script, how to get it running?

When testing my steps are creating a new source code file where I put the script - normally I would create a function. At the next step I use command to load the code from the file from Vim. I use command :source <file path> for loading the code and :call <function name> to execute the function.

Let’s take an example. I have the function which I save into the file /tmp/test.function.vim.

function! ListBuffers()
  let buffers = filter(range(1, bufnr('$')), 'bufexists(v:val)')
  for buffer in buffers
    if getbufvar(buffer, "&filetype") == 'help'
      " this is a 'help' buffer, skipping
    " ec is short version for command echo
    ec 'Having an opened buffer: ' . buffer
notice use of the ! at the end of the function declaration. This says loading the function for the second time will override the existing function with the same name. If you do not add the ! then the Vim will not load the function and only shouts at you that the error happened.

The function iterates over all opened buffers and prints their name. It will skip to print the name of the buffer of type 'help'.

When I have the function I can load and execute it.

" loading the source code file aka. loading the function
" shortened version of :source command is :so
:source /tmp/test.function.vim
" invoking the function ListBuffers
" shortened version of :call command is :cal
:call ListBuffers()

If you want to get printed the return value of the function you can use :echo command. In my case the return value is 0.

" shortedned version of :echo command is :ec
:echo ListBuffers()

There is more echo commands than only the :echo. Let’s mention :echoerr which prints to error output. Or :echomsg which interpret expressions.

if you have opened n empty Vim window the function ListBuffers prints only number 1. To open another buffer (to get at least printed two numbers 1 and 2), use "badd command. :badd open buffer with a file but it does not load it. For example :badd something.

Now I can start to tune my function. But it’s a bit cumbersome to write :so and :cal for any change in the script. What about map these commands to a keyboard shortcuts? In the following example if I press ;s the source is loaded from the file and after pressing ;b the function listing buffers is invoked.

" mapping loading the file with :source command to the keyboard shortcut ';s'
:nmap ;s :source /tmp/test.function.vim<CR>
" mapping calling the function ListBuffers to the keyboard shortcut ';b'
:nmap ;b :call ListBuffers()<CR>
:nmap <silent> ;= :call ListBuffers()<CR>
  • <CR> means literal carriage return which is needed when function should be executed (it replaces hitting `<enter> when you write it at the Vim command line)

  • <silent> causes that nmap mapping does not echo the command which is executing. In our case where the function just prints to the command line the usage of the silent won’t make any difference in the behaviour.

Let me know better ways in this.

A little bit more about key mapping in Vim

Why mapping keys have to be put between <C-R>= and <CR> in the insert mode?

  • <C-R>= tells Vim to insert the result of the command (a function name which ends with ()) to the text.

  • <CR> tells Vim to actually evaluate the preceding expression.

For example you can create mapping to insert current date at place the cursor stands when you double press the Ctrl+D in the insert mode this way

" double CTRL+D prints date into the place of cursor
:imap <silent> <C-D><C-D> <C-R>=strftime("%e %b %Y")<CR>
" double CTRL+T prints time into the place of cursor
:imap <silent> <C-T><C-T> <C-R>=strftime("%l:%M %p")<CR>
" CTRL+Y prints output of the function 'ListBuffers' at the place of the cursor
:imap <silent> <C-Y> <C-R><C-R>=ListBuffers()<CR>

abbreviation for Ctrl+R is not the same as <CR> (literal carriage return)


it inserts the result of the subsequent expression as if it had been directly typed, which means that any special characters within the result retain their special meanings and behavior

  • To run a function that needs to escape from the insert mode use C-O (:help i_CTRL-O) ` let cursor_back = "\<C-O>:call setpos('.'," . string(cursorpos) . ")\<CR>" `

  • For mapping TAB use inoremap (see :help i_CTRL-R).

    • The inoremap is a form of the imap. It’s used when the returned characters could contain the TAB character. That means if TAB is returned it won’t invoked by the mapped function again.

See more about mapping at Vim mapping keys wiki page.

General notes on Vim scripting

  • To show messages printed during the execution try :messages (shortened to :mes).

  • For writing functions check quick tutorial at

  • The Vim uses symbol . for the strings concatenation.

  • Comments to be placed with " (double-quote)

  • String in Vim script can be declared within " too. If you want to use comment but vim script expects " for the string declaration then use |" (vertical bar + double-quote) as the separator.

Each statement ends with end of line. For the statement over more lines use \(backslash) at the start of the line, e.g.

call SetName(
\  first_name,
\  second_name

On the other hand more statements could be put onto one line when separated with the vertical bar (|)

echo "Starting..." | call ListBuffers() | echo "Done"

A strange thing (at least for me) is existence of the prefixed variables. If you won’t use the prefix they do not work for you as you would expect. See below or check list of prefixes at

Notes on functions writing a text to the opened document

  • For adding a text to the current line you can use function setline. :call setline(line('.'), getline('.') . ' hello') which adds text ' hello' at the end of the current line

  • For pasting a new line to the position of the particular row you can use function append. :call append(line('.'), "new line to be added at the bottom of the current line")

Vim scripting language cheatsheet


Table 1. Variables types
type example


let height = 165


let interests = [ 'Cinema', 'Literature', 101 ]


let phone = { 'cell':5551017346, 'work':'?' }


Variable types, once assigned, are permanent and strictly enforced at runtime. As we set the interests as a list there will be error now (see at, vim script part 1)

let interests = 'unknown' " Error: variable type mismatch

Table 2. Scopes and prefix meanings
Prefix Meaning


The variable is global


The variable is local to the current script file


The variable is local to the current editor window


The variable is local to the current editor tab


The variable is local to the current editor buffer


The variable is local to the current function


The variable is a parameter of the current function


The variable is one that Vim predefines

Table 3. Pseudovariables
Prefix Meaning


A Vim option (local option if defined, otherwise global)


A local Vim option


A global Vim option


A Vim register


An environment variable


Table 4. Pseudovariables
Operation Operator syntax


let var=expr


let var+=expr


let var-=expr


let var.=expr

Ternary operator


Logical OR



Logical AND


Numeric or string equality


String case insensitive eq


String case sensitive eq


Numeric or string inequality


Numeric or string greater-then


Numeric or string gr-or-eq


Numeric or string less than


Numeric or string l-or-eq


Numeric addition


Numeric subtraction


String concatenation


Numeric multiplication


Numeric division


Numeric modulus


Convert to number


Numeric negation


Logical NOT


Parenthetical precedence


  • numeric value zero is false in the boolean context; any non-zero numeric value is considered true

  • when a string is used as a boolean, it is first converted to an integer, and then evaluated for true

    • for checking emptiness is needed to be used a function: empty(a_string)

  • comparators always perform numeric comparison (unless both operands are strings). In particular, if one operand is a string and the other a number, the string will be converted to a number.

  • let ident='Vim'

    • ident == 0 always numeric equality - always true (string 'Vim' converted to number 0)

    • ident == '0' uses string equality if ident contains string but numeric equality if ident contains number

  • case sentistive/insensitive could be used with any comparators (<# operator means less-than case sensitive)

    • string comparision honor the settings of vim’s ignorecase option, using the # and ? will force either case sensitive or case insensitive comparision

Floating point operations

Floating point has to be explicitly marked. Floating point arithmetic comes at Vim 7.2 and later.

let filecount = 234

echo filecount/100   |" echoes 2
echo filecount/100.0 |" echoes 2.34

Code structure/Syntax

IF syntax

if left_width >= 0
    let max_align_col = max([max_align_col, left_width])

FOR syntax

for linetext in getline(firstline, lastline)
    " working with text on the line
for linenum in range(firstline, lastline)
    " working with line numbers

Iterating over nested list

for [name, rank, serial] in list_of_lists
    echo rank . ' ' . name . '(' . serial . ')'

Ternary operator

return completion . (restore ? cursor_back : "")

If with regexp (if statement, conditional)

if curr_line =~ '\k' . curr_pos_pat
  return "\<C-N>"

Elif statement (elseif statement)

if 0
 echom "if"
elseif "nope!"
 echom "elseif"
 echom "finally!"


" mkdir can throw E739 error when is unable to create the requested directory
    call mkdir( required_dir, 'p' )
    echo "Can't create directory " . required_dir


List creation could be
let data = [1,2,3,4,5,6,"seven"]

And various manipulation on it - e.g. indices less than zero, which then count backward from the end of the list: let data[-1] .= ' samurai'

List comparision
  • operator = compares values (all values are the same, containers can be different)

  • operator is compares identity (containers have to be the same)

Nested list
let pow = [
\   [ 1, 0, 0, 0  ],
\   [ 1, 1, 1, 1  ],
" and later...
echo pow[x][y]
List concatenation
let activities = ['sleep', 'eat'] + ['game', 'drink']
let activities += ['code']
let weekdays = week[1:5]
  • list assignment to a variable is assignment of pointer/reference. For having copy use function copy() or deepcopy().

Filter and map functionality

That’s specific functionality working on lists. Filter filters values and map applies some function on each value of the list.

  • let positive_only = filter(copy(list_of_numbers), 'v:val >= 0')

  • let increased_numbers = map(copy(list_of_numbers), 'v:val + 10')


let seen = {}   " Haven't seen anything yet
let daytonum = { 'Sun':0, 'Mon':1, 'Tue':2, 'Wed':3, 'Thu':4, 'Fri':5, 'Sat':6 }
let day = daytonum['Sun']
For loop
for [next_key, next_val] in items(dict)
    let result = process(next_val)
    echo "Result for " next_key " is " result
remove(dictionary, 'key')
unlet dictionary['key']  "command unlet used

Functions declaration

  • function <name>() declares function, name has to be unique, parentheses can contain arguments of the function

  • function! <name>() declares function, if function of the name exists it’s overridden

  • function ends with definition endfunction

  • function name has to start with capital letter or with s: which declares it as local for the current script file

Function could be scoped in the same way as variables can be - e.g. function s:<name>() says that function is visible only in scope of current script file (see s:).


In difference from other scripting languages you can’t ignore return value of function. If function returns anything you need to use it - ie. let a = s:fuctionname(). Or you can use echo command like echo s:functionname().

If function does not return anything then you can invoke it by calling through call like call s:functionname().

Function arguments

function name(param, param2)

You can access to parameters by name or by position. In both cases you need to use prefix a: for get value.

function printme(text)
  echo a:text
  echo a:1
if position argument is used then a:0 contains number of arguments.

For undefined number of arguments use …​. For examplee function CommentBlock(comment, …​). Now you can access to comment as a:comment and any other arguments are accesible via position parameter declaration. You can check number of argument by let introducer = a:0 >= 1 ? a:1 : "//".

Normally the function is called in scope of current line. You can define scope that function work at, by scope definition <from,to>call <function_name>. For example 5,$call CommentBlock will call CommentBlock function for each line starting line number 5 and ending at the end of the file.

If we want to have special handling of the ranges we can say that range attributes won’t be considered and function will called just once function DeAmperfyAll() range. The word range says to call function only once (not once for each line in the range). Then there could be added special parameters a:firstline and a:lastline which returns the range that user called the function within. We can use it for example for linenum in range(a:firstline, a:lastline)

If visual mode is used then function for the range of the visual block could be used as Vip:call DeAmperfyAll().

Interesting out-of-the-box functions

To help and to see what functions we can use :help functions and listing :help function-list


internal statement showing a string (result of function) on line at bottom of window


says if particular property is declared/exists - e.g. exists('b:backup_count')


ask user to write a text and that is returned from the function


returns on-screen column (or "virtual column"), '.' argument specifies that you want the column number of the current cursor position


returns position of cursor - for current line it’s getpos('.')


setting position of cursor


function to look backwards through the file from the cursor position, search(regexp pattern to find, configuration string - e.g. bnW means search backwards but not to move the cursor nor to wrap the search, if search fails returns 0; or flags nW search downwards and returns -1 if search hit the end of file


shows guifont name, works only for gvim, font name could be set with :set guifont=Monospace\ 20 or on win :set guifont=Monospace:h20


returns line defined with number - getline(line_number)


brother of getline() but it repaces text at passed line and changes to particular text setline('.', 'hello') - this changes text on current line for phrase 'hello'


returns line number e.g. line('.') returns number of the current line or line('$') returns number of last line of the text


returns part of the string that matches pattern - ie. matchstr(string_to_check, target_pattern) - returns that part of the string where target pattern matches, ie. matchstr('abc', 'b.') returns 'bc'


tries to match a character from text defined by regexp - ie. match(linetext, '\s*', ASSIGN_OP) returns -1 if does not match assigment operation character in the text


returns a list of all the fields captured by the regex - ie. matchlist(linetext, regexp)


substitutes text - substitute(text_of_line, regexp_to_find_on_the_line, replacement_string, flags/tags)


returns bigger number from a list (see below)


returns length of a string


printing text in reformated way specified by formatter


used to evaluate a string as if it were a Vimscript command

silent! execute

executing regexp stuff - e.g. silent ! execute "'[,']s/" . signature . '/\= ' . replacement . '/'


expanding expression, expansion could be modified (see :h expand), for example "head" of file path of currently opened file filepath: expand("%:h")


is requested path directory which exists


creating directory, flags could be used mkdir(dir_path, 'p') where flag p means create parent dir if not exists


asking user for confirmation, it will gives options for user in way of confirm("is that ok?", "yes\nno") and returns 1 or 2


inner command that exits function or so


returns current date


Functions for list


providing shallow copy of a list (as normally assigning a list to different property means only referencing the same pointer)


providing deep copy of a list


length of list


is list empty?, the same as len(a_list) == 0


maximum or minimum from list of numbers


index of first occurrence of value or pattern in list, is index(list, value), uses == comparision


index of first occurrence of value or pattern in list, is match(list, pattern), uses =~ comparision


generating list of numbers in some range e.g. range(min,max,step)


split to sequence of word ie. split(text, delimiter_pattern)


joining list values





remove({list},{index}) removes item from the list and returns it



Function for dictionaries


list of keys from dictionary


list of values from dictionary


say if particular dictionary has a key


list of lists where each sublist contains 'key' and 'value' of the dictionary item


adding other dictionary to a dictionary


remove key from dictionary


remove from dictionary (inner command, not a function)


true if no entries at all


how many entries?


how many values are equal to str?


find largest value of any entry


find smallest value of any entry

call map()

transform values by eval’ing string

echo string()

print dictionary as key/value pairs

Vim events

To get info about what are available events for the hooks run :help autocmd-events or for detailed info :help autocmd-events-abc.

Mechanism of interception of events is known as autocommand.

autocmd  EventName  filename_pattern   :command
autocmd  EventName,EventName2,...  filename_pattern   :command
autocmd  EventName  filename_pattern   :silent command
  • EventName is one of help page :help autocmd-events, if more events are specified the autocmd will be invoked for any of them

  • filename_pattern is similar to bash pattern see :help autocmd-patterns

  • command is any valid vim command (colon at the start of command is optional but recommended)

    • Vim normally displays a notification after command completes. To disable that the option silent could be used

Autocommands could be grouped and then worked together

    " autocommand specifications here ...
augroup END

Deactivation with autocmd! command

" generic syntax
autocmd!  [group]  [EventName [filename_pattern]]

" deactivate whole group not depending on event name
autocmd!  Unfocussed      *      *.txt
" deactivate whole group not concerning file type
autocmd!  Unfocussed

autocmd! itself deactivates from current group and is useful for doing cleanup before settings group autocommands. Adding an autocmd! to the start of every group is important because autocommands do not statically declare event handlers; they dynamically create them.

augroup Unfocussed

    autocmd  FocusLost  *.txt   :call Autosave()
augroup END

Another useful set of events are BufWritePre, FileWritePre, and FileAppendPre. These Pre events are queued just before your Vim session writes a buffer back to disk (as a result of a command such as :write, :update, or :saveas). For all three types of events, Vim sets the special line-number aliases '[ and '] to the range of lines being written. For example usage for s is:

'[,']s/^This file last updated: \zs.*/\= strftime("%c") /

User commands

:help user-commands

Simple example how user can create his own command: command Showme echo 'hello'

user command has to start with the capital letter

For command that will call a function and pass arguments do

command! -nargs=1 MyCommand call s:MyFunc(<f-args>)
command! -nargs=1 Showme echo <f-args>

For quoting arguments use (as seen above for echo we can do it easier without execute command being used but this is for showing how that could be)

:command! -nargs=1 FW execute "echo" string(<q-args>)


These were my notes on Vim scripting. Hopefully somebody finds them useful. I will be happy if you let me know how to enhance this guide.

Published Aug 7, 2018

Developer notes.