From eee44d9e2c711e3810c35a8921656d5f62d09366 Mon Sep 17 00:00:00 2001 From: Konfekt Date: Mon, 16 Dec 2024 18:11:49 +0100 Subject: [PATCH 1/7] add vim9script as translation of @hiphish 's legacy vimscript file --- vim9script.md | 452 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 vim9script.md diff --git a/vim9script.md b/vim9script.md new file mode 100644 index 00000000..bc697abd --- /dev/null +++ b/vim9script.md @@ -0,0 +1,452 @@ +--- +name: Vim9script +filename: learn9vimscript.vim +contributors: + - ["HiPhish", "http://hiphish.github.io/"] +--- + +```vim +# ############## +# Introduction +# ############## + +# Vim9 script is a modernized version of Vim's scripting language, offering features like variables, functions, and loops. +# Accoding to `:help vim9-differences`, the principal differences from legacy Vim script are as follows: +# +# - Comments start with #, not ": > +# echo "hello" # comment +# - Using a backslash for line continuation is hardly ever needed: > +# echo "hello " +# .. yourName +# .. ", how are you?" +# - White space is required in many places to improve readability. +# - Assign values without `:let` *E1126* , declare variables with `:var`: > +# var count = 0 +# count += 3 +# - Constants can be declared with `:final` and `:const`: > +# final matches = [] # add to the list later +# const names = ['Betty', 'Peter'] # cannot be changed +# - `:final` cannot be used as an abbreviation of `:finally`. +# - Variables and functions are script-local by default. +# - Functions are declared with argument types and return type: > +# def CallMe(count: number, message: string): bool +# - Call functions without `:call`: > +# writefile(['done'], 'file.txt') +# - You cannot use old Ex commands: +# `:Print` +# `:append` +# `:change` +# `:d` directly followed by 'd' or 'p'. +# `:insert` +# `:k` +# `:mode` +# `:open` +# `:s` with only flags +# `:t` +# `:xit` +# - Some commands, especially those used for flow control, cannot be shortened. +# E.g., `:throw` cannot be written as `:th`. *vim9-no-shorten* +# - You cannot use curly-braces names. +# - A range before a command must be prefixed with a colon: > +# :%s/this/that +# - Executing a register with "@r" does not work, you can prepend a colon or use +# `:exe`: > +# :exe @a +# - Unless mentioned specifically, the highest |scriptversion| is used. +# - When defining an expression mapping, the expression will be evaluated in the +# context of the script where it was defined. +# - When indexing a string the index is counted in characters, not bytes: +# |vim9-string-index| +# - Some possibly unexpected differences: |vim9-gotchas|. +# +# You can run Vim9 script commands in command-line mode or write them to a file and source it in Vim. +# This guide assumes familiarity with ex-commands and focuses on scripting. + +# Comments start with # +# The vertical line '|' separates commands +echo 'Hello' | echo 'world!' + +# Putting a comment after a command usually works +pwd # Displays the current working directory + +# Line continuation is rarely needed +echo "Hello " + .. "world" + +echo [1, 2] + +echo { + 'a': 1, + 'b': 2 +} + +# ####### +# Types +# ####### + +# Numbers +echo 123 # Decimal +echo 0b1111011 # Binary +echo 0173 # Octal +echo 0x7B # Hexadecimal +echo 123.0 # Floating-point +echo 1.23e2 # Floating-point (scientific notation) + +# Booleans +echo true # Evaluates to true +echo false # Evaluates to false + +# Boolean values from comparison +echo x == y # Equality by value +echo x != y # Inequality +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 +echo x isnot y # Instance non-identity + +# Strings +echo 'a' < 'B' # True or false depending on 'ignorecase' +echo 'a' x * x} # Anonymous function + +# Regular expression +substitute/hello/Hello/ + +# ########################### +# Implicit type conversions +# ########################### + +echo "1" + 1 # Number +echo "1" .. 1 # String +echo "0xA" + 1 # Number + +# ########### +# Variables +# ########### + +var b_my_var = 1 # Local to current buffer +var w_my_var = 1 # Local to current window +var t_my_var = 1 # Local to current tab page +var g_my_var = 1 # Global variable +var l_my_var = 1 # Local to current function +var s_my_var = 1 # Local to current script file +var a_my_arg = 1 # Function argument + +# The Vim scope is read-only +echo true # Special built-in Vim variables + +# Access special Vim memory like variables +var @a = 'Hello' # Register +var $PATH='' # Environment variable +var &textwidth = 79 # Option +var &l_textwidth = 79 # Local option +var &g_textwidth = 79 # Global option + +# Access scopes as dictionaries +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 # Constant + +# Function reference variables +var IsString = {x -> type(x) == type('')} # Global +var s_isNumber = {x -> type(x) == type(0)} # Local + +# Multiple value binding +var [x, y] = [1, 2] + +# Assign the remainder to a rest variable +var [mother, father; children] = ['Alice', 'Bob', 'Carol', 'Dennis', 'Emily'] + +# ############## +# Flow control +# ############## + +# Conditional +var condition = true + +if condition + echo 'First condition' +elseif another_condition + echo 'Second condition' +else + echo 'Fail' +endif + +# Loops + +# For-loop +for person in ['Alice', 'Bob', 'Carol', 'Dennis', 'Emily'] + echo 'Hello ' .. person +endfor + +# Iterate over a nested list +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) + 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 +var there_yet = true +while !there_yet + echo 'Are we there yet?' +endwhile + +# Exception handling +try + source path/to/file +catch /Cannot open/ + echo 'Looks like that file does not exist' +catch /.*/ + echo 'Something went wrong, but I do not know what' +finally + echo 'I am done trying' +endtry + +# ########## +# Functions +# ########## + +# Defining functions +def AddNumbersLoudly(x: number, y: number): number + echo 'Adding' .. x .. 'and' .. y + return x + y +enddef + +def s:addNumbersLoudly(x: number, y: number): number + echo 'Adding' .. x .. 'and' .. y + return x + y +enddef + +# Range functions +def FirstAndLastLine() range + echo [a:firstline, a:lastline] +enddef + +# Aborting functions +def SourceMyFile() abort + source my-file.vim + echo 'This will never be printed' +enddef + +# Closures +def MakeAdder(x: number) + def Adder(n: number) closure + return n + x + enddef + return funcref('Adder') +enddef +var AddFive = MakeAdder(5) +echo AddFive(3) # Prints 8 + +# Dictionary functions +def Mylen() dict + return len(self.data) +enddef +var mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")} +echo mydict.len() + +# Alternatively, more concise +var mydict = {'data': [0, 1, 2, 3]} +def mydict.len() + return len(self.data) +enddef + +# Calling functions +var animals = keys({'cow': 'moo', 'dog': 'woof', 'cat': 'meow'}) + +# Call a function for its side effects only +call sign_undefine() + +# The call() function +echo call(function('get'), [{'a': 1, 'b': 2}, 'c', 3]) # Prints 3 + +# Function namespaces +def foo#bar#log(value: string) + echomsg value +enddef + +call foo#bar#log('Hello') + +# ############################# +# Frequently used ex-commands +# ############################# + +# Sourcing runtime files +runtime plugin/my-plugin.vim + +# Defining new ex-commands +command! SwapAdjacentLines normal! ddp + +command! -nargs=1 Error echoerr + +# Defining auto-commands +autocmd BufWritePost $MYVIMRC source $MYVIMRC + +# Auto groups +augroup auto-source + autocmd! + autocmd BufWritePost $MYVIMRC source $MYVIMRC +augroup END + +# Executing +var line = 3 +execute line .. 'delete' + +# Executing normal-mode commands +normal! ggddGp + +# Window commands +wincmd L + +# ########################### +# Frequently used functions +# ########################### + +# Feature check +echo has('nvim') +echo has('python3') +echo has('unix') +echo has('win32') + +# Test if something exists +echo exists('&mouse') +echo exists('+mouse') +echo exists('$HOSTNAME') +echo exists('*strftime') +echo exists('**s:MyFunc') +echo exists('bufcount') +echo exists('my_dict["foo"]') +echo exists(':Make') +echo exists("#CursorHold") +echo exists("#BufReadPre#*.gz") +echo exists("#filetypeindent") +echo exists("##ColorScheme") + +# Various dynamic values +echo expand('%') +echo expand('') +echo expand('%:p') + +# Type tests +echo type(my_var) == v:t_number +echo type(my_var) == v:t_string +echo type(my_var) == v:t_func +echo type(my_var) == v:t_list +echo type(my_var) == v:t_dict +echo type(my_var) == v:t_float +echo type(my_var) == v:t_bool +echo my_var is v:null + +# Format strings +echo printf('%d in hexadecimal is %X', 123, 123) + +# ##################### +# Tricks of the trade +# ##################### + +# Source guard +if exists('g:loaded_my_plugin') + finish +endif +var g_loaded_my_plugin = true + +# Default values +var s_greeting = get(g:, 'my_plugin_greeting', 'Hello') +``` From 7e95db1d99950b8598c80c15c07d09c2c1eb4c7f Mon Sep 17 00:00:00 2001 From: Konfekt Date: Sun, 13 Apr 2025 10:14:41 +0200 Subject: [PATCH 2/7] update vim9script using yeggapan's guide --- vim9script.md | 743 ++++++++++++++++++++++++-------------------------- 1 file changed, 358 insertions(+), 385 deletions(-) diff --git a/vim9script.md b/vim9script.md index bc697abd..5b21ea1d 100644 --- a/vim9script.md +++ b/vim9script.md @@ -1,452 +1,425 @@ --- -name: Vim9script -filename: learn9vimscript.vim +name: Vim9Script +filename: learnvim9script.vim contributors: - - ["HiPhish", "http://hiphish.github.io/"] + - ["Alejandro Sanchez", "http://hiphish.github.io/"] + - ["Yegappan Lakshmanan", "https://github.com/yegappan"] + - ["LacyGoill", "https://github.com/lacygoill"] --- +Vim9Script is a modernized scripting language introduced in Vim 9.0. +It improves performance, readability, and structure over legacy Vimscript. +Try [vim9-conversion-aid](https://github.com/ubaldot/vim9-conversion-aid) as a starting point to convert legacy Vimscript to Vim9Script. + ```vim -# ############## -# Introduction -# ############## +vim9script +# Above line is necessary to distinguish code in a *.vim file from legacy vimscript. -# Vim9 script is a modernized version of Vim's scripting language, offering features like variables, functions, and loops. -# Accoding to `:help vim9-differences`, the principal differences from legacy Vim script are as follows: -# -# - Comments start with #, not ": > -# echo "hello" # comment -# - Using a backslash for line continuation is hardly ever needed: > -# echo "hello " -# .. yourName -# .. ", how are you?" -# - White space is required in many places to improve readability. -# - Assign values without `:let` *E1126* , declare variables with `:var`: > -# var count = 0 -# count += 3 -# - Constants can be declared with `:final` and `:const`: > -# final matches = [] # add to the list later -# const names = ['Betty', 'Peter'] # cannot be changed -# - `:final` cannot be used as an abbreviation of `:finally`. -# - Variables and functions are script-local by default. -# - Functions are declared with argument types and return type: > -# def CallMe(count: number, message: string): bool -# - Call functions without `:call`: > -# writefile(['done'], 'file.txt') -# - You cannot use old Ex commands: -# `:Print` -# `:append` -# `:change` -# `:d` directly followed by 'd' or 'p'. -# `:insert` -# `:k` -# `:mode` -# `:open` -# `:s` with only flags -# `:t` -# `:xit` -# - Some commands, especially those used for flow control, cannot be shortened. -# E.g., `:throw` cannot be written as `:th`. *vim9-no-shorten* -# - You cannot use curly-braces names. -# - A range before a command must be prefixed with a colon: > -# :%s/this/that -# - Executing a register with "@r" does not work, you can prepend a colon or use -# `:exe`: > -# :exe @a -# - Unless mentioned specifically, the highest |scriptversion| is used. -# - When defining an expression mapping, the expression will be evaluated in the -# context of the script where it was defined. -# - When indexing a string the index is counted in characters, not bytes: -# |vim9-string-index| -# - Some possibly unexpected differences: |vim9-gotchas|. -# -# You can run Vim9 script commands in command-line mode or write them to a file and source it in Vim. -# This guide assumes familiarity with ex-commands and focuses on scripting. - -# Comments start with # -# The vertical line '|' separates commands -echo 'Hello' | echo 'world!' - -# Putting a comment after a command usually works -pwd # Displays the current working directory - -# Line continuation is rarely needed -echo "Hello " - .. "world" - -echo [1, 2] - -echo { - 'a': 1, - 'b': 2 -} - -# ####### -# Types -# ####### +#################################################### +## 1. Primitive Datatypes and Operators +#################################################### # Numbers -echo 123 # Decimal -echo 0b1111011 # Binary -echo 0173 # Octal -echo 0x7B # Hexadecimal -echo 123.0 # Floating-point -echo 1.23e2 # Floating-point (scientific notation) +var i: number = 42 +var f: float = 3.14 # Booleans -echo true # Evaluates to true -echo false # Evaluates to false - -# Boolean values from comparison -echo x == y # Equality by value -echo x != y # Inequality -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 -echo x isnot y # Instance non-identity +var done: bool = true # Strings -echo 'a' < 'B' # True or false depending on 'ignorecase' -echo 'a' 1 # true +echo 2 >= 2 # true -# Strings -echo "Hello world\n" # Newline -echo 'Hello world\n' # Literal -echo 'Let''s go!' # Two single quotes become one +# String equality +echo 'foo' == 'foo' # true +echo 'foo' != 'bar' # true -# String concatenation -echo 'Hello ' .. 'world' # String concatenation +# Pattern matching +echo 'foobar' =~ 'foo' # true (matches pattern) +echo 'foobar' !~ 'baz' # true (does not match pattern) -# String indexing -echo 'Hello'[0] # First character -echo 'Hello'[1] # Second character -echo 'Hellö'[4] # Returns a character +# Regex Basics: +# - `.` any char, `*` zero+ times, `\+` one+ times +# - `\d` digit, `\w` word char, `^` start, `$` end, `\|` OR +# Case Sensitivity & Magic Modes +# - `\c` / `\C`: case-insensitive / case-sensitive +# - `\v`: very magic, most chars are special, closer to extended regexes +# - `\V`: very nomagic, all but \ are literal +echo 'Foobar' =~ '\cfoo' # true +echo 'abc123' =~ '\v\d+' # true +echo 'a|b' =~ '\Va|b' # true -# Substrings -echo 'Hello'[:] # Copy of entire string -echo 'Hello'[1:3] # Substring -echo 'Hello'[1:-2] # Substring until second to last character -echo 'Hello'[1:] # Substring with starting index -echo 'Hello'[:2] # Substring with ending index -echo 'Hello'[-2:] # Substring relative to end +# Logical +echo true && false # false +echo true || false # true +echo !true # false + +# Ternary +echo true ? 'yes' : 'no' + +#################################################### +## 2. Variables and Collections +#################################################### + +# Variable declaration +var name: string = 'Vim' +var count = 10 # type inferred + +# Constants +const pi = 3.1415 # Lists -echo [] # Empty list -echo [1, 2, 'Hello'] # List with elements -echo [1, 2, 'Hello', ] # Trailing comma permitted -echo [[1, 2], 'Hello'] # Nested lists +var l: list = [1, 2, 3] +echo l[0] +l->add(4) +l->remove(1) -# List concatenation -echo [1, 2] + [3, 4] # Creates a new list +# Tuples +var t: tuple = (1, 'a', 3) +echo t[1] -# List indexing -echo [1, 2, 3, 4][2] # Third element -echo [1, 2, 3, 4][-1] # Last element - -# List slicing -echo [1, 2, 3, 4][:] # Shallow copy -echo [1, 2, 3, 4][:2] # Sublist until third item -echo [1, 2, 3, 4][2:] # Sublist from third item -echo [1, 2, 3, 4][:-2] # Sublist until second-to-last item +# Vim help on scopes: https://vimhelp.org/eval.txt.html#variable-scope +# Use `g:` for global variables, `b:` for buffer-local, `w:` for window-local, and so on. +# - @a accesses the contents of register "a" +# - $PATH references the environment variable PATH +# - &l:textwidth gets the buffer‐local value of the textwidth option +g:global_var = 'I am global' # Dictionaries -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 +var d: dict = {a: 1, b: 2} +echo d.a +d.c = 3 -# Indexing a dictionary -echo {'a': 1, 'b': 2}['a'] # Literal index -echo {'a': 1, 'b': 2}.a # Syntactic sugar +# Sets (via dict keys) +var set = {1: true, 2: true} -# Funcref -echo function('type') # Reference to function type() -echo {x -> x * x} # Anonymous function +#################################################### +## 3. Control Flow +#################################################### -# Regular expression -substitute/hello/Hello/ - -# ########################### -# Implicit type conversions -# ########################### - -echo "1" + 1 # Number -echo "1" .. 1 # String -echo "0xA" + 1 # Number - -# ########### -# Variables -# ########### - -var b_my_var = 1 # Local to current buffer -var w_my_var = 1 # Local to current window -var t_my_var = 1 # Local to current tab page -var g_my_var = 1 # Global variable -var l_my_var = 1 # Local to current function -var s_my_var = 1 # Local to current script file -var a_my_arg = 1 # Function argument - -# The Vim scope is read-only -echo true # Special built-in Vim variables - -# Access special Vim memory like variables -var @a = 'Hello' # Register -var $PATH='' # Environment variable -var &textwidth = 79 # Option -var &l_textwidth = 79 # Local option -var &g_textwidth = 79 # Global option - -# Access scopes as dictionaries -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 # Constant - -# Function reference variables -var IsString = {x -> type(x) == type('')} # Global -var s_isNumber = {x -> type(x) == type(0)} # Local - -# Multiple value binding -var [x, y] = [1, 2] - -# Assign the remainder to a rest variable -var [mother, father; children] = ['Alice', 'Bob', 'Carol', 'Dennis', 'Emily'] - -# ############## -# Flow control -# ############## - -# Conditional -var condition = true - -if condition - echo 'First condition' -elseif another_condition - echo 'Second condition' +# If / Else +if count > 5 + echo 'big' +elseif count == 5 + echo 'medium' else - echo 'Fail' + echo 'small' endif -# Loops - -# For-loop -for person in ['Alice', 'Bob', 'Carol', 'Dennis', 'Emily'] - echo 'Hello ' .. person +# For loop +for j in range(3) + echo j endfor -# Iterate over a nested list -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) - 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 -var there_yet = true -while !there_yet - echo 'Are we there yet?' +# While loop +var k = 0 +while k < 3 + echo k + k += 1 endwhile -# Exception handling +# Loop control +for x in [1, 2, 3] + if x == 2 + continue + endif + if x == 3 + break + endif + echo x +endfor + +#################################################### +## 4. Functions +#################################################### + +# Basic function +def Add(x: number, y: number): number + return x + y +enddef + +echo Add(3, 4) + +# Default arguments +def Power(base: number, exp: number = 2): number + return float2nr(pow(base, exp)) +enddef + +# Variable arguments +def Sum(...args: list): number + return reduce(args, (x, y) => x + y) +enddef + +# Define a function that returns a closure capturing a local variable +def MakeAdder(x: number): func + # Return a reference to the inner function + return (y: number) => x + y +enddef + +# Create a closure that adds 5 +var Add5 = MakeAdder(5) + +# Call the closure with 3; result is 8 +echo Add5(3) + +#################################################### +## 5. Classes +#################################################### + +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.new(1, 2) +p.Move(3, 4) +echo p.ToString() # => (4, 6) + +#################################################### +## 6. Modules and Imports +#################################################### + +# Source another script file +source mylib.vim + +# Runtime path +runtime plugin/myplugin.vim + +# Vim loads `.vimrc`, then plugin files in `plugin/` directories. +# Vim9Script can coexist with older scripts. +# Place Vim9 files in separate `.vim` files with `vim9script` at the top. +# Use `autoload/` or `ftplugin/` for specialized features. + +# Define script-local functions +def Helper() + echo 'internal' +enddef + +#################################################### +## 7. File I/O +#################################################### + +# Write +writefile(['line1', 'line2'], 'file.txt') + +# Append +writefile(['line3'], 'file.txt', 'a') + +# Read +var lines = readfile('file.txt') +echo lines + +#################################################### +## 8. Exceptions +#################################################### + try - source path/to/file -catch /Cannot open/ - echo 'Looks like that file does not exist' -catch /.*/ - echo 'Something went wrong, but I do not know what' + var lines = readfile('nofile.txt') +catch /E484:/ + echo 'File not found' finally - echo 'I am done trying' + echo 'Done' endtry -# ########## -# Functions -# ########## +# Throw +throw 'MyError' -# Defining functions -def AddNumbersLoudly(x: number, y: number): number - echo 'Adding' .. x .. 'and' .. y - return x + y +#################################################### +## 9. Advanced Features +#################################################### + +# Lambda +var Square = (x) => x * x +echo Square(4) + +# Partial +def Log(level: string, msg: string) + echo $"[{level}] {msg}" enddef -def s:addNumbersLoudly(x: number, y: number): number - echo 'Adding' .. x .. 'and' .. y - return x + y +var Warn = function('Log', ['WARN']) +Warn('Disk low') + +# Decorator-like +def LogWrap(F: func): func + def wrapper(...args: list): any + echo 'Calling' + var result = call(F, args) + echo 'Done' + return result + enddef + return funcref('wrapper') enddef -# Range functions -def FirstAndLastLine() range - echo [a:firstline, a:lastline] -enddef +#################################################### +## 10. Testing +#################################################### -# Aborting functions -def SourceMyFile() abort - source my-file.vim - echo 'This will never be printed' -enddef +v:errors = [] -# Closures -def MakeAdder(x: number) - def Adder(n: number) closure - return n + x - enddef - return funcref('Adder') -enddef -var AddFive = MakeAdder(5) -echo AddFive(3) # Prints 8 +assert_equal(4, 2 + 2) +assert_notequal(1, 2) +assert_true(1 < 2) +assert_false(2 < 1) +assert_match('\d\+', 'abc123') +assert_notmatch('\d\+', 'abc') -# Dictionary functions -def Mylen() dict - return len(self.data) -enddef -var mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")} -echo mydict.len() +if len(v:errors) == 0 + echo 'All tests passed' +else + echo 'Test failures:' + echo v:errors +endif -# Alternatively, more concise -var mydict = {'data': [0, 1, 2, 3]} -def mydict.len() - return len(self.data) -enddef +#################################################### +## 11. External Commands +#################################################### -# Calling functions -var animals = keys({'cow': 'moo', 'dog': 'woof', 'cat': 'meow'}) +# Run a shell command and capture output +var result = system('ls') +echo result -# Call a function for its side effects only -call sign_undefine() +# Run and split into lines +var lines = systemlist('ls') +for line in lines + echo line +endfor -# The call() function -echo call(function('get'), [{'a': 1, 'b': 2}, 'c', 3]) # Prints 3 +# Check exit status +echo v:shell_error -# Function namespaces -def foo#bar#log(value: string) - echomsg value -enddef +#################################################### +## 12. JSON and Regex +#################################################### -call foo#bar#log('Hello') +# JSON encode/decode +var data = {'name': 'Vim', 'version': 9} +var json = json_encode(data) +echo json -# ############################# -# Frequently used ex-commands -# ############################# +var parsed = json_decode(json) +echo parsed.name -# Sourcing runtime files -runtime plugin/my-plugin.vim +# Regex match +var s = 'abc123' +if s =~ '\d\+' + echo 'Contains digits' +endif -# Defining new ex-commands -command! SwapAdjacentLines normal! ddp +# Replace +var new = substitute('foo bar', 'bar', 'baz', '') +echo new -command! -nargs=1 Error echoerr +#################################################### +## 13. Vim Idioms +#################################################### -# Defining auto-commands -autocmd BufWritePost $MYVIMRC source $MYVIMRC +# Source guard (plugin pattern) +if exists('g:loaded_myplugin') + finish +endif +var g:loaded_myplugin = true -# Auto groups -augroup auto-source - autocmd! - autocmd BufWritePost $MYVIMRC source $MYVIMRC +# Default value +var greeting = get(g:, 'myplugin_greeting', 'Hello') + +# Command definition +command! Hello echo 'Hello Vim9' +# You can specify attributes like `-nargs`, `-range`, `-complete`; +# see https://vimhelp.org/usr_40.txt.html#40.2 +command! -nargs=1 -complete=file MyCmd edit + +# Group autocommands to manage them systematically +# to prevent duplicate autocommands on re-sourcing. +augroup AutoReload + autocmd! + autocmd BufWritePost $MYVIMRC source $MYVIMRC + autocmd BufReadPost *.txt echo 'Hello text file' augroup END -# Executing -var line = 3 -execute line .. 'delete' +# Define a script-local function and map it via +def DoSomething() + echo 'Action triggered' +enddef -# Executing normal-mode commands +nnoremap (MyPluginAction) :call DoSomething() +nmap a (MyPluginAction) + +# You can run normal commands from Vim9Script: +# This executes like pressing `ggddGp` in normal mode. normal! ggddGp -# Window commands -wincmd L - -# ########################### -# Frequently used functions -# ########################### - -# Feature check -echo has('nvim') -echo has('python3') +# `exist({name})` checks if a variable, function, or command is defined. +echo exist(':myVariable') == 2 +# `has({feature})` checks if Vim has a specific feature (e.g. `has('unix')`). echo has('unix') -echo has('win32') - -# Test if something exists -echo exists('&mouse') -echo exists('+mouse') -echo exists('$HOSTNAME') -echo exists('*strftime') -echo exists('**s:MyFunc') -echo exists('bufcount') -echo exists('my_dict["foo"]') -echo exists(':Make') -echo exists("#CursorHold") -echo exists("#BufReadPre#*.gz") -echo exists("#filetypeindent") -echo exists("##ColorScheme") - -# Various dynamic values -echo expand('%') -echo expand('') -echo expand('%:p') - -# Type tests -echo type(my_var) == v:t_number -echo type(my_var) == v:t_string -echo type(my_var) == v:t_func -echo type(my_var) == v:t_list -echo type(my_var) == v:t_dict -echo type(my_var) == v:t_float -echo type(my_var) == v:t_bool -echo my_var is v:null - -# Format strings -echo printf('%d in hexadecimal is %X', 123, 123) - -# ##################### -# Tricks of the trade -# ##################### - -# Source guard -if exists('g:loaded_my_plugin') - finish +if has('nvim') + echo 'Running in Neovim' endif -var g_loaded_my_plugin = true +# `expand({expr})` expands filenames, ``, etc. +echo expand('%:p') +# `printf({fmt}, ...)` formats strings. +echo printf('Hello, %s!', 'world') +# `type()`, along with `v:t_*` constants, indicates object types. +echo type(123) == v:t_number -# Default values -var s_greeting = get(g:, 'my_plugin_greeting', 'Hello') +if v:version >= 900 + echo 'Vim 9+ detected' +endif + +# Toggle a boolean setting +def ToggleFeature() + g:myplugin_enabled = !get(g:, 'myplugin_enabled', false) + echo g:myplugin_enabled ? 'Enabled' : 'Disabled' +enddef +command! ToggleMyPlugin call ToggleFeature() + +# Create a quickfix list from Git diff +def GitDiffQuickfix() + var diff_lines = systemlist('git diff --name-only') + if v:shell_error != 0 + echo 'Git not available or not a repo' + return + endif + + var qf_list = [] + for file in diff_lines + add(qf_list, {'filename': file, 'lnum': 1, 'text': 'Modified file'}) + endfor + + call setqflist(qf_list, 'r') + copen +enddef +command! GitDiffQF call GitDiffQuickfix() ``` + +### Additional Resources + +- [Vim9 Script Reference](https://vimhelp.org/vim9.txt.html) +- [Yegappan's Vim9 for Python Developers](https://github.com/yegappan/Vim9ScriptForPythonDevelopers) +- [Lacygoill's Vim9 Notes](https://github.com/jessepav/lacygoill-wiki-backup/blob/master/vim/vim9.md) From b66078ea41b6cd3f8abea8cca4f2e8f1ccadf822 Mon Sep 17 00:00:00 2001 From: Konfekt Date: Mon, 14 Apr 2025 03:54:34 +0200 Subject: [PATCH 3/7] apply @ubaldot 's kind improvement suggestions --- vim9script.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/vim9script.md b/vim9script.md index 5b21ea1d..791824d5 100644 --- a/vim9script.md +++ b/vim9script.md @@ -7,7 +7,7 @@ contributors: - ["LacyGoill", "https://github.com/lacygoill"] --- -Vim9Script is a modernized scripting language introduced in Vim 9.0. +Vim9Script is a modern scripting language introduced in Vim 9.0. It improves performance, readability, and structure over legacy Vimscript. Try [vim9-conversion-aid](https://github.com/ubaldot/vim9-conversion-aid) as a starting point to convert legacy Vimscript to Vim9Script. @@ -305,11 +305,25 @@ var result = system('ls') echo result # Run and split into lines -var lines = systemlist('ls') +silent var lines = systemlist('ls') for line in lines echo line endfor +# Using :!ls +:!ls + +# Using job_start() callback +var shell_job: job +def GotOutput(channel: channel, msg: string) + echo msg +enddef +# Start a shell in the background. +shell_job = job_start(["/bin/sh", "-c", "ls"], { + out_cb: GotOutput, + err_cb: GotOutput + }) + # Check exit status echo v:shell_error @@ -367,7 +381,7 @@ def DoSomething() echo 'Action triggered' enddef -nnoremap (MyPluginAction) :call DoSomething() +nnoremap (MyPluginAction) DoSomething() nmap a (MyPluginAction) # You can run normal commands from Vim9Script: @@ -397,7 +411,7 @@ def ToggleFeature() g:myplugin_enabled = !get(g:, 'myplugin_enabled', false) echo g:myplugin_enabled ? 'Enabled' : 'Disabled' enddef -command! ToggleMyPlugin call ToggleFeature() +command! ToggleMyPlugin ToggleFeature() # Create a quickfix list from Git diff def GitDiffQuickfix() From 325602753b1b17b8d46d69baa802a5f144bb566b Mon Sep 17 00:00:00 2001 From: Konfekt Date: Mon, 14 Apr 2025 04:03:50 +0200 Subject: [PATCH 4/7] rearrange content to reduce number of sections --- vim9script.md | 100 +++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/vim9script.md b/vim9script.md index 791824d5..40314b15 100644 --- a/vim9script.md +++ b/vim9script.md @@ -49,17 +49,6 @@ echo 'foo' != 'bar' # true echo 'foobar' =~ 'foo' # true (matches pattern) echo 'foobar' !~ 'baz' # true (does not match pattern) -# Regex Basics: -# - `.` any char, `*` zero+ times, `\+` one+ times -# - `\d` digit, `\w` word char, `^` start, `$` end, `\|` OR -# Case Sensitivity & Magic Modes -# - `\c` / `\C`: case-insensitive / case-sensitive -# - `\v`: very magic, most chars are special, closer to extended regexes -# - `\V`: very nomagic, all but \ are literal -echo 'Foobar' =~ '\cfoo' # true -echo 'abc123' =~ '\v\d+' # true -echo 'a|b' =~ '\Va|b' # true - # Logical echo true && false # false echo true || false # true @@ -89,12 +78,14 @@ l->remove(1) var t: tuple = (1, 'a', 3) echo t[1] -# Vim help on scopes: https://vimhelp.org/eval.txt.html#variable-scope # Use `g:` for global variables, `b:` for buffer-local, `w:` for window-local, and so on. +# Vim help on scopes: https://vimhelp.org/eval.txt.html#variable-scope +g:global_var = 'I am global' + # - @a accesses the contents of register "a" # - $PATH references the environment variable PATH # - &l:textwidth gets the buffer‐local value of the textwidth option -g:global_var = 'I am global' +echo expand($PATH) # Dictionaries var d: dict = {a: 1, b: 2} @@ -104,6 +95,14 @@ d.c = 3 # Sets (via dict keys) var set = {1: true, 2: true} +# JSON encode/decode +var data = {'name': 'Vim', 'version': 9} +var json = json_encode(data) +echo json + +var parsed = json_decode(json) +echo parsed.name + #################################################### ## 3. Control Flow #################################################### @@ -161,6 +160,10 @@ def Sum(...args: list): number return reduce(args, (x, y) => x + y) enddef +# Lambda +var Square = (x) => x * x +echo Square(4) + # Define a function that returns a closure capturing a local variable def MakeAdder(x: number): func # Return a reference to the inner function @@ -173,6 +176,25 @@ var Add5 = MakeAdder(5) # Call the closure with 3; result is 8 echo Add5(3) +# Partial +def Log(level: string, msg: string) + echo $"[{level}] {msg}" +enddef + +var Warn = function('Log', ['WARN']) +Warn('Disk low') + +# Decorator-like +def LogWrap(F: func): func + def wrapper(...args: list): any + echo 'Calling' + var result = call(F, args) + echo 'Done' + return result + enddef + return funcref('wrapper') +enddef + #################################################### ## 5. Classes #################################################### @@ -249,33 +271,6 @@ endtry # Throw throw 'MyError' -#################################################### -## 9. Advanced Features -#################################################### - -# Lambda -var Square = (x) => x * x -echo Square(4) - -# Partial -def Log(level: string, msg: string) - echo $"[{level}] {msg}" -enddef - -var Warn = function('Log', ['WARN']) -Warn('Disk low') - -# Decorator-like -def LogWrap(F: func): func - def wrapper(...args: list): any - echo 'Calling' - var result = call(F, args) - echo 'Done' - return result - enddef - return funcref('wrapper') -enddef - #################################################### ## 10. Testing #################################################### @@ -328,23 +323,30 @@ shell_job = job_start(["/bin/sh", "-c", "ls"], { echo v:shell_error #################################################### -## 12. JSON and Regex +## 12. Regex #################################################### -# JSON encode/decode -var data = {'name': 'Vim', 'version': 9} -var json = json_encode(data) -echo json - -var parsed = json_decode(json) -echo parsed.name - # Regex match var s = 'abc123' if s =~ '\d\+' echo 'Contains digits' endif +# Regex Basics: +# +# - `.` any char, `*` zero+ times, `\+` one+ times +# - `\d` digit, `\w` word char, `^` start, `$` end, `\|` OR + +# Case Sensitivity & Magic Modes: +# +# - `\c` / `\C`: case-insensitive / case-sensitive +# - `\v`: very magic, most chars are special, closer to extended regexes +# - `\V`: very nomagic, all but \ are literal + +echo 'Foobar' =~ '\cfoo' # true +echo 'abc123' =~ '\v\d+' # true +echo 'a|b' =~ '\Va|b' # true + # Replace var new = substitute('foo bar', 'bar', 'baz', '') echo new From f805b92b0549830870664ea9396b58e5f78cbc57 Mon Sep 17 00:00:00 2001 From: Konfekt Date: Fri, 18 Apr 2025 09:55:16 +0200 Subject: [PATCH 5/7] include most gratefully @kennypete 's improvements --- vim9script.md | 511 ++++++++++++++++++++++++++++---------------------- 1 file changed, 287 insertions(+), 224 deletions(-) diff --git a/vim9script.md b/vim9script.md index 40314b15..8226c66f 100644 --- a/vim9script.md +++ b/vim9script.md @@ -13,95 +13,191 @@ Try [vim9-conversion-aid](https://github.com/ubaldot/vim9-conversion-aid) as a s ```vim vim9script -# Above line is necessary to distinguish code in a *.vim file from legacy vimscript. +# The vim9script namespace, above, is required to distinguish a Vim9 script +# *.vim file from a legacy vimscript file. In Vim's command-line mode, +# or in a legacy script, using the command `:vim9cmd` (or just `:vim9`) before +# a command also evaluates and executes code as Vim9 script. +# +# There is no distinction between single and multi-line comments. +# Use # inside a line at any position to comment out all following characters. -#################################################### -## 1. Primitive Datatypes and Operators -#################################################### +################################################################## +## 1. Primitive types, collection types, operators, and regex +################################################################## -# Numbers -var i: number = 42 -var f: float = 3.14 +# The builtin function typename() may be used to reveal the type. +# Primitive data types are number (integer), float, and bool(ean) +echo typename(1) # number +echo typename(1.1) # float +echo typename(true) # bool -# Booleans -var done: bool = true +# Collection data types are string, blob, list, tuple, and dict(ionary) +echo typename("Hello world") # string +# Blob is a binary object +echo typename(0zFE0F) # blob +echo typename([1, 2]) # list +# echo typename((1, 2)) # tuple (Yet commented as it's a recent addition) +echo typename({1: 'one', 2: 'two'}) # dict -# Strings -var s: string = 'hello' +# 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 -# Arithmetic -var sum = 1 + 2 -var div = 10 / 3 -var mod = 10 % 3 -var pow = float2nr(pow(2, 3)) +# Similarly, for the float type. +echo 3.14 + 0.0015 # 3.1415 +echo 3.0 * 2.0 # 6.0 +# An integer and float will return a float. +echo 9 / 2.0 # 4.5 -# Numeric comparisons -echo 1 == 1 # true -echo 2 != 3 # true -echo 2 > 1 # true -echo 2 >= 2 # true +# Logical OR (||), AND (&&), and NOT (!). +echo true || false # true +echo true && false # false +echo !true # false -# String equality -echo 'foo' == 'foo' # true -echo 'foo' != 'bar' # true +# 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 -# Pattern matching +# Ternary operator. +echo 9 > 8 ? true : false # true + +# Falsy ("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 # 5 + +# String concatenation. +echo "Hello" .. " " .. 'world' # Hello world +# String indexing is at the character level (not byte level like vimscript) +echo 'fenêtre'[-4 : -1] # être +# There are dozens of builtin string functions. Examples. +echo reverse("moor") # room +echo toupper("summer") # SUMMER +echo str2list('yes') # [121, 101, 115] +echo strcharlen('length') # 6 + +# Type casting may be used to give an error for a type mismatch. +# (This example could use `try`, but that's covered later.) +echo 3 # 3 +# echo '3' # This errors, but could be caught with try/catch + +# Not-a-real-number values may be tested with isinf() and isnan() builtin +# functions. These examples also illustrate method chaining. +echo 1.0 / 0.0 ->isinf() # inf +echo -1.0 / 0.0 ->isinf() # -inf +echo 0.0 / 0.0 ->isnan() # nan + +# The maximum number is either a 32 or 64 bit signed number, which may +# be checked using: +echo v:numbersize # echos either 32 or 64 +# The implication is that any arithmatic where the number exceeds the +# permitted value, for 64-bit 9,223,372,036,854,775,807, will fail: +echo 9223372036854775807 + 1 # -9223372036854775808 + +# Numbers may be expressed as decimal, hexadecimal (prefixed with 0x), +# octal (0o or 0O), or binary (0b). These are all decimal 15: +echo 15 + 0b1111 + 0xF + 0o17 # 60 + +# Pattern matching on strings. echo 'foobar' =~ 'foo' # true (matches pattern) echo 'foobar' !~ 'baz' # true (does not match pattern) -# Logical -echo true && false # false -echo true || false # true -echo !true # false +# Vim uses distinct regular expressions. The basics are the same as many +# other languages. The basics: +# `.` any character, `*` zero+ times, `\+` one+ times +# `\c` case-insensitive, `\C` case-sensitive +# `\d` digit, `\w` word char, `\s` whitespace, `^` start, `$` end, `\|` OR +# Character classes may also be used. Examples. +# [:blank:] is the same as \s, [:digit:] is the same as \d +# Some things like the "very nomagic" and "very magic" are unique. +# `\v` very magic, most chars are special, closer to extended regexes +# `\V`: very nomagic, all but \ are literal +echo 'Foobar' =~ '\c^foo' # true +echo "The year is 2025" =~ '[[:digit:]]\+$' # true +echo 'abc123' =~ '\v\d+' # true +echo 'a|b' =~ '\Va|b' # true -# Ternary -echo true ? 'yes' : 'no' #################################################### -## 2. Variables and Collections +## 2. Variables #################################################### -# Variable declaration -var name: string = 'Vim' -var count = 10 # type inferred +# `var` is used to declare a variable, which may have a type +var name: string = 'Vim' # type declared +var count = 10 # type inferred (number) +# When the type is declared as a list or dict, its type(s) must be also, but +# may be "". +var Alist: list> = [[1, 2], [3, 4]] +var Adict: dict = {a: 1, b: 'two'} # Constants -const pi = 3.1415 +# `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 -# Lists -var l: list = [1, 2, 3] -echo l[0] -l->add(4) -l->remove(1) +# There are many builtin functions for variable manipulation. Some have been +# illustrated, above. A selection of some more related to lists. +var MyList: list = [0, 1, 2, 3] +echo MyList[0] # 0 +MyList->add(4) +MyList->remove(0) +echo join(MyList, ', ') # 1, 2, 3, 4 +# String interpolation with $. +echo $"The first and last items in MyList are {MyList[0]} and {MyList[-1]}" -# Tuples -var t: tuple = (1, 'a', 3) -echo t[1] - -# Use `g:` for global variables, `b:` for buffer-local, `w:` for window-local, and so on. +# Variables exist within scopes. When unprefixed, as the examples are, above, +# the scope is script-local (which differs from vimscript, which uses `s:`). +# Other scopes are global (prefixed with `g:`), window-local (`w:`), +# buffer-local (`b:`), and tab-local (`t:`). # Vim help on scopes: https://vimhelp.org/eval.txt.html#variable-scope +# Use `g:` for global variables, `b:` for buffer-local, `w:` for window-local, +# and so on. Vim also has many Vim-defined `v:` prefixed global variables. g:global_var = 'I am global' +echo g:global_var # I am global +echo v:version # 901 (or whatever version you are running; 901 means 9.1) +echo b:current_syntax # vim (if the buffer's filetype is vim with 'syntax' on) -# - @a accesses the contents of register "a" -# - $PATH references the environment variable PATH -# - &l:textwidth gets the buffer‐local value of the textwidth option -echo expand($PATH) +# Registers are a form of variable used to store string values of many +# pre-defined or user-defined items. When used in a Vim9 script, they are +# prefixed with `@`. +echo @: # (echos the last command entered by the user) +echo @/ # (echos the last search performed by the user) +echo @1 # (echos the penultimate yanked or deleted text) +echo @% # (echos the current file path) +@z = 'There are 26 named registers, a to z' +echo @z -# Dictionaries -var d: dict = {a: 1, b: 2} -echo d.a -d.c = 3 +# Other builtin variables. +echo $MYVIMRC # (location of your .vimrc, or _vimrc using Windows, file) +echo $VIMRUNTIME # (the Vim directory of the current Vim executable) +echo $PATH # (references the environment variable, PATH) -# Sets (via dict keys) -var set = {1: true, 2: true} +# Vim has many settings, which are also variables. They may be local or +# global scoped. +echo &textwidth # (echos the buffer‐local value of the textwidth option) +echo &wildmenu # (echos the boolean value of command-line wildcard expansion) -# JSON encode/decode -var data = {'name': 'Vim', 'version': 9} -var json = json_encode(data) -echo json - -var parsed = json_decode(json) -echo parsed.name #################################################### ## 3. Control Flow @@ -139,6 +235,38 @@ for x in [1, 2, 3] echo x endfor +# Exceptions +try + DoesNotExist() # This fails +catch + echo 'Function DoesNotExist() does not exist!' +endtry + +try + var lines = readfile('nofile.txt') +catch /E484:/ + echo 'File not found' +finally + echo 'Done' +endtry + +try + if !filereadable('file.txt') + throw 'MyError' + else + # Read + var lines = readfile('file.txt') + # Append + writefile(['line3'], 'file.txt', 'a') + endif +echo lines + + endif +catch /MyError/ + echo 'File not found' +endtry + + #################################################### ## 4. Functions #################################################### @@ -160,10 +288,6 @@ def Sum(...args: list): number return reduce(args, (x, y) => x + y) enddef -# Lambda -var Square = (x) => x * x -echo Square(4) - # Define a function that returns a closure capturing a local variable def MakeAdder(x: number): func # Return a reference to the inner function @@ -176,29 +300,15 @@ var Add5 = MakeAdder(5) # Call the closure with 3; result is 8 echo Add5(3) -# Partial -def Log(level: string, msg: string) - echo $"[{level}] {msg}" -enddef - -var Warn = function('Log', ['WARN']) -Warn('Disk low') - -# Decorator-like -def LogWrap(F: func): func - def wrapper(...args: list): any - echo 'Calling' - var result = call(F, args) - echo 'Done' - return result - enddef - return funcref('wrapper') -enddef +# A lambda function. +var Divide = (val: number, by: number): number => val / by +echo Divide(420, 10) # 42 #################################################### -## 5. Classes +## 5. Classes and enums #################################################### +# Class class Point var x: number var y: number @@ -222,137 +332,58 @@ var p = Point.new(1, 2) p.Move(3, 4) echo p.ToString() # => (4, 6) +# Enum +enum Quad + Square('four', true), + Rectangle('opposite', true), + Rhombus('opposite', false) + var es: string + var ra: bool + def QuadProps(): list + return [this.es, this.ra] + enddef +endenum +var q: Quad = Quad.Square +# result using string interpolation and showing multiple lines without `\`, +# which is required in vimscript but not in Vim9 script. +var result = $"A {tolower(q.name)} has {q.QuadProps()[0]} sides of equal " .. + "length and " .. + (q.QuadProps()[1] ? 'has only right angles' : 'has no right angle') +echo result + + #################################################### ## 6. Modules and Imports #################################################### -# Source another script file -source mylib.vim - -# Runtime path -runtime plugin/myplugin.vim - -# Vim loads `.vimrc`, then plugin files in `plugin/` directories. -# Vim9Script can coexist with older scripts. -# Place Vim9 files in separate `.vim` files with `vim9script` at the top. -# Use `autoload/` or `ftplugin/` for specialized features. - -# Define script-local functions -def Helper() - echo 'internal' -enddef - -#################################################### -## 7. File I/O -#################################################### - -# Write -writefile(['line1', 'line2'], 'file.txt') - -# Append -writefile(['line3'], 'file.txt', 'a') - -# Read -var lines = readfile('file.txt') -echo lines - -#################################################### -## 8. Exceptions -#################################################### +# Sourcing another script file. +# In this case, sourcing the optional matchit plugin, distributed with Vim. +source $VIMRUNTIME/pack/dist/opt/matchit/plugin/matchit.vim +# That's not an ideal way though. It's better to use `packadd`. +packadd helptoc +# Using `runtime` is another way (which can be used with wildcards) +runtime pack/**/**/**/comment.vim +# Importing functions from other files is achieved with `export` in the script +# file exporting the function/constant/variable and `import` in the +# script using the exported function/constant/variable. +# If this is in file, `MyFile.vim` in the same directory as the script file. +export const THEEND: string = 'The end.' +# The script importing THEEND would include (try used here to prevent error) try - var lines = readfile('nofile.txt') -catch /E484:/ - echo 'File not found' -finally - echo 'Done' + import "MyFile.vim" +catch + # Do something if the import fails +endtry +# And using the constant would be (try again used to prevent error) +try + echo MyFile.THEEND +catch + echo "THE END" endtry -# Throw -throw 'MyError' - #################################################### -## 10. Testing -#################################################### - -v:errors = [] - -assert_equal(4, 2 + 2) -assert_notequal(1, 2) -assert_true(1 < 2) -assert_false(2 < 1) -assert_match('\d\+', 'abc123') -assert_notmatch('\d\+', 'abc') - -if len(v:errors) == 0 - echo 'All tests passed' -else - echo 'Test failures:' - echo v:errors -endif - -#################################################### -## 11. External Commands -#################################################### - -# Run a shell command and capture output -var result = system('ls') -echo result - -# Run and split into lines -silent var lines = systemlist('ls') -for line in lines - echo line -endfor - -# Using :!ls -:!ls - -# Using job_start() callback -var shell_job: job -def GotOutput(channel: channel, msg: string) - echo msg -enddef -# Start a shell in the background. -shell_job = job_start(["/bin/sh", "-c", "ls"], { - out_cb: GotOutput, - err_cb: GotOutput - }) - -# Check exit status -echo v:shell_error - -#################################################### -## 12. Regex -#################################################### - -# Regex match -var s = 'abc123' -if s =~ '\d\+' - echo 'Contains digits' -endif - -# Regex Basics: -# -# - `.` any char, `*` zero+ times, `\+` one+ times -# - `\d` digit, `\w` word char, `^` start, `$` end, `\|` OR - -# Case Sensitivity & Magic Modes: -# -# - `\c` / `\C`: case-insensitive / case-sensitive -# - `\v`: very magic, most chars are special, closer to extended regexes -# - `\V`: very nomagic, all but \ are literal - -echo 'Foobar' =~ '\cfoo' # true -echo 'abc123' =~ '\v\d+' # true -echo 'a|b' =~ '\Va|b' # true - -# Replace -var new = substitute('foo bar', 'bar', 'baz', '') -echo new - -#################################################### -## 13. Vim Idioms +## 7. Vim Idioms #################################################### # Source guard (plugin pattern) @@ -369,14 +400,12 @@ command! Hello echo 'Hello Vim9' # You can specify attributes like `-nargs`, `-range`, `-complete`; # see https://vimhelp.org/usr_40.txt.html#40.2 command! -nargs=1 -complete=file MyCmd edit - -# Group autocommands to manage them systematically -# to prevent duplicate autocommands on re-sourcing. -augroup AutoReload - autocmd! - autocmd BufWritePost $MYVIMRC source $MYVIMRC - autocmd BufReadPost *.txt echo 'Hello text file' -augroup END +# Toggle a boolean setting +def ToggleFeature() + g:myplugin_enabled = !get(g:, 'myplugin_enabled', false) + echo g:myplugin_enabled ? 'Enabled' : 'Disabled' +enddef +command! ToggleMyPlugin ToggleFeature() # Define a script-local function and map it via def DoSomething() @@ -386,6 +415,14 @@ enddef nnoremap (MyPluginAction) DoSomething() nmap a (MyPluginAction) +# Group autocommands to manage them systematically +# to prevent duplicate autocommands on re-sourcing. +augroup AutoReload + autocmd! + autocmd BufWritePost $MYVIMRC source $MYVIMRC + autocmd BufReadPost *.txt echo 'Hello text file' +augroup END + # You can run normal commands from Vim9Script: # This executes like pressing `ggddGp` in normal mode. normal! ggddGp @@ -404,16 +441,42 @@ echo printf('Hello, %s!', 'world') # `type()`, along with `v:t_*` constants, indicates object types. echo type(123) == v:t_number -if v:version >= 900 - echo 'Vim 9+ detected' -endif -# Toggle a boolean setting -def ToggleFeature() - g:myplugin_enabled = !get(g:, 'myplugin_enabled', false) - echo g:myplugin_enabled ? 'Enabled' : 'Disabled' +#################################################### +## 8. External Commands +#################################################### + +# Run a shell command and capture output +var result = system('ls') +echo result + +# Run and split into lines +silent var lines = systemlist('ls') +for line in lines + echo line +endfor + +# output files in folder of current file +# ... to non-interactive shell +:!ls %:h:S +# ... to current buffer, replacing its contents +:%!ls %:h:S +# ... to current buffer, appending at cursor position +:.read!ls %:h:S + +# Using job_start() callback +var shell_job: job +def GotOutput(channel: channel, msg: string) + echo msg enddef -command! ToggleMyPlugin ToggleFeature() +# Start a shell in the background. +shell_job = job_start(["/bin/sh", "-c", "ls"], { + out_cb: GotOutput, + err_cb: GotOutput + }) + +# Check exit status +echo v:shell_error # Create a quickfix list from Git diff def GitDiffQuickfix() From ae5c07ec49c26c8cc9a3b567559c07aa6acd2497 Mon Sep 17 00:00:00 2001 From: Konfekt Date: Fri, 18 Apr 2025 13:20:02 +0200 Subject: [PATCH 6/7] address @girishji 's suggestion --- vim9script.md | 99 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/vim9script.md b/vim9script.md index 8226c66f..1c04b7e8 100644 --- a/vim9script.md +++ b/vim9script.md @@ -131,6 +131,12 @@ echo "The year is 2025" =~ '[[:digit:]]\+$' # true echo 'abc123' =~ '\v\d+' # true echo 'a|b' =~ '\Va|b' # true +# `\zs`: Sets the start of the match. +# `\ze`: Sets the end of the match. +# Match only the domain from an email: +var email = 'user@example.com' +echo matchstr(email, '@\zs[^ ]\+\ze') # Output: 'example.com' + #################################################### ## 2. Variables @@ -258,9 +264,7 @@ try var lines = readfile('file.txt') # Append writefile(['line3'], 'file.txt', 'a') - endif -echo lines - + echo lines endif catch /MyError/ echo 'File not found' @@ -268,7 +272,7 @@ endtry #################################################### -## 4. Functions +## 4. Functions and Lambdas #################################################### # Basic function @@ -300,10 +304,30 @@ var Add5 = MakeAdder(5) # Call the closure with 3; result is 8 echo Add5(3) -# A lambda function. +# Lambdas in Vim9Script use `(args) => expr` syntax: var Divide = (val: number, by: number): number => val / by echo Divide(420, 10) # 42 +# Sample list +var nums = [1, 2, 3, 4, 5, 6] + +# map(): Square each number +var squares = map(copy(nums), (i, v) => v * v) +echo squares # [1, 4, 9, 16, 25, 36] + +# filter(): Keep even numbers +var evens = filter(copy(nums), (i, v) => v % 2 == 0) +echo evens # [2, 4, 6] + +# reduce(): Sum all numbers +var sum = reduce(copy(nums), (acc, v) => acc + v, 0) +echo sum # 21 + +# sort(): Sort descending using lambda +# Use `copy()` to avoid mutating the original list. +var sorted = sort(copy(nums), (a, b) => b - a) +echo sorted # [6, 5, 4, 3, 2, 1] + #################################################### ## 5. Classes and enums #################################################### @@ -387,10 +411,9 @@ endtry #################################################### # Source guard (plugin pattern) -if exists('g:loaded_myplugin') - finish -endif -var g:loaded_myplugin = true +if exists('g:loaded_myplugin') | finish | endif +# Set to true to avoid sourcing the following code multiple times. +# g:loaded_myplugin = true # Default value var greeting = get(g:, 'myplugin_greeting', 'Hello') @@ -427,8 +450,8 @@ augroup END # This executes like pressing `ggddGp` in normal mode. normal! ggddGp -# `exist({name})` checks if a variable, function, or command is defined. -echo exist(':myVariable') == 2 +# `exists({name})` checks if a variable, function, or command is defined. +echo exists(':myVariable') == 2 # `has({feature})` checks if Vim has a specific feature (e.g. `has('unix')`). echo has('unix') if has('nvim') @@ -447,7 +470,7 @@ echo type(123) == v:t_number #################################################### # Run a shell command and capture output -var result = system('ls') +silent result = system('ls') echo result # Run and split into lines @@ -458,11 +481,11 @@ endfor # output files in folder of current file # ... to non-interactive shell -:!ls %:h:S +!ls %:h:S # ... to current buffer, replacing its contents :%!ls %:h:S -# ... to current buffer, appending at cursor position -:.read!ls %:h:S +# ... to the same buffer, appending at cursor position +:.read! ls %:h:S # Using job_start() callback var shell_job: job @@ -474,7 +497,6 @@ shell_job = job_start(["/bin/sh", "-c", "ls"], { out_cb: GotOutput, err_cb: GotOutput }) - # Check exit status echo v:shell_error @@ -495,6 +517,51 @@ def GitDiffQuickfix() copen enddef command! GitDiffQF call GitDiffQuickfix() + +#################################################### +## 9. Debugging, Compiling and Inspecting Bytecode +#################################################### + +def MyFunc() + var x = 10 + var y = x * 2 + echo y +enddef + +# To debug this function: +# Add a breakpoint at line 3 of the function (line numbers are relative to the function) +# :breakadd func MyFunc 3 + +# Then run the function in debug mode +# :debug call MyFunc() + +# While debugging, use commands like: +# - :step to step into +# - :next to step over +# - :finish to run to end +# - :cont to continue +# - :echo to inspect variables + +## Listing Bytecode of a Function, +## Useful to understand how Vim optimizes Vim9Script functions + +def MyMultiply(a: number, b: number): number + return a * b +enddef + +# To compile and check for syntax/type errors: +# Use :func MyMultiply to view the function definition + +# To inspect the compiled bytecode: +disassemble MyMultiply + +# Example output: +# 757_MyMultiply +# return a * b +# 0 LOAD arg[-2] +# 1 LOAD arg[-1] +# 2 OPNR * +# 3 RETURN ``` ### Additional Resources From 57d988a1df839754da1f9ab4cb4d3f66e976fd92 Mon Sep 17 00:00:00 2001 From: Konfekt Date: Fri, 18 Apr 2025 21:19:39 +0200 Subject: [PATCH 7/7] add short legacy vimscript intro as suggested by @kennypete --- vim9script.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vim9script.md b/vim9script.md index 1c04b7e8..3b9a0077 100644 --- a/vim9script.md +++ b/vim9script.md @@ -8,16 +8,20 @@ contributors: --- Vim9Script is a modern scripting language introduced in Vim 9.0. -It improves performance, readability, and structure over legacy Vimscript. +It is designed to replace legacy Vimscript (also called VimL), which is a sequence of ex-commands enhanced with scripting constructs like variables, functions, and control flow. + +Unlike legacy Vimscript, Vim9Script enforces stricter syntax, improves performance, and supports modern programming features such as strong typing, classes, lambdas, and modules. + Try [vim9-conversion-aid](https://github.com/ubaldot/vim9-conversion-aid) as a starting point to convert legacy Vimscript to Vim9Script. ```vim vim9script + # The vim9script namespace, above, is required to distinguish a Vim9 script # *.vim file from a legacy vimscript file. In Vim's command-line mode, # or in a legacy script, using the command `:vim9cmd` (or just `:vim9`) before # a command also evaluates and executes code as Vim9 script. -# + # There is no distinction between single and multi-line comments. # Use # inside a line at any position to comment out all following characters.