使用 Vim 搭建 C/C++ 開發環境


剛接觸 Vim 的同學往往因為無法搭建開發環境而“從入門到放棄”,本文旨在幫助這些同學搭建開發環境,聚焦於最核心的開發需求,忽略換配色調字體之類的細枝末節。如果需要開箱即用的 vim 配置(發行版),可以使用 Spacevim

本文使用 neovim-nightly,但也適用於 Vim 8.2+,不需要讀者有任何 VimL 基礎,以 C/C++ 為例,但應該適用於任何語言。

插件管理

在 Vim 中,插件只是一些腳本,存放在特定的目錄中,運行時將它們所在的目錄加入到 runtimepath 中。Vim 8 內置了插件管理功能,但不支持高級的插件管理功能。Vimmers 實現了多個插件管理器,可以自動下載、更新、安裝插件,還可以延遲加載、按需加載,提高啟動速度。

上古時期流行手動或使用 Vundle 管理插件,以上兩種方式已經落伍了,這里介紹目前比較流行的三個插件管理器:

  • vim-plug:簡單易用高效,是目前最流行的插件管理器
  • dein.vim:功能強大,但使用復雜
  • vim-pathogen:另一款流行的插件管理器,沒有用過不做評價

以上三款插件管理器風格各不相同,都有大量用戶,功能相當完善,根據自己的喜好選取即可。推薦新手選擇 vim-plug,對啟動時間特別敏感的同學可以考慮 dein.vim,我的配置在安裝 70 余個插件的情況下,啟動僅需 60 余秒。

使用 vim-plug 安裝插件只需要在 .vimrc 中寫入以下代碼:

call plug#begin('~/.vim/plugged') " 括號里面是插件目錄
                                  " 只能在 plug#begin() 和 plug#end() 之間寫安裝插件的命令
Plug 'junegunn/vim-easy-align'    " 用戶名/插件名,默認從 github 下載安裝
Plug 'https://github.com/junegunn/vim-github-dashboard.git' " 從特定 URL 下載安裝
call plug#end()

使用 dein.vim 安裝插件:

set runtimepath+=~/.vim/plugged/repos/github.com/Shougo/dein.vim " 將 dein.vim 添加到 runtimepath
                                                                 " 注意這是 Ex 命令,路徑不要加引號
if dein#load_state('~/.vim/plugged')        " 參數是插件目錄
    call dein#begin(general#plugin_dir)
    call dein#add('~/.vim/plugged/repos/github.com/Shougo/dein.vim')  " 安裝 dein.vim
	call dein#add('junegunn/vim-easy-align') " 用戶名/插件名,默認從 github 下載安裝
	call dein#end()
	call dein#save_state()
endif

if dein#check_install() " 自動安裝未安裝的插件
	call dein#install()
endif

在安裝插件的代碼后加上這兩行設置:

filetype plugin indent on
syntax on

這樣可以確保特定於文件類型的插件正常工作。

代碼補全

最簡單的代碼補全方式是利用 ctags 生成 tag 文件,補全插件解析 tag 文件進行補全,這種方式有以下兩個好處:

  • 最小依賴
  • 高效可靠,適用於任何規模的項目

基於 tag 的補全不夠智能,后來又誕生了 life-changer 級別的補全插件 YouCompleteMe,可以提供 IDE 級別的代碼補全。但YouCompleteMe 不是開箱即用的,需要下載依賴並編譯,並且耦合度比較大,只支持特定語言(主要是 C++)。

目前體驗補全體驗最好的方式是基於 LSPLanguage Server protocal)的方案。LSP 是一套通信協議,遵從 LSP 規范的客戶端(各種編輯器/IDE)可以通過眾多 LSP 服務端按協議標准進行通信,由客戶端完成用戶界面相關的事情,由服務端提編程語言相關的:補全,定義引用查找,診斷,幫助文檔,重構等服務。架構圖如下:

LSP

有了 LSP,不同的 IDE/編輯器只需要實現 LSP 客戶端,專心改進用戶體驗,所有補全的工作都交給 LSP 服務器。使用基於 LSP 的方案,用戶可以在多種語言間無縫切換,讓 Vim 支持所有語言(只要有 LSP 實現),用戶只需要做以下兩件事:

  • 選擇 LSP 客戶端
  • 選擇 LSP 服務器端

