learnxinyminutes-docs/vimscript.html.markdown
2019-12-22 12:34:17 +01:00

21 KiB

language filename contributors
Vimscript learnvimscript.md
HiPhish
http://hiphish.github.io/

Introduction

Vim script (also called VimL) is the subset of Vim's ex-commands which supplies a number of features one one would expect from a scripting language, such as values, variables, functions or loops. Always keep in the back of your mind that a Vim script file is just a sequence of ex-commands. It is very common for a script to mix programming-language features and raw ex-commands.

You can run Vim script directly by entering the commands in command-mode (press : to enter command-mode), or you can write them to a file (without the leading :) and source it in a running Vim instance (:source path/to/file). Some files are sourced automatically as part of your configuration (see :h startup). This guide assumes that you are familiar with ex-commands and will only cover the scripting. Help topics to the relevant manual sections are included.

See :h usr_41.txt for the official introduction to Vim script. A comment is anything following an unmatched " until the end of the line, and | separates instructions (what ; does in most other languages).

" This is a comment

" The vertical line '|' (pipe) separates commands
echo 'Hello' | echo 'world!'

" Putting a comment after a command usually works
pwd                   " Displays the current working directory

" Except for some commands it does not; use the command delemiter before the
" comment (echo assumes that the quotation mark begins a string)
echo 'Hello world!'  |" Displays a message

" Line breaks can be escaped by pacing a backslash as the first non-whitespace
" character on the *following* line. Only works in script files, not on the
" command line
echo " Hello
    \ world "

echo [1, 
    \ 2]

echo {
    \ 'a': 1,
    \ 'b': 2
\}

Types

For an overview of types see :h E712. For an overview of operators see :h expression-syntax

Numbers

See :h expr-number

echo  123         |" Decimal
echo  0b1111011   |" Binary
echo  0173        |" Octal
echo  0x7B        |" Hexadecimal
echo  123.0       |" Floating-point
echo  1.23e2      |" Floating-point (scientific notation)

Note that an integer number with a leading 0 is in octal notation. The usual arithmetic operations are supported.

echo  1 + 2       |" Addition
echo  1 - 2       |" Subtraction
echo  - 1         |" Negation (unary minus)
echo  + 1         |" Unary plus (does nothing really, but still legal)
echo  1 * 2       |" Multiplication
echo  1 / 2       |" Division
echo  1 % 2       |" Modulo (remainder)

Booleans

See :h Boolean

The number 0 is false, every other number is true. Strings are implicitly converted to numbers (see below). There are two pre-defined semantic constants.

echo  v:true      |" Evaluates to 1 or the string 'v:true'
echo  v:false     |" Evaluates to 0 or the string 'v:false'

Boolean values can result from comparison of two objects.

echo  x == y             |" Equality by value
echo  x != y             |" Unequality
echo  x >  y             |" Greater than
echo  x >= y             |" Greater than or equal
echo  x <  y             |" Smaller than
echo  x <= y             |" Smaller than or equal
echo  x is y             |" Instance identity (lists and dictionaries)
echo  x isnot y          |" Instance non-identity (lists and dictionaries)

" Strings are compared based on their alphanumerical ordering
" echo 'a' < 'b'. Case sensitivity depends on the setting of 'ignorecase'

" Explicit case-sensitivity is specified by appending '#' (match case) or '?'
" (ignore case) to the operator. Prefer explicity case sensitivity when writing
" portable scripts.

echo  'a' <  'B'         | " True or false depending on 'ignorecase'
echo  'a' <? 'B'         | " True
echo  'a' <# 'B'         | " False

" Regular expression matching
echo  "hi" =~  "hello"    |" Regular expression match, uses 'ignorecase'
echo  "hi" =~# "hello"    |" Regular expression match, case sensitive
echo  "hi" =~? "hello"    |" Regular expression match, case insensitive
echo  "hi" !~  "hello"    |" Regular expression unmatch, use 'ignorecase'
echo  "hi" !~# "hello"    |" Regular expression unmatch, case sensitive
echo  "hi" !~? "hello"    |" Regular expression unmatch, case insensitive

