vi/vim使用进阶: 剑不离手 – quickfix

<< 返回vim使用进阶: 目录

本节所用命令的帮助入口:

:help quickfix
:help :make
:help 'makeprg'
:help 'errorformat'
:help 'switchbuf'
:help location-list
:help grep
:help :vimgrep
:help :grep
:help starstar-wildcard 

以前读武侠小说,看到武林高手们都是从来剑不离手的。使用vim写程序,你也可以做到这一点,:-)

vim由一个程序员开发,而且为更多的程序员所使用,所以在vim中加强了对软件开发的支持,quickfix模式的引入就是一个例子。所谓quickfix模式,它和Normal模式、Insert模式没什么关系,它只是一种加快你开发速度的工作方式。

Quickfix模式的主要思想是保存一个位置列表,然后提供一系列命令,实现在这个位置列表中跳转。

位置列表的产生可以从编译器的编译输出信息中获得,也可以由grep命令的输出信息中获得,我们上篇文章所介绍的cscope命令,也可以产生位置列表信息(:help 'cscopequickfix')。

[编译]

通常,我们在开发过程中,经常要写代码,编译,修改编译错误,这个过程会数十遍上百遍的重复。如果你是根据编译器输出的错误信息,打开出错的文件,找到出错的行,然后再开始修改,那效率未免太低下了。

利用vim的quickfix模式,可以大大加快这一过程,你可以在vim启动编译,然后vim会根据编译器输出的错误信息,自动跳到第一个出错的地方,让你进行修改;修改完后,使用一个快捷键,跳到下一个错误处,再进行修改,方便的很。

为了做到这一点,你首先要定义编译时所使用的程序,对大多数使用Makefile的项目来说,vim的缺省设置"make"已经可以满足要求了。如果你的项目需要用一个特殊的程序进行编译,就需要修改'makeprg'选项的值。

大家在学编程时大概都读过"hello world"程序,我们就以这个简单的例子为例,讲一下quickfix模式的用法。

该程序的内容如下,里面包含了三个小小的错误:

/* hello world demo */
#include <stdio.h"
int main(int argc, char **argv)
{
    int i;
    print("hello world\n");
    return 0;
} 

我们可以为这个程序写个小小的Makefile文件,不过为了演示'makeprg'的设置方法,我们并不用Makefile,而直接设置'makeprg'选项,如下:

:set makeprg=gcc\ -Wall\ -ohello\ hello.c 

上面的命令会把hello.c编译为名hello的可执行文件,并打开了所有的Warnning。如果编译命令中有空格,需要使用'\'对空格进行转义,上面的例子就使用了'\'转义空格。

我们设置好'makeprg'选项后,输入下面的命令就可以编译了:

:make 

在使用":make"时,vim会自动调用'makeprg'选项定义的命令进行编译,并把编译输出重定向到一个临时文件中,当编译出现错误时,vim会从上述临时文件中读出错误信息,根据这些信息形成quickfix列表,并跳转到第一个错误出现的地方。

对于我们上面的程序来说,光标会停在第三行,也就是第一个出错的位置,vim同时会提示出错信息。如果你没看清出错信息,可以输入":cc"命令,vim会更次显示此信息,或者干脆使用":cw"命令,打开一个quickfix窗口,把所有的出错信息显示出来,见下图:

现在我们知道错在哪儿了,修正一下,然后使用":cn"命令(或者在Quickfix List对应行上输入回车)跳到下一个出错的地方,以此类推,直到修正全部错误。

好了,千辛万苦,我们的hello world终于工作了。乍一看这个例子,似乎Quickfix并没有提高什么效率,但如果你的错误出现在多个不同目录的不同文件里,它可以帮你省很多时间,使你可以集中精力在修正bug上。

vim可以同时记住最新的10个错误列表,也就是说你最近10次使用":make"命令编译所遇到的错误都保存着,可以使用":colder"和":cnewer"命令,回到旧的错误列表,或者到更新的错误列表。

在quickfix模式里经常用到的命令有:

:cc                显示详细错误信息 ( :help :cc )
:cp                跳到上一个错误 ( :help :cp )
:cn                跳到下一个错误 ( :help :cn )
:cl                列出所有错误 ( :help :cl )
:cw                如果有错误列表,则打开quickfix窗口 ( :help :cw )
:col               到前一个旧的错误列表 ( :help :col )
:cnew              到后一个较新的错误列表 ( :help :cnew ) 

更多的命令,以及这些命令更详细的解释,请参见手册。

