読者です 読者をやめる 読者になる 読者になる

at kaneshin

Free space for me.

Vimプラグインの拡張機能プラグインを作ってVimをさらに使いやすくしよう

Vim

この記事はVim Advent Calendar 2012 の 31 日目の記事です。

2012年も今日で終わりですね。12月は忙しかったので気付いたら年末でした。

最近はXcodeばかりを使用していたのでVimエディタからは離れていましたが、XcodeVimライクにするプラグインのXVimを使ってました。XVim最高ですね!

@jugglershuさんの27日目の記事で知ったのですが、ViciousというXcodeのプラグインもあるらしいです。

でも、私はXVimを使うでしょう。なぜなら、ソースコードが公開されているから。

今度、時間があれば、気になったところを修正してpull requestしたいものです。

さてさて本題です。皆さんはVimを使ってるときに欠かせないプラグインがそれぞれあると思います。

私の場合だとctrlp.vimがそれにあたるプラグインです。

このようなプラグインは他に unite.vim がありますが、github.com を見る限り ctrlp.vim は相当使われているプラグインです。

今回、その ctrlp.vim の機能を拡張させる拡張プラグインの作り方を説明します。

ctrlp.vim の拡張プラグインの作り方

本家の extensions ブランチに sample.vim があります。

以下の説明は大体、このサンプルを基に作っております。

また、今回、Vim advent calendarのために軽いものを作ってみました。

gitのログをCtrlPの結果に出して、ログを選択するとそのコミットの詳細を見れる簡単な拡張機能です。

中身はこんな感じになってます。

ctrlp-git-log
├── README.mkd
├── autoload
│   └── ctrlp
│       └── git_log.vim
└── plugin
    └── ctrlp-git-log.vim

autoloadフォルダにあるファイル (git_log.vim) でCtrlPの拡張機能を実現しています。

pluginフォルダではautoloadを呼び出すコマンドを定義しているだけです。

では、autoloadのファイル (git_log.vim) を説明していきます

autoload/ctrlp/git_log.vim

拡張機能の設定

拡張機能の設定を記述します。

作ったプラグインだと s:git_log_var です。これを g:ctrlp_ext_vars に加えます。

let s:git_log_var = {
  \ 'init':   'ctrlp#git_log#init()',
  \ 'accept': 'ctrlp#git_log#accept',
  \ 'lname':  'git-log',
  \ 'sname':  'git-log',
  \ 'type':   'line',
  \ 'enter':  'ctrlp#git_log#enter()',
  \ 'exit':   'ctrlp#git_log#exit()',
  \ 'sort':   0,
  \ }

if exists('g:ctrlp_ext_vars') && !empty(g:ctrlp_ext_vars)
  let g:ctrlp_ext_vars = add(g:ctrlp_ext_vars, s:git_log_var)
else
  let g:ctrlp_ext_vars = [s:git_log_var]
endif

この設定では必ず必要なものと、オプションで必要ならば記述するものがあります。

それが下記となります。

  • Required

    • init: ctrlp#git_log#init()
    • accept: ctrlp#git_log#accept
    • lname: long statusline name
    • sname: short name
    • type: matching type
  • Optional

    • enter: ctrlp#git_log#enter()
    • exit: ctrlp#git_log#exit()
    • opts: ctrlp#git_log#opts()
    • sort: 0

init, accept, enter, exit そして opts には関数を定義します。

残りは、それぞれ値を代入します。

Required

init: ctrlp#git_log#init()

initには関数を定義し、その中にCtrlPで表示して欲しいリストをreturnします。

今回作ったプラグインでは、gitのコミットログ1つずつをctlrpの1行に表示させるためにgit-logにいろいろとオプションをつけています。

function! ctrlp#git_log#init()
  let s:log = split(system('git log --oneline -50 --pretty=format:"%h | %cr, %an - %s"'), "\n")
  return s:log
endfunc

1行に整形させたコミットログを改行で分割したリストにしてそれをreturnしています。

ちなみに、git-logオプションの説明

  • git log --oneline -50 --pretty=format:"%h | %cr, %an - %s"
    • --oneline => 1行で出力(デフォルトだとコミットハッシュとコミットメッセージのみ)
    • -50 => ログは最新から遡って最高で50コミットだけ取得
    • --pretty=format:"%h | %cr, %an - %s" => ハッシュ | 現在からの相対時間, コミットした人 - コミットメッセージ

accept: ctrlp#git_log#accept

initによりCtrlPで表示させたコミットログのリストを選択したときに起動します。

function! ctrlp#git_log#accept(mode, str)
  call ctrlp#exit()
  let hash = substitute(a:str, "^\\(.\\+\\)\\s|.*$", "\\1", "")
  echo system('git log '.hash.'~..'.hash.' --stat -p')
