mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2025-04-27 07:33:57 +00:00
572 lines
16 KiB
Markdown
572 lines
16 KiB
Markdown
---
|
||
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.
|
||
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
|
||
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.
|
||
|
||
##################################################################
|
||
## 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. 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:
|
||
# <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)
|