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.

132 lines
8.3 KiB

2 years ago
# 第三章 Vim 常用命令
## 3.6\* 调试命令
对任何一门语言,都有必要掌握调试技巧或手段。本节介绍 VimL 语言编程可以怎么调试
,介绍一些自己的经验与体会。
### echo 大法
对于不太庞大的程序或脚本,在关键疑点处打印消息都是简单方便的发现问题的手段,姑
且也算一种调试方法吧。
不过这明显有个问题,当程序调试完毕后,这些只为调试用的 `echo` 打印命令留着很碍
事呀,可能会与正常的输出混杂在一起,干扰正常结果呢。所以最好是能将正常的 `echo`
与调试的临时 `echo` 区分开来。正好,VimL 有个奇葩规定,在每行行语句之前的 `:`
冒号是可选的。这是为了与命令行表观上一致,然而正常的 vim 脚本一般都不会自找麻
烦多加这个冒号。但是若按语法规则,你在每行语句之前加一个冒号(甚至多个冒号)都
是没有关系的。
于是,不妨自己规范一下,将调试用的打印语句,都写成 `:echo`,或者喜欢多个空格
`: echo` 也行。而在正常的程序输出语句中,则用整洁的无冒号 `echo` 版。这样,当
调试完毕,确认程序无误后,就可以用 vim 强大的编辑命令将这些调试命令都删了:
```vim
: g/:\s*echo/delete
```
当然,你也许并不是想彻底删除,只是想注释掉,那就可用替换命令:
```vim
: g/:\s*echo/s/:\s*echo/" echo/
```
`:s` 命令使用的正则表达式与前面的 `:g` 命令的正则表达式是一样的时候,可以简
写成 `: g/:\s*echo/s//" echo/`。因为 `:s//{replace}/` 命令中,空模式的意图是重
复使用上次的模式(寄存器 `/` 的内容)。若是为达这个目的,直接用替换命令也可以
的:`: %s/:\s*echo/" echo/`。不过与 `:g` 命令联用(先查找目标行,再替换)会更
灵活点,比如想将首列替换为注释符 `"`,而不影响内缩进的 `:echo` 命令,则可使用
这样的替换命令:
```vim
: g/:\s*echo/s/^./"/
```
如果想更细致点,可以自行将 `:echo``::echo` 用于不同场合,比如不同等级的调
试输出。
还有个问题,`:echo` 命令的输出是易逝的,后一批的命令(vim 的解释单元)输出会覆
盖掉前一批的命令输出。如果想保存这样的输入,有以下几种办法:
* `:echomsg` 用这个命令替换 `:echo`,则输出信息会保存在消息区,以后可用
`:message` 再次查看,当消息区的信息比较多时,可能需要翻页查看,`G` 跳到最后
一页,基本上就是最近的输出了。
* `:redir` 命令重定向,可以将随后的 `:echo` 消息重定向至文件、寄存器、变量中,
当然也会同时显示在屏幕上。不再需要重定向功能时用 `:redir END` 命令取消。
- `:redir! > {file}` 重定向到文件中,当文件已存在时,用 `!` 强制覆盖。
- `:redir @{reg}>` 重定向至寄存器,如果支持系统剪贴板,用 `*``+` 表示。
- `:redir => {var}` 重定义向至一个变量中。
- `:redir >>` 将上述命令中的 `>` 换为 `>>` 表示附加。
* `&verbosfile` 将详情信息写入这个选项值指定的文件中。`&verbose` 选项值设定详
情信息的等级。
### 断点进入调试模式
Vim 也提供了正式的调试模式,那有点像允许单步执行的 Ex 模式。一般需要先设置断点
,随后当脚本运行到断点处,就进入了调试模式。添加断点用 `:breakadd` 命令:
* `:breakadd file [lnum] {name}` 在一个 vim 脚本文件中的某行加断点,行号可选。
注意如果提供行号,行号参数位于文件名之前,如果省略行号,相当于第 1 行。随后
`:source {name}` 加载该脚本时,执行到那行时会暂停,进入调试模式。
* `:breakadd func [lnum] {name}` 在某函数的第几行打断点。`{name}` 指函数名。如
果是全局函数,那就是直接的函数名,如 `FuncName`。如果是脚本局部函数,如
`s:FuncName` ,则要先找到那个脚本在当前 vim 会话的脚本号(`:scriptnames`),
然后实际的函数名是 `<SNR>dd_FuncName`,其中 `dd` 就是脚本号数字。如果是匿名
函数,它没有名字,就只能用其函数编号了,如 `:breakadd func 1 21` 表示在第 21
个匿名函数的第1行处打断点。那匿名函数编号如何确定呢?如果这个函数有出错了,
在错误信息中会打印出出错函数的名字与行号,匿名函数没名字就用编号代替了。(没
有出错么?没出错为啥调试?)至于 `[lnum]` 行号,可理解为函数体内相对于函数头
定义的相对行号,可不是该函数定义块在脚本文件中的行号。即从函数定义头按
`[lnum]``j` 就是函数断点处。
* `:breakadd here` 当你在编辑一个 vim 脚本文件时,相当于在当前文件的当前行加入
断点。如果你已经进入了调试模式,并且已经单步进入了某个函数,`:breakadd here`
也可以在当前函数的当前行加入断点,下次再次调用该函数时(或下次循环)运行到
此处时也会暂停。
当用 `:breakadd` 添加了一些断点后,可用 `:breaklist` 查看断点信息。也可用
`:breakdel` 删除断点。
* `:breakdel {nr}` 按断点号删除某个断点(`:breaklist` 会列出断点号)。
* `:breakdel *` 删除所有断点。
* `:breakdel file [lnum] {name}`
* `:breakdel func [lnum] {name}`
* `:breakdel here` 这三个命令与 `:breakadd` 相似,但是删除断点。
除了通过 `:breakadd` 添加断点,以期将来运行到彼处时进入调试模式外,还有另外两
种方式直接进入调试模式:
* `:debug {cmd}` 在执行命令之前附加 `:debug` ,就将在执行该命令时立即进入调试
模式,一般接着用 `s` (step in)深入调试,如果用 `n` (step over)可能就将整
`{cmd}` 命令当作一步直接执行完了,并不能达到调试效果。
* `vim -D {other args}` 在启动 vim 时,通过 `-D` 命令行参数,直接在加载 `vimrc`
时就开始进入调试模式了。
### 调试模式
调试模式是一种特殊的 Ex 模式,除了一般的 ex 命令,还可以使用以下调试命令:
* cont (c),表示继续执行,直到遇到下一断点,或结束。
* quit (q),中断,类似 `<Ctrl-C>`
* interrupt (i), 也类似 `<Ctrl-C>`
* next (n),单步执行,类似 step over,会跳过函数调用与加载文件。
* step (s),单独执行,类似 step in,会步进函数调用或加载文件。
* finish (f),结束当前加载脚本或函数调用,回到调用处。
* backtrace (bt) 或 where,显示调用堆栈。
* frame (fr) {N} ,切换到堆栈的第 N 层,可用 `+` `-` 表示相对层。
* up / donw, 在堆栈处上移一层(`fr +1`)或下移一层(`fr -1`)。
以上这些调试命令可以尽可能缩写,只要前缀字符不冲突(小括号里也已标出最简缩写)
。直接敲回车表示重复上一次命令,这样就不必每次输入 `s``n` 命令了。
调试命令没有补全功能,只有普通 ex 命令才能补全。如果要使用与调试命令相同的普通
ex 命令,多加一个冒号,如 `:next`。但是,由于在 Ex 模式,编辑窗口是不更新的(
事实上,只要调试过程稍长,vim 窗口就完全被调试信息覆盖了),很多普通 ex 命令是
没有效果后,只有在完成调试模式后重回普通模式才能反映编辑窗口的变化。
真正有价值的 ex 命令是可用 `echo` 命令查看变量值,并且能根据当前环境查看相应作用
域的变量值,比如在加载脚本时可查看 `s:var`,运行到函数内部可看局部变量 `l:var`
(在函数内默认局部变量,`:echo var` 就相当于 `:echo l:var`)。而在正常的命令行
下面,是无法查看 `s:var``l:var` 变量的。
在调试模式中,只能打印出正要执行的那行的源代码。这是典型的命令行式的调试方式,
并不能像 IDE 那般分裂出源码窗口,直接将光标定位到正在执行的行上。如果想查看完
整代码,只能用另外一个 vim 打开源文件查看了(有可能出现 `*.swp` 冲突问题,用只
读模式打开就好)。所以 VimL 调试的可视化程序仍稍嫌不足,希望日后还有改进。