GVATECHブログ
🐡

Neovim 0.10のwinfixbuf対応: dduで学ぶバッファ管理の実践

に公開

Hello, World!

既に一年以上前の話ではありますがneovimのバッファ管理でアップデートがありましたね[1]
(技術ブログを書くのが遅くなってしまって旬を逃してしまいましたが)
この記事では、その解決過程で学んだバッファ管理の概念と解決策を整理します。

TL;DR

  • neovim 10で追加されたwinfixbufdduの設定を解決
  • bwipeout!を使って階層が深くても全てのfloating windowを確実に閉じる
  • 元のウィンドウでファイルを開くシンプルな実装

ことの発端

一仕事を終え気分転換にsudo pacman -Syuを実行していたら、neovimが0.10.0にアップデートされていました。[2]
早速起動してみると、普段使っているddu.nvimの挙動が違っています

-- エラーは見やすいように整形しています
Error  10:57:30 PM msg_show.rpc_error 
Error detected while processing function ddu#ui#sync_action[1]
ddu#ui_sync_action[4]..
ddu#denops#_request[15]..
denops#request[1]..
denops#_internal#server#chan#request[6]..
denops#_internal#rpc#nvim#request:line1:
Error invoking 'invoke' on channel 4 (denops):
Error: Failed to call 'uiAction' API in 'ddu':
Error: Failed to call 'ddu#item_action' in.......

といったエラーが出てしまいました。
どうやらddu.nvimのバッファ管理に関するエラーが出ており
winfixbufが有効な状態でバッファを閉じようとしたことが原因のようです。

背景: winfixbufとは何か

winfixbufは、Neovim 0.10.0で追加されたウィンドウローカルオプションで、ウィンドウを特定のバッファに「固定」する機能です。
このオプションが有効になってると、そのウィンドウでは別のバッファを開こうとしても拒否されます。
これにより、helpやquickfixウィンドウで誤って別のファイルを開くことを防げます

主な特徴

  • :edit:buffer:bnextなどのコマンドがエラーになる
  • プラグインがバッファを切り替えようとしても防がれる
  • ファイラーやターミナルなど、専用ウィンドウの実装に便利

利用用途

  • ファイラー (例: ddu, NERDTree, nvim-tree.lua)
  • ターミナル (例: toggleterm.nvim)
  • その他、特定のバッファを常に表示したい場合

ここで重要なのは、winfixbufが有効なウィンドウでは、バッファを切り替える操作が制限されるため、プラグインが意図した動作をしないことがある、という点です。

試した解決策とその問題

問題:dduでE1513エラーが発生

Error: Failed to call 'itemAction' API in 'ddu': 
Vim(buffer):E1513: Cannot switch buffer. 'winfixbuf' is enabled

はい、そうです。
上記のエラーもその一例です。
私の場合はdduをflaoting windowのファイラーとして使っているので、dduのバッファを閉じようとした際にエラーが発生しました。

解決策1 単純にwinfixbufを無効化

setlocal nowinfixbuf

❌問題点:

いつもであればdduでファイルを選択してEnterを押すとdduのfloating windowのバッファが閉じて元のウィンドウに戻るのですが
今回は、そのbufferが閉じずそのbuffer内でファイルが開いてしまいました。

解決策2 dduのactionOptionsでquitアクションを使用

'actionOptions': {
  'open': {
    'quit': v:true,
  },
}

問題点:

最後の1つのwindowしか閉じない
これはどういうことかというと、dduのfloating windowで階層が深くなっている場合に、1つのwindowしか閉じないので、結局元のウィンドウに戻れないことがありました。

解決策3. 複数回:quitを実行する

let i = 0
while i < 10
  call ddu#ui#do_action('quit')
  let i += 1
endwhile

問題点:

の、脳筋!!!!余りにも脳筋すぎる!!!!!
エルデンリングの脳筋ビルドは好きですが、コードでやるのは違うと思います。[3]
階層によって必要な回数が変わるので、無駄にループ回数を増やすとパフォーマンスが悪くなります。

Vimのバッファ管理における階層性と選択基準

ここからが本題です。
quitとか使っていたのですが、Vimにはバッファを閉じるためのコマンドがいくつかあったので
整理してみました。

概念

バッファ操作コマンドには「強度」の違いがあって、winfixbufが設定されてる時でも動作するコマンドと、エラーになるコマンドがあるので、これを理解しておくと、プラグインのエラーにも対処しやすくなります。

階層構造(弱い → 強い)

  1. :close - ウィンドウのみ閉じる
  2. :quit - 現在のウィンドウ/バッファを終了
  3. :bdelete - バッファを削除(ウィンドウは残る場合がある)
  4. :bwipeout - バッファを完全に削除
  5. :bwipeout! - 強制的にバッファを完全削除(変更があっても)

実践的な選択基準

floating window を確実に閉じたい場合

bwipeout! を選択

理由:

  • バッファが完全に削除されることで、関連するウィンドウも連鎖的に閉じる
  • 階層が深くても一回の実行で効果的

通常のファイル編集での終了

:quit または :close を選択

最終的な解決策

コンセプト

  • 階層が深くても一回の実行で全てのfloating windowを閉じる
  • 元のウィンドウでファイルを開く

実装のポイント

  • ddu開始前に現在のウィンドウIDを保存
  • ファイル選択時に元のウィンドウに移動してからファイルを開く
  • bwipeout!で全てのddu windowを確実に閉じる

上記の整理を踏まえて、以下のように実装しました。

" DDU開始前のウィンドウIDを保存する変数
let g:ddu_prev_winid = 0

" DDU開始前に現在のウィンドウIDを保存
function! s:save_prev_winid() abort
  let g:ddu_prev_winid = win_getid()
endfunction

" 前のウィンドウでファイルを開く関数
function! s:open_in_prev_window() abort
  let item = ddu#ui#get_item()
  if !empty(item) && has_key(item, 'action') && has_key(item.action, 'path')
    if !item->get('isTree', v:false)
      " ファイルの場合
      let filepath = item.action.path
      let ddu_bufnr = bufnr('%')
      " 元のウィンドウに移動
      if g:ddu_prev_winid != 0 && win_gotoid(g:ddu_prev_winid)
        execute 'edit ' . fnameescape(filepath)
        " bwipeout! で階層が深くても全てのddu windowを確実に閉じる
        execute 'bwipeout! ' . ddu_bufnr
      endif
    else
      " ディレクトリの場合は通常のnarrowアクション
      call ddu#ui#do_action('itemAction', #{name: 'narrow'})
    endif
  endif
endfunction

" キーマッピング
nmap <silent> ,m <Cmd>call <SID>save_prev_winid()<CR><Cmd>call ddu#start({ 'name' : 'file' })<CR>

" ddu-filerの設定
autocmd FileType ddu-filer call s:ddu_filer_my_settings()
function! s:ddu_filer_my_settings() abort
  " winfixbufを無効化
  setlocal nowinfixbuf
  " ファイル選択時に元のウィンドウで開く
  nnoremap <buffer><silent> <CR>
        \ <Cmd>call <SID>open_in_prev_window()<CR>
endfunction

まとめ

この実装により:

  • ✅ winfixbufが有効でもdduが正常に動作
  • ✅ ファイルは常に元のウィンドウで開かれる
  • ✅ 階層が深くても全てのfloating windowが確実に閉じる
  • ✅ シンプルで理解しやすいコード

上記コードの完全版はdotfilesで公開しています。

脚注
  1. この記事自体は実は3ヶ月前に書き始めていたのですが、ドタバタで放置していました。ようやく完成しました。 ↩︎

  2. Arch Linuxはいいぞぉー、neovimの最新バージョンがすぐに使える。開発者の皆様に感謝。 ↩︎

  3. 脳みそ筋肉ビルドですが筆者は上質戦士,ギンバサ戦士を使っています。ナイトレインはもっぱら追跡者を使用しています。 ↩︎

GVATECHブログ
GVATECHブログ

Discussion