Python
Vim
neovim

NeovimでモダンなPython環境を構築する

経緯

  • 転職してPythonメインの会社で働いてます
  • エディタハラスメントはなくなってVim使ってもいい会社です
  • VimでPythonをゴリゴリかける環境を作ろう

追記

以前この記事を書いてからそこそこ経ちました。
いまだに、たまにイイねとかが飛んできて、ありがたい気持ちとともに、
記事作成当時とはだいぶvimrcも代わり、内容が古いままで申し訳ないなと思い追記をさせて頂きました。
各プラグインの設定も記載したので参考にしていただければ幸いです。
なお一通り設定するとこうなります。
Screen Shot 2017-09-06 at 22.11.38.png

必須

会社に入ってからvimの設定をすべてneovim用に書き直しました。
今環境構築するならサクサク動くneovimでやってやりましょう。
vim8のプラグイン対応状況が変わり、deoplete以外であればvim8で動きます。
しかし私はdeopleteが使いたいのでneovimです。

  • neovim 0.2.0
  • macOS sierra 10.12.3

neovimのインストールについては過去記事を参照。
Ubuntu向けで書いたけどmacでもほぼ同様。 apt-getbrew に読み替えれば行けるはず
Ubuntuでzsh+neovimの環境設定

導入プラグイン

Pythonをvimで書きたいなら幾つかのプラギンは必須。
今回ご紹介するプラギンは次のとおりです。

  • dein.vim
  • denite.nvim
  • deoplete.nvim
  • vim-airline
  • nerdtree
  • vim-fugitive
  • vim-gitgutter
  • deoplete-jedi
  • vim-quickrun
  • neomake
  • ALE
  • vim-virtualenv
  • vim-python-pep8-indent
  • jedi-vim
  • gen_tags.vim
  • vim-test + vim-dispatch
  • gtags.vim + vim-qfreplace

dein.vim

Shougo/dein.vim

  • プラグインマネージャ
  • 暗黒美夢王の謹製プラギン。暗黒の力をあなたのVimに

沢山のプラグインを管理する上でプラグインのインストール、アップデートを補助するプラグインマネージャは必須。
以前は dein.vim の前身たる neobundle を使用していましたが、転職を機に乗り換えました。 *.toml で設定ファイルを作成できるようになったため、プラグインの依存関係や遅延ロード設定は neobundle 時代に比べ、格段に可読性がよくなりました。
暗黒美夢王は私が最もお世話になっているVimmerです。そう思っているのは私だけじゃないだろうけど。

設定例

let s:dein_dir = expand('$CACHE/dein')
if dein#load_state(s:dein_dir)
    call dein#begin(s:dein_dir)
    call dein#load_toml('~/.vim/rc/dein.toml',          {'lazy': 0})

    call dein#load_toml('~/.vim/rc/dein_lazy.toml',     {'lazy': 1})
    call dein#load_toml('~/.vim/rc/dein_neo.toml',      {'lazy': 1})
    call dein#load_toml('~/.vim/rc/dein_python.toml',   {'lazy': 1})
    call dein#load_toml('~/.vim/rc/dein_go.toml',       {'lazy': 1})
    call dein#load_toml('~/.vim/rc/dein_frontend.toml', {'lazy': 1})
    call dein#end()
    call dein#save_state()
endif

deinの設定の肥大化とともに、プラグインの設定をtomlで管理しています。

遅延ロードなしをdein.toml
複数のfiletypeに紐付いて遅延ロードするものをdein_lazy.toml
単一のファイルタイプに紐付いて遅延ロードするものをdein_{filetype}.toml
としています。

denite.nvim

Shougo/denite.nvim

  • ファイルランチャーしかし、それだけにとどまらない脅威の汎用性
  • 暗黒美夢王の謹製プラギン。暗黒の力をあなたのVimに
  • neovim専用プラギン
  • Python3必須

主にファイルランチャーとして使用します。しかしそれだけではない。 denite はありとあらゆるものをリスト表示、絞り込み、操作する統合プラットフォームです。
私の場合は、プロジェクト内のファイル一覧を表示→絞り込み→開くがメインで、ファイル内の文字列をGrep検索(システムのgrepコマンドの内容をQuickFixに出力するため、現在は使用していない)。関数リストの表示などがサブ用途となります。

ファイルのみならずありとあらゆるものを扱えるだけあって、その汎用性は高く。ソート順、検索エンジンやGrepコマンドの切り替え、選択した対象の操作などカスタマイズが豊富です。

本体動作が高速なのはもちろん、denite起動→絞り込み→開くの流れは自分の思考をそのまま反映するが如く、ファイル・行ジャンプを可能にします。使いこなせばエディットのスピードが加速することは間違いありません。

反面 denite は設定が複雑になりがちです。helpを読みながら自分の身体に合わせたカスタマイズが必要となります。インスコ後すぐに爆速〜とはなりません。

設定例

" Denite用プレフィックス
nmap [denite] <Nop>
map <C-j> [denite]

" プロジェクト内のファイル検索
nmap <silent> [denite]<C-P> :<C-u>Denite file_rec -highlight-mode-insert=Search<CR>
" バッファに展開中のファイル検索
nmap <silent> [denite]<C-B> :<C-u>Denite buffer -highlight-mode-insert=Search<CR>
" ファイル内の関数/クラス等の検索
nmap <silent> [denite]<C-O> :<C-u>Denite outline -highlight-mode-insert=Search<CR>
" dotfiles配下をカレントにしてfile_rec起動
nmap <silent> [denite]<C-V> :<C-u>call denite#start([{'name': 'file_rec', 'args': ['~/.dotfiles']}]) -highlight-mode-insert=Search=Search<CR>

" 上下移動を<C-N>, <C-P>
call denite#custom#map('normal', '<C-N>', '<denite:move_to_next_line>')
call denite#custom#map('normal', '<C-P>', '<denite:move_to_previous_line>')
call denite#custom#map('insert', '<C-N>', '<denite:move_to_next_line>')
call denite#custom#map('insert', '<C-P>', '<denite:move_to_previous_line>')
" 入力履歴移動を<C-J>, <C-K>
call denite#custom#map('insert', '<C-J>', '<denite:assign_next_text>')
call denite#custom#map('insert', '<C-K>', '<denite:assign_previous_text>')
" 横割りオープンを`<C-S>`
call denite#custom#map('insert', '<C-S>', '<denite:do_action:split>')
" 縦割りオープンを`<C-I>`
call denite#custom#map('insert', '<C-I>', '<denite:do_action:vsplit>')
" タブオープンを`<C-O>`
call denite#custom#map('insert', '<C-O>', '<denite:do_action:tabopen>')

" file_rec検索時にfuzzymatchを有効にし、検索対象から指定のファイルを除外
call denite#custom#source(
    \ 'file_rec', 'matchers', ['matcher_fuzzy', 'matcher_project_files', 'matcher_ignore_globs'])

" 検索対象外のファイル指定
call denite#custom#filter('matcher_ignore_globs', 'ignore_globs',
    \ [ '.git/', '.ropeproject/', '__pycache__/',
    \   'venv/', 'images/', '*.min.*', 'img/', 'fonts/'])

deniteは全プラグイン中で最多使用率のため、可能な限り早く打てるキーバインドを採用しています。結果として私は<C-J><C-*>に落ち着きました。
denite検索時の上下移動なんかは完全に好みの問題かと思います。

deoplete.nvim

Shougo/deoplete.nvim

  • 自動補完システム
  • 暗黒美夢王の謹製プラギン。暗黒の力をあなたのVimに
  • neovim専用プラギン
  • Python3必須

自動補完プラグインです。そしてまた暗黒美夢王の暗黒の力です。
頭に自動補完システムと記載したのは、それはこのプラグインが他の自動補完プラグインとは一線を画すためです。

例えば、Pythonの自動補完プラグインならば jedi-vim というプラグインがあるのですが、コレはPythonの自動補完のみを実施するプラグインです。
対して deoplete は補完機能のベースのみを提供し、言語毎の独自の補完は、 deoplete のインタフェースに従った外部プラグイン source (と呼ばれるそうです)を使用して補完を追加する形式になります。
なおPythonの自動補完は、後述する deoplete-jedi で追加されます。

こうして書いてみると、暗黒美夢王が作ってるものって、プラグインじゃなくてエコシステムなんじゃないのかなと思う。

設定例

現在いろいろ工事中

vim-airline

