Neovimのdiagnostics.open_float()とlsp.buf.hover()がかぶってしまうのを防ぐ

私がメインエディタとして使っているneovimでは,最近(2022/1にリリースされたv.0.6.1以降)標準でLSP(language server protocol)機能が搭載され,コンパイラ支援でエラー表示・補完などが行えるようになります.

私も最近移行して各種設定をしていたのですが,一つ気になることがありました.今回はその解決法を共有していきたいと思います.

気になった現象

私はエラーや警告などがあった行でカーソルを保持すると,その詳細が表示されるようにしています.

local function on_cursor_hold()
  if vim.lsp.buf.server_ready() then
    vim.diagnostic.open_float()
  end
end

local diagnostic_hover_augroup_name = "lspconfig-diagnostic"
vim.api.nvim_set_option('updatetime', 500)
vim.api.nvim_create_augroup(diagnostic_hover_augroup_name, { clear = true })
vim.api.nvim_create_autocmd({ "CursorHold" }, { group = diagnostic_hover_augroup_name, callback = on_cursor_hold })

カーソル保持で警告がfloating windowが表示される

また,<Leader>lkでカーソル上にあるシンボルをホバーするキーマップを設定しています.

vim.keymap.set('n', '<Leader>lk', vim.lsp.buf.hover, opt)

シンボルのホバー

これらの設定のそれぞれは便利なのですが,エラーがある行でシンボルを表示をするとシンボル表示用のfloating windowが500msだけ表示された後エラー表示用のfloating windowが表示されてしまってシンボル情報をゆっくり見ることができません.

2つの機能によるfloating window同士が衝突してしまう

今回はこれを改善していく方法について書いていきます.

ホバーする際に一時的にエラー表示を無効にする

やり方は他にもあるとは思いますが*1,今回はホバーする際に,一時的にカーソル保持でのエラー表示を無効にする,というやり方で実装します.

local function on_cursor_hold()
  if vim.lsp.buf.server_ready() then
    vim.diagnostic.open_float()
  end
end

local diagnostic_hover_augroup_name = "lspconfig-diagnostic"

local function enable_diagnostics_hover()
  vim.api.nvim_create_augroup(diagnostic_hover_augroup_name, { clear = true })
  vim.api.nvim_create_autocmd({ "CursorHold" }, { group = diagnostic_hover_augroup_name, callback = on_cursor_hold })
end

local function disable_diagnostics_hover()
  vim.api.nvim_clear_autocmds({ group = diagnostic_hover_augroup_name })
end

vim.api.nvim_set_option('updatetime', 500)
enable_diagnostics_hover()

-- diagnosticがある行でホバーをするとすぐにdiagnosticのfloating windowで上書きされてしまうのを阻止する
-- ホバーをしたら一時的にdiagnosticを開くautocmdを無効化する
-- これだけだとそれ以降diagnosticが自動表示されなくなってしまうので有効化するautocmdを一回だけ発行して削除する
local function on_hover()
  disable_diagnostics_hover()

  vim.lsp.buf.hover()

  vim.api.nvim_create_augroup("lspconfig-enable-diagnostics-hover", { clear = true })
  -- ウィンドウの切り替えなどのイベントが絡んでくるとおかしくなるかもしれない
  vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, { group = "lspconfig-enable-diagnostics-hover", callback = function()
    vim.api.nvim_clear_autocmds({ group = "lspconfig-enable-diagnostics-hover" })
    enable_diagnostics_hover()
  end })
end

vim.keymap.set('n', '<Leader>lk', on_hover, opt)

少し複雑ですが,ホバー表示を行うとき(on_hover())に以下のようにしてエラー表示機能の無効化・有効化を行います.

  1. disable_diagnostics_hover()を呼び出してエラー表示を無効化する.これによってシンボル表示がエラー表示によってかき消されることがなくなります.
  2. 1だけだとそれ以降エラー表示がされなくなってしまうのでシンボル表示が終わったら有効化します.
    1. エラー表示機能を有効化した後自身を削除するautocmdを定義する
    2. カーソル移動時にこのautocmdを発火するようにする

こうすると,下のようにホバー表示中はエラーが表示されないようになります.

ホバーがかき消されていない

*1:エラー表示をしようとする時にホバー表示がされているかを確認してから行うなど.最初はこの方向でやろうとしたのですがfloating windowが思ったよりも柔軟に表示位置指定できるので,ホバー用のfloating windowとエラー表示用のfloating windowがかぶるかどうかの判定が難しかったので今回の方向にしました