endfunction

modeには選択するときに押されたキーによってそれぞれ値が代入されます。

そのmodeによって処理分けをしたりしますが、大抵のプラグインでは何もしていないと思います。

mruやfile を開くデフォルトのものだと

  • mode
    • <cr> => e: バッファ
    • <c-v> => v: 縦分割のウィンドウ
    • <c-t> => t: 新しいタブ
    • <c-x> => h: 横分割のウィンドウ

strには、選択された文字列がそのまま入ります。

git_log.vim では選択したgitログのコミットハッシュを置換によって整形し、取得しています。

そのハッシュを使って git log を該当のコミットの詳細を出力しています。

ちなみに、acceptの最初にある

call call#exit()

で、ctrlpを終了してからそれぞれの処理をしています。

lname & sname

lnameはステータスラインに表示されるロングネーム、snameはショートネームのことです。 今回はそのまま、 git-log にしました。

type: matching type

typeは、ctrlp実行時のリストに入力した文字列をどの範囲でマッチングさせるかを指定します。

  • line: 1行全てに対してマッチング
  • path: ファイルやディレクトリパスのような1行全てに対してマッチング
  • tabs: 最初のタブ文字までのマッチング
  • tabe: 最後のタブ文字までのマッチング

よくわからなければ line を選んでしまって問題ないです。

type に tabs を設定して、CtrlP で下記のように1行の中身がタブで区切られているリストが出力されているとき、bar を入力すると 'bar baz foo' のみマッチングします。

foo	bar	baz
bar	baz	foo
baz	foo	bar

Optional

enter: ctrlp#git_log#enter()

initより先に呼ばれます。

たまに、initの中では取得できない値なんかをctrlp#foo#id()の中で定義している拡張プラグインがありますが、そういったときにctrlp#foo#enter()内で定義します。

編集中のファイルタイプなんかは、init中では取得できません。

今回のプラグインではこのように記述してもOKです。

function! ctrlp#git_log#init()
  return s:log
endfunc

function! ctrlp#git_log#enter()
  let s:log = split(system('git log --oneline -50 --pretty=format:"%h | %cr, %an - %s"'), "\n")
endfunc

exit: ctrlp#git_log#exit()

CtlPを終える前にしたい処理を書くのですが、今まで使ったことがないです。

なにか後処理をしたいときに使用してください。

opts: ctrlp#git_log#opts()

初期化されるときに呼ばれる関数で、オプションを設定するなどをすべきようです。

これも使ったことがない…

sort: 0

sort を設定しない場合、CtrlPには結果がソートされたものが出力されます。

git_log.vimではgitのログについてはソートされていない方がコミットを追えるのでソートをオフにしています。

ctrlp#git_log#id()

CtrlPのおまじない

let s:id = g:ctrlp_builtins + len(g:ctrlp_ext_vars)
function! ctrlp#git_log#id()
  return s:id
endfunction

これで、拡張プラグインが作成できます。

コマンドは.vimrcに定義してもいいのですが、せっかくなのでpluginのフォルダに ctrlp-git-log.vim のようなファイルを作り

command! CtrlPGitLog cal ctrlp#init(ctrlp#git_log#id())

のように定義します。

これで、:CtrlPGitLog とコマンドを打てば使用できますし、.vimrc に

let g:ctrlp_extensions = [
      \'git_log'
      \]

を追加してあげると、<c-p> で起動したときに <c-f> or <c-b> で表示されるようになります。

実際に起動してみるとこのように出力されます

f:id:laplus-knsn:20121230070404p:plain

どれかを選択すると

f:id:laplus-knsn:20121230070412p:plain

と git log --stat -p の結果が見れます。

特定のコミットハッシュのログに限定するために

git log hash~..hash

のように指定しているので、Initial commitを選択するとエラーが出ます。

f:id:laplus-knsn:20121230070418p:plain

特定のコミットハッシュのログを見る違う方法って他にないんですかね?

終わりです

これで、CtrlPの拡張プラグインの書き方がわかると思います。

これからVim pluginを書いてみたい!と、思っている人。

プラグインの拡張プラグインならすごく簡単に書けるのでオススメです。

というより、CtrlPはオススメですのでぜひ使って下さい。

使っているうちに、あの結果を手軽くやりたいと思うので、その時にこの記事を再度眺めてみて下さい。

Let's create Vim plugins or extensions!!

では、今年も24時間切っておりますので、来年もよろしくお願いします!

次回のVim Advent Calendar

2013年最初のVim Advent Calendar 2012 は@tavi4444さんです。