blob: 19a37dca5e3a15c7ef682a416dc54253c1223ae3 [file] [log] [blame]
Christian Brabandt83d74402025-03-19 21:55:59 +01001*usr_52.txt* For Vim version 9.1. Last change: 2025 Mar 17
Bram Moolenaar65e0d772020-06-14 17:29:55 +02002
3 VIM USER MANUAL - by Bram Moolenaar
4
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +01005 Write larger plugins
Bram Moolenaar65e0d772020-06-14 17:29:55 +02006
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +01007When plugins do more than simple things, they tend to grow big. This file
8explains how to make sure they still load fast and how to split them up in
Bram Moolenaar016188f2022-06-06 20:52:59 +01009smaller parts.
Bram Moolenaar65e0d772020-06-14 17:29:55 +020010
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +010011|52.1| Export and import
12|52.2| Autoloading
13|52.3| Autoloading without import/export
14|52.4| Other mechanisms to use
15|52.5| Using a Vim9 script from legacy script
Christian Brabandt015c84c2024-10-07 21:48:32 +020016|52.6| Vim9 examples: comment and highlight-yank plugin
Bram Moolenaar65e0d772020-06-14 17:29:55 +020017
18 Next chapter: |usr_90.txt| Installing Vim
Bram Moolenaar30ab04e2022-05-14 13:33:50 +010019 Previous chapter: |usr_51.txt| Create a plugin
Bram Moolenaar65e0d772020-06-14 17:29:55 +020020Table of contents: |usr_toc.txt|
21
22==============================================================================
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +010023*52.1* Export and import
Bram Moolenaar65e0d772020-06-14 17:29:55 +020024
25Vim9 script was designed to make it easier to write large Vim scripts. It
26looks more like other script languages, especially Typescript. Also,
27functions are compiled into instructions that can be executed quickly. This
28makes Vim9 script a lot faster, up to a 100 times.
29
30The basic idea is that a script file has items that are private, only used
Bram Moolenaar016188f2022-06-06 20:52:59 +010031inside the script file, and items that are exported, which can be used by
32scripts that import them. That makes very clear what is defined where.
Bram Moolenaar65e0d772020-06-14 17:29:55 +020033
34Let's start with an example, a script that exports one function and has one
35private function: >
36
Bram Moolenaar016188f2022-06-06 20:52:59 +010037 vim9script
Bram Moolenaar65e0d772020-06-14 17:29:55 +020038
Bram Moolenaar016188f2022-06-06 20:52:59 +010039 export def GetMessage(count: string): string
40 var nr = str2nr(count)
41 var result = $'To {nr} we say '
42 result ..= GetReply(nr)
Bram Moolenaar65e0d772020-06-14 17:29:55 +020043 return result
44 enddef
45
Bram Moolenaar016188f2022-06-06 20:52:59 +010046 def GetReply(nr: number): string
47 if nr == 42
Bram Moolenaar65e0d772020-06-14 17:29:55 +020048 return 'yes'
jbm950f5c8f522024-05-16 13:53:39 -040049 elseif nr == 22
Bram Moolenaar016188f2022-06-06 20:52:59 +010050 return 'maybe'
Bram Moolenaar65e0d772020-06-14 17:29:55 +020051 else
52 return 'no'
53 endif
54 enddef
55
Bram Moolenaar016188f2022-06-06 20:52:59 +010056The `vim9script` command is required, `export` only works in a |Vim9| script.
Bram Moolenaar65e0d772020-06-14 17:29:55 +020057
Bram Moolenaar016188f2022-06-06 20:52:59 +010058The `export def GetMessage(...` line starts with `export`, meaning that this
59function can be called by other scripts. The line `def GetReply(...` does not
60start with `export`, this is a script-local function, it can only be used
61inside this script file.
Bram Moolenaar65e0d772020-06-14 17:29:55 +020062
Bram Moolenaar016188f2022-06-06 20:52:59 +010063Now about the script where this is imported. In this example we use this
64layout, which works well for a plugin below the "pack" directory:
65 .../plugin/theplugin.vim
66 .../lib/getmessage.vim
Bram Moolenaar65e0d772020-06-14 17:29:55 +020067
Bram Moolenaar016188f2022-06-06 20:52:59 +010068Assuming the "..." directory has been added to 'runtimepath', Vim will look
69for plugins in the "plugin" directory and source "theplugin.vim". Vim does
70not recognize the "lib" directory, you can put any scripts there.
71
72The above script that exports GetMessage() goes in lib/getmessage.vim. The
73GetMessage() function is used in plugin/theplugin.vim: >
74
75 vim9script
76
77 import "../lib/getmessage.vim"
78 command -nargs=1 ShowMessage echomsg getmessage.GetMessage(<f-args>)
79
80The `import` command uses a relative path, it starts with "../", which means
81to go one directory up. For other kinds of paths see the `:import` command.
82
83How we can try out the command that the plugin provides: >
84 ShowMessage 1
85< To 1 we say no ~
86>
87 ShowMessage 22
88< To 22 we say maybe ~
89
90Notice that the function GetMessage() is prefixed with the imported script
91name "getmessage". That way, for every imported function used, you know what
92script it was imported from. If you import several scripts each of them could
93define a GetMessage() function: >
94
95 vim9script
96
97 import "../lib/getmessage.vim"
98 import "../lib/getother.vim"
99 command -nargs=1 ShowMessage echomsg getmessage.GetMessage(<f-args>)
100 command -nargs=1 ShowOther echomsg getother.GetMessage(<f-args>)
101
102If the imported script name is long or you use it in many places, you can
103shorten it by adding an "as" argument: >
104 import "../lib/getmessage.vim" as msg
105 command -nargs=1 ShowMessage echomsg msg.GetMessage(<f-args>)
106
107
108RELOADING
109
110One thing to keep in mind: the imported "lib/getmessage.vim" script will be
111sourced only once. When it is imported a second time sourcing it will be
112skipped, since the items in it have already been created. It does not matter
113if this import command is in another script, or in the same script that is
114sourced again.
115
116This is efficient when using a plugin, but when still developing a plugin it
117means that changing "lib/getmessage.vim" after it has been imported will have
118no effect. You need to quit Vim and start it again. (Rationale: the items
119defined in the script could be used in a compiled function, sourcing the
120script again may break those functions).
121
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100122
123USING GLOBALS
124
125Sometimes you will want to use global variables or functions, so that they can
126be used anywhere. A good example is a global variable that passes a
127preference to a plugin. To avoid other scripts using the same name, use a
128prefix that is very unlikely to be used elsewhere. For example, if you have a
129"mytags" plugin, you could use: >
130
131 g:mytags_location = '$HOME/project'
132 g:mytags_style = 'fast'
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200133
134==============================================================================
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100135*52.2* Autoloading
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200136
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100137After splitting your large script into pieces, all the lines will still be
138loaded and executed the moment the script is used. Every `import` loads the
139imported script to find the items defined there. Although that is good for
140finding errors early, it also takes time. Which is wasted if the
141functionality is not often used.
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200142
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100143Instead of having `import` load the script immediately, it can be postponed
Bram Moolenaar016188f2022-06-06 20:52:59 +0100144until needed. Using the example above, only one change needs to be made in
145the plugin/theplugin.vim script: >
146 import autoload "../lib/getmessage.vim"
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200147
Bram Moolenaar016188f2022-06-06 20:52:59 +0100148Nothing in the rest of the script needs to change. However, the types will
149not be checked. Not even the existence of the GetMessage() function is
150checked until it is used. You will have to decide what is more important for
151your script: fast startup or getting errors early. You can also add the
152"autoload" argument later, after you have checked everything works.
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200153
Bram Moolenaar016188f2022-06-06 20:52:59 +0100154
155AUTOLOAD DIRECTORY
156
157Another form is to use autoload with a script name that is not an absolute or
158relative path: >
Christian Brabandtb7258732024-05-12 19:04:47 +0200159 import autoload "monthlib.vim"
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200160
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100161This will search for the script "monthlib.vim" in the autoload directories of
Bram Moolenaar016188f2022-06-06 20:52:59 +0100162'runtimepath'. With Unix one of the directories often is "~/.vim/autoload".
Bram Moolenaar3c053a12022-10-16 13:11:12 +0100163It will also search under 'packpath', under "start".
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100164
Bram Moolenaar016188f2022-06-06 20:52:59 +0100165The main advantage of this is that this script can be easily shared with other
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100166scripts. You do need to make sure that the script name is unique, since Vim
167will search all the "autoload" directories in 'runtimepath', and if you are
Bram Moolenaar016188f2022-06-06 20:52:59 +0100168using several plugins with a plugin manager, it may add a directory to
169'runtimepath', each of which might have an "autoload" directory.
170
171Without autoload: >
172 import "monthlib.vim"
173
174Vim will search for the script "monthlib.vim" in the import directories of
175'runtimepath'. Note that in this case adding or removing "autoload" changes
176where the script is found. With a relative or absolute path the location does
177not change.
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100178
179==============================================================================
180*52.3* Autoloading without import/export
181
182 *write-library-script*
183A mechanism from before import/export is still useful and some users may find
184it a bit simpler. The idea is that you call a function with a special name.
185That function is then in an autoload script. We will call that one script a
186library script.
187
Bram Moolenaard592deb2022-06-17 15:42:40 +0100188The autoload mechanism is based on a function name that has "#" characters: >
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100189
190 mylib#myfunction(arg)
191
192Vim will recognize the function name by the embedded "#" character and when
193it is not defined yet search for the script "autoload/mylib.vim" in
194'runtimepath'. That script must define the "mylib#myfunction()" function.
195Obviously the name "mylib" is the part before the "#" and is used as the name
196of the script, adding ".vim".
197
198You can put many other functions in the mylib.vim script, you are free to
199organize your functions in library scripts. But you must use function names
200where the part before the '#' matches the script name. Otherwise Vim would
201not know what script to load. This is where it differs from the import/export
202mechanism.
203
204If you get really enthusiastic and write lots of library scripts, you may
205want to use subdirectories. Example: >
206
207 netlib#ftp#read('somefile')
208
209Here the script name is taken from the function name up to the last "#". The
210"#" in the middle are replaced by a slash, the last one by ".vim". Thus you
211get "netlib/ftp.vim". For Unix the library script used for this could be:
212
213 ~/.vim/autoload/netlib/ftp.vim
214
215Where the function is defined like this: >
216
217 def netlib#ftp#read(fname: string)
218 # Read the file fname through ftp
219 enddef
220
221Notice that the name the function is defined with is exactly the same as the
222name used for calling the function. And the part before the last '#'
223exactly matches the subdirectory and script name.
224
225You can use the same mechanism for variables: >
226
227 var weekdays = dutch#weekdays
228
229This will load the script "autoload/dutch.vim", which should contain something
230like: >
231
232 var dutch#weekdays = ['zondag', 'maandag', 'dinsdag', 'woensdag',
233 \ 'donderdag', 'vrijdag', 'zaterdag']
234
235Further reading: |autoload|.
236
237==============================================================================
238*52.4* Other mechanisms to use
239
240Some may find the use of several files a hassle and prefer to keep everything
241together in one script. To avoid this resulting in slow startup there is a
242mechanism that only defines a small part and postpones the rest to when it is
243actually used. *write-plugin-quickload*
244
245The basic idea is that the plugin is loaded twice. The first time user
246commands and mappings are defined that offer the functionality. The second
247time the functions that implement the functionality are defined.
248
249It may sound surprising that quickload means loading a script twice. What we
250mean is that it loads quickly the first time, postponing the bulk of the
251script to the second time, which only happens when you actually use it. When
252you always use the functionality it actually gets slower!
253
254This uses a FuncUndefined autocommand. This works differently from the
255|autoload| functionality explained above.
256
257The following example shows how it's done: >
258
259 " Vim global plugin for demonstrating quick loading
260 " Last Change: 2005 Feb 25
261 " Maintainer: Bram Moolenaar <Bram@vim.org>
262 " License: This file is placed in the public domain.
263
264 if !exists("s:did_load")
265 command -nargs=* BNRead call BufNetRead(<f-args>)
266 map <F19> :call BufNetWrite('something')<CR>
267
268 let s:did_load = 1
269 exe 'au FuncUndefined BufNet* source ' .. expand('<sfile>')
270 finish
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200271 endif
272
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100273 function BufNetRead(...)
274 echo 'BufNetRead(' .. string(a:000) .. ')'
275 " read functionality here
276 endfunction
277
278 function BufNetWrite(...)
279 echo 'BufNetWrite(' .. string(a:000) .. ')'
280 " write functionality here
281 endfunction
282
283When the script is first loaded "s:did_load" is not set. The commands between
284the "if" and "endif" will be executed. This ends in a |:finish| command, thus
285the rest of the script is not executed.
286
287The second time the script is loaded "s:did_load" exists and the commands
288after the "endif" are executed. This defines the (possible long)
289BufNetRead() and BufNetWrite() functions.
290
291If you drop this script in your plugin directory Vim will execute it on
292startup. This is the sequence of events that happens:
293
2941. The "BNRead" command is defined and the <F19> key is mapped when the script
295 is sourced at startup. A |FuncUndefined| autocommand is defined. The
296 ":finish" command causes the script to terminate early.
297
2982. The user types the BNRead command or presses the <F19> key. The
299 BufNetRead() or BufNetWrite() function will be called.
300
3013. Vim can't find the function and triggers the |FuncUndefined| autocommand
302 event. Since the pattern "BufNet*" matches the invoked function, the
303 command "source fname" will be executed. "fname" will be equal to the name
304 of the script, no matter where it is located, because it comes from
305 expanding "<sfile>" (see |expand()|).
306
3074. The script is sourced again, the "s:did_load" variable exists and the
308 functions are defined.
309
310Notice that the functions that are loaded afterwards match the pattern in the
311|FuncUndefined| autocommand. You must make sure that no other plugin defines
312functions that match this pattern.
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200313
314==============================================================================
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100315*52.5* Using a Vim9 script from legacy script *source-vim9-script*
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200316
317In some cases you have a legacy Vim script where you want to use items from a
318Vim9 script. For example in your .vimrc you want to initialize a plugin. The
319best way to do this is to use `:import`. For example: >
320
Bram Moolenaar016188f2022-06-06 20:52:59 +0100321 import 'myNicePlugin.vim'
322 call myNicePlugin.NiceInit('today')
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200323
Bram Moolenaar016188f2022-06-06 20:52:59 +0100324This finds the exported function "NiceInit" in the Vim9 script file and makes
325it available as script-local item "myNicePlugin.NiceInit". `:import` always
326uses the script namespace, even when "s:" is not given. If "myNicePlugin.vim"
327was already sourced it is not sourced again.
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200328
329Besides avoiding putting any items in the global namespace (where name clashes
330can cause unexpected errors), this also means the script is sourced only once,
331no matter how many times items from it are imported.
332
333In some cases, e.g. for testing, you may just want to source the Vim9 script.
334That is OK, but then only global items will be available. The Vim9 script
335will have to make sure to use a unique name for these global items. Example: >
336 source ~/.vim/extra/myNicePlugin.vim
337 call g:NicePluginTest()
338
339==============================================================================
Christian Brabandt015c84c2024-10-07 21:48:32 +0200340*52.6* Vim9 examples: comment and highlight-yank plugin
Christian Brabandtbad95772024-05-31 14:58:26 +0200341
342COMMENT PACKAGE
343
h-east9c4389a2024-06-09 16:32:19 +0200344Vim comes with a comment plugin, written in Vim9 script. |comment-install|
Christian Brabandtbad95772024-05-31 14:58:26 +0200345Have a look at the package located at $VIMRUNTIME/pack/dist/opt/comment/
346
347HIGHLIGHT YANK PLUGIN
348
Christian Brabandt83d74402025-03-19 21:55:59 +0100349Vim comes with the highlight-yank plugin, written in Vim9 script
350|hlyank-install|, here is a simplified implementation: >vim
Christian Brabandtbad95772024-05-31 14:58:26 +0200351
352 vim9script
353
354 def HighlightedYank(hlgroup = 'IncSearch', duration = 300, in_visual = true)
355 if v:event.operator ==? 'y'
h-east9c4389a2024-06-09 16:32:19 +0200356 if !in_visual && visualmode() != null_string
357 visualmode(1)
358 return
359 endif
360 var [beg, end] = [getpos("'["), getpos("']")]
361 var type = v:event.regtype ?? 'v'
Jim Zhou7db96132025-03-12 20:57:24 +0100362 var pos = getregionpos(beg, end, {type: type, exclusive: false})
h-east9c4389a2024-06-09 16:32:19 +0200363 var m = matchaddpos(hlgroup, pos->mapnew((_, v) => {
364 var col_beg = v[0][2] + v[0][3]
Jim Zhou7db96132025-03-12 20:57:24 +0100365 var col_end = v[1][2] + v[1][3] + 1
h-east9c4389a2024-06-09 16:32:19 +0200366 return [v[0][1], col_beg, col_end - col_beg]
367 }))
368 var winid = win_getid()
369 timer_start(duration, (_) => m->matchdelete(winid))
Christian Brabandtbad95772024-05-31 14:58:26 +0200370 endif
371 enddef
372
373 autocmd TextYankPost * HighlightedYank()
374<
Christian Brabandt83d74402025-03-19 21:55:59 +0100375For the complete example, have a look into the package located at
376`$VIMRUNTIME/pack/dist/opt/hlyank/`
377
Christian Brabandtbad95772024-05-31 14:58:26 +0200378==============================================================================
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200379
380Next chapter: |usr_90.txt| Installing Vim
381
Bram Moolenaarcfa8f9a2022-06-03 21:59:47 +0100382
Bram Moolenaar65e0d772020-06-14 17:29:55 +0200383Copyright: see |manual-copyright| vim:tw=78:ts=8:noet:ft=help:norl: