The VimScript language is rather idiosyncratic, and because few people – if anyone – uses it to make a living, not many people learn it in-depth.

This document gives tips for writing VimScript code.

For string comparison use is# or is? , as the behaviour of is and == are affected by the ignorecase setting. In general, it’s a good practice to just always use is# instead of == ; it will work fine for other types as well.

Use is instead of == . Like PHP and JavaScript == will coerce types ( '0' == 0 ). The is operator doesn’t, like === . You do need to be careful when comparing entire lists or dicts, since is will test identity rather than value.

Use lambda expressions rather than string functions when supported (e.g. map() and filter() ). This allows syntax highlighting and avoids ugly string escaping. This is available since Vim 7.4.2044 (July 2016).

Use execute() rather than redir . It’s a lot less clumsy. Many older Stack Overflow answers and the like recommend :redir , but execute() is available since Vim 7.4.2008 (July 2016).

Prefer printf() over string concatenation ; e.g. echo printf('x: %s', [42]) will work, whereas echo 'x: ' . [42] will give you a useless error.

This is also an issue when passing lists or dicts as arguments; since they’re passed by reference rather than value, modifications are not local to just the function:

l:filtered will be ['a', 'b'] , but so will g:plugin_default , which is probably not what you wanted! Use copy() or deepcopy() instead:

Be careful when modifying lists and dicts . Many operations change the value of a list or dict in place but also return the new value. This leads to code like:

Other common use cases are restoring the view with winsaveview() / winrestview() and changing the directory with :cd .

The finally block will always get executed:

But the resetting of &setting will fail if the code returns in the “do work” path, either because of a return or because of an error.

Use try .. finally A common pattern is something like:

abort functions . Without it Vim will keep executing code after an error, which rarely what you want. Use the abort keyword to abort function execution on error (e.g. fun! MyFun() abort ). You can still use try .. catch to recover from errors.

Scope to filetype. Quite a few plugins that work on only a single filetype exist globally. There is no reason to have a :PythonFrob when editing Ruby files. Moving plugin/myplugin.vim to ftplugin/python.vim and adding buffer to :command and :map is often all that’s needed; this will only load the file when that filetype is set and scope the commands and mappings to the buffer. See :help ftplugin for details. Alternatively, you can use a FileType autocmd if your plugin works for many different filetypes.

Use autoload. VimScript in the plugin and ftplugin directory will always be loaded on startup. Code in the autoload directory will be loaded on first use. This also allows some rudimentary modularisation instead of putting everything in the global namespace (e.g. plugin#foo#fun() instead of PluginFooFun() ). See :help autoload . Using plugin might be okay if your plugin is very small though.

Make functions script-local when possible; not everything needs to be exported. Consider using s:name() when it’s only used in the current script context and isn’t useful for your plugin users. Note: you can use <SID> in mappings, e.g. nnoremap x <SID>fun()<CR> .

There are a few different approaches to plugin settings, the “best way” depends on the plugin and personal preference: Define the default on use: call do_something ( get ( g:, 'plugin_setting' , 'default value' )) Advantage: it’s the simplest approach. Downside: you’ll have to define the default multiple times if used more than once. Many plugins don’t, so it can still be a good option for small plugins. Another downside is that users can’t inspect current value. Want to know what g:plugin_setting is? You’ll have to read the docs and hope they’re correct. Define the defaults on startup: let g:plugin_setting = get ( g:, 'plugin_setting' , 'default value' ) Advantage: one location for the default values, allows users to inspect values. Downsides: may add a lot of global variables. In general, I would recommend the first approach for small plugins and the second one for larger ones. Other considerations: Always prefix variable names with the name of your plugin.

Make sure to document all settings and add appropriate help tags. This means people can easily discover documentation with :help g:plugin_<Tab> .