📝

vim + coc.nvim + (ccls) + cmake で C/C++ 補完を整える

2023/01/30に公開

背景

ale だといまいち...

vim ale で clangd で C++ コンパイルオプションがうまく渡らないっぽいメモ
https://qiita.com/syoyo/items/e90a83c37b3ea17e8f3d

Vimの非同期文法チェッカALEにプロジェクト固有の情報を教える
https://qiita.com/kaityo256/items/cb76c3f73753fe921e7b

coc.nvim + ccls がよさそうでしたので coc.nvim + ccls にします!

ccls はでもちょっと微妙かも... coc デフォの clangd で十分そうでした.

設定

NeoVimでC/C++を書くときはcoc.nvim + cclsが良さげ
https://endaaman.me/tips/nvim-coc-ccls

Vimにcoc.nvimを入れたら便利すぎて感動したっていう話
https://qiita.com/Amadeus_vn/items/5dfcf8d3676c2c5576ef

ありがとうございます.

yarn install なんちゃらが必要と言われたら,

:CocInstall coc#util#install()

とするといけます.

C++ lsp 関連はとりま clangd 入れます.

:CocInstall coc-clangd

ccls はおこのみで...

coc + clangd のデフォ? 設定だと, auto の型推論もだしてくれたりで良き良きでした 😊

coc.nvim 用の追加設定

lua でもいいのですが, lua にするほどでもないので, init.vim で設定します.

~/.config/nvim/init.vim(正確には $XDG_CONFIG_HOME/nvim/init.nvim)
https://wiki.archlinux.jp/index.php/XDG_Base_Directory

を編集します!

設定サンプルは

https://github.com/neoclide/coc.nvim

を参照ください.

処理ステータスを表示する

最初のころは処理ステータスが見えていたほうがきちんと動いているか確認できてよいと思います.

