2011年11月4日 星期五

Vim 自動提示編譯錯

Vim error highlight

2013/08/12 更新[[[ 自從 YouCompleteMe 這個 plugin 出來之後,一堆 plugin 都變成垃圾了,包含這篇文章的所有內容,也變成垃圾了,請直接參照 https://github.com/Valloric/YouCompleteMe 然後不用再往下讀了。 ]]]

一直想要一種功能,就是 vim 可以自動顯示錯誤的程式碼,像是 visual studio 或是 eclipse 那樣。最近摸索了一下終於讓我實現了這個功能 :P

事先準備

  • AsyncCommand plugin

    可以在背景執行動作,不會妨礙現在進行的編輯作業。

  • errormarker plugin

    可以把 quick fix list 裡面的錯誤,轉而使用 syntex highlight 直接在編輯視窗內標示出來,看一下官方網站的 screensho:

    http://mh21.piware.de/vim-error-markers-doxygen.png

進行修改

理論上這兩個東西都有安裝的話,好像就成功了,不但可以在背景編譯,還可以在編輯畫面表示錯誤。當然,實際上這兩個都安裝了以後,還是沒辦法直接達成目的的,不然我也不用寫這篇文了 XD

經過 trace 兩者的 script 以後,發現原因在於 errormarker 是透過 hook QuickFixCmdPost event 來呼叫 SetErrorMarkers() 函數來把 quick fix list 裡面的資料轉標到編輯畫面。當平常使用 :make 的時候,QuickFixCmdPost 會被觸發,但是當透過 AsyncCommand 進行 make 的時候,似乎不會觸發 QuickFixCmdPost 事件,於是 SetErrorMarkers() 也不會被呼叫,然後一切都完啦。所以我們的目標就是讓 AsyncCommand 「正確」的呼叫 SetErrorMarker() 就好。

先講一下 AsyncCommand 的機制,AsyncMake 其實是一個 wrapper,包住了 asynccommand#run(make, asynchandler#qf) 這個動作,後面那個 asynchandler#qf 是等到 make 結束以後,用來處理結果的 handler,而這個 handler 會在 asynccommand#done() 的時候被呼叫,所以我們只要在 asynccommand#done() 裡面呼叫 SetErrorMarkers() 就可以了。

asynccommand#done().vim/bundle/asynccommand/autoload/asynccommand.vim 這個檔案裡面,找到他,

 1   function! asynccommand#done(temp_file_name)
 2     " Called on completion of the task
 3     let r = s:receivers[a:temp_file_name] "handlers are registered in s:receivers
 4     if type(r.dict) == type({})
 5       call call(r.func, [a:temp_file_name], r.dict)
 6     else
 7       call call(r.func, [a:temp_file_name])
 8     endif
 9     unlet s:receivers[a:temp_file_name]
10     delete a:temp_file_name
16   endfunction

在函數結束之前加上呼叫 SetErrorMarkers() 的程式碼,變成這樣:

 1   function! asynccommand#done(temp_file_name)
 2     " Called on completion of the task
 3     let r = s:receivers[a:temp_file_name] "handlers are registered in s:receivers
 4     if type(r.dict) == type({})
 5       call call(r.func, [a:temp_file_name], r.dict)
 6     else
 7       call call(r.func, [a:temp_file_name])
 8     endif
 9     unlet s:receivers[a:temp_file_name]
10     delete a:temp_file_name
11
12     if exists("*ExposeSetErrorMarkers") " Check and
13       call ExposeSetErrorMarkers()      " Call ExposeSetErrorMarkers()
14     endif                               " here
15     
16   endfunction

注意到我們呼叫的不是 SetErrorMarkers() 而是 ExposeSetErrorMarkers(),這是因為 SetErrorMarkers() 是一個 script local 函數,只能在 script 內部被呼叫,所以我在 errormarker.vim 裡面加了一個函數來曝光 SetErrorMarkers()

1   function! ExposeSetErrorMarkers()
2       call s:SetErrorMarkers()
3   endfunction

這樣以後只要輸入 AsyncMake 就可以在背景編譯,編譯完成以後自動 highlight 錯誤了。為了方便使用,我還加上了幾個 autocommand:

1   au BufWritePost *.h   AsyncMake
2   au BufWritePost *.c   AsyncMake
3   au BufWritePost *.cc  AsyncMake
4   au BufWritePost *.cpp AsyncMake

這樣編輯 c/c++ 的時候每次存檔都會自動編譯然後 highlight 錯誤。

其實我知道我的改法很醜啦,但是他 work XD 如果有人知道比較優雅的改法請跟我分享阿~

小記:我發現我 ubuntu 安裝 vim 的話,是沒辦法作這件事的,執行 script 的時候會發生錯誤,但是安裝 vim-gtk 的話就可以了,不知道是因為設定的時候哪個選項沒開,但是總之如果有人遇到一樣的問題的話請試試看安裝 vim-gtk。