Boolean operations are possible.

echo  v:true && v:false       |" Logical AND
echo  v:true || v:false       |" Logical OR
echo  ! v:true                |" Logical NOT
echo  v:true ? 'yes' : 'no'   |" Ternary operator

Strings

See :h String

An ordered zero-indexed sequence of bytes. The encoding of text into bytes depends on the option :h 'encoding'.

" Literal constructors
echo  "Hello world\n"   |" The last two characters stand for newline
echo  'Hello world\n'   |" The last two characters are literal
echo  'Let''s go!'      |" Two single quotes become one quote character

Single-quote strings take all characters are literal, except two single quotes, which are taken to be a single quote in the string itself. See :h expr-quote for all possible escape sequences.

" String concatenation
" The .. operator is preferred, but only supported in since Vim 8.1.1114
echo  'Hello ' .  'world'  |" String concatenation
echo  'Hello ' .. 'world'  |" String concatenation (new variant)

" String indexing
echo  'Hello'[0]           |" First byte
echo  'Hello'[1]           |" Second byte
echo  'Hellö'[4]           |" Returns a byte, not the character 'ö'

" Substrings (second index is inclusive)
echo  'Hello'[:]           |" Copy of entire string
echo  'Hello'[1:3]         |" Substring, second to fourth byte
echo  'Hello'[1:-2]        |" Substring until second to last byte
echo  'Hello'[1:]          |" Substring with starting index
echo  'Hello'[:2]          |" Substring with ending index
echo  'Hello'[-2:]         |" Substring relative to end of string

A negative index is relative to the end of the string. See :h string-functions for all string-related functions.

Lists

See :h List

An ordered zero-indexed heterogeneous sequence of arbitrary Vim script objects.

" Literal constructor
echo  []                   |" Empty list
echo  [1, 2, 'Hello']      |" List with elements
echo  [1, 2, 'Hello', ]    |" Trailing comma permitted
echo  [[1, 2], 'Hello']    |" Lists can be nested arbitrarily

" List concatenation
echo  [1, 2] + [3, 4]      |" Creates a new list

" List indexing, negative is relative to end of list (:h list-index)
echo  [1, 2, 3, 4][2]      |" Third element
echo  [1, 2, 3, 4][-1]     |" Last element

" List slicing (:h sublist)
echo  [1, 2, 3, 4][:]      |" Shallow copy of entire list
echo  [1, 2, 3, 4][:2]     |" Sublist until third item (inclusive)
echo  [1, 2, 3, 4][2:]     |" Sublist from third item (inclusive)
echo  [1, 2, 3, 4][:-2]    |" Sublist until second-to-last item (inclusive)

All slicing operations create new lists. To modify a list in-place use list functions (:h list-functions) or assign directly to an item (see below about variables).

Dictionaries

See :h Dictionary

An unordered sequence of key-value pairs, keys are always strings (numbers are implicitly converted to strings).

" Dictionary literal
echo  {}                       |" Empty dictionary
echo  {'a': 1, 'b': 2}         |" Dictionary literal
echo  {'a': 1, 'b': 2, }       |" Trailing comma permitted
echo  {'x': {'a': 1, 'b': 2}}  |" Nested dictionary

" Indexing a dictionary
echo  {'a': 1, 'b': 2}['a']    |" Literal index
echo  {'a': 1, 'b': 2}.a       |" Syntactic sugar for simple keys

See :h dict-functions for dictionary manipulation functions.

Funcref

See :h Funcref

Reference to a function, uses the function name as a string for construction. When stored in a variable the name of the variable has the same restrictions as a function name (see below).

