6.2 操作编辑对象

优质
小牛编辑
113浏览
2023-12-01

6.2 操作编辑对象

与 Vim 可视编辑的有关的几个概念对象是缓冲(buffer)、窗口(window)与标签页( tabpage),还有目前较少用到的在命令行参数提供的文件列表(argument list)。VimL 也提供了许多函数以供脚本来控制这些编辑对象。

编辑对象背景知识

很早期的 vi 一次只能编辑一个文件。不过从命令行启动时可以提供多个文件名参数, 首先编辑第一个文件,编辑完后可以接着编辑下一个文件。如以下命令启动:

$ vim file1 file2 file3

Vim 就记忆着这三个文件,称之为参数列表,相当于执行了如下 VimL 语句:

: let arglist = ['file1', 'file2', 'file3']

注意在 Vim 启动时,还可以加很多命令行选项(以 - 开头的参数),一般用于指定 Vim 以何种方式、何种配置等启动。这些选项在 Vim 启动过程中就会被处理掉,不会保 存在参数列表中,所以参数列表只保存待编辑的文件名。

后来,Vim 支持同时编辑多个文件。作为通用编辑器,配置好 vimrc 后,它也经常省 略命令行参数,直接以裸命令 $ vim 启动,其参数列表就为空 []。然后在 Vim 自 己的命令行中用命令 :edit file 打开要编辑的文件。

Vim 每打开一个文件,就创建一个缓冲(buffer),并记录相应的缓冲信息。即使打开另 一个文件,曾经打开的而目前看不见的文件,也记忆着它的缓冲,除非用命令显示地清除 它。Vim 的这个缓冲概念与系统缓存并不一样,对于非活跃 buffer (看不见的文件), Vim 也不可能将文件的所以内容留在内存中,尤其是打开了很多大文件。可认为 buffer 是 Vim 为每个编辑文件创建的一个对象,记录着一些必要的信息。但是,也不一定每个 buffer 都对应着文件系统内的物理文件(磁盘上的文件),例如新建 buffer 尚未保存 甚至未命名,还有很多标准插件与三方插件的辅助窗口中的特殊 buffer 根本就不想写入 文件。

然后正在编辑的活跃 buffer 必然是显示在窗口的。早期的 vi/vim 也只支持一个窗口 ,后来实现了多窗口。一个窗口只能装载一个 buffer,但一个 buffer 可以同时显示在 多个窗口中。再后来更扩展到多个标签页,每个标签页都可以分隔为多个窗口。

缓冲、窗口与标签页都被 Vim 顺序编号以便维护,这有点像参数文件列表的索引。不过 参数列表是真当作列表变量类型的,索引从 0 开始。而缓冲、窗口与标签页的编号都 从 1 开始。关闭文件并不意味着关闭缓冲,即使清除缓冲或隐藏缓冲也不会改变每个 缓冲的编号,但是关闭或移动窗口(或标签页),却会改变它们的编号。为此,自 Vim8 起,又引入窗口 id 概念,它是唯一且稳定的(不过似乎尚未有标签页 id 的概念)。

然而要指出,即使引入了缓冲概念,参数列表也还是有价值的。在有些情况下启动 vim 确实有明确目标要编辑某系列文件,将所有文件保存在参数列表中(其实在进入 vim 后 也可以提供或更改文件参数列表),就有很多批量命令能统一处理这系列文件。即使引入 窗口 id 概念,也还有窗口编号的价值。因为窗口编号更直观,从左到右,从上到下,很 容易知道哪个窗口是 1 2 3 4 。

查看缓冲可用如下命令之一:

: buffers
: ls

注意 :buffers 是有 s 后缀的复数形式,那才是打印缓冲列表的意思。如果是单数 命令 :buffer 则一般需要接个参数,用于打开另一个缓冲的意思。:ls 更简短,这 命令在 shell 中是列出文件意思,而在 Vim 中是列出缓冲的意思。这两个命令的输出中 ,包含缓冲编号及相应的文件名等信息。

