設定ファイルは正しいのに Neovim が固まる、という壊れ方があります。原因がリポジトリの外にあるからです。

  • mason に C# の言語サーバーが 3 つ入っている。omnisharp、omnisharp-mono、 csharp-language-server。どれも .cs バッファに attach するので、ソリューションの読み込みが 3 回走り、最初のハイライトが出るまで UI スレッドが止まります。設定が要求しているのは 1 つだけ。余分に入っているだけです。
  • lsp.log が数 MB ある。どこかのサーバーが WARN を吐き続けていて、その 1 行ずつが編集中に同期で書かれます。
  • blink.cmp が InsertEnter より前にロードされている。require に約 1 秒かかるので(このマシンでの話です)、ファイルを開くたびにそのぶん待たされます。

どれも lint には出ません。リポジトリの中身は正しくて、外の状態がずれています。

検査本体は 1 か所

nvim/src/lua/my/checks/init.lua が全部持っています。各関数は{ ok, msg } のリストを返すだけで、表示にも終了コードにも触りません。

M.lsp_log_size = function()
  local max_bytes = 5 * 1024 * 1024
  local path = vim.lsp.get_log_path()
  local stat = vim.uv.fs_stat(path)
  local size = stat and stat.size or 0
  return { result(size < max_bytes,
    ("lsp.log is %.1f MB (%s)"):format(size / 1024 / 1024, path)) }
end

同じ検査を、対話的にも CI でも走らせます。2 回書くと、片方だけ直したときに差が開きます。

:checkhealth my は今そうなっているかを見る

lua/my/health.lua は結果を vim.health.ok.error に流すだけです。

LSP の attach 検査だけ事情が違います。バッファが開いていないと判定できないので、開いているバッファを走査して、期待を宣言してある filetype のものだけ見ます。該当が無ければ info を出して終わり。

M.expected_lsp_clients = { cs = { "omnisharp" }, lua = { "lua_ls" } }
M.lsp_client_noise = { copilot = true, ["null-ls"] = true, ["GitHub Copilot"] = true }

noise 側が要るのは、言語に紐づかないクライアントも同じバッファに attach するからです。これを除かないと、copilot が居るだけで期待と一致しなくなります。

CI は状態を作ってから見る

headless 側には開いているバッファがありません。nvim/tests/cs_single_lsp.lua は fixture の .cs を開き、omnisharp が attach するまで最大 120 秒待ちます。

そのあと、もう 5 秒待ちます

vim.wait(5000) -- let any unexpected second server show itself

「2 つ目が attach していない」は、その瞬間に見ても確認できません。まだ来ていないだけかもしれないからです。不在を確かめるには待つ以外にありません。

omnisharp が入っていない環境では SKIP して終わります。ワークフローはMasonInstall omnisharp を明示的に走らせるので、CI で黙って飛ぶことはありません。

終了コードと、走らせる側

各プローブは最後に cq!qa! を呼びます。cq! が非ゼロ終了です。

nvim/tests/run.shnvim/tests/*.lua を順に回します。ここに引っかかりどころが 2 つ。

  • GNU の timeout があるかを調べてから使う。 Windows のSystem32\timeout.exe は同名の別物です。timeout 1 true が通るかどうかで判定しています。
  • パスを native 形式に直す。 Windows の nvim は MSYS 形式の /d/... を開けないので、cygpath -m を通してから渡します。

ワークフローは ubuntu と windows で走り、nvim/** が変わったときだけ動きます。