echo  function('type')                   |" Reference to function type()
echo  funcref('type')                    |" Reference by identity, not name
echo  {x -> x * x}                       |" Anonymous function
echo  function('substitute', ['hello'])  |" Partial function

A lambda (:h lambda) is an anonymous function; it can only contain one expression in its body, which is also its implicit return value.

Regular expression

See :h regular-expression

A regular expression pattern is generally a string, but in some cases you can also use a regular expression between a pair of delimiters (usually /, but you can choose anything).

" Substitute 'hello' for 'Hello'
substitute/hello/Hello/

Implicit type conversions

Strings are converted to numbers, and numbers to strings when necessary. A number becomes its decimal notation as a string. A string becomes its numerical value if it can be parsed to a number, otherwise it becomes zero.

echo  "1" + 1         |" Number
echo  "1" .. 1        |" String
echo  "0xA" + 1       |" Number

" Strings are treated like numbers when used as booleans
echo "true" ? 1 : 0   |" This string is parsed to 0, which is false

Variables

Variables are bound within a scope; if no scope is provided a default is chosen by Vim. Use :let and :const to bind a value and :unlet to unbind it.

let b:my_var = 1        |" Local to current buffer
let w:my_var = 1        |" Local to current window
let t:my_var = 1        |" Local to current tab page
let g:my_var = 1        |" Global variable
let l:my_var = 1        |" Local to current function (see functions below)
let s:my_var = 1        |" Local to current script file
let a:my_arg = 1        |" Function argument (see functions below)

" The Vim scope is read-only
echo  v:true            |" Special built-in Vim variables (:h v:var)

" Access special Vim memory like variables
let @a = 'Hello'        |" Register
let $PATH=''            |" Environment variable
let &textwidth = 79     |" Option
let &l:textwidth = 79   |" Local option
let &g:textwidth = 79   |" Global option

" Access scopes as dictionaries (can be modified like all dictionaries)
" See the :h dict-functions, especially get(), for access and manipulation
echo  b:                |" All buffer variables
echo  w:                |" All window variables
echo  t:                |" All tab page variables
echo  g:                |" All global variables
echo  l:                |" All local variables
echo  s:                |" All script variables
echo  a:                |" All function arguments
echo  v:                |" All Vim variables

" Constant variables
const x = 10            |" See :h :const, :h :lockvar

" Function reference variables have the same restrictions as function names
let IsString = {x -> type(x) == type('')}    |" Global: capital letter
let s:isNumber = {x -> type(x) == type(0)}   |" Local: any name allowed

When omitted the scope g: is implied, except in functions, there l: is implied.

Multiple value binding (list unpacking)

" Assign values of list to multiple variables (number of items must match)
let [x, y] = [1, 2]

" Assign the remainer to a rest variable (note the semicolon)
let [mother, father; children] = ['Alice', 'Bob', 'Carol', 'Dennis', 'Emily']

Flow control

Conditional

Conditions are set between if and endif. They can be nested.

if condition
    echo 'First condition'
elseif another_condition
    echo 'Second condition'
else
    echo 'Fail'
endif

Loops

Two types of loops: :for and :while. Use :continue to skip to the next iteration, :break to break out of the loop.

For-loop

For-loops iterate over lists and nothing else. If you want to iterate over another sequence you need to use a function which will create a list.

" Iterate over a list
for person in ['Alice', 'Bob', 'Carol', 'Dennis', 'Emily']
    echo 'Hello ' .. person
endfor

" Iterate over a nested list by unpacking it
for [x, y] in [[1, 0], [0, 1], [-1, 0], [0, -1]]
    echo 'Position: x ='  .. x .. ', y = ' .. y
endfor

" Iterate over a range of numbers
for i in range(10, 0, -1)  " Count down from 10
    echo 'T minus'  .. i
endfor

" Iterate over the keys of a dictionary
for symbol in keys({'π': 3.14, 'e': 2.71})
    echo 'The constant ' .. symbol .. ' is a transcendent number'
endfor

