coc.nvimを使ってvimをもっと強くする


先日neovimがバージョン0.4にアップデートされました。
直接的な原因は分かりませんが、今までdeoplete+LanguageClient-Neovimの組み合わせによる補完が効かなくなったため
試しにcoc.nvimを使ってみたところちゃんと動いた上に結構使い勝手が良さげだったので各種設定などをまとめておきます。
名前にnvimとついてはいますが、vimでも動くようです。

ちなみにdeoplete+LanguageClient-Neovimは問題なく動くようになっていました。

coc.nvimの良いところを挙げておくと

  • 補完用のフレームワークとLSPクライアントを別々にインストールする必要がないこと
  • language serverが拡張機能としてcoc.nvimの機能でインストール可能なこと
  • 予め用意されていない言語についても別途設定すれば使えること
  • coc.nvimの設定ファイルはjsonで記述できること

などがあります。

インストール

coc.nvimはtypescriptで書かれておりnode.jsを使用します。予めnode.jsを利用できるようにしておいてください。
インストールの際にはreleaseブランチが推奨されています。

deinを使用する場合

call dein#add(’neoclide/coc.nvim’, {‘merged’:0, ‘rev’: ‘release’})

tomlファイルを使うなら

[[plugins]]
repo = 'neoclide/coc.nvim'
rev = 'release'
on_i = 1
merged = '0'
hook_source = 'call coil398#init#coc#hook_source()'

vim plugであれば

Plug ’neoclide/coc.nvim’, {‘branch’: ‘release’}

など

他のインストール方法はこちら

デフォルトでもバッファ内の単語の補完やファイルパスの補完はできますが
様々な言語の補完を有効にするためには別途CocInstallコマンドを使ってlanguage serverの拡張機能をインストールする必要があります。
coc.nvimはそれ単体でLSPに対応した補完エンジンとなるため、他に比べると導入が楽です。

拡張機能のインストール

vim内コマンドラインモードで
:CocInstallコマンドを使用することで拡張機能がインストールできます。
例えばpythonであれば
:CocInstall coc-python
と打つことでpythonの拡張機能がインストールされます。
また、coc.nvimと各種拡張機能の設定は後述するようにjsonで記述可能です。

cocで使用できる拡張機能は全てcocから始まる名前となっています。
一覧はこちら
依存するモジュール(Language server本体など)が存在する場合は別途手動でインストールする必要があります。各ページにマニュアルがあるのでそちらを参照してください。

拡張機能についてはLanguage serverだけでなく、他の機能を提供している場合もあり
コマンドとして提供されているものについては:CocList commandsを実行することで一覧を表示することができます。

coc用拡張機能にはvscodeの拡張機能からforkしたものもあり、例えばvs codeのvue用プラグインであるveturもcoc-veturという名前で拡張機能が存在します。(要vue-language-server)

アップデートについては毎日初回起動時にチェックされるようです。

設定の記述

:CocConfigコマンドを打つことで設定ファイルが開かれます。
jsonファイルになっているので予めcoc-jsonをインストールしておくのをお勧めします。

設定ファイルであるcoc-settings.jsonは
neovimの場合は\$XDG_CONFIG_HOME/nvimもしくは\$HOME/.config/nvim内
vimの場合は\$HOME/.vim内にそれぞれ格納されます。
設定ファイルのスキーマ一覧はこちら
このjsonファイルで変更できるのは上記スキーマの”properties”プロパティ以下と拡張機能の設定だけのようです。
coc-jsonがそれらが補完されるようになります。

また、cocの拡張機能として提供されていないlanguage serverもこのファイルに設定することで使用可能になります。

ほぼ公式のものですが、以下が設定例です。

