--- language: Vimscript filename: learnvimscript.md contributors: - ["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). ```vim " 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` ```vim 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. ```vim 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. ```vim 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. ```vim 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' 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). ```vim " 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. ```vim 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. ```vim 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) ```vim " 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. ```vim 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. ```vim " 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 ```vim 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 ```vim " 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 ```vim " 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. ```vim " 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 ```vim " 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` ```vim " 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'` ```vim " Source first match among runtime paths runtime plugin/my-plugin.vim ``` ### Defining new ex-commands See `:h 40.2`, `:h :command` ```vim " 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. ```vim :command -nargs=1 Error echoerr ``` ### Defining auto-commands See `:h 40.3`, `:h autocmd`, `:h autocommand-events` ```vim " 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. ```vim 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. ```vim " 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. ```vim 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. ```vim 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 ```vim " 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('') |" 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 ```vim " 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 ```vim " 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') ```