前言
YCM應(yīng)該是vim補(bǔ)全的標(biāo)配了,關(guān)于配置可以直接閱讀官方README文件,寫得十分詳細(xì),但是需要點(diǎn)耐心。
我之前寫過Ubuntu 16.04 64位安裝YouCompleteMe,主要針對的是apt-get安裝的vim版本不支持YCM的python引擎問題,所以得手動編譯vim源碼。不過關(guān)于YCM的配置文件很多都是直接照抄的,有些用法沒有理解,最近想要看看開源代碼,由于include的頭文件很多都把項(xiàng)目目錄作為包含目錄,而YCM配置文件缺只使用了系統(tǒng)目錄,所以無法進(jìn)行代碼跳轉(zhuǎn)和補(bǔ)全。
修改YCM配置文件
YCM配置文件的路徑在.vimrc文件中定義,比如我的配置是
let g:ycm_global_ycm_extra_conf='~/.ycm_extra_conf.py'
用的默認(rèn)命名ycm_extra_conf.py,直接扔到了用戶主目錄下,修改文件名和路徑都是可以的。該配置文件的核心就是修改flags變量。
flags = [
'-Wall',
'-Wextra',
'-Werror',
'-Wno-long-long',
'-Wno-variadic-macros',
'-fexceptions',
'-DNDEBUG',
'-std=c++11',
'-x',
'c++',
'-I',
'/usr/include',
'-isystem',
'/usr/lib/gcc/x86_64-linux-gnu/5/include',
'-isystem',
'/usr/include/x86_64-linux-gnu',
'-isystem'
'/usr/include/c++/5',
'-isystem',
'/usr/include/c++/5/bits'
]
可以看到flags是一個列表,參數(shù)是和gcc的參數(shù)一致。
比如對下面的示例項(xiàng)目
$ tree sample/
sample/
├── include
│ └── foo.h
└── src
└── main.cc
其中main.cc的include代碼如下

可以發(fā)現(xiàn)vim提示foo.h無法找到,但是我們可以這樣編譯該文件
g++ main.cc -isystem $HOME/sample/include也可以用下列編譯方式
g++ main.cc -I $HOME/sample/include
用man gcc查看手冊,找到-isystem的說明
-isystem dir
Search dir for header files, after all directories specified by -I
but before the standard system directories.
搜索頭文件的順序是:-I指定目錄、-isystem指定目錄、標(biāo)準(zhǔn)系統(tǒng)目錄。
OK,回顧之前flags參數(shù)的設(shè)置,一目了然,只需要在末尾添加新的-I或-isystem,后接頭文件目錄路徑即可。在python中可以定義一個變量保存用戶主目錄路徑
home_dir = os.environ['HOME']
需要注意的細(xì)節(jié)是,flags列表最后一項(xiàng)的的結(jié)尾沒有加逗號,如果在后面添加新項(xiàng),記得加上逗號(我好幾次忘記加了導(dǎo)致語法錯誤)
# 錯誤的代碼
'/usr/include/c++/5/bits' # 粗心大意,直接在后面換行,忘記加逗號。
'-isystem',
os.path.join(home_dir, 'sample/include')
]
拷貝YCM配置文件到項(xiàng)目目錄
之前修改的是全局YCM配置文件,如果有多個項(xiàng)目,每次都修改全局配置文件的話,flags列表就會越來越長,頭文件搜索目錄越來越多,會影響YCM的效率,因此可以拷貝一份到項(xiàng)目目錄下,然后再進(jìn)行修改。
~/sample$ cp ~/.ycm_extra_conf.py .
~/sample$ ls -a
. .. include src .ycm_extra_conf.py
YCM配置文件的查找順序是當(dāng)前目錄>上層目錄>...>根目錄>YCM全局目錄。
然后問題來了,每次打開文件都會提示是否載入YCM配置文件。

這問題十分煩人,不過也給出了提示,想要關(guān)閉這個提示可以參考YCM文檔。找到的解決方案如下
let g:ycm_confirm_extra_conf = 0官方說明表示該選項(xiàng)的默認(rèn)值為1是為了阻止不是由你寫的YCM配置代碼,比如你從網(wǎng)上下載一個項(xiàng)目,由于YCM配置文件默認(rèn)是隱藏文件,所以你也不知道項(xiàng)目目錄下包含了YCM配置文件,此時vim打開代碼時就會提示你是否載入,從而提醒你項(xiàng)目目錄里有YCM配置文件。
所以安全的做法是自己寫代碼時,可以把該選項(xiàng)設(shè)為0,其余情況下保持默認(rèn)值1。
實(shí)踐:閱讀Linux內(nèi)核源碼的配置
這幾天想確認(rèn)下內(nèi)核是否真的用do_fork來區(qū)分線程和進(jìn)程,于是下載了源碼來查找。首先找到kernel/fork.c,vim打開后如下所示

