Vim script: Functions

Functions in Vim script starts with the function keyword and ends with the endfunction keyword.

:function PrintMessage()
    :echo "Hello World"
:endfunction

To run this function use the call command:

:call PrintMessage()

Function names

User-defined functions in Vim script must start with a capital letter unless they are explicitly scoped. This is to prevent name conflicts with Vim’s internal functions. Functions without an explicit scope like the one in the previous example, have global scope. They can be accessed from anywhere in Vim. Script local functions are discussed later in the article.

Reloading functions

Sometimes you need to modify and reload existing functions. This works fine as long as you are reloading from the same script where the function was originally defined. But if you move the function to another script or try to redefine it from the command line, Vim will throw an error. You can use the function! keyword instead of function to force a replacement. Use the ! option with care since it hides name conflicts and can cause hard to debug errors.

Returning a value

Use the return keyword to return a value.

:function GetMessage()
    :return "Hello World"
:endfunction

Test this function using the following command:

:echo GetMessage()

The return statement can be used to short circuit a function.

:function SampleFunction()
    :let error = 1
    :if error
        :echo "Exiting function"
        :return
    :endif
    :echo "Continuing with the function"
:endfunction

:call SampleFunction()

This function returns after printing “Exiting function”. If you change the error variable to 0, the function will run till the end. If the return keyword is used without an argument it returns 0. Similarly, if the function ends without hitting a return, a value of 0 is returned.

Calling a function

We have already seen two examples of how to call a function, but it deserves an explanation. If the function does not return a value or if you don’t intend to consume the returned value, you have to use the call command to execute the function.  If you want to use the return value, then don’t use the call command. 

:let msg=GetMessage()

We will see some extra functionality provided by the call command later.

Variable Scope

Unscoped variables defined inside a function are local to the function. They are not accessible outside the function. Inside a function, let variable_name=1 is the same as let l:variable_name=1. If you want a different scope, specify it explicitly. Example: let g:variable_name=1.

Passing arguments to functions

Let us start with a simple example.

:function Sum(num1,num2)
    :return a:num1 + a:num2
:endfunction

:echo Sum(1,2)

This is similar to functions in other languages, except for one difference. To access function arguments you have to prefix them with a:

Variable number of arguments

In Vim script you can define functions with a variable number of optional arguments. You can have a maximum of 20 additional arguments.

:function Sum(...)
    :let sum = 0
    :for num in a:000
        :let sum+=num
    :endfor
    :return sum
:endfunction

:echo Sum(1,2,3,4,5)

The notation “…” stands for variable number of arguments. It is possible to pass named, fixed arguments along with variable arguments. Make sure to put the fixed arguments before the variable arguments.

:function AvgWithFilter(min,max,...)

The additional arguments are stored in a list named a:000. The arguments can also be accessed using the notation a:1, a:2, a:3, etc. Here a:1 is the first additional argument, a:2 is the second additional argument, and so forth. The count of additional arguments is stored in a:0.

Default Arguments

Unlike many other languages, Vim script does not have direct support for default arguments. But optional arguments can be used to simulate the functionality.

:function Backup(...)    
    :let backup_location = get(a:000, 0, '~/.vim/temp')
    :let extension = get(a:000, 1, 'bak')
    :echo backup_location
    :echo extension
:endfunction

:call Backup("~/.vim/swap")

Here we use the get function to handle the arguments. The get function takes a list, a list index, and an optional default value as arguments. If the item is available in the list it returns the item, else the default value is returned.

The abort option

Unlike most languages, Vim script functions do not abort if it encounters an error.

:function AbortExample()
    :set a=1
    :echo "Still Working"
:endfunction

:call AbortExample()

Vim prints an error message when it encounters the first line. But it will continue executing the rest of the function. This might not be the behavior you want; you can use the abort argument to tell Vim to stop the execution as soon as it encounters an error.

:function AbortExample() abort
    :set a=1
    :echo "Still Working"
:endfunction

:call AbortExample()

Now Vim script’s behavior becomes similar to most other languages. But Vim script is not like other languages, many Vim commands throw errors when we don’t expect them to. For example, the substitute command causes an error if it does not find the search pattern. To suppress the error you have to use the :silent! option. In the case of the substitute command, you can also use the e switch.

Whether to use abort or not is a matter of personal preference. But this unusual behavior of Vim is something you should be aware of.

Range

You can put a range in front of the call command.

:function RangeLine()
    :echo getline('.')
    :.s#\v^(\s*)(.+$)#\1:\2
:endfunction

:6,10call RangeLine()

RangeLine() will be called for each line in the range. The function prints the current line using getline() and puts a colon in front of non-empty lines.

You might want to process the range of lines with a single function call. Use the range argument to achieve this. A special variable a:firstline will be automatically set to the start of the range and a:lastline will be set to the end the range.

:function RangeBlock() range
    :execute a:firstline . ',' . a:lastline . 's#\v^(\s*)(.+$)#\1:\2'
:endfunction

When using the range argument you can use a for loop with the range() function to process each line.

:function RangeBlock() range    
    :for line_number in range(a:firstline, a:lastline)
        :let current_line = getline(line_number)
        :echo current_line        
    :endfor
:endfunction

Both the RangeLine() and RangeBlock() functions work with visual selections as well. Select the lines you want the function to apply to and type a colon. Vim will set the range for you; now just call the function.

:'<,'>call RangeBlock()

You can create a mapping for either of the functions and make use of Vim’s numeric prefixes.

:noremap <leader>rb :call RangeBlock()

If we press “4<leader>rb”, Vim will apply RangeBlock() to 4 lines starting with the current line. This mapping works for visual selections as well.

Script local functions

Script local functions are accessible only inside the script they are defined. To create a script local function prefix the function name with s:

:function s:get_vimdir()
    :return fnamemodify(expand("$MYVIMRC"), ":p:h")
:endfunction

A common practice is to make all the helper functions script local and make only the functions that make up the public interface of the script/plugin global. This is similar to public and private functions in other languages.

There are ways of making script local functions accessible outside the script but this is beyond the scope of this article.

Autoloads

The purpose of autoloads is to make Vim’s startup faster. They are useful if you are writing a large number of functions or real plugins. If you only have a few functions, you don’t need to bother with autoloads; just put them in your vimrc.

The autoload mechanism allows you to load a script on-demand. Autoload scripts are not loaded when Vim starts up, they are loaded only when a function defined in the script is called. This could lead to a slight first-time delay. All the functions defined in the script are loaded at this point, not just the one that was called. Any additional calls to functions in the script will be serviced from memory and will be fast.

To take advantage of autoload’s performance gain you have to group related functions and put each group in different scripts.

Where should autoload scripts be created?

Create autoload scripts inside a directory named autoload; the parent of the autoload directory must be in your runtimepath. For example .vim/autoload/autoload_script.vim. You can also use directories and subdirectories. Example:  .vim/autoload/group1/subgroup2/autoload_script.vim. If you are writing a plugin, create the autoload directory inside the plugin directory and use your plugin manager to put the plugin directory in Vim’s rutimepath.

How to define an autoload function?

Let us start with an example:-

:function script_name#Message()
    :echo "hello world"
:endfunction

This function must be defined inside script_name.vim in the autoload directory. To use a subdirectory define the function like so:

:function dir_name#script_name#Message()
    :echo "hello world"
:endfunction

How to call an autoload function?

Just use the full name of the function.

:call dir_name#script_name#Message()

Leave a Comment

Your email address will not be published. Required fields are marked *