在任一时刻,都有(正在编辑中的)当前缓冲,当前窗口与当前标签页的概念。如果提供 了参数文件列表,也有当前文件的概念。不过当前文件不一定与当前缓冲相同。因为常规 编辑命令不会改变参数列表,你可以用 :e:b 命令切换到编辑另一个可能并不在 参数列表中的“无关”文件,但在 vim 内部随参数文件列表保存的当前索引并不会改变。

最后,将这三个或四个概念统称为编辑对象。当了解这些编辑对象的意义后,就能更好地 理解相关的函数功能了。初学者可能会对缓冲与文件(参数列表)有所迷惑,日常使用时 可不求甚解认为缓冲即是指文件。不过编程时需要准确理解其中的不同。

获取编辑对象信息

  • bufnr() 获取缓冲编号
  • bufname() 获取缓冲名字
  • winnr() 当前窗口编号,winnr('$') 获取窗口数量即最大编号
  • tabpagenr() 当前标页面编号,'$' 参数获取标签页数量
  • tabpagewinnr() 某个标签页的当前窗口编号
  • bufwinnr() 获取某个缓冲的窗口编号
  • winbufnr() 获取某个窗口的缓冲编号

其中,bufnr()bufname() 的参数是一样意义,指示如何搜索一个缓冲,搜索失 败时前者返回 -1,后者返回空字符串:

  • 数字:即表示缓冲编号。bufnr(nr) 一般返回编号本身(无效时返回 -1)。 bufname(nr) 用于获取指定编号的缓冲文件名。
  • 字符串:除了以下特殊字符意义,将该字符串当作文件名模式去搜索缓冲,也就是说不 必指定文件全名,可以按文件名通配符(不同于正则表达式)搜索。但如果有歧义能匹 配多个,或未能匹配,都算失败,返回 -1 或空串。当然会优先匹配全名,如果要限 定只当作全名匹配,可加前后缀 ^$
  • 缺省:不能缺省参数,至少提供空字符串。
  • "":空字符串表示当前缓冲。
  • "%":也表示当前缓冲。
  • "#":表示另一个轮换缓冲(在编辑当前缓冲之前的那个缓冲)。
  • 0:数字零也表示另一个缓冲。

注意,bufname() 返回的缓冲名,与 :ls 命令输出的相应缓冲行的主体部分相同。 该缓冲名是否包含文件全路径名,可能与当前路径有关。所以,如果要在程序中唯一确定 一个缓冲,应该用 bufnr() 的返回值,bufname() 一般只用于显示。

bufnr() 函数不能无参数调用,空字符串或数字零都是有特殊意义的参数。但是, winnr()tabpagenr() 一般是无参数调用,以获取当前窗口(标签页)的编号, 而用 "$" 参数表示获取最后一后窗口(标签页)编号,也就是最大编号或其总数量。 tabpagewinnr() 用于获取另一个标签页的当前窗口编号,比 winnr() 多加一个标签 页编号参数在前面。因为每个标签页都有当前窗口的概念,即是最后驻留的那个窗口。此 外,"#" 参数可用于 winnr()tabpagewinnr() 表示之前窗口编号(即进入当 前窗口之前的那个窗口,<C-w>p:wincmd p 将进入的窗口);但不可用于 tabpagenr() 函数,因为 Vim 似乎没有维护之前标签页的概念。

以窗口编号为例,其典型调用方式小结如下:

  • winnr() 当前窗口编号
  • winnr('$') 最大窗口编号或窗口数量
  • winnr('#') 之前窗口的编号

