vim语法文件编写总结

    最近对之前写的vim的less插件(http://www.vim.org/scripts/script.php?script_id=3964)进行了重写,对于使用vim scripit脚本来支持一种新的文件类型有一些收获(主要是对于语法文件的定义),做些总结。

    (一)

    首先,vim打开一个文件时,是怎么决定应该对这个文件使用哪些合适的配置的。答案是根据文件的filetype属性。有了这个filetype,vim就可以对文件进行对应的配置了。可以使用命令set filetype=less或者setfiletype less来进行设置,两者的区别主要是setfiletype可以避免对filetype进行多次设置,以免影响其它配置的应用。

    (二)

    less插件的整体结构是这样的:

    ftdectect文件夹下的vim脚本主要是用来“嗅探“并设置filetype属性的;ftplugin文件夹下的脚本是filetype plugin,就是针对特定文件类型的一些设置;indent和syntax分别是缩进和语法高亮。

    (三)

    首先是ftdetect下的less.vim。我们需要”嗅探“并设置合适的filetype,只需要当vim打开后缀名是less的文件时设置其filetype为less即可。所以,这个脚本只需要一行代码:

autocmd BufNewFile,BufRead *.less setfiletype less

    (四)

    然后是ftplugin下的less.vim。这里我们想要实现当保存后缀名为less的文件时,使用安装的lessc将文件编译成同名的css文件,并且echo错误信息。首先定义一个进行这些操作的函数,如下:

func! s:CompileLess()
    let l:input = fnameescape(expand("%:p"))
    let l:output = fnameescape(expand("%:p:r") . ".css")

    let l:cmd = "lessc " . l:input . " " . l:output

    let l:errs = system(l:cmd)

    if (!empty(l:errs))
      " replace the escape string(\%oxxx match the octal character).  e.g: \033[33m
      let l:errs = substitute(l:errs, "\\%o033[\\d\\+m", "", "g") 
      " replace the blank lines
      let l:errs = substitute(l:errs, "^$", "", "g")
      " we jsut need the error message
      let l:errs = split(l:errs, "\\n")[0]

      echo l:errs
    endif
endfunc

    expand("%:p")就是展开当前文件的全路径,而expand("%:p:r")就是当前文件全路径,但是把最后的后缀省略。具体可以:h expand。由于lessc产生的错误信息包含转义序列(主要是为了在终端输出时进行有颜色的输出),所以我们在echo前把这些转义序列全部替换掉,以免影响阅读。替换的正则其实是\%o033[\d\+m,但是由于传入的是字符串,所以\要使用\\进行转义。

    有了这个函数,在后缀名为less的文件保存时调用之就可以了。即:

autocmd! BufWritePost,FileWritePost *.less call s:CompileLess()

    (五)

    less其实缩进的规则和css大体相似,所以,我们直接使用css的缩进已经基本可以满足需求了。所有indent下的脚本就很简单,直接使用runtime命令装载(source)下css的indent文件即可。如下:

if exists("b:did_indent")
  finish
endif

" use css indent is enough
runtime! indent/css.vim

    (六)

    最复杂的部分,应该是语法的定义了。对语法的定义主要是使用正则表达式,所以,还是要恶补下这个东东啊==。

    less很像css,所以我们首先装载css的syntax文件,然后再对其进行改进。即:

" use the css syntax and then enhance it.>.<
runtime! syntax/css.vim

syn case ignore

    syn是syntax的缩写,是主要用来进行语法元素定义的命令。

    1)我们从比较简单的注释定义开始。less有2种注释,一种和css的一样,/**/的形式,编译后不会移除;一种是//的形式,编译后会移除。于是,我们进行如下定义:

" comments 
syn keyword lessTodo FIXME NOTE TODO OPTIMIZE XXX contained
syn match lessComment "\/\/.*" contains=@Spell,lessTodo
syn region lessCssComment start="/\*" end="\*/" contains=@Spell,lessTodo
hi def link lessCssComment lessComment
hi def link lessComment Comment
hi def link lessTodo Todo

    syntax的定义用法,主要有3种:keyword,match,region,这段里都用上了。

    (1)keyword就是关键字一样,必须是整个词匹配才算匹配。比如:上面的TODO。

    (2)match是可以指定一个正则,用正则来匹配。比如:"\/\/.*"就是表示//再跟任意数量(正则里用*表示)的任意字符(正则里用.表示)。

    (3)region是通过start和end两个正则来定义一个区域。比如:start="/\*" end="\*/"就表示/*开头,*/结尾的一个区域。

    (4)syntax命令可以带其余的参数,比如:contains,它指定这个语法元素里包含的其它元素。contains=@Spell,lessTodo是指,里面可以包含我们定义的lessTodo,这样,即使lessTodo元素包含在lessComment和lessCssComment语法元素里,它也可以通过highlight命令link不同的颜色。

    hi是highlight命令的缩写,主要用来定义语法项目的颜色。比如:hi def link lessTodo Todo就是指将lessTodo这个语法元素定义成vim默认的Todo配色组。(可以使用:h group-name查看vim定义的配色组)。

    这样,注释使用Comment的配色,但是TODO这些字符使用Todo的配色。

    2)然后我们定义一些基础元素,供后面使用。如下:

