かなりすごいブログ

automatic.vimでウィンドウの一時的分割をハンドリングしよう

Vim Advent Calendar 2012、264日目の記事になります。

本日は、automatic.vimというプラグインを使って、Vimにおける「ウィンドウの一時的分割」をハンドリングする方法をお伝えします。

automatic.vimとは

私が「popwin.el」のVim版欲しいなーとつぶやいたらおしょーさんが作ってくれたプラグインです。感謝。

automatic.vimは、ウィンドウの状態変化をフックとして人気の処理を実行するための、薄いライブラリです。ここでいう「ウィンドウ」とは、Vim用語のウィンドウです。例えば、Vimの画面が二分割されている場合、Vimにはウィンドウが2つ存在する状態となります。

automatic.vim自体の詳しい説明や設定方法については、作者のおしょーさんがブログで解説してくれていますので、記事「Vim で指定したウィンドウに対して任意の設定を行うプラグインをつくった」をご覧ください。

今回の記事では、automatic.vimを用いて、「ウィンドウの一時的分割」をハンドリングするノウハウをお伝えします。

ウィンドウの「一時的分割」とは

この記事における「一時的分割」のウィンドウとは、「何らかの操作のために分割したが、その目的が終わったらすぐ閉じるウィンドウ」を指します。たとえば、以下の様なシーンにおける分割ウィンドウは、一時的分割のウィンドウです。

  • :Unite fileで開き、ファイルを選択してすぐ閉じるUniteの分割ウィンドウ
  • :VimShellPop -toggleで開き、少し操作をした後再び:VimShellPop -toggleで閉じるVimShellの分割ウィンドウ
  • q:で開き、コマンド実行後自動で閉じるCmdWinの分割ウィンドウ

逆に、以下の様なシーンにおける分割は、一時的分割ではありません

  • :spもしくは:vspし画面を分割し、二つのファイルのソースコードを見比べている

automatic.vimによってできること

automatic.vimを使用することで、これらの一時的分割ウィンドウの出現位置やサイズなどを、一元化して管理することができます。 また、automatic.vimにはapplyという仕組みがあり、任意の関数をこれらのウィンドウにおいて実行させることができ、これを活用することで一時的ウィンドウに共通したキーバインドや設定を定義することが可能となります。

一時的ウィンドウハンドリングの設定例

nnoremap <silent> <plug>(quit) :<c -u>q<cr>
function! s:my_temporary_window_init(config, context)
  nmap <buffer> <c -[>  <plug>(quit)
endfunction

let g:automatic_default_match_config = {
      \   'is_open_other_window' : 1,
      \ }
let g:automatic_default_set_config = {
      \   'height' : '60%',
      \   'move' : 'bottom',
      \   'apply' : function('s:my_temporary_window_init')
      \ }
let g:automatic_config = [
      \   { 'match' : { 'buftype' : 'help' } },
      \   { 'match' : { 'bufname' : '^.vimshell' } },
      \   { 'match' : { 'bufname' : '^.unite' } },
      \   {
      \     'match' : {
      \       'filetype' : '\v^ref-.+',
      \       'autocmds' : [ 'FileType' ]
      \     }
      \   },
      \   {
      \     'match' : {
      \       'bufname' : '\[quickrun output\]',
      \     },
      \     'set' : {
      \       'height' : 8,
      \     }
      \   },
      \   {
      \     'match' : {
      \       'autocmds' : [ 'CmdwinEnter' ]
      \     },
      \     'set' : {
      \       'is_close_focus_out' : 1,
      \       'unsettings' : [ 'move', 'resize' ]
      \     },
      \   }
      \ ]

g:automatic_default_set_configheightmogeはそのまんま、位置とサイズの指定です。これによって、特に個別指定しない場合に関しては、高さがVim全体の60%、位置が下で分割が行われるようになります。QuickRunウィンドウはもう少し小さめで表示したいので、個別設定で8行分の高さとなるように設定、特殊なウィンドウであるCmdWinは、リサイズや移動ができず、行おうとするとエラーが発生してしまうため、unsettingsオプションでmoveresizeheightおよびwidthに該当)のデフォルト設定を無効化しています。

ここまではautomatic.vimのヘルプにも記載されている一般的な設定なのですが、キモはapplyオプションで指定しているs:my_temporary_window_init関数になります。

この関数は、該当バッファ内のみで有効なマッピングとして、「ノーマルモードにおけるC-[:qを実行する」というものを定義するというものです。この関数を適用することで、一時的分割のウィンドウは、どのような状態からでもC-[を連打すれば閉じることができ、分割状態からニュートラルなウィンドウ分割状態へと戻ることが出来る様になります。さながら、Emacsの「とりあえず困ったらC-g連打」の様な操作感です。

この設定は地味なものの、かなり強力です。私はもうautomatic.vimなしのVimは使えなくなってしまいました。みなさんも、ぜひお試しください。

おまけ

なぜわざわざ<Plug>(quit)を定義しているんだ?と思われる方もいると思いますので解説しておきます。

私は:q:iにマッピングしており、:を打鍵するとCmdWinが開くような設定をしています。ですので直接nmap <buffer> <C-[> :<C-u>q<CR>といったマッピングを行うと、実際はq:i<C-u>q<CR>と入力した際と同じ様な動作になってしまいます。こういった場合、この様にマッピングを経由させて本来の:を入力するというテクニックが使えます。