" The `vim9script` command is required to distinguish Vim9 script from " legacy Vim script in a `.vim` file. Before the command, legacy Vim script " comments (starting with the " character) can be used, like these lines do. " " After the `vim9script` command, the # character is used for comments vim9script # PRIMITIVE AND COLLECTION TYPES # Vim’s builtin function `typename()` may be used to reveal the type # Primitive data types echo typename(1) # number echo typename(1.1) # float echo typename(true) # bool # Collection data types echo typename("Hi") # string echo typename(0zFE0F) # blob (a binary object) echo typename([1]) # list echo typename((1, )) # tuple echo typename({1: 'one'}) # dict # Type casting may be used to fail early when there is a type mismatch echo 3 # 3 echo '3' # E1012: Type mismatch; expected number but got str # ---------------------------------------------------------------------------- vim9script # OPERATORS AND EXPRESSIONS # Arithmetic with the number (integer) type echo 1 + 1 # 2 echo 2 - 1 # 1 echo 3 * 2 # 6 echo 8 / 2 # 4 # If the result is not an integer, the remainder is not returned echo 9 / 2 # 4 # But modulo returns the remainder echo 9 % 2 # 1 # The float type returns with the applicable number of decimal places echo 1.25 * 6.1 # 7.625 echo 3.1405 + 0.0005 # 3.141 # An integer and float expression returns a float echo 9 / 2.0 # 4.5 # ---------------------------------------------------------------------------- vim9script # LOGICAL AND COMPARISON OPERATORS # Logical OR (||), AND (&&), and NOT (!) echo true || false # true echo true && false # false echo !true # false # Equality (==) and inequality (!=) work on all three primitive types and # comparisons (>, >=, <=, and <) on numbers and floats echo [1, 2] == [1, 2] # true echo 'apples' != 'pears' # true echo 9 > 8 # true echo 8 >= 8 # true echo 8 <= 9 # true echo 8 < 9 # true # Ternary operator echo 9 > 8 ? true : false # true # Falsy operator ("null coalescing operator") echo 9 > 8 ?? 1 + 1 # true echo 8 > 9 ?? 1 + 1 # 2 # Bitwise operators (>> and <<) echo 1 << 2 # 4 echo 9 >> 1 # 4 # ---------------------------------------------------------------------------- vim9script # NUMBERS # Numbers may be expressed as decimal, hexadecimal (prefixed with 0x), # octal (prefixed with 0o or 0O), or binary (prefixed with 0b) # These are all decimal 15 echo 15 # 15 echo 0b1111 # 15 echo 0xF # 15 echo 0o17 # 15 # The maximum number is either a 32 or 64 bit signed number, which may # be checked using Vim’s builtin variable `v:numbersize` echo v:numbersize # either 32 or 64 # The implication is that any arithmetic where the number exceeds the # permitted value (for 64-bit 9,223,372,036,854,775,807) will fail echo 9223372036854775807 + 1 # -9223372036854775808 # Not-a-real-number values may be tested with `isnan()` and `isinf()` builtin # functions - the second and third lines also illustrate method calling echo isnan(0.0 / 0.0) # nan echo 1.0 / 0.0 ->isinf() # inf echo -1.0 / 0.0 ->isinf() # -inf # ---------------------------------------------------------------------------- vim9script # STRINGS # String concatenation with `..` echo "Hello" .. " " .. 'world' # Hello world # String interpolation with `$` echo $"Adam is {1 + 41}" # Adam is 42 # String indexing is by character (like Java/Python 3) not byte (like C/Rust) echo 'fenêtre'[-4 : -1] # être # Vim has dozens of builtin string functions, which can be called # procedurally, as methods, and may be chained echo toupper("summer") # SUMMER echo 'metre'->strcharlen() # 5 echo "Ho"->reverse()->tolower() # oh # Pattern matching on strings echo 'foobar' =~ 'foo' # true (matches pattern) echo 'foobar' !~ 'baz' # true (does not match pattern) # ---------------------------------------------------------------------------- vim9script # REGULAR EXPRESSIONS # Vim uses a distinct flavor of regular expressions, though the basics # are similar to many other languages. # Using Vim's builtin function matchstr() to show regex matches: echo matchstr("Hello", '.....') # '.' is any character echo matchstr("Hello", 'Hel*o') # ('*' is zero+ times) echo matchstr("Hello", 'H.\+') # ('\+' is one+ times) echo matchstr("Hello", '\cheLlo') # ('\c' is case-insensitive) echo matchstr("Hello", '^Hello$') # ('^' is start, '$' is end) echo matchstr("Hello", 'H\(e\|x\)llo') # ('\|' is or) # Shorthand character classes and POSIX bracket expressions may be used # \u is uppercase, \a is alpha, \s is space or tab, \d is decimal digit echo "Dec 2025" =~ '\u\a\a\s\d\{4\}' # true # [[:digit:]] is a decimal digit as are [0-9] and \d echo "2025-12-10" =~ '[[:digit:]]\{4\}-[0-9]\{2\}-\d\{2\}' # true # Some things like "very magic" and "very nomagic" are unique to Vim: # \v very magic means most chars are special, closer to extended regexes # \V very nomagic, all but \ and the terminating character are literal echo matchstr("Hello", '\v^(.*)$') # Hello echo matchstr("Hello", '\V\^\(\.\*\)\$') # Hello # \zs sets the start of a match and \ze sets the end of the match var email = 'Addy user@example.com is fictitious' echo matchstr(email, '@\zs\a\+[.]com\ze[^\w]') # example.com # PCRE-style assertions may also be used: # \@<= is positive lookbehind, \@= is positive lookahead echo matchstr(email, '@\@<=\a\+[.]com\@=[^\w]') # example.com # \@` may be used to allow mixed types var a_list: list> = [[1, 2], [3, 4]] var a_dict: dict = {a: 1, b: 'two'} echo $"a_list is type {typename(a_list)} and a_dict is type {typename(a_dict)}" # Constants # `const` may be used to make both the variable and values constant const PI: dict = {2: 3.14, 4: 3.1415} # Cannot add items to this dict echo PI[4] # 3.1415 # `final` may be used to make the variable a constant and the values mutable. final pi: dict = {2: 3.14, 4: 3.1415} # Adding a key-value pair to `pi` pi[3] = 3.142 echo pi # {2: 3.14, 3: 3.142, 4: 3.1415} # Dictionary key-value pairs may also be accessed using `{dict}.key`. echo pi.4 # 3.1415 # ---------------------------------------------------------------------------- vim9script # MANIPULATING CONTAINER VARIABLES # There are many builtin functions for variable manipulation. These are a few # relevant to lists. var MyList = ['a', 'b', 'c'] echo MyList[0] # a MyList->add('d') # Adds 'd' to the end of the list MyList->remove(2) # Removes item at index 2 (starting at 0) echo join(MyList, ', ') # a, b, d # String interpolation with $. (More idiomatic than `printf()`) echo $"The first and last items in MyList are '{MyList[0]}' and '{MyList[-1]}'" # ---------------------------------------------------------------------------- vim9script # VARIABLES’ SCOPES # Variables exist within scopes. When unprefixed their scope is script-local. # Other scopes are global (prefixed with `g:`), window-local (`w:`), # buffer-local (`b:`), and tab-local (`t:`). Vim also has many Vim-defined # (`v:`) global variables. var my_var: string = 'Script-local variables have no prefix in Vim9 script' g:my_var = 'Global variables with "g:" prefixed are available everywhere' b:my_var = 'Buffer variables ("b:") are only visible within a buffer' echo $"{my_var}\n{g:my_var}\n{b:my_var}\n" echo v:version # Vim variable for a Vim instance's major version (901 == 9.1) # ---------------------------------------------------------------------------- vim9script # REGISTERS # Registers are a form of variable used to store string values of many # pre-defined and user-defined items. When used in a Vim9 script, they are # prefixed with `@`. echo 'Current register values:' echo "- Last command (:)\t" .. @: echo "- Last search (/) \t" .. @/ echo "- Current file (%)\t" .. @% @a = 'There are 26 named registers "a to "z and "A to "Z.' echo $"- Register a equals\t{@a}" # ---------------------------------------------------------------------------- vim9script # BUILTIN VARIABLES AND SETTINGS echo $MYVIMRC # (location of your .vimrc or, using Windows, _vimrc file) echo $VIMRUNTIME # (the Vim directory of the current Vim executable) # Vim has many settings variables. Some are global in scope, some are local, # and others are both echo &fileencoding # (buffer-local: the character encoding, e.g., utf-8) echo &equalalways # (global: do/don't make newly split windows the same size) # ---------------------------------------------------------------------------- vim9script # CONDITIONALS AND LOOPS const INT: number = 5 # If / Else if INT > 5 echo 'big' elseif INT == 5 echo 'medium' else echo 'small' endif # For loop (using builtin function range() for INT times, 0 indexed) for j in INT->range() echo j endfor # While loop (using builtin function add() to add values to the list l) var k: number var l: list while k < INT l->add(k) k += 1 endwhile echo l # Loop control using a tuple literal for x in (1, 2, 3) if x == 2 continue elseif x == 3 echo $'Stopping at {x}' break endif echo x endfor # ---------------------------------------------------------------------------- vim9script # EXCEPTIONS try DoesNotExist() # This fails catch echo 'Function DoesNotExist() does not exist!' endtry try var lines = readfile('nofile.txt') catch echo v:exception finally echo 'Done' endtry try if !filereadable('file.txt') throw 'MyError' else # Read var lines = readfile('file.txt') # Append writefile(['line3'], 'file.txt', 'a') echo lines endif catch /MyError/ echo 'File not found' endtry # ---------------------------------------------------------------------------- vim9script # FUNCTION BASICS # :def functions have 0+ arguments and a return type, which may be `void` def Add(x: number, y: number): void echo x + y enddef Add(3, 4) # Echos 7 # Arguments may be defaulted def Power(base: number, exp: number = 2): number return float2nr(pow(base, exp)) enddef echo Power(8) # Echos 64 because `exp` defaults to 2 # There may be a variable number of arguments def MinMax(...args: list): tuple return (args->min(), args->max()) enddef echo MinMax(8, 9, 2, 4) # Echoes (2, 9) # ---------------------------------------------------------------------------- vim9script # GENERIC FUNCTIONS # Return the maximum value of a list or, if it's empty, a default value def MaxOrDefault(lst: list, default: U): any return lst->len() > 0 ? lst->max() : default enddef echo MaxOrDefault([1, 2], 0) # Echos 2 echo MaxOrDefault([], 0) # Echos 0 echo MaxOrDefault([], 'empty') # Echos empty # ---------------------------------------------------------------------------- vim9script # LAMBDAS # Syntax is `(args) => expr` var Divide = (val: number, by: number): number => val / by echo Divide(420, 10) # Echos 42 # Sample list used in the following lambda examples var nums = [1, 2, 3, 4, 5, 6] # Using builtin function filter() to keep only the even numbers in nums list. # This example uses the `_` argument, which ignores an argument. It is useful # in callbacks where the argument is not needed in the expression, but an # argument must be provided to match the called function. (Using the builtin # function, `copy()`, avoids mutating the original list.) var evens: list = filter(copy(nums), (_, v) => v % 2 == 0) echo evens # Echos [2, 4, 6] # Using builtin function map() to square each number in the nums list. # This example also uses method chaining and the argument "_". var squares: list = nums->copy()->map((_, v) => v * v) echo squares # Echos [1, 4, 9, 16, 25, 36] # Using builtin function `reduce()` to sum all the numbers in the nums # list - it is called with two arguments: # 1) the result so far (acc), and # 2) the current item (v). var sum: number = nums->copy()->reduce((acc, v) => acc + v, 0) echo sum # Echos 21 # Descending sort using a lambda var sorted: list = sort(copy(nums), (a, b) => b - a) echo sorted # Echos [6, 5, 4, 3, 2, 1] # ---------------------------------------------------------------------------- vim9script # CLOSURES def Outer(): func var x: number = 2 # Initial value captured by closure def Inner(): number x = x * 2 # Modifies the captured x return x # Returns the new value enddef return Inner # Returns the closure (function + captured x) enddef var Double = Outer() # Creates a closure (with its own x = 2) # Calling the closure echo Double() # Echos 4 echo Double() # Echos 8 echo Double() # Echos 16 echo Double() # Echos 32 # ---------------------------------------------------------------------------- vim9script # A CLASS EXAMPLE # Class class Point var x: number var y: number def new(x: number, y: number) this.x = x this.y = y enddef def Move(dx: number, dy: number) this.x += dx this.y += dy enddef def ToString(): string return $"({this.x}, {this.y})" enddef endclass var p: Point = Point.new(1, 2) p.Move(3, 4) echo p.ToString() # => (4, 6) # ---------------------------------------------------------------------------- vim9script # AN ENUM EXAMPLE enum Quad Square('four', true), Rhombus('opposite', false) var es: string var ra: bool def QuadAbout(): string return $"A {tolower(this.name)} has {this.es} sides of equal length; it" .. (this.ra ? ' has only right angles' : ' has no right angles') enddef endenum var q: Quad = Quad.Square echo q.QuadAbout() q = Quad.Rhombus echo q.QuadAbout() # ---------------------------------------------------------------------------- vim9script # SOURCING ANOTHER SCRIPT # Source the optional helptoc plugin, distributed with Vim source $VIMRUNTIME/pack/dist/opt/helptoc/plugin/helptoc.vim # It’s better to use `packadd` for this, though packadd helptoc # Using `runtime` is another way (`*` or `**` wildcards can be used) runtime pack/**/helptoc.vim # ---------------------------------------------------------------------------- vim9script # EXPORT-IMPORT EXAMPLE: `MyModule.vim` # Without `export`, this constant is visible only within MyModule.vim const LOCAL: string = 'Not exported' # This exported constant will be available from scripts importing MyModule.vim export const GREETING: string = 'Hello from MyModule' # ---------------------------------------------------------------------------- vim9script # EXPORT-IMPORT EXAMPLE (THE SCRIPT DOING THE IMPORT) import "./MyModule.vim" # Using the constant from MyModule.vim echo MyModule.GREETING # Trying to use MyModule.LOCAL fails because it's not exported try echo MyModule.LOCAL catch echo v:exception endtry # ---------------------------------------------------------------------------- vim9script # PLUGIN DEVELOPMENT # These are some of the patterns used when creating well-behaved plugins # (which enable others to easily use your Vim9 scripts). # Source guard (plugin pattern), which prevents duplicate loading of plugins if exists('g:loaded_myplugin') finish endif # This would be put in the myplugin script (NB: intentionally commented here) # g:loaded_myplugin = true # Getting a global variable or, where it does not exist, a default value var greeting: string = get(g:, 'myplugin_greeting', 'Hello') # Toggling boolean settings is a common user interface pattern def ToggleFeature() g:myplugin_enabled = !get(g:, 'myplugin_enabled', false) echo g:myplugin_enabled ? 'Enabled' : 'Disabled' enddef command! ToggleMyPlugin ToggleFeature() # (After sourcing this script, try :ToggleMyPlugin a few times) # ---------------------------------------------------------------------------- vim9script # COMMAND AND MAPPING DEFINITIONS # Commands and mappings provide the primary user interface for plugins - # see https://vimhelp.org/usr_05.txt.html#plugin - and custom functionality. # Basic command definition command! Hello echo 'Hello Vim9' execute ':Hello' # Script-local function mapping via - recommended plugin mapping pattern def DoSomething() echo 'Action triggered' enddef # Map function to unique identifier, then to user keys nnoremap (MyPluginAction) DoSomething() nmap a (MyPluginAction) # Command with arguments and completion command! -nargs=1 -complete=file MyCmd edit # ---------------------------------------------------------------------------- vim9script # EVENT HANDLING # Autocommand groups provide systematic event management and prevent # duplicates on re-sourcing augroup AutoReload autocmd! autocmd BufWritePost $MYVIMRC source $MYVIMRC autocmd BufReadPost *.txt echo 'Hello text file' augroup END # ---------------------------------------------------------------------------- vim9script # EXECUTING NORMAL MODE COMMANDS # Yank eight lines into register a normal! "a8Y # Echo register a echo @a # (Sourcing this script as a visual selection echos the script itself) # ---------------------------------------------------------------------------- vim9script # FEATURE DETECTION AND UTILITY FUNCTIONS # Feature and existence checking echo has('clipboard') # Check for a feature - 1 means it is present echo exists('myVariable') # Check whether a script local variable exists # Path and expression expansion echo expand('%:p') # Full filepath of the current file # Type checking with Vim `v:t_*` constants or more granularly with type names echo type(123) == v:t_number # true echo typename([1, 2, 3]) == 'list' # true # ---------------------------------------------------------------------------- vim9script # DIRECT SHELL COMMAND EXECUTION # This example should work in Linux, PowerShell, and Windows cmd.exe: # output user info: (a) to non-interactive shell. :!whoami # Examples (b) and (c) would modify this buffer, so they're shown in # a function, preventing execution. def MoreExamples(): void # (b) to current line in the current buffer, replacing its contents :.!whoami # (c) to the same buffer, appending at cursor position :.read! whoami enddef # ---------------------------------------------------------------------------- vim9script # CAPTURING SHELL COMMAND OUTPUT WITH `system()` # This script creates a 4-second popup of the date, removing any line feed # characters (U+000A), which would otherwise appear as "^@" in the popup. silent var result = system('date') popup_notification(result->substitute('[\xA]', '', 'g'), {time: 4000}) # ---------------------------------------------------------------------------- vim9script # PROCESSING COMMAND OUTPUT AS LINES WITH `systemlist()` silent var lines = systemlist('ls *.vim') for line in lines echo line endfor # ---------------------------------------------------------------------------- vim9script # ASYNCHRONOUS COMMAND EXECUTION WITH JOBS # Jobs - see https://vimhelp.org/channel.txt.html#job%5Fstart%28%29 - execute # commands asynchronously (unlike `system()` or `systemlist()`) var mydate: list def Date(channel: channel, msg: string): void mydate->add(msg) enddef var myjob = job_start([&shell, &shellcmdflag, 'date'], {out_cb: Date}) echo [myjob, myjob->job_status()] sleep 1 echo $"The date and time is {mydate->join('')}" echo [myjob, myjob->job_status()] # Check exit status of the most recent shell command echo v:shell_error # Clear the variable; release the job's resources myjob = null_job # ---------------------------------------------------------------------------- vim9script # A MORE ADVANCED EXAMPLE: GIT INTEGRATION # The following script populates Vim’s quickfix list with files modified in # the current Git repository when `:GitDiffQF` is executed after sourcing # the script def GitDiffQuickfix() var diff_lines: list = systemlist('git diff --name-only') if v:shell_error != 0 echo 'Git not available or not a repo' return endif var qf_list: list for file in diff_lines add(qf_list, {'filename': file, 'lnum': 1, 'text': 'Modified file'}) endfor setqflist(qf_list, 'r') copen enddef command! GitDiffQF GitDiffQuickfix() # ---------------------------------------------------------------------------- vim9script # ASSERTIONS # The predefined Vim variable, `v:errors`, is populated when errors occur # in builtin `assert_*` functions, like the three in this example v:errors = [] assert_equal(4, 2 + 1) # Expected 4 but got 3 assert_false(1 < 2) # Expected False but got true assert_notmatch('\d\+', '123') # Pattern '\\d\\+' does match '123' if !empty(v:errors) echo "Test failures:\n" .. $'{v:errors->join("\n")}' endif # ---------------------------------------------------------------------------- vim9script # BREAKPOINTS # Sourcing the following script runs the function in debug mode: # - https://vimhelp.org/repeat.txt.html#%3Adebug # While debugging, use commands like these (at the `>` command prompt): # - "step" to execute the command and come back to debug mode for the # next command # - "echo" to inspect variables # - "cont" to continue execution until the next breakpoint is hit # - "finish" to finish the current user function (or script) breakdel * # clears all breakpoints def MyFunc() var x = 10 var y = x * 2 echo y enddef breakadd func 3 MyFunc # sets a breakpoint at line 3 of MyFunc debug MyFunc() # call MyFunc() with debugging on # ---------------------------------------------------------------------------- vim9script # LISTING INSTRUCTIONS / BYTECODE OF A FUNCTION # This can help understand how Vim optimizes a Vim9 script function, # showing the instructions / bytecode generated by the function def MyMultiply(a: number, b: number): number return a * b enddef # To compile and check for syntax/type errors: # - Using `func MyMultiply` reveals the function definition func MyMultiply # - Using `disassemble` shows the instructions generated for MyMultiply disassemble MyMultiply # Example output: # 757_MyMultiply # return a * b # 0 LOAD arg[-2] # 1 LOAD arg[-1] # 2 OPNR * # 3 RETURN # ----------------------------------------------------------------------------