Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Pythonのパス解決問題を解消し、pyls(Python Language Server)を導入した on NeoVim

More than 1 year has passed since last update.

この記事はiRidge Advent Calendar 2018 の10日目です。Vimのほうのアドベントカレンダーではない。

tl;dr

みなさん、VimでPythonツールを使っていますか?

私は現在勤めているiRidgeにおいて、
主要開発言語であるPythonで構築されたシステムを開発しています。
その中で開発環境の整備に付随するパスの問題に悩まされてきました。

今回はそのパス問題を解決して開発環境の構築がようやく満足できるレベルとなり、
自分の中で一応の決着を迎えたため、この機会のその内容を共有したいと思います。

同じような悩みを抱えている方の一助となれば幸いです。

前提条件

Python開発環境を整えるツール群

Pythonの開発環境を整える上で私が用いたツールは以下の通りです。

Pythonのコレ系のツールは非常に多くあり、Pythonを使う立場によって
必要なツールや不必要なツールは異なります。初学者からすれば混乱の種になってしまっていますね。
このような事情を憂いている方は他にもいらっしゃるようで、参考記事として挙げさせていただきます。

Pythonの仮想環境構築(2017年版) pyenvとpyenv-virtualenvとvirtualenvとvirtualenvwrapperとpyvenvとvenv

私の立場はPythonを用いた開発者ですので、その前提で見ていただければと思います。

それでは順番に解説していきます。

なお、各ツールのインストール手順は、本記事に書くよりも
個別にREADMEや別記事などを参照頂いたほうが確実かと思いますので、割愛させていただきます。

Pyenv(Pythonのバージョン管理)

  • pyenv
    • Pythonバージョンの切り替えを行う
    • 後に語る仮想環境ツールの親玉的存在

Pythonの開発を行うのであれば、複数バージョンのPythonに対応することは必須です。
現に私はPython2系のプロダクトを触ることもあればPython3系のプロダクトを触ることもあります。
Python2系のサポートが2020年を境に打ち切りになるものの、現実としてはまだ全然両方を触る機会はあります。

たとえPython2がなくなったとしても、マイナーバージョンにより機能が異なるため、
今後も複数バージョンを切り替えることはあるでしょう。

pyenv-virtualenv(Python開発ツールのために隔離した仮想環境)

  • pyenv-virtualenv
    • 以下の用にNeoVimからコールするPythonツールを隔離するために使用
      • NeoVimのリモートプラグインに使用されるPython
      • Vimプラグインに使用されるPython

Vimに限らず、開発ツールなどから利用されるツール群。
具体例をいえば以下のような(静的解析やフォーマットを行う)ツールをインストールしたPythonを
隔離するための仮想環境を構築するため、pyenv-virtualenvを使用しています。

  • flake8
  • autopep8
  • isort
  • yapf
  • pyls(Python Language Server)

これはなぜかというと、これらのツールは各プロジェクト毎で使用するものではなく、
どのプロジェクトを参照するにしても共通して使うものだからです。

pyenv-virtualenvwrapper(プロジェクトのために使用ライブラリを隔離した仮想環境)

こちらはプロジェクトが使用しているPythonライブラリ、DjangoやRequestsなどを仮想環境に隔離するために使用しています。
こちら素のvirtualenvで良いのではと言われるかもしれませんね。
確かにvirtualenvwrapperを使用している理由は、
私が利用しているvim-virtualenvがvirtualenvwrapper使用を前提としているからです。
vim-virtualenvについては後に書きます。

開発環境構築時のワークフロー

上記ツールは以下の流れで使用します。

導入時

この手順は各ツールインストール後に最初に一回のみ実施する手順です。

  1. pyenvを用いて使用するPythonバージョンをインストール

    $ pyenv install ${PYTHON_VERSION}
    
  2. pyenv-virtualenvを用いてNeoVim用の仮想環境を構築

    $ pyenv virtualenv ${VIRTUALENV_NAME}
    
    • Python2とPython3それぞれ用意
  3. 上記仮想環境にNeoVimからコールするツールをインストール

    $ pyenv shell ${VIRTUALENV_NAME}
    $ pip install ${DEVTOOL_NAME}
    $ pyenv shell --unset
    
  4. 各仮想環境のパスをNeoVimに登録

プロジェクト開始時

