Vim のタブページ番号とウィンドウ番号について

この記事は Vim Advent Calendar 2012 305日目の記事になります。


id:cohama さんの記事の余談というか補足というか、ちょっとウィンドウ、タブページ番号について書いてみました。

[バッファ番号とウィンドウ、タブページ番号の違い]

バッファ番号とウィンドウ、タブページ番号は似ているようなイメージですが実際には少し違います。
バッファ番号はバッファを開いた時に順番にナンバリングされてバッファと番号が1対1
一方、ウィンドウ、タブページ番号は各ウィンドウ、タブページに対して番号が割り振られるのではなくて、ウィンドウ、タブページの位置によって番号が決まります。
例えば、ウィンドウ、タブページ番号は以下のような感じになります。



ですので、ウィンドウの位置を入れ替えても番号が入れ替わるわけではなくて、ウィンドウ番号はそのままです(何が開かれているウィンドウなのかは関係ない。

[どのような問題が起こるか]

これによって問題が起こる場合があります。
例えば、id:cohama さんが記事内で使われていた関数をちょっと変更して次のように
カレントのタブページで指定した filetype のウィンドウをすべて閉じる
というような処理にしてみました。

function! CloseWindowAll(filetype)
  for w in range(1, winnr('$'))
    let ft = getwinvar(w, '&filetype')
    if ft ==# a:filetype
      execute w . 'wincmd w'
      q
    endif
  endfor
endfunction

" filetype=help をすべて閉じる
" call CloseWindowAll("help")


これで動作しそうな感じなのですが、実際にはうまく動作しません。
なぜならこの書き方では『ウィンドウを閉じるたびにウィンドウ番号が変わってしまう』からです。
これを回避するためには次のように書き直す必要があります。

function! CloseWindowAll(filetype)
  for w in range(1, winnr('$'))
    let ft = getwinvar(w, '&filetype')
    if ft ==# a:filetype
      execute w . 'wincmd w'
      q
      " もう1度最初から捜査する
      call CloseWindowAll(a:filetype)
    endif
  endfor
endfunction

" filetype=help をすべて閉じる
" call CloseWindowAll("help")


これであれば問題なく動作します。
また、『一時的にウィンドウ番号を保持して、あとからそのウィンドウ番号を使用する』というような処理を行う場合にも注意が必要です。
ウィンドウ番号を取得してからウィンドウ番号を使用するまでの間にウィンドウレイアウトが変更されていれば意図しないウィンドウが対象になる可能性が高いです。
これはタブページ番号も同じで、ウィンドウ、タブページ番号を扱う場合にはこれらのことに注意する必要があります。

[gift.vim]

id:cohama さんの記事でもちらっと出てきましたが、上記のような問題を解決する手段として gift.vim というプラグインをつくっていたりします。

NeoBundle "osyo-manga/vim-gift"


このプラグインを使用すればバッファ番号のようにウィンドウやタブページごとの固有番号を取得する事ができます。
例えば、さっき問題になった CloseWindowAll() で gift.vim を使用すると次のような使い方になります。

function! CloseWindowAll(filetype)
  " 固有のウィンドウ番号を使用
  let uniq_winnrs = map(range(1, winnr('$')), "gift#uniq_winnr(v:val)")
  for w in uniq_winnrs
    let ft = gift#getwinvar(w, '&filetype')
    if ft ==# a:filetype
      " 閉じるときはウィンドウ番号に変換してから使用
      let winnr = gift#winnr(w)
      execute winnr . 'wincmd w'
      q
    endif
  endfor
endfunction


一度、固有のウィンドウ番号変換してから走査し、閉じるときにはウィンドウ番号に戻して閉じます。
ちなみに gift.vim にはウィンドウと閉じる関数もあって、それを使用すれば次のような書き方もできます。

" filetype=vim を閉じる
call gift#close_window_by("tabnr == tabpagenr() && getbufvar(bufnr, '&filetype') ==# 'vim'")


gift#close_window_by() に閉じるウィンドウの条件を渡せばそのウィンドウが閉じられます。

[おまけ]

ちなみに unite.vim ではタブページで開かれたバッファの管理をしています。
unite-buffer_tab でそのタブページで開かれたバッファの一覧が出てくるので気になる方は使ってみるといいと思います。