VimにおけるLSPやデバッガの使い方

この記事はLayerX Tech Advent Calendar 2022 の16日目の記事です。LayerXのバクラク事業部で機械学習エンジニアをしている @yoppiblog です。 現在は、OCRチームで帳票を読み取る機械学習モデルの開発をしています。 今回は、主にOCRチームで使われている、バックエンド側のプログラミング言語における、Vimでの開発を効率化する

  • Language Server Protocol
  • デバッガ

周りの話を簡単に紹介できればと思います。

Language Server Protocol

LSP が策定されてからというものの、Vimでもその恩恵に預かり定義元へのジャンプやコード補完など、IDEと遜色ない機能を使えるようになりました。 VimでLSPを喋る場合、 vim-lsp がありますが、今回は、 coc-nvimについて紹介します。

coc.nvim

cov.nvimvim-lsp と同様にLSPをサポートするプラグインです。 Vim Scriptではなく、Node.jsで実装されており、vim-lspより高速だと感じており個人的に一番のメリットだと思います。また、jsonファイルでプロジェクト毎に柔軟にカスタマイズ可能な点も使いやすいです。 加えて、オムニ補完をとくに設定を細かくすることもなく、いい感じに補完してくれるのも嬉しい点です。 .nvim とsuffixがついていますが、Vimでも利用できます(nvim特有の機能に依存する機能は使えないですが...)。

環境構築

インストールは各々使っているプラグインマネージャで完結します。

" using vim-plug
Plug 'neoclide/coc.nvim', {'branch': 'release'}

また、各言語のLanguage Serverはこちらを参考にインストールします。 https://github.com/neoclide/coc.nvim/wiki/Language-servers Pythonでは coc-jedi(裏ではjediが動く) を、Goだと coc-go(裏ではgoplsが動く) が使いやすいプラグインになります。

:CocInstall coc-jedi coc-go

環境によってはうまく jedi-language-server が動かない場合もあるため、その場合は手動でインストールして使うようにすると良いでしょう。

:CocConfig  " coc.nvimの設定ファイルを開く
{
  "jedi": {
    "enable": true,
    "startupMessage": false,
    "executable.command": "~/.pyenv/shims/jedi-language-server"
  }
}

使い方

READMEに書いてある通り.vimrc に記述すればすぐに使えるようになりますが、重要なポイントはKey mappingです。 プログラミング中、常にコード定義を見に行ったり、renameしたり、参照元を探したり、、、という作業を頻繁にするため自分の使いやすいKey mappingを定義すると良いでしょう。

" e.g. Key mappings
nmap <silent> <C-]> <Plug>(coc-definition)
nmap <silent> <C-[> <C-o>
nmap <silent> gy <Plug>(coc-type-definition)
nmap <silent> gi <Plug>(coc-implementation)
nmap <silent> gr <Plug>(coc-references)
nmap <silent> gn <Plug>(coc-rename)
inoremap <silent><expr> <C-m> coc#pum#visible() ? coc#_select_confirm() : "\<C-m>"

デバッガ

ソフトウェアを開発する上で一番必要になるのは、個人的にデバッガだと思っています。 単純なprintデバッグで解決するシーンもあると思いますが、深く原因究明するためにはデバッガで状態を確認することで早期に解決できると考えています。

Pythonにおけるデバッガ

Pythonでは、 IPython が提供している機能を使うと一番楽です。 標準添付ライブラリに pdb があるのですが、より直感的で使いやすいものがIPythonになります。

ブレークポイントを設定したい場所に、

from datetime import datetime
from dateutil import parser

from IPython import embed


def parse(arg: str) -> datetime:
    dt = parser.parse(arg)
    embed()
    return dt

parse("2022-12-16")

embed 関数を埋め込み、通常実行するだけで任意の場所でデバッグできるようになります。

# IPythonのreplが動く
In [1]: dt
Out[1]: datetime.datetime(2022, 12, 16, 0, 0)

Goにおけるデバッガ

Goでのデバッガは delve がよく使われています。 他のIDEでのデバッガもdelveを元に実装されています。 Vimから使うためには vim-delve が使いやすくておすすめです。

delveのインストール後、

$ go install github.com/go-delve/delve/cmd/dlv@latest

vim-delveをインストールします。

" using vim-plug
Plug 'sebdah/vim-delve'

よく使うコマンドとして、

があります。

下記の例は、10行目に DlvAddBreakpointブレークポイント設定したのち、 DlvDebug で実行した場面です。 デバッガが起動し、変数の確認など可能になります。

> main.main() ./time.go:10 (hits goroutine(1):1 total:1) (PC: 0x1028bf9b0)
     5:         "time"
     6: )
     7:
     8: func main() {
     9:         date, err := time.Parse("2006-01-02", "2022-12-16")
=>  10:         if err != nil {
    11:                 panic(err)
    12:         }
    13:         fmt.Printf("date:%v\n", date)
    14: }
(dlv)

終わりに

Vimでの開発環境を少し紹介しました。IDEのほうが機能が盛りだくさんで使いやすいシーン(特にデバッグ周り)は多いと思いますが、 必要な機能を自分で取捨選択してカスタマイズできるエディタは、それはそれで使いやすいと感じています。