18a7d6542SBram Moolenaar 28a7d6542SBram Moolenaar 38a7d6542SBram Moolenaar# What is Vim9? 48a7d6542SBram Moolenaar 58a7d6542SBram MoolenaarThis is an experimental side of [Vim](https://github.com/vim/vim). 68a7d6542SBram MoolenaarIt explores ways of making Vim script faster and better. 78a7d6542SBram Moolenaar 8e7b1ea02SBram MoolenaarWARNING: The Vim9 script features are still under development, anything can 9e7b1ea02SBram Moolenaarbreak! 108a7d6542SBram Moolenaar 118a7d6542SBram Moolenaar# Why Vim9? 128a7d6542SBram Moolenaar 138a7d6542SBram Moolenaar## 1. FASTER VIM SCRIPT 148a7d6542SBram Moolenaar 158a7d6542SBram MoolenaarThe third item on the poll results of 2018, after popup windows and text 168a7d6542SBram Moolenaarproperties, is faster Vim script. So how do we do that? 178a7d6542SBram Moolenaar 188a7d6542SBram MoolenaarI have been throwing some ideas around, and soon came to the conclusion 198a7d6542SBram Moolenaarthat the current way functions are called and executed, with 208a7d6542SBram Moolenaardictionaries for the arguments and local variables, is never going to be 218a7d6542SBram Moolenaarvery fast. We're lucky if we can make it twice as fast. The overhead 228a7d6542SBram Moolenaarof a function call and executing every line is just too high. 238a7d6542SBram Moolenaar 248a7d6542SBram MoolenaarSo what then? We can only make something fast by having a new way of 258a7d6542SBram Moolenaardefining a function, with similar but different properties of the old 268a7d6542SBram Moolenaarway: 278a7d6542SBram Moolenaar* Arguments are only available by name, not through the a: dictionary or 288a7d6542SBram Moolenaar the a:000 list. 298a7d6542SBram Moolenaar* Local variables are not available in an l: dictionary. 308a7d6542SBram Moolenaar* A few more things that slow us down, such as exception handling details. 318a7d6542SBram Moolenaar 328a7d6542SBram MoolenaarI Implemented a "proof of concept" and measured the time to run a simple 338a7d6542SBram Moolenaarfor loop with an addition (Justin used this example in his presentation, 348a7d6542SBram Moolenaarfull code is below): 358a7d6542SBram Moolenaar 368a7d6542SBram Moolenaar``` vim 378a7d6542SBram Moolenaar let sum = 0 388a7d6542SBram Moolenaar for i in range(1, 2999999) 398a7d6542SBram Moolenaar let sum += i 408a7d6542SBram Moolenaar endfor 418a7d6542SBram Moolenaar``` 428a7d6542SBram Moolenaar 438a7d6542SBram Moolenaar| how | time in sec | 448a7d6542SBram Moolenaar| --------| -------- | 458a7d6542SBram Moolenaar| Vim old | 5.018541 | 468a7d6542SBram Moolenaar| Python | 0.369598 | 478a7d6542SBram Moolenaar| Lua | 0.078817 | 48*53f7fcccSBram Moolenaar| LuaJit | 0.004245 | 498a7d6542SBram Moolenaar| Vim new | 0.073595 | 508a7d6542SBram Moolenaar 518a7d6542SBram MoolenaarThat looks very promising! It's just one example, but it shows how much 528a7d6542SBram Moolenaarwe can gain, and also that Vim script can be faster than builtin 538a7d6542SBram Moolenaarinterfaces. 548a7d6542SBram Moolenaar 55*53f7fcccSBram MoolenaarLuaJit is much faster at Lua-only instructions. In practice the script would 56*53f7fcccSBram Moolenaarnot do something useless as counting but change the text. For example, 57*53f7fcccSBram Moolenaarreindent all the lines: 588a7d6542SBram Moolenaar 598a7d6542SBram Moolenaar``` vim 608a7d6542SBram Moolenaar let totallen = 0 618a7d6542SBram Moolenaar for i in range(1, 100000) 628a7d6542SBram Moolenaar call setline(i, ' ' .. getline(i)) 638a7d6542SBram Moolenaar let totallen += len(getline(i)) 648a7d6542SBram Moolenaar endfor 658a7d6542SBram Moolenaar``` 668a7d6542SBram Moolenaar 678a7d6542SBram Moolenaar| how | time in sec | 688a7d6542SBram Moolenaar| --------| -------- | 69*53f7fcccSBram Moolenaar| Vim old | 0.578598 | 70*53f7fcccSBram Moolenaar| Python | 0.152040 | 71*53f7fcccSBram Moolenaar| Lua | 0.164917 | 72*53f7fcccSBram Moolenaar| LuaJit | 0.128400 | 73*53f7fcccSBram Moolenaar| Vim new | 0.079692 | 74*53f7fcccSBram Moolenaar 75*53f7fcccSBram Moolenaar[These times were measured on a different system by Dominique Pelle] 768a7d6542SBram Moolenaar 778a7d6542SBram MoolenaarThe differences are smaller, but Vim 9 script is clearly the fastest. 78*53f7fcccSBram MoolenaarUsing LuaJIT is only a little bit faster than plain Lua here, clearly the call 79*53f7fcccSBram Moolenaarback to the Vim code is costly. 808a7d6542SBram Moolenaar 818a7d6542SBram MoolenaarHow does Vim9 script work? The function is first compiled into a sequence of 828a7d6542SBram Moolenaarinstructions. Each instruction has one or two parameters and a stack is 838a7d6542SBram Moolenaarused to store intermediate results. Local variables are also on the 848a7d6542SBram Moolenaarstack, space is reserved during compilation. This is a fairly normal 858a7d6542SBram Moolenaarway of compilation into an intermediate format, specialized for Vim, 868a7d6542SBram Moolenaare.g. each stack item is a typeval_T. And one of the instructions is 878a7d6542SBram Moolenaar"execute Ex command", for commands that are not compiled. 888a7d6542SBram Moolenaar 898a7d6542SBram Moolenaar 904f4d51a9SBram Moolenaar## 2. DEPRIORITIZE INTERFACES 918a7d6542SBram Moolenaar 928a7d6542SBram MoolenaarAttempts have been made to implement functionality with built-in script 938a7d6542SBram Moolenaarlanguages such as Python, Perl, Lua, Tcl and Ruby. This never gained much 948a7d6542SBram Moolenaarfoothold, for various reasons. 958a7d6542SBram Moolenaar 968a7d6542SBram MoolenaarInstead of using script language support in Vim: 978a7d6542SBram Moolenaar* Encourage implementing external tools in any language and communicate 988a7d6542SBram Moolenaar with them. The job and channel support already makes this possible. 998a7d6542SBram Moolenaar Really any language can be used, also Java and Go, which are not 1008a7d6542SBram Moolenaar available built-in. 101e7b1ea02SBram Moolenaar* No priority for the built-in language interfaces. They will have to be kept 102e7b1ea02SBram Moolenaar for backwards compatibility, but many users won't need a Vim build with these 103e7b1ea02SBram Moolenaar interfaces. 1048a7d6542SBram Moolenaar* Improve the Vim script language, it is used to communicate with the external 1058a7d6542SBram Moolenaar tool and implements the Vim side of the interface. Also, it can be used when 1068a7d6542SBram Moolenaar an external tool is undesired. 1078a7d6542SBram Moolenaar 1083d1cde8aSBram MoolenaarAltogether this creates a clear situation: Vim with the +eval feature 1098a7d6542SBram Moolenaarwill be sufficient for most plugins, while some plugins require 1108a7d6542SBram Moolenaarinstalling a tool that can be written in any language. No confusion 1118a7d6542SBram Moolenaarabout having Vim but the plugin not working because some specific 1128a7d6542SBram Moolenaarlanguage is missing. This is a good long term goal. 1138a7d6542SBram Moolenaar 1148a7d6542SBram MoolenaarRationale: Why is it better to run a tool separately from Vim than using a 1158a7d6542SBram Moolenaarbuilt-in interface and interpreter? Take for example something that is 1168a7d6542SBram Moolenaarwritten in Python: 1178a7d6542SBram Moolenaar* The built-in interface uses the embedded python interpreter. This is less 1188a7d6542SBram Moolenaar well maintained than the python command. Building Vim with it requires 1198a7d6542SBram Moolenaar installing developer packages. If loaded dynamically there can be a version 1208a7d6542SBram Moolenaar mismatch. 1218a7d6542SBram Moolenaar* When running the tool externally the standard python command can be used, 1228a7d6542SBram Moolenaar which is quite often available by default or can be easily installed. 1238a7d6542SBram Moolenaar* The built-in interface has an API that is unique for Vim with Python. This is 1248a7d6542SBram Moolenaar an extra API to learn. 1258a7d6542SBram Moolenaar* A .py file can be compiled into a .pyc file and execute much faster. 1268a7d6542SBram Moolenaar* Inside Vim multi-threading can cause problems, since the Vim core is single 1278a7d6542SBram Moolenaar threaded. In an external tool there are no such problems. 1288a7d6542SBram Moolenaar* The Vim part is written in .vim files, the Python part is in .py files, this 1298a7d6542SBram Moolenaar is nicely separated. 1308a7d6542SBram Moolenaar* Disadvantage: An interface needs to be made between Vim and Python. 1318a7d6542SBram Moolenaar JSON is available for this, and it's fairly easy to use. But it still 1328a7d6542SBram Moolenaar requires implementing asynchronous communication. 1338a7d6542SBram Moolenaar 1348a7d6542SBram Moolenaar 1358a7d6542SBram Moolenaar## 3. BETTER VIM SCRIPT 1368a7d6542SBram Moolenaar 1378a7d6542SBram MoolenaarTo make Vim faster a new way of defining a function needs to be added. 1388a7d6542SBram MoolenaarWhile we are doing that, since the lines in this function won't be fully 1398a7d6542SBram Moolenaarbackwards compatible anyway, we can also make Vim script easier to use. 1408a7d6542SBram MoolenaarIn other words: "less weird". Making it work more like modern 1418a7d6542SBram Moolenaarprogramming languages will help. No surprises. 1428a7d6542SBram Moolenaar 1438a7d6542SBram MoolenaarA good example is how in a function the arguments are prefixed with 1448a7d6542SBram Moolenaar"a:". No other language I know does that, so let's drop it. 1458a7d6542SBram Moolenaar 1468a7d6542SBram MoolenaarTaking this one step further is also dropping "s:" for script-local variables; 1478a7d6542SBram Moolenaareverything at the script level is script-local by default. Since this is not 1488a7d6542SBram Moolenaarbackwards compatible it requires a new script style: Vim9 script! 1498a7d6542SBram Moolenaar 150e7b1ea02SBram MoolenaarTo avoid having more variations, the syntax inside a compiled function is the 151e7b1ea02SBram Moolenaarsame as in Vim9 script. Thus you have legacy syntax and Vim9 syntax. 152e7b1ea02SBram Moolenaar 1538a7d6542SBram MoolenaarIt should be possible to convert code from other languages to Vim 1548a7d6542SBram Moolenaarscript. We can add functionality to make this easier. This still needs 1558a7d6542SBram Moolenaarto be discussed, but we can consider adding type checking and a simple 1568a7d6542SBram Moolenaarform of classes. If you look at JavaScript for example, it has gone 1578a7d6542SBram Moolenaarthrough these stages over time, adding real class support and now 1588a7d6542SBram MoolenaarTypeScript adds type checking. But we'll have to see how much of that 1598a7d6542SBram Moolenaarwe actually want to include in Vim script. Ideally a conversion tool 1608a7d6542SBram Moolenaarcan take Python, JavaScript or TypeScript code and convert it to Vim 1618a7d6542SBram Moolenaarscript, with only some things that cannot be converted. 1628a7d6542SBram Moolenaar 1638a7d6542SBram MoolenaarVim script won't work the same as any specific language, but we can use 1648a7d6542SBram Moolenaarmechanisms that are commonly known, ideally with the same syntax. One 1658a7d6542SBram Moolenaarthing I have been thinking of is assignments without ":let". I often 1668a7d6542SBram Moolenaarmake that mistake (after writing JavaScript especially). I think it is 1678a7d6542SBram Moolenaarpossible, if we make local variables shadow commands. That should be OK, 1688a7d6542SBram Moolenaarif you shadow a command you want to use, just rename the variable. 1694466ad6bSBram MoolenaarUsing "var" and "const" to declare a variable, like in JavaScript and 1708a7d6542SBram MoolenaarTypeScript, can work: 1718a7d6542SBram Moolenaar 1728a7d6542SBram Moolenaar 1738a7d6542SBram Moolenaar``` vim 1748a7d6542SBram Moolenaardef MyFunction(arg: number): number 1754466ad6bSBram Moolenaar var local = 1 1764466ad6bSBram Moolenaar var todo = arg 1778a7d6542SBram Moolenaar const ADD = 88 1788a7d6542SBram Moolenaar while todo > 0 1798a7d6542SBram Moolenaar local += ADD 1804466ad6bSBram Moolenaar todo -= 1 1818a7d6542SBram Moolenaar endwhile 1828a7d6542SBram Moolenaar return local 1838a7d6542SBram Moolenaarenddef 1848a7d6542SBram Moolenaar``` 1858a7d6542SBram Moolenaar 1868a7d6542SBram MoolenaarThe similarity with JavaScript/TypeScript can also be used for dependencies 1878a7d6542SBram Moolenaarbetween files. Vim currently uses the `:source` command, which has several 1888a7d6542SBram Moolenaardisadvantages: 1898a7d6542SBram Moolenaar* In the sourced script, is not clear what it provides. By default all 1908a7d6542SBram Moolenaar functions are global and can be used elsewhere. 1918a7d6542SBram Moolenaar* In a script that sources other scripts, it is not clear what function comes 1928a7d6542SBram Moolenaar from what sourced script. Finding the implementation is a hassle. 1938a7d6542SBram Moolenaar* Prevention of loading the whole script twice must be manually implemented. 1948a7d6542SBram Moolenaar 1958a7d6542SBram MoolenaarWe can use the `:import` and `:export` commands from the JavaScript standard to 1968a7d6542SBram Moolenaarmake this much better. For example, in script "myfunction.vim" define a 1978a7d6542SBram Moolenaarfunction and export it: 1988a7d6542SBram Moolenaar 1998a7d6542SBram Moolenaar``` vim 2008a7d6542SBram Moolenaarvim9script " Vim9 script syntax used here 2018a7d6542SBram Moolenaar 2024466ad6bSBram Moolenaarvar local = 'local variable is not exported, script-local' 2038a7d6542SBram Moolenaar 2048a7d6542SBram Moolenaarexport def MyFunction() " exported function 2058a7d6542SBram Moolenaar... 2068a7d6542SBram Moolenaar 2078a7d6542SBram Moolenaardef LocalFunction() " not exported, script-local 2088a7d6542SBram Moolenaar... 2098a7d6542SBram Moolenaar``` 2108a7d6542SBram Moolenaar 2118a7d6542SBram MoolenaarAnd in another script import the function: 2128a7d6542SBram Moolenaar 2138a7d6542SBram Moolenaar``` vim 2148a7d6542SBram Moolenaarvim9script " Vim9 script syntax used here 2158a7d6542SBram Moolenaar 2168a7d6542SBram Moolenaarimport MyFunction from 'myfunction.vim' 2178a7d6542SBram Moolenaar``` 2188a7d6542SBram Moolenaar 2198a7d6542SBram MoolenaarThis looks like JavaScript/TypeScript, thus many users will understand the 2208a7d6542SBram Moolenaarsyntax. 2218a7d6542SBram Moolenaar 2228a7d6542SBram MoolenaarThese are ideas, this will take time to design, discuss and implement. 2238a7d6542SBram MoolenaarEventually this will lead to Vim 9! 2248a7d6542SBram Moolenaar 2258a7d6542SBram Moolenaar 2268a7d6542SBram Moolenaar## Code for sum time measurements 2278a7d6542SBram Moolenaar 2288a7d6542SBram MoolenaarVim was build with -O2. 2298a7d6542SBram Moolenaar 2308a7d6542SBram Moolenaar``` vim 2318a7d6542SBram Moolenaarfunc VimOld() 2328a7d6542SBram Moolenaar let sum = 0 2338a7d6542SBram Moolenaar for i in range(1, 2999999) 2348a7d6542SBram Moolenaar let sum += i 2358a7d6542SBram Moolenaar endfor 2368a7d6542SBram Moolenaar return sum 2378a7d6542SBram Moolenaarendfunc 2388a7d6542SBram Moolenaar 2398a7d6542SBram Moolenaarfunc Python() 2408a7d6542SBram Moolenaar py3 << END 2418a7d6542SBram Moolenaarsum = 0 2428a7d6542SBram Moolenaarfor i in range(1, 3000000): 2438a7d6542SBram Moolenaar sum += i 2448a7d6542SBram MoolenaarEND 2458a7d6542SBram Moolenaar return py3eval('sum') 2468a7d6542SBram Moolenaarendfunc 2478a7d6542SBram Moolenaar 2488a7d6542SBram Moolenaarfunc Lua() 2498a7d6542SBram Moolenaar lua << END 2508a7d6542SBram Moolenaar sum = 0 2518a7d6542SBram Moolenaar for i = 1, 2999999 do 2528a7d6542SBram Moolenaar sum = sum + i 2538a7d6542SBram Moolenaar end 2548a7d6542SBram MoolenaarEND 2558a7d6542SBram Moolenaar return luaeval('sum') 2568a7d6542SBram Moolenaarendfunc 2578a7d6542SBram Moolenaar 2584466ad6bSBram Moolenaardef VimNew(): number 2594466ad6bSBram Moolenaar var sum = 0 2608a7d6542SBram Moolenaar for i in range(1, 2999999) 2614466ad6bSBram Moolenaar sum += i 2628a7d6542SBram Moolenaar endfor 2638a7d6542SBram Moolenaar return sum 2648a7d6542SBram Moolenaarenddef 2658a7d6542SBram Moolenaar 2668a7d6542SBram Moolenaarlet start = reltime() 2678a7d6542SBram Moolenaarecho VimOld() 2688a7d6542SBram Moolenaarecho 'Vim old: ' .. reltimestr(reltime(start)) 2698a7d6542SBram Moolenaar 2708a7d6542SBram Moolenaarlet start = reltime() 2718a7d6542SBram Moolenaarecho Python() 2728a7d6542SBram Moolenaarecho 'Python: ' .. reltimestr(reltime(start)) 2738a7d6542SBram Moolenaar 2748a7d6542SBram Moolenaarlet start = reltime() 2758a7d6542SBram Moolenaarecho Lua() 2768a7d6542SBram Moolenaarecho 'Lua: ' .. reltimestr(reltime(start)) 2778a7d6542SBram Moolenaar 2788a7d6542SBram Moolenaarlet start = reltime() 2798a7d6542SBram Moolenaarecho VimNew() 2808a7d6542SBram Moolenaarecho 'Vim new: ' .. reltimestr(reltime(start)) 2818a7d6542SBram Moolenaar``` 2828a7d6542SBram Moolenaar 2838a7d6542SBram Moolenaar## Code for indent time measurements 2848a7d6542SBram Moolenaar 2858a7d6542SBram Moolenaar``` vim 2868a7d6542SBram Moolenaardef VimNew(): number 2874466ad6bSBram Moolenaar var totallen = 0 2888a7d6542SBram Moolenaar for i in range(1, 100000) 2898a7d6542SBram Moolenaar setline(i, ' ' .. getline(i)) 2908a7d6542SBram Moolenaar totallen += len(getline(i)) 2918a7d6542SBram Moolenaar endfor 2928a7d6542SBram Moolenaar return totallen 2938a7d6542SBram Moolenaarenddef 2948a7d6542SBram Moolenaar 2958a7d6542SBram Moolenaarfunc VimOld() 2968a7d6542SBram Moolenaar let totallen = 0 2978a7d6542SBram Moolenaar for i in range(1, 100000) 2988a7d6542SBram Moolenaar call setline(i, ' ' .. getline(i)) 2998a7d6542SBram Moolenaar let totallen += len(getline(i)) 3008a7d6542SBram Moolenaar endfor 3018a7d6542SBram Moolenaar return totallen 3028a7d6542SBram Moolenaarendfunc 3038a7d6542SBram Moolenaar 3048a7d6542SBram Moolenaarfunc Lua() 3058a7d6542SBram Moolenaar lua << END 3068a7d6542SBram Moolenaar b = vim.buffer() 3078a7d6542SBram Moolenaar totallen = 0 3088a7d6542SBram Moolenaar for i = 1, 100000 do 3098a7d6542SBram Moolenaar b[i] = " " .. b[i] 3108a7d6542SBram Moolenaar totallen = totallen + string.len(b[i]) 3118a7d6542SBram Moolenaar end 3128a7d6542SBram MoolenaarEND 3138a7d6542SBram Moolenaar return luaeval('totallen') 3148a7d6542SBram Moolenaarendfunc 3158a7d6542SBram Moolenaar 3168a7d6542SBram Moolenaarfunc Python() 3178a7d6542SBram Moolenaar py3 << END 3188a7d6542SBram Moolenaarcb = vim.current.buffer 3198a7d6542SBram Moolenaartotallen = 0 3208a7d6542SBram Moolenaarfor i in range(0, 100000): 3218a7d6542SBram Moolenaar cb[i] = ' ' + cb[i] 3228a7d6542SBram Moolenaar totallen += len(cb[i]) 3238a7d6542SBram MoolenaarEND 3248a7d6542SBram Moolenaar return py3eval('totallen') 3258a7d6542SBram Moolenaarendfunc 3268a7d6542SBram Moolenaar 3278a7d6542SBram Moolenaarnew 3288a7d6542SBram Moolenaarcall setline(1, range(100000)) 3298a7d6542SBram Moolenaarlet start = reltime() 3308a7d6542SBram Moolenaarecho VimOld() 3318a7d6542SBram Moolenaarecho 'Vim old: ' .. reltimestr(reltime(start)) 3328a7d6542SBram Moolenaarbwipe! 3338a7d6542SBram Moolenaar 3348a7d6542SBram Moolenaarnew 3358a7d6542SBram Moolenaarcall setline(1, range(100000)) 3368a7d6542SBram Moolenaarlet start = reltime() 3378a7d6542SBram Moolenaarecho Python() 3388a7d6542SBram Moolenaarecho 'Python: ' .. reltimestr(reltime(start)) 3398a7d6542SBram Moolenaarbwipe! 3408a7d6542SBram Moolenaar 3418a7d6542SBram Moolenaarnew 3428a7d6542SBram Moolenaarcall setline(1, range(100000)) 3438a7d6542SBram Moolenaarlet start = reltime() 3448a7d6542SBram Moolenaarecho Lua() 3458a7d6542SBram Moolenaarecho 'Lua: ' .. reltimestr(reltime(start)) 3468a7d6542SBram Moolenaarbwipe! 3478a7d6542SBram Moolenaar 3488a7d6542SBram Moolenaarnew 3498a7d6542SBram Moolenaarcall setline(1, range(100000)) 3508a7d6542SBram Moolenaarlet start = reltime() 3518a7d6542SBram Moolenaarecho VimNew() 3528a7d6542SBram Moolenaarecho 'Vim new: ' .. reltimestr(reltime(start)) 3538a7d6542SBram Moolenaarbwipe! 3548a7d6542SBram Moolenaar``` 355