目前 LSP 客戶端有以下幾種選擇:

coc.nvim 使用 Typescript 開發,是目前最流行、最強大的 LSP 客戶端,已經發展成了一個 Vim 插件平台,存在大量基於 coc.nvim 開發的插件(coc 拓展),推薦大家使用 coc.nvim。

coc.nvim 依賴於 node.js,但 node.js 似乎已經不再支持 32 位機,因此最新的 coc.nvim 很可能無法在 32 位機上運行,請考慮其他幾種方案。

" <Tab>選擇補全候選
inoremap <silent><expr> <TAB>
            \ pumvisible() ? "\<C-n>" :
            \ <SID>check_back_space() ? "\<TAB>" :
            \ coc#refresh()
inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"

function! s:check_back_space() abort
    let col = col('.') - 1
    return !col || getline('.')[col - 1]  =~# '\s'
endfunction
" gn 跳轉到下一個錯誤,gN 跳轉到上一個錯誤
nmap <silent> gN <Plug>(coc-diagnostic-prev)
nmap <silent> gn <Plug>(coc-diagnostic-next)

" gd 跳轉到定義,gs 跳轉到引用,gt 跳轉到類型定義,gK 顯示文檔
nmap <silent> gd <Plug>(coc-definition)
nmap <silent> gs <Plug>(coc-references)
nmap <silent> gt <Plug>(coc-type-definition)
nnoremap <silent> gK :call <SID>show_documentation()<CR>
function! s:show_documentation()
    if (index(['vim','help'], &filetype) >= 0)
        execute 'h '.expand('<cword>')
    elseif (coc#rpc#ready())
        call CocActionAsync('doHover')
    else
        execute '!' . &keywordprg . " " . expand('<cword>')
    endif
endfunction

" <Leaderf>rv 改名,<Leaderf>rf 重構
nmap <leader>rv <Plug>(coc-rename)
nmap <Leader>rf <Plug>(coc-refactor)

" <C-f> 和 <C-b> 滾動懸浮窗口
nnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : "\<C-f>"
nnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : "\<C-b>"
inoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? "\<c-r>=coc#float#scroll(1)\<cr>" : "\<Right>"
inoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? "\<c-r>=coc#float#scroll(0)\<cr>" : "\<Left>"
vnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : "\<C-f>"
vnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : "\<C-b>"
autocmd User CocJumpPlaceholder call CocActionAsync('showSignatureHelp')

coc.nvim 有自己的配置文件,叫做 coc-settings.json,一般存放在 .vim(neovim 的話在 ~/.config/nvim)。一般我們會在 coc-settings.json 中微調 coc.nvim 和配置 LSP。

目前功能最強的 C++ LSP 服務器是 ccls,在 coc-settings.json 中配置 ccls:

{
	"languageserver": {
        "ccls": {
			"command": "ccls",
			"filetypes": ["c", "cc", "cpp", "c++"],
			"rootPatterns": [".ccls", "compile_commands.json", ".git/", ".root"],
			"initializationOptions": {
				"cache": {
					"directory": ".cache/ccls"
				},
                "highlight": {"lsRanges": true }
			}
		}
    }
}

以上配置僅在編輯 C/C++ 文件使用命令ccls啟動 LSP 服務器,將項目根目錄設置為包含rootpatterns中任意文件的目錄,索引文件存放在項目根目錄的隱藏目錄 .cache/ccls 中。highlight字段指示 ccls 生成高亮信息,基於 LSP 的語法高亮插件會利用 LSP 服務器提供的信息進行精確的語法高亮。

為了讓 C++ LSP 服務器知道以程序以何種方法編譯,必須要在項目根目錄生成 編譯數據庫(compile_commands.json)。CMake 內置了對編譯數據庫的支持,只需要在執行 CMake 時加上-DCMAKE_EXPORT_COMPILE_COMMANDS=1即可;如果使用 Makefile,可以利用 Bear 生成編譯數據庫,通過bear make來執行 Makefile。Bear 需要執行 Makefile 才能生成編譯數據庫,如果項目無法正常構建,將不能生成編譯數據庫,沒法使用 LSP 的功能。

有些 vimmer 還基於 coc.nvim 開發了一些插件,可以在這里查看完整的拓展列表。這里給兩點建議:

  • 盡量不要用 Vim 開發環境開發環境插件管理器安裝 coc.nvim 拓展

coc 基於 coc.nvim,使用 Typescript 編寫,有些 coc 拓展僅支持使用 coc.nvim 安裝。建議在 .vimrc 中定義列表let g:coc_global_extensions,把自己想安裝的 coc 拓展寫進入,coc.nvim 會在第一次打開文件時安裝。

let g:coc_global_extensions` = ['coc-vimlsp', 'coc-rust-analyzer']
  • 優先使用 coc 拓展配置 LSP

coc-rust-analyzer 之類的 LSP coc 拓展通常利用 coc.nvim 實現了更多 LSP 功能,請優先使用這些拓展,只在沒有對應語言的 LSP coc 拓展時手動配置 LSP。

使用 ccls ,即使是在 Linux 這種規模的代碼倉庫中也可以流暢地補全代碼。code-complete

錯誤檢查

目前錯誤檢查有兩種方案:

  • 定時調用外部程序實現實時錯誤檢測
  • LSP

如果使用 coc.nvim,不需要額外配置,開箱即用。coc.nvim 使用 LSP 進行錯誤檢查,不夠靈活,無法使用 linter 實時檢測代碼。

基於外部程序的方案非常靈活,比如,可以在基礎的錯誤檢測之外同時使用 clang-tidy 等工具進行檢測。目前這種方案最好的插件是 ale,並且 ale 可以與 coc.nvim 共存,用 ale 做實時錯誤檢查,coc.nvim 做補全。如果沒有特殊需求,直接使用 coc.nvim 即可。

dynamic check

符號索引

LSP 已經提供了符號索引的功能,可以方便地跳轉到定義/引用。通常 LSP 的功能已經夠用,但 LSP 存在以下缺點:

  • 僅支持單一語言,在多語言項目中無法無法工作

比如可能存在匯編和 C 混合的項目,匯編定義了一個變量在 C 中讀寫,LSP 無法理解匯編,找不到變量定義的地方。

  • LSP 的符號索引功能有限

LSP 一般不支持跳轉到變量賦值的地方,不支持查找包含該頭文件的源文件等。

我們可以使用靜態代碼索引工具,克服 LSP 的以上缺點。目前靜態代碼索引最好的方案是 ctags 和 global(gtags)混合使用,具體的方法參考韋應笑的深度文章Vim 8 中 C/C++ 符號索引:GTags 篇,不再贅述。。

Tips:

  • 建議同時使用 LSP 和靜態索引工具,LSP 支持的功能用 LSP,不支持的功能或沒有 LSP 時用靜態索引工具,由於實現這個功能需要用 VimL 編程,這里不介紹,有興趣的話可以參考我的配置

  • ccls 實現了更多的功能,如查看類繼承體系,查看調用鏈,查找類的全部實例等。參考配置如下,部分功能用的比較少,就不創建快捷鍵了,直接使用命令。

" This comands are defined for ccls(only supports C/C++)
command! -nargs=0 Derived :call CocLocations('ccls','$ccls/inheritance',{'derived':v:true})
command! -nargs=0 Base :call CocLocations('ccls','$ccls/inheritance')
command! -nargs=0 VarAll :call CocLocations('ccls','$ccls/vars')
command! -nargs=0 VarLocal :call CocLocations('ccls','$ccls/vars', {'kind': 1})
command! -nargs=0 VarArg :call CocLocations('ccls','$ccls/vars', {'kind': 4})
command! -nargs=0 MemberFunction :call CocLocations('ccls','$ccls/member', {'kind': 3})
command! -nargs=0 MemberType :call CocLocations('ccls','$ccls/member', {'kind': 2})
command! -nargs=0 MemberVar :call CocLocations('ccls','$ccls/member', {'kind': 4})
nmap <silent> gc :call CocLocations('ccls','$ccls/call')<CR>
nmap <silent> gC :call CocLocations('ccls','$ccls/call', {'callee': v:true})<CR>

symbol jump

任務系統

在古老的 Vim 工作流中,項目的構建一直是個老大難的問題,要么手動完成,要么自己寫簡單的腳本完成,VSCode 引入任務系統解決了這個問題,韋易笑大佬的 asyncrun.vimasynctasks.vim 又將 VSCode 的任務系統引入到了 Vim 中,徹底改變了 Vim 的工作流。這充分體現了 Vim 的優勢,Vim 用戶非常樂於吸收別的編輯器的優點,讓 Vim 變得更好。

asyncrun.vim 讓用戶可以異步運行 shell 命令,asynctasks 讓用戶可以將常用的命令寫入到配置文件中(~/.vim/tasks.ini 或項目根目錄中的 tasks.ini),一次編寫多次使用。詳細的使用方法請參考插件的中文文檔。基本配置如下:

" 將終端放到 tab 中
let g:asynctasks_term_pos = 'tab'
" 設置 quickfix 大小
let g:asyncrun_open = 10
" 設置項目根目錄標志
" 實際上,許多插件都使用這種方法定位根目錄,因此可以定一個變量 g:rootmarks,
" 將所有插件的根目錄標志都設置為 g:rootmarks
let g:asyncrun_rootmarks = ['.compile_commands.json', '.ccls', '.git']

以構建 CMake 項目為例,我需要以不同的模式(Debug/Release)執行 CMake,編譯項目,可能還會刪除二進制目錄,利用這兩個 life-changer 級別的插件,可以實現一鍵配置、編譯、運行、清理目錄。

[project-build]
command = cmake --build _builds -- VERBOSE=1
cwd=$(VIM_ROOT)
notify=echo
save=2

[project-run]
command/linux=_builds/$(VIM_PRONAME)
command/win32=_builds\$(VIM_PRONAME).exe
cwd=$(VIM_ROOT)
output=terminal

[project-clean]
command/linux=rm -rf _builds
command/win32=rd/s/q _builds
notify=echo
cwd=$(VIM_ROOT)

[project-configure]
command/linux=cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug -S. -B_builds && ln -sf _builds/compile_commands.json .
command/win32=cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles" -S. -B_builds && copy _builds\compile_commands.json .
cwd=$(VIM_ROOT)
notify=echo
save=2

我的 tasks.ini 中寫好常用的任務命令,您可以直接將我的tasks.ini直接復制到自己的配置中。

在 .vimrc 中映射幾個快捷鍵:

" 編輯全局 tasks.ini,隨時優化工作流
command! TaskEdit exec 'vsp ' .. general#vimfiles .. '/tasks.ini'

" 由於 <Tab> 的碼值和 <C-I> 相同,映射這些快捷鍵后 <C-I> 會變慢
nnoremap  <Tab>5 :AsyncTask file-build<cr>
nnoremap  <Tab>6 :AsyncTask file-run<cr>
nnoremap  <Tab>7 :AsyncTask project-configure<CR>
nnoremap  <Tab>8 :AsyncTask project-build<CR>
nnoremap  <Tab>9 :AsyncTask project-run<CR>
nnoremap  <Tab>0 :AsyncTask project-clean<CR>

build-project

再回頭看前面提到的錯誤檢測,我們可以將執行 linter 的命令寫成一個任務,在代碼沒有語法錯誤后調用。

[clang-tidy]
command=find . -type d -name '.cache' -prune -o -type d -name '_builds' -prune -o -name '*.cpp' -print | xargs clang-tidy -checks=level2 -p default/compile_commands.json
cwd=(VIM_ROOT)
notify=echo

可能您還想統計本項目的代碼量,也可以通過 asynctask.vim 完成:

[cloc]
command=cloc --exclude-dir=_builds,.* .
cwd=(VIM_ROOT)
notify=echo

只有想象力豐富,asyncrun.vim 幾乎沒有完不成的工作!

您可能會有這樣的疑問,假如我定義了 100 個任務,平時只用其中的少數幾個任務,豈不是要經常打開 tasks.ini 查詢?這樣的困擾根本不存在,Vim 的另一個強大之處就是插件可以配合工作,我們會在后面介紹解決這個問題的辦法。

語法高亮

基於正則表達式的語法高亮在 C++ 這種語法非常復雜的語言上表現的很差,2021 年可以徹底拋棄掉這種老掉牙的高亮方案了。請使用 nvim-treesitter,它是目前最好的高亮方案(只支持 neovim-nightly),如果用 Vim 的話請使用 vim-lsp-cxx-highlight

vim-lsp-cxx-highlight 基於 LSP 實現精確的高亮,但存在性能問題,打開文件時有點晃眼,前面 coc-settings.json 中已經配置好了 vim-lsp-cxx-highlight。

nvim-treesitter 是 neovim 移植的 treesitter(是的,從別的編輯器超過來的),基於語義高亮代碼,性能強,容錯好。

highlight

配置代碼如下:

lua <<EOF
require'nvim-treesitter.configs'.setup {
  ensure_installed = {'c', 'cpp', 'toml', 'json', 'lua', 'python', 'bash', 'rust'},
  highlight = {
    enable = true,
  }
}
-- integrate with rainbow
require "nvim-treesitter.highlight"
local hlmap = vim.treesitter.highlighter.hl_map
hlmap.error = nil
hlmap["punctuation.delimiter"] = "Delimiter"
hlmap["punctuation.bracket"] = nil
EOF

Tip:您可以在 vimrc 中進行判斷,在 Vim 中使用 vim-lsp-cxx-highlight,在 neovim-nightly 中使用 nvim-treesitter,可以參考我配置中的 init.vim 和 autoload/tools.vim。

文件操作

許多 Vim 外的編輯器用戶喜歡使用文件樹定位項目文件,但 Vimmer 更喜歡使用模糊查找插件定位文件。盡管如此,文件樹也並非一無用處,在瀏覽自己不熟悉的項目時,文件樹插件可以幫助我們了解項目結構。Vim 自帶文件樹插件,也有許多 vimmer 編寫的插件,這里介紹最經典的 NERDtree

NERDtree 雖然是最經典的文件樹插件,但在許多介紹 Vim 的文章中被罵的狗血臨頭。許多人批評 NERDtree 性能差,在 Linux 這種規模的項目中會直接卡死,但應付中小型項目綽綽有余。

Leaderf 是國人開發的一款模糊查找插件,性能最強,並且支持許多插件。配置如下:

let g:Lf_PreviewResult = {
			\ 'File': 0,
			\ 'Buffer': 0,
			\ 'Mru': 0,
			\ 'Tag': 1,
			\ 'BufTag': 1,
			\ 'Function': 1,
			\ 'Line': 0,
			\ 'Colorscheme': 0,
			\ 'Rg': 1,
			\ 'Gtags': 1
			\}
let g:Lf_PreviewInPopup = 1                       " 在 popup 窗口中預覽結果
let g:Lf_PreviewCode = 1                          " 預覽代碼
let g:Lf_RootMarkers = ['.root', 'compile_command.json', '.git'] "你的根目錄標志
let g:Lf_WorkingDirectoryMode = 'A'              " 設置 LeaderF 工作目錄為項目根目錄,如果不在項目中,則為當前目錄。
let g:Lf_ShortcutF = "<Leader>f"
let g:Lf_ShortcutB = "<Leader>bl"
nnoremap <silent><Leader>p :LeaderfFunctionAll<CR> " 搜索函數
nnoremap <silent><Leader>l :LeaderfBufTagAll<CR>   " 搜索緩沖區中的 tag
nnoremap <silent><Leader>d :LeaderfTag<CR>         " 搜索項目中的 tag
nnoremap <silent><leader>h :LeaderfHelp<CR>        " 搜索 vim help
nnoremap <Leader>rg :Leaderf rg<Space>             " 調用 ripgrep 查找字符串

現在,只要按下 <Leader>f ,即使是 Linux 這種級別的項目,也能在一瞬間切換到目標文件。

LeaderF

既然 LeaderF 的模糊搜索功能如此強大,能不能讓 LeaderF 搜索我們定義的 asynctask.vim 任務?答案當然是可以的!

function! s:lf_task_source(...)
	let rows = asynctasks#source(&columns * 48 / 100)
	let source = []
	for row in rows
		let name = row[0]
		let source += [name . '  ' . row[1] . '  : ' . row[2]]
	endfor
	return source
endfunction

function! s:lf_task_accept(line, arg)
	let pos = stridx(a:line, '<')
	if pos < 0
		return
	endif
	let name = strpart(a:line, 0, pos)
	let name = substitute(name, '^\s*\(.\{-}\)\s*$', '\1', '')
	if name != ''
		exec "AsyncTask " . name
	endif
endfunction

function! s:lf_task_digest(line, mode)
	let pos = stridx(a:line, '<')
	if pos < 0
		return [a:line, 0]
	endif
	let name = strpart(a:line, 0, pos)
	return [name, 0]
endfunction

function! s:lf_win_init(...)
	setlocal nonumber
	setlocal nowrap
endfunction

let g:Lf_Extensions = get(g:, 'Lf_Extensions', {})
let g:Lf_Extensions.task = {
			\ 'source': string(function('s:lf_task_source'))[10:-3],
			\ 'accept': string(function('s:lf_task_accept'))[10:-3],
			\ 'get_digest': string(function('s:lf_task_digest'))[10:-3],
			\ 'highlights_def': {
			\     'Lf_hl_funcScope': '^\S\+',
			\     'Lf_hl_funcDirname': '^\S\+\s*\zs<.*>\ze\s*:',
			\ },
			\ }
nnoremap <silent><leader>T :Leaderf task<CR> "<leader>T 模糊搜索任務

Tips:使用 nerdfontvim-devicons 可以在 LeaderF、NERDtree 等插件中顯示漂亮的文件圖標。

調試

調試一直是 Vim 的弱點,最近 DAPDebug Adapter Protocol)的提出帶來了一些改變。YouCompleteMe 的主要開發者 puremourning 創建了 vimspector,這是目前最強的 Vim 調試插件,仍處於開發階段,您如果有興趣的話可以參考我的博客 Vim 最強調試插件:vimspector

Git

Tpope 的 vim-fugitive 讓 Git 工作流在 Vim 中順暢無比,使用 vim-gitgutter 在側邊欄展示 Git 狀態。

command! -bang -nargs=* -complete=file Make AsyncRun -program=make @ <args> " 異步 git push
" git-gutter
let g:gitgutter_map_keys = 0
nmap ghp <Plug>(GitGutterPreviewHunk) " 預覽修改(diff)
nmap ghs <Plug>(GitGutterStageHunk)   " 暫存修改
nmap ghu <Plug>(GitGutterUndoHunk)    " 丟棄修改
nmap [c <Plug>(GitGutterPrevHunk)     " 上一處修改
nmap ]c <Plug>(GitGutterNextHunk)     " 下一處修改

Tip:vim-fugitve 還可以用來處理 git conflict,這里不介紹。

格式化

注釋請使用 vim-format,它易於拓展,可以支持所有文件類型。vim-format 會根據文件類型執行對應的格式化命令,C/C++ 默認使用 clang-format,所以您只需要將 .clang-format 放到項目根目錄即可。

定義一個快捷鍵快速格式化代碼。

nnoremap <Leader>bf :Autoformat<CR>

Tip:您還可以利用自動命令在寫入文件時自動格式化,利用替換命令在寫入文件時自動清除行尾空白。

注釋

目前最流行的注釋/反注釋是 nerdcommentervim-commentary。nerdcommenter 相比於 vim-commentary 功能更強,拓展性更好,因此推薦使用 nerdcommenter。

" Add spaces after comment delimiters by default
let g:NERDSpaceDelims = 1

" Align line-wise comment delimiters both sides
let g:NERDDefaultAlign = 'both'

" Allow commenting and inverting empty lines (useful when commenting a region)
let g:NERDCommentEmptyLines = 1

" Enable trimming of trailing whitespace when uncommenting
let g:NERDTrimTrailingWhitespace = 1

" Enable NERDCommenterToggle to check all selected lines is commented or not
let g:NERDToggleCheckAllLines = 1

" Usefull when comment argument
let g:NERDAllowAnyVisualDelims = 0
let g:NERDAltDelims_asm = 1

nerdcommenter 默認的快捷鍵請參考文檔。請不要再蝸牛一樣地用:help命令查看文檔,用<Leader>h模糊搜索!

Tip:您會發現注釋/反注釋后光標仍停留在原來的位置,如果您希望光標停留在可視區域結尾,可以添加上以下代碼:

let g:NERDCreateDefaultMappings = 0

" It is impossible to determine execute mode in hooks. Thus, I wrap raw NERDComment()
" to pass mode infomation to hooks and create mappings manually.
"
" NERDCommenterAltDelims is not wrapped and it would execute hooks. So I
" delete variable g:NERDCommenter_mode in NERDCommenter_after() to disable
" hooks executed by NERDCommenterAltDelims
function! s:NERDCommenter_wrapper(mode, type) range
    let g:NERDCommenter_mode = a:mode
    execute a:firstline .. ','  .. a:lastline 'call NERDComment(' .. string(a:mode) .. ',' .. string(a:type) .. ')'
endfunction

" modes: a list of mode(n - normal, x - visual)
function! s:create_commenter_mapping(modes, map, type)
    for l:mode in split(a:modes, '\zs')
        execute l:mode .. 'noremap <silent> <Leader>' .. a:map .. ' :call <SID>NERDCommenter_wrapper(' .. string(l:mode) .. ', ' .. string(a:type) .. ')<CR>'
    endfor
endfunction

function! CreateCommenterMappings()
    " All mappings are equal to standard NERDCommenter mappings.
    call s:create_commenter_mapping('nx', 'cc', 'Comment')
    call s:create_commenter_mapping('nx', 'cu', 'Uncomment')
    call s:create_commenter_mapping('n', 'cA', 'Append')
    call s:create_commenter_mapping('nx', 'c<space>', 'Toggle')
    call s:create_commenter_mapping('nx', 'cm', 'Minimal')
    call s:create_commenter_mapping('nx', 'cn', 'Nested')
    call s:create_commenter_mapping('n', 'c$',  'ToEOL')
    call s:create_commenter_mapping('nx', 'ci', 'Invert')
    call s:create_commenter_mapping('nx', 'cs', 'Sexy')
    call s:create_commenter_mapping('nx', 'cy', 'Yank')
    call s:create_commenter_mapping('n', 'cA',  'Append')
    call s:create_commenter_mapping('nx', 'cl', 'AlignLeft')
    call s:create_commenter_mapping('nx', 'cb', 'AlignBoth')
    call s:create_commenter_mapping('nx', 'cu', 'Uncomment')
    call s:create_commenter_mapping('n', 'ca',  'AltDelims')
    nmap <leader>ca <plug>NERDCommenterAltDelims
endfunction

" NERDCommenter hooks
function! NERDCommenter_before()
    let g:nerdcommmenter_visual_flag = v:false
    if get(g:, 'NERDCommenter_mode', '') =~# '[vsx]'    " executed in visual mode
        let l:marklist = getmarklist('%')
        for l:mark in l:marklist
            if l:mark['mark'] =~ "'>"
                let g:nerdcommmenter_cursor = l:mark.pos
                let g:nerdcommmenter_visual_flag = v:true
                break
            endif
        endfor
    endif
endfunction

function! NERDCommenter_after()
    if g:nerdcommmenter_visual_flag
        call setpos('.', g:nerdcommmenter_cursor)
    endif
    let g:nerdcommmenter_visual_flag = v:false
    unlet! g:NERDCommenter_mode
endfunction

nerdcommenter

結語

本文介紹了用 Vim 搭建開發環境的思路,但 Vim 的魅力不在於“千篇一律”,而在於“各不相同”,每個 Vimmer 都有自己的 Vim,根據自己的習慣不斷改進工作流。總之,希望本文可以幫助大家走進 Vim 的世界。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM