利用vim的terminal特性和cmake,構建c\c++編譯系統
前言:
自從vim更新了8.1以來,對其使用terminal特性的文章還比較少,網路上用vim構建編譯c家族編譯系統的文章比較落後和簡單。因此本文在windows系統演示一下:利用cmake的跨平臺編譯工具,搭建在vim中“一鍵編譯”的功能。
介紹:
總的來說:terminal特性可以讓使用者在vim上執行終端命令,基於job的非同步特性,實時把命令內容反饋給使用者並可以與使用者互動。
優勢:利用cmake工具較好地解決了跨平臺問題、非同步過程流暢等
擴充套件:terminal特性可以讓使用者執行終端命令,因此不單單隻用於編譯。
環境準備:
1、unix系或者windows 等系統
2、建議vim8.0以上版本
3、cmke工具
4、windows系統vs2010以上版本,linux系統gcc或clang
本文用windows和vs2017演示
準備知識:
1、熟悉cmake
2、瞭解vim指令碼
3、:h terminal ,
我們知道:函式term_start(command,options)用於執行command命令並反饋給使用者,其中options是一個dict可以用於控制,例如:let options={'hidden':1}傳遞給term_start可以控制反饋buffer是否可視。
函式term_sendkeys(buffer,msg)可以把訊息傳遞給terminal。
基本思路:
vim使用者按下一鍵編譯,vim指令碼得到工程中預設定的CMakeLists.txt檔案的目錄,vim進入指定目錄cmake,開始對工程進行gernarate(構建),構建好後文件將生產在工作目錄cmake中,當每次依賴關係發生改變,只需改進CMakeLists.txt的規則,再次進行gernarate。首次用cmake構建會比較慢,往後無大改cmake的規則的情況下構建都會很快。同理可利用cmake執行編譯命令。
實現:
1、 確保當前使用的vim支援terminal特性(echo has('terminal')不返回0 )
構建一個函式放在vimrc中:
func! Compile(isGernarate) exec "normal! w" if &filetype == 'c' || &filetype == 'cpp' let l:CMakeListsDir=GetObjectFile(Current_buffer_path(), \'CMakeLists.txt') if l:CMakeListsDir=='' echo 'Can not find CmakeLists file.' else let l:CMakeListsDir=fnamemodify(l:CMakeListsDir,':h') let l:GernarateDir=l:CMakeListsDir.'/cmake' if !isdirectory(l:GernarateDir) if mkdir(l:GernarateDir,"p", 0700)|| !exists("*mkdir") echo 'Failed to create GernarateDir.' endif else let l:GernarateDir=s:Slash(l:GernarateDir) let l:CMakeListsDir=s:Slash(l:CMakeListsDir) let l:options={"out_cb":function('s:CallBackOfTerminal'), \"cwd":l:GernarateDir} if a:isGernarate call term_start("cmake . ".l:CMakeListsDir,l:options) else let l:target='app' let l:Release='Release' call term_start( \"cmake --build . --target ". \l:target." --config ". \l:Release, \l:options) endif " call term_sendkeys( "\term_start('C:\Windows\System32\cmd.exe', "\l:options),"cmake . ".l:CMakeListsDir."\<CR>") " " the parameter of command must be surrouded " by double quotation mark in windows endif endif " elseif &filetype == 'java' …… else echo 'No setting for ['.&filetype.']' endif endfunc function! GetObjectFile(WhereToStart,ObjectFileName) abort "{{{ let l:path=tr(a:WhereToStart, '\', '/') if isdirectory(l:path) let l:path = fnamemodify(l:path, ':p:s?/$??') endif let l:PathToObjectFilePath='' while 1 let l:path = fnamemodify(l:path, ':h') let l:mode=l:path.'/'.a:ObjectFileName if filereadable(l:mode) let l:PathToObjectFilePath=l:mode break endif if l:path==fnamemodify(l:path, ':h') break endif endwhile return l:PathToObjectFilePath "}}} endfunc
其中GetObjectFile可以一層層往上地遍歷出最近的CMakeLists.txt檔案的目錄,Current_buffer_path函式可以獲得當前buffer檔案的所在的目錄,expand()在windows系統中獲得檔案路徑可能會出現字首的缺失。變數GernarateDir是cmake構建編譯系統的工作目錄,構建檔案將放在這裡。函式Slash是用於相容路徑中/和\。l:options={"out cb":function('s:CallBackOfTerminal'), "cwd":l:GernarateDir}中out_cb可以實時反饋資訊給callback處理。cwd是命令的工作路徑。至於cmake的build命令中的目標target可以靈活改變,這裡不做演示。
把 compile繫結在F5(構建)和F6(編譯)上
nmap <F5> :call Compile(1)<cr> nmap <F6> :call Compile(0)<cr>
2、編寫CMakeLists
我們寫上面的程式碼再寫一個基本工程框架:cmake目錄中是makefile等檔案,src是原始碼目錄,CMakeLists檔案在../cmake。如圖:

定義的依賴關係如下:
cmake_minimum_required (VERSION 2.8) project (app) aux_source_directory(${CMAKE_SOURCE_DIR}/src CURREN_SRC) add_executable(app ${CURREN_SRC})
至此,編譯系統已比較完善。

Tips:
1、vs對c家族的程式碼除錯的支援還是很不錯的,可以用vim寫程式碼,vs編譯並且除錯。
2、vim的terminal不太完善,但可以使用系統給的shell工具
call term_sendkeys(" term_start('C:\Windows\System32\cmd.exe'," \ l:options),"cmake .".l:CMakeListsDir."\<CR>")
使用term_sendkeys控制執行命令。
3、把terminal視窗隱藏,利用callback把反饋內容列印到quickfix視窗中,如有編譯錯誤可以解析到quickfix視窗,但vim返回的資訊有不規則碼(不懂)。
function! s:CallBackOfTerminal(channel, text) let l:context=a:text " let l:context= substitute(l:context, '\[.\{-}[a-z]', "", "") caddexpr l:context endfunction
使用正則替換的使用substitute不能用上非貪婪模式,對此疑惑,望解答!
推薦這個外掛可把編譯的錯誤資訊非同步的解析到quickfix中: ofollow,noindex">skywind3000 / asyncrun.vim
但這個外掛是使用job特性來執行命令的,因此對某些命令的支援不太好,例如:cmake命令,和git的clone命令,對編碼的支援不太好。