noice.nvim のfilterの設定難しすぎて死んだ

:::note alert
(2024/01/27現在) この記事にはまだ正確性がありません 
記事執筆後から始めた作業に於いて、 `format` がおかしいと怒られ始めました
:::

# `noice.nvim` 、いいよね (はじまり)

(neo)vimを使用していると、よく、ターミナルの一番下の行を眺めることになるかと思います

多くの場合、 `:` から始まるvimコマンドの呼び出しや `/` などでの検索、
また、 `echo` や `echoe` などで表示されるログメッセージの類がここに表示されるためです

しかしながら、何かしらのメッセージが表示されるたびに視線を下に下げるのは非常にめんどうです

特に `vimrc` を何か誤っている場合のエラーメッセージの場合、 **カーソル移動をするたびにエラーが表示され** 、そのたびに `Enter` を連打しないとならないことがあったりするため、とてもしんどいです

しかし、 `noice.nvim` はこの問題を解決してくれます
**あらゆるメッセージを任意の位置の通知として表示してくれる** ようになります (デフォルトだと `macOS` と同じ右上)

これによって、 **エラーメッセージに対するキーアクションが必要とされなくなる** ため、
たとえ `LSP` でエラーがたくさん出てくるような場合でも、比較的スムーズにコーディング作業をすることができます

# そして私は死んだ (何があったのか？)

> これによって、 **エラーメッセージに対するキーアクションが必要とされなくなる** ため、
> たとえ `LSP` でエラーがたくさん出てくるような場合でも、比較的スムーズにコーディング作業をすることができます

これはちょっと罠の部分があり、エラーが大量に出るような場合、 **その通知で画面が埋め尽くされる** ことがよくよく発生します
そしてこの場合、 `float window` を大量に生成する処理がneovimに発生します

一度に大量にインスタンスを作成すると何が起こるかは、皆さんならよくよくご存知でしょう

……何も操作ができなくなって死にます (死んだ)

![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507143/190d8553-5680-88a1-2c02-5758f6d1f6b5.png)

vim-jpのslackで相談してしまったレベルです、つらい
![スクリーンショット 2024-01-27 145152.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507143/39224d49-367d-dd9d-53fc-4ed372011159.png)


# 問題

特定の大量発生しがちなエラーを出してしまった場合、何も作業ができなくなって死にます

> e.g. LSPの `textDocument/hover` など、LSPが対応していないファイルを操作している際に `hover` を呼ぶような場合

:::note warn
あくまで私の場合の話です
私の場合、カーソルが特定のワードに置かれた際に、1秒程度の間隔を空けて `textDocument/hover` を呼ぶような設定にしているため、このようなことが発生します
:::

# 前提

## 環境情報

- Ubuntu 22/04 LTS (on Windows11(WSL2))
- neovim v0.9.5

```bash
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.3 LTS
Release:        22.04
Codename:       jammy
```

```bash
$ nvim -v
NVIM v0.9.5
Build type: Release
LuaJIT 2.1.1692716794

   system vimrc file: "$VIM/sysinit.vim"
  fall-back for $VIM: "/__w/neovim/neovim/build/nvim.AppDir/usr/share/nvim"

Run :checkhealth for more info
```

# `noice.nvim` の `filter` について

製作者様にはかなり申し訳ないのですが、私にとってはちょっとわかりづらいです…

