ushumpei’s blog

生活で気になったことを随時調べて書いていきます。

【初めてプラグイン作って見た】Markdownのテーブル雛形を作るプラグイン

この記事は Vim Advent Calendar 2017 8日目の参加記事です。

こんにちは。プログラマーをやっています ushumpei と申します。Vim歴は3年くらいです。

今回は、そこそこVimを覚えてきたけれど、 Vimの仕組みをもっと深く理解していきたいと思っている人(自分)向けVim script勉強しました的な記事を書かせていただきました。自分がプラグインを作って見た時に感じたことがつらつらと書いてあります。

プラグインVimversion 8.0 で書きました。もし試していただけた方で、動かないよ!ということがあれば、大変恐縮ですがその旨コメントしていただけると幸いです。(ごめんなさい!バージョンごとの文法の違いは追えてません)

目次

動機

基本的な操作は覚えたけどもっとVimに関して詳しくなりたいというのが動機です。聞いた話によると、 Vim知るにはVim script書くのが早い ということなので勉強し始めました(すごい人たちは書いているし)。せっかくなのでplugin作ってみようと思い、今回はMarkdownのテーブルを作るプラグインを作成することにしました。同じ機能のプラグインはもうすでにいくつかありますが、世の中ほとんどのものはすでに作られているのでしょうがない、ということで練習のつもりで作りました。

リファレンス

基本的な文法については以下のリンクにお世話になりました。

作るもののイメージ

インタフェースとしては :Mdtable コマンドというものを作っていこうと思います。 Markdown table の短縮です。引数に行数、列数を渡すとその行数と列数を持ったテーブルをカーソル位置以降の行に挿入します。

"入力
:Mdtable 2 3

"出力
|   |   |   |
|:--|:--|:--|
|   |   |   |
|   |   |   |

またこのコマンド入力を補助するキーマッピング mdt もデフォルトで設定するようにします。

なので学べることとしては、 コマンドの定義と引数の渡し方キーマッピングカレントバッファへの書き込み引数チェック です。

書いて見た

書いて見ましたushumpei/mdtable-vim。ソースは2つだけで、ディレクトリ構成は以下のようになっています。

.
├── README.md
├── autoload
│   └── mdtable.vim
└── plugin
    └── mdtable.vim

試すには ~/.vim/plugin/ ディレクトリで git clone git@github.com:ushumpei/mdtable-vim.git とかしてもらえる嬉しいです( :echo &rtp でどこに置けばいいかわかるそうです )。 dein.vim とかを使っている方は call dein#add('ushumpei/mdtable-vim') とか書いてもらえてもやっぱり嬉しいです。

追記: 2017/12/09: Vim 8の場合、上記の方法ではなく、~/.vim/pack/mdtable/startディレクトリを作成して、その中でgit clone git@github.com:ushumpei/mdtable-vim.gitしてもらえればいいです。プラグインの配置についてまとめました: 自作Vimプラグインの置き場所と置き方 - ushumpei’s blog

それぞれのファイルの説明

  • autoload/mdtable.vim: 実際の処理を記述しています。 行数、列数を引数にテーブル文字列を生成する関数 と、 テーブル文字列生成関数を呼び出してバッファに書き込む関数 の2つだけです。
  • plugin/mdtable.vim: インタフェースを記述しています。上記の関数をコマンド、キーマップとして登録しています。

f:id:ushumpei:20171203120943g:plain

学んだことの詳細

コマンドの定義と引数の渡し方

if !exists(":Mdtable")
  command -nargs=* Mdtable :call mdtable#write(<f-args>)
endif

:Mdtable を使えるようにするには command でコマンドを定義するのですが、「引数2個だから -nargs=2 だ!」とか書いて若干ハマりました。 :help command-nargs をちゃんと読んでおけばよかったです。。。

キーマッピング

mapの設定方法は複雑に感じました、 :help 41.12 のマップの箇所で説明されているものを真似して書いていきました。

if !hasmapto('<Plug>Mdtable')
  nmap mdt <Plug>Mdtable
endif
nmap <Plug>Mdtable :Mdtable 
  • hasmapto: <Plug>Mdtable に対してマッピングが行われているか確認
  • 2行目はマッピングがなければデフォルトとしてmdtでマップ
  • 4行目は <Plug>Mdtable:Mdtable にマップ

このマップが二回行われる必要性が、ユーザーが独自にマッピングする場合の考慮、だということだとなかなか分からなかったです。<Plug> を使う意味はユーザーが独自にマッピングするためだとリファレンスにも書いてあるんですけどね!

カレントバッファへの書き込み

これは本当にいい方法だったのかわかっていないのですが、以下のように executenormal を使って記述しました。

" 変数tableにはマークダウンテーブル文字列が入っています
execute "normal o" . table . "\<Esc>"

バッファをスクリプトから触っている記事を見たりするのですが、まだそのあたり勉強不足です。

引数チェック

function! s:create(rowNum, colNum) abort
...
  if !(a:rowNum > 0) || !(a:colNum > 0)
    echoerr 'Arguments must be positive numbers.'
  endif

関数の引数をチェックして 正の数字以外を弾く ということがやりたかったのですが良い方法が見つけられなかったです。はじめは type 関数を使って見たのですが、全ての引数で弾かれるようになってしまい軽く混乱、結局 引数は文字列で渡ってくる という話でした。最終的に、 数値との比較の際に文字列が数値に変換される こと、 文字列がうまく数値に変換できないときは0になる こと、の2つを使ってゆるいチェックをおこなっています。

Vim scriptは途中でエラー出ても止まらない力強いスクリプトなので、間違った入力でもなんらかの出力をしてくれちゃうみたいです。書くにあたってそのあたりのエラーハンドリングのために tryabort を使って見ました。

感想

Vim少し使える」と思っていたのですが、全然知らないことが多くて驚いています。 プラグインを書いた後に自分の .vimrc を見返してみると、今まで曖昧で済ましていた部分が違って見えてとても嬉しい です。

僕はもともと、Vim自体の使い勝手を変えないように、プラグインは使わないようにしていたのですが、今回プラグインを書いたことで、各プラグインがどのようにVimに影響を与えているのか少しわかるようになったため、これからは多少使っていってもいいという気持ちになりました。

これからのことで言えば、もう少し実際に役に立つ機能をかいて行きたい感じです。このプラグインで言えば、多分テーブルとか最初からサイズ決めうちより調節機能とかあったほうがいいとか思います。(まあでも、他のプラグインを色々触ってみることから始めます)

拙い文章、内容になりました。 読んでいただいてありがとうございます!!!

Vim Advent Calendar 2017