2009年4月22日星期三

使用vim开发python的quickfix方案

这个问题用google一搜就能看到很普遍的一种做法, 但是我认为不对的设置方法:
在vimrc中设置:

autocmd FileType python set makeprg=python\ %
autocmd FileType python set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m

对于vim设置, 我并不是特别熟悉, 所以我也照抄了上面的设置, 使用一下这个设置就知道了, 如果你的python脚本中出错了, 那么quickfix会把你定位到错误堆栈的最高点, 但是在python中错误信息是most recent call last, 所以quickfix捕获到错误实际上是函数调用的顶端, 而在此并非你的代码真正错误的地方, 加入你的代码比较长的话, 那么实际上还会被mislead,然后你再傻傻的去找你的错误, 而且上面的设置把整个错误堆栈下面的信息吞掉了, 要找错误根本还不好找.

这个问题一直没有体会到,直到最近使用wxPython开发, 才知道没有一个好的quickfix是多么的痛苦, 所以下定决心要解决, 花了一个下午加一个晚上, 下面是我的心得.

从上面的分析可以想到最直接的办法, 修改errorformat, 让quickfix可以捕获错误堆栈中所有的内容, 然后用:cl查看, 用:clast定位到实际的错误处, 这个方案再vim站点上有, 可以看一下,不过仍然非常麻烦, 而且用wxPython编程时, 有时会包wxPython lib中的错误, 你就会被定位到lib中,而不是你在你的源代码中出错的地方.

方法二: 借助万能的google找到了别人的一个解决方案:
这个解决方案的核心思想是把python的error message逆转过来, 那么就能让quickfix默认停到第一条出错处, 不过这个解决方案值得改进的地方我认为有两个, 第一, 要能处理当你的源文件没有错误时,防止那个efm_filer.py出错, 应该对errors进行判断, 列表非空才调用pop方法, 第二, 它仍然没有解决在上个方案中我说的在wxPython编程中会被定位到类库中的情况, 也需要进行判断, 只有在当前编辑文件中找到的错误才是我们需要定位的错误. 下面是我改进之后的
efm_filter.py源代码:

#!/usr/bin/env python

import sys
import string
import re

errors = sys.stdin.readlines()

#find current filename, only errors
#in current file will be considered as *true* errors
current_file = None

for error in errors:
m = re.search("""^\s+File\s+"([^"]+)".+""", error)
if m is not None:
current_file = m.group(1)
# print current_file #for testing
break
if len(errors) > 0:
message = errors.pop()[:-1]
errors.reverse()

for error in errors:
if string.find(error, ' File \"%s\"' % current_file) == 0:
print error[:-1] + ", " + message
message = "Traceback"

为了避免上面的链接失效, 我把在compiler/python.vim中的设置贴过来

" python.vim
if exists("current_compiler")
finish
endif
let current_compiler = "python"

setlocal makeprg=python\ %
setlocal shellpipe=2>&1\ \|\ ~/.vim/tools/efm_filter.py\ \|\ tee
setlocal errorformat=\ \ File\ \"%f\"\\,\ line\ %l\\,\ in\ %*[^\\,]\\,\ %m


另外, 在python.vim中, 它说可以改写efm_filter.py,让其将它得到的输入直接输出, 处理后的输出写到vim quickfix临时文件中 (这个临时文件是vim传递的), 这样做的好处是可以在错误信息中看到熟悉的python错误信息, 又可以让quickfix捕获到准确的出错位置, 这个改写很容易,这里就不贴了, 感兴趣的可以试一下.

从python.vim中可以看到, shellpipe中用到了管道符号, 这是*nix系统下才有的概念, 在windows不能使用, 所以上面的解决方案仅仅适合于*nix系统的, 如果你的主要工作环境是Linux, 那么上面的方案非常不错了.

在windows下我也用gvim做编程工作, 那么在windows下如何解决呢? 我的方案是使用一个python脚本调用python解释器,然后做进一步的处理, 这样也许在速度上有点影响, 但是在windows系统上测试使用效果还不错. 改写上面我贴出的efm_filter.py, 你可以重新命名为pymake.py, 把从标准输入流读取错误信息的部分改为如下代码

from subprocess import Popen, PIPE
from cStringIO import StringIO

if len(sys.argv) < 1:
sys.exit(0)
pysrc = sys.argv[1]
output = Popen(["python", pysrc], stderr=PIPE).communicate()[1]
buf = StringIO()
buf.write(output)
buf.seek(0) #move pointer to the start of the file
errors = buf.readlines() #take place sys.stdin.readlines()
buf.close()

把pymake.py放置到PATH中, 然后改vimrc, 指定使用pymake编译:
autocmd FileType python set makeprg=pymake.py\ %
autocmd FileType python set efm=\ \ File\ \"%f\"\\,\ line\ %l\\,\ in\ %*[^\\,]\\,\ %m
当然你要愿意, 通过改python.vim的compiler设置也可以, 不过我认为, 既然不涉及到对其他配置文件的改变, 改这里更加直接.

当然上面的解决方法也同时适合Linux, 如果你同时在windows和Linux下面开发, 需要共享VIM配置的话, 最后一个方案看起来是最好的, 对于速度方面的影响, 一般配置的电脑是感觉不到的.

没有评论: