This commit is contained in:
Enno 2025-04-19 01:53:34 +00:00 committed by GitHub
commit fb0301e329
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

615
vim9script.md Normal file
View File

@ -0,0 +1,615 @@
---
name: Vim9Script
filename: learnvim9script.vim
contributors:
- ["Alejandro Sanchez", "http://hiphish.github.io/"]
- ["Yegappan Lakshmanan", "https://github.com/yegappan"]
- ["LacyGoill", "https://github.com/lacygoill"]
---
Vim9Script is a modern scripting language introduced in Vim 9.0.
Vim9script, which is exclusive to Vim version 9+, improves on its predecessor, legacy Vimscript, also called VimL, which is a sequence of Ex commands enhanced with scripting constructs like variables, functions, and control flow.
(Ex commands, such as `:echo`, `:write`, `:substitute`, `:quit`, ... are commands that are part of the legacy Ex editor to execute a single action, one-liners without return values.)
Legacy Vimscript is interpreted line by line, requiring backslashes to continue lines, and commands like `let` and `call` are used to make them resemble ex-commands.
In contrast, Vim9script supports multi-line syntax natively, without needing line continuation.
This makes it less suited for command-line usage, unlike traditional ex-commands, so that Vim9Script complements these for scripting.
Vim9Script enforces stricter syntax, improves performance, and supports modern programming features such as strong typing, classes, lambdas, and modules.
Differences (see https://vimhelp.org/vim9.txt.html#vim9-differences) include:
1. New syntax basics
• Comments start with `#` instead of `"`.
• Line-continuation backslashes are rarely needed --- just concatenate with `..`.
• Whitespace is significant in many places to keep things readable.
2. Variables and constants
• Declare regular variables with `:var`, e.g. `var count = 0`
• Change them with standard operators (`count += 3`) --- no more `:let`.
• Declare immutable names with `:const` or `:final`.
3. Typed, script-local functions
• All functions (and variables) are script-local by default.
• Use `:def` with typed params and a return type, e.g.
`def foo(x: number, y: string): bool`
• Call them like normal functions (no `:call`).
All Ex commands can still be used inside functions and, vice-versa, you can call a function by an Ex command with `:vim9` (respectively `:call` in legacy vimscript) on the command line.
You can also define your own commands that call functions.
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.
# You can run this Vim9 script directly in Vim. After pasting the content
# into a Vim buffer, enter the command `so` in command-line mode (press `:`
# first to enter command-line mode).
##################################################################
## 1. Primitive types, collection types, operators, and regex
##################################################################
# 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
# 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<number>
# echo typename((1, 2)) # tuple (Yet commented as it's a recent addition)
echo typename({1: 'one', 2: 'two'}) # dict<string>
# 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
# 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
# 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 ("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 <number>3 # 3
# echo <number>'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)
# 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
# `\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
####################################################
# `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 "<any>".
var Alist: list<list<number>> = [[1, 2], [3, 4]]
var Adict: dict<any> = {a: 1, b: 'two'}
# Constants
# `const` may be used to make both the variable and values constant.
const PI: dict<float> = {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<float> = {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
# There are many builtin functions for variable manipulation. Some have been
# illustrated, above. A selection of some more related to lists.
var MyList: list<number> = [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]}"
# 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)
# 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
# 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)
# 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)
####################################################
## 3. Control Flow
####################################################
# If / Else
if count > 5
echo 'big'
elseif count == 5
echo 'medium'
else
echo 'small'
endif
# For loop
for j in range(3)
echo j
endfor
# While loop
var k = 0
while k < 3
echo k
k += 1
endwhile
# Loop control
for x in [1, 2, 3]
if x == 2
continue
endif
if x == 3
break
endif
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')
echo lines
endif
catch /MyError/
echo 'File not found'
endtry
####################################################
## 4. Functions and Lambdas
####################################################
# 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)
# 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
####################################################
# 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.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<any>
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
####################################################
# 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
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
####################################################
## 7. Vim Idioms
####################################################
# Source guard (plugin pattern)
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')
# 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>
# 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 <Plug>
def DoSomething()
echo 'Action triggered'
enddef
nnoremap <silent> <Plug>(MyPluginAction) <ScriptCmd>DoSomething()<CR>
nmap <silent> <Leader>a <Plug>(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
# `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')
echo 'Running in Neovim'
endif
# `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
####################################################
## 8. External Commands
####################################################
# Run a shell command and capture output
silent 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 the same 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
# 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()
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()
############################################################
## 9. Testing, Debugging, Compiling and Inspecting Bytecode
############################################################
v:errors = []
assert_equal(4, 2 + 2)
assert_false(2 < 1)
assert_notmatch('\d\+', 'abc')
if !empty(v:errors)
echo $"Test failures: {v:errors}"
endif
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:
# <SNR>757_MyMultiply
# return a * b
# 0 LOAD arg[-2]
# 1 LOAD arg[-1]
# 2 OPNR *
# 3 RETURN
```
### 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)