YouCompleteMe 配合 UltiSnips 补全 C/C++ 函数参数
一直在 Vim 上用 YouCompleteMe 进行 C/C++ 自动补全,一个大的缺陷是不能进行函数参数的补全。后来在 GitHub 上搜索到了这个 issue 中的一个评论,解决了一部分问题,然而仍有一些问题:
在有些时候选中了结果,但并不希望进行函数参数补全,比如输入 C++ 的 I/O manipulator 的时候。因为
std::endl
之类的 I/O manipulator 实际上是个函数,但std::ios_base
的operator<<
是接受了一个函数指针作为参数,因此使用的时候只需std::cout << "xxx" << std::endl
不需要写std::endl
的参数。而此时如果你用了这个方法,选中了补全结果后再输入任何键它都会进行参数列表的展开。在选中补全结果的时候必须使用
Ctrl-Y
进行 snippet 展开。如果直接输入左括号,则会出现多输入一个括号的现象。无法正确处理参数是函数指针的函数。
经过我一番摸索,改进了这个方法,并在那个 issue 下面贴了改进后的代码。也在这里贴一下:
function! s:onCompleteDone()
let abbr = v:completed_item.abbr
let startIdx = stridx(abbr,"(")
if startIdx < 0
return abbr
endif
let endIdx = strridx(abbr,")")
if endIdx - startIdx > 1
let argsStr = strpart(abbr, startIdx+1, endIdx - startIdx -1)
"let argsList = split(argsStr, ",")
let argsList = []
let arg = ''
let countParen = 0
for i in range(strlen(argsStr))
if argsStr[i] == ',' && countParen == 0
call add(argsList, arg)
let arg = ''
elseif argsStr[i] == '('
let countParen += 1
let arg = arg . argsStr[i]
elseif argsStr[i] == ')'
let countParen -= 1
let arg = arg . argsStr[i]
else
let arg = arg . argsStr[i]
endif
endfor
if arg != '' && countParen == 0
call add(argsList, arg)
endif
else
let argsList = []
endif
let snippet = '('
let c = 1
for i in argsList
if c > 1
let snippet = snippet . ", "
endif
" strip space
let arg = substitute(i, '^\s*\(.\{-}\)\s*$', '\1', '')
let snippet = snippet . '${' . c . ":" . arg . '}'
let c += 1
endfor
let snippet = snippet . ')' . "$0"
return UltiSnips#Anon(snippet)
endfunction
autocmd VimEnter * imap <expr> (
\ pumvisible() && exists('v:completed_item') && !empty(v:completed_item) &&
\ v:completed_item.word != '' && (v:completed_item.kind == 'f' \|\|
\ v:completed_item.kind == 'm') ?
\ "\<C-R>=\<SID>onCompleteDone()\<CR>" : "<Plug>delimitMate("
使用改进后的方法后,输入左括号时才会进行函数参数的补全,并且部分解决了函数参数是函数指针的问题。不过由于这个问题我并不是依靠 Clang 的语义分析来解决的,而是直接进行左右括号的匹配,一定会存在仍不能解决的情况。不过大部分情况应该都没问题了。另外我使用了 delimitMate,上面这个配置也兼容 delimitMate。
另外再提供两个 Vim 的实用配置。
用 Tab 键进行 delimitMate 的光标跳转(也就是说,输入左括号后使用 Tab 键就可跳转到 delimitMate 生成的右括号的右边,而无需 <S-TAB>
),且不破坏 UltiSnips 的 Tab 键展开,同时禁用 delimitMate 自带的 <S-TAB>
:
autocmd VimEnter * imap <silent> <expr> <TAB> delimitMate#ShouldJump() ? delimitMate#JumpAny() : "\<C-r>=UltiSnips#ExpandSnippetOrJump()\<CR>"
autocmd VimEnter * inoremap <S-TAB> <S-TAB>
让补全下拉菜单支持用回车键选择补全结果,同时不破坏 endwise 的回车键:
autocmd VimEnter * imap <expr> <CR>
\ pumvisible() ?
\ (exists('v:completed_item') && !empty(v:completed_item) &&
\ v:completed_item.word != '' && (v:completed_item.kind == 'f' \|\|
\ v:completed_item.kind == 'm')) ?
\ "\<C-R>=\<SID>onCompleteDone()\<CR>" :
\ "\<C-y>" :
\ "\<Plug>delimitMateCR\<Plug>DiscretionaryEnd"
好了,这样我的 Vim 终于可以像 IDE 一样愉快地进行补全了。