この手順は新しいシステムを触る際に一回のみ実施する手順です。

  1. pyenv-virtualenvwrapperを用いてプロジェクト用の仮想環境を構築

    $ mkproject ${PROJECT_NAME}
    
  2. プロジェクトの必要ライブラリをインストール

    $ mkproject ${PROJECT_NAME}
    $ workon ${PROJECT_NAME}
    $ pip install -r requirements.txt
    $ deactivate
    

NeoVim起動時

この手順は、開発開始時に都度実施する手順です。

  1. プロジェクトの仮想環境をロード

    $ workon ${PROJECT_NAME}
    
  2. NeoVim起動

    $ nvim
    

構築後の状況

開発環境構築後のPythonのパスは以下のようになります。
Python本体のパスは以下の各ディレクトリの./bin/pythonとなっています。

  • $HOME
    • .pyenv(pyenvのroot)
      • versions(pyenvが管理するバージョン毎のPython, pyenv-virtualenvが管理しているもの含む)
        • 2.x.x(特定バージョンのPythonのroot)
        • 3.x.x(同上)
        • neovim2(エディタ(NeoVim)からコールするツールを隔離したPython2の仮想環境)
        • neovim3(エディタ(NeoVim)からコールするツールを隔離したPython3の仮想環境)
    • .virtualenv(pyenv-virtualenvwrapperが管理するプロジェクト毎のPython仮想環境)
      • group1_project1(プロジェクトが必要とするライブラリを隔離したPythonの仮想環境)
      • group1_project2(同上)

LSP導入 on Neovim

NeoVimでLSPクライアントをインストールし、Python Language Serverを用いて、
静的解析及び定義ジャンプをできるようにします。Pythonのパス周りの話を除けばVimでもできるかと思います。

余談ですが私は過去に3度ほどLanguage Serverの導入に失敗しています。
これはPythonパスの解決がうまくいっていなかったのが理由ですが、
VimのLSPクライアントが戦国時代状態で導入するたびに違うツールを使っていたりしたのも原因の一つです。

というわけでちゃんと導入できて感動しています。

Python Language Server(pyls)のインストール

まずはPython Language Serverの導入です。
この作業はNeoVimから参照するPythonに対して実施します。

$ pip install python-language-server

なおこの操作により導入されるのはpython-language-serverです。

READMEに書いてありますがpython-language-serverの実態は、
これまでもPythonツールとして使われていたJediなどをLSPのプロトコルで参照できるようにしたもののようですね。

現在Microsoft名義で開発されているMicrosoft/python-language-serverもあり、こちらは開発がかなり活発な様子で今後に期待です。

LSPプラグイン

VimのLSPプラグインはいろいろあるのですが、今回は割と古株であるvim-lspをチョイスしました。
vim-lspは非同期処理をVimとNeoVim共通のインタフェースとして扱うためにasync.vimに依存しているようです。

インストール

私はパッケージマネージャとしてdeinを使用しているため、こちらでインストールします。

call dein#add('prabirshrestha/vim-lsp')
call dein#add('prabirshrestha/async.vim')

vim-lspにpyls登録

vim-lsp wiki pythonに従ってvimrcの設定をします。

let s:pyls_path = fnamemodify(g:python_host_prog, ':h') . '/'. 'pyls'
if (executable('pyls'))
    au User lsp_setup call lsp#register_server({
  \ 'name': 'pyls',
  \ 'cmd': {server_info->[expand(s:pyls_path)]},
  \ 'whitelist': ['python']
  \ })
endif

上記は私の環境用にちょっとした書き換えを行っています。
以下がその部分になっていて

let s:pyls_path = fnamemodify(g:python_host_prog, ':h') . '/'. 'pyls'

pylsをNeoVim参照用のパスに固定しています。こうすることで
vim-lspが参照するpylsをNeoVimが参照する仮想環境のPythonに固定しています。

キーマップ

vim-lspの持つコマンドを使いやすいようにマッピングしています。
vim-lspの持つコマンドはvim-lsp supported commandsまたは:h vim-lsp-commandsを参照。

ちなみに私はLocalLeader(特定のファイルタイプでのみ使用するプラグイン用キー)として\(バックスラッシュ)を割り当てており、
今回Python限定なのでそちらを使っています。

なお今回設定しているコマンドは参照系のみでLintの設定などしていません。
LinterとしてはALE、自動補完はdeoplete-jediを使用しているからです。

