【初めてプラグイン作って見た】Markdownのテーブル雛形を作るプラグイン
この記事は Vim Advent Calendar 2017 8日目の参加記事です。
こんにちは。プログラマーをやっています ushumpei と申します。Vim歴は3年くらいです。
今回は、そこそこVimを覚えてきたけれど、 Vimの仕組みをもっと深く理解していきたいと思っている人(自分)向け のVim script勉強しました的な記事を書かせていただきました。自分がプラグインを作って見た時に感じたことがつらつらと書いてあります。
プラグインはVimの version 8.0 で書きました。もし試していただけた方で、動かないよ!ということがあれば、大変恐縮ですがその旨コメントしていただけると幸いです。(ごめんなさい!バージョンごとの文法の違いは追えてません)
目次
動機
基本的な操作は覚えたけどもっとVimに関して詳しくなりたいというのが動機です。聞いた話によると、 Vim知るにはVim script書くのが早い ということなので勉強し始めました(すごい人たちは書いているし)。せっかくなのでplugin作ってみようと思い、今回はMarkdownのテーブルを作るプラグインを作成することにしました。同じ機能のプラグインはもうすでにいくつかありますが、世の中ほとんどのものはすでに作られているのでしょうがない、ということで練習のつもりで作りました。
リファレンス
基本的な文法については以下のリンクにお世話になりました。
- Vimスクリプト基礎文法最速マスター: 可変長引数、分割代入素敵
- usr_41 - Vim日本語ドキュメント:
execute
、normal
コマンドとか整理しなければ。range
キーワードとか面白い - モテる男のVim script短期集中講座Add Star: 辞書は関数モテる。try..catch、abortでエラーハンドリング。mapの文法むずい
- usr_41 - Vim日本語ドキュメント | プラグインを書く: mapの文法怖い
- usr_41 - Vim日本語ドキュメント | ライブラリスクリプトを書く: autoloadについて
作るもののイメージ
インタフェースとしては :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: インタフェースを記述しています。上記の関数をコマンド、キーマップとして登録しています。
学んだことの詳細
コマンドの定義と引数の渡し方
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>
を使う意味はユーザーが独自にマッピングするためだとリファレンスにも書いてあるんですけどね!
カレントバッファへの書き込み
これは本当にいい方法だったのかわかっていないのですが、以下のように execute
と normal
を使って記述しました。
" 変数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は途中でエラー出ても止まらない力強いスクリプトなので、間違った入力でもなんらかの出力をしてくれちゃうみたいです。書くにあたってそのあたりのエラーハンドリングのために try と abort を使って見ました。
感想
「Vim少し使える」と思っていたのですが、全然知らないことが多くて驚いています。 プラグインを書いた後に自分の .vimrc
を見返してみると、今まで曖昧で済ましていた部分が違って見えてとても嬉しい です。
僕はもともと、Vim自体の使い勝手を変えないように、プラグインは使わないようにしていたのですが、今回プラグインを書いたことで、各プラグインがどのようにVimに影響を与えているのか少しわかるようになったため、これからは多少使っていってもいいという気持ちになりました。
これからのことで言えば、もう少し実際に役に立つ機能をかいて行きたい感じです。このプラグインで言えば、多分テーブルとか最初からサイズ決めうちより調節機能とかあったほうがいいとか思います。(まあでも、他のプラグインを色々触ってみることから始めます)
拙い文章、内容になりました。 読んでいただいてありがとうございます!!!