对于经常用到的命令,最好提供更方便的使用方法,在我的vimrc中的定义:

autocmd FileType c,cpp  map <buffer> <leader><space> :w<cr>:make<cr>
nmap <leader>cn :cn<cr>
nmap <leader>cp :cp<cr>
nmap <leader>cw :cw 10<cr> 

现在使用",<space>"(先按,再按空格)就可以编译,使用",cp"和",cn"跳到上一个和下一个错误,使用",cw"来打开一个quickfix窗口。这下顺手多了!

如果你希望跳转到出错的文件时,使用一个分隔的窗口打开,请参阅'switchbuf'选项的值。

在vim7中,每个窗口都可以拥有自己的位置列表,这样,你就能够同时打开多个位置列表了,而quickfix列表在整个vim中只有一个。你可以使用位置列表来显示编译错误信息,具体命令参阅手册:":help location-list"以及":help :lmake"。

[GREP]

我们在程序员的利器 - cscope中讲过,cscope可以做为一个快速的grep程序使用,对于我们的软件项目,用cscope生成一个数据库,可以大大加快查找字符串的速度。但cscope需要事先生成一个数据库,对一些简单的查找,不需要专门为之生成数据库,这时候可以使用grep。

Grep的名字来源于"g/re/p","re"是正则表达式(regex)的意思,"p"是打印,也就是把匹配正则表达式的行打印出来。

vim既可以使用外部的grep程序,也可以使用内部集成的grep功能。

使用集成的grep命令非常简单,通常使用格式为:

