Neovimの診断が行末に出ない vim.diagnostic.configの設定
Neovimを0.11以降に上げたら、LSPのエラーが行末に出なくなった。左端のEは付いているのに、何のエラーなのかが画面のどこにも無い。設定を何も変えていなければ、これは壊れたのではなく既定値が変わった結果です。0.11のリリースノートには次の1行があります。
The "virtual_text" handler is disabled by default.
Neovim News-0.11
もう1つ、0.12ではsign_define()で診断の記号を変える昔の書き方が効かなくなりました。こちらはエラーも警告も出ないので、設定が読まれていないことに気づきにくい変更です。この記事では、黙って効かなくなるこの2か所を実機で確かめ、vim.diagnostic.config()で今の書き方に直します。サーバーをつなぐところはNeovimの標準LSP設定(lsp/とvim.lsp.enable)で扱ったので、ここではつないだあとの表示を受け持ちます。
検証環境:NVIM v0.12.4(macOS)。nvim --cleanにgoplsをつなぐ6行の設定だけを読ませ、未使用の変数と未定義の変数を1つずつ含むGoのファイルを開いて確かめました。同じ行に重さの違う診断を並べる確認だけは、vim.diagnostic.set()で診断を直接作っています。0.10以前の既定値は手元で動かしておらず、同梱のnews-0.11.txtとnews.txtの記述に拠ります。
目次(9項目)
| やりたいこと | vim.diagnostic.config() に渡すもの |
|---|---|
| 行末に出す | virtual_text = true |
| 行の下に出す | virtual_lines = true |
| カーソル行だけ | virtual_text = { current_line = true } |
| 記号を変える | signs = { text = { ... } } |
| 重いものを優先 | severity_sort = true |
0.11から行末のエラーが出なくなった理由
原因は1つで、virtual_textというハンドラの既定値が0.11で有効から無効に変わりました。virtual_textは、診断の文面を行末に灰色や赤の文字で付け足す表示です。0.10までは何も書かなくてもこれが出ていたので、init.luaにvirtual_textの行が一度も出てこない人ほど影響を受けます。逆に、前からvirtual_text = trueや細かい表の形で明示していた人は、上げても見た目が変わりません。
手元の0.12.4で、goplsをつないだだけの状態がどう見えるかを撮り、この節の終わりに載せました。6行目と7行目にエラーがあるのに、出ているのは左端のEと、右下のステータスラインのE:2だけです。
「LSPが壊れた」と疑う前に、診断そのものが届いているかを分けて確かめると早く済みます。届いていれば、表示の設定だけの問題です。
:lua print(vim.inspect(vim.tbl_map(function(x) return {x.lnum,x.severity,x.message,x.source} end, vim.diagnostic.get(0))))
{ { 5, 1, "declared and not used: x", "compiler" }, { 6, 1, "undefined: y", "compiler" } }
2件とも入っています(lnumは0始まりなので5と6が画面の6行目と7行目です)。ここが空なら表示ではなくサーバーの接続を疑う番で、:checkhealth vim.lspの見方はNeovimの構成診断コマンド:checkhealthの使い方にまとめてあります。なお右下のE:2は、0.12で既定のステータスラインにvim.diagnostic.status()が入ったことによる表示です。0.12以降なら、行末に何も出ていなくてもここを見れば診断の件数が分かります。
E:2は出るが、何のエラーかは画面に無い今の既定値をvim.diagnostic.config()で確かめる
vim.diagnostic.config()は、引数を渡せば設定を変え、何も渡さなければ今の設定を返します。どのハンドラが無効になっているかは、記事やリリースノートを読むより、手元のNeovimにこれを聞くほうが確実です。nvim --cleanの0.12.4で打った結果がこれです。
:lua =vim.diagnostic.config()
{
float = true,
jump = {
wrap = true
},
severity_sort = false,
signs = true,
underline = true,
update_in_insert = false,
virtual_lines = false,
virtual_text = false
}
| キー | 既定 | 何を決めるか |
|---|---|---|
virtual_text | false | 行末に文面を付け足す |
virtual_lines | false | 行の下に文面の行を差し込む(0.11で追加) |
signs | true | 左端の記号 |
underline | true | 該当箇所の下線 |
float | true | 浮動ウィンドウでの表示 |
severity_sort | false | 同じ行に複数あるとき重いものを優先するか |
update_in_insert | false | 挿入モード中も表示を更新するか |
jump | wrap = true | ]dなどで移動するときの既定 |
文面を出すハンドラが2つともfalseなので、画面に文字で出る経路が無いことが読み取れます。自分のinit.luaを読み込んだ状態で同じ1行を打てば、どこまでが既定でどこからが自分の設定なのかも見分けられます。
設定するときは、変えたいキーだけを渡せば足ります。vim.diagnostic.config({ virtual_text = true })のあとに引数なしで読み直すと、virtual_textだけがtrueになり、signsやjumpは元の値のまま残っていました。上の表のキーを全部書き写す必要はありません。
行末に出す・行の下に出す(virtual_text / virtual_lines)
0.10までの見た目に戻すだけなら、init.luaにこの1行を足します。
vim.diagnostic.config({ virtual_text = true })
0.11からは、もう1つの出し方としてvirtual_linesが入りました。こちらは行末ではなく、該当する行の下に罫線付きの行を差し込んで文面を出します。この節の終わりに並べた2枚の画面で、違いを見比べられます。
差は3つありました。1つ目は長さで、virtual_textは文面が長いとウィンドウの右端で切れますが、virtual_linesは1行まるごと使えます。2つ目は位置で、virtual_linesは罫線の角が診断の列を指すので、7行目ならyの真下から線が出ます。3つ目は出る情報で、virtual_linesにはgoplsが付けたコード名(UnusedVarやUndeclaredName)が前に付き、virtual_textには付きませんでした。virtual_textでも出どころを知りたければvirtual_text = { source = true }にすると、■ compiler: undefined: yのように診断のsourceが前に付きます。
引き換えに、virtual_linesは診断の数だけ本文が下へ押し出されます。エラーが10件ある関数なら10行ぶん画面がずれるので、編集中のコードの位置が変わって落ち着かない人もいるはずです。次の節のcurrent_lineは、この押し出しを抑えるための選択肢でもあります。
virtual_text = true。行末に文面が付く
virtual_lines = true。診断ごとに1行増え、gopls のコード名(UnusedVar)まで出るカーソル行だけに出す(current_line)
virtual_textに表を渡し、current_line = trueを指定すると、文面はカーソルのある行にだけ出ます。0.11で入ったオプションです。
vim.diagnostic.config({ virtual_text = { current_line = true } })
カーソルを6行目に置くと、6行目の行末にだけdeclared and not used: xが出て、7行目は左端のEだけになりました。どの行に問題があるかは記号で分かり、文面は読みたい行で読む、という分担です。
同じオプションはvirtual_linesにもあります。virtual_lines = { current_line = true }にして7行目に移ると、7行目の下にだけ└──── UndeclaredName: undefined: yの行が差し込まれました。ただし、こちらはカーソルが診断のある行に入るたびに1行増えて下の行が動くので、押し出しが無くなるわけではありません。押し出しを避けたいならvirtual_text側のcurrent_lineが向いています。
virtual_text = { current_line = true }でカーソルを6行目に置いたところ。7行目の文面は隠れる記号を変える 0.12でsign_defineが効かなくなった
左端のEを別の文字やアイコンに変える設定は、古い記事ではsign_define()で書かれていることが多いです。0.12.4で同じことをすると、次のようになりました。
:lua print(vim.fn.sign_define("DiagnosticSignError", { text = "X" }))
0
:echo sign_getdefined("DiagnosticSignError")
[{'name': 'DiagnosticSignError', 'texthl': 'DiagnosticSignError', 'text': 'X '}]
戻り値の0は成功を表し、sign_getdefined()で読むとXも登録されています。それでも画面の記号はEのままでした。開いたあとに打ったので反映が遅れているのかと思い、init.luaの先頭に書いて起動し直しても同じでした。0.12のnews.txtに次の記述があり、診断の記号はもう:sign-defineの定義を見に行きません。
diagnostic-signs can no longer be configured with :sign-define or sign_define() (deprecated in Nvim 0.10 deprecated-0.10).
Neovim News-0.12
sign_define()はサイン機能そのものの関数としては残っているので、呼んでもエラーになりません。これが気づきにくさの原因です。今の書き方はsignsにtextの表を渡す形です。
vim.diagnostic.config({
signs = {
text = {
[vim.diagnostic.severity.ERROR] = "X",
[vim.diagnostic.severity.WARN] = "W",
[vim.diagnostic.severity.INFO] = "I",
[vim.diagnostic.severity.HINT] = "H",
},
},
})
これで左端がXに変わり、右下のステータスラインもE:2からX:2に変わりました。0.12の既定のステータスラインは、このsigns.textの文字を使って件数を出しているようです。0.12のnews.txtには、ステータス表示のformatに重さごとの文字の表を渡す形をやめ、signs.textを使うよう書かれています。
同じ行に複数あるときはseverity_sort
記号の欄は1行に1つしか見えないので、同じ行に警告とエラーとヒントが重なると、どれが見えるかが問題になります。vim.diagnostic.set()で1行に警告・エラー・ヒントの順に3つ作って比べました。
-- severity_sort = false(既定)
H line two ■■■ hint third
-- severity_sort = true
E line two ■■■ error second
既定のままだと、エラーがあるのに記号はHで、行末の文面もヒントのものでした。virtual_textは診断の数だけ■を並べますが、文面は1つしか出しません。severity_sort = trueにすると記号も文面もエラーに変わります。エラーが一番目立つ位置に来ないのは不便なので、virtual_textを有効にするなら一緒にtrueにしておくのが無難です。
診断の間を移動して全文を読む
行末に出さない設定のままでも、文面を読む手段は既定のキーマップで揃っています。
| キー | 動き |
|---|---|
| ]d / [d | 次 / 前の診断へ移動。0.11からカウントを受け付ける |
| ]D / [D | バッファの最後 / 最初の診断へ移動(0.11で追加) |
| Ctrl-w d | カーソル位置の診断を浮動ウィンドウで開く |
6行目でCtrl-w dを押すと、浮動ウィンドウに次のように出ました。こちらにもgoplsのコード名が角括弧で付きます。
Diagnostics:
1. declared and not used: x [UnusedVar]
]dは最後の診断から押すと先頭へ戻ります。7行目14列(y)で押すと6行目へ移りました。これが既定値に出ていたjump = { wrap = true }の効果で、wrap = falseにすると最後で止まり、No more valid diagnostics to move toと出ます。
移動と同時に浮動ウィンドウを開きたい場合、vim.diagnostic.jump({ count = 1, float = true })は0.12.4でも動きました。ただしfloatは0.12で非推奨になり、同梱のdeprecated.txtはon_jumpを使うよう案内しています。次のようにjumpの既定に入れておくと、]dと[dでも移動のたびに開きました。
vim.diagnostic.config({
jump = {
on_jump = function(diagnostic, bufnr)
if diagnostic then
vim.diagnostic.open_float({ bufnr = bufnr, scope = 'cursor', focus = false })
end
end,
},
})
非推奨になったvim.diagnostic.goto_next()(0.12.4にも関数としては残っています)は、移動と同時に浮動ウィンドウを開く関数でした。deprecated.txtも置き換え先としてfloat=true付きのvim.diagnostic.jump()を挙げています。昔のキーマップをjump()に書き換えたら浮動ウィンドウが出なくなった、という場合はここが抜けています。
診断を一時的に止める
大量の警告が出るファイルを読むだけのときなど、表示を消したい場面があります。0.12で使えるのはvim.diagnostic.enable()とvim.diagnostic.is_enabled()です。
:lua vim.diagnostic.enable(false)
:lua print(vim.diagnostic.is_enabled())
false
切り替えのキーマップにするなら、今の状態を反転させて渡します。
vim.keymap.set('n', '<leader>td', function()
vim.diagnostic.enable(not vim.diagnostic.is_enabled())
end)
古い設定にあるvim.diagnostic.disable()は0.12で削除されました。呼ぶとE5108: Lua: [string ":lua"]:1: attempt to call field 'disable' (a nil value)で止まります。is_disabled()も同じく無くなっています。
意外だったのは、enable(false)で消えるのが画面の表示だけだったことです。記号と行末の文面は消えましたが、vim.diagnostic.get(0)は2件を返し続け、右下のE:2も残り、]dを押すと6行目へ移動し、Ctrl-w dでも浮動ウィンドウが開きました。診断を受け取るのを止めるわけではないので、サーバーごと止めたい場合は別の手段が要ります。
記号の節を確かめていたとき、sign_define()が0を返し、sign_getdefined()にもXが入っているのを見て、てっきり反映されたと思いました。画面のEが変わらないのは再描画の問題だと決めつけ、起動時の設定に移して開き直すまで5分ほど別の原因を探していました。エラーが出ない変更は、戻り値ではなく画面で確かめないと分からないと痛感しています。enable(false)のあとに]dがまだ効いたのも、止めたつもりで押して初めて気づいたことでした。
まとめ
0.11以降で行末にエラーが出ないのは、virtual_textの既定値が無効になったためで、vim.diagnostic.config({ virtual_text = true })の1行で戻ります。何が有効なのか迷ったら、引数なしのvim.diagnostic.config()で手元の値を読むのが早道です。行の下に出したいならvirtual_lines、画面を静かにしたいならcurrent_lineを選べます。
0.12で黙って効かなくなるのは、sign_define()による記号の変更です。jump()のfloatは非推奨になりましたが、まだ動きます。disable()は呼ぶとエラーになるので、こちらは気づけます。0.12で外せるようになったプラグインの全体像はNeovim 0.12の新機能 外せるプラグインと外せないもので扱っています。