ALEの設定の話は別記事(ALE(on NeoVim)でPythonコードを楽に整形する)をご参照ください。

使用感として私の好みに合うLSPクライアントがないため、現状はこうしていますが、
このあたりをカバーできるようなLSPクライアントが出てくれば、その時考えます。

augroup PylsCommands
    autocmd!
    autocmd FileType python nnoremap <C-]> :<C-u>LspDefinition<CR>
    autocmd FileType python nnoremap K :<C-u>LspHover<CR>
    autocmd FileType python nnoremap <LocalLeader>R :<C-u>LspRename<CR>
    autocmd FileType python nnoremap <LocalLeader>n :<C-u>LspReferences<CR>
augroup END

自分の環境でうまく動かすハック的ななにか

vim-lsp導入に際して、困ったことが一つありました。
私の環境要因の可能性があるので明確にバグなのかはわかりませんが、事象として以下のようになりました。

  1. Vim起動時にPythonファイルを開くとpylsが起動する
  2. Vim起動時にファイル無指定で起動のみ行い、後にPythonファイルを開くとpylsが起動しない

vim-lspソースを読んでみると、Vim起動時lsp#enable()をコールしており、
登録ずみのLanguageServerに紐付いたファイルタイプがないとこの関数のトリガーとして引っかからないようでした。

なので一旦Pythonファイルを開いたときにこの関数をコールするようにAutoCmdを設定しています。

augroup LaunchPyls
    autocmd!
    autocmd BufWinEnter *.py :call lsp#enable()
augroup END

落ち着いたらvim-lspにIssueを投げてみる予定です。

vim-lspのデバッグ

上記のように動かなかったときは、vim-lsp debuggingを参考にするとvim-lspのデバッグが可能です。
躓いたらやってみたらいかがでしょうか。

let g:lsp_log_verbose = 1
let g:lsp_log_file = expand('~/vim-lsp.log')

プロジェクト毎の仮想環境を見てくれない

上記で一通り完了しましたが、まだ終わりではありませんでした。

なぜならばDjangoなど各プロジェクト毎にインストールしているライブラリをLspDefinitionコマンドで参照することが出来なかったからです。
おそらく原因はpylsの中で起動しているJediが仮想環境を静的解析の対象として認識していないのが原因です。

この問題を解決するためにはvim-virtualenvが必要となります。

このプラグインはvirtualenvwrapperでactivateした仮想環境を認識し、
Jedi側の静的解析の対象に加えるような動きとなるようです。(このあたりはjediのソースを読む必要がありそうでちゃんと追えてない)

最終的なvimrc

私はdeinのtomlファイル定義を用いているので、最終的には以下のような定義となりました。

[[plugins]]
repo = 'jmcantrell/vim-virtualenv'
on_ft = ['python']

[[plugins]]
repo = 'prabirshrestha/async.vim'

[[plugins]]
repo = 'prabirshrestha/vim-lsp'
on_ft = ['python']
depends = ['async.vim', 'vim-virtualenv']
hook_add = '''
" registar pyls to vim-lsp
let s:pyls_path = fnamemodify(g:python_host_prog, ':h') . '/'. 'pyls'
if (executable('pyls'))
    au User lsp_setup call lsp#register_server({
    \ 'name': 'pyls',
    \ 'cmd': {server_info->[expand(s:pyls_path)]},
    \ 'whitelist': ['python']
    \ })
endif

augroup LaunchPyls
    autocmd!
    autocmd BufWinEnter *.py :call lsp#enable()
augroup END

augroup PylsCommands
    autocmd!
    autocmd BufWinEnter *.py :call lsp#enable()
    " local key mapping
    autocmd FileType python nnoremap <C-]> :<C-u>LspDefinition<CR>
    autocmd FileType python nnoremap K :<C-u>LspHover<CR>
    autocmd FileType python nnoremap <LocalLeader>R :<C-u>LspRename<CR>
    autocmd FileType python nnoremap <LocalLeader>n :<C-u>LspReferences<CR>
augroup END
'''

最後に

なお今回の記事の作成および検証はmeguro.vimというもくもく会にて行いました。まだ来ていないVimmerの方はぜひ一回来てみてはいかがでしょうか?

それでは皆さん。良いVim&Pythonライフを。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away