Blank File

LinuxとかPythonとかVimとか、趣味でいじる感じで

vim-watchdogsで快適なシンタックスチェック

訳あってVimでのシンタックスチェックをSyntastic + pyflakes-vimからosyo-manga/vim-watchdogsに置き換えたらかなり快適になったので紹介したいと思います。

これまで

Vimでのシンタックスチェックは主に Syntastic を使い、Pythonを書く時は pyflakes-vimをフォークして少し修正したもの を使っていました。 Syntasticは対応しているファイルタイプは多いのですが、保存しないとチェックできなかったり少し動作が重かったりする(特にWindows環境)ところが不満でした。

なので、書くことの多いPythonではSyntasticを使わず、ほぼリアルタイムでチェックできるpyflakes-vimを使っていました。 特にFlaskなどでサーバーをデバッグモード *1 で走らせていると、シンタックスエラーのあるファイルをうっかり保存 → サーバー停止、という微妙に面倒なことになるので、保存せずにエラーを確認できるのは便利でした。

変更したきっかけ

最近Optional Typingが気になっていたので、Python 3で導入された関数アノテーション *2 を試したところ、pyflakes-vimに全力でシンタックスエラー出されました。

f:id:h-miyako:20141014201436p:plain

解決策を調べたところ、Python2ではなくPython3でpyflakesを実行すればいいらしいです *3 。 試しに同じファイルで!python3 -m pyflakes %した所、確かにエラーは出ませんでした。

解決策がわかったのでさっさとpyflakes-vimを修正しようとしたのですが、 Vimpythonインターフェイス (ver. 2.7) でpyflakesを直接importして処理しているんですね、このプラグイン。 これを単純にPython3で置き換えると私の環境で使えなくなる *4 ので、Syntasticで我慢するか、外部プロセスでpyflakesを起動する(ほぼ全部作り直し) *5 か、と悩みながらTwitterで愚痴っていたらid:cohamaさんに vim-watchdogs を教えていただきました。

watchdogsは以前少し試して、当時は設定が難しく感じたのと、確かエラー箇所のハイライトができずに諦めていたのですが、きちんと作者さんの紹介記事( watchdogs.vim つくりました - C++でゲームプログラミング )とドキュメントを読んで、 jceb/vim-hier もインストールしたらちゃんと動きました。

watchdogsの特徴を紹介記事から引用します。

  • quickrun-module を利用した幅広い拡張性
  • vimproc を使用した非同期で処理

ステキです。 特に非同期処理はもっさり感から開放されそうでかなり魅力的です。

以上のような経緯で実際使ってみた所、かなり快適な使い心地でした。 一方で、拡張性の高さの裏返しでもありますが、Syntasticと比べて設定が複雑なところがあるので、導入と設定の過程を書き残しておきます。

vim-watchdogsの導入と設定例

作者のosyo-mangaさんの記事 が非常に詳しいので、実際に導入する場合はそちらも参照してください。 ここでは主に私が行った設定について書きます。

導入後の様子

こんな感じです。

f:id:h-miyako:20141015011248g:plain

インストール

依存プラグインを含めてインストールします。 以下を.vimrcに記述して :NeoBundleInstall します。

NeoBundle "thinca/vim-quickrun"
NeoBundle "Shougo/vimproc"
NeoBundle "osyo-manga/shabadou.vim"
NeoBundle "osyo-manga/vim-watchdogs"

設定

チェック後にquickfixウィンドウを開かせない

デフォルトではエラーチェック後にエラー箇所がquickfixに登録され、quickfixウィンドウが開きます。 私はエラーの有無と場所だけわかればいいので、とりあえずquickfixウィンドウを開かないように設定します *6

if !exists("g:quickrun_config")
    let g:quickrun_config = {}
endif
let g:quickrun_config["watchdogs_checker/_"] = {
      \ "outputter/quickfix/open_cmd" : "",
      \ }

Python3のPyflakesを使うcheckerを設定する

そもそも pyflakes が使えないと話にならないのでインストールします。 Ubuntuではパッケージが提供されているので apt-get install pyflakes でインストールできます。 これでpyflakespyflakes3コマンドが使えるようになります。 それ以外のOSやvirtualenvを使った仮想環境などではpip install pyflakesします。

vim-watchdogsにはデフォルトでpyflakesでチェックする設定(watchdogs_checker/pyflakes)が用意されていますが、今回それはPython2用に残して、Python3のpyflakesを使うcheckerの設定を追加しました。 普段Ubuntnuを使うことが多いので、pyflakes3コマンドが使える場合はそれを優先し、無理な時はpython3でpyflakesモジュールを実行するように設定しています。 それでも無理ならpython2を使います。

let s:pyflakes = executable('pyflakes3') ? 'pyflakes3' :
      \          executable('python3') ? 'python3' :
      \          executable('pyflakes') ? 'pyflakes' :
      \          'python'
let s:cmdopt = executable('pyflakes3') ? '' :
      \          executable('python3') ? '-m pyflakes' :
      \          executable('pyflakes') ? '' :
      \          '-m pyflakes'
let g:quickrun_config["watchdogs_checker/pyflakes3"] = {
      \ "command" : s:pyflakes,
      \ "cmdopt" : s:cmdopt,
      \ "exec"    : "%c %o %s:p",
      \ "errorformat" : '%f:%l:%m',
      \ }
unlet s:pyflakes
unlet s:cmdopt

上記の設定でpyflakes3でチェックしてくれるcheckerが定義できました。 以下でPythonのファイルではこのcheckerを使うように設定し、最後に設定をwatchdogsに読み込ませています *7

let g:quickrun_config["python/watchdogs_checker"] = {
      \ "type" : "watchdogs_checker/pyflakes3",
      \ }

call watchdogs#setup(g:quickrun_config)

保存せずにエラーチェックを実行する

デフォルトでは明示的に:WatchdogsRunを実行しないとエラーチェックをしてくれません。 保存時にチェックする設定が提供されていますが、私はもっと頻繁にチェックしてほしいので、以下のautocmdを設定しました。 ですが、ひたすら並んでいる[xn]noremapは副作用もあるので、おすすめしません。

[2014/10/18 17:45 追記]

下記のコードに冗長な記述があったので修正しました。
cohamaさん、ご指摘ありがとうございました。

autocmd ... *.py execute "WatchdogsRun"
autocmd ... *.py WatchdogsRun

[2014/11/9 16:15 追記]

InsertLeaveでWatchdogsRunすると他のプラグインと干渉したので、が押された時に呼び出すよう変更しました。

[追記終わり]

augroup my_watchdogs
  autocmd!
  autocmd BufWritePost *.py WatchdogsRun
  autocmd BufRead,BufNewFile *.py WatchdogsRun
  autocmd BufRead,BufNewFile *.py
        \   xnoremap <buffer><silent> x x:WatchdogsRun<CR><left>
        \ | xnoremap <buffer><silent> d d:WatchdogsRun<CR><left>
        \ | xnoremap <buffer><silent> D D:WatchdogsRun<CR><left>
        \ | nnoremap <buffer><silent> D D:WatchdogsRun<CR><left>
        \ | nnoremap <buffer><silent> dd dd:WatchdogsRun<CR><left>
        \ | nnoremap <buffer><silent> dw dw:WatchdogsRun<CR><left>
        \ | nnoremap <buffer><silent> u u:WatchdogsRun<CR><left>
        \ | nnoremap <buffer><silent> <C-R> <C-R>:WatchdogsRun<CR><left>
        \ | inoremap <buffer><silent> <Esc> <Esc>:WatchdogsRun<CR><left>
augroup END

ノーマルモードで x 押しっぱなし削除のようなお行儀の悪いこと *8 をしない人は、以下のようにTextChangedイベント ((TextChangedイベントは比較的最近のVimで導入されたような気がするので、古いVimでは動作しないかもしれません)) を使った設定のほうがいいと思います。

augroup my_watchdogs
  autocmd!
  autocmd InsertLeave,BufWritePost,TextChanged *.py WatchdogsRun
  autocmd BufRead,BufNewFile *.py WatchdogsRun
augroup END

エラー箇所(行)をハイライトする

jceb/vim-hierというプラグインを使うと、quickfixに登録されている行がハイライトされます。 このプラグインはインストールするだけで実行されるので、特に設定は必要ありません。

NeoBundle "jceb/vim-hier"

副作用(?)として、他の用途でquickfixを使った時もエラーっぽくハイライトされてしまいます(例: :vimgrepした時など)。

エラーメッセージを表示する

Syntasticやpyflakes-vimでは、カーソルがエラー箇所に来た時にエラーの内容が画面最下部 *9 に表示されます。 それと同じ機能は dannyob/quickfixstatus で実現できます。 このプラグインもインストールするだけで動作します。

NeoBundle "dannyob/quickfixstatus"

実はこのプラグインid:cohamaさんに教えていただきました。 cohamaさんありがとうございました!

ステータスラインにエラーの有無や場所を表示する(lightline.vim使用)

私はステータスラインに各種情報を表示するために lightline.vim というプラグインを使っています。 ここにエラーの有無も表示します。 冒頭のGifで、右下の赤い部分です。

