Input中心のブログ

2020年度現在のVimでのGoの開発環境

October 22, 2020

僕はVimとGolangが大好きなのですが、Vim好きの友達によく何のプラグインを使っているの?と相談されます。 しかし、いつもプラグインの列挙をするばかりであまり詳しく解説できてないので、とりあえず記事に書いておき、聞かれたときに

「この記事をどうぞ(シュッ」

って感じになるように書くことにしました。

よもやま話

以前ならvim-goを使ってるよ。で終わっていたと思います。 vim-goはGoをVimで書くための様々な機能がてんこ盛りのこれさえあればすべて解決するプラグインでした。実際、vim-goを使っていた人はとても多いです。(2020/10/17現在でStar 12.7k)しかし、LSPの登場によってvim-goの何でも入りプラグインが逆に重荷になってきました。LSPはコード補完や定義ジャンプなどの機能を提供するプロトコルです。これによって、LSPとvim-goの機能がかなりバッティングしてしまうのです。他の言語を書くためにLSPを使っている人はGoを書く時だけ、vim-goに入れ替えるみたいなことやこの機能はLSPでやって他の機能はvim-goで行うなどちょっとめんどくさい設定が必要でした。(しかも、何を使っているかを結構把握しないと設定が厳しい。) また、vim-go自身もGoのLSPサーバであるgoplsを使うようになり、LSPクライアントとvim-goを両方使うのがますます苦しくなってきました。(設定で使わないように出来ますがめんどくさいし、一時期設定するとエラーが起きる問題もあった) 全体的にLSPを使う流れになっている今vim-goは重たすぎるのではないかという問題があり、僕の中ではどのようにvim-goを卒業するかという考えで現在の環境にたどり着きました。僕はmattnさんのこの記事でLSPの存在を知り、LSPへの乗り換えを開始しました。

最近のコードを書く上での設定はLSP + αのような設定が基本になっているんだろうなあと思います。(このαというのはLSPにない言語特有の機能だったり) なので、今回はLSPの導入と+αの設定について書いていこうと思います。

LSP

LSPはMicroSoftが作成しているエディタや言語にかかわらずIDEのような機能を作成するためのプロトコルになります。 正直このLSPの機能があれば環境が9割くらい完成しているといっても過言ではありません。 僕はvim-lspを使っているので、vim-lspでの環境設定について書きますが、他にもcoc.nvimvim-lscなど様々なLSPクライアントプラグインがあります。

.vimrc
Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings'

vim-lsp-settingsはLanguage Serverのインストールからvim-lspの設定までほとんどを行ってくれる便利なプラグインです。上の2つのプラグインをインストールした状態でGoのファイルを開いて、:LspInstallServerを実行します。すると、goplsというGo公式のLanguage Serverのインストールをしてくれます。

これで、自動補完・定義ジャンプ・変数名等の変更など様々なことが出来ます。もし、何が出来るかを知りたいという人がいれば、僕が以前書いた記事ですがvim-lspで出来ることという記事があります。基本的には:LspDefinitionなどのコマンドを打てば使用することが出来ますが、一回一回打つのは面倒なので以下の様に使うものに対して、マッピングをすることをお勧めします。(vim-lspのREADMEから拝借)

.vimrc
function! s:on_lsp_buffer_enabled() abort
    setlocal omnifunc=lsp#complete
    setlocal signcolumn=yes
    if exists('+tagfunc') | setlocal tagfunc=lsp#tagfunc | endif
    nmap <buffer> gd <plug>(lsp-definition)
    nmap <buffer> gr <plug>(lsp-references)
    nmap <buffer> gi <plug>(lsp-implementation)
    nmap <buffer> gt <plug>(lsp-type-definition)
    nmap <buffer> <leader>rn <plug>(lsp-rename)
    nmap <buffer> [g <Plug>(lsp-previous-diagnostic)
    nmap <buffer> ]g <Plug>(lsp-next-diagnostic)
    nmap <buffer> K <plug>(lsp-hover)
endfunction

augroup lsp_install
    au!
    autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END

自動補完

上の設定では補完はオムニ補完が出来るので<C-X><C-O>で補完することが出来ます。もし、オムニ補完をするのが面倒なら自動補完が便利です。

.vimrc
Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim'

もし、deopleteなど他の自動補完プラグインを使うならそれに対応したプラグインを入れれば自動補完をすることも出来ます。

.vimrc
Plug 'Shougo/deoplete.nvim'
Plug 'lighttiger2505/deoplete-vim-lsp'

スニペット機能

スニペットとは文章を定型文として挿入して、変更したい所だけ変更するのを助ける機能になります。

LSPでは補完時にスニペットとして送信するプロトコルがあります。goplsはスニペットの機能がありますが、vim-lsp単体では補完する機能がありません。 そのためには別のプラグインを入れる必要があります。

.vimrc
Plug 'hrsh7th/vim-vsnip'
Plug 'hrsh7th/vim-vsnip-integ'

vim-vsnipはスニペットと呼ばれる定型のコードを生成して、変えたい所だけをジャンプしながら変更していくプラグインになります。

次に、vim-vsnip-integは、vim-vsnipとvim-lspを連携するプラグインになっています。 これを入れることによって、以下のようにgoplsから補完候補がスニペット形式で送られてきて、候補を選択するとスニペット形式で展開されます。

上のgifのようにfunctionの引数が返ってくるようにするにはvimrcに以下のようなコードを書く必要があります。

.vimrc
let g:lsp_settings = {
  \   'gopls': {
  \     'initialization_options': {
  \       'usePlaceholders': v:true,
  \     },
  \   },
  \ }

他のスニペットプラグインであるneosnippet.vimなどで使いたい場合は以下のようにプラグインを入れると同じように動かすことが出来ます。もし、すでにスニペットを使っている人がいるなら慣れているプラグインで連携してみるのがいいでしょう。

.vimrc
Plug 'Shougo/neosnippet.vim'
Plug 'thomasfaingnaert/vim-lsp-snippets'
Plug 'thomasfaingnaert/vim-lsp-neosnippet'

vim-goimports

Goにはgoimportsという超便利機能があります。具体的には以下のようなことをしてくれます。

  • importしていないライブラリを自動で挿入
  • 使っていないライブラリを削除する
  • コード全体の整形

これをvimの保存時に自動で行ってくれるのがvim-goimportsです。 このプラグインを使うには、goimports自体もインストールする必要があります。

$ go get golang.org/x/tools/cmd/goimports
.vimrc
Plug 'mattn/vim-goimports'

let g:goimports_simplify = 1  " 保存時に`gofmt -s`を実行する

実は、、この機能も最近LSPだけで近いことができます。以下のように書くことで保存時に、ライブラリの保存とコード全体の整形を行ってくれます。ただ、vim-goimportsではgofmt -sなど他の整形コマンドも実行出来たりするので便利で使っています。あまりプラグイン入れたくないという人は下のLSPでのコード整形を使うといいのかもしれないです。goplsのみで完結できる方法があればまた乗り換えるかもしれません。

.vimrc
autocmd BufWritePre *.go call execute(['LspCodeActionSync source.organizeImports', 'LspDocumentFormatSync'])

おまけ

ここからは必須ではないけど、あると便利なプラグインを紹介していきます。

vim-goaddtags

インストール方法 依存ツールのインストール
$ go get github.com/fatih/gomodifytags

プラグインのインストール

Plug 'mattn/vim-goaddtags'

これはGoのstructにタグをつけてくれるプラグインです。

type Foo struct {
    Name  string
    Value int
}

このようなstructがあったときに、:GoAddTags json dbを実行すると

type Foo struct {
    Name string `json:"name" db:"name"`
    Age  int    `json:"age" db:"age"`
}

のようにタグをつけてくれます。

vim-goimpl

インストール方法 依存ツールのインストール
$ go get github.com/josharian/impl

プラグインのインストール

Plug 'mattn/vim-goimpl'

これはGoのstructがあるinterfaceを満たすようにfunctionを付け加えたりしてくれるものです。

何もない状態で、:GoImpl io.Reader testを実行すると以下のようにio.Readerインタフェースを満たすtestという名前の構造体を自動で生成してくれます。 他にも使い方がいくつかあるので、詳しくはREADMEを見てください。

type test struct {
}

func (t *test) Read(p []byte) (n int, err error) {
    panic("not implemented") // TODO: Implement
}

最後に

mattnさんへの依存度たけーな。