vim-airline/vim-airline

  • ステータスバー改造プラギン
  • 導入するとあら不思議。vimのスタータスバーがとってもおしゃれになっちゃうぞ♪

ステータスバーを色々弄っておしゃれにしてくれます。
エディットにとても影響をあたえるかと聞かれたらそこまででもないです。しかし、エディットのテンションを上げるためには、「私はカッコイイエディタでエディットしてるんだ。」というカッコつけな何かが必要となります。

Normal,Insert,Visualの各モードで色変更、編集中のGitブランチの表示、env中のvirtualenvの表示、その他カーソル位置情報の表示などをしてくれます。おしゃれに。

設定例

" モードの表示名を定義(デフォルトだと長くて横幅を圧迫するので略称にしている)
let g:airline_mode_map = {
    \ '__' : '-',
    \ 'n'  : 'N',
    \ 'i'  : 'I',
    \ 'R'  : 'R',
    \ 'c'  : 'C',
    \ 'v'  : 'V',
    \ 'V'  : 'V',
    \ '' : 'V',
    \ 's'  : 'S',
    \ 'S'  : 'S',
    \ '' : 'S',
    \ }

" パワーラインでかっこよく
let g:airline_powerline_fonts = 1
" カラーテーマ指定してかっこよく
let g:airline_theme = 'badwolf'
" タブバーをかっこよく
let g:airline#extensions#tabline#enabled = 1

" 選択行列の表示をカスタム(デフォルトだと長くて横幅を圧迫するので最小限に)
let g:airline_section_z = airline#section#create(['windowswap', '%3p%% ', 'linenr', ':%3v'])

" virtulenvを認識しているか確認用に、現在activateされているvirtualenvを表示(vim-virtualenvの拡張)
let g:airline#extensions#virtualenv#enabled = 1

" gitのHEADから変更した行の+-を非表示(vim-gitgutterの拡張)
let g:airline#extensions#hunks#enabled = 0

" Lintツールによるエラー、警告を表示(ALEの拡張)
let g:airline#extensions#ale#enabled = 1
let g:airline#extensions#ale#error_symbol = 'E:'
let g:airline#extensions#ale#warning_symbol = 'W:'

基本的にかっこよくなるように書きました。ただしファイル名が見えなくなるため、横幅を圧迫するようなものは可能な限り排除しています。

nerdtree

scrooloose/nerdtree

  • ファイルエクスプローラー
  • 弱いvim使いの助っ人

IDEの左によくついてるエクスプローラーです。
deniteがあればファイルエクスプローラなんていらないじゃんって思ってたんですが。人間多くの事を覚えてはいられないもので、階層構造でファイルを見たい時って結構あるんですよね。

すぐにdeniteに打ち込むファイル名が浮かんでこないとき、nerdtreeを開くとそこには安心のファイル一覧が。

なお、nerdtree-git-pluginと後述するfugitiveを使用すれば。nerdtree上にファイル編集状態が表示されます。

「そんなのAtomやVsCodeなら最初からついとるわボケぇ」とか言わないで。心が折れる。

vim-fugitive

tpope/vim-fugitive

  • Gitクライアントプラグイン
  • vimから離れないでGitが使えます

基本的に私はずっとvimに居ないと心が落ち着かないとかいう人間ではなく、普通にCtrl+zでターミナルとvimを行き来したりするんで、そこまで必要か?と思っていました。
けどターミナルで操作するより圧倒的に有利な部分があって、それがfugitive:Gdiff:Gblameコマンド。

:Gdiffは、「しまったコレは編集前のほうが良かったぞ」と思う場合に使用する。
そういう場合にgitのdiffを表示してそこから元コードを引っ張ってくるという操作をしているのだが、
ターミナルに戻る→diff表示→コピー→vimに戻ってペースト

":Gdiff"の実行→ヤンク→ペースト
となる。かなりの速度向上。

:Gblameに関してもコードを見ている最中に、コレどうしてこうなってたっけとなったものがその場で:Glame一発で見れてログ情報も参照できる。
しかも:Gdiffも同様だが、vimのsyntax highlightが適用された状態で。速い。

設定例

nmap [figitive] <Nop>
map <Leader>g [figitive]
nmap <silent> [figitive]s :<C-u>Gstatus<CR>
nmap <silent> [figitive]d :<C-u>Gdiff<CR>
nmap <silent> [figitive]b :<C-u>Gblame<CR>
nmap <silent> [figitive]l :<C-u>Glog<CR>

