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)endfunctionautocmd 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 一样愉快地进行补全了。