LoginSignup
20
9

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-27

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ツールを入れる。golintgovetを選択した。

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

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

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


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

20
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
9