" Add (Neo)Vim's native statusline support
" NOTE: Please see `:h coc-status` for integrations with external plugins that
" provide custom statusline: lightline.vim, vim-airline
set statusline^=%{coc#status()}%{get(b:,'coc_current_function','')}

こんな感じで表示されるようになります.

tab 補間

" Use tab for trigger completion with characters ahead and navigate
" NOTE: There's always complete item selected by default, you may want to enable
" no select by `"suggest.noselect": true` in your configuration file
" NOTE: Use command ':verbose imap <tab>' to make sure tab is not mapped by
" other plugin before putting this into your config
inoremap <silent><expr> <TAB>
      \ coc#pum#visible() ? coc#pum#next(1) :
      \ CheckBackspace() ? "\<Tab>" :
      \ coc#refresh()
inoremap <expr><S-TAB> coc#pum#visible() ? coc#pum#prev(1) : "\<C-h>"

" Make <CR> to accept selected completion item or notify coc.nvim to format
" <C-g>u breaks current undo, please make your own choice
inoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#confirm()
                              \: "\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>"

function! CheckBackspace() abort
  let col = col('.') - 1
  return !col || getline('.')[col - 1]  =~# '\s'
endfunction

コメントにあるように, nvim 設定の最初のほうに書いておかないと他の tab 設定とかちあうようですので注意です! なるべく nvim 設定の最初のほうに書いておきましょう.

また, これだと候補がでたときに tab すると二番目の候補が選択されてしまします.

たとえば ParseUSD と入力して tab 押すと, 候補二番目の ascii:ParseUnr... で補完されてしまいます.
(第一候補は implicitly に選択されている)

vim ale などで, 入力中に tab 押したら最初のマッチで補完するのになれている方は, これもコメントにあるように, coc-config.json(or :CocConfig で開く) で suggest.noselect を設定しておきましょう(ちょっとわかりずらいパラメータ名ネ)

{
  suggest.noselect": true
}

また, Enter すると関数などの場合は関数の引数も fill してくれて便利です.

clang-format をかける

vim で clang-format 使っている方は :ClangFormat でフォーマットしているかと思います.

" Add `:Format` command to format current buffer
command! -nargs=0 Format :call CocActionAsync('format')

:Format で(言語非依存で)同等のことができます.

範囲指定してフォーマットは,

" Formatting selected code
xmap <leader>f  <Plug>(coc-format-selected)
nmap <leader>f  <Plug>(coc-format-selected)

のようにして, v で選択後, leader(デフォルトではバックスラッシュキー) + f でフォーマットできます!

インタラクティブに関数引数の hint

一応デフォルト設定でこんな感じで引数の hint 出してくれます.

ただ, プロジェクト(compile_commands.json かなにかの設定がよくに?)によってはうまく出してくれないときもあるようです.

関数のヒント(ドキュメント表示)

" Use K to show documentation in preview window
nnoremap <silent> K :call ShowDocumentation()<CR>

function! ShowDocumentation()
  if CocAction('hasProvider', 'hover')
    call CocActionAsync('doHover')
  else
    call feedkeys('K', 'in')
  endif
endfunction

これで関数にカーソルおいて K キーで hint が表示されます.

ただ, 著者環境では C++ プロジェクトによってはうまく hint が表示されないときや, 大きめのプロジェクトだと hint 取得に時間かかったりしました(5 秒くらい)

色を変える

Terminal のテーマカラーによっては見ずらいときがあります. higlight で調整しましょう.

https://stackoverflow.com/questions/68233531/how-to-change-coc-vim-pmenu-syntax

cmake

cmake の compile_commands.json では, コマンドは一行で -I などは分離されていません. ale の c.vim ではコマンド文字列を分離していろいろ処理してくれるようですが

https://github.com/dense-analysis/ale/blob/master/autoload/ale/c.vim

それでもうまくいかないときがあります.

cland だと cmake 出力の compile_commands.json でもある程度うまくいくようです.

ccls で compile_flags.txt と組み合わせである程度うまくやってくれるかもですが, それでもうまくいかなかったら

↑の参考 URL にあるように,
bear を使って compile_commands.json 生成するのがいいかもしれません.

C++ 設定

インデント

coc 自体はインデント処理はしません.

vim で C/C++ 系ファイルでインデントさせないようにする
https://qiita.com/syoyo/items/fd979913e879c2467a03

を参考に,

set indentexpr=
set noautoindent
set nocindent
set nosmartindent

au! BufNewFile,BufRead,BufEnter *.c setl ts=2 sw=2 expandtab nocindent noautoindent nosmartindent
au! BufNewFile,BufRead,BufEnter *.cc setl ts=2 sw=2 expandtab nocindent noautoindent nosmartindent
au! BufNewFile,BufRead,BufEnter *.cpp setl ts=2 sw=2 expandtab nocindent noautoindent nosmartindent
au! BufNewFile,BufRead,BufEnter *.h setl ts=2 sw=2 expandtab nocindent noautoindent nosmartindent
au! BufNewFile,BufRead,BufEnter *.hh setl ts=2 sw=2 expandtab nocindent noautoindent nosmartindent
au! BufNewFile,BufRead,BufEnter *.hpp setl ts=2 sw=2 expandtab nocindent noautoindent nosmartindent

あたりでインデント設定します.
↑は vim のを流用なので, nvim だともうちょっといい設定方法があるかもしれません.

make コマンド(ビルドコマンド)呼び出し

vim と同様でよいでしょうか. 著者は ctrl-m で makeprog を呼び出すようにしています.

map <C-m> :make<CR>

vim makeprog で ninja と make を自動で切り替えるメモ
https://qiita.com/syoyo/items/54a1e033ed565831f535

makeshift も nvim でも使えるかもです!

トラブルシューティング

補完がうまくいかない?...

筆者環境では, ccls 利用だと, デフォルト?では補完(tab)するといろいろな候補が出てしまいました(たとえば class Bora のインスタンス変数に対して補完したら Bora のメンバー関数だけなどが候補に出てほしい).

いい感じにするにはいろいろ設定が必要そうです.

とりあえずは ccls ではなくて coc + clangd で整えてみるのがいいかもしれません.

インデックスが生成されない...?

ccls で gl.h(OpenGL) など含んだプロジェクトだと, /usr/include のヘッダを全部インデックス化するからかうまくインデックスが生成されないように見えました. ccls 利用の場合, 一度オフラインで ccls 動作させてうまくいっているか確認してみるとよさそうです

Reference や Hint がおそい...

↑にあるように gl.h などでインクルードが多いプロジェクトだと, Reference の探索が遅い(5 秒くらいかかる)です. 依存関係減らせば探索早くなるのでしょうか...

何かしらキャッシュみたいな機能はあると思うのですが...

TODO

  • Ale, Visuial Studio intellisense のようにいい感じに補完する設定を探す
  • インデックスがきちんと生成されているか確認する方法を探す.

Discussion