RubyKaigi Takeout 2021 に参加して、typeprof などをフルに利用するには LSP を使えるようにしておく必要があるな、と気づきました。
思い立ったが吉日ということで、自分の vim 環境を ALE から LSP に乗り換えることにしました。
その過程で、mypy による lint を有効にするのに苦労したので、備忘録としてメモを残します。
前提
- python 使い
- ALE は lint 用に使っていて、formatter としては使っていない
- flake8, isort, mypy を linter として使っている
- vim の plug-in manager には dein を使っている
LSP を有効にする
本体である vim-lsp を使います。
細かい設定をやってくれる mattn/vim-lsp-settings も合わせて入れます。
# LSP (Language Server Protocol)
[[plugins]]
repo = 'prabirshrestha/vim-lsp'
# LSP settings
[[plugins]]
repo = 'mattn/vim-lsp-settings'
これで基本的な動きはオッケーです。
このタイミングで ALE をリストから削除しています。
python の linter を pycodestyle から flake8 に変更する
python 向けの設定をしていきます。
これまで flake8 を使って lint をしていたので、vim-lsp-settings のデフォルトである pycodestyle から flake8 を利用するように変更します。
g:lsp_settings
経由で設定をします。vim-lsp-settings はデフォルトで pylsp-all という設定(?)を使っているので、 pylsp-all.workspace_config
以下に設定を書き加えます。
- ロードするプラグインの切り替え
-
pylsp-all.workspace_config.plugins
でデフォルトのプラグインを無効化し、かわりに flake8 を有効化します。- mccabe, pycodestyle, pyflakes を
enabled: 0
に設定します。 - flake8 を
enabled: 1
に設定します。
- mccabe, pycodestyle, pyflakes を
-
- flake8 の設定ファイルを自動ロードする設定の追加
-
pylsp-all.workspace_config.pylsp.configurationSources
を['flake8']
と設定します。 - これにより flake8 の設定ファイル(setup.cfg, tox.ini など)を探索するようになります
-
# LSP settings
[[plugins]]
repo = 'mattn/vim-lsp-settings'
hook_add = '''
" Enable flake8
let g:lsp_settings = {
\ 'pylsp-all': {
\ 'workspace_config': {
\ 'pylsp': {
\ 'configurationSources': ['flake8'],
\ 'plugins': {
\ 'flake8': {
\ 'enabled': 1
\ },
\ 'mccabe': {
\ 'enabled': 0
\ },
\ 'pycodestyle': {
\ 'enabled': 0
\ },
\ 'pyflakes': {
\ 'enabled': 0
\ },
\ }
\ }
\ }
\ }
\}
'''
※ 僕は設定を一箇所にまとめたいので、プラグインの設定を hook_add
を使って plug-in のロードの隣に定義を書いていますが、 .vimrc に直接書いてもよいはずです。
flake8 を有効にする(enabled: 1
)だけでは pycodestyle や pyflakes による lint が行われるため、すべての linter で lint の設定をする必要があります。例えば、自分のプロジェクトでは max-line-length を長めに設定しているのですが、
- 設定により flake8 はエラーとならない
- 特に設定されていない pycodestyle, pyflakes は行長エラーを出す
という動きになっており、意図した lint 結果が得られませんでした。
重複しているツールが起動しているのは意図したものではないため、flake8 とかぶっている 3ツール(mccabe, pycodestyle, pyflakes8)は無効化することにしました。
余談: pyls (python-language-server) と pylsp
vim-lsp-settings の設定を調べていて混乱したのが pyls と pylsp の違いです。
なぜ似たような設定が2つあるのか、何が違うのか、というのがわからなくてググったりコード読んだりして、しばらくぐるぐるしていました。
どうやら、昨年末までは python-language-server がよく利用されていたようなのですが、メンテナンスが停止してしまったためにプロジェクトが fork されて、現在では python-lsp-server として再公開されているようです。
vim-lsp-settings では前者を pyls、後者を pylsp と呼んでいるようです。
なお、この他にも Microsoft が管理している microsoft/python-language-server というものもあるようですが、深追いしていないのでここでは割愛します。
余談: flake8 プラグインを書き換える
自前で flake8 プラグインを作成している場合は、pylsp 経由では意図通りに動かない可能性があります。
通常の実行では保存されているファイルを対象にしていたものが、stdin 経由での呼び出しに対応していない場合はクラッシュすることになります。
pylsp 経由で flake8 が動かない場合、以下のように stdin 経由で flake8 にファイルを与えてクラッシュしないか確認すると良いでしょう。
$ cat /path/to/file | flake8 -
mypy を有効にする
続いて mypy を有効にします。ここがハマりポイントでした。
ググって出てくる記事にも、python-lsp-server の README にも、mypy を有効にするには pyls-mypy を使えと書いてあるのですが、現時点(2021/9/11)時点ではこれは誤りです。
python-lsp-server で mypy を有効にするには pylsp-mypy をインストールしてください (一文字違い)。
$ /Users/tkomiya/.local/share/vim-lsp-settings/servers/pylsp-all/venv/bin/pip install pylsp-mypy
その後、 g:lsp_settings
の plug-in リストに pylsp_mypy を追加します。
hook_add = '''
" Enable flake8 and mypy
let g:lsp_settings = {
\ 'pylsp-all': {
\ 'workspace_config': {
\ 'pylsp': {
\ 'configurationSources': ['flake8'],
\ 'plugins': {
\ 'flake8': {
\ 'enabled': 1
\ },
\ 'mccabe': {
\ 'enabled': 0
\ },
\ 'pycodestyle': {
\ 'enabled': 0
\ },
\ 'pyflakes': {
\ 'enabled': 0
\ },
\ 'pylsp_mypy': {
\ 'enabled': 1
\ }
\ }
\ }
\ }
\ }
\}
'''
課題: pylsp-mypy のインストールを自動化する
今回は pylsp-mypy を手動でインストールしていますが、このアプローチでは環境を作り直すたびに手動でインストールする必要があります。
この部分を自動化できるとよいのですが、やり方が分かっていません。
そのため、 LspInstallServer
を実行するたびに pylsp-mypy が消えてしまうんですよね。
課題: isort を有効化する
isort を使ってソースコードを lint しようと思ったのですが、今の所実現できていません。
pyls-isort は linter としては動かない(フォーマッタ専用)ようなので、いまのところ有効になっていません。
flake8-isort を入れても意図したように動いていないので、もう少し試行錯誤が必要そうです。実際のところ、もう手癖になってしまったので isort なしでも困らないのですが、つまらないミスで CI でこけないようにするためにもチェックしておきたいところです。
LSP のエラー、警告を lightline に表示する
LSP が検知した文法エラーや警告などを lightline に表示します。
必要な定義は lightline-lsp としてパッケージングされているので、これを入れます。
# LSP-lightline bridge
[[plugins]]
repo = 'halkn/lightline-lsp'
あとは、この定義を lightline から参照するだけです。 lightline_lsp#warnings
, lightline_lsp#errors
などを参照すればオッケーです。
# Customize status line
[[plugins]]
repo = 'itchyny/lightline.vim'
hook_add = '''
let g:lightline = {
\ 'mode_map': {'c': 'NORMAL'},
\ 'active': {
\ 'right': [
\ [ 'lsp_errors', 'lsp_warnings' ],
\ ]
\ },
\ 'component_expand': {
\ 'lsp_warnings': 'lightline_lsp#warnings',
\ 'lsp_errors': 'lightline_lsp#errors',
\ },
\ 'component_type': {
\ 'lsp_warnings': 'warning',
\ 'lsp_errors': 'errors',
\ },
\}
'''
いままで lightline-ale を使っていたので、参照する関数を差し替えるだけだったのでかんたんでした。
vim-lsp の表示を変更する (ハイライト)
vim-lsp はデフォルトでは signcolumns を使って lint の警告/エラーを表示します。
僕はエラー行をハイライト表示するのに慣れてしまったので、この表示には違和感があります (ALE でも同様の設定をしていた)。
そのため、以下のような設定を加えました。
# LSP settings
[[plugins]]
repo = 'mattn/vim-lsp-settings'
hook_add = '''
" Hide signcolumn.
let g:lsp_diagnostics_signs_enabled = 0
'''
hook_post_source = '''
" Highlight LSP warnings strongly (like errors)
highlight link LspWarningHighlight Error
'''
g:gls_diagnostics_sign_enabled
で signcolumns を無効化しています。
そして LspWarningHighlight
の色を変えることでハイライト表示にしています。
ハイライトの色などはもう少しいい感じのものに調整したほうが良さそうですが、いまのところ Error
と同じにしています。
他にもいくつかハイライトが定義されているので、highlight コマンドで眺めつついい感じの色を設定すると良さそうです。
いまのところ LspWarningHighlight
だけアップデートすればよさそうでした。
エラーの詳細をステータス行に表示する
lint で警告/エラーが発生している行にカーソルを当てたらエラーの詳細を知りたいですよね。
g:lsp_diagnostics_echo_cursor
を有効にします。
# LSP settings
[[plugins]]
repo = 'mattn/vim-lsp-settings'
hook_add = '''
" Show diagnostics message to status line
let g:lsp_diagnostics_echo_cursor = 1
'''
オートコンプリートを有効にする
ついでにオートコンプリートを LSP 経由のものに置き換えます。
asyncomplete と asyncomplete-lsp を入れるだけで有効になります。
# Auto completion
[[plugins]]
repo = 'prabirshrestha/asyncomplete.vim'
# Auto completion for LSP
[[plugins]]
repo = 'prabirshrestha/asyncomplete-lsp.vim'
※ deoplete.nvim + deoplete-vim-lsp を使う方法もあるようですが、試していないため割愛します。
余談: pylsp にデバッグログを出力させる
flake8 が意図通り動いていないときに、pylsp の状態を知りたくなったため、デバッグログを出力させることにしました。
g:lsp_settings
経由で pylsp-all.args
に pylsp コマンドの引数を指定できます。
デバッグログを得るには --log-file
と -v
オプションを適量指定すると良さそうです (調査の際は 3つ指定してました)。
# LSP settings
[[plugins]]
repo = 'mattn/vim-lsp-settings'
hook_add = '''
let g:lsp_settings = {
\ 'pylsp-all': {
\ 'args': ['--log-file=pylsp.log', '-vvv']
\ }
\}
まとめ
最終的には以下の設定になりました。
# LSP (Language Server Protocol)
[[plugins]]
repo = 'prabirshrestha/vim-lsp'
# LSP settings
[[plugins]]
repo = 'mattn/vim-lsp-settings'
hook_add = '''
" Hide signcolumn.
let g:lsp_diagnostics_signs_enabled = 0
" Show diagnostics message to status line
let g:lsp_diagnostics_echo_cursor = 1
" Enable flake8 and mypy
let g:lsp_settings = {
\ 'pylsp-all': {
\ 'workspace_config': {
\ 'pylsp': {
\ 'configurationSources': ['flake8'],
\ 'plugins': {
\ 'pylsp_mypy': {
\ 'enabled': 1
\ }
\ }
\ }
\ }
\ }
\}
'''
hook_post_source = '''
" Highlight LSP warnings strongly (like errors)
highlight link LspWarningHighlight Error
'''
# LSP-lightline bridge
[[plugins]]
repo = 'halkn/lightline-lsp'
# Auto completion
[[plugins]]
repo = 'prabirshrestha/asyncomplete.vim'
# Auto completion for LSP
[[plugins]]
repo = 'prabirshrestha/asyncomplete-lsp.vim'
# Customize status line
[[plugins]]
repo = 'itchyny/lightline.vim'
hook_add = '''
let g:lightline = {
\ 'mode_map': {'c': 'NORMAL'},
\ 'active': {
\ 'right': [
\ [ 'lsp_errors', 'lsp_warnings' ],
\ ]
\ },
\ 'component_expand': {
\ 'lsp_warnings': 'lightline_lsp#warnings',
\ 'lsp_errors': 'lightline_lsp#errors',
\ },
\ 'component_type': {
\ 'lsp_warnings': 'warning',
\ 'lsp_errors': 'errors',
\ },
\}
'''