[`noice.nvim` の `filter` のヘルプ](https://github.com/folke/noice.nvim/blob/main/doc/noice.nvim.txt#L370)
[`noice.nvim` の `route` のヘルプ](https://github.com/folke/noice.nvim/blob/main/doc/noice.nvim.txt#L601)

以下抜粋 (2024/01/27時点)

```txt
FILTERS                       *noice.nvim-noice-(nice,-noise,-notice)-filters*

**Noice** uses filters to route messages to specific views.

  ------------------------------------------------------------------------------
  Name         Type       Description
  ------------ ---------- ------------------------------------------------------
  any          filter[]   checks that at least one of the filters matches

  blocking     boolean    are we in blocking mode?

  cleared      boolean    checks if the message is cleared, meaning it’s in the
                          history

  cmdline      boolean or checks if the message was generated by executing a
               string     cmdline. When string, then it is used as a pattern

  error        boolean    all error-like kinds from ext_messages

  event        string or  any of the events from ext_messages or cmdline. See :h
               string[]   ui-messages

  find         string     uses lua string.find to match the pattern

  has          boolean    checks if the message is exists, meaning it’s in the
                          history

  kind         string or  any of the kinds from ext_messages. See :h ui-messages
               string[]   

  max_height   number     maximum height of the message

  max_length   number     maximum length of the message (total width of all the
                          lines)

  max_width    number     maximum width of the message

  min_height   number     minimum height of the message

  min_length   number     minimum length of the message (total width of all the
                          lines)

  min_width    number     minimum width of the message

  mode         string     checks if vim.api.nvim_get_mode() contains the given
                          mode

  not          filter     checks whether the filter matches or not

  warning      boolean    all warning-like kinds from ext_messages
  ------------------------------------------------------------------------------
````

## わからなかったポイント

### `FILTERS` は `FILTER` ですか？

この部分

> FILTERS                       *noice.nvim-noice-(nice,-noise,-notice)-filters*

と、この部分

> any          filter[]   checks that at least one of the filters matches

`FILTERS` が `FILTER` の説明なのかがちょっとわかりづらかったです
特に、 `any` の型が `filter[]` となっているところもあり、この入れ子構造のイメージがしづらかった…

また、ドキュメント側でのこのあたりの説明がかなりさらっとしており、 `any` を利用するパターンのイメージがうまくできませんでした

# 構造の理解

`filter` はこのような構造になっていることで理解しました

1. `filter` を持つテーブル構造には、ほかのキーを持たせてはならない
2. `any` は`filter` の**配列構造を前提** としている
    - これがかなりわかりづらいのですが、つまり、 `{ filter = {...} }` を期待しています
3. `filter` は少なくとも `event` と `find` を持たないと、フィルタリングできない
4. `filter` に `view` を指定することで、任意のビューの方法で表示してくれる
    - 例えば `view = "mini"` とすると、右下に小さく、通知を表示してくれます

コードで表すと、このような形になります

```config.lua
{
  {
    filter = {
      event = "msg_show",
      any = {
        { filter = { kind = "", find = "", } }
        { filter = { kind = "", find = "", } }
        { filter = { kind = "", find = "", } }
        { : }
        { : }
      },
      opts = { skip = true },
    },
  },
  {
    filter = {
      event = "lsp",
      any = {
        { filter = { kind = "", find = "", } }
        { filter = { kind = "", find = "", } }
        { filter = { kind = "", find = "", } }
        { : }
        { : }
      },
      view = "mini",
    },
  },
}
````

# 問題の解決

このような設定を書くことで、解決することができました

```noice-nvim-config.lua
local suppressMessages = {
  -- ※ `suppressMessage()` は後述の `appendix` の部分で説明しています
  noiceWrapper("^%d+ lines .ed %d+ times?$"),
  noiceWrapper("^%d+ lines yanked$"),
  noiceWrapper(".*E490.*", "emsg"),
  noiceWrapper("search_count"),
  -- :
  -- (中略)
  -- :
}

local M = {
  routes = {
    {
      filter = {
        event = "msg_show",
        any = suppressMessages,
        opts = { skip = true },
      },
    },
  }
}

return M
```

```noice.init.lua
-- lazy.nvim を使用している想定です
local noice_config = require("noice-nvim-config")
return {
  {
    lazy = true,
    "folke/noice.nvim",
    event = { "VeryLazy" },
    dependencies = {
      "MunifTanjim/nui.nvim",
      "rcarriga/nvim-notify",
      "nvim-telescope/telescope.nvim",
    },
    opts = function(_, opts)
      -- add any options here
      opts.routes = noice_config.routes
      opts.notify = {
        enabled = true,
        view = "notify",
      }
      opts.messages = {
        -- NOTE: If you enable messages, then the cmdline is enabled automatically.
        -- This is a current Neovim limitation.
        enabled = true, -- enables the Noice messages UI
        view = "notify", -- default view for messages
        view_error = "notify", -- view for errors
        view_warn = "notify", -- view for warnings
        view_history = "messages", -- view for :messages
        view_search = "virtualtext", -- view for search count messages. Set to `false` to disable
      }
      opts.redirect = {
        view = "popup",
        filter = { event = "msg_show" },
      }
      opts.presets = {
        bottom_search = true, -- use a classic bottom cmdline for search
        command_palette = true, -- position the cmdline and popupmenu together
        long_message_to_split = true, -- long messages will be sent to a split
        inc_rename = false, -- enables an input dialog for inc-rename.nvim
        lsp_doc_border = false, -- add a border to hover docs and signature help
      }
    end,
    config = function(_, opts)
      require("noice").setup(opts)
      -- ※ ここは `telescope` 使用される方はお好みで
      --require("telescope").load_extension("notify")
    end,
  }
}
````

## appendix: ユーティリティ関数の追加

こんな感じの `noice-filter` のラッパーを作成しておくことで、わざわざテーブル構造を書くコストを減らしています

```util.noice.lua
local function noiceWrapper(pattern, kind)
  kind = kind or ""
  return {
    filter = {
      kind = kind,
      find = pattern,
    },
  }
end
```

# さいごに

入れ子構造はちょっと難しいです
キーが指定されていたり、されていなかったりすると、さらに難しさに拍車がかかるような気がしています

この問題の解決のために、私は結構精神をすり減らしましたが、うまく解決できてよかったです