LanguageClient-neovimとgo-langserverでGoの開発を快適にする

LanguageClient-neovimとgo-langserverの設定例を共有する。

## 背景

今までvim-goを使っていたのだけれど、Macbook Proを新調したら、何故か時々CPUをモリモリ使い切るようになってしまい、そのたびに手動でkillする羽目になった。

原因はよく分からず、何か代替をと考えていて、前から気になっていた[go-langserver](https://github.com/sourcegraph/go-langserver)を試してみることにした。

go-langserverというのはLanguageServerのGo対応の実装である。

## LanguageServer

LanguageServerというのはLSP(LanguageServerProtocol)というプロトコルを喋るサーバーのことで、要するにヘッドレスのIDEである。バックエンド・サービスとしてIDEを動かすのでフロントエンドは好きなエディタを使ってねというもので、LSPさえ実装すれば、クライアント、サーバサイドともに好きなものを選べる。

## 構成

僕は普段Goの開発をしているのでサーバ側は[go-langserver](https://github.com/sourcegraph/go-langserver)を使う。

クライアント側は当然[Neovim](https://neovim.io/)だ。
ただし、NeovimはそのままではLSPを喋れない[^1]のでプラグインを入れる必要がある。
vim/Neovim向けにLSPクライアントの実装はいくつかある。

https://qiita.com/gorilla0513/items/783836797ff84a3a4753
僕は最初上の記事を参考にして[vim-lsp](https://github.com/prabirshrestha/vim-lsp)を試してみたんだけれど、補完の設定がめんどくさそうだなと思ったので[LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim)を試してみることにした。

## LanguageClient-neovim

[LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim)を採用した理由は、標準で[deoplete](https://github.com/Shougo/deoplete.nvim)をサポートしていたからだ。
僕は元々deopleteユーザだからこの恩恵は大きい。

あと、クライアント本体がrustで実装されているのも面白い。なんとなく速そうな印象だ。

ちなみに、neovimとついているがvimでも使えるらしい。
僕は試したことがないのでこの記事ではNeovimのみ対象とする。

さて、`vim-lsp`など他のクライアントにも言えることだけど、LSPクライアントのプラグインは単に汎用のクライアントを実装しているだけなので、各言語用の設定とかキーバインディングとかは自分で設定しないといけない。
大したことはやらなくていいのだけれど、全部お任せで設定してくれるvim-goとかと比べるとちょっとめんどくさいので設定を共有する。

## 設定

### ~/.config/nvim/init.vim
僕のinit.vimから必要な設定を抜粋した。

```vimscript
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](github.com/junegunn/vim-plug))の設定である。
今回、[LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim/)を最初に試したとき、READMEの設定をそのままコピったのでPlugを使うことになったんだけど、シンプルで使いやすいのが気に入ったので、本採用することにした。

他のパッケージマネージャを使っている人でも何やっているか大体分かると思う。
deinとかでも同じことが出来るだろう。

1個目の`Plug 'autozimu/LanguageClient-neovim' ...`のオプションでは、branchを`next`にして、インストール時にインストールスクリプトを実行させている。これでrust製のビルド済みクライアントをインストールする。ちなみにオプションを書き換えれば、ソースからビルドするようにも出来る。

2個目の`fzf`は実はよく分かっていない。ドキュメントの設定をそのままコピーしたけど、`Optional`だと書いてあるので入れなくてもよいだろう。fzfというのは`peco`のような絞り込み検索ツールのことだから、おそらく何か複数候補から選択するような機能があって、そこで絞り込みをかけるのに使うのだと思う。

3個目のは[deoplete](https://github.com/Shougo/deoplete.nvim)、有名な補完プラグインだ。入れるだけで`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](https://github.com/vim-syntastic/syntastic)は自動でLintツールを実行してくれるプラグインだ。

今回フォーマッタとLintはLanguageServer側で実行するのではなく、直にツールを実行することにした。

なぜそうするかというと、LSPはどうも非同期で実行されることを前提に設計されているらしく、
ブロッキングして実行するのが難しいらしい。

参考: https://github.com/autozimu/LanguageClient-neovim/issues/260#issuecomment-359271432

裏でディレクトリ単位でフォーマットするなら非同期のほうが便利だが、開いているバッファにフォーマッタをかけるなら直で実行したほうが早いということだ。

それでフォーマッタは自前で用意することにした。

またgo-langserverはLintツールのオプションがあまり充実していないので、LintツールもLanguageServerではなく、別途実行することにする。

syntasticはこの手のツールの中で一番メジャーなので選択した。

init.vimに以下の設定も入れる。

```vimscript
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を走らせてくれるのだけれどフォーマットはやってくれないので自前でスクリプトを書いた。


```vimscript
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の開発環境として整えるためには、
ガワになる部分を色々作っていかなければいけないんだろうなと思った。

逆に、例えば定義ジャンプと補完さえ出来ればいいのであれば上のような設定だけで十分だし、僕の使い方では特にそれで問題ないと思っている。

この記事が参考になれば幸いである。

[^1]: 将来的にはLSPを標準でサポートする計画があるらしい