因为一个缓冲可能显示在多个窗口中,所以 bufwinnr() 返回的是显示了指定缓冲的第 一个窗口编号。其参数与 bufnr()意义相同,可认为先调用 bufnr() 确定缓冲编号 再查找相应窗口编号。反之,一个窗口在一个时刻只显示一个缓冲,所以 winbufnr() 返回的缓冲编号是确定的。其参数是窗口编号,用 0 表示当前窗口,但不能像 winnr() 那样使用 $# 字符表示特殊窗口,否则字符串按 VimL 自动转换规则 转为数字 0,仍是调用 winbufnr(0)。

  • bufexists() 检测一个缓冲是否存在
  • buflisted() 检测一个缓冲是否能列表出来(:ls
  • bufloaded() 检测一个缓冲是否已加载
  • tabpagebuflist() 返回显示在某个标签页中的所有缓冲编号列表

以上三个检测缓冲状态的函数,所以接收的参数除了缓冲编号外,若字符必须是文件全名 (全路径或相对当前路径),并能像 bufnr() 的参数那样支持文件通配符。一些特殊 缓冲并不会被列表出来,取决于局部选项 &buflisted 的设置。已加载的缓冲是指显示 在某个窗口的缓冲,但如果一个缓冲设置了 &bufhidden 局部选项为可隐藏 hide, 则它即使不显示了也仍算加载状态。

若要获取所有已显示在窗口中的缓冲,可用 tabpagebuflist() 函数,它返回一个列表 ,收集了指定标签页中所有窗口内显示的缓冲(编号);缺省参数时指当前标签页。在所 有标签页中显示的缓冲都是已加载状态(但已载缓冲可能还包含一些隐藏缓冲),如下函 数可返回几乎所有已加载缓冲的列表:

function! BufLoaded() abort
    let l:lsBufShow = []
    for i in range(1, tabpagenr('$'))
          call extend(l:lsBufShow, tabpagebuflist(i))
    endfor
    return l:lsBufShow
endfunction
  • argc() 参数文件列表个数
  • argv() 参数文件列表,或返回指定索引的文件参数
  • argidx() 当前所处参数文件的索引
  • arglistid() 返回参数文件列表的ID

这是几个处理参数文件列表的函数。在去除启动选项后,argc()argv() 就是命 令行参数。无参数调用 argv() 返回整个文件列表,但可指定索引 argv(idx) 返回 相应的文件名,argc() 就是这个列表的长度,即文件个数,而 argidx() 是指所谓 的当前文件的索引。但是 Vim 还对参数文件列表作了扩展,除了从命令行启动时指定的 参数列表叫做全局参数文件列表外,还可以为每个窗口定义局部参数文件列表,所以有了 arglistid(winnr, tabnr) 函数用以返回某个指定窗口(参数都可缺省,即用当前窗口 或当前标签页)的参数文件列表,全局的参数文件列表 ID 用 0 表示。

  • win_getid() 获取指定标签页与窗口编号(可缺省默认当前)的窗口ID
  • win_gotoid() 切换到指定窗口ID的窗口,有可能切换当前标签页
  • win_id2win() 将窗口ID转换为窗口编号,只在本标签页查找
  • win_id2tabwin() 将窗口ID转换为二元组 [标签页编号, 窗口编号]
  • win_findbuf() 根据缓冲编号查找所有相应的窗口ID(是列表类型)

这几个处理 window-ID 的函数是从Vim8 版本引入的。函数名已经很望文生义了,可以 在窗口ID与窗口编号(及标签页编号)之间互相转换。要注意的是,每个标签页的窗口编 号都是从 1 开始重新编号,相互独立。但窗口ID是全局的,所有标签页的窗口共享一 套统一的ID。

获取编辑对象数据

前文在介绍 VimL 变量作用域时,提到三个特殊的局部作用域前缀 b: w: t: ,那 就是分别保存在特定缓冲、窗口与标签页的变量。如果仅用这个前缀,而无后缀主体变量 名,那就是表示收集了所有相应局部变量的字典(如 b: 也是个类似 s: 的特殊字典 )。从语义上理解,字典可当作一个对象,键当作属性。那么这些局部变量也就相当于相 应编辑对象的属性数据了。以下的 get/set 函数就是处理这些变量的函数:

  • getbufvar() 返回缓冲局部变量 b:
  • setbufvar() 设置缓冲局部变量的值
  • getwinvar() 返回窗口局部变量 w: (限当前标签页)
  • setwinvar() 设置窗口局部变量的值
  • gettabvar() 返回标签页局部变量 t:
  • settabvar() 设置标签页局部变量的值
  • gettabwinvar() 返回窗口局部变量 w:
  • settabwinvar() 设置窗口局部变量的值

以缓冲局部变量为例,函数参数原型是 getbufvar(缓冲, 变量名, 默认值)。其参数一 是缓冲编号或名字(类似 bufnr()的参数意义);参数二的变量名是没有 b: 前缀的 主体名字,即 b: 字典的键;参数三是默认值,当不存在相应变量时的返回值,该参数 可缺省,缺省时就是空字符串,即当变量不存在时也不会出错,而至少返回空字符串。参 数二变量名不可缺省,当它是空(字符串)时,返回 b: 字典本身。设值函数参数原型 时 setbufvar(缓冲,变量名,新值),第三参数不可缺省。

窗口局部变量取值与设值函数,可能与标签页有关。gettabwinvar(标签页号,窗口编号 ,变量名,默认值),需要在第一个参数前多插入一个标签页编号,如果取当前标签页的 窗口变量,则用 getwinvar(窗口编号,变量名,默认值)。窗口编号参数传 0 的话 ,表示当前窗口。

  • getbufinfo() 返回缓冲对象信息列表
  • getwininfo() 返回窗口对象信息列表
  • gettabinfo() 返回窗口对象信息列表

这三个函数是从 Vim8 引入的。其返回类型是字典的列表,即每个列表元素都是字典,字 典所包含的属性键依对象而不同。如果参数限定了一个对象,返回值也是包含一个元素的 列表;如果根据参数无法确定(搜索到)任一对象,则返回空列表。如果没有参数,则返 回由所有对象的信息字典组成的列表。

如果提供参数,getwininfo() 需传入窗口ID,而 gettabinfo() 传入标签页编号。 而 getbufinfo() 稍为复杂,除了可像 bufnr() 那样传入缓冲编号或名字外,还可 以用字典指定筛选缓冲的条件:buflisted 已列出的,bufloaded 已加载的。

这三个函数返回的对象信息字典,详细的键名解释请参考文档。但是都有一个键 variables (注意单词复数形式),其值是另一个字典(引用),即是特殊字典 b:w: t:。所以 get...info() 函数也实现了 get...var() 的功能,不过前者 所得信息大而全,用法更复杂。另外 get...var() 函数可获取局部选项的值,以 & 为前缀的变量名传入即可,但这无法由 get...info() 获得,因为选项值并不保存在 b: 字典中。

获取光标位置信息

显然,当前光标只有一个确定位置。但 Vim 另有一个光标标记(mark)的概念,用于 记忆多个位置信息。例如在普通模式下用 mx 命令,就定义了标记 x ,保存着当前 光标的位置。此后移动到他处后,再用命令 'x(单引用)就能跳回标记 x 的行首, 使用 \x(反引号)就跳回标记x的准确行列位置。每个缓冲都能让用户定义以小 写字母a-z为名的标记,称为局部标记;而大写字母为名的标记是全局的,可以跨文 件缓冲跳转。此外,Vim 还有些自动定义的标记,如在选择模式下按:进入命令行, 会自动添加:'<,'>`,那就分别表示选区起始行与终止行的标记。

  • line() 光标或标记的行号
  • col() 光标或标记的列号(字节索引)
  • virtcol() 光标或标记的屏幕占位列号
  • winline() 光标在当前窗口的行号
  • wincol() 光标在当前窗口的列号
  • screenrow() 光标在屏幕的行号
  • screencol() 光标在屏幕的列号

以上 line() col() 返回的行列号是相当缓冲文件而言。col() 是按字节列号的, 第一列是 10 用于表示错误列号。virtcol() 指屏幕占位列号,光标所在字符所 占的最后一列。假如一行全是汉字,光标停在第四个汉字上,col()10,因为前 三个汉字只 9 字节,第四汉字从第 10 字节开始;virtcol()8 ,因为每个 汉字占两列宽,第四个汉字已占到第 8 列。当有制表符 \t 时,屏幕列与字节显然 也是不同的。不过这三个函数必须带参数调用,字符串参数意义如下:

  • . 单点号表示当前光标
  • $ 当前行最后一列
  • 'x 表示 x 标记
  • v 用于选择模式下,表示选区起始(因当前光标只表示选区终止)

特殊用法是 col([行号, '$']) 可获得指定行的最后一列。

winline()wincol() 不带参数,只用于获取当前光标相对于窗口的行列号,因为 标记位置可能不在窗口显示区域,为标记调用这两个函数无意义。winline()line('.') 的意义不同显而易见,长文件经常滚动,窗口的第一行在不同时刻对应着文 件的不同行。水平滚动条不如垂直滚动条用得多,但即使无水平滚动,wincol() 可能 也与 col('.') 不同。仍以上例汉字行,光标停在第四汉字上,wincol() 返回的是 7,因为前三汉字占 6 屏幕宽度,第四字从第 7 开始。

因为 Vim 可以分隔多个窗口,所以屏幕行列号 screenrow() screencol() 又与窗口 行列号 winline() wincol() 不同。不过屏幕行列号一般只用于测试。且直接在命令 行手动输入 :echo screencol() 时,它始返回 1 ,因为执行命令时光标已经在命令 行首列了。

  • getpos() 获取光标或标记的位置信息
  • setpos() 设定光标或标记的位置信息
  • getcurpos() 获取当前光标的位置信息
  • cursor() 放置当前光标

顾名思义,可能会觉得 getpos() 就是 line()col() 的综合效果,但其实位 置信息不仅是行列号。getpos() 的返回值是一个四元列表 [bufnr, line, col, off], 其意义如下:

  • bufnr 缓冲编号,0 表示当前缓冲,只有在取跨文件的全局标记,才需要返回其所 在缓冲的编号,否则就是 0
  • line 行号,这就相当于 line() 函数了
  • col 列号,这就相当于 col() 函数了
  • off 偏移,只有在 &virtualedit 选项打开时才不是 0。比如 <Tab> 键可能 占多列,但在一般情况下移动光标时是直接跳过的,但在打开 &virtualedit 选项时 ,就可能移动到制表符中间某个位置了,这就是第四个返回值的意义。

setpos()getpos() 的对应函数,它所接收的第二参数就是后者返回的四元列表 。第一参数就是标记名 'x(注意含单引用,而非反引号) 或表示当前光标的 .

getcurpos() 无参数,只返回当前光标的位置信息,基本与 getpos('.') 功能相同 ,不过返回值列表还多一个第五元素 curswant ,它表示当光标垂直移动(jk)时, 它优先移动到的列号,因为当前列号在下一行或上一行未必是有效的,这时该移动到哪列 呢,这第五个返回值就有效果了。

cursor() 用于放置当前光标,从语义上是 getcurpos() 函数的“反函数”,但是却不 能将后者的返回参数传给前者。因为 getcurpos() 返回值是五元列表,而 curosr() 函数用不到其第一个返回值 bufnr,将第一个元素移除后的列表传给 cursor() 是可 行的。事实上,cursor() 还可以几个非列表的参数直接调用。如 cursor(line, col, off) ,或 coursor([line, col, off, curswant]) 当然,只有行列号是必须的。当 需要明确移动光标到某处时,直接调用 cursor(line, col) 是最方便的。当需要恢复 光标时,最好与 setpos() 联用,如:

: let save_cursor = getcurpos()
" 移动光标干活
:call setpos('.', save_cursor)
  • byte2line() 文件的第几字节处于第几行
  • line2byte() 第几行是从文件第几字节开始的

这两个函数将整个缓冲文件的字节索引与行号相互转换。注意包含换行符,换行符是一字 节还是两字节则与文件格式有关。line2byte(line("$") + 1) 可获取缓冲的大小,其 实比缓冲大小多 1,因为是文件最后一行的下一行的起始索引。除此之外,非法行号返 回 -1

  • winheight() 返回指定编号窗口的高度,参数 0 表示当前窗口
  • winwidth() 返回指定编号窗口的宽度
  • winrestcmd() 返回一系列可恢复窗口大小的命令
  • winsaveview() 保存当前窗口视图,返回一个字典
  • winrestview() 由保存的字典恢复当前窗口视图

注意,winrestcmd() 只能恢复窗口大小,以字符串形式返回,将它用于 :execute 执行后才能恢复窗口大小。而 winsaveview()winrestview() 能保存恢复比较完 整的窗口信息。其参数字典保存哪些键名及释义请参阅相关文档。

  • screenchar() 返回屏幕指定行列坐标的字符
  • screenattr() 返回屏幕指定行列坐标的字符有关的特征属性

Vim 的屏幕不仅包括缓冲窗口,还有标签页行,状态栏,命令行,窗口分隔符等都占据一 定屏幕坐标。不过这两个函数主要用于测试。

操作当前缓冲文本

然后是操作缓冲文件文本内容的函数,这是 Vim 作为文本编辑器的基础工作。

  • getline() 从当前缓冲中获取一行文本字符串,或多行组成的列表
  • setline() 从当前缓冲指定行开始替换文本行
  • append() 从当前缓冲指定行下方始插入文本行
  • getbufline() 从指定缓冲中获取文本行
  • wordcount() 统计当前缓冲的字节、字符、单词,返回值是字典

如果 getline() 传入一个行地址参数,则返回一个字符串;如果传入两个起止行地址 参数,则返回一个列表,每个元素为一行文本。行地址参数可以是数量或字符 . 表示 当前行,字符 $ 表示最后一行。setline() 可传入一个行地址参数,以及一个字符 串或字符串列表,用以替换指定行以及后续行。appendline() 用法与 setline() 一 样,不过是从指定行(下方)开始插入,并不会覆盖原有行。

getbufline()getline() 类似,不过是取其他缓冲,所以要在第一个参数多传入 一个缓冲编号或名字。另外,行地址参数不能用 . 点号表示当前行,因为在其他缓冲 的当前行意义不明显(用户角度),而且返回值必定是列表,即使只有一个起始行地址参 数,也是一个元素的列表。

  • mode() 当前的编辑模式:普通、选择、命令行等
  • visualmode() 上次使用的选择模式:字符、行、或列块选择

Vim 有很多种模式,在脚本中可用这两个函数获取模式信息,然后根据模式作不同的响应 工作。一个非常有用的用途是用于状态栏定制中,否则触发该函数的时刻经常是命令行模 式(通过命令行调用或加载脚本),或普通模式(映射中调用)。

  • indent() 指定行的缩进空白列数
  • cindent() 按 C 语法应该缩进的空白列数
  • lispindent() 按 Lisp 语法应该缩进的空白的列数
  • shiftwidth() 每层缩进的有效空白列数

这几个缩进函数其实都是只读函数,并不会改变缓冲内容(执行缩进操作的命令 =)。 indent() 是返回指定行(参数按 getline() 惯例)的当前实际缩进数,按缩进的空 白数计,如果缩进字符是制表符,与相关的制表符宽度选项有关。而 cindent()listindent() 是假设按 C 或 Lisp 语法规则缩进,该行应该缩进多少。需要根据这个 返回结果调用其他命令或函数执行真正的修改操作。与缩进相关的选项有好几个,而 shiftwidth() 函数是综合这几个选项的设置,给出的当前缓冲实际生效的每级缩进数 量。

  • nextnonblank() 寻找下一行非空行
  • prevnonblank() 寻找上一行非空行

这两个函数很简单,就是从参数指定的起始行地址查找非空行,如果起始行已经是非空行 ,直接返回该行地址。返回值是数字,失败时返回 0,因为行地址索引从 1 开始。作为 通用文本编辑器,Vim 假定文本文件用空行分隔段落。而且良好编程风格的大多数语言源 文件,也是应该有空行分隔段落的,所以这两个函数有时挺实用。

  • search() 搜索正则表达,返回行地址
  • searchpos() 搜索正则表达式,返回行列号组成的二元列表
  • searchpair() 按成对关键字搜索
  • searchpairpos() 按成对关键字搜索
  • searchdecl() 搜索一个变量的定义

这几个搜索函数可用于从脚本实现类似 / 的搜索命令,但有更灵活细致的控制。先看 最基本的搜索函数的参数原型 search(pattern, flag, stopline, timeout),只有第 一个参数是必须的:

  • {pattern} 就是 VimL 的正则表达式,在该函数中,一些影响搜索的选项如 &ignorecase '&magic' 等将影响正则表达式的解析。
  • {flag} 是一个字符串,每个字符表示不同的意义,一些冲突的标志不能并存:
    • b 表示反向搜索,默认正向搜索;
    • c 在光标处也能匹配成功;
    • e 光标移动到匹配成功处的末尾,默认移动到匹配处的起始位置;
    • n 即使匹配成功也不移动光标,但可利用函数的返回值,行地址;
    • p 返回值不再是行地址,而是匹配成功的(或连接)子模式索引加 1
    • s 移动光标到匹配处前,将原位置保存在特殊标记 ' 中;
    • w 搜索到文件末尾时,折回文件起始,与 b 并存时是到文件首折回;
    • W 搜索到文件末尾或起始时不折回;
    • z 从光标的列位置开始搜索,默认是从光标所在行首开始搜索。
  • {stopline} 搜索从当前光标开始,可指定终止搜索的行。
  • {timeout} 按毫秒数指定搜索的时间,搜索可能是个费时的操作,尤其是正则表达式 写得复杂写得低效时,可指定时间,超时不再搜索。

所以这个函数有两个作用,一是返回值表示匹配的行地址,另一个副作用是会移动光标, 除非指定 n 标志不移动光标。匹配失败时返回 0 ,当然也不会移动光标。

searchpos() 函数意义一样,只是返回多值(列表),除行号外,还返回列号,如果指 定 p 标记,还返回所匹配的子模式索引(加 1)。这里的子模式是指由或操作 \| 连接的多个模式分支,匹配其中任一个都算匹配成功,但若需要知道匹配的是哪个分支, p 标记就有用了,注意需要被索引标记的子模式还得整个放在 \(\) 中。

searchpair() 成对搜索的意义类似在 VimL 脚本(或其他类似语法的语言,需加载自 带的 matchit 插件)中在 if 关键字中按 % 命令,它会搜索配对的 endif 以 及中间的 elseif。其参数就是在 search() 的基础上,将第一个正则表达式参数 {pattern} 换为三个正则表达式参数 ({start}, {middle}, {end}, ...)。并且可以 在可选参数 {flag}{stopline} 之间再加一个可选参数 {skip} ,其意义是表 示如何忽略某些匹配,比如 elseif endif 在注释或字符串中应该是要忽略的。{skip} 是一个可执行字符串,当作表达式执行后返回非 0 就表示要忽略,执行时光标相当于已 移动到匹配处。

searchpairpos() 的意义也类似,返回多值,即由行列号组成的列表而已。

searchdecl() 的作用与 gdgD 普通命令类似,当然命令是取光标下的单词, 函数需要将变量名字符串当作参数传入。

  • getcharsearch() 获得字符搜索信息
  • setcharsearch() 设定字符搜索信息

这两个函数是从 Vim8 版本新增的。字符搜索是指 f F t T 这几个命令用于实 现行内搜索字符的,同时还有分号 ; 与逗号 , 按正反向重复上次字符搜索。如果要 从脚本控制这种行为,可参考这两个函数。

修订窗口(quickfix)

很多命令会生成一个所谓的 quickfix 列表,这里将其译为修订。最早的应用来源是编 译源代码给出的错误列表,每条项目会指出错误出现的文件、位置等,用于方便定位错误 并修改。后来该概念扩展到其他许多命令,比如 grep 搜索,所以它就是一个有关定位 的列表。该列表显示在单独的窗口中,就叫做修订窗口,可在该窗口预览各个“错误”信息 ,并像在普通窗口上移动,然后有方便的命令跳到相应位置外,并遍历整个列表。

据说最早的 Vim 版本并无此功能,只是一个插件功能,后来由于功能太过强大实用,就 整合为 Vim 的内置功能了。而且还扩展出了局部修订列表的概念,即每个窗口都可以有 自己的修订列表了。术语上,qflist 是全局的,locallist 是局部的。

  • getqflist() 获得修订列表
  • setqflist() 设置修订列表
  • getlocallist() 获得局部修订列表
  • setlocallist() 设置局部修订列表

getqflist() 返回的是字典列表,每个字典元素的键名解释请参考相应文档。 setqflist() 接收这样的字典列表作为参数,并且有个可选的参数指出是添加到原修订 列表末尾还是覆盖原列表。后两个函数用法一样,不过在最前面多插入一个参数指出窗口 编号(不是窗口ID)。

  • taglist() 获得匹配的 tag 列表
  • tagfiles() 获得 tag 文件列表

tag 文件是外部文件,记录着一些 tag (如变量名、函数名、类名等需要在大项目 中检索与交叉引用的东西)的定义位置,该文件是由外部程序扫描(所有相关)源文件生 成的,并遵循一定的格式。有了这样的文件,才能使用快捷键 C-]:tag 命令。 而 taglist() 是其函数形式,参数就是所要检过的 tag 名称,以正则表达式解析, 要提供全名应自行加上 ^$ 界定。

