koturnの日記

普通の人です.ブログ上のコードはコピペ自由です.

タブ番号,バッファ番号,ウィンドウ番号,ウィンドウIDの相互変換

はじめに

先日,「Vimで既に対象バッファを開いているウィンドウがあるとき,そのウィンドウに移動する」「Vimのタブ番号,ウィンドウ番号,ウィンドウID,バッファ番号の一覧情報を表示する」の2記事で,タブ番号,バッファ番号,ウィンドウ番号,ウィンドウIDを取り扱った. そして,Vim scriptの組み込み関数で,これらの相互変換を行った. しかし,単純に変換する関数が用意されているわけではなく,そこそこ工夫が必要だったので,この忘備録として,この記事にまとめようと思う.

Vimのタブ番号,ウィンドウ番号,ウィンドウID,バッファ番号について

Vimのタブ番号,ウィンドウ番号,ウィンドウID,バッファ番号について,簡単にまとめる.

種類 説明
タブ番号 :tabnew 等でオープンされたタブに振られる番号.左から1, 2, 3, ...と振られ,タブ消去,タブ移動を行うと振り直される
ウィンドウ番号 1つのタブにおけるウィンドウの番号.ウィンドウの移動,消去で振り直される
ウィンドウID タブに関係無く,1つのウィンドウに一意に割り当てられる番号.
バッファ番号 バッファに関連付けられた番号.バッファを削除しても振り直しは行われないため,IDとして扱うことも可能.

ウィンドウIDについては,Vim8.0からの機能であり,thincaさんのVim 8.0 Advent Calendar 7 日目 ウィンドウ IDという記事で解説されている.

相互変換表

本題の相互変換について,簡単に表にまとめた. 変換先が一意ではなく,複数あるものは「(複数)」と表記してある.

個別の詳細は,次の章で紹介する.

変換元 変換先 手法 制限
bufnr winnr(複数) buf_winnr() / win_findbuf()win_id2tabwin() から逆引き辞書作成 前者は同一タブ最初のウィンドウのみ
bufnr winid(複数) bufwinid() / win_findbuf() 前者は同一タブ最初のウィンドウのみ
bufnr tabnr(複数) tabpagebuflist() から逆引き辞書作成
winnr bufnr winbufnr() / win_getid()win_findbuf() 前者は同一タブのみ
winnr winid win_getid()
winnr tabnr 無意味であるため省略
winid bufnr win_findbuf() から逆引き辞書を作成
winid winnr win_id2tabwin()
winid tabnr win_id2win() / win_id2tabwin() 前者は同一タブのみ
tabnr bufnr(複数) tabpagebuflist()
tabnr winnr(複数) tabpagewinnr()
tabnr winid(複数) tabpagewinnr()win_getid()

個々の変換手法

bufnr から winnr (複数)

指定したバッファ番号のバッファを開いているウィンドウの番号を得る.

buf_winnr() だと同一タブ内における若い番号のウィンドウ番号を1つしか取得することができない. 複数個取得しようと思うと, win_findbuf()win_id2tabwin() で逆引き辞書を作成しておく必要がある.

まず,ウィンドウ番号は個々のタブで1から始まるので,インプットとしてはバッファ番号の他にタブ番号も受け取るものとする. (タブ番号は省略可能で,省略時はカレントタブの番号を受け取ったものとする)

function! s:create_bufnr2tabwin_dict() abort " {{{
  let bufnr2tabwin_dict = {}
  for bnr in filter(range(1, bufnr('$')), 'bufexists(v:val)')
    let bufnr2tabwin_dict[bnr] = map(win_findbuf(bnr), 'win_id2tabwin(v:val)')
  endfor
  return bufnr2tabwin_dict
endfunction " }}}

function! s:bufnr2winnr(bnr, ...) abort " {{{
  if bufexists(a:bnr)
    throw 'E86 Buffer ' . a:bnr . 'does not exist'
  endif
  let tnr = a:0 > 0 ? a:1 : tabpagenr()
  return map(filter(s:create_bufnr2tabwin_dict()[a:bnr], 'v:val[0] == tnr'), 'v:val[1]')
endfunction " }}}

bufnr から winid (複数)

指定したバッファ番号のバッファを開いているウィンドウのIDを得る.

win_findbuf() を呼び出すだけでよい.

function! s:bufnr2winid(bnr) abort " {{{
  return win_findbuf(a:bnr)
endfunction " }}}

bufnr から tabnr (複数)

指定したバッファ番号のバッファを開いているウィンドウが存在するタブページの番号を得る.

tabpagebuflist() でtabnrからbufnrのリストが得られるので,逆引き辞書を作成する.

function! s:create_bufnr2tabnr_dict() abort " {{{
  let bufnr2tabnr_dict = {}
  for tnr in range(1, tabpagenr('$'))
    for bnr in tabpagebuflist(tnr)
      let bufnr2tabnr_dict[bnr] = has_key(bufnr2tabnr_dict, bnr) ? add(bufnr2tabnr_dict[bnr], tnr) : [tnr]
    endfor
  endfor
  for val in values(bufnr2tabnr_dict)
    call uniq(sort(val))
  endfor
  return bufnr2tabnr_dict
endfunction " }}}

