update vim9script using yeggapan's guide

This commit is contained in:
Konfekt 2025-04-13 10:14:41 +02:00
parent eee44d9e2c
commit 7e95db1d99

View File

@ -1,452 +1,425 @@
--- ---
name: Vim9script name: Vim9Script
filename: learn9vimscript.vim filename: learnvim9script.vim
contributors: 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 ```vim
# ############## vim9script
# Introduction # 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: ## 1. Primitive Datatypes and Operators
# ####################################################
# - 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 # Numbers
echo 123 # Decimal var i: number = 42
echo 0b1111011 # Binary var f: float = 3.14
echo 0173 # Octal
echo 0x7B # Hexadecimal
echo 123.0 # Floating-point
echo 1.23e2 # Floating-point (scientific notation)
# Booleans # Booleans
echo true # Evaluates to true var done: bool = 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 # Strings
echo 'a' < 'B' # True or false depending on 'ignorecase' var s: string = 'hello'
echo 'a' <? 'B' # True
echo 'a' <# 'B' # False
# Regular expression matching # Arithmetic
echo "hi" =~ "hello" # Regular expression match var sum = 1 + 2
echo "hi" =~# "hello" # Case sensitive var div = 10 / 3
echo "hi" =~? "hello" # Case insensitive var mod = 10 % 3
echo "hi" !~ "hello" # Regular expression unmatch var pow = float2nr(pow(2, 3))
echo "hi" !~# "hello" # Case sensitive
echo "hi" !~? "hello" # Case insensitive
# Boolean operations # Numeric comparisons
echo true && false # Logical AND echo 1 == 1 # true
echo true || false # Logical OR echo 2 != 3 # true
echo !true # Logical NOT echo 2 > 1 # true
echo true ? 'yes' : 'no' # Ternary operator echo 2 >= 2 # true
# Strings # String equality
echo "Hello world\n" # Newline echo 'foo' == 'foo' # true
echo 'Hello world\n' # Literal echo 'foo' != 'bar' # true
echo 'Let''s go!' # Two single quotes become one
# String concatenation # Pattern matching
echo 'Hello ' .. 'world' # String concatenation echo 'foobar' =~ 'foo' # true (matches pattern)
echo 'foobar' !~ 'baz' # true (does not match pattern)
# String indexing # Regex Basics:
echo 'Hello'[0] # First character # - `.` any char, `*` zero+ times, `\+` one+ times
echo 'Hello'[1] # Second character # - `\d` digit, `\w` word char, `^` start, `$` end, `\|` OR
echo 'Hellö'[4] # Returns a character # 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 # Logical
echo 'Hello'[:] # Copy of entire string echo true && false # false
echo 'Hello'[1:3] # Substring echo true || false # true
echo 'Hello'[1:-2] # Substring until second to last character echo !true # false
echo 'Hello'[1:] # Substring with starting index
echo 'Hello'[:2] # Substring with ending index # Ternary
echo 'Hello'[-2:] # Substring relative to end 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 # Lists
echo [] # Empty list var l: list<number> = [1, 2, 3]
echo [1, 2, 'Hello'] # List with elements echo l[0]
echo [1, 2, 'Hello', ] # Trailing comma permitted l->add(4)
echo [[1, 2], 'Hello'] # Nested lists l->remove(1)
# List concatenation # Tuples
echo [1, 2] + [3, 4] # Creates a new list var t: tuple<number, string, number> = (1, 'a', 3)
echo t[1]
# List indexing # Vim help on scopes: https://vimhelp.org/eval.txt.html#variable-scope
echo [1, 2, 3, 4][2] # Third element # Use `g:` for global variables, `b:` for buffer-local, `w:` for window-local, and so on.
echo [1, 2, 3, 4][-1] # Last element # - @a accesses the contents of register "a"
# - $PATH references the environment variable PATH
# List slicing # - &l:textwidth gets the bufferlocal value of the textwidth option
echo [1, 2, 3, 4][:] # Shallow copy g:global_var = 'I am global'
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
# Dictionaries # Dictionaries
echo {} # Empty dictionary var d: dict<number> = {a: 1, b: 2}
echo {'a': 1, 'b': 2} # Dictionary literal echo d.a
echo {'a': 1, 'b': 2, } # Trailing comma permitted d.c = 3
echo {'x': {'a': 1, 'b': 2}} # Nested dictionary
# Indexing a dictionary # Sets (via dict keys)
echo {'a': 1, 'b': 2}['a'] # Literal index var set = {1: true, 2: true}
echo {'a': 1, 'b': 2}.a # Syntactic sugar
# Funcref ####################################################
echo function('type') # Reference to function type() ## 3. Control Flow
echo {x -> x * x} # Anonymous function ####################################################
# Regular expression # If / Else
substitute/hello/Hello/ if count > 5
echo 'big'
# ########################### elseif count == 5
# Implicit type conversions echo 'medium'
# ###########################
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 else
echo 'Fail' echo 'small'
endif endif
# Loops # For loop
for j in range(3)
# For-loop echo j
for person in ['Alice', 'Bob', 'Carol', 'Dennis', 'Emily']
echo 'Hello ' .. person
endfor endfor
# Iterate over a nested list # While loop
for [x, y] in [[1, 0], [0, 1], [-1, 0], [0, -1]] var k = 0
echo 'Position: x =' .. x .. ', y = ' .. y while k < 3
endfor echo k
k += 1
# 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 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>): 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 try
source path/to/file var lines = readfile('nofile.txt')
catch /Cannot open/ catch /E484:/
echo 'Looks like that file does not exist' echo 'File not found'
catch /.*/
echo 'Something went wrong, but I do not know what'
finally finally
echo 'I am done trying' echo 'Done'
endtry endtry
# ########## # Throw
# Functions throw 'MyError'
# ##########
# Defining functions ####################################################
def AddNumbersLoudly(x: number, y: number): number ## 9. Advanced Features
echo 'Adding' .. x .. 'and' .. y ####################################################
return x + y
# Lambda
var Square = (x) => x * x
echo Square(4)
# Partial
def Log(level: string, msg: string)
echo $"[{level}] {msg}"
enddef enddef
def s:addNumbersLoudly(x: number, y: number): number var Warn = function('Log', ['WARN'])
echo 'Adding' .. x .. 'and' .. y Warn('Disk low')
return x + y
# Decorator-like
def LogWrap(F: func): func
def wrapper(...args: list<any>): any
echo 'Calling'
var result = call(F, args)
echo 'Done'
return result
enddef
return funcref('wrapper')
enddef enddef
# Range functions ####################################################
def FirstAndLastLine() range ## 10. Testing
echo [a:firstline, a:lastline] ####################################################
enddef
# Aborting functions v:errors = []
def SourceMyFile() abort
source my-file.vim
echo 'This will never be printed'
enddef
# Closures assert_equal(4, 2 + 2)
def MakeAdder(x: number) assert_notequal(1, 2)
def Adder(n: number) closure assert_true(1 < 2)
return n + x assert_false(2 < 1)
enddef assert_match('\d\+', 'abc123')
return funcref('Adder') assert_notmatch('\d\+', 'abc')
enddef
var AddFive = MakeAdder(5)
echo AddFive(3) # Prints 8
# Dictionary functions if len(v:errors) == 0
def Mylen() dict echo 'All tests passed'
return len(self.data) else
enddef echo 'Test failures:'
var mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")} echo v:errors
echo mydict.len() endif
# Alternatively, more concise ####################################################
var mydict = {'data': [0, 1, 2, 3]} ## 11. External Commands
def mydict.len() ####################################################
return len(self.data)
enddef
# Calling functions # Run a shell command and capture output
var animals = keys({'cow': 'moo', 'dog': 'woof', 'cat': 'meow'}) var result = system('ls')
echo result
# Call a function for its side effects only # Run and split into lines
call sign_undefine() var lines = systemlist('ls')
for line in lines
echo line
endfor
# The call() function # Check exit status
echo call(function('get'), [{'a': 1, 'b': 2}, 'c', 3]) # Prints 3 echo v:shell_error
# Function namespaces ####################################################
def foo#bar#log(value: string) ## 12. JSON and Regex
echomsg value ####################################################
enddef
call foo#bar#log('Hello') # JSON encode/decode
var data = {'name': 'Vim', 'version': 9}
var json = json_encode(data)
echo json
# ############################# var parsed = json_decode(json)
# Frequently used ex-commands echo parsed.name
# #############################
# Sourcing runtime files # Regex match
runtime plugin/my-plugin.vim var s = 'abc123'
if s =~ '\d\+'
echo 'Contains digits'
endif
# Defining new ex-commands # Replace
command! SwapAdjacentLines normal! ddp var new = substitute('foo bar', 'bar', 'baz', '')
echo new
command! -nargs=1 Error echoerr <args> ####################################################
## 13. Vim Idioms
####################################################
# Defining auto-commands # Source guard (plugin pattern)
autocmd BufWritePost $MYVIMRC source $MYVIMRC if exists('g:loaded_myplugin')
finish
endif
var g:loaded_myplugin = true
# Auto groups # Default value
augroup auto-source var greeting = get(g:, 'myplugin_greeting', 'Hello')
autocmd!
autocmd BufWritePost $MYVIMRC source $MYVIMRC # 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 <args>
# 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 augroup END
# Executing # Define a script-local function and map it via <Plug>
var line = 3 def DoSomething()
execute line .. 'delete' echo 'Action triggered'
enddef
# Executing normal-mode commands nnoremap <silent> <Plug>(MyPluginAction) :<c-u>call <SID>DoSomething()<CR>
nmap <silent> <Leader>a <Plug>(MyPluginAction)
# You can run normal commands from Vim9Script:
# This executes like pressing `ggddGp` in normal mode.
normal! ggddGp normal! ggddGp
# Window commands # `exist({name})` checks if a variable, function, or command is defined.
wincmd L echo exist(':myVariable') == 2
# `has({feature})` checks if Vim has a specific feature (e.g. `has('unix')`).
# ###########################
# Frequently used functions
# ###########################
# Feature check
echo has('nvim')
echo has('python3')
echo has('unix') echo has('unix')
echo has('win32') if has('nvim')
echo 'Running in Neovim'
# 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('<cword>')
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 endif
var g_loaded_my_plugin = true # `expand({expr})` expands filenames, `<cword>`, 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 if v:version >= 900
var s_greeting = get(g:, 'my_plugin_greeting', 'Hello') 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)