coc-settings.json
{
    "suggest.noselect": true,
    "suggest.preferCompleteThanJumpPlaceholder": true,
    "languageserver": {
        "clangd": {
            "command": "clangd",
            "args": ["--background-index"],
            "rootPatterns": ["compile_flags.txt", "compile_commands.json", ".vim/", ".git/", ".hg/"],
            "filetypes": ["c", "cpp", "objc", "objcpp"]
        },
        "dockerfile": {
            "command": "docker-langserver",
            "filetypes": ["dockerfile"],
            "args": ["--stdio"]
        },
        "haskell": {
            "command": "hie-wrapper",
            "rootPatterns": [".stack.yaml", "cabal.config", "package.yaml"],
            "filetypes": ["hs", "lhs", "haskell"],
            "initializationOptions": {
                "languageServerHaskell": {
                    "hlintOn": true
                }
            }
        }
    },
    "python.jediEnabled": false
}

拡張機能に存在しないlanguage serverはlanguageserverプロパティに記述することで使用可能になります。
各種language-serverの設定例一覧はこちら
基本的には拡張機能が存在すればそちらをインストールすることで楽に環境構築が可能です。

最後の”python.jediEnabled”: falseはcoc-pythonの設定です。
こうすることでpythonの補完がjediではなくmicrosoftのpython-language-server(MLPS)で行われるようになります。
coc-pythonの他の設定についてはこちらを参照してください。

coc.nvim用のvim設定例

公式のvim設定例が以下のものになります。基本的にはこれをベースにするのが良いと思います。

" if hidden is not set, TextEdit might fail.
set hidden

" Some servers have issues with backup files, see #649
set nobackup
set nowritebackup

" Better display for messages
set cmdheight=2

" You will have bad experience for diagnostic messages when it's default 4000.
set updatetime=300

" don't give |ins-completion-menu| messages.
set shortmess+=c

" always show signcolumns
set signcolumn=yes

" Use tab for trigger completion with characters ahead and navigate.
" Use command ':verbose imap <tab>' to make sure tab is not mapped by other plugin.
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

" Use <c-space> to trigger completion.
inoremap <silent><expr> <c-space> coc#refresh()

" Use <cr> to confirm completion, `<C-g>u` means break undo chain at current position.
" Coc only does snippet and additional edit on confirm.
inoremap <expr> <cr> pumvisible() ? "\<C-y>" : "\<C-g>u\<CR>"
" Or use `complete_info` if your vim support it, like:
" inoremap <expr> <cr> complete_info()["selected"] != "-1" ? "\<C-y>" : "\<C-g>u\<CR>"

" Use `[g` and `]g` to navigate diagnostics
nmap <silent> [g <Plug>(coc-diagnostic-prev)
nmap <silent> ]g <Plug>(coc-diagnostic-next)

" Remap keys for gotos
nmap <silent> gd <Plug>(coc-definition)
nmap <silent> gy <Plug>(coc-type-definition)
nmap <silent> gi <Plug>(coc-implementation)
nmap <silent> gr <Plug>(coc-references)

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

function! s:show_documentation()
  if (index(['vim','help'], &filetype) >= 0)
    execute 'h '.expand('<cword>')
  else
    call CocAction('doHover')
  endif
endfunction

" Highlight symbol under cursor on CursorHold
autocmd CursorHold * silent call CocActionAsync('highlight')

" Remap for rename current word
nmap <leader>rn <Plug>(coc-rename)

" Remap for format selected region
xmap <leader>f  <Plug>(coc-format-selected)
nmap <leader>f  <Plug>(coc-format-selected)

augroup mygroup
  autocmd!
  " Setup formatexpr specified filetype(s).
  autocmd FileType typescript,json setl formatexpr=CocAction('formatSelected')
  " Update signature help on jump placeholder
  autocmd User CocJumpPlaceholder call CocActionAsync('showSignatureHelp')
augroup end

" Remap for do codeAction of selected region, ex: `<leader>aap` for current paragraph
xmap <leader>a  <Plug>(coc-codeaction-selected)
nmap <leader>a  <Plug>(coc-codeaction-selected)

" Remap for do codeAction of current line
nmap <leader>ac  <Plug>(coc-codeaction)
" Fix autofix problem of current line
nmap <leader>qf  <Plug>(coc-fix-current)

" Create mappings for function text object, requires document symbols feature of
languageserver.