" css props and attrs
syn cluster lessCssProperties contains=css.*Prop
syn cluster lessCssAttributes contains=css.*Attr,cssValue.*,cssColor,cssURL,cssImportant,cssErr,cssStringQ,cssStringQQ,lessComment

syn region lessDefinition matchgroup=cssBraces start='{' end='}' contains=TOP

    (1)cluster可以将多个语法元素定义成一个,以方便后面使用contains来包含。这里我们定义lessCssProperties包括所有的css properties,lessCssAttributes包括所有的css attribute,颜色值,字符串等等表示attribute的东西。

    (2)然后我们定义了lessDefinition,从{到}的部分,包括所有没有contained参数的语法元素。matchgroup是另一个参数,由于region定义的区域会将整个区域都视为一体,这样在定配色时,也会对{和}使用相同的配色,通过使用matchgroup=cssBraces,我们可以将{和}单独定义为cssBraces这个语法元素,从而对它进行不一样的配色。

    3)然后我们进行less的property和attribute的定义。如下:

" less props (contain in less definition)
" (?<=[{};]\s*|^\s*)([\w-])+\s*:
syn match lessProperty "\%([{};]\s*\|^\s*\)\@<=\%([[:alnum:]-]\)\+\s*:" contains=@lessCssProperties skipwhite nextgroup=lessAttribute contained containedin=lessDefinition

" less attrs (contains all the css attr, less variable, less functinos, less string interpolation)
" ([^{};])* 
syn match lessAttribute "\%([^{};]\)*" contained contains=@lessCssAttributes,lessVariable,lessFunction,lessInterpolation

    (1)lessProperty这个语法元素的正则是指:首先通过向后查找(即:\@<=)匹配一个位置,这个位置是{};后跟空白或者一行的开头后跟空白,然后是任意数量的字符,然后是冒号。这样就解决了less的嵌套写法。感谢sass语法文件的作者!(https://github.com/tpope/vim-haml/blob/master/syntax/sass.vim

    (2)contains指定可以包含我们定义的lessCssProperties簇(通过@lessCssProperties的方式引用)。

    (3)contained是指这个语法元素只有被contains才有效,只能存在于另一个语法元素中。

    (4)containedin值这个语法元素存在于哪里,这里是lessDefinition。

    (5)nextgroup指定该语法元素的跟随元素,这里是lessAttribute。

    (6)lessAttribute就比较简单,就是不含{};的字符,包括所以css的attribute,less变量,less函数,less字符串插值。

    4)接着,再看看@import的定义,如下:

" include
" me=e-1 means the last char of the match does not highlighted.(i.e the ';')
" me: match end
syn region lessInclude start="@import" end=";\|$"me=e-1 contains=lessComment,cssStringQ,cssStringQQ,cssURL,cssUnicodeEscape,cssMediaType
hi def link lessInclude Include

    (1)前面说过,region区域定义的语法元素会被全部”着色“,这里我们不想结尾的分号被”着色“,所以使用了位移。end=";\|$"me=e-1是匹配分号或者行尾,me是匹配结束的意思,me=e-1即是匹配的结尾往前移一位,就是不把分号算进去。这样,link lessInclude到Include时,分号就不会”着色“了。

    5)在写less语法文件时,关于vim用到的语法定义方式,大致就用到这些了,其余部分就不多说了。所有对less语法元素进行定义的代码可以在这里获取:https://github.com/KohPoll/vim-less/blob/master/syntax/less.vim

 

    写这种比较无聊的文章果然很费精力,其实不如直接读vim的帮助文档。= =

posted on 2012-08-04 23:14  KohPoll  阅读(3515)  评论(2编辑  收藏  举报