" Iterate over the values of a dictionary
for value in values({'π': 3.14, 'e': 2.71})
    echo 'The value ' .. value .. ' approximates a transcendent number'
endfor

" Iterate over the keys and values of a dictionary
for [symbol, value] in items({'π': 3.14, 'e': 2.71})
    echo 'The number ' .. symbol .. ' is approximately ' .. value
endfor

While-loops

while !there_yet
    echo 'Are we there yet?'
endwhile

Exception handling

See :h exception-handling

Throw new exceptions as strings, catch them by pattern-matching a regular expression against the string

" Throw new exception
throw "Wrong arguments"

" Guard against an exception (the second catch matches any exception)
try
    source path/to/file
catch /Cannot open/
    echo 'Looks like that file does not exist'
catch /.*/
    echo 'Something went wrong, but I don't know what'
finally
    echo 'I'm done trying'
endtry

Functions

Defining functions

" Unscoped function names have to start with a capital letter
function! AddNumbersLoudly(x, y)
    " Use a: scope to access arguments
    echo 'Adding'  .. a:x ..  'and'  .. a:y   |" A side effect
    return a:x + a:y                          |" A return value
endfunction

" Scoped function names may start with a lower-case letter
function! s:addNumbersLoudly(x, y)
    echo 'Adding'  .. a:x ..  'and'  .. a:y
    return a:x + a:y
endfunction

Without the exclamation mark it would be an error to re-define a function, with the exclamation mark the new definition can replace the old one. Since Vim script files can be reloaded several times over the course of a session it is best to use the exclamation mark unless you really know what you are doing.

Function definitions can have special qualifiers following the argument list.

" Range functions define two implicit arguments, which will be set to the range
" of the ex-command
function! FirstAndLastLine() range
    echo [a:firstline, a:lastline]
endfunction

" Prints the first and last line that match a pattern (:h cmdline-ranges)
/^#!/,/!#$/call FirstAndLastLine()


" Aborting functions, abort once error occurs (:h :func-abort)
function! SourceMyFile() abort
    source my-file.vim        |" Try sourcing non-existing file
    echo 'This will never be printed'
endfunction

" Closures, functions carrying values from outer scope (:h :func-closure)
function! MakeAdder(x)
    function! Adder(n) closure
        return a:n + a:x
    endfunction
    return funcref('Adder')
endfunction
let AddFive = MakeAdder(5)
echo AddFive(3)               |" Prints 8

" Dictionary functions, poor man's OOP methods (:h Dictionary-function)
function! Mylen() dict
    return len(self.data)     |" Implicit variable self
endfunction
let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")}
echo mydict.len()

" Alternatively, more concise
let mydict = {'data': [0, 1, 2, 3]}
function! mydict.len()
    return len(self.data)
endfunction

Calling functions

" Call a function for its return value, and possibly for its side effects
let animals = keys({'cow': 'moo', 'dog': 'woof', 'cat': 'meow'})

" Call a function for its side effects only, ignore potential result
call sign_undefine()

" The call() function calls a function reference and passes parameters as a
" list, and returns the function's result.
echo  call(function('get'), [{'a': 1, 'b': 2}, 'c', 3])   |" Prints 3

Recall that Vim script is embedded within the ex-commands, that is why we cannot just call a function directly, we have to use the :call ex-command.

Function namespaces

See :h write-library-script, :h autoload

" Must be defined in autoload/foo/bar.vim
" Namspaced function names do not have to start with a capital letter
function! foo#bar#log(value)
    echomsg value
endfunction

call foo#bar#log('Hello')

Frequently used ex-commands

Sourcing runtime files

See :h 'runtimepath'

" Source first match among runtime paths
runtime plugin/my-plugin.vim

Defining new ex-commands

See :h 40.2, :h :command

" First argument here is the name of the command, rest is the command body
command! SwapAdjacentLines normal! ddp