借助
find命令來查找頭文件的位置
~/linux-4.16.13$ find . -name "mm.h"
./drivers/gpu/drm/nouveau/include/nvkm/core/mm.h
./include/linux/decompress/mm.h
./include/linux/mm.h
./include/linux/sched/mm.h
./arch/unicore32/mm/mm.h
./arch/arm/mm/mm.h
./tools/testing/scatterlist/linux/mm.h
./tools/include/linux/sched/mm.h
find默認(rèn)就是遞歸查找,可以用maxdepth選項(xiàng)來控制遞歸最大深度,比如
~/linux-4.16.13$ find . -maxdepth 3 -name "mm.h"
./include/linux/mm.h
于是可以把YCM配置文件拷貝到linux-4.16.13目錄下,修改flags如下
flags = [
# 省略之前的參數(shù)
'-isystem',
os.path.join(home_dir, 'linux-4.16.13'),
'-isystem',
os.path.join(home_dir, 'linux-4.16.13/include')
]
這里還不夠,只是解決了頭文件包含的問題,代碼跳轉(zhuǎn)時,比如光標(biāo)停在task_struct,點(diǎn)擊跳轉(zhuǎn)快捷鍵時,發(fā)現(xiàn)無法跳轉(zhuǎn)。這里就需要用grep命令來查找,
~/linux-4.16.13/include$ egrep -rn "struct +task_struct +{"
linux/sched.h:524:struct task_struct {
egrep即使用擴(kuò)展正則表達(dá)式,內(nèi)核編碼風(fēng)格是把左大括號和結(jié)構(gòu)體名稱放在一行,所以用這個正則表達(dá)式就找到了task_struct的定義。
可以用sed命令多看幾行來確認(rèn)下
~/linux-4.16.13/include$ sed -n '524,530p' linux/sched.h
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
嗯,定義了thread_info成員,繼續(xù)把該目錄加入flags中。但還是跳轉(zhuǎn)不了,因?yàn)榘l(fā)現(xiàn)fork.c中并未包含linux/sched.h。這種情況暫時也沒找到好的方法,只能用grep命令來手動查找。
.vimrc中的其他YCM配置選項(xiàng)
之前的配置文件是東抄抄西抄抄湊的,這次整理了下,給每個選項(xiàng)設(shè)置都加了注釋。
" YouCompleteMe
" Python Semantic Completion
let g:ycm_python_binary_path = '/usr/bin/python3'
" C family Completion Path
let g:ycm_global_ycm_extra_conf='~/.ycm_extra_conf.py'
" 跳轉(zhuǎn)快捷鍵
nnoremap <c-k> :YcmCompleter GoToDeclaration<CR>|
nnoremap <c-h> :YcmCompleter GoToDefinition<CR>|
nnoremap <c-j> :YcmCompleter GoToDefinitionElseDeclaration<CR>|
" 停止提示是否載入本地ycm_extra_conf文件
let g:ycm_confirm_extra_conf = 0
" 語法關(guān)鍵字補(bǔ)全
let g:ycm_seed_identifiers_with_syntax = 1
" 開啟 YCM 基于標(biāo)簽引擎
let g:ycm_collect_identifiers_from_tags_files = 1
" 從第2個鍵入字符就開始羅列匹配項(xiàng)
let g:ycm_min_num_of_chars_for_completion=2
" 在注釋輸入中也能補(bǔ)全
let g:ycm_complete_in_comments = 1
" 在字符串輸入中也能補(bǔ)全
let g:ycm_complete_in_strings = 1
" 注釋和字符串中的文字也會被收入補(bǔ)全
let g:ycm_collect_identifiers_from_comments_and_strings = 1
" 彈出列表時選擇第1項(xiàng)的快捷鍵(默認(rèn)為<TAB>和<Down>)
let g:ycm_key_list_select_completion = ['<C-n>', '<Down>']
" 彈出列表時選擇前1項(xiàng)的快捷鍵(默認(rèn)為<S-TAB>和<UP>)
let g:ycm_key_list_previous_completion = ['<C-p>', '<Up>']
" 主動補(bǔ)全, 默認(rèn)為<C-Space>
"let g:ycm_key_invoke_completion = ['<C-Space>']
" 停止顯示補(bǔ)全列表(防止列表影響視野), 可以按<C-Space>重新彈出
"let g:ycm_key_list_stop_completion = ['<C-y>']