LanguageClient-neovimとgo-langserverの設定例を共有する。
背景
今までvim-goを使っていたのだけれど、Macbook Proを新調したら、何故か時々CPUをモリモリ使い切るようになってしまい、そのたびに手動でkillする羽目になった。
原因はよく分からず、何か代替をと考えていて、前から気になっていたgo-langserverを試してみることにした。
go-langserverというのはLanguageServerのGo対応の実装である。
LanguageServer
LanguageServerというのはLSP(LanguageServerProtocol)というプロトコルを喋るサーバーのことで、要するにヘッドレスのIDEである。バックエンド・サービスとしてIDEを動かすのでフロントエンドは好きなエディタを使ってねというもので、LSPさえ実装すれば、クライアント、サーバサイドともに好きなものを選べる。
構成
僕は普段Goの開発をしているのでサーバ側はgo-langserverを使う。
クライアント側は当然Neovimだ。
ただし、NeovimはそのままではLSPを喋れない1のでプラグインを入れる必要がある。
vim/Neovim向けにLSPクライアントの実装はいくつかある。
https://qiita.com/gorilla0513/items/783836797ff84a3a4753
僕は最初上の記事を参考にしてvim-lspを試してみたんだけれど、補完の設定がめんどくさそうだなと思ったのでLanguageClient-neovimを試してみることにした。
LanguageClient-neovim
LanguageClient-neovimを採用した理由は、標準でdeopleteをサポートしていたからだ。
僕は元々deopleteユーザだからこの恩恵は大きい。
あと、クライアント本体がrustで実装されているのも面白い。なんとなく速そうな印象だ。
ちなみに、neovimとついているがvimでも使えるらしい。
僕は試したことがないのでこの記事ではNeovimのみ対象とする。
さて、vim-lsp
など他のクライアントにも言えることだけど、LSPクライアントのプラグインは単に汎用のクライアントを実装しているだけなので、各言語用の設定とかキーバインディングとかは自分で設定しないといけない。
大したことはやらなくていいのだけれど、全部お任せで設定してくれるvim-goとかと比べるとちょっとめんどくさいので設定を共有する。
設定
~/.config/nvim/init.vim
僕のinit.vimから必要な設定を抜粋した。
if empty($XDG_DATA_HOME)
let $XDG_DATA_HOME = $HOME."/.local/share"
endif
call plug#begin($XDG_DATA_HOME.'/nvim/plugged')
Plug 'autozimu/LanguageClient-neovim', {
\ 'branch': 'next',
\ 'do': 'bash install.sh',
\ }
" (Optional) Multi-entry selection UI.
Plug 'junegunn/fzf'
Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }
Plug 'vim-syntastic/syntastic'
call plug#end()
set hidden
filetype plugin indent on
let g:LanguageClient_serverCommands = {'go': [$GOPATH.'/bin/go-langserver','-format-tool','gofmt','-lint-tool','golint']}
let g:deoplete#enable_at_startup = 1
プラグイン
call plug#end()
まではパッケージマネージャのPlug(vim-plug)の設定である。
今回、LanguageClient-neovimを最初に試したとき、READMEの設定をそのままコピったのでPlugを使うことになったんだけど、シンプルで使いやすいのが気に入ったので、本採用することにした。
他のパッケージマネージャを使っている人でも何やっているか大体分かると思う。
deinとかでも同じことが出来るだろう。
1個目のPlug 'autozimu/LanguageClient-neovim' ...
のオプションでは、branchをnext
にして、インストール時にインストールスクリプトを実行させている。これでrust製のビルド済みクライアントをインストールする。ちなみにオプションを書き換えれば、ソースからビルドするようにも出来る。
2個目のfzf
は実はよく分かっていない。ドキュメントの設定をそのままコピーしたけど、Optional
だと書いてあるので入れなくてもよいだろう。fzfというのはpeco
のような絞り込み検索ツールのことだから、おそらく何か複数候補から選択するような機能があって、そこで絞り込みをかけるのに使うのだと思う。
3個目のはdeoplete、有名な補完プラグインだ。入れるだけでLanguageClient-neovim
側で連携してくれるのが素晴らしい。
4個目のsyntasticはなんで入れているかは後で説明する。
その他の設定
set hidden
を入れているのは複数バッファをLanguageServerが同時に更新する場合があるからだそうだ。
filetype plugin indent on
は後でファイルタイプ別の設定を入れるので有効にしている。
let g:LanguageClient_serverCommands = ...
はLanguageServerのパスと引数を指定する。
今回の場合はgo-langserverが対象なのでそのパスと引数を入れた。引数の中身は僕の好みなので変えてもいい。
let g:deoplete#enable_at_startup = 1
はdeopleteをデフォルトで有効にするフラグだ。
syntastic
syntasticは自動でLintツールを実行してくれるプラグインだ。
今回フォーマッタとLintはLanguageServer側で実行するのではなく、直にツールを実行することにした。
なぜそうするかというと、LSPはどうも非同期で実行されることを前提に設計されているらしく、
ブロッキングして実行するのが難しいらしい。
参考: https://github.com/autozimu/LanguageClient-neovim/issues/260#issuecomment-359271432
裏でディレクトリ単位でフォーマットするなら非同期のほうが便利だが、開いているバッファにフォーマッタをかけるなら直で実行したほうが早いということだ。
それでフォーマッタは自前で用意することにした。
またgo-langserverはLintツールのオプションがあまり充実していないので、LintツールもLanguageServerではなく、別途実行することにする。
syntasticはこの手のツールの中で一番メジャーなので選択した。
init.vimに以下の設定も入れる。
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*
let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0
let g:syntastic_go_checkers = ['golint','govet']
一番下の行以外はsyntasticのREADMEの設定をそのままコピーした。
開いたときと、書き込み時に自動でLintが走るようになる。
一番下の行の配列にはsyntasticで走らせるLintツールを入れる。golint
とgovet
を選択した。
~/.config/nvim/ftplugin/go.vim
ファイルタイプ *.go
の設定も入れる。
キーバインディングと、
保存時にgofmt
を実行する設定を入れる。
syntasticは自動でLintを走らせてくれるのだけれどフォーマットはやってくれないので自前でスクリプトを書いた。
noremap <C-]> :call LanguageClient#textDocument_definition()<CR>
noremap <C-T> <C-O>
set noexpandtab
function! s:gofmt_on_save()
let l:curw = winsaveview()
silent execute "0,$! gofmt"
try | silent undojoin | catch | endtry
call winrestview(l:curw)
endfunction
augroup vim-gofmt-autosave
autocmd!
autocmd BufWritePre *.go call s:gofmt_on_save()
augroup END
noremap ...
はキーバインディングの設定で、基本的に定義ジャンプさえあれば僕の用途では事足りるのでそれだけ入れている。
LanguageClient#textDocument_definition
が定義ジャンプの関数だ。
<C-]>
、<C-T>
にマッピングしてあるのは僕がvim-go
でタグジャンプ式に慣れていたから踏襲しているだけで、基本的に好きなキーにマッピングしていいと思う。
s:gofmt_on_save
は現在のバッファに対してgofmt
を実行する関数で、
autocmdで、BufWritePre、つまり保存前に自動で走らせるようにしてある。
以上で簡単な設定は終わりだ。
感想
しばらく使ってみたけど、とにかくvim-go
と比べて軽い!、早い!というのが魅力だ。
一方で機能的にはそんなに沢山は用意されていない。
もしかしたらgo-langserverの方にはもっと多様な機能が用意されているのかもしれないけれど、
それらを呼び出すには結局自前でスクリプトを書かないといけない。
LSPクライアントは結局汎用的なクライアントでしか無いので、Goの開発環境として整えるためには、
ガワになる部分を色々作っていかなければいけないんだろうなと思った。
逆に、例えば定義ジャンプと補完さえ出来ればいいのであれば上のような設定だけで十分だし、僕の使い方では特にそれで問題ないと思っている。
この記事が参考になれば幸いである。
-
将来的にはLSPを標準でサポートする計画があるらしい ↩