function! s:bufnr2tabnr(bnr) abort " {{{
  return s:create_bufnr2tabnr_dict()[a:bnr]
function " }}}

bufnr から winnrの事例のように,win_findbuf()win_id2tabwin() で逆引き辞書を作ってもよい.

function! s:create_bufnr2tabwin_dict() abort " {{{
  let bufnr2tabwin_dict = {}
  for bnr in filter(range(1, bufnr('$')), 'bufexists(v:val)')
    let bufnr2tabwin_dict[bnr] = map(win_findbuf(bnr), 'win_id2tabwin(v:val)')
  endfor
  return bufnr2tabwin_dict
endfunction " }}}

function! s:bufnr2winnr02(bnr) abort " {{{
  if bufexists(a:bnr)
    throw 'E86 Buffer ' . a:bnr . 'does not exist'
  endif
  return map(s:create_bufnr2tabwin_dict()[a:bnr], 'v:val[0]')
endfunction " }}}

winnr から bufnr

指定したウィンドウ番号のウィンドウが開いているバッファの番号を得る.

まず,ウィンドウ番号からウィンドウIDに変換し,後述するウィンドウIDからバッファ番号の辞書を作成する手法を利用する.

function! s:create_winid2bufnr_dict() abort " {{{
  let winid2bufnr_dict = {}
  for bnr in range(1, bufnr('$'))
    for wid in win_findbuf(bnr)
      let winid2bufnr_dict[wid] = bnr
    endfor
  endfor
  return winid2bufnr_dict
endfunction " }}}

function! s:winid2tabnr(wid, ...) abort " {{{
  return a:0 > 0 ? s:create_winid2bufnr_dict()[win_getid(a:wid, tnr)] : winbufnr(a:wid)
endfunction " }}}

winnr から winid

指定したウィンドウ番号のウィンドウIDを得る.

当然,タブを指定する必要はあるよねという話. win_getid() を呼び出すだけ(タブ番号を省略すると,カレントタブのウィンドウ番号からウィンドウIDを引くことになる). 関数を呼び出すだけなので,自作関数を定義する必要はないが,とりあえず作っておく.

function! s:winnr2winid(wnr, ...) abort " {{{
  return a:0 > 0 ? win_getid(a:wnr, a:1) : win_getid(a:wnr)
endfunction " }}}

winid から tabnr

例えば,「番号2のウィンドウ」を持つタブのリストを作成することはできるが意味が無いので割愛. それは,2個以上のウィンドウに分割されているタブの検索にしかならない.

winid から bufnr

指定したウィンドウIDのウィンドウで開かれているバッファの番号を得る.

直接取得できる関数は無いため, winid2bufnr_dict() を利用して,winidからbufnrの逆引き辞書を作成する. (ループして一致したらreturnというスタンスでもよいが,他への転用がしんどいので辞書を作成する)

function! s:create_winid2bufnr_dict() abort " {{{
  let winid2bufnr_dict = {}
  for bnr in range(1, bufnr('$'))
    for wid in win_findbuf(bnr)
      let winid2bufnr_dict[wid] = bnr
    endfor
  endfor
  return winid2bufnr_dict
endfunction " }}}

function! s:winid2bufnr(wid) abort " {{{
  return s:create_winid2bufnr_dict()[a:wid]
endfunction " }}}

winid から winnr

指定したウィンドウIDのウィンドウの番号を得る.

win_id2tabwin() を呼び出すだけでよい.

function! s:winid2tabnr(wid) abort " {{{
  return win_id2tabwin(a:wid)[1]
endfunction " }}}

winid から tabnr

指定したウィンドウIDのウィンドウを持つタブページの番号を得る.

win_id2win() は同一タブ内のウィンドウしか検索対象にしないため,1つ前と同じく win_id2tabwin() を利用する. そもそも,ウィンドウ番号はタブと共に無いとあまり意味がないので, win_id2tabwin() の結果を個々に取得するのは微妙だろう.

function! s:winid2tabnr(wid) abort " {{{
  return win_id2tabwin(a:wid)[0]
endfunction " }}}

tabnr から bufnr (複数)

指定したタブページ番号ののバッファ番号のリストを得る.

単に関数を呼び出すだけでよい.

function! s:tabnr2bufnr_list(tnr) abort " {{{
  return tabpagebuflist(a:tnr)
endfunction " }}}

tabnr から winnr (複数)

指定したタブページ番号のウィンドウ番号のリストを得る.

ウィンドウIDはウィンドウ削除時に振り直されるため,必ず1, 2, 3, ...という風に整数が連続するので,最後のウィンドウ番号を得るとよい.

function! s:tabnr2winid_list(tnr) abort " {{{
  return range(1, tabpagewinnr(a:tnr, '$'))
endfunction " }}}

tabnr から winid (複数)

指定したタブページ番号のウィンドウIDのリストを得る.

function! s:tabnr2winid_list(tnr) abort " {{{
  return map(range(1, tabpagewinnr(a:tnr, '$')), 'win_getid(v:val, a:tnr)')
endfunction " }}}

まとめ

個人的なメモとして,タブ番号,バッファ番号,ウィンドウ番号,ウィンドウIDの相互変換についてまとめた. 今後,.vimrcに書く設定やプラグインの作成に活かすことがあるかもしれない.