Vim documentation: repeat
*repeat.txt* For Vim version 8.0. 最近更新: 2017年8月
VIM 参考手册 by Bram Moolenaar
译者: Willis
http://vimcdoc.sf.net
重复命令,Vim 脚本和调试 *repeating*
用户手册第 26 章 |usr_26.txt| 介绍了重复命令。
1. 单次重复 |single-repeat|
2. 多次重复 |multi-repeat|
3. 重复组合操作 |complex-repeat|
4. 使用 Vim 脚本 |using-scripts|
5. 使用 Vim 软件包 |packages|
6. 创建 Vim 软件包 |package-create|
7. 调试脚本 |debug-scripts|
8. 刨视 |profiling|
==============================================================================
1. 单次重复 *single-repeat*
*.*
. 重复上次改变,有计数前缀的重复 [count] 次。
如果 'cpoptions' 包括 'y' 标志, 也可以用来重复一个抽
出 (yank) 操作。不能重复命令行命令。
简单的改变操作可以用 "." 重复。如果没有数字前缀,使用上次的改变的重复次数。如
果输入一个重复次数,就用它取代上次的计数。设置 v:count 和 v:count1。
如果上次改变的操作使用数字编号的寄存器,则逐次递增使用寄存器的序号。一个使用的
示例可以参见 |redo-register|。
注意,如果重复的命令涉及可视的选择区域,使用相同_大小_的区域。参见
|visual-repeat|。
*@:*
@: 重复上次命令行 [count] 次。
{仅当编译时加入 |+cmdline_hist| 特性才可用}
==============================================================================
2. 多次重复 *multi-repeat*
*:g* *:global* *E148*
:[range]g[lobal]/{pattern}/[cmd]
在 [range] 界定的匹配模式 {pattern} 的文本行上执行
Ex 命令 [cmd] (缺省是 ":p")。
:[range]g[lobal]!/{pattern}/[cmd]
在 [range] 界定的_不_匹配模式 {pattern} 的文本行上执
行 Ex 命令 [cmd] (缺省是 ":p")。
*:v* *:vglobal*
:[range]v[global]/{pattern}/[cmd]
等同于 :g!。
除了用 '/' 来包围 {pattern} 以外,你可以用任何其它单字节字符,但不包括字母、
'\'、'"' 或 '|'。这对你想在搜索模式或者替代字符串里包含 '/' 很有用。
模式的定义见 |pattern|。
注意 [cmd] 可能包含范围;示例见 collapse 和 edit-paragraph-join。
global 命令先扫描 [range] 界定的行,然后对每个匹配的行进行标记 (对于一个跨行的
匹配,则只针对开始的一行)。
第二次扫描对每个标记的行,就像光标在那行那样,执行指定的 [cmd] 命令。 ":v" 和
":g!" 命令针对未标记的行进行。如果一行被删除,其标记也自动消失。
缺省的 [range] 是整个缓冲区 (1,$)。用 "CTRL-C" 中断该命令。如果某行的执行给
出错误,该行的执行被中断,但 global 命令仍从下一个被标记或未被标记的行继续。
*E147*
命令递归使用时,只能作用于一行。此时不能给出范围。可用于找到所有匹配某模式,但
不匹配另一模式的行: >
:g/found/v/notfound/{cmd}
这会先找到所有包含 "found" 的行,但只当不匹配 "notfound" 时才执行 {cmd}。
要执行一个非 Ex 的命令, 你可以使用 ":normal" 命令:
:g/pat/normal {commands}
要确保 {commands} 以完整的命令结束,不然 Vim 会等待你输入该命令余下的部分,对
每次匹配都将如此。由于屏幕此时还未更新,你恐怕会因此不知所措。参见 |:normal|。
撤销/重做命令将会针对整个 global 命令。换而言之,前次上下文标记只有在命令开头
会设一次 (用 "''" 可以回到 global 之前的光标位置)。
global 命令也设置最近使用的搜索模式和最近使用的替换模式 (后者是 vi 兼容的),以
便于全局替换一个字符串: >
:g/pat/s//PAT/g
该命令替换所有的 "pat" 为 "PAT"。同样的功能也可以用: >
:%s/pat/PAT/g
实现。这样省下了两个字符!
Ex 模式下执行 "global" 时,使用 ":visual" 命令是特例。该命令移动到匹配行,进入
普通模式并在该模式下执行命令,直到你用 |Q| 回到 Ex 模式为止。对每个匹配行重复
此操作。此时不能再用 ":global"。要中止,按 CTRL-C 两次。
==============================================================================
3. 重复组合操作 *complex-repeat*
*q* *recording*
q{0-9a-zA-Z"} 在寄存器 {0-9a-zA-Z"} 里记录键入的字符 (大写名字的寄存
器表示附加键入的内容)。'q' 命令不能在执行寄存器时使
用。同样,在映射和 |:normal| 里也不能。
注意: 如果用作记录的寄存器用于 y 和 p,其结果多数不如
你的预想,因为 p 会粘贴记录的宏,而 y 会覆盖记录的宏。
{Vi: 没有记录功能}
q 停止记录。(注意 实现细节: 停止记录的 'q' 不会被保存在
寄存器里,除非它是映射的结果) {Vi: 没有记录功能}
*@*
@{0-9a-z".=*+} 执行寄存器 {0-9a-z".=*+} 的内容 [count] 次。 注意 不能
用寄存器 '%' (当前文件名) 和 '#' (轮换文件名)。
寄存器的执行方式与映射类同,'wildchar' 和 'wildcharm'
的差异适用此处。
"@=" 则会提示你输入一个表达式。这个表达式的结果会被执
行。
参见 |@:|。{Vi: 只有命名寄存器可用}
*@@* *E748*
@@ 执行上次执行的 @{0-9a-z":*} [count] 次。
:[addr]*{0-9a-z".=+} *:@* *:star*
:[addr]@{0-9a-z".=*+} 把寄存器 {0-9a-z".=*+} 里的内容看成一个 Ex 命令执行。
首先,把光标放在 [addr] 行 (缺省是当前行) 上。如果
'cpoptions' 设定了 'e' 标志位而寄存器的内容最后一行没
有 <CR>,会自动加上。
注意 ":*" 命令只有在 'cpoptions' 设置 '*' 标志位时才会
识别。如果用 'nocompatible',这_并_非缺省。
":@=" 使用最近使用的表达式。表达式执行的结果被当作 Ex
命令执行。
这些命令里,不识别映射。
{Vi: 只有某些版本有此功能}
将来: 会对地址范围里每一行执行寄存器的命令。
*:@:*
:[addr]@: 先把光标放在 [addr] 行 (缺省是当前行) 上,然后重复上次
的命令行。 {Vi 无此功能}
:[addr]@ *:@@*
:[addr]@@ 先把光标放在 [addr] 行 (缺省是当前行) 上,然后重复上次
的 :@{0-9a-z"}。 {Vi 只有某些版本有此功能}
==============================================================================
4. 使用 Vim 脚本 *using-scripts*
参阅用户手册第 41 章 |usr_41.txt| 了解如何写 Vim 脚本。
*:so* *:source* *load-vim-script*
:so[urce] {file} 从 {file} 里读取 Ex 命令,即 ":" 开头的命令,并执行。
激活 |SourcePre| 自动命令。
:so[urce]! {file} 从 {file} 里读取 Vim 命令,就像你在普通模式下键入的命
令一样。
如果要执行的命令在 |:global|、|:argdo|、|:windo| 或
|:bufdo| 之后、在循环体内、或者有另外一个命令紧跟其
后,那么执行时不会更新屏幕显示。
{Vi 无此功能}
*:ru* *:runtime*
:ru[ntime][!] [where] {file} ..
从 'runtimepath' 和/或 'packpath' 指定的目录里查找
{file}。从匹配的文件里读取 Ex 命令。如果没有匹配的文
件,不报错。
例如: >
:runtime syntax/c.vim
< 可以指定多个以空格分隔的 {file} 参数。每个 {file} 都从
'runtimepath' 指定的第一个目录开始查找,然后是第二个、
第三个,等等。{file} 里可以通过加反斜杠来包含空格 (不
过,为了避免麻烦,最好不要在文件名里使用空格)。
如果使用了 [!],所有找到的文件都被执行。否则,只执行第
一个找到的文件。
如果 [where] 省略,只使用 'runtimepath'。其它可用值是:
START 搜索 'packpath' 里的 "start"
OPT 搜索 'packpath' 里的 "opt"
PACK 搜索 'packpath' 里的 "start" 和 "opt"
ALL 先用 'runtimepath',然后搜索
'packpath' 里的 "start" 和 "opt"
如果 {file} 包含通配符,它被扩展为所有的匹配文件名。例
如: >
:runtime! plugin/*.vim
< 这是 Vim 启动的时候启动插件所使用的命令。类似的: >
:runtime plugin/*.vim
< 只会执行其中的第一个文件。
当 'verbose' 至少为一时,如果没有文件找到,会显示信
息。
当 'verbose' 至少为二时,对每个搜索到的文件都会提示信
息。
{Vi 无此功能}
*:pa* *:packadd* *E919*
:pa[ckadd][!] {name} 在 'packpath' 里搜索可选的插件目录,然后执行找到的插件
文件。目录必须匹配:
pack/*/opt/{name} ~
如果目录还不在 'runtimepath',加入之。
如果目录 pack/*/opt/{name}/after 存在,在
'runtimepath' 的尾部加入之。
注意 {name} 是目录名,而不是 .vim 文件名。所有匹配模式
的
pack/*/opt/{name}/plugin/**/*.vim ~
文件都被执行。这样可以允许 "plugin" 下使用子目录,就像
'runtimepath' 里的插件一样。
如果还未打开文件类型检测 (这通常是在 .vimrc 里的
"syntax enable" 或 "filetype on" 命令完成的),也会寻找
"{name}/ftdetect/*.vim" 这样的文件。
如果加入可选的 !,不载入插件文件或 ftdetect 脚本,只把
匹配的目录加入 'runtimepath'。这可用于 .vimrc。插件会
随后在初始化时载入,见 |load-plugins|。
另见 |pack-add|。
*:packl* *:packloadall*
:packl[oadall][!] 载入 'packpath' 每个项目的 "start" 目录的所有软件包。
首先把所有找到的目录加入 'runtimepath',然后执行所有目
录里找到的插件。这允许插件依赖于另一个插件的某个部分,
例如 "autoload" 目录。|packload-two-steps| 说明此功能
如何有用。
这一步通常在启动时在载入 .vimrc 文件后自动完成。用此命
令,可以在更早时执行这一步。
软件包只载入一次。此命令后不会再发生。但如果加入可选的
!,即使已经载入过,此命令还会再次载入软件包。
执行脚本发生错误时终止该脚本,后续的插件继续载入。
见 |packages|。
:scripte[ncoding] [encoding] *:scripte* *:scriptencoding* *E167*
指定脚本使用的字符编码。后续以 [encoding] 编码的脚本行
会被转换成 'encoding' 选项所指定的编码,如果两者不同的
话。如: >
scriptencoding iso-8859-5
scriptencoding cp932
<
如果 [encoding] 为空,则不作任何转换。这可以用来避免对
一些行进行转换: >
scriptencoding euc-jp
... 被转换的行 ...
scriptencoding
... 不被转换的行 ...
< 如果系统不支持所需的转换,不会有错误信息,但转换也不会
发生。如果不能转换某一行,不报错,保留原来的行。
不要用 "ucs-2" 或者 "ucs-4"。Vim 不能用这些编码的脚本
(因为其中会有 NUL 字节)。如果一个待执行的脚本以一个
BOM (Byte Order Mark 字节顺序标记) 开头,Vim 会识别其
为 utf-8 编码,从而无须指定 ":scriptencoding utf-8"。
如果在 |.vimrc| 里设置 'encoding' 选项,
`:scriptencoding` 必须在放在它后面。例如: >
set encoding=utf-8
scriptencoding utf-8
<
如果编译时没有指定 |+multi_byte| 特性,这个命令会被忽
略。
{Vi 无此命令}
*:scr* *:scriptnames*
:scr[iptnames] 列出所有执行过的脚本名字,以它们初次执行之顺序排列。排
列的次序号码被用作相应的脚本 ID |<SID>|。
{Vi 无此功能} {仅当编译时加入 |+eval| 特性才有功能}
*:fini* *:finish* *E168*
:fini[sh] 停止执行脚本。只能用在 Vim 脚本中,来快速跳过脚本的其
余内容。如果出现在 |:try| 之后但在相应的 |:finally|
(如果存在的话) 之前,":finally" 到 |:endtry| 的内容还
会执行。执行完所有嵌套的 ":try" 层的 ":finally" 代码
后,最外层的 ":endtry" 才会最终真正停止脚本的执行。
{Vi 无此功能}
所有的命令和命令序列可以通过把它们放在命名的寄存器里执行来重复调用。有两个方法
可以把命令放在寄存器里:
- 用记录命令 "q"。可以键入一串命令,在执行的同时它们被存入一个寄存器里。这很简
明,因为你能看见你所做的事情。如果你敲错了,用 "p" 把寄存器的内容 "放置" 在
一个文件里,然后编辑这个命令序列,把它们再次放回到 (比如,用删除命令) 寄存器
里。你也可以用大写名字的寄存器名来附加命令,从而继续上次的纪录。
- 删除 (delete) 或者抽出 (yank) 命令序列到寄存器。
常用的命令序列可以用 ":map" 命令映射到一个功能键上。
另外一个办法则是把命令写到一个文件里,用 ":source!" 命令执行之。这对很长的命令
序列有用。你甚至可以把它和 ':map' 命令混合使用,从而用一个功能键来储存复杂的功
能。
':source' 命令从文件里逐行读取 Ex 命令。如果其间需要键盘输入,你需要自己键入。
'source!' 命令从脚本里逐字读取命令,就像你自己敲入每一个字符一样。
示例: 如果你给出一个 ':!ls' 命令,你得到一个 |hit-enter| 提示。如果你用
':source' 执行包含 '!ls' 一行的文件,你必须按一个回车。不过如果你用 ':source!'
来执行包含 ':!ls' 的文件,其后的字符会依次读入,直到遇到一个 <CR> 为止。你不需
要通过键盘键入这个 <CR>,除非 ":!ls" 是文件的最后一行。
在脚本里当然也可以有 ':source[!]' 命令,从而可以建立一个自顶而下的脚本调用树。
':source' 命令允许的嵌套深度由同时打开的最大的文件数目决定 (大概 15 个左右),
':source!' 命令许可的嵌套深度则最多为 15 层。
在被执行的文件里,你可以在需要文件名的地方用 "<sfile>" 字符串 (直接的文本,不
是一个特殊的键)。它会被被执行的文件的名字来代替。例如,如果你在 ".vimrc" 文件
相同的目录里有一个 "other.vimrc" 文件,你可以在 ".vimrc" 里如此调用它: >
:source <sfile>:h/other.vimrc
在脚本文件里,依赖于终端的键码由不依赖于终端的两个字符码代表。这样,他们就能在
不同的终端里代表相同的意义。这里,第一个字符码是 0x80 或者 128,屏幕上显示
为 "~@"。第二个字符可以在 |key-notation| 列表里找到。这些编码也可以用 CTRL-V
加上一个三位数字的十进制码来键入。这个方法_不_适用于 <t_xx> termcap 代码,它们
只能用在映射里。
*:source_crnl* *W15*
MS-DOS、Win32 和 OS/2: 用 ":source" 执行的文件通常每行以 <CR><NL> 结尾。这没有
问题。在 'fileformats' 非空并且第一行不以 <CR> 结尾的时候,用 <NL> 的行结尾
(比如,Unix 编写的文件) 会被识别。不过,如果第一行里有 ":map <F1> :help^M" 这
样的内容 (其中 ^M 是一个 <CR>),这个机制会失败。如果第一行以
<CR> 结尾,但其后的行不是,那你会得到错误信息,因为第一行里的 <CR> 会丢失。
Mac Classic: 用 ":source" 执行的文件通常每行以 <CR> 结尾。这没有问题。在
'fileformats' 非空并且第一行不以 <CR> 结尾的时候,用 <NL> 的行结尾 (比如,Unix
编写的文件) 会被识别。如果用 <NL> 行结尾的时候,要小心第一行不要有 <CR>。
在别的系统上,Vim 期待 ":source" 执行的文件以 <NL> 行结尾。这没有问题。如果你
的文件用 <CR><NL> 结束行 (比如,MS-DOS 编写的文件),所有的行都会有一个附尾的
<CR>。有些命令 (比如映射命令) 会因此有问题。这里不使用自动换行符识别机制,因为
第一行出现以 <CR> 结尾的映射命令很常见,自动机制这时容易出错。
*line-continuation*
":source" 执行的 Ex 命令脚本里的长行可以用通过在下一行的开始插入续行符 "\" (反
斜杠) 来分开。反斜杠之前可以出现空格,它们将被忽略。
示例: 如下几行 >
:set comments=sr:/*,mb:*,el:*/,
\://,
\b:#,
\:%,
\n:>,
\fb:-
会被解读为一行: >
:set comments=sr:/*,mb:*,el:*/,://,b:#,:%,n:>,fb:-
每行反斜杠之前的所有引导空白字符会被忽略。注意: 在此之前的一行的行尾的空格可能
不能随便添加;这由命令分开的位置决定,那里也许可以也许不可以有额外的空格。
需要空格时最好放在反斜杠之后。行尾的空格很难注意到,很可能会被意外删除掉。 >
:syn match Comment
\ "very long regexp"
\ keepend
在用 ":append" 和 ":insert" 命令时,有一个问题: >
:1append
\asdf
.
反斜杠被看作续行符,所以这等价于以下命令: >
:1appendasdf
.
要避免这一点,在 'cpoptions' 选项里加上 'C' 标志位: >
:set cpo+=C
:1append
\asdf
.
:set cpo-=C
要 注意 在函数里的命令里用到这些命令时,你需要在定义函数的时候加上 'C' 标志
位,不是在执行函数的时候。 >
:set cpo+=C
:function Foo()
:1append
\asdf
.
:endfunction
:set cpo-=C
原理:
许多程序用行尾的反斜杠来指示行要继续。如果这样,Vim 会和 Vi 不兼容。例
如下面的 Vi 映射: >
:map xx asdf\
< 因此,只能用特殊形式的出现在行首的反斜杠。
==============================================================================
5. 使用 Vim 软件包 *packages*
Vim 软件包是包含一个或多个插件的目录。和普通插件相比,它的优点是:
- 软件包可作为归档下载,并可解压到单独的目录。这样文件就不会和其它插件的文件混
杂。这样方便更新和删除。
- 软件包可以用 git、mercurial 等版本库。这样更新尤其方便。
- 软件包可包含相互依赖的多个插件。
- 软件包可包含启动时自动载入的插件和通过 :packadd 加入仅当需要时才载入的插件。
使用软件包并自动载入 ~
让我们假定,你的 Vim 文件在 "~/.vim" 目录里,而你想加入来自 zip 归档
"/tmp/foopack.zip" 的软件包:
% mkdir -p ~/.vim/pack/foo
% cd ~/.vim/pack/foo
% unzip /tmp/foopack.zip
目录名 "foo" 是任意的,可以选任何名字。
现在在 ~/.vim 里就有这些文件:
pack/foo/README.txt
pack/foo/start/foobar/plugin/foo.vim
pack/foo/start/foobar/syntax/some.vim
pack/foo/opt/foodebug/plugin/debugger.vim
Vim 启动时,处理完 .vimrc 后,扫描 'packpath' 里的所有目录,寻找
"pack/*/start" 目录里的插件。首先这些目录被加到 'runtimepath' 里。然后载入所有
插。|packload-two-steps| 说明此功能如何有用。
此例中,Vim 会找到 "pack/foo/start/foobar/plugin/foo.vim" 并把
"~/.vim/pack/foo/start/foobar" 加入 'runtimepath'。
如果 "foobar" 插件把 'filetype' 设为 "some",Vim 会找到 syntax/some.vim
文件,因为此目录在 'runtimepath' 里。
如果有的话,Vim 也会载入 ftdetect 文件。
注意 不自动载入 "pack/foo/opt" 里的文件,只载入 "pack/foo/start" 里的。下面的
|pack-add| 说明 "opt" 目录的用法。
如果关闭了插件的载入,不会自动载入软件包,见 |load-plugins|。
要再早些载入软件包,以便更新 'runtimepath': >
:packloadall
这里即使关闭了插件载入也可以。自动载入只会发生一次。
如果软件包有 "after" 目录,此目录加入 'runtimepath' 的尾部,这样那里的内容会较
后载入。
使用单个插件并自动载入 ~
如果没有软件包但有单个插件,需要建立额外的目录结构:
% mkdir -p ~/.vim/pack/foo/start/foobar
% cd ~/.vim/pack/foo/start/foobar
% unzip /tmp/someplugin.zip
现在有这些文件:
pack/foo/start/foobar/plugin/foo.vim
pack/foo/start/foobar/syntax/some.vim
此后,工作方式就和上面一样。
可选插件 ~
*pack-add*
要从软件包里载入可选插件,用 `:packadd` 命令: >
:packadd foodebug
从 'packpath' 里搜索 "pack/*/opt/foodebug",找到
~/.vim/pack/foo/opt/foodebug/plugin/debugger.vim 并执行之。
可以只在满足某些条件时才这样做。例如,取决于 Vim 是否支持某特性或缺少某依赖关
系。
也可以在启动时载入可选插件,在 |.vimrc| 里放上: >
:packadd! foodebug
额外的 "!" 确保 Vim 以 |--noplugin| 启动时不载入插件。
软件包只包含 "opt" 目录的文件是完全正常的。每个插件只有在需要用的时候才载入。
什么放在哪里 ~
因为通过 `:colorscheme` 载入的色彩方案可在 "pack/*/start" 和 "pack/*/opt" 下找
到,你可以把它们放在任何地方。我们建议你放在 "pack/*/opt" 底下,例如
".vim/pack/mycolors/opt/dark/colors/very_dark.vim"。
文件类型插件应该在 "pack/*/start" 下,这样总能找到它们。除非某个文件类型有多于
一个插件,而你想用 `:packadd` 选择载入哪个。例如,取决于编译器版本: >
if foo_compiler_version > 34
packadd foo_new
else
packadd foo_old
endif
软件包内最没用的是 "after" 目录。但不禁止。
==============================================================================
6. 建立 Vim 软件包 *package-create*
这里假定你在编写一个或多个插件,并想以软件包形式分发。
两个无关的插件要用两个软件包,这样 Vim 用户可以选择要装什么。也可以决定用带可
选插件的软件包,然后告知用户用 `:packadd` 加入他们想要的.
你要决定你如何分发软件包。可以建立一个归档或使用版本库。归档可以被更多的用户使
用,但更新版本要困难一些。版本库可以方便的保持更新,但需要 "git" 之类的程序。
也可以两者都用,github 可以自动为发布版建立归档。
目录布局应该是这样:
start/foobar/plugin/foo.vim " 总是载入,定义命令
start/foobar/plugin/bar.vim " 总是载入,定义命令
start/foobar/autoload/foo.vim " 使用 foo 命令时载入
start/foobar/doc/foo.txt " foo.vim 的帮助
start/foobar/doc/tags " 帮助标签
opt/fooextra/plugin/extra.vim " 可选插件,定义命令
opt/fooextra/autoload/extra.vim " 使用 extra 命令时载入
opt/fooextra/doc/extra.txt " extra.vim 的帮助
opt/fooextra/doc/tags " 帮助标签
这样用户就可以: >
mkdir ~/.vim/pack/myfoobar
cd ~/.vim/pack/myfoobar
git clone https://github.com/you/foobar.git
这里 "myfoobar" 是用户可选的名字,唯一的条件是和其它软件包的名字不同。
在你的文档里,你要解释插件的用途,并告知用户如何载入可选插件: >
:packadd! fooextra
可以在你提供的插件之一加入此 packadd 命令,在需要可选插件时执行。
运行 `:helptags` 命令生成文档/标签文件。把此生成文件包含在软件包内,用户就可以
把软件包放在自己的 pack 目录里,立即可以使用 help 命令。不要忘记改变插件帮助后
重新运行此命令: >
:helptags path/start/foobar/doc
:helptags path/opt/fooextra/doc
插件间的依赖关系 ~
*packload-two-steps*
假定你有两个插件,它们都依赖于同一个功能。可以把公共的功能放在 autoload 目录
里,以便自动找到。你的软件包里有以下文件:
pack/foo/start/one/plugin/one.vim >
call foolib#getit()
< pack/foo/start/two/plugin/two.vim >
call foolib#getit()
< pack/foo/start/lib/autoload/foolib.vim >
func foolib#getit()
这样可行,因为软件包在载入时先把所有找到的目录加入 'runtimepath',然后再执行这
些插件。
==============================================================================
7. 调试脚本 *debug-scripts*
除了在自己的脚本里提示明显的消息,Vim 提供了调试模式来让你了解自己的代码在做什
么。你可以单步执行脚本文件和函数和设置断点。
请 注意: 调试模式远未完善。调试程序会对 Vim 的工作产生副作用。你不能用它调试一
切细节。例如,调试信息会弄乱屏幕的显示。
{Vi 没有调试模式}
另外一个办法是设置 'verbose' 选项。设置一个比较大的数字,你会得到 Vim 在做什么
的更详尽的信息。
启 动 调 试 模 式 *debug-mode*
以下方法可以进入调试模式:
1. 用 |-D| 参数启动 Vim: >
vim -D file.txt
< 调试会在执行第一个 vimrc 文件的时候开始。这有助于了解 Vim 启动的时候干了些
什么。一个副作用是 Vim 会在初始化完成之前切换终端模式,这会有意想不到的后
果。
对只用 GUI 的版本 (Windows、Macintosh) 调试会在 GUI 窗口打开的一刻开始。要
提早进入调试,在 vimrc 文件里加上 ":gui" 命令。
*:debug*
2. 执行命令前加上 ":debug" 前缀。这样,调试只对这个命令进行。这对调试某一个特
定的脚本和用户函数,或者 autocommands 用到的脚本和函数有用。例如: >
:debug edit test.txt.gz
3. 在一个执行的文件和用户函数里设置断点。你可以在命令行里这么做: >
vim -c "breakadd file */explorer.vim" .
< 这会启动 Vim 并在 "explorer.vim" 脚本的第一行停下。进入调试模式后也可以设置
断点。
在调试模式里,每个命令都会在执行前被显示。注释行、空行和其他不执行的行会被跳
过。如果一行里有两个 "|" 分隔的命令,它们被分别显示。
调 试 模 式
进入调试模式以后,可以使用通常的 Ex 命令。比如,要检查某变量的值: >
echo idx
在一个用户函数里,这会显示局域变量 "idx" 的值。在变量前加上 "g:" 可以得到全局
变量的值: >
echo g:idx
所有的命令都在当前函数或脚本的上下文下执行。你可以设置选项,比如设置或者重设
'verbose' 来显示当前发生的事情,但是你需要在执行你感兴趣的行之前设置它们: >
:set verbose=20
要避免更新屏幕的命令,因为直到退出调试模式之前,无法看到它们的效果。例如,帮助
命令: >
:help
不会很有帮助。
调试模式有自己的命令行历史。
函数行的行号是相对于函数开始的地方的。如果你要知道你在哪里,在另外一个 Vim 里
编辑包含这个函数的文件,先找到函数的开始处,然后用 "99j",其中的 "99" 用实际的
行号代替。
另外,可以使用如下的命令:
*>cont*
cont 继续执行到下一个断点。
*>quit*
quit 终止执行。这和 CTRL-C 类似,但是还是有要执行的东西,并
不是所有的都中止。在遇到断点时还是会停下来。
*>next*
next 执行一个命令,并在它结束时返回调试模式。步过用户函数
调用和被执行的脚本。
*>step*
step 执行一个命令,并在它结束时返回调试模式。步入用户函数
调用和被执行的脚本。
*>interrupt*
interrupt 类似于 CTRL-C,但不同于 ">quit",这会在执行下个命令前
返回到调试模式。它有助于测试 |:finally| 和 |:catch|
对中断例外的处理。
*>finish*
finish 结束当前的脚本或者用户函数,并在调用该脚本或函数的行的
下一行之前返回到调试模式。
*>bt*
*>backtrace*
*>where*
backtrace 显示当前调试会话的调用堆栈回溯。
bt
where
*>frame*
frame N 转到 N 层堆栈回溯。+ 和 - 标记进行相对移动。例如,
":frame +3" 往上三层堆栈。
*>up*
up 向上一层调用堆栈回溯。
*>down*
down 向下一层调用堆栈回溯。
关于调试模式下的附加命令:
- 没有它们的命令行自动补全。补全只对一般的 Ex 命令有效。
- 除非有多于一个命令以相同字母开始,可以用一个字符来简写。"f" 代表 "finish",
"fr" 代表 "frame"。
- 按 <CR> 会重复上一个调试命令。在执行另一个命令之后,这个功能会被关掉 (因为不
知道你要重复什么)。
- 如果你想调用相同的名字的 Ex 命令,在前面附加一个冒号: ":cont"、":next"、
":finish" (或者它们的缩写)。
堆栈回溯显示函数调用的层级,例如:
>bt ~
3 function One[3] ~
2 Two[3] ~
->1 Three[3] ~
0 Four ~
line 1: let four = 4 ~
"->" 指向当前堆栈。用 "up"、"down" 和 "frame N" 可选择另一层堆栈。
在当前栈中可以计算本地函数变量。还没有办法看到当前行所在的命令。
定 义 断 点
*:breaka* *:breakadd*
:breaka[dd] func [lnum] {name}
在函数上设置断点。例如: >
:breakadd func Explore
< 它不会检查函数名的合法性。这样,断点可以在函数定义之前设置。
:breaka[dd] file [lnum] {name}
在一个脚本文件上设置断点。例如: >
:breakadd file 43 .vimrc
:breaka[dd] here
在当前文件的当前行上设置断点。类似于: >
:breakadd file <cursor-line> <current-file>
< 注意 只能用于执行此文件时会执行的命令,不适用于该文件此处定义
的函数。
其中 [lnum] 是断点所在的行号。Vim 会在它或它之后的行上停止。如果省略,就用行号
1。
*:debug-name*
{name} 是用来匹配文件名或者函数名的模式。这和 autocommands 所有的模式类似。它
必须是一个完整匹配 (如同它以 "^" 开头和 "$" 结尾一样)。"*" 匹配任何字符序列。
它不用 'ignorecase' 选项,但是模式里可以用 "\c" 来忽略大小写 |/\c|。不要给函数
名加上 ()!
对脚本文件的匹配给予它的完整文件名。例如: >
breakadd file explorer
不会匹配。因为没有给出路径。 >
breakadd file *explorer.vim
匹配 ".../plugin/explorer.vim" 和 ".../plugin/iexplorer.vim"。 >
breakadd file */explorer.vim
只匹配 ".../plugin/explorer.vim"。
对函数名的匹配基于 ":function" 输出结果里它的显示方式。对于局部函数,这意味着
函数名前会附加上类似 "<SNR>99_" 的字符串。
注意函数先被载入然后再执行。载入时,检查 "file" 断点,而执行时检查 "func" 断
点。
删 除 断 点
*:breakd* *:breakdel* *E161*
:breakd[el] {nr}
删除断点 {nr}。用 |:breaklist| 可以看到每个断点的编号 {nr}。
:breakd[el] *
删除所有断点。
:breakd[el] func [lnum] {name}
删除函数断点。
:breakd[el] file [lnum] {name}
删除脚本断点。
:breakd[el] here
删除当前文件的当前行上的断点。
如果 [lnum] 省略,删除该函数或者文件的第一个断点。
{name} 必须和 ":breakadd" 所匹配的名字完全相同。"explorer"、"*explorer.vim" 和
"*explorer*" 是不一样的。
列 出 断 点
*:breakl* *:breaklist*
:breakl[ist]
列出所有的断点。
不 常 用 的
*:debugg* *:debuggreedy*
:debugg[reedy]
从正常的输入流而不是直接从用户输入里读取调试模式命令。这只对测
试脚本有用。例如: >
echo 'q^Mq' | vim -e -s -c debuggreedy -c 'breakadd file script.vim' -S script.vim
:0debugg[reedy]
撤销 ":debuggreedy": 从用户那里直接读取调试模式命令,而不为调
试命令预读取。
==============================================================================
8. 刨视 *profile* *profiling*
进行刨视意味着 Vim 测量执行函数和/或脚本时使用的时间。为此,需要 |+profile| 特
性。只有 Vim 编译时使用了 "huge" (巨大) 特性包才会包含此特性。
{Vi 没有刨视功能}
你也可以用 |reltime()| 函数来测量时间。这只需要 |+reltime| 特性,它更常出现。
关于语法高亮的刨视可见 |:syntime|。
例如,要刨视 one_script.vim 脚本文件: >
:profile start /tmp/one_script_profile
:profile file one_script.vim
:source one_script.vim
:exit
:prof[ile] start {fname} *:prof* *:profile* *E750*
启动刨视,退出时把结果写到 {fname} 文件。
扩展 {fname} 中的 "~/" 和环境变量。
如果 {fname} 已存在,安静地覆盖它。
变量 |v:profiling| 设为一。
:prof[ile] pause
直到下个 ":profile continue" 为止,停止刨视。可用来执行一些不
需计算的操作 (例如,执行外部命令)。不能嵌套。
:prof[ile] continue
在 ":profile pause" 之后出现,恢复刨视。
:prof[ile] func {pattern}
刨视匹配模式 {pattern} 的函数。
|:debug-name| 说明如何使用 {pattern}。
:prof[ile][!] file {pattern}
刨视匹配模式 {pattern} 的脚本。
|:debug-name| 说明如何使用 {pattern}。
只刨视脚本自身,不包括其中定义的函数。
如果加入 [!],同时刨视脚本里定义的所有函数。
注意 只有在此命令后载入脚本完成时才会开始刨视。脚本自身不可以
启动 :profile 命令。
:profd[el] ... *:profd* *:profdel*
停止对指定参数的刨视,|:breakdel| 说明其中的参数。
刨视总是用 ":profile start fname" 开始。Vim 退出时写入结果文件。这里是输出的示
例,加上行号是为了方便解释:
1 FUNCTION Test2() ~
2 Called 1 time ~
3 Total time: 0.155251 ~
4 Self time: 0.002006 ~
5 ~
6 count total (s) self (s) ~
7 9 0.000096 for i in range(8) ~
8 8 0.153655 0.000410 call Test3() ~
9 8 0.000070 endfor ~
10 " Ask a question ~
11 1 0.001341 echo input("give me an answer: ") ~
头部 (行号 1-4) 给出整个函数的时间。"Total" 时间是函数执行时使用的时间。
"Self" 时间是 "Total" 时间减去用于下面事项的时间:
- 其它用户定义的函数
- 执行脚本
- 执行自动命令
- 外部 (外壳) 命令
行号 7-11 显示每个执行行花费的时间。不计算不执行的行。所以注释行从不参与计算。
Count 列显示每行执行的次数。注意 到第 7 行的 "for" 命令执行次数比后面的行要多
一。这是因为该行也被用来检测循环结束。
Vim 花费在等待用户输入的时间完全不参与计算。所以 input() 提示后你的响应时间不
相关。
刨视给出关于时间花费在哪里的很好的指示,但要记住很多因素会影响输出结果:
- 测量时间的准确度取决于 gettimeofday() 系统函数。精确度可能是 1/100 秒,但显
示的时间仍以微秒计。
- 测量的是实际流逝的时间。如果其它进程繁忙,可能造成的延迟无法预测。你可能需要
多次运行刨视,并使用时间最少的结果。
- 如果一行内执行多个命令,你只会得到一个时间。要看到个别命令的时间,把行分开。
- 各行时间的加总几乎总会比整个函数的时间要少。中间有少许开销。
- Vim 退出前删除的函数不会产生刨视信息。如果需要,你可以检查 |v:profiling| 变
量: >
:if !v:profiling
: delfunc MyFunc
:endif
<
- 在多处理器系统中,因为睡眠模式或者为省电而减低处理器频率等因素影响,刨视结果
可能不正常。
- "self" 时间对递归调用的函数是不正确的。