Neovim + ddu.vim で自動的にフィルタを開く方法

前置き

ddu.vim をファイラーやバッファの切り替えやヘルプの検索などに活用していますが、日々使っているとちょっとした不満も出てきます。

どういう不満かと言いますと、ddu#start() で表示したリストを絞り込むには、リスト表示後に <Cmd>call ddu#ui#do_action("openFilterWindow")<CR> を実行してコマンドラインに移動する必要があるという点です。このコマンドを割り当てた i をタイプすれば絞り込みを始められますが、ヘルプ検索のように絞り込み必須の場合、毎回毎回 i をタイプするのが面倒だと感じていました。

そこで、特定のリストを表示した時だけ自動的に絞り込みが始まるという設定を行いましたので、その内容を備忘録として残します。

環境

1$ nvim --version
2NVIM v0.10.1
3Build type: Release
4LuaJIT 2.1.1713773202
1ddu.vim: commit f6480d2
2ddu-ui-ff: commit c1a8644

設定方法

私と同じことを考える人が他にもいるようで、ddu-ui-ff の FAQ-25 にそのための設定が掲載されています。。

Q: I want to start filter window when UI is initialized.

A: It is not supported. Because it has too many problems. But you can use the function.

1call ddu#start()
2autocmd User Ddu:uiDone ++nested
3      \ call ddu#ui#async_action('openFilterWindow')

問題が多いからその機能はサポートしないと回答されていますが、試してみてダメなら止めれば良いだけだと考えて、一度試してみることにしました

この機能の実装ですが、単純に考えれば FAQ で示されている設定を追加するだけでよさそうです。しかし、この設定を追加するだけでは別の問題が発生します。なぜなら、この設定は「ddu.vim の UI に全てのアイテムが表示された時(Ddu:uiDone)に call ddu#ui#async_action('openFilterWindow') を実行して絞り込みを開始する」というものなので、どのリストを表示しても自動的に絞り込みが始まってしまい、今度は絞り込み不要のリストを開いたときに esc をタイプして絞り込みを中止する必要が生じてしまいます。

よって、「特定のソースを開く場合のみ上記の設定を実行し、そのソースのリストを閉じたら autocmd User Ddu:uiDone で設定したオートコマンドを削除する」という処理を実装する必要があります。

結論から言いますと、以下の設定で上記の処理を実現できます。なお、私は Lua で設定していますので、Lua のコードを示します。

 1-- バグがあったため修正(2024/11/26)
 2
 3-- オートコマンドを削除する時の目印となるグループを設定
 4-- グループ名は任意の値に設定できる
 5local ddu_vim_autocmd_group = vim.api.nvim_create_augroup('ddu_vim', {})
 6
 7-- 自動的に絞り込みを開始するソースは、ヘルプとファイルとする。
 8vim.keymap.set('n', ';h', function() Ddu_start_with_filter_window('help') end)
 9vim.keymap.set('n', ';f', function() Ddu_start_with_filter_window('file_recursive') end)
10
11-- リスト表示とリストにアイテムが表示されたら絞り込みを開始する設定を行う関数
12function Ddu_start_with_filter_window(source_name)
13  -- 引数で指示されたソースのアイテムリストを表示
14  vim.fn['ddu#start']({name = source_name})
15  -- リストにアイテムが表示されたら絞り込みを開始するというオートコマンドを設定
16  return vim.api.nvim_create_autocmd("User",
17    {
18      -- リストにアイテムが表示された時点をオートコマンドのタイミングに設定
19      pattern = 'Ddu:uiDone',
20      -- 後でオートコマンドを削除するための目印としてグループを設定
21      group = ddu_vim_autocmd_group,
22      nested = true,
23      callback = function()
24        -- 絞り込みを開始する
25        vim.fn['ddu#ui#async_action']('openFilterWindow')
26      end,
27    }
28  )
29end
30
31-- 絞り込み終了時点で `Ddu_start_with_filter_window()` 関数で設定したオートコマンドを
32-- 削除するというオートコマンドを設定する
33vim.api.nvim_create_autocmd({'User'},
34  {
35    -- 絞り込みウィンドウを抜けた時点を自動コマンド実行のタイミングに設定
36    pattern = 'Ddu:ui:ff:closeFilterWindow',
37    callback = function()
38      -- `Ddu_start_with_filter_window()` 関数で設定したオートコマンドの ID を取得
39      -- 引数を Table で渡すと名前付き引数のように扱える
40      local autocmd_id = Get_autocmd_id({group = ddu_vim_autocmd_group, pattern = 'Ddu:uiDone'})
41      
42      if autocmd_id ~= 0 then
43        -- `Ddu_start_with_filter_window()` 関数で設定したオートコマンドを削除
44        -- これを忘れるとリストを表示するたびに絞り込みが始まってしまう
45        vim.api.nvim_del_autocmd(autocmd_id)
46      end
47    end
48  }
49)
50
51-- 引数に対応するオートコマンドの ID を返す関数
52-- 引数は Table で渡されているので `args.xx` の形で取り出す
53function Get_autocmd_id(args)
54  local pcall_result, function_return = pcall(
55    vim.api.nvim_get_autocmds, { group = args.group, pattern = { args.pattern } }
56  )
57  if pcall_result then
58    return function_return[1].id -- lua の配列の添字は 1 から始まる
59  else
60    return 0
61  end
62end

コメントに各コードの意味を記載していますが、Ddu_start_with_filter_window() で設定したオートコマンドを絞り込み終了時点で削除しなかった場合、それ以降はリストを表示すると自動的に絞り込み処理が始まります。そのため vim.api.nvim_del_autocmd(autocmd_id_openfilterwindow) でオートコマンドを削除しています。この設定で少々つまずきました。

なお、このオートコマンドの削除は nvim_get_autocmds() の検索結果が1つだけということを前提にしていますので、grouppattern で検索してヒットするオートコマンドが2つ以上あると処理が破綻します。そのため、ddu_vim_autocmd_group = vim.api.nvim_create_augroup('ddu_vim', {}) で用意した目印をオートコマンド作成時の group に設定しています。

この設定により、ヘルプやファイルのリストを表示したら自動的に絞り込みを始められる一方、それ以外のリストを表示した時は必要な時だけ絞り込みを行うという動作を実現できるようになりました。

補足

リストを表示したら自動的に絞り込みを開始するという設定は、以前は startFilter = true という設定を追加するだけで可能だったようです。しかし、この設定は、問題が多すぎるということで2024年3月11日に削除されています(該当のコミット)。