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语言源文件实现--这样子不方便啊)

  14. Easwy :
    @karlzheng
    估计是因为你getmatches()得到的列表是空的,你可以用echo命令把它输出来看一下

    :echo getmatches()
    显示:
    [{'group': 'MyGroup', 'pattern': 'TODO', 'priority': 10, 'id': 1}]
    但:cgete getmatches()得到的窗口是空的...

  15. @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"

  16. Easwy :
    @karlzheng
    不会,我把locale设置成下面,可以在vim中正常使用中文:
    export LANG=”en_US.UTF-8″
    export LC_CTYPE=”zh_CN.UTF-8″

    我在本地这样子设置可以;但ssh连上去的就不行,不知道为什么....
    (本地和远程的locale设置成了完全一样的,系统都是ubuntu,使用gnometerminal)

  17. Easwy :
    @karlzheng
    好像没有清除命令,不过你可以用重新生成一个quickfix的方法来实现类似的功能,比如:
    :cgete getmatches()
    :help cgete
    :help getmatches()

    奇怪:我照getmatches的manual定义了能找到的字符模式;但cgete getmatches()执行后quickfix窗口是空的;cgete不是可以从getmatches()构造吗?(btw:要清空,直接:cgete []就行了:)

  18. 请问quickfix buffer里的内容怎么清除?(我用cscope输到quickfix窗口里的内容是不断加上去的,有时需要清除掉)

  19. export LANG=en_US后,make可以识别路径了;但这样子:每次要修改LANG还是比较麻烦...
    quickfix那个发现tee下来的log是不包括错误的;还要2>&1才包括,真晕...

  20. 上面中:目录已切到cpu/s5pc11x,fimd.c是cpu/s5pc11x目录下的文件,但提示还是:/home/zkl/svn/compiled/uboot-1.3.4-m9_v4/fimd.c

  21. Easwy :
    @karlzheng
    这和你所用编译器的错误输出格式有关,看看这个帮助
    :help compiler-select
    或者看看编译器有没有选项可以输出文件的全路径?

    好像GNU-make输出的就是全路径;但是make -C dir之后,输出的信息没有把-C dir这个目录加进入;见如下提示信息:

    37 || make[1]: 正在进入目录 `/home/zkl/svn/compiled/uboot-1.3.4-m9_v4/cpu/s5pc11x'
    38 || arm-none-linux-gnueabi-gcc -g -Os -fno-strict-aliasing -fno-common -ffixed-r8 -msoft-float -D__KERNEL__ -DTEXT_BASE=0xc3e00000 -I/h ome/zkl/svn/compiled/uboot-1.3.4-m9_v4/include -fno-builtin -ffreestanding -nostdinc -isystem /usr/local/arm/arm-2009q3/bin/../lib/gcc/arm- none-linux-gnueabi/4.4.1/include -pipe -DCONFIG_ARM -D__ARM__ -march=armv5te -mabi=apcs-gnu -mno-thumb-interwork -Wall -Wstrict-prototypes -fno-stack-protector -c -o fimd.o fimd.c
    39 || fimd.c: In function 'LCD_Initialize_LS035B3SX01':
    40 /home/zkl/svn/compiled/uboot-1.3.4-m9_v4/fimd.c|204| warning: implicit declaration of function 's5pc_dism_init'

  22. Easwy :
    @karlzheng
    这和你所用编译器的错误输出格式有关,看看这个帮助
    :help compiler-select
    或者看看编译器有没有选项可以输出文件的全路径?

    我用的是arm-linux-gcc, gcc的选项没找到,找到:
    http://vimcdoc.sourceforge.net/doc/quickfix.html#quickfix-directory-stack
    这一个,说的是设置quickfix目录栈的,需要修改Makefile,Makefile一般都是整个项目组定好的了;所以不太合适;杨兄你没碰到这个问题吗?

  23. 我编译整个工程时,用quickfix碰到问题:
    比如: 工程编译出错的提示是:nand_cp.c|74| warning: unused variable 'i'

    onenand_cp.c文件不在当前目录,而是下面这个文件:
    ./cpu/s5pc1xx/onenand_cp.c

    这时quickfix打开的文件是当前目录onenand_cp.c

    其它的文件也是这样子.

    不知有什么办法改变这种情况?

  24. 请问怎么让set makeprg 和make在一个函数中全部实现?
    func! QuickfixGcc()
    exec "w"
    exec "!set makeprg=gcc\ -Wall\ -o%<\ %"
    exec "!make"
    exec "cw"
    endfunc

    请文这样怎么错了阿 谢谢~~

  25. 大哥,你的命令:
    nmap lv :lv /=expand("")/ %:lw
    可以查找当前文件查找光标下的单词,如何在某个目录里递归并在指定的某些文件类型(比如.c .s等)里查找某个单词?
    在公司里是使用windows,grep.vim的插件在windows下Grep功能正常,Rgrep的功能不正常。在cygwin里的vim中倒是可以正常使用,不过cygwin中的vim速度慢了些,没有win版的gvim来得快(而且console模式下colorscheme不好看^_^),google了好久,并没有相关的解决方案,grep.vim的作者好像也消失了。
    不知道您平时是如何做到相关功能的,非常感谢!!!

  26. (以下为CSDN评论的转帖)

    #新人 发表于2008-07-04 20:27:03 IP: 162.105.75.*
    请问您用的配色方案是什么?就是这个墨绿色背景
    的?十分喜欢,谢谢。。

    #easwy 发表于2008-07-07 16:06:27 IP: 213.70.90.*
    参见我的另外一篇文章:
    http://blog.csdn.net/easwy/archive/2007/03/29/1545707.aspx

    #Canbus 发表于2008-08-08 16:11:16 IP: 202.101.116.*
    nmap <silent> <leader>ld :lv /<c-r>=expand("<cword>")<cr>/ *<cr>:lw<cr>
    --------
    这个一堆的乱码,能否再贴一次,3q
    ==============
    另这个快截键怎么用呢?
    nmap lv :lv /=expand("")/ %:lw

    是不是在command方式下输入lv呢?但是我输入完后显示E471:argument required

    菜菜鸟一个,见笑了

    2008-10-29 15:36:32作者回复
    在normal模式下用,就是按ESC后的模式,输入",lv"

  27. (以下为CSDN评论的转帖)

    #easwy 发表于2007-06-01 09:58:51 IP: 213.70.90.*
    首先定义你的mapleader是什么,例如:
    let mapleader = ","

    然后,对上面的映射,输入",lv",就会查找当前光标下的单词,然后在窗口的位置列表里显示出来。

    :help mapleader

    #xiaoqiang 发表于2008-06-01 15:24:07 IP: 125.46.31.*
    最后设的那个快捷键我使了一下,可是只是在一个location列表中显示本文件中搜索结果。。我想查找当前光标下的单词,显示本文件夹中所有文件的搜索结果应该怎么设置呢?
    谢谢。。。

    2008-06-11 14:17:56作者回复
    nmap <silent> <leader>ld :lv /<c-r>=expand("<cword>")<cr>/ *<cr>:lw<cr>

    #xiaoqiang 发表于2008-06-07 13:37:32 IP: 162.105.75.*
    我搞定了。。。thanks all the same

发表评论

电子邮件地址不会被公开。 必填项已用*标注