上記以外のコマンドはtigで操作するので私は使用していない。

vim-gitgutter

airblade/vim-gitgutter

gitのHEADからのコード追加、削除、変更を左端の隙間(gitter)に表示してくれる。昔は邪魔だと思ってましたが、使ってたら手放せなくなりました。

deoplete-jedi

zchee/deoplete-jedi

  • deopleteのPython用sourceを追加するプラグイン
  • 作者はPython覚えて数ヶ月でコレを作ったらしい。すごい

前述したdeopleteに対してPythonの自動補完を追加します。
裏側ではPythonの静的解析ライブラリjedi(スター・ウォーズとは関係ない?)を使用して解析結果をdeopleteに渡しているようです

設定例

現在いろいろ工事中

vim-quickrun

thinca/vim-quickrun

  • プログラムランナー

vim上でPythonのプログラムを実行することができます。
私の使い方は実行結果にエラーがなければBufferに出力、エラーがあればQuickFixに表示してそのままコードジャンプしています。
なお単体では非同期実行をサポートできないため、別途vimproc.vimを追加して、vim-quickrunで設定する必要があります。

なお類似プラギンにasyncrunという非同期実行を、vimprocではなくvim本体の機能で行うプラギンがあるのですが、どうもパス周りをうまく読み込んでくれなかったため、インストールだけして使っていません。

設定例

" vimprocで非同期実行
" 成功時にバッファ、失敗時にQuickFixで表示
" 結果表示のサイズ調整など
let g:quickrun_config = {
    \ '_' : {
        \ 'runner' : 'vimproc',
        \ 'runner/vimproc/updatetime' : 40,
        \ 'outputter' : 'error',
        \ 'outputter/error/success' : 'buffer',
        \ 'outputter/error/error'   : 'quickfix',
        \ 'outputter/buffer/split' : ':botright 8sp',
    \ }
\}

" 実行時に前回の表示内容をクローズ&保存してから実行
let g:quickrun_no_default_key_mappings = 1
nmap <Leader>r :cclose<CR>:write<CR>:QuickRun -mode n<CR>

キーバインドに既存の実行結果クローズとファイル保存を付けとくと便利。
vimprocはいわずもがな。

neomake

neomake/neomake

  • linter実行プラグイン

Pythonにはpep8と呼ばれる構文ルールがあり、それを自動検出するためのlinterとよばれる静的解析コマンドがあります。neomakeではそのlinterを非同期実行し、実行結果をvim上にマーカー表示してくれます。

私の場合はpep8に加えpylintも検出してくれるflake8を実行させています。
なお、もともとは類似プラギンであるsyntasticを使用していましたが、こちらは非同期実行をサポートしていないため、neomakeに乗り換えました。

ALE

w0rp/ale

  • linter実行プラグイン
  • neomakeからの乗り換え

neomakeをしばらく使用していたが、大きいプロジェクトを操作しているときに、linterの反映に結構なラグ(2,3秒くらい)が発生することがよくあった。
そのため、以前から注目していたALEを試してみたところ、処理が高速、見た目がオサレ、機能が豊富で痒いところに手がとどく、READMEが丁寧などの理由から乗り換えた。

設定例

" エラー行に表示するマーク
let g:ale_sign_error = '⨉'
let g:ale_sign_warning = '⚠'
" エラー行にカーソルをあわせた際に表示されるメッセージフォーマット
let g:ale_echo_msg_format = '[%linter%] %s [%severity%]'
" エラー表示の列を常時表示
let g:ale_sign_column_always = 1

" ファイルを開いたときにlint実行
let g:ale_lint_on_enter = 1
" ファイルを保存したときにlint実行
let g:ale_lint_on_save = 1
" 編集中のlintはしない
let g:ale_lint_on_text_changed = 'never'

" lint結果をロケーションリストとQuickFixには表示しない
" 出てると結構うざいしQuickFixを書き換えられるのは困る
let g:ale_set_loclist = 0
let g:ale_set_quickfix = 0
let g:ale_open_list = 0
let g:ale_keep_list_window_open = 0

" 有効にするlinter
let g:ale_linters = {
\   'python': ['flake8'],
\}