:vimgrep /main/gj **/*.c 

在上面的例子里,我们使用vim内部集成的grep功能,在当前目录及其子目录树的所有c文件中查找main字符串,如果一行中main出现了多次,每个匹配都计入;在查找到后,不立即跳转到第一个匹配的地方。

使用内部集成的grep功能速度要比外部grep慢一些,因为它会打开每个文件,对其进行检查,然后关闭;但集成的grep支持vim增强的正则表达式,可以利用它进行更为复杂的查找。它也支持vim扩展的文件通配符表示方式,见":help starstar-wildcard"。

vimgrep查找到的结果,也会放在quickfix列表中。下图是在vim 7.0的源代码目录中执行上面的命令生成的quickfix列表:

我们可以使用上面介绍的quickfix模式的命令,来查看这些匹配。

你也可以用外部的grep程序来查找,如果你的系统中所用的不是标准的grep程序,那么就需要修改'grepprg'选项,详情请参阅手册。

使用外部grep的语法与grep程序相同,请参阅grep的手册。

无论使用内部的vimgrep,还是使用外部的grep,vim都允许你将查找到的结果放在与窗口相关联的位置列表,要了解详细信息,":help :lvimgrep"及":help :lgrep"。

在我的vimrc中,定义下面的键映射,利用它可以在当前文件中快速查找光标下的单词:

nmap <leader>lv :lv /<c-r>=expand("<cword>")<cr>/ %<cr>:lw<cr> 

[参考文档]

<< 返回vim使用进阶: 目录

本文以创作共用版权协议发布,转载本文要求遵循“署名-非商业性使用-相同方式共享3.0”的创作共用协议,并以链接形式指明本文地址。

本文链接地址: https://blog.easwy.com/archives/advanced-vim-skills-quickfix-mode/

文章的脚注信息由WordPress的wp-posturl插件自动生成

“vi/vim使用进阶: 剑不离手 – quickfix”的62个回复

  1. 我的工程目录uboot下有个net文件夹,我现在vim net/tftp.c,打开tftp.c,修改一些内容后:make编译,执行:cw查看quickfix窗口,但是报错之后自动打开一个tftp.c文件,屏幕一片空白,很明显路径是错的,vim打开了一个新的tftp.c文件,并没有跳转到net/tftp.c文件。请问如何才能使quickfix正确跳转的出错的文件?

  2. makeprg 默认不就是make吗? 一个文件还好,如果是复杂的文件依赖关系,每次去设定makeprg岂不是很麻烦?

  3. 请教一下楼主,:set makeprg=gcc\ -Wall\ -ohello\ hello.c 这个在每次编译的时候都要写一遍吗?没有通用方法吗,是写在.vimrc中吗

    1. 可以用%表示当前文件名,%<表示不带后缀的文件名,写到vimrc里,例如下面。
      autocmd FileType c set makeprg=gcc\ %\ -Wall \ -o\ %<
      (初学,见笑勿怪)

  4. 楼主写得太好了。请问楼主能否介绍下什么情况下可以用quickfix模式呢?如果吧这些写进去就很完美了。

  5. 没事了楼主.....:make会被变成:!make 2>&1|tee d:/tmp/xxxxx.tmp,我的tee没有放到环境变量里....还是很感激楼主,这篇文章通俗易懂啊....话说这个tee又是什么东东...

  6. 相问一下关于quickfix的问题。我在用ncverilog编译verilog时;
    设置 set makegrp=ncverilog\
    set errorformat=ncvlog:\ \*E,ILLPRI\ (%f,%l\|%c):\ %m
    希望匹配的错误行是这样的:
    “ncvlog: *E,ILLPRI (example.v,29|22): illegal expression primary [4.2(IEEE)].”

    现在的问题是运行make后,没有错误的list产生,:cn命令会把所有的ncverilog编译信息逐行显示出来。不知道是啥原因

  7. Easwy :
    @karlzheng
    如果在函数里执行execute “pwd”或silent execute “pwd”,v:statusmsg变量里有东西
    也许是cd /usr/src这条命令在函数中和ex模式下表现不太一样吧。你用”cd /usr/src”命令的原因是什么?可以先执行”cd /usr/src”,然后再用”pwd”命令吗?有一些bug如果有绕过的方法,暂时绕过去就行了

    好的,谢谢!
    这两个函数只是为了练习写vim的函数时,测试怎么可以得到exec命令得到的屏幕提示信息....

  8. @karlzheng

    如果在函数里执行execute “pwd”或silent execute “pwd”,v:statusmsg变量里有东西
    也许是cd /usr/src这条命令在函数中和ex模式下表现不太一样吧。你用"cd /usr/src"命令的原因是什么?可以先执行"cd /usr/src",然后再用"pwd"命令吗?有一些bug如果有绕过的方法,暂时绕过去就行了

  9. karlzheng :

    Easwy :
    @karlzheng
    不知道是不是bug。
    如果你是想获得exec命令的输出,用v:statusmsg可能会更好些,比如:
    :exec “cd /usr/src”
    :echo v:statusmsg
    :help v:statusmsg

    发现在函数里执行:
    exec “cd /usr/src”
    然后:
    echo v:statusmsg
    得到的信息跟直接在命令行里一步一步运行也不一样….

    看着,好样是:
    :cd /usr/src
    在命令行里执行会有信息提示出来,
    在函数里执行:
    exec “cd /usr/src”
    没有提示信息放到v:statusmsg变量里.

  10. Easwy :
    @karlzheng
    不知道是不是bug。
    如果你是想获得exec命令的输出,用v:statusmsg可能会更好些,比如:
    :exec “cd /usr/src”
    :echo v:statusmsg
    :help v:statusmsg

    发现在函数里执行:
    exec “cd /usr/src”
    然后:
    echo v:statusmsg
    得到的信息跟直接在命令行里一步一步运行也不一样....

  11. 请教:下面这两个函数:
    function! My_test1()
    redir => g:mymsg
    silent execute "pwd"
    redir END
    echo g:mymsg
    endfunction

    function! My_test2()
    redir => g:mymsg
    execute "cd /usr/src"
    redir END
    echo g:mymsg
    endfunction

    为什么:
    :call My_test1()
    显示出来的变量值g:mymsg有内容;

    :call My_test2()
    显示出来的变量值g:mymsg内容是空?

  12. 你好,首先对你的教程致谢。影响了很多人。
    我在这里想请教一个问题,如果你的makefile不在当前目录下的时候怎么办?
    make -C ../debug/src/ 这也这样设置了也没有办法。空格我也加了转义符的。
    能解决吗?我在网上查了很久也没有发现。
    还有一个问题,当用cscope查找一个变量时,比如说结构体变量
    desp_message.ip 这样的变量我本意是想查找desp_message这个结构变量,可是它很傻地把整个都认为是一个变量了。这种有没有好的解决方法呢?
    非常感谢。

  13. Easwy :
    @karlzheng
    看一下手册里的这段话:
    Normally the quickfix window is at the bottom of the screen. If there are
    vertical splits, it’s at the bottom of the rightmost column of windows. To
    make it always occupy the full width: >
    :botright cwindow
    You can move the window around with |window-moving| commands.
    你试一下这条命令:”:vert copen 30″

    再请教一下:我用quickfix窗口显示cscope查到的内容时不想跳转到第一个匹配,请问vim7.2可以设置吗?(在网上搜的内容都是要修改vim的C语言源文件实现--这样子不方便啊)