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的帮助文档。= =