" ALE用プレフィックス
nmap [ale] <Nop>
map <C-k> [ale]
" エラー行にジャンプ
nmap <silent> [ale]<C-P> <Plug>(ale_previous)
nmap <silent> [ale]<C-N> <Plug>(ale_next)

正直README見れば大体書いてあるけど一応貼っておきます。

vim-virtualenv

jmcantrell/vim-virtualenv

  • virtual-envのパス自動追加プラギン

Pythonで開発しているならば、virtualenvを使用するシーンが多々あるはずです。その場合、virtualenvでしかインストールしていないライブラリなどをvim上で参照する必要があるでしょう。
このプラギンはそんな状況のためにあります。
virtualenvをactivateしてvimを起動すれば、virtualenvのライブラリを読み込んだ状態でvimが起動します。

vim-python-pep8-indent

Vimjas/vim-python-pep8-indent

  • 自動でpep8準拠のインデント

vimのautoindentではpep8の規約に準拠してくれません。このプラグインを入れれば、改行時にpep8に準拠した改行を勝手にやってくれます。便利。

# 普通のautoindent
format_str = format('なんや:{} かんや:',
    a, b)
# pep8準拠のautoindent
format_str = format('なんや:{} かんや:',
                    a, b)

jedi-vim

davidhalter/jedi-vim

  • jediを利用した自動補完などPythonのコーディングをする際の様々な便利ツールを提供
  • しかし私はコード参照しか使ってない

jediを活用した色々をやってくれるプラグインです。Python環境構築の当初、自動補完をdeoplete-jediにて行っていたため、使用していませんでした。
しかし、ctagsを使用したタグジャンプをしている中で同スペルのタグジャンプを行う際に、タグジャンプからの対象選択が煩わしかったため、コード参照用に導入しました。
そのためほとんどの機能を潰してコード参照のみを残しています。一発で参照元に飛べるのはやっぱり便利。
ただ、このせいでvimが重くなっている気がするので、もっといいやり方が無いか調査中。

設定例

" 勝手にキーバインド設定やら自動補完などをやるので潰しておく
let g:jedi#auto_initialization = 0
let g:jedi#auto_vim_configuration = 0
let g:jedi#smart_auto_mappings = 0
let g:jedi#completions_enabled = 0
" コード参照のキーバインドを登録
let g:jedi#goto_command = "<Leader>d"

gen_tags.vim

jsfaint/gen_tags.vim

  • ctags自動生成
  • gtagsも対応

ctagsの自動生成プラグインは色々ありますが、jobによる非同期実行とgtagsに対応しているという点において頭ひとつ抜けています。
ctagsを知らない方は、導入記事が色々あるので検索してみてください。

設定例

let g:gen_tags#ctags_auto_gen = 1
let g:gen_tags#gtags_auto_gen = 1

自動保存を有効にするだけ。簡単。

vim-test + vim-dispatch

anko-m/vim-test
tpope/vim-dispatch

  • テスト実行プラグインとそのランナー

QuickRunとは別にテスト用のプラグインを導入しています。詳細は下記の記事を参照ください。
結構マジメにVimのテスト環境を整えてみる(Python用)

gtags.vim + vim-qfreplace

vim-scripts/gtags.vim
thinca/vim-qfreplace

  • gtagsによる参照検索とQuickFix置換

ctagsでは宣言元を取得できても参照先が取得できないため別途GNU globalを採用しています。
gtags.vimで参照を取得し、qfreplaceで置換を行います。要するにリファクタリングツールですね。

GNU globalによる検索については下記の記事を参照ください。
vimのコードジャンプをgtagsでやってみるぞい

.vimrc

ホントは各プラギン毎に、抜粋したvimrcを記載したいのですが、力尽きたのでまた時間があるときに更新します。
vimrcは下記を御覧ください。
lighttiger2505/.dotfiles

最後に

vimで仕事をして1ヶ月ちょいを経てこれまで趣味レベルと比べて、かなり実用的な環境が整ってきたので、この機会にご紹介しようかと。
半年間ひたすらvimを整備してきましたが、まだまだ道は遠い。精進します。
この記事をご覧になっている方で、俺のほうがもっといいvimだ。という人もいるかと思いますので、教えてくれたらうれしいです。