最近對之前寫的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的幫助文檔。= =