diff options
Diffstat (limited to 'mnv/runtime/doc/usr_52.txt')
| -rw-r--r-- | mnv/runtime/doc/usr_52.txt | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/mnv/runtime/doc/usr_52.txt b/mnv/runtime/doc/usr_52.txt new file mode 100644 index 0000000000..c054b9161a --- /dev/null +++ b/mnv/runtime/doc/usr_52.txt @@ -0,0 +1,385 @@ +*usr_52.txt* For MNV version 10.0. Last change: 2026 Feb 14 + + + MNV USER MANUAL by Bram Moolenaar + + + Write larger plugins + +When plugins do more than simple things, they tend to grow big. This file +explains how to make sure they still load fast and how to split them up in +smaller parts. + +|52.1| Export and import +|52.2| Autoloading +|52.3| Autoloading without import/export +|52.4| Other mechanisms to use +|52.5| Using a MNV9 script from legacy script +|52.6| MNV9 examples: comment and highlight-yank plugin + + Next chapter: |usr_90.txt| Installing MNV + Previous chapter: |usr_51.txt| Create a plugin +Table of contents: |usr_toc.txt| + +============================================================================== +*52.1* Export and import + +MNV9 script was designed to make it easier to write large MNV scripts. It +looks more like other script languages, especially Typescript. Also, +functions are compiled into instructions that can be executed quickly. This +makes MNV9 script a lot faster, up to a 100 times. + +The basic idea is that a script file has items that are private, only used +inside the script file, and items that are exported, which can be used by +scripts that import them. That makes very clear what is defined where. + +Let's start with an example, a script that exports one function and has one +private function: > + + mnv9script + + export def GetMessage(count: string): string + var nr = str2nr(count) + var result = $'To {nr} we say ' + result ..= GetReply(nr) + return result + enddef + + def GetReply(nr: number): string + if nr == 42 + return 'yes' + elseif nr == 22 + return 'maybe' + else + return 'no' + endif + enddef + +The `mnv9script` command is required, `export` only works in a |MNV9| script. + +The `export def GetMessage(...` line starts with `export`, meaning that this +function can be called by other scripts. The line `def GetReply(...` does not +start with `export`, this is a script-local function, it can only be used +inside this script file. + +Now about the script where this is imported. In this example we use this +layout, which works well for a plugin below the "pack" directory: + .../plugin/theplugin.mnv + .../lib/getmessage.mnv + +Assuming the "..." directory has been added to 'runtimepath', MNV will look +for plugins in the "plugin" directory and source "theplugin.mnv". MNV does +not recognize the "lib" directory, you can put any scripts there. + +The above script that exports GetMessage() goes in lib/getmessage.mnv. The +GetMessage() function is used in plugin/theplugin.mnv: > + + mnv9script + + import "../lib/getmessage.mnv" + command -nargs=1 ShowMessage echomsg getmessage.GetMessage(<f-args>) + +The `import` command uses a relative path, it starts with "../", which means +to go one directory up. For other kinds of paths see the `:import` command. + +How we can try out the command that the plugin provides: > + ShowMessage 1 +< To 1 we say no ~ +> + ShowMessage 22 +< To 22 we say maybe ~ + +Notice that the function GetMessage() is prefixed with the imported script +name "getmessage". That way, for every imported function used, you know what +script it was imported from. If you import several scripts each of them could +define a GetMessage() function: > + + mnv9script + + import "../lib/getmessage.mnv" + import "../lib/getother.mnv" + command -nargs=1 ShowMessage echomsg getmessage.GetMessage(<f-args>) + command -nargs=1 ShowOther echomsg getother.GetMessage(<f-args>) + +If the imported script name is long or you use it in many places, you can +shorten it by adding an "as" argument: > + import "../lib/getmessage.mnv" as msg + command -nargs=1 ShowMessage echomsg msg.GetMessage(<f-args>) + + +RELOADING + +One thing to keep in mind: the imported "lib/getmessage.mnv" script will be +sourced only once. When it is imported a second time sourcing it will be +skipped, since the items in it have already been created. It does not matter +if this import command is in another script, or in the same script that is +sourced again. + +This is efficient when using a plugin, but when still developing a plugin it +means that changing "lib/getmessage.mnv" after it has been imported will have +no effect. You need to quit MNV and start it again. (Rationale: the items +defined in the script could be used in a compiled function, sourcing the +script again may break those functions). + + +USING GLOBALS + +Sometimes you will want to use global variables or functions, so that they can +be used anywhere. A good example is a global variable that passes a +preference to a plugin. To avoid other scripts using the same name, use a +prefix that is very unlikely to be used elsewhere. For example, if you have a +"mytags" plugin, you could use: > + + g:mytags_location = '$HOME/project' + g:mytags_style = 'fast' + +============================================================================== +*52.2* Autoloading + +After splitting your large script into pieces, all the lines will still be +loaded and executed the moment the script is used. Every `import` loads the +imported script to find the items defined there. Although that is good for +finding errors early, it also takes time. Which is wasted if the +functionality is not often used. + +Instead of having `import` load the script immediately, it can be postponed +until needed. Using the example above, only one change needs to be made in +the plugin/theplugin.mnv script: > + import autoload "../lib/getmessage.mnv" + +Nothing in the rest of the script needs to change. However, the types will +not be checked. Not even the existence of the GetMessage() function is +checked until it is used. You will have to decide what is more important for +your script: fast startup or getting errors early. You can also add the +"autoload" argument later, after you have checked everything works. + + +AUTOLOAD DIRECTORY + +Another form is to use autoload with a script name that is not an absolute or +relative path: > + import autoload "monthlib.mnv" + +This will search for the script "monthlib.mnv" in the autoload directories of +'runtimepath'. With Unix one of the directories often is "~/.mnv/autoload". +It will also search under 'packpath', under "start". + +The main advantage of this is that this script can be easily shared with other +scripts. You do need to make sure that the script name is unique, since MNV +will search all the "autoload" directories in 'runtimepath', and if you are +using several plugins with a plugin manager, it may add a directory to +'runtimepath', each of which might have an "autoload" directory. + +Without autoload: > + import "monthlib.mnv" + +MNV will search for the script "monthlib.mnv" in the import directories of +'runtimepath'. Note that in this case adding or removing "autoload" changes +where the script is found. With a relative or absolute path the location does +not change. + +============================================================================== +*52.3* Autoloading without import/export + + *write-library-script* +A mechanism from before import/export is still useful and some users may find +it a bit simpler. The idea is that you call a function with a special name. +That function is then in an autoload script. We will call that one script a +library script. + +The autoload mechanism is based on a function name that has "#" characters: > + + mylib#myfunction(arg) + +MNV will recognize the function name by the embedded "#" character and when +it is not defined yet search for the script "autoload/mylib.mnv" in +'runtimepath'. That script must define the "mylib#myfunction()" function. +Obviously the name "mylib" is the part before the "#" and is used as the name +of the script, adding ".mnv". + +You can put many other functions in the mylib.mnv script, you are free to +organize your functions in library scripts. But you must use function names +where the part before the '#' matches the script name. Otherwise MNV would +not know what script to load. This is where it differs from the import/export +mechanism. + +If you get really enthusiastic and write lots of library scripts, you may +want to use subdirectories. Example: > + + netlib#ftp#read('somefile') + +Here the script name is taken from the function name up to the last "#". The +"#" in the middle are replaced by a slash, the last one by ".mnv". Thus you +get "netlib/ftp.mnv". For Unix the library script used for this could be: + + ~/.mnv/autoload/netlib/ftp.mnv + +Where the function is defined like this: > + + def netlib#ftp#read(fname: string) + # Read the file fname through ftp + enddef + +Notice that the name the function is defined with is exactly the same as the +name used for calling the function. And the part before the last '#' +exactly matches the subdirectory and script name. + +You can use the same mechanism for variables: > + + var weekdays = dutch#weekdays + +This will load the script "autoload/dutch.mnv", which should contain something +like: > + + var dutch#weekdays = ['zondag', 'maandag', 'dinsdag', 'woensdag', + \ 'donderdag', 'vrijdag', 'zaterdag'] + +Further reading: |autoload|. + +============================================================================== +*52.4* Other mechanisms to use + +Some may find the use of several files a hassle and prefer to keep everything +together in one script. To avoid this resulting in slow startup there is a +mechanism that only defines a small part and postpones the rest to when it is +actually used. *write-plugin-quickload* + +The basic idea is that the plugin is loaded twice. The first time user +commands and mappings are defined that offer the functionality. The second +time the functions that implement the functionality are defined. + +It may sound surprising that quickload means loading a script twice. What we +mean is that it loads quickly the first time, postponing the bulk of the +script to the second time, which only happens when you actually use it. When +you always use the functionality it actually gets slower! + +This uses a FuncUndefined autocommand. This works differently from the +|autoload| functionality explained above. + +The following example shows how it's done: > + + " MNV global plugin for demonstrating quick loading + " Last Change: 2005 Feb 25 + " Maintainer: Bram Moolenaar <Bram@mnv.org> + " License: This file is placed in the public domain. + + if !exists("s:did_load") + command -nargs=* BNRead call BufNetRead(<f-args>) + map <F19> :call BufNetWrite('something')<CR> + + let s:did_load = 1 + exe 'au FuncUndefined BufNet* source ' .. expand('<sfile>') + finish + endif + + function BufNetRead(...) + echo 'BufNetRead(' .. string(a:000) .. ')' + " read functionality here + endfunction + + function BufNetWrite(...) + echo 'BufNetWrite(' .. string(a:000) .. ')' + " write functionality here + endfunction + +When the script is first loaded "s:did_load" is not set. The commands between +the "if" and "endif" will be executed. This ends in a |:finish| command, thus +the rest of the script is not executed. + +The second time the script is loaded "s:did_load" exists and the commands +after the "endif" are executed. This defines the (possible long) +BufNetRead() and BufNetWrite() functions. + +If you drop this script in your plugin directory MNV will execute it on +startup. This is the sequence of events that happens: + +1. The "BNRead" command is defined and the <F19> key is mapped when the script + is sourced at startup. A |FuncUndefined| autocommand is defined. The + ":finish" command causes the script to terminate early. + +2. The user types the BNRead command or presses the <F19> key. The + BufNetRead() or BufNetWrite() function will be called. + +3. MNV can't find the function and triggers the |FuncUndefined| autocommand + event. Since the pattern "BufNet*" matches the invoked function, the + command "source fname" will be executed. "fname" will be equal to the name + of the script, no matter where it is located, because it comes from + expanding "<sfile>" (see |expand()|). + +4. The script is sourced again, the "s:did_load" variable exists and the + functions are defined. + +Notice that the functions that are loaded afterwards match the pattern in the +|FuncUndefined| autocommand. You must make sure that no other plugin defines +functions that match this pattern. + +============================================================================== +*52.5* Using a MNV9 script from legacy script *source-mnv9-script* + +In some cases you have a legacy MNV script where you want to use items from a +MNV9 script. For example in your .mnvrc you want to initialize a plugin. The +best way to do this is to use `:import`. For example: > + + import 'myNicePlugin.mnv' + call myNicePlugin.NiceInit('today') + +This finds the exported function "NiceInit" in the MNV9 script file and makes +it available as script-local item "myNicePlugin.NiceInit". `:import` always +uses the script namespace, even when "s:" is not given. If "myNicePlugin.mnv" +was already sourced it is not sourced again. + +Besides avoiding putting any items in the global namespace (where name clashes +can cause unexpected errors), this also means the script is sourced only once, +no matter how many times items from it are imported. + +In some cases, e.g. for testing, you may just want to source the MNV9 script. +That is OK, but then only global items will be available. The MNV9 script +will have to make sure to use a unique name for these global items. Example: > + source ~/.mnv/extra/myNicePlugin.mnv + call g:NicePluginTest() + +============================================================================== +*52.6* MNV9 examples: comment and highlight-yank plugin + +COMMENT PACKAGE + +MNV comes with a comment plugin, written in MNV9 script. |comment-install| +Have a look at the package located at $MNVRUNTIME/pack/dist/opt/comment/ + +HIGHLIGHT YANK PLUGIN + +MNV comes with the highlight-yank plugin, written in MNV9 script +|hlyank-install|, here is a simplified implementation: >mnv9 + + mnv9script + + def HighlightedYank(hlgroup = 'IncSearch', duration = 300, in_visual = true) + if v:event.operator ==? 'y' + if !in_visual && visualmode() != null_string + visualmode(1) + return + endif + var [beg, end] = [getpos("'["), getpos("']")] + var type = v:event.regtype ?? 'v' + var pos = getregionpos(beg, end, {type: type, exclusive: false}) + var m = matchaddpos(hlgroup, pos->mapnew((_, v) => { + var col_beg = v[0][2] + v[0][3] + var col_end = v[1][2] + v[1][3] + 1 + return [v[0][1], col_beg, col_end - col_beg] + })) + var winid = win_getid() + timer_start(duration, (_) => m->matchdelete(winid)) + endif + enddef + + autocmd TextYankPost * HighlightedYank() +< +For the complete example, have a look into the package located at +`$MNVRUNTIME/pack/dist/opt/hlyank/` + +============================================================================== + +Next chapter: |usr_90.txt| Installing MNV + + +Copyright: see |manual-copyright| mnv:tw=78:ts=8:noet:ft=help:norl: |
