Vim9 脚本设计使大型 Vim 脚本的编写更为容易。它看来更像其它脚本语言,尤其是
Typescript。另外,函数被编译为指令以快速执行。这使 Vim9 脚本快很多,多达百倍。
这里的基本想法是脚本文件包含只用于脚本文件内部的私有项目,和可被脚本文件之外使
用的导出项目。然后,这些导出项目可用于导入它们的脚本里。这样什么在哪里定义就会
很清晰。
让我们从一个例子开始,有一个导出函数和有一个私有函数的脚本:
vim9script
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
vim9script 命令是必须的, export
只能用于 Vim9 脚本里。
`export def GetMessage(...` 行以 export
开始,意味着此函数可以被其它脚本调
用。`def GetPart(...` 行不以 export
开头,意味着这是局部于脚本的函数,只能在
此脚本文件中使用。
现在说说导入此脚本的脚本。此例中使用如下布局,这适用于放在 "pack" 目录之下的插
件:
.../plugin/theplugin.vim
.../lib/getmessage.vim
假定 "..." 目录已添加进 'runtimepath',Vim 会寻找 "plugin" 目录里的插件并载入
"theplugin.vim"。Vim 并不识别 "lib" 目录,你可以在那里放任何脚本。
上面导出 GetMessage() 的脚本放在 lib/getmessage.vim 里。GetMessage() 函数在
plugin/theplugin.vim 里调用:
vim9script
import "../lib/getmessage.vim"
command -nargs=1 ShowMessage echomsg getmessage.GetMessage(<f-args>)
import
命令使用了相对路径,因为它以 "../" 开始,也就是上一层目录。其它类型的
路径可见 :import 命令。
现在试试插件提供的命令吧:
ShowMessage 1
To 1 we say no
ShowMessage 22
To 22 we say maybe
注意
GetMessage() 使用了导入脚本名字 "getmessage" 作为前缀。这样,对每个导入函
数,你会知道它是从哪里导入的。如果导入多个脚本,每个脚本都可以定义自己的
GetMessage() 函数:
vim9script
import "../lib/getmessage.vim"
import "../lib/getother.vim"
command -nargs=1 ShowMessage echomsg getmessage.GetMessage(<f-args>)
command -nargs=1 ShowOther echomsg getother.GetMessage(<f-args>)
如果导入脚本名字太长或者需要在很多地方使用,可以通过 "as" 参数来缩短名字:
import "../lib/getmessage.vim" as msg
command -nargs=1 ShowMessage echomsg msg.GetMessage(<f-args>)
重 载
记住一件事: 导入的 "lib/getmessage.vim" 脚本只会载入一次。第二次载入时会跳过,
因为里面的项目都已经创建了。不管是在其它脚本还是同一脚本里再次执行导入命令,都
是如此。
对插件的使用这是高效的,但在插件开发期间,这意味着导入 "lib/getmessage.vim" 之
后对它的修改不会生效。为此,只能退出 Vim 后重启。(理据: 脚本定义的项目可能在编
译过的函数里使用,再次载入脚本可能会破坏这些函数)。
使 用 全 局 值
有时需要使用全局变量或函数,以便在任何地方都能使用。一个全局变量的好例子是控制
插件行为的首选项。为了避免其它脚本使用的重名,使用别人不太可能使用的前缀。例
如,如果插件是 "mytags" 的话:
g:mytags_location = '$HOME/project'
g:mytags_style = 'fast'
在把大脚本分割为若干部分后,使用脚本时所有行仍然被载入和执行。每个 import
都
会载入被导入的脚本以找到其中定义的项目。虽然这样容易早点发现错误,但颇耗时。如
果提供的功能不常用,这很浪费。
import
除了立即载入脚本之外,也可以延迟载入。使用上面的例子,只要在
plugin/theplugin.vim 里改一处就可以了:
import autoload "../lib/getmessage.vim"
脚本其余部分无须改动。不过,不会检查类型。甚至 GetMessage() 函数存在与否,也是
在实际使用时才会检查。需要你自己决定是快速启动还是早点发现错误更加重要。也可以
在检查完一切都正常之后再加上 "autoload" 参数。
自 动 载 入 目 录
另一种使用自动载入的形式,脚本名既不是绝对也不是相对路径:
import autload "monthlib.vim"
会在 'runtimepath' 的 autoload 目录下搜索脚本 "monthlib.vim"。Unix 一个常见目
录是 "~/.vim/autoload"。也会在 'packpath' 里 "start" 之下搜索脚本。
这种形式的主要好处是容易和其它脚本共享。但要确保脚本名唯一,因为 Vim 会在
'runtimepath' 下搜索所有的 "autoload" 目录,如果使用插件管理器管理多个插件,它
会在 'runtimepath' 下新增目录,每个目录都可能有 "autoload" 目录。
没有自动载入的话:
import "monthlib.vim"
Vim 会在 'runtimepath' 的 import 目录下搜索脚本 "monthlib.vim"。注意
这种形式
里,加上或去掉 "autoload" 关键字会影响脚本寻找的位置。而使用相对或绝对路径的形
式时位置是不会改变的。
52.3 不经导入/导出的自动载入
write-library-script
在导入/导出机制引入之前,已经有另一套有用的机制,有些用户会觉得它比较简单些。
基本想法是调用一个有特殊名字的函数,然后这个函数在某自动载入脚本中。我们把这种
脚本称为库脚本。
此自动载入机制是基于有 "#" 字符的函数名的:
mylib#myfunction(arg)
Vim 会识别有内嵌 "#" 字符的函数名,如果该函数还没有定义,查找 'runtimepath'
里的 "autoload/mylib.vim"。该脚本必须定义 "mylib#myfunction()" 函数。显然
"mylib" 名字是 "#" 之前的部分,加上 ".vim" 后就被用作脚本名。
在 mylib.vim 脚本里可以放上许多其它函数,你可以自由组织库脚本的函数。但必须使
函数名 "#" 前面的部分匹配脚本名。否则 Vim 无法知道载入哪个脚本。这是和导入/导
出机制不同之处。
如果你真的热情高涨写了很多库脚本,现在可能想要用子目录吧。例如:
netlib#ftp#read('somefile')
这里脚本名取自函数名最后 "#" 之前的部分。中间的 "#" 用斜杠替代,最后加上
".vim"。这样得到的名字就是 "netlib/ftp.vim"。Unix 上,这里使用的库脚本可以是:
~/.vim/autoload/netlib/ftp.vim
其中的函数应该如此定义:
def netlib#ftp#read(fname: string)
" 用 ftp 读入文件 fname
enddef
注意
定义所用的函数名必须和调用的函数名完全相同。最后一个 '#' 之前的部分必须准
确匹配子目录和脚本名。
同样的机制可以用来定义变量:
var weekdays = dutch#weekdays
会载入脚本 "autoload/dutch.vim",它应该包含这样的内容:
var dutch#weekdays = ['zondag', 'maandag', 'dinsdag', 'woensdag',
\ 'donderdag', 'vrijdag', 'zaterdag']
进一步的阅读可见: autoload 。
有些人觉得维护多个文件很麻烦,而把所有内容放在一个脚本。为了避免这样引起的启动
时的延迟,有一个机制只定义很小的一部分,而其余部分的载入会延迟到实际使用的时
候。 write-plugin-quickload
基本的方法是调用插件两次。第一次定义用户命令和映射,提供需要的功能。第二次定义
实现这些功能的函数。
听起来很吓人,快速载入意味着载入两次!我们的意思是,第一次载入很快,把脚本的大
部分内容延迟到第二次才载入,只有实际使用这些功能时才会这么做。当然,如果你总是
用这些功能,实际上更慢了!
这里使用 FuncUndefined 自动命令。和上述的 autoload 功能不同。
下例演示如何这是如何完成的:
" 演示快速载入的 Vim 全局插件
" Last Change: 2005 Feb 25
" Maintainer: Bram Moolenaar <Bram@vim.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) .. ')'
" 读入功能在此
endfunction
function BufNetWrite(...)
echo 'BufNetWrite(' .. string(a:000) .. ')'
" 写回功能在此
endfunction
第一次载入脚本时,没有设置 "s:did_load"。这时执行 "if" 和 "endif" 之间的命令。
它以 :finish 命令结束,这样脚本的其余部分不再执行。
第二次载入脚本时,"s:did_load" 已经存在,这时执行 "endif" 之后的命令。这里定义
(可能很长的) BufNetRead() 和 BufNetWrite() 函数。
如果把该脚本放到你的 plugin 目录,Vim 启动时会执行它。下面列出发生事件的序列:
1. 启动期间执行脚本时,定义 "BNRead" 命令并映射 <F19>
键。定义 FuncUndefined
自动命令。":finish" 命令使脚本提前终止。
2. 用户输入 BNRead 命令或者按了 <F19>
键。BufNetRead() 或 BufNetWrite() 函数会
被调用。
3. Vim 找不到这些函数并因此激活了 FuncUndefined 自动命令事件。因为模式
"BufNet*" 匹配要调用的函数,执行命令 "source fname",其中 "fname" 被赋予脚
本的名字,不管它实际在何处都没问题。这是因为该名字来自 "<sfile>
" 扩展的结果
(见 expand() )。
4. 再次执行脚本。"s:did_load" 变量已经存在,此时定义函数。
注意
后来载入的函数匹配 FuncUndefined 自动命令的模式。要确信其它插件没有定义
匹配此模式的函数。
有些情况下,在老式脚本里你会想使用 Vim9 脚本里的项目。例如在 .vimrc 里初始化插
件。最好的方法是用 :import 。例如:
import 'myNicePlugin.vim'
call myNicePlugin.NiceInit('today')
这会找到 Vim9 脚本文件里的 "NiceInit" 导出函数,并使之成为局部于脚本的项目
"myNicePlugin.NiceInit"。即使不给出 "s:", :import 也总使用脚本命名空间,如果
"myNicePlugin.vim" 已经执行过,不会再次执行。
除了避免把任何项目放进全局命名空间 (因为命名冲突会导致意料之外的错误) 以外,这
也意味着无论其中的项目被导入多少次,一份脚本只执行一次。
有些情况下,例如为了测试用途,可能就想要直接执行 Vim9 脚本。这没问题,但你只能
得到其中的全局项目。该 Vim9 脚本要确保这些全局项目使用独一无二的名字。例如:
source ~/.vim/extra/myNicePlugin.vim
call g:NicePluginTest()