xmap if <Plug>(coc-funcobj-i)
xmap af <Plug>(coc-funcobj-a)
omap if <Plug>(coc-funcobj-i)
omap af <Plug>(coc-funcobj-a)

" Use <C-d> for select selections ranges, needs server support, like: coc-tsserver, coc-python
nmap <silent> <C-d> <Plug>(coc-range-select)
xmap <silent> <C-d> <Plug>(coc-range-select)

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

" Use `:Fold` to fold current buffer
command! -nargs=? Fold :call     CocAction('fold', <f-args>)

" use `:OR` for organize import of current buffer
command! -nargs=0 OR   :call     CocAction('runCommand', 'editor.action.organizeImport')

" Add status line support, for integration with other plugin, checkout `:h coc-status`
set statusline^=%{coc#status()}%{get(b:,'coc_current_function','')}

" Using CocList
" Show all diagnostics
nnoremap <silent> <space>a  :<C-u>CocList diagnostics<cr>
" Manage extensions
nnoremap <silent> <space>e  :<C-u>CocList extensions<cr>
" Show commands
nnoremap <silent> <space>c  :<C-u>CocList commands<cr>
" Find symbol of current document
nnoremap <silent> <space>o  :<C-u>CocList outline<cr>
" Search workspace symbols
nnoremap <silent> <space>s  :<C-u>CocList -I symbols<cr>
" Do default action for next item.
nnoremap <silent> <space>j  :<C-u>CocNext<CR>
" Do default action for previous item.
nnoremap <silent> <space>k  :<C-u>CocPrev<CR>
" Resume latest coc list
nnoremap <silent> <space>p  :<C-u>CocListResume<CR>

自分の場合は

set cmdheight=2
set statusline^=%{coc#status()}%{get(b:,'coc_current_function','')}

の2行を削除して、更に競合しているマッピングを変更しています。

また、floating window用に以下の設定を追加するのをお勧めします。

set pumblend=10

これは補完用のfloating windowの透明度を設定するものです。値は色々弄って良い感じにしてみてください。

定義参照ジャンプ

若干余談ですが、LSPでは補完だけでなく他の機能についても提供されています。
その中に定義へのジャンプと参照へのジャンプがあります。
vimでは元々ctagsやgtagsを使ってこれらの機能が実現できました。参考
とはいえctagsでは同じ名前の関数が複数あると一発で正しい関数に飛べず、gtagsもインストールの面倒さがありました(主観ですが...)。
今現在であればどのLSPでもおおよそこれらの機能は実装されています。
対応状況はこちらのページで確認可能です。
上記のvim設定そのままであればgdとgrでそれぞれ定義と参照へのジャンプが可能になっていると思います。
速度も早くライブラリの関数へも一瞬で飛べるので非常に便利です。

CocList

:CocListを実行すると以下のような画面が現れます。

カーソルキーでカーソルを動かすもしくはFUZZYサーチしてEnterを押すことでカーソル下のソースについての一覧が表示されます。
また、:CocList commandsのように実行することで直接commands一覧が表示できます。
各種ソースで表示される要素は

ソース 要素
location ジャンプした場所の一覧
outline 現在開いているファイル内の変数や関数などのシンボルの一覧
sources 補完対象の一覧
commands 拡張機能を含めた実行可能なコマンドの一覧
symbols ワークスペース(ディレクトリ以下?)内の全てのファイルにおけるシンボルの一覧
diagnostics ワークスペース内における診断結果の一覧(恐らく拡張機能によって、言語に対応するlinterなどが有効になっているとその結果が表示される)
services 自分で追加したlanguage serverや拡張機能によって追加されたlinterの一覧?
extensions CocInstallで追加された拡張機能の一覧。アンインストールや無効化なども可能
links 不明
output 不明

更に拡張機能であるcoc-listsをインストールすることでファイルやバッファなどのソースが追加され、deniteのような使用感になります。

最後に

補完用のLSPクライアントとして軽い気持ちでインストールしたら思いの外機能が多く、網羅するのが大変そうですね…
とりあえず現状の調査結果は以上です。