The exclamation mark works the same as with :function. User-defined commands must start with a capital letter. The :command command can take a number of attributes (some of which have their own parameters with =), such as -nargs, all of them start with a dash to set them apart from the command name.

:command -nargs=1 Error echoerr <args>

Defining auto-commands

See :h 40.3, :h autocmd, :h autocommand-events

" The arguments are "events", "patterns", rest is "commands"
autocmd BufWritePost $MYVIMRC source $MYVIMRC

Events and patterns are separated by commas with no space between. See :h autocmd-events for standard events, :h User for custom events. Everything else are the ex-commands which will be executed.

Auto groups

When a file is sourced multiple times the auto-commands are defined anew, without deleting the old ones, causing auto-commands to pile up over time. Use auto-groups and the following ritual to guard against this.

augroup auto-source   |" The name of the group is arbitrary
    autocmd!          |" Deletes all auto-commands in the current group
    autocmd BufWritePost $MYVIMRC source $MYVIMRC
augroup END           |" Switch back to default auto-group

It is also possible to assign a group directly. This is useful if the definition of the group is in one script and the definition of the auto-command is in another script.

" In one file
augroup auto-source
    autocmd!
augroup END

" In another file
autocmd auto-source BufWritePost $MYVIMRC source $MYVIMRC

Executing (run-time macros of sorts)

Sometimes we need to construct an ex-command where part of the command is not known until runtime.

let line = 3                |" Line number determined at runtime
execute line .. 'delete'    |" Delete a line

Executing normal-mode commands

Use :normal to play back a sequence of normal mode commands from the command-line. Add an exclamation mark to ignore user mappings.

normal! ggddGp             |" Transplant first line to end of buffer

" Window commands can be used with :normal, or with :wincmd if :normal would
" not work
wincmd L                   |" Move current window all the way to the right

Frequently used functions

" Feature check
echo  has('nvim')                  |" Running Neovim
echo  has('python3')               |" Support for Python 3 plugins
echo  has('unix')                  |" Running on a Unix system
echo  has('win32')                 |" Running on a Windows system


" Test if something exists
echo  exists('&mouse')             |" Option (exists only)
echo  exists('+mouse')             |" Option (exists and works)
echo  exists('$HOSTNAME')          |" Environment variable
echo  exists('*strftime')          |" Built-in function
echo  exists('**s:MyFunc')         |" User-defined function
echo  exists('bufcount')           |" Variable (scope optional)
echo  exists('my_dict["foo"]')     |" Variable (dictionary entry)
echo  exists('my_dict["foo"]')     |" Variable (dictionary entry)
echo  exists(':Make')              |" Command
echo  exists("#CursorHold")        |" Auto-command defined for event
echo  exists("#BufReadPre#*.gz")   |" Event and pattern
echo  exists("#filetypeindent")    |" Auto-command group
echo  exists("##ColorScheme")      |" Auto-commnand supported for event

" Various dynamic values (see :h expand())
echo  expand('%')                  |" Current file name
echo  expand('<cword>')            |" Current word under cursor
echo  expand('%:p')                |" Modifier are possible

" Type tests
echo  type(my_var) == type(0)                  |" Number
echo  type(my_var) == type('')                 |" String
echo  type(my_var) == type([])                 |" List
echo  type(my_var) == type({})                 |" Dictionary
echo  type(my_var) == type(function('type'))   |" Funcref

" Format strings
echo  printf('%d in hexadecimal is %X', 123, 123)

Tricks of the trade

Source guard

" Prevent a file from being source multiple times; users can set the variable
" in their configuration to prevent the plugin from loading at all.
if exists('g:loaded_my_plugin')
    finish
endif
let g:loaded_my_plugin = v:true

Default values

" Get a default value: if the user defines a variable use it, otherwise use a
" hard-coded default. Uses the fact that a scope is also a dictionary.
let s:greeting = get(g:, 'my_plugin_greeting', 'Hello')