snipMate反引号转义补丁

vim-logo对于一套IDE来说,一个好的snippet管理工具可以大大提高程序员的工作效率。作为一个适应不了Emacs的Vim geek,Eclipse自带的代码补全、Visual Studio的Visual Assist插件、Emacs下由Pluskid荣誉出品的yasnippet,都让我十分垂涎。之前曾经用过很长一段时间的snippetEmu(这也是Debian/Ubuntu vim-scripts包中所带的snippet插件),虽然确实有助于提高效率,却有诸多不足:视觉效果很不清爽,时不时还出些问题,最难忍的便是其晦涩不堪的snippet定义方式。后来也尝试过同事推荐的另一个已经不记得名字的插件,仍旧不趁手,又换回snippetEmu。

前两天无意中发现snipMate,试用之后大呼惊艳!虽然和snippetEmu同是模仿TextMate,snipMate要精致得多。Snippet的定义方式也非常灵活和人性化。只有一处让人不待见的地方,就是snippet定义必须像Makefile一样以tab开头。通读文档之后依照自己的代码风格改写了默认的C/C++ snippet文件,又录入了Emacs erlang-mode所带的几个OTP behaviour的snippet。把玩一番,爱不释手 :-D

周末闲时接着翻译《Erlang并发编程》第9章,又想到snipMate。于是顺手定义了一个rst.snippets文件,用来简化reStructuredText格式中多种Markup的输入。其中有这么一个用于输入等宽格式文本的snippet:

# Literal text
snippet l
    ``${1}``${2}

写到一半的时候就想起来,反引号在snipMate中是有特殊用途的:snipMate的snippet占位符中可以插入Vim脚本表达式以实现一些高级功能,Vim表达式就需要以一对反引号包围起来,例如默认的_.snippets中:

snippet date
    `strftime("%Y-%m-%d")`

就可以将“date”展开为当前日期。这样一来,我的rst.snippets中的反引号会不会被错误地解释呢?如果这么写不行,那么snipMate是否支持反引号的转义呢?试了一下,发现果然出错了。在snipMate文档中也没有找到反引号转义相关的说明。无奈之下只有去翻snipMate的源码。说来可耻,用Vim 4年了,一直都没有仔细学过Vim的脚本语言 :oops: 除了日常的.vimrc配置以外,也从来没有写过别的Vim脚本。

所幸snipMate的代码并不复杂,很快在autoload/snipMate.vim中找到了这么一段:

78
79
80
81
82
83
84
85
86
87
    " Evaluate eval (`...`) expressions.
    " Using a loop here instead of a regex fixes a bug with nested "\=".
    if stridx(snippet, '`') != -1
        while match(snippet, '`.\{-}`') != -1
            let snippet = substitute(snippet, '`.\{-}`',
                        \ substitute(eval(matchstr(snippet, '`\zs.\{-}\ze`')),
                        \ "\n\\%$", '', ''), '')
        endw
        let snippet = substitute(snippet, "\r", "\n", 'g')
    endif

从第81行的正则表达式`.\{-}`来看,snipMate的作者只是简单的匹配了成对的反引号及其间的内容,而没有作任何转义处理。简单构思了一下,决定以传统方式用反斜杠来转义反引号,于是动手打了一个简单的patch。

首先将第81行的正则式修改为[^\\]`.\{-}`,这样snipMate就不会将以反斜杠开头的反引号纳入处理范畴。同时,在第86行之后增加了这么一行:

let snippet = substitute(snippet, "\\\\`", "`", 'g')

用来将所有的\`再次还原为单个反引号。最后,将之前的snippet改写为:

# Literal text
snippet l
    \`\`${1}\`\`${2}

简单测试了一下,大功告成! :-D 开心之余屁颠地跑到snipMate的Google Code主页上去提交了这个patch。说来再次可耻,虽然一直享着OpenSource的福,却从未正式提交过补丁,以至于我都不知道应该如何正确地提交一个补丁 :oops: Google了一把倒也迅速搞定。

提交patch的时候写道:我是个Vim脚本新手,不敢说这个补丁有没有什么问题。果不其然,在写这篇blog的时候便发现果然有个bug:第81行的那个修改过的正则式[^\\]`.\{-}`要求在反引号前必须有一个不是反斜杠的字符,于是当反引号位于行首时,就匹配失败了。上文提到的用于输入当前日期的snippet便会因此而被错误地展开(我也正是因为尝试这个snippet才发现这个bug的)。好在解决起来也简单,需要匹配的反引号对所应满足的条件应该是:第一个反引号位于行首或者前一个字符不是反斜杠。于是将正则式改为改成\(^|[^\\]\)`.\{-}`就可以了。

This entry was posted in Tools and tagged . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

11 Comments

  1. Posted 十月 13, 2009 at 2:10 上午 | Permalink

    看了一下,我一直在用snipMate呢,以前在Vim脚本里面把各种补全插件都试了一遍……
    不过不能嵌套snippet也是不太舒服,补全的时候^P就不好用了,Shift-tab回退也不能用,时不时还会出一点对不齐而补全失败的问题 -.-bb
    不过毕竟有补全比没有补全舒服许多 :P

    我觉得开源世界可以学的太多,可以做的事情也太多,不会用vim脚本也不是什么可耻的事情 -.-

  2. Posted 十月 13, 2009 at 11:00 上午 | Permalink

    @quark
    <s-tab>确实不好用,所以我按照文档中的描述,替换成<c-k>了。从Google Code的项目主页上来看,似乎GIT上的版本已经支持嵌套snippet了,只是vim.org上还未更新,不过我还没有确认过这一点。

    • cncyber
      Posted 六月 23, 2010 at 9:13 上午 | Permalink

      github 上的 snipMate branch 才支持snippet 嵌套,不过主干上还没更新进来。

  3. Posted 十月 13, 2009 at 12:13 下午 | Permalink

    哈哈,Rhythm 写 blog 也这么爱用表情啊,突然觉得是小 Rhythm ,哈哈 :D
    Vim 脚本确实是一种能让人发疯的语言啊,不敢尝试去理解它……
    震惊啊,quark 居然在用 vim 的?

  4. Posted 十月 13, 2009 at 2:33 下午 | Permalink

    @pluskid
    我本来也就不大……其实相比起Elisp那本1000+页的手册,从实用目的考虑,我觉得Vim脚本还好了~

  5. Posted 十月 13, 2009 at 4:10 下午 | Permalink

    liancheng :

    @quark
    <s-tab>确实不好用,所以我按照文档中的描述,替换成<c-k>了。从Google Code的项目主页上来看,似乎GIT上的版本已经支持嵌套snippet了,只是vim.org上还未更新,不过我还没有确认过这一点。

    确认了一下,GIT上的版本主要只是更新了几个snippet而已。看来要想有好用的嵌套支持,还是要自己动手了。

  6. Posted 十月 13, 2009 at 6:27 下午 | Permalink

    @pluskid
    那你觉得我是用什么的?

  7. Posted 十月 13, 2009 at 7:52 下午 | Permalink

    @quark
    你俩不是Emacs的死忠么?

  8. Posted 十月 13, 2009 at 11:11 下午 | Permalink

    @liancheng
    澄清澄清,我不是,受不了滥用Ctrl键,而且emacs五个字母也太长了 -.-bbb

  9. Posted 十月 14, 2009 at 1:28 下午 | Permalink

    @quark
    我倒是因为ECB和Erlang的缘故在逐渐向Emacs痛苦地转型 :-(

  10. Posted 十月 15, 2009 at 10:15 上午 | Permalink

    技术达人。
    路过一下。

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">