Vim 使用的 tag 文件可用选项 &tags 设置,它是以逗号分隔的文件名字符串。函数 tagfiles() 返回的是当前缓冲实际所用的 tag 文件列表(VimL 列表类型)。

  • complete() 设置补全列表
  • complete_add() 向补全列表中增加条目
  • complte_check() 检查是否终止补全
  • pumvisible() 检查是否弹出补全窗口

插入模式下的补全是相对高级的话题。Vim 的默认模式是普通模式,定制插入模式本身就 比较复杂。VimL 只提供了几个 api 函数。complete() 是简单地提供补全列表。 complete_add()complete_check() 只能用于自定义的补全函数(&compltefunc) 中。

Vim 本身只是定位于通用文本编辑器,并非程序开发 IDE,但提供了这些基本接口,允许 三方插件将其打造成的类似IDE的大多功能。尤其是 Vim8 版本新增的异步功能,能显著 增加补全的性能与可用性。此不再详述,这些高级话题可能另辟章节讨论。

命令行信息

最后看几个有关命令行的函数。因为命令行也是可编辑区域,也是可以通过脚本访问的, 不过一般只适于正在编辑命令行时使用,比如 :cmap 定义的映射等。

  • getcmdline() 获得当前命令行
  • getcmdpos() 获得光标在命令行的列位置
  • setcmdpos() 设置光标在命令行的列位置
  • getcmdtype() 获取命令行类型
  • getcmdwintype() 获取命令行窗口类型

命令行类型比如通过 : / ? 进入的命令行都是属于不同的命令行类型。命令行窗 口是通过特殊键在命令之上再打开的一个窗口,里面是命令行历史记录列表,可以方便选 择某个历史命令或在彼基础上作小修改后再次执行。故 getcmdwintype() 只有在命令 行窗口时才有意义,其值与 getcmdtype() 相同。

  • getreg() 获取某个寄存器的内容
  • setreg() 设置某个寄存器的内容
  • getregtype() 获取某个寄存器的类型

寄存器相当于 Vim 自己管理的剪贴板,允许用户自命名的寄存器有 26 个(即单字母表示), 另外 Vim 还自动更新了许多以特殊符号表示的寄存器,各表示相应的特殊意义。寄存器 的内容可用 :registers 查看。这几个函数则用于脚本访问与控制寄存器。此外,对于 常规字母命名的寄存器,以 @ 前缀的变量可直接表示该寄器(如 @a)。寄存器类型 与选择类型(字符、行、列块)相烦,因为寄存器内容经常是选择后复制进去的。

用 Vim 编辑文本要善于利用命令行与寄存,这几个函数一般只在映射(调用)中比较有 效果。