You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
16 KiB

2 years ago
# 第六章 VimL 内建函数使用
## 6.3 操作外部系统资源
本节介绍的函数主要着眼于访问外部资源,比如最常用便是系统文件。
### 文件系统相关函数
* glob() 按文件通配符搜索文件
* globpath() 在指定目录中搜索文件
* findfile() 在搜索路径中查找文件
* finddir() 在搜索路径中查找目录
`glob()` 函数的作用,就相当于在 linux 终端命令 `ls` 所能列出的文件名。它可接收
至多四个参数,只有第一个是必须的:
* `{wildcard}` 通配符文件名模式,非正则表达式;
* `{nosuf}` 让两个选项生效,`&wildignore` 可忽略某些文件,`&suffixes` 按文件名
后缀影响结果的排序;
* `{list}` 提供该参数则返回列表类型,否则是用换行符分隔的字符串;
* `{alllinks}` 一般情况下只会找出存在的文件,对于软链接文件,则其指向的文件有
效才被包含在结果中,但若提供该参数,无效链接文件也接收。
一般第三个参数比较常用,即将结果按列表返回,以 `glob(wild, 0, 1)` 方式调用。
`globpath()` 用法是在 `glob()` 基础上,额外提供一个参数指定要哪些目录下搜索文
件,必选参数,且插在第一个参数位置上。这是一个以逗号分隔的目录名列表字符串,如
`&rtp` 的表示法。例如 `globpath(&rtp, 'readme.md')` 就能搜索出所有运行时目录下
的说明文档(目前许多插件安装习惯是安装在独立的运行时目录下,一般会有个
`readme.md` 说明文档)。
`glob()` 函数将返回所有匹配的文件名,但 `findfile()``finddir()` 只返回第一
个匹配的文件名,一个查找文件,一个查找目录,类似命令 `:find` 的作用。接收三个
参数,只有第一个必选:
* `{name}` 文件名,必须是全名,不是通配符;
* `{path}` 在这些目录下查找文件,也是逗号分隔的目录列表,省略的话用选项 `&path`
代替。所以实际所查到的文件名类似 `{first-path}/{name}`
* `{count}` 指定返回第几个匹配的文件,而不是第一个,负数时返回所有匹配文件组成
的列表类型变量。
Vim 的 `:find` 命令及 `gf` 命令使用 `&path` 选项值,这叫做搜索路径,这是搜索普
通文件的;不同于 `&rpt` 运行时路径是搜索 vim 脚本的。搜索路径同时支持向下搜索
与向上搜索的机制,在 `{path}` 参数或 `&path` 选项中使用特殊字符达成:
* 向下搜索:`*` 表示任意字符,`**` 表示任意子目录;
* 向上搜索:`{one-path};{upto-path},{another-path}` 即在一个路径(逗号分隔的)
末尾再加一个分号,接一个相对基目录(`{one-path}`)更上层的目录(`{upto-path}`)
就能从指定目录开始向上搜索,依次在其父目录搜索,直到终止目录 `{upto-path}`
终止目录可省,但分号不可省,否则在该目录中就认为不需要支持向上搜索。建议不限
定终止目录时写成 `{base-path};/,``{base-path};~,` 一直上溯到系统根目录或
自己的家目录。
* 相对 Vim 当前路径写成单点 `.` ,相对当前正编辑的文件缓冲的路径写成 `./`
向上搜索机制,对于搜索工程项目文件很有用。比如当你正在编辑一个源代码文件,它一
般被组织在各层子目录下,要找到项目文件就得使用向上机制了,例如 `.git/` 目录或
`tags` 文件,都一般放在项目顶层目录中。
* resolve() 解析链接文件名
* simplify() 简化文件名路径
* pathshorten() 缩写文件名的中间路径
* fnamemodify() 文件名修饰
`resolve()` 是处理软链接文件(linux 系统)或快捷方式(MS-Windows 的 `.lnk`)的
,将其转为实际指向的文件名。在其他系统同 `simplify()` 简化处理。文件名需要简化
的一个例子是包含一系列的点号与双点号,如 `./dir/.././/file/` ,这可能是由其他
函数拼接而来。`simplify()` 简化后不改变其意义,如上例简化结果为 `./file/`。但
`pathshorten()` 只是简单地将中间路径都缩写至首字母,显然是不保证其有效意义
的。比如在默认的多标签页的名字,为节省屏幕空间就将当前编辑文件缩写目录名,
`~.vim/autoload/myfile.vim` 将简写为 `~/.v/a/myfile.vim`(如果觉得这比较丑,可
寻插件定制标签页栏)。
文件名修饰是指如何从一个文件名中获取其目录、全路径名、后缀名等相关的名字字符串
。函数 `fnamemodify({fname}, {mods})` 的第二参数就叫做修饰符,修饰符以冒号开头
带一个单字母表示不同意义,且可连续使用。主要的修饰符如:
* `:p` 文件全路径名
* `:h` 父目录名(文件名头部,去除路径分隔符最后一部分)
* `:t` 文件名尾部(一般是 `:h` 剩余部分,纯文件名)
* `:e` 文件名后缀
* `:r` 文件名主体(相对于 `:e` 而言,不包括后缀,但可能包含父目录)
注意 `fnamemodify()` 不处理特殊文件名变量,需用 `expand()` 先展开,不过后者也
可以直接加修饰符后缀。如以下两个语句等效:
```vim
: echo fnamemodify(expand('%'), ':p:t')
: echo expand('%:p:t')
```
* executable() 检查是否可执行程序
* exepath() 可执行程序的全路径
* filereadable() 文件是否可读
* filewriteable() 文件是否可写
* getfperm() 获取文件权限(类 `rwxrwxrwx` 字符串)
* getftype() 获取文件类型
* isdirectory() 检测目录是否存在
* getfsize() 获取文件字节大小(目录返回 `0`
* getftime() 获取文件的最后修改时间(整数,按秒计)
这几个函数用于检查指定文件的属性,其中 `getftype()` 返回的字符串主要有如:
* `file` 普通文件
* `dir` 目录
* `link` 软链接文件
* `bdev` `cdev` `socket` `fifo` `other`
* getcwd() 获取当前工作路径
* haslocaldir() 检测当前窗口是否有局部当前路径(`:lcd`)
这两个函数都可选带两个参数,指定窗口编号与标签页编号,因为取当前窗口的当前路径
。Vim 启动时,从 shell 环境中继续当前路径,这是全局当前路径,可用 `:cd` 命令修
改。每个窗口可有自己的局部当前路径,这用 `:lcd` 修改。如果从未用过 `:lcd` ,窗
口的局部当前路径就与全局当前路径相同。新分裂的窗口继承原窗口的当前路径。`:pwd`
打印的是全局当前路径,因此有可能与 `getcwd()` 不同。
* mkdir() 创建新目录(类似 `$mkdir`
* delete() 删除文件(类似 `$rm`
* rename() 重命名文件(类似 `$mv`
* readfile() 读文件至一个字符串列表
* writefile() 将字符串列表写入文件
这几个文件操作函数,除了 `readfile()` 返回列表外,其他函数在操作成功时返回 `0`
,失败时返回非零错误码。其功能与相应的 linux 命令类似,不过将命令行参数改成函
数调用参数。如 `mkdir('name', 'p')` 类似 shell 命令 `$mkdir -p name` 可以自动
创建中间目录;`delete()` 删除非空目录时必须加参数 `rf`(谨慎);`rename()` 重
命名文件可能覆盖已有文件无警告。当然这些操作也涉及系统权限。
读文件函数支持三个参数,`readfile(fname, binary, max)` ,后两个是可选的。默认
是按文本格式读入,主要会处理换行符。如果提供 `{binary}` 参数,按二进制格式读入
,虽然也会根据换行符分隔为列表元素,但元素中可能再保留回车符(dos 格式的文件)
,且最后可能多加一个空元素(若文件末尾是换行符)。第三个可选参数 `{max}` 可指
定只读入前几行,类似 linux 命令 `$head -n` ,但如果 `{max}` 参数是负数,则只读
入末尾几行,类似命令 `$tail -n`
写文件函数要求两个参数,作为内容的字符串列表,以及文件名,还有个可选参数标记:
`writefile(list, fname, flags)`。标记 `{flags}` 若包含 `b` 则按二进制格式写入
,若包含 `a` 则添加到原文件末尾,否则是覆盖原文件。
一般情况下,Vim 是处理文件文本的,在使用这两个读写文件函数时,没必要指定 `b`
二进制格式。但是按二进制格式先 `readfile()``writefile()` 确实能达到复制文
件的作用。
### 调用外部系统命令
在 Vim 的命令行中,可用 `:!` 叹号开头,调用外部系统命令。而在 VimL 脚本中,相
应功能的函数是 `system()`
* system() 执行系统命令,结果为字符串形式返回
* systemlist() 执行系统命令,结果以列表形式返回
* libcall() 调用外部库函数,结果返回字符串
* libcallnr() 调用外部库函数,结果返回数字
`system(cmd, input)` 将字符串 `{cmd}` 当作系统命令执行,返回字符串结果。如果
`{cmd}` 命令需要输入,则可提供可选参数 `{input}` ,一般也是字符串,首先写入临
时文件,再当作标准输入传给 `{cmd}` 。如果 `{input}` 是字符串列表,则以二进制
`b` 方式调用 `writefile()` 写入临时文件。
`{cmd}` 命令字符串不支持管道。并且为了安全与正确性起见,最好调用
`shellescape()` 转义特殊字符。`systemlist()` 用法类似,只是返回结果是字符串列
表。
`libcall()` 类似于 `call()` 的基础用法,只是调用外库(`.so` 或 `.dll`)的函数,
故需要库名、函数名与参数列表。当然不能随意调用外部库,只能调用专为扩展 vim 的
库,那才比较安全与实用。该函数将结果返回为字符串,另一个 `libcallnr()` 函数返
回的是数字结果。
* hostname() 获取 vim 所在运行的系统(计算机)名字
* getpid() 获取 vim 运行的进程号 PID
* tempname() 获取可用于临时文件的文件名
在实现比较复杂的功能时,可能需要用到临时文件,用 `tempname()` 获得一个可用的文
件名(保证不重名)。也可以自己根据进程 PID 构建有规律的临时文件名。
### 日期时间函数
* localtime() 获取当前时间
* strftime() 格式化时间
* reltime() 获取相对时间
* reltimestr() 将相对时间转为字符串
* reltimefloat() 格式化相对时间转为浮点数
`localtime()` 用于获取当前的标准时间,即从 1970 年至今的秒数。将这样的时间转为
可读模式,用 `strftime(format, time)` 函数,缺省 `{time}` 参数时,取当前时间,
相当于先调用 `localtime()`。可用时间格式 `{format}` 与 C 语言的同名标准函数相
同,如 `strftime('%Y-%m-%d')` 将返回类似 `2017-11-11` 的字符串。
`reltime()` 返回更精确的时间,具体格式与系统有关。无参数调用返回当前时间,一个
参数 `reltime(start)` 返回从开始时刻(`{start}`也应该是由该函数返回的)到现在
所经过的时间,两个参数 `reltime(start, end)` 返回两个时刻之间的时间。用
`reltimestr()` 将这样的时间转为字符串表示,`reltimefloat()` 转为浮点数表示,因
为字符串表示法也正像个浮点数(即秒数加小数点加毫秒数)。因其精确到毫秒,可用来
计算命令或函数执行的时间。
### 用户交互函数
* input() 获得用户从命令行输入的一行文本
* inputsave() 保存用户输入序列
* inputrestore() 恢复用户输入序列
* inputsecret() 按密文输入
* intputdialog() 从对话框中输入一行文本
在 VimL 脚本中与用户交互的最常用的函数是 `input(提示, 默认值, 补全方法)`。提示
字符串参数必须给,可以是空字符串,也可以用 `\n` 表示多行提示。后面两个参数可选
。Vim 首先在命令打印提示字符串,等待用户输入一行文本,按回车返回用户刚输入的这
行文本。如果直接回车没任何输入,则返回传给函数的默认值(或空字符串)。当用户输
入时,相当于编辑命令行,所以为便于用户输入,可提供补全方法,类似自定义命令那般
。而且用户的输入也有独立的命令行历史记录。
显然,`input()` 函数不宜用于启动配置 vimrc 中。此外,也要避免用于映射中,因为
映射的后续键相当于用户输入,会当作 `input()` 的回应输入。如果一定要用于映射中
,请在调用 `input()` 前后分别调用 `inputsave()``inputrestore()`
`inputsecret()` 用法一样,只是用户输入的文本不直接显示在屏幕命令行中,以星号
`*` 代替。此外也不支持补全,不放入历史记录中,因为这主要用于提示输入密码。
在 GUI 版本中,`inputdialog()` 可弹出对话框,让用户从对话框中输入,否则类似
`input()` 函数。
* inputlist() 让用户从一个列表中选择一项
* confirm() 也是让用户从列表中选择一项
`inputlist()` 接收一个字符串列表参数,Vim 将每个元素一行显示在命令行上方的消息
区,然后提示用户输入一个数字选择一项(GUI 版本可用鼠标)。注意按列表索引惯例,
`0` 表示选择第一项。为弥补这个反人类设计,这有个技巧:将提示文本写在列表的第一
项,后续有效选项字符串也以索引 `1.` `2.` 之类的开始,让用户能直观地选择数字。
让用户做选择还有另一个函数 `confirm()` ,它可用于 GUI 版本,也可用于终端版本。
它可接收四个参数,`confirm(提示,选项列表,默认选项,对话框类型)`,一般只用到
前两个。与 `inputlist()` 不同的是,提示文本为独立参数,且选项列表是字符串,用
回车分隔每一选项,且第一项是 `1` 。在每一项的字符串中,可以将 `&` 加在某个字符
之前,则按该字符时直接选择了项目(选择快捷键),且不像 `inputlist()` 那样会将
所按键显示在命令行中(因为其实这是为GUI版本设计的),也不需要多按回车确认,就
是快捷键直接选择。当然函数返回的仍是选项索引,并非快捷键字符。可选的默认选项参
数也应该是数字索引,不提供时默认 `0` ,算是无效选项。
* getchar() 获取用户按下的下一个键
* getcharmod() 获取用户按键时的修饰键
* feedkeys() 将一个字符串放入 vim 待响应的按序序列
`getchar()` 用于获取用户(或输入流)的下一个键。不同于 `input()` 进入命令行等
待交互,而是默默地等待获取下一输入键。相对细节很多,用到时请参考文档。因为 vim
本身的总体工作(消息)循环,就是等待用户按键,然后作出不同响应。
`getcharmod()` 用于获取修饰键(收到上个键时同时按下的修饰键),如 `shift = 2`
、`ctrol = 4` 、`alt = 8` 等。将可用修改键用二进制编码,返回一个数字就能表示哪
些修饰键被按下了。
`feedkeys()` 的用途就比较诡秘了。它把一个字符串放回输入流中,当作是用户的按键
输入序列。特殊按键用 `"\<标记>"` 表示。默认情况下,放回的这些字符键是可再
被重映射的,然而也有一些可选参数控制细节。
* browse() 打开浏览文件对话框
* browsedir() 打开目录选择对话框
这两个函数只能用于 GUI 版本,弹出标准对话框,让你选择一个文件或目录,返回所选
择的文件路径名。可以传入参数指定对话框标题及初始浏览目录。
* getfontname() 获取当前所用的字体
* getwinposx() 获取 gVim 窗口的坐标
* getwinposy() 获取 gVim 窗口的坐标
这几个函数只能用于 GUI 版本,检索 GUI 才用得到的信息。
### 异步通讯函数
自 Vim8 版本引入了一些全新的特性:任务(job)、定时器(timer)、通道(channel),
这都涉及异步编程,主要通过回调函数实现功能。为此也提供了一系列相关的内建 api
函数。不过本章不想罗列这些函数,毕竟需要理解相应的功能才有理解函数用法的意义。
留待后续章节专门讨论吧。