koturnの日記

転職したい社会人2年生の技術系日記

Vimで既に対象バッファを開いているウィンドウがあるとき,そのウィンドウに移動する

はじめに

僕はVimのタブ機能をそこそこ活用する方である. だが,「このバッファは既に開いている」ということを忘れることが多々あり,複数のタブで同じバッファを開くことがある.

Vim8.0になり, win_gotoid() というVim scriptの関数が追加された. これは,指定したIDのウィンドウにフォーマスを当てるという関数であり,まさに「Vimで既に対象バッファを開いているウィンドウがあるとき,そのウィンドウに移動する」という機能に持ってこいである.

そこで,そういった機能を実現するコマンドを考えてみた.

実装

何はともあれ,実装は以下のようになる. win_gotoid() はVim8.0からの機能であるため,Vim7.3, Vim7.4向けの実装も用意しておく.

if exists('*win_gotoid')
  function! s:buf_open_existing(qmods, bname) abort " {{{
    let bnr = bufnr(a:bname)
    if bnr == -1
      echoerr 'Buffer not found:' a:bname
      return
    endif
    let wids = win_findbuf(bnr)
    if empty(wids)
      execute a:qmods 'new'
      execute 'buffer' bnr
    else
      call win_gotoid(wids[0])
    endif
  endfunction " }}}
  command! -bar -nargs=1 -complete=buffer Buffer  call s:buf_open_existing(<q-mods>, <f-args>)
else
  function! s:buf_open_existing(bname) abort " {{{
    let bnr = bufnr(a:bname)
    if bnr == -1
      echoerr 'Buffer not found:' a:bname
      return
    endif
    let tindice = map(filter(map(range(1, tabpagenr('$')), '{"tindex": v:val, "blist": tabpagebuflist(v:val)}'), 'index(v:val.blist, bnr) != -1'), 'v:val.tindex')
    if empty(tindice)
      new
      execute 'buffer' bnr
    else
      execute 'tabnext' tindice[0]
      execute bufwinnr(bnr) 'wincmd w'
    endif
  endfunction " }}}
  command! -bar -nargs=1 -complete=buffer Buffer  call s:buf_open_existing(<f-args>)
endif

コマンドとしては,以下のように使用する. <バッファ名> はTabキーで補完可能である.

:Buffer <バッファ名>

バッファが :hide 等で隠れている場合は,新たにウィンドウを作成して,開き直す. Vim8.0からは,Exコマンド定義において <mod> が利用できるようになったため, :topleft:botright と併用することも可能になっている.

ターミナルへの応用

Vim8.0からは :terminal が実装された. この :terminal についても,複数タブを開いている場合,必要ないのに新たにターミナルを立ち上げてしまうかもしれない. そこで,前述と同様,ターミナルが既に起動していれば,そこにフォーカスを当てるコマンドを考えた.

実は,バンビちゃん氏のVim で :terminal の使い勝手をよくしたという記事に影響を受けている.

if has('terminal')
  function! s:complete_term_bufname(arglead, cmdline, cursorpos) abort " {{{
    let arglead = tolower(a:arglead)
    return filter(map(term_list(), 'bufname(v:val)'), '!stridx(tolower(v:val), arglead)')
  endfunction " }}}

  function! s:term_open_existing(qmods, ...) abort " {{{
    if a:0 == 0
      let bnrs = term_list()
      if empty(bnrs)
        execute a:qmods 'terminal'
      else
        let wids = win_findbuf(bnrs[0])
        if empty(wids)
          terminal
        else
          call win_gotoid(wids[0])
        endif
      endif
    else
      let bnr = bufnr(a:1)
      if bnr == -1
        throw 'E94: No matching buffer for ' . a:1
      elseif index(term_list(), bnr) == -1
        throw a:1 . ' is not a terminal buffer'
      endif
      let wids = win_findbuf(bnr)
      if empty(wids)
        execute a:qmods term_getsize(bnr)[0] 'new'
        execute 'buffer' bnr
      else
        call win_gotoid(wids[0])
      endif
    endif
  endfunction " }}}
  command! -bar -nargs=? -complete=customlist,s:complete_term_bufname Terminal  call s:term_open_existing(<q-mods>, <f-args>)
endif

このコマンドはターミナルバッファ名を引数に取り,ターミナルであるバッファのみをTab補完候補に出す.

:Terminal <ターミナルバッファ名>

まとめ

「既に開いているバッファがあるなら,そこに移動する」という機能は win_gotoid() を利用すれば簡単に実装できる.

参考文献