diff options
Diffstat (limited to 'runtime/doc/vim9.txt')
-rw-r--r-- | runtime/doc/vim9.txt | 1487 |
1 files changed, 1487 insertions, 0 deletions
diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt new file mode 100644 index 0000000..7246ff8 --- /dev/null +++ b/runtime/doc/vim9.txt @@ -0,0 +1,1487 @@ +*vim9.txt* For Vim version 8.2. Last change: 2021 Jan 23 + + + VIM REFERENCE MANUAL by Bram Moolenaar + + +THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + +Vim9 script commands and expressions. *Vim9* *vim9* + +Most expression help is in |eval.txt|. This file is about the new syntax and +features in Vim9 script. + +THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + + +1. What is Vim9 script? |Vim9-script| +2. Differences |vim9-differences| +3. New style functions |fast-functions| +4. Types |vim9-types| +5. Namespace, Import and Export |vim9script| +6. Future work: classes |vim9-classes| + +9. Rationale |vim9-rationale| + +============================================================================== + +1. What is Vim9 script? *Vim9-script* + +THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + +Vim script has been growing over time, while preserving backwards +compatibility. That means bad choices from the past often can't be changed +and compatibility with Vi restricts possible solutions. Execution is quite +slow, each line is parsed every time it is executed. + +The main goal of Vim9 script is to drastically improve performance. This is +accomplished by compiling commands into instructions that can be efficiently +executed. An increase in execution speed of 10 to 100 times can be expected. + +A secondary goal is to avoid Vim-specific constructs and get closer to +commonly used programming languages, such as JavaScript, TypeScript and Java. + +The performance improvements can only be achieved by not being 100% backwards +compatible. For example, making function arguments available in the +"a:" dictionary adds quite a lot of overhead. In a Vim9 function this +dictionary is not available. Other differences are more subtle, such as how +errors are handled. + +The Vim9 script syntax and semantics are used in: +- a function defined with the `:def` command +- a script file where the first command is `vim9script` +- an autocommand defined in the context of the above + +When using `:function` in a Vim9 script file the legacy syntax is used, with +the highest |scriptversion|. However, this can be confusing and is therefore +discouraged. + +Vim9 script and legacy Vim script can be mixed. There is no requirement to +rewrite old scripts, they keep working as before. You may want to use a few +`:def` functions for code that needs to be fast. + +============================================================================== + +2. Differences from legacy Vim script *vim9-differences* + +THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + +Overview ~ + +Brief summary of the differences you will most often encounter when using Vim9 +script and `:def` functions; details are below: +- 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. +- Assign values without `:let`, declare variables with `:var`: > + var count = 0 + count += 3 +- Constants can be declared with `:final` and `:const`: > + final matches = [] # add matches + 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 `:xit`, `:t`, `:append`, `:change`, `:insert` or curly-braces + names. +- A range before a command must be prefixed with a colon: > + :%s/this/that +- Unless mentioned specifically, the highest |scriptversion| is used. + + +Comments starting with # ~ + +In legacy Vim script comments start with double quote. In Vim9 script +comments start with #. > + # declarations + var count = 0 # number of occurrences + +The reason is that a double quote can also be the start of a string. In many +places, especially halfway through an expression with a line break, it's hard +to tell what the meaning is, since both a string and a comment can be followed +by arbitrary text. To avoid confusion only # comments are recognized. This +is the same as in shell scripts and Python programs. + +In Vi # is a command to list text with numbers. In Vim9 script you can use +`:number` for that. > + 101 number + +To improve readability there must be a space between a command and the # +that starts a comment: > + var name = value # comment + var name = value# error! + +In legacy Vim script # is also used for the alternate file name. In Vim9 +script you need to use %% instead. Instead of ## use %%% (stands for all +arguments). + + +Vim9 functions ~ + +A function defined with `:def` is compiled. Execution is many times faster, +often 10x to 100x times. + +Many errors are already found when compiling, before the function is executed. +The syntax is strict, to enforce code that is easy to read and understand. + +Compilation is done when either of these is encountered: +- the first time the function is called +- when the `:defcompile` command is encountered in the script where the + function was defined +- `:disassemble` is used for the function. +- a function that is compiled calls the function or uses it as a function + reference + +`:def` has no options like `:function` does: "range", "abort", "dict" or +"closure". A `:def` function always aborts on an error (unless `:silent!` was +used for the command or inside a `:try` block), does not get a range passed +cannot be a "dict" function, and can always be a closure. + +The argument types and return type need to be specified. The "any" type can +be used, type checking will then be done at runtime, like with legacy +functions. + +Arguments are accessed by name, without "a:", just like any other language. +There is no "a:" dictionary or "a:000" list. + +Variable arguments are defined as the last argument, with a name and have a +list type, similar to TypeScript. For example, a list of numbers: > + def MyFunc(...itemlist: list<number>) + for item in itemlist + ... + + +Functions and variables are script-local by default ~ + *vim9-scopes* +When using `:function` or `:def` to specify a new function at the script level +in a Vim9 script, the function is local to the script, as if "s:" was +prefixed. Using the "s:" prefix is optional. To define a global function or +variable the "g:" prefix must be used. For functions in an autoload script +the "name#" prefix is sufficient. > + def ThisFunction() # script-local + def s:ThisFunction() # script-local + def g:ThatFunction() # global + def scriptname#function() # autoload + +When using `:function` or `:def` to specify a nested function inside a `:def` +function, this nested function is local to the code block it is defined in. +In a `:def` function it is not possible to define a script-local function. It +is possible to define a global function by using the "g:" prefix. + +When referring to a function and no "s:" or "g:" prefix is used, Vim will +search for the function: +- in the function scope, in block scopes +- in the script scope, possibly imported +- in the list of global functions +However, it is recommended to always use "g:" to refer to a global function +for clarity. + +In all cases the function must be defined before used. That is when it is +called, when `:defcompile` causes it to be compiled, or when code that calls +it is being compiled (to figure out the return type). + +The result is that functions and variables without a namespace can usually be +found in the script, either defined there or imported. Global functions and +variables could be defined anywhere (good luck finding out where!). + +Global functions can still be defined and deleted at nearly any time. In +Vim9 script script-local functions are defined once when the script is sourced +and cannot be deleted or replaced. + +When compiling a function and a function call is encountered for a function +that is not (yet) defined, the |FuncUndefined| autocommand is not triggered. +You can use an autoload function if needed, or call a legacy function and have +|FuncUndefined| triggered there. + + +Reloading a Vim9 script clears functions and variables by default ~ + *vim9-reload* +When loading a legacy Vim script a second time nothing is removed, the +commands will replace existing variables and functions and create new ones. + +When loading a Vim9 script a second time all existing script-local functions +and variables are deleted, thus you start with a clean slate. This is useful +if you are developing a plugin and want to try a new version. If you renamed +something you don't have to worry about the old name still hanging around. + +If you do want to keep items, use: > + vim9script noclear + +You want to use this in scripts that use a `finish` command to bail out at +some point when loaded again. E.g. when a buffer local option is set: > + vim9script noclear + setlocal completefunc=SomeFunc + if exists('*g:SomeFunc') | finish | endif + def g:SomeFunc() + .... + + +Variable declarations with :var, :final and :const ~ + *vim9-declaration* *:var* +Local variables need to be declared with `:var`. Local constants need to be +declared with `:final` or `:const`. We refer to both as "variables" in this +section. + +Variables can be local to a script, function or code block: > + vim9script + var script_var = 123 + def SomeFunc() + var func_var = script_var + if cond + var block_var = func_var + ... + +The variables are only visible in the block where they are defined and nested +blocks. Once the block ends the variable is no longer accessible: > + if cond + var inner = 5 + else + var inner = 0 + endif + echo inner # Error! + +The declaration must be done earlier: > + var inner: number + if cond + inner = 5 + else + inner = 0 + endif + echo inner + +To intentionally hide a variable from code that follows, a block can be +used: > + { + var temp = 'temp' + ... + } + echo temp # Error! + +Declaring a variable with a type but without an initializer will initialize to +zero, false or empty. + +In Vim9 script `:let` cannot be used. An existing variable is assigned to +without any command. The same for global, window, tab, buffer and Vim +variables, because they are not really declared. They can also be deleted +with `:unlet`. + +Variables and functions cannot shadow previously defined or imported variables +and functions. +Variables may shadow Ex commands, rename the variable if needed. + +Global variables and user defined functions must be prefixed with "g:", also +at the script level. > + vim9script + var script_local = 'text' + g:global = 'value' + var Funcref = g:ThatFunction + +Since `&opt = value` is now assigning a value to option "opt", ":&" cannot be +used to repeat a `:substitute` command. + + +Constants ~ + *vim9-const* *vim9-final* +How constants work varies between languages. Some consider a variable that +can't be assigned another value a constant. JavaScript is an example. Others +also make the value immutable, thus when a constant uses a list, the list +cannot be changed. In Vim9 we can use both. + +`:const` is used for making both the variable and the value a constant. Use +this for composite structures that you want to make sure will not be modified. +Example: > + const myList = [1, 2] + myList = [3, 4] # Error! + myList[0] = 9 # Error! + muList->add(3) # Error! +< *:final* +`:final` is used for making only the variable a constant, the value can be +changed. This is well known from Java. Example: > + final myList = [1, 2] + myList = [3, 4] # Error! + myList[0] = 9 # OK + muList->add(3) # OK + +It is common to write constants as ALL_CAPS, but you don't have to. + +The constant only applies to the value itself, not what it refers to. > + final females = ["Mary"] + const NAMES = [["John", "Peter"], females] + NAMES[0] = ["Jack"] # Error! + NAMES[0][0] = "Jack" # Error! + NAMES[1] = ["Emma"] # Error! + NAMES[1][0] = "Emma" # OK, now females[0] == "Emma" + +< *E1092* +Declaring more than one variable at a time, using the unpack notation, is +currently not supported: > + var [v1, v2] = GetValues() # Error! +That is because the type needs to be inferred from the list item type, which +isn't that easy. + + +Omitting :call and :eval ~ + +Functions can be called without `:call`: > + writefile(lines, 'file') +Using `:call` is still possible, but this is discouraged. + +A method call without `eval` is possible, so long as the start is an +identifier or can't be an Ex command. Examples: > + myList->add(123) + g:myList->add(123) + [1, 2, 3]->Process() + {a: 1, b: 2}->Process() + "foobar"->Process() + ("foobar")->Process() + 'foobar'->Process() + ('foobar')->Process() + +In the rare case there is ambiguity between a function name and an Ex command, +prepend ":" to make clear you want to use the Ex command. For example, there +is both the `:substitute` command and the `substitute()` function. When the +line starts with `substitute(` this will use the function. Prepend a colon to +use the command instead: > + :substitute(pattern (replacement ( + +Note that while variables need to be defined before they can be used, +functions can be called before being defined. This is required to allow +for cyclic dependencies between functions. It is slightly less efficient, +since the function has to be looked up by name. And a typo in the function +name will only be found when the function is called. + + +Omitting function() ~ + +A user defined function can be used as a function reference in an expression +without `function()`. The argument types and return type will then be checked. +The function must already have been defined. > + + var Funcref = MyFunction + +When using `function()` the resulting type is "func", a function with any +number of arguments and any return type. The function can be defined later. + + +Lambda using => instead of -> ~ + +In legacy script there can be confusion between using "->" for a method call +and for a lambda. Also, when a "{" is found the parser needs to figure out if +it is the start of a lambda or a dictionary, which is now more complicated +because of the use of argument types. + +To avoid these problems Vim9 script uses a different syntax for a lambda, +which is similar to Javascript: > + var Lambda = (arg) => expression + +No line break is allowed in the arguments of a lambda up to and including the +"=>". This is OK: > + filter(list, (k, v) => + v > 0) +This does not work: > + filter(list, (k, v) + => v > 0) +This also does not work: > + filter(list, (k, + v) => v > 0) +But you can use a backslash to concatenate the lines before parsing: > + filter(list, (k, + \ v) + \ => v > 0) + +Additionally, a lambda can contain statements in {}: > + var Lambda = (arg) => { + g:was_called = 'yes' + return expression + } +NOT IMPLEMENTED YET + + *vim9-curly* +To avoid the "{" of a dictionary literal to be recognized as a statement block +wrap it in parenthesis: > + var Lambda = (arg) => ({key: 42}) + +Also when confused with the start of a command block: > + ({ + key: value + })->method() + + +Automatic line continuation ~ + +In many cases it is obvious that an expression continues on the next line. In +those cases there is no need to prefix the line with a backslash (see +|line-continuation|). For example, when a list spans multiple lines: > + var mylist = [ + 'one', + 'two', + ] +And when a dict spans multiple lines: > + var mydict = { + one: 1, + two: 2, + } +Function call: > + var result = Func( + arg1, + arg2 + ) + +For binary operators in expressions not in [], {} or () a line break is +possible just before or after the operator. For example: > + var text = lead + .. middle + .. end + var total = start + + end - + correction + var result = positive + ? PosFunc(arg) + : NegFunc(arg) + +For a method call using "->" and a member using a dot, a line break is allowed +before it: > + var result = GetBuilder() + ->BuilderSetWidth(333) + ->BuilderSetHeight(777) + ->BuilderBuild() + var result = MyDict + .member + +For commands that have an argument that is a list of commands, the | character +at the start of the line indicates line continuation: > + autocmd BufNewFile *.match if condition + | echo 'match' + | endif + +< *E1050* +To make it possible for the operator at the start of the line to be +recognized, it is required to put a colon before a range. This will add +"start" and print: > + var result = start + + print +Like this: > + var result = start + print + +This will assign "start" and print a line: > + var result = start + :+ print + +Note that the colon is not required for the |+cmd| argument: > + edit +6 fname + +It is also possible to split a function header over multiple lines, in between +arguments: > + def MyFunc( + text: string, + separator = '-' + ): string + +Since a continuation line cannot be easily recognized the parsing of commands +has been made stricter. E.g., because of the error in the first line, the +second line is seen as a separate command: > + popup_create(some invalid expression, { + exit_cb: Func}) +Now "exit_cb: Func})" is actually a valid command: save any changes to the +file "_cb: Func})" and exit. To avoid this kind of mistake in Vim9 script +there must be white space between most command names and the argument. + +However, the argument of a command that is a command won't be recognized. For +example, after "windo echo expr" a line break inside "expr" will not be seen. + + +Notes: +- "enddef" cannot be used at the start of a continuation line, it ends the + current function. +- No line break is allowed in the LHS of an assignment. Specifically when + unpacking a list |:let-unpack|. This is OK: > + [var1, var2] = + Func() +< This does not work: > + [var1, + var2] = + Func() +- No line break is allowed in between arguments of an `:echo`, `:execute` and + similar commands. This is OK: > + echo [1, + 2] [3, + 4] +< This does not work: > + echo [1, 2] + [3, 4] + +No curly braces expansion ~ + +|curly-braces-names| cannot be used. + + +Dictionary literals ~ + +Traditionally Vim has supported dictionary literals with a {} syntax: > + let dict = {'key': value} + +Later it became clear that using a simple text key is very common, thus +literal dictionaries were introduced in a backwards compatible way: > + let dict = #{key: value} + +However, this #{} syntax is unlike any existing language. As it turns out +that using a literal key is much more common than using an expression, and +considering that JavaScript uses this syntax, using the {} form for dictionary +literals is considered a much more useful syntax. In Vim9 script the {} form +uses literal keys: > + var dict = {key: value} + +This works for alphanumeric characters, underscore and dash. If you want to +use another character, use a single or double quoted string: > + var dict = {'key with space': value} + var dict = {"key\twith\ttabs": value} + var dict = {'': value} # empty key + +In case the key needs to be an expression, square brackets can be used, just +like in JavaScript: > + var dict = {["key" .. nr]: value} + + +No :xit, :t, :append, :change or :insert ~ + +These commands are too easily confused with local variable names. +Instead of `:x` or `:xit` you can use `:exit`. +Instead of `:t` you can use `:copy`. + + +Comparators ~ + +The 'ignorecase' option is not used for comparators that use strings. + + +For loop ~ + +Legacy Vim script has some tricks to make a for loop over a list handle +deleting items at the current or previous item. In Vim9 script it just uses +the index, if items are deleted then items in the list will be skipped. +Example legacy script: > + let l = [1, 2, 3, 4] + for i in l + echo i + call remove(l, index(l, i)) + endfor +Would echo: + 1 + 2 + 3 + 4 +In compiled Vim9 script you get: + 1 + 3 +Generally, you should not change the list that is iterated over. Make a copy +first if needed. + + +White space ~ + +Vim9 script enforces proper use of white space. This is no longer allowed: > + var name=234 # Error! + var name= 234 # Error! + var name =234 # Error! +There must be white space before and after the "=": > + var name = 234 # OK +White space must also be put before the # that starts a comment after a +command: > + var name = 234# Error! + var name = 234 # OK + +White space is required around most operators. + +White space is required in a sublist (list slice) around the ":", except at +the start and end: > + otherlist = mylist[v : count] # v:count has a different meaning + otherlist = mylist[:] # make a copy of the List + otherlist = mylist[v :] + otherlist = mylist[: v] + +White space is not allowed: +- Between a function name and the "(": > + Func (arg) # Error! + Func + \ (arg) # Error! + Func + (arg) # Error! + Func(arg) # OK + Func( + arg) # OK + Func( + arg # OK + ) + + +Conditions and expressions ~ + +Conditions and expressions are mostly working like they do in other languages. +Some values are different from legacy Vim script: + value legacy Vim script Vim9 script ~ + 0 falsy falsy + 1 truthy truthy + 99 truthy Error! + "0" falsy Error! + "99" truthy Error! + "text" falsy Error! + +For the "??" operator and when using "!" then there is no error, every value +is either falsy or truthy. This is mostly like JavaScript, except that an +empty list and dict is falsy: + + type truthy when ~ + bool true, v:true or 1 + number non-zero + float non-zero + string non-empty + blob non-empty + list non-empty (different from JavaScript) + dictionary non-empty (different from JavaScript) + func when there is a function name + special true or v:true + job when not NULL + channel when not NULL + class when not NULL + object when not NULL (TODO: when isTrue() returns true) + +The boolean operators "||" and "&&" expect the values to be boolean, zero or +one: > + 1 || false == true + 0 || 1 == true + 0 || false == false + 1 && true == true + 0 && 1 == false + 8 || 0 Error! + 'yes' && 0 Error! + [] || 99 Error! + +When using "!" for inverting, there is no error for using any type and the +result is a boolean. "!!" can be used to turn any value into boolean: > + !'yes' == false + !![] == false + !![1, 2, 3] == true + +When using "`.."` for string concatenation arguments of simple types are +always converted to string: > + 'hello ' .. 123 == 'hello 123' + 'hello ' .. v:true == 'hello true' + +Simple types are string, float, special and bool. For other types |string()| +can be used. + *false* *true* *null* +In Vim9 script one can use "true" for v:true, "false" for v:false and "null" +for v:null. When converting a boolean to a string "false" and "true" are +used, not "v:false" and "v:true" like in legacy script. "v:none" is not +changed, it is only used in JSON and has no equivalent in other languages. + +Indexing a string with [idx] or [idx : idx] uses character indexes instead of +byte indexes. Example: > + echo 'bár'[1] +In legacy script this results in the character 0xc3 (an illegal byte), in Vim9 +script this results in the string 'á'. +A negative index is counting from the end, "[-1]" is the last character. +To exclude the last character use |slice()|. +If the index is out of range then an empty string results. + +In legacy script "++var" and "--var" would be silently accepted and have no +effect. This is an error in Vim9 script. + +Numbers starting with zero are not considered to be octal, only numbers +starting with "0o" are octal: "0o744". |scriptversion-4| + + +What to watch out for ~ + *vim9-gotchas* +Vim9 was designed to be closer to often used programming languages, but at the +same time tries to support the legacy Vim commands. Some compromises had to +be made. Here is a summary of what might be unexpected. + +Ex command ranges need to be prefixed with a colon. > + -> legacy Vim: shifts the previous line to the right + ->func() Vim9: method call in a continuation line + :-> Vim9: shifts the previous line to the right + + %s/a/b legacy Vim: substitute on all lines + x = alongname + % another Vim9: modulo operator in a continuation line + :%s/a/b Vim9: substitute on all lines + 't legacy Vim: jump to mark t + 'text'->func() Vim9: method call + :'t Vim9: jump to mark t + +Some Ex commands can be confused with assignments in Vim9 script: > + g:name = value # assignment + g:pattern:cmd # invalid command - ERROR + :g:pattern:cmd # :global command + +Functions defined with `:def` compile the whole function. Legacy functions +can bail out, and the following lines are not parsed: > + func Maybe() + if !has('feature') + return + endif + use-feature + endfunc +Vim9 functions are compiled as a whole: > + def Maybe() + if !has('feature') + return + endif + use-feature # May give a compilation error + enddef +For a workaround, split it in two functions: > + func Maybe() + if has('feature') + call MaybeInner() + endif + endfunc + if has('feature') + def MaybeInner() + use-feature + enddef + endif +Or put the unsupported code inside an `if` with a constant expression that +evaluates to false: > + def Maybe() + if has('feature') + use-feature + endif + enddef +< *vim9-user-command* +Another side effect of compiling a function is that the presence of a user +command is checked at compile time. If the user command is defined later an +error will result. This works: > + command -nargs=1 MyCommand echom <q-args> + def Works() + MyCommand 123 + enddef +This will give an error for "MyCommand" not being defined: > + def Works() + command -nargs=1 MyCommand echom <q-args> + MyCommand 123 + enddef +A workaround is to invoke the command indirectly with `:execute`: > + def Works() + command -nargs=1 MyCommand echom <q-args> + execute 'MyCommand 123' + enddef + +Note that for unrecognized commands there is no check for "|" and a following +command. This will give an error for missing `endif`: > + def Maybe() + if has('feature') | use-feature | endif + enddef + +Other differences ~ + +Patterns are used like 'magic' is set, unless explicitly overruled. +The 'edcompatible' option value is not used. +The 'gdefault' option value is not used. + + +============================================================================== + +3. New style functions *fast-functions* + +THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + + *:def* +:def[!] {name}([arguments])[: {return-type}] + Define a new function by the name {name}. The body of + the function follows in the next lines, until the + matching `:enddef`. + + When {return-type} is omitted or is "void" the + function is not expected to return anything. + + {arguments} is a sequence of zero or more argument + declarations. There are three forms: + {name}: {type} + {name} = {value} + {name}: {type} = {value} + The first form is a mandatory argument, the caller + must always provide them. + The second and third form are optional arguments. + When the caller omits an argument the {value} is used. + + The function will be compiled into instructions when + called, or when `:disassemble` or `:defcompile` is + used. Syntax and type errors will be produced at that + time. + + It is possible to nest `:def` inside another `:def` or + `:function` up to about 50 levels deep. + + [!] is used as with `:function`. Note that + script-local functions cannot be deleted or redefined + later in Vim9 script. They can only be removed by + reloading the same script. + + *:enddef* +:enddef End of a function defined with `:def`. It should be on + a line by its own. + + +If the script the function is defined in is Vim9 script, then script-local +variables can be accessed without the "s:" prefix. They must be defined +before the function is compiled. If the script the function is defined in is +legacy script, then script-local variables must be accessed with the "s:" +prefix and they do not need to exist (they can be deleted any time). + + *:defc* *:defcompile* +:defc[ompile] Compile functions defined in the current script that + were not compiled yet. + This will report errors found during the compilation. + + *:disa* *:disassemble* +:disa[ssemble] {func} Show the instructions generated for {func}. + This is for debugging and testing. + Note that for command line completion of {func} you + can prepend "s:" to find script-local functions. + +:disa[ssemble]! {func} Like `:disassemble` but with the instructions used for + profiling. + +Limitations ~ + +Local variables will not be visible to string evaluation. For example: > + def MapList(): list<string> + var list = ['aa', 'bb', 'cc', 'dd'] + return range(1, 2)->map('list[v:val]') + enddef + +The map argument is a string expression, which is evaluated without the +function scope. Instead, use a lambda: > + def MapList(): list<string> + var list = ['aa', 'bb', 'cc', 'dd'] + return range(1, 2)->map(( _, v) => list[v]) + enddef + +The same is true for commands that are not compiled, such as `:global`. +For these the backtick expansion can be used. Example: > + def Replace() + var newText = 'blah' + g/pattern/s/^/`=newText`/ + enddef + +============================================================================== + +4. Types *vim9-types* + +THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + +The following builtin types are supported: + bool + number + float + string + blob + list<{type}> + dict<{type}> + job + channel + func + func: {type} + func({type}, ...) + func({type}, ...): {type} + +Not supported yet: + tuple<a: {type}, b: {type}, ...> + +These types can be used in declarations, but no value will have this type: + {type}|{type} {not implemented yet} + void + any + +There is no array type, use list<{type}> instead. For a list constant an +efficient implementation is used that avoids allocating lot of small pieces of +memory. + +A partial and function can be declared in more or less specific ways: +func any kind of function reference, no type + checking for arguments or return value +func: {type} any number and type of arguments with specific + return type +func({type}) function with argument type, does not return + a value +func({type}): {type} function with argument type and return type +func(?{type}) function with type of optional argument, does + not return a value +func(...{type}) function with type of variable number of + arguments, does not return a value +func({type}, ?{type}, ...{type}): {type} + function with: + - type of mandatory argument + - type of optional argument + - type of variable number of arguments + - return type + +If the return type is "void" the function does not return a value. + +The reference can also be a |Partial|, in which case it stores extra arguments +and/or a dictionary, which are not visible to the caller. Since they are +called in the same way the declaration is the same. + +Custom types can be defined with `:type`: > + :type MyList list<string> +Custom types must start with a capital letter, to avoid name clashes with +builtin types added later, similarly to user functions. +{not implemented yet} + +And classes and interfaces can be used as types: > + :class MyClass + :var mine: MyClass + + :interface MyInterface + :var mine: MyInterface + + :class MyTemplate<Targ> + :var mine: MyTemplate<number> + :var mine: MyTemplate<string> + + :class MyInterface<Targ> + :var mine: MyInterface<number> + :var mine: MyInterface<string> +{not implemented yet} + + +Variable types and type casting ~ + *variable-types* +Variables declared in Vim9 script or in a `:def` function have a type, either +specified explicitly or inferred from the initialization. + +Global, buffer, window and tab page variables do not have a specific type, the +value can be changed at any time, possibly changing the type. Therefore, in +compiled code the "any" type is assumed. + +This can be a problem when the "any" type is undesired and the actual type is +expected to always be the same. For example, when declaring a list: > + var l: list<number> = [1, g:two] +At compile time Vim doesn't know the type of "g:two" and the expression type +becomes list<any>. An instruction is generated to check the list type before +doing the assignment, which is a bit inefficient. + *type-casting* +To avoid this, use a type cast: > + var l: list<number> = [1, <number>g:two] +The compiled code will then only check that "g:two" is a number and give an +error if it isn't. This is called type casting. + +The syntax of a type cast is: "<" {type} ">". There cannot be white space +after the "<" or before the ">" (to avoid them being confused with +smaller-than and bigger-than operators). + +The semantics is that, if needed, a runtime type check is performed. The +value is not actually changed. If you need to change the type, e.g. to change +it to a string, use the |string()| function. Or use |str2nr()| to convert a +string to a number. + + +Type inference ~ + *type-inference* +In general: Whenever the type is clear it can be omitted. For example, when +declaring a variable and giving it a value: > + var name = 0 # infers number type + var name = 'hello' # infers string type + +The type of a list and dictionary comes from the common type of the values. +If the values all have the same type, that type is used for the list or +dictionary. If there is a mix of types, the "any" type is used. > + [1, 2, 3] list<number> + ['a', 'b', 'c'] list<string> + [1, 'x', 3] list<any> + + +Stricter type checking ~ + *type-checking* +In legacy Vim script, where a number was expected, a string would be +automatically converted to a number. This was convenient for an actual number +such as "123", but leads to unexpected problems (but no error message) if the +string doesn't start with a number. Quite often this leads to hard-to-find +bugs. + +In Vim9 script this has been made stricter. In most places it works just as +before, if the value used matches the expected type. There will sometimes be +an error, thus breaking backwards compatibility. For example: +- Using a number other than 0 or 1 where a boolean is expected. *E1023* +- Using a string value when setting a number options. +- Using a number where a string is expected. *E1024* + +One consequence is that the item type of a list or dict given to map() must +not change. This will give an error in compiled code: > + map([1, 2, 3], (i, v) => 'item ' .. i) + E1012: Type mismatch; expected list<number> but got list<string> +Instead use |mapnew()|. + +============================================================================== + +5. Namespace, Import and Export + *vim9script* *vim9-export* *vim9-import* + +THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + +A Vim9 script can be written to be imported. This means that everything in +the script is local, unless exported. Those exported items, and only those +items, can then be imported in another script. + +You can cheat by using the global namespace explicitly. We will assume here +that you don't do that. + + +Namespace ~ + *vim9-namespace* +To recognize a file that can be imported the `vim9script` statement must +appear as the first statement in the file. It tells Vim to interpret the +script in its own namespace, instead of the global namespace. If a file +starts with: > + vim9script + var myvar = 'yes' +Then "myvar" will only exist in this file. While without `vim9script` it would +be available as `g:myvar` from any other script and function. + +The variables at the file level are very much like the script-local "s:" +variables in legacy Vim script, but the "s:" is omitted. And they cannot be +deleted. + +In Vim9 script the global "g:" namespace can still be used as before. And the +"w:", "b:" and "t:" namespaces. These have in common that variables are not +declared and they can be deleted. + +A side effect of `:vim9script` is that the 'cpoptions' option is set to the +Vim default value, like with: > + :set cpo&vim +One of the effects is that |line-continuation| is always enabled. +The original value of 'cpoptions' is restored at the end of the script. + + +Export ~ + *:export* *:exp* +Exporting an item can be written as: > + export const EXPORTED_CONST = 1234 + export var someValue = ... + export final someValue = ... + export const someValue = ... + export def MyFunc() ... + export class MyClass ... + +As this suggests, only constants, variables, `:def` functions and classes can +be exported. {classes are not implemented yet} + + *E1042* +`:export` can only be used in Vim9 script, at the script level. + + +Import ~ + *:import* *:imp* *E1094* +The exported items can be imported individually in another Vim9 script: > + import EXPORTED_CONST from "thatscript.vim" + import MyClass from "myclass.vim" + +To import multiple items at the same time: > + import {someValue, MyClass} from "thatscript.vim" + +In case the name is ambiguous, another name can be specified: > + import MyClass as ThatClass from "myclass.vim" + import {someValue, MyClass as ThatClass} from "myclass.vim" + +To import all exported items under a specific identifier: > + import * as That from 'thatscript.vim' + +{not implemented yet: using "This as That"} + +Then you can use "That.EXPORTED_CONST", "That.someValue", etc. You are free +to choose the name "That", but it is highly recommended to use the name of the +script file to avoid confusion. + +`:import` can also be used in legacy Vim script. The imported items still +become script-local, even when the "s:" prefix is not given. + +The script name after `import` can be: +- A relative path, starting "." or "..". This finds a file relative to the + location of the script file itself. This is useful to split up a large + plugin into several files. +- An absolute path, starting with "/" on Unix or "D:/" on MS-Windows. This + will rarely be used. +- A path not being relative or absolute. This will be found in the + "import" subdirectories of 'runtimepath' entries. The name will usually be + longer and unique, to avoid loading the wrong file. + +Once a vim9 script file has been imported, the result is cached and used the +next time the same script is imported. It will not be read again. + *:import-cycle* +The `import` commands are executed when encountered. If that script (directly +or indirectly) imports the current script, then items defined after the +`import` won't be processed yet. Therefore cyclic imports can exist, but may +result in undefined items. + + +Import in an autoload script ~ + +For optimal startup speed, loading scripts should be postponed until they are +actually needed. A recommended mechanism: + +1. In the plugin define user commands, functions and/or mappings that refer to + an autoload script. > + command -nargs=1 SearchForStuff searchfor#Stuff(<f-args>) + +< This goes in .../plugin/anyname.vim. "anyname.vim" can be freely chosen. + +2. In the autoload script do the actual work. You can import items from + other files to split up functionality in appropriate pieces. > + vim9script + import FilterFunc from "../import/someother.vim" + def searchfor#Stuff(arg: string) + var filtered = FilterFunc(arg) + ... +< This goes in .../autoload/searchfor.vim. "searchfor" in the file name + must be exactly the same as the prefix for the function name, that is how + Vim finds the file. + +3. Other functionality, possibly shared between plugins, contains the exported + items and any private items. > + vim9script + var localVar = 'local' + export def FilterFunc(arg: string): string + ... +< This goes in .../import/someother.vim. + +When compiling a `:def` function and a function in an autoload script is +encountered, the script is not loaded until the `:def` function is called. + + +Import in legacy Vim script ~ + +If an `import` statement is used in legacy Vim script, the script-local "s:" +namespace will be used for the imported item, even when "s:" is not specified. + + +============================================================================== + +6. Future work: classes *vim9-classes* + +Above "class" was mentioned a few times, but it has not been implemented yet. +Most of Vim9 script can be created without this functionality, and since +implementing classes is going to be a lot of work, it is left for the future. +For now we'll just make sure classes can be added later. + +Thoughts: +- `class` / `endclass`, everything in one file +- Class names are always CamelCase +- Single constructor +- Single inheritance with `class ThisClass extends BaseClass` +- `abstract class` +- `interface` (Abstract class without any implementation) +- `class SomeClass implements SomeInterface` +- Generics for class: `class <Tkey, Tentry>` +- Generics for function: `def <Tkey> GetLast(key: Tkey)` + +Again, much of this is from TypeScript. + +Some things that look like good additions: +- Use a class as an interface (like Dart) +- Extend a class with methods, using an import (like Dart) + +An important class that will be provided is "Promise". Since Vim is single +threaded, connecting asynchronous operations is a natural way of allowing +plugins to do their work without blocking the user. It's a uniform way to +invoke callbacks and handle timeouts and errors. + +============================================================================== + +9. Rationale *vim9-rationale* + +The :def command ~ + +Plugin writers have asked for much faster Vim script. Investigations have +shown that keeping the existing semantics of function calls make this close to +impossible, because of the overhead involved with calling a function, setting +up the local function scope and executing lines. There are many details that +need to be handled, such as error messages and exceptions. The need to create +a dictionary for a: and l: scopes, the a:000 list and several others add too +much overhead that cannot be avoided. + +Therefore the `:def` method to define a new-style function had to be added, +which allows for a function with different semantics. Most things still work +as before, but some parts do not. A new way to define a function was +considered the best way to separate the legacy style code from Vim9 style code. + +Using "def" to define a function comes from Python. Other languages use +"function" which clashes with legacy Vim script. + + +Type checking ~ + +When compiling lines of Vim commands into instructions as much as possible +should be done at compile time. Postponing it to runtime makes the execution +slower and means mistakes are found only later. For example, when +encountering the "+" character and compiling this into a generic add +instruction, at runtime the instruction would have to inspect the type of the +arguments and decide what kind of addition to do. And when the type is +dictionary throw an error. If the types are known to be numbers then an "add +number" instruction can be used, which is faster. The error can be given at +compile time, no error handling is needed at runtime, since adding two numbers +cannot fail. + +The syntax for types, using <type> for compound types, is similar to Java. It +is easy to understand and widely used. The type names are what were used in +Vim before, with some additions such as "void" and "bool". + + +Removing clutter and weirdness ~ + +Once decided that `:def` functions have different syntax than legacy functions, +we are free to add improvements to make the code more familiar for users who +know popular programming languages. In other words: remove weird things that +only Vim does. + +We can also remove clutter, mainly things that were done to make Vim script +backwards compatible with the good old Vi commands. + +Examples: +- Drop `:call` for calling a function and `:eval` for manipulating data. +- Drop using a leading backslash for line continuation, automatically figure + out where an expression ends. + +However, this does require that some things need to change: +- Comments start with # instead of ", to avoid confusing them with strings. + This is good anyway, it is known from several popular languages. +- Ex command ranges need to be prefixed with a colon, to avoid confusion with + expressions (single quote can be a string or a mark, "/" can be divide or a + search command, etc.). + +Goal is to limit the differences. A good criteria is that when the old syntax +is accidentally used you are very likely to get an error message. + + +Syntax and semantics from popular languages ~ + +Script writers have complained that the Vim script syntax is unexpectedly +different from what they are used to. To reduce this complaint popular +languages are used as an example. At the same time, we do not want to abandon +the well-known parts of legacy Vim script. + +For many things TypeScript is followed. It's a recent language that is +gaining popularity and has similarities with Vim script. It also has a +mix of static typing (a variable always has a known value type) and dynamic +typing (a variable can have different types, this changes at runtime). Since +legacy Vim script is dynamically typed and a lot of existing functionality +(esp. builtin functions) depends on that, while static typing allows for much +faster execution, we need to have this mix in Vim9 script. + +There is no intention to completely match TypeScript syntax and semantics. We +just want to take those parts that we can use for Vim and we expect Vim users +will be happy with. TypeScript is a complex language with its own history, +advantages and disadvantages. To get an idea of the disadvantages read the +book: "JavaScript: The Good Parts". Or find the article "TypeScript: the good +parts" and read the "Things to avoid" section. + +People familiar with other languages (Java, Python, etc.) will also find +things in TypeScript that they do not like or do not understand. We'll try to +avoid those things. + +Specific items from TypeScript we avoid: +- Overloading "+", using it both for addition and string concatenation. This + goes against legacy Vim script and often leads to mistakes. For that reason + we will keep using ".." for string concatenation. Lua also uses ".." this + way. And it allows for conversion to string for more values. +- TypeScript can use an expression like "99 || 'yes'" in a condition, but + cannot assign the value to a boolean. That is inconsistent and can be + annoying. Vim recognizes an expression with && or || and allows using the + result as a bool. TODO: to be reconsidered +- TypeScript considers an empty string as Falsy, but an empty list or dict as + Truthy. That is inconsistent. In Vim an empty list and dict are also + Falsy. +- TypeScript has various "Readonly" types, which have limited usefulness, + since a type cast can remove the immutable nature. Vim locks the value, + which is more flexible, but is only checked at runtime. + + +Declarations ~ + +Legacy Vim script uses `:let` for every assignment, while in Vim9 declarations +are used. That is different, thus it's good to use a different command: +`:var`. This is used in many languages. The semantics might be slightly +different, but it's easily recognized as a declaration. + +Using `:const` for constants is common, but the semantics varies. Some +languages only make the variable immutable, others also make the value +immutable. Since "final" is well known from Java for only making the variable +immutable we decided to use that. And then `:const` can be used for making +both immutable. This was also used in legacy Vim script and the meaning is +almost the same. + +What we end up with is very similar to Dart: > + :var name # mutable variable and value + :final name # immutable variable, mutable value + :const name # immutable variable and value + +Since legacy and Vim9 script will be mixed and global variables will be +shared, optional type checking is desirable. Also, type inference will avoid +the need for specifying the type in many cases. The TypeScript syntax fits +best for adding types to declarations: > + var name: string # string type is specified + ... + name = 'John' + const greeting = 'hello' # string type is inferred + +This is how we put types in a declaration: > + var mylist: list<string> + final mylist: list<string> = ['foo'] + def Func(arg1: number, arg2: string): bool + +Two alternatives were considered: +1. Put the type before the name, like Dart: > + var list<string> mylist + final list<string> mylist = ['foo'] + def Func(number arg1, string arg2) bool +2. Put the type after the variable name, but do not use a colon, like Go: > + var mylist list<string> + final mylist list<string> = ['foo'] + def Func(arg1 number, arg2 string) bool + +The first is more familiar for anyone used to C or Java. The second one +doesn't really have an advantage over the first, so let's discard the second. + +Since we use type inference the type can be left out when it can be inferred +from the value. This means that after `var` we don't know if a type or a name +follows. That makes parsing harder, not only for Vim but also for humans. +Also, it will not be allowed to use a variable name that could be a type name, +using `var string string` is too confusing. + +The chosen syntax, using a colon to separate the name from the type, adds +punctuation, but it actually makes it easier to recognize the parts of a +declaration. + + +Expressions ~ + +Expression evaluation was already close to what other languages are doing. +Some details are unexpected and can be improved. For example a boolean +condition would accept a string, convert it to a number and check if the +number is non-zero. This is unexpected and often leads to mistakes, since +text not starting with a number would be converted to zero, which is +considered false. Thus using a string for a condition would often not give an +error and be considered false. That is confusing. + +In Vim9 type checking is stricter to avoid mistakes. Where a condition is +used, e.g. with the `:if` command and the `||` operator, only boolean-like +values are accepted: + true: `true`, `v:true`, `1`, `0 < 9` + false: `false`, `v:false`, `0`, `0 > 9` +Note that the number zero is false and the number one is true. This is more +permissive than most other languages. It was done because many builtin +functions return these values. + +If you have any type of value and want to use it as a boolean, use the `!!` +operator: + true: !`!'text'`, `!![99]`, `!!{'x': 1}`, `!!99` + false: `!!''`, `!![]`, `!!{}` + +From a language like JavaScript we have this handy construct: > + GetName() || 'unknown' +However, this conflicts with only allowing a boolean for a condition. +Therefore the "??" operator was added: > + GetName() ?? 'unknown' +Here you can explicitly express your intention to use the value as-is and not +result in a boolean. This is called the |falsy-operator|. + + +Import and Export ~ + +A problem of legacy Vim script is that by default all functions and variables +are global. It is possible to make them script-local, but then they are not +available in other scripts. This defies the concept of a package that only +exports selected items and keeps the rest local. + +In Vim9 script a mechanism very similar to the JavaScript import and export +mechanism is supported. It is a variant to the existing `:source` command +that works like one would expect: +- Instead of making everything global by default, everything is script-local, + unless exported. +- When importing a script the symbols that are imported are explicitly listed, + avoiding name conflicts and failures if functionality is added later. +- The mechanism allows for writing a big, long script with a very clear API: + the exported function(s) and class(es). +- By using relative paths loading can be much faster for an import inside of a + package, no need to search many directories. +- Once an import has been used, it can be cached and loading it again can be + avoided. +- The Vim-specific use of "s:" to make things script-local can be dropped. + +When sourcing a Vim9 script from a legacy script, only the items defined +globally can be used, not the exported items. Alternatives considered: +- All the exported items become available as script-local items. This makes + it uncontrollable what items get defined and likely soon leads to trouble. +- Use the exported items and make them global. Disadvantage is that it's then + not possible to avoid name clashes in the global namespace. +- Completely disallow sourcing a Vim9 script, require using `:import`. That + makes it difficult to use scripts for testing, or sourcing them from the + command line to try them out. +Note that you can also use `:import` in legacy Vim script, see above. + + +Compiling functions early ~ + +Functions are compiled when called or when `:defcompile` is used. Why not +compile them early, so that syntax and type errors are reported early? + +The functions can't be compiled right away when encountered, because there may +be forward references to functions defined later. Consider defining functions +A, B and C, where A calls B, B calls C, and C calls A again. It's impossible +to reorder the functions to avoid forward references. + +An alternative would be to first scan through the file to locate items and +figure out their type, so that forward references are found, and only then +execute the script and compile the functions. This means the script has to be +parsed twice, which is slower, and some conditions at the script level, such +as checking if a feature is supported, are hard to use. An attempt was made +to see if it works, but it turned out to be impossible to make work nicely. + +It would be possible to compile all the functions at the end of the script. +The drawback is that if a function never gets called, the overhead of +compiling it counts anyway. Since startup speed is very important, in most +cases it's better to do it later and accept that syntax and type errors are +only reported then. In case these errors should be found early, e.g. when +testing, the `:defcompile` command will help out. + + +Why not use an embedded language? ~ + +Vim supports interfaces to Perl, Python, Lua, Tcl and a few others. But +these interfaces have never become widely used, for various reasons. When +Vim9 was designed a decision was made to make these interfaces lower priority +and concentrate on Vim script. + +Still, plugin writers may find other languages more familiar, want to use +existing libraries or see a performance benefit. We encourage plugin authors +to write code in any language and run it as an external tool, using jobs and +channels. We can try to make this easier somehow. + +Using an external tool also has disadvantages. An alternative is to convert +the tool into Vim script. For that to be possible without too much +translation, and keeping the code fast at the same time, the constructs of the +tool need to be supported. Since most languages support classes the lack of +support for classes in Vim is then a problem. + + +Classes ~ + +Vim supports a kind-of object oriented programming by adding methods to a +dictionary. With some care this can be made to work, but it does not look +like real classes. On top of that, it's quite slow, because of the use of +dictionaries. + +The support of classes in Vim9 script is a "minimal common functionality" of +class support in most languages. It works much like Java, which is the most +popular programming language. + + + + vim:tw=78:ts=8:noet:ft=help:norl: |