VimでPython Doctestを実行するプラグインを作成する （１）


# はじめに
「[はじめてのVimプラグイン作成（autopep8）～ GitHubの練習を兼ねて，既存レポジトリのフォーク／クローン／更新／プッシュを行ってみた](http://qiita.com/daizutabi/items/4862351b34bb045a3cb4)」では．既存のVimプラグインをカスタマイズして使用する方法について説明しました．今回は，スクラッチからプラグインを作成することにチャレンジします．Vim初心者が書いた自分のための備忘録ですので，かなりくどい記述になっています．これからVimプラグインを作成しようと思っている方に役立つことがあれば幸いです．

題材に選んだのはPython Doctestです．私は，Pythonの[NumPy](http://www.numpy.org/)，[SciPy](http://www.scipy.org/)，[pandas](http://pandas.pydata.org/)などのライブラリを使うことが多いです．これらのライブラリを使った科学技術計算では，コーディングが間違っていた場合，エラーが出るわけではなく，不正解の答えを黙って返してきます．正しい結果かそうでないかを判断するのは，作った当人以外にいません．自作の関数が常に正しい結果を返すことを保証することが，非常に重要になります．ひとつの間違いが，全体に波及して，完全に間違った結論に至る，なんてことは起こしてはいけません．Doctestは，関数の定義のすぐそばで，計算結果のチェックができるので，日ごろから積極的に使っています．もっとDoctestを使いやすくするために，自作プラグインを作成することにしました．

以下の記事では，

* [Windows 10にGVimベースのPython開発環境を構築する（１）インストール偏](http://qiita.com/daizutabi/items/72fa1c2f05d721e5cd48)
* [Windows 10にGVimベースのPython開発環境を構築する（２）基本設定偏](http://qiita.com/daizutabi/items/8b0156eba0ce6f7e9b50)

でVim環境を構築したことを前提としています．参考にされる方は適宜ご自身の環境に置き換えてください．

# レポジトリの作成とVim設定ファイルの更新

GitHubで新規にレポジトリを作成します．名前は`vim-greater3`としました．`C:\Users\daizu\Documents\Vim\plugin`下にクローンします．そして，新規に作成された`vim-greater3`ディレクトリの下に，`plugin`と`autoload`の2つのディレクトリを作成します．さらに，これら2つのディレクトリ各々の下に`greater3.vim`という名前で空のファイルを一つずつ作成します．

`C:\Users\daizu\vimfiles\dotfiles\_vimrc`内で，このプラグインを登録します．

```vim:_vimrc.vim
NeoBundleLazy 'vim-greater3', {
\   'base': '~/Documents/Vim/plugin',
\   'type': 'nosync',
\   'autoload': {
\       'mappings' : ['<Plug>(greater3)'],
\   }
\}
```

次に，`C:\Users\daizu\vimfiles\after\ftplugin\python.vim`に，キーマッピングを登録します．

```vim:ftplugin\python.vim
nmap <buffer> <F10> <Plug>(greater3)
```

Vimを立ち上げて，`:nmap`としてみます．F10キーのマッピングは見当たりません．次に適当なPythonファイルを開いて`:nmap`とすると，

```vim:
n <F10>    @<Plug>(greater3)
```

という行が見つかりました．ここまでは順調のようです．ここで，F10キーを押してみます．何も起こりませんでした．何かしらのエラーメッセージがでるものと予想していましたが，何も出ないのですね．

プラグインを書いていきましょう．`C:\Users\daizu\Documents\Vim\plugin\vim-greater3\plugin\greater3.vim`に以下の内容を書いて保存します．

```vim:plugin\greater3.vim
if exists('g:loaded_greater3')
  finish
endif

nnoremap <silent> <Plug>(greater3) :<C-U>echo 'greater3'<CR>

let g:loaded_greater3 = 1
```

今行った変更をすぐに反映させるために，`plugin\greater3.vim`を編集しているVimとは別にもうひとつVimを立ち上げます（以下の例でも同様です）．Pythonファイルを開いてF10キーを押すと，コマンドラインにgreater3と表示されるようになりました．PythonファイルをQuickRunで実行するのも簡単です：

```python:a.py
print('greater3!!!')
```
```vim:plugin\greater3.vim
nnoremap <silent> <Plug>(greater3) :<C-U>QuickRun<CR>
```

いよいよVimScriptを書いていきます．

```vim:plugin\greater3.vim
nnoremap <silent> <Plug>(greater3) :<C-U>call greater3#run()<CR>
```

PythonファイルでF10キーを押すと，コマンドラインに，

```vim:
E117: Unknown function: greater3#run
```

と表示されます．予想通りです．プラグインの実体を作成していきます．Vimでは，定義されていない関数を呼び出そうとすると，`autoload`ディレクトリ下を探索するそうです．`C:\Users\daizu\Documents\Vim\plugin\vim-greater3\autoload\greater3.vim`に以下の内容を書いて保存します．

```vim:autoload\greater3.vim
function! greater3#run()
  let l = line('.')
  echo l
endfunction
```

F10キーでカーソルの行番号が表示されるようになりました．

VimScriptとPythonを連携させていきましょう．「[はじめてのVimプラグイン作成（autopep8）～ GitHubの練習を兼ねて，既存レポジトリのフォーク／クローン／更新／プッシュを行ってみた](http://qiita.com/daizutabi/items/4862351b34bb045a3cb4)」で学んだことの実践です．VimScriptの中でPythonが使えるように，`autoload\greater3.vim`を以下の内容に書き換えます．

```vim:autoload\greater3.vim
function! greater3#run()
  let l = line('.')
py3 << EOF
l = int(vim.eval('l'))
vim.command('echo {}'.format(l))
EOF
endfunction

function! s:init_py_modules()
py3 << EOF
import vim
vim.command('echo "init_py_modules"')
EOF
endfunction

call s:init_py_modules()
```

Pythonファイルを開いてF10キーを押すと，

```vim:
init_py_modules
1
```

と表示されます．1は現在のカーソルがバッファの先頭にあるためですね．Pythonの`vim`モジュールの`vim.eval`関数，`vim.command`関数でPython内からVimにアクセスできることが分かります．この機能を使えばいろいろなことができそうです．ここで，もう一度F10キーを押してみます．すると，

```vim:
1
```

となりました．`s:init_py_modules`関数は，`autoload\greater3.vim`が読み込まれたときのみ実行されるので，2回目のF10キーで`init_py_modules`は表示されません．これは予想通りです．驚いたのが，`greater3#run`関数内では`vim`モジュールをインポートしていないにも関わらず，2回目の実行でも問題なく`vim`モジュールにアクセスできている点です．つまり，1回目のF10キーによる`vim`モジュールのインポートは有効のまま，言い換えれば，Pythonセッションが継続している，ということでしょうか．これはDoctestのプラグインを作るうえで希望が見えてきました．

なぜこのようなこと言うかというと，次のファイルをQuickRunで実行するとしましょう．科学技術計算をするPythonプログラムでの典型的な書き出しです．

```python:a.py
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
```
`:QuickRun`としてから実行が終わるまでちょっと時間がかかりますよね．2回目以降も同じくらい時間がかかっています．これは実行のたびに，ライブラリをインポートするためです．では，プラグインのほうで試してみます．`autoload\greater3.vim`を以下の内容に書き換えます．

```vim:autoload\greater3.vim
function! greater3#run()
py3 << EOF
vim.command('echo {:.6f}'.format(np.pi))
EOF
endfunction

function! s:init_py_modules()
py3 << EOF
import vim
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
vim.command('echo "init_py_modules"')
EOF
endfunction

call s:init_py_modules()
```

1回目の実行ではQuickRunと同じくらいの時間がかかりますが，2回目以降では瞬時に結果が返ってきます．すばらしい．Doctestをするたびにライブラリのインポートが終わるのを待つのは嫌ですから．

# まとめ

VimでPython Doctestを快適に行うことを目的としてVimプラグイン作成にチャレンジしています．本題に入る前ですが，長くなってきたので，一旦ここで終了します．今回の記事をまとめます．

* GitHubでVimプラグイン用レポジトリを作成し，ローカルにクローンしました．
* プラグインのひな形を作成して，F10キーで実行できるようにしました．
* プラグインの中でPythonを使えること，Vim環境と連携できること，を確認しました．
* プラグインの中でインポートしたPythonモジュールが，後から再度のインポートなしに利用可能であることが分かりました．その都度Python実行プロセスを立ち上げるQuickRunに対して実行速度の点で有利となることが期待されます．

次回は，Doctestを行うPythonモジュールを作成していきます．