エラーがある時だけ赤い部分を表示し、L5 (1)と L[最初のエラー行] (ファイルのエラー数) の形式で表示しています。 Syntasticと組み合わせてこの機能を実現する方法は 作者の方が詳しく説明されている のでそちらをご参照ください。 要はエラーチェックが終わった時

call lightline#update()

すればいいわけですね。 今回はすでに大量に WatchdogsRun してるので、その後にcall〜書けばいいだけでしょ、単純作業で簡単簡単・・・

Watchdogsは非同期実行だからこの時点じゃエラーチェック終わってないorz

非同期難しいです・・・ とりあえずQuickFixCmdPostイベントを使って無理だったところで諦めかけましたが、ググったらすでに対応されてプラグインまで作ってくださっている方がいらっしゃいました。

というわけで、 vim-qfstatusline を使えば非同期の問題は解決できます。 設定は以下のように行いました。

[2014/10/19 14:21 修正]

KazuakiM/vim-qfstatusline を変更していただいたので、それに合わせて設定を書き直しました。

NeoBundle "itchyny/lightline.vim"
NeoBundle "KazuakiM/vim-qfstatusline"

" watchdogsのフックを設定
let g:quickrun_config["watchdogs_checker/_"] = {
      \ "outputter/quickfix/open_cmd" : "",
      \ "hook/qfstatusline_update/enable_exit" : 1,
      \ "hook/qfstatusline_update/priority_exit" : 4,
      \ }

" :WatchdogsRun後にlightline.vimを更新
let g:Qfstatusline#UpdateCmd = function('lightline#update')

" lightline.vimの設定
let g:lightline = {
    \ 'mode_map': {'c': 'NORMAL'},
    \ 'active': {
    \   'right': [
    \     [ 'syntaxcheck' ],
    \   ]
    \ },
    \ 'component_expand': {
    \   'syntaxcheck': 'qfstatusline#Update',
    \ },
    \ 'component_type': {
    \   'syntaxcheck': 'error',
    \ },
    \ }

エラーの総数も得たかったので、メッセージ部分はプラグイン組み込みのqfstatusline#Qfstatusline()を参考に、関数 MySyntaxUpdate を作りました。 動作がもっさりする場合は上記MySyntaxUpdateではなくqfstatusline#Qfstatusline()を使った方がいいと思います。

qfstatusline#Updateでエラー件数も表示されるようになったので、MySyntaxUpdate関数を作る必要はなくなりました。

[ 修正終わり ]

他の設定、プラグインなど

ここまでの設定で、冒頭のGifアニメのような動きになります。 上記のコードは一部の設定だけを抜き出したので、このままでは動かないかもしれません。 その場合はコメント欄か twitter (@MiyakoDev)あたりでご指摘いただければ幸いです。

以下、今回私は使いませんでしたが、便利そうなプラグインを紹介だけさせていただきます。

vim-qfsigns

これまでの設定でエラー箇所のハイライトはされますが、Syntasticのようにsign表示はされません。 signを表示する場合は、前記qfstatuslineと同じ方が作られたvim-qfsignsというプラグインを利用するのが良さそうです。

cohama/vim-hier

今回の設定ではエラー行全体をハイライトしていままが、これだとエラーの原因がわかりにくく、かつインデントが深くなってくると見栄えも悪くなって来ます。 この点はcohamaさんがvim-hierをフォークして修正したものがあるので、そちらを試してみるといいと思います。

終わりに

長くなってしまいましたが、vim-watchdogs便利なのでもっと皆さん使うといいと思います。

本当は mypyjedilinterオプションを使ったシンタックスチェックをwatchdogsで使う設定についても書きたかったのですが、これは気が向いたら別記事に書きます。 そちらではwatchdogsの設定というかvimerrorforamtに苦しみました・・・

*1:ファイルの変更を検出すると自動的にサーバーが再起動する

*2:PEP 3107 -- Function Annotations

*3:How to use flake8 for Python 3 ? - Stack Overflow

*4:私の環境ではPython2とPython3は同時に使えませんでした。参考: Debian: vimでPythonとPython3の両方使えるようになってた(ように見えたけどそうでもなかった)

*5:vim-dartanalyzerのコードが使いまわせても面倒

*6:watchdogs.vim で終了時に別のウィンドウへカーソルが移動してしまう問題の解決 - C++でゲームプログラミング

*7:今は呼ぶ必要なさそうですが一応 watchdogs.vim をいろいろと更新した - C++でゲームプログラミング

*8:私はこれをよくやっています・・・キーリピート速度の設定をみたら40/sでした・・・

*9:コマンドモードで入力したりする領域。正式な名前はなんでしょう・・・?