← Vim研究所

Neovimの診断が行末に出ない vim.diagnostic.configの設定

2026-10-21 公開 ・

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以降なら、行末に何も出ていなくてもここを見れば診断の件数が分かります。

Neovim 0.12.4でgoplsの診断が2件ある Goのファイルを開いた画面。6行目と7行目の左端にEの記号が付き、右下のステータスラインにE:2と出ているが、エラーの文面はどこにも表示されていない
NVIM v0.12.4、設定はgoplsをつなぐ6行だけ。記号と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_textfalse行末に文面を付け足す
virtual_linesfalse行の下に文面の行を差し込む(0.11で追加)
signstrue左端の記号
underlinetrue該当箇所の下線
floattrue浮動ウィンドウでの表示
severity_sortfalse同じ行に複数あるとき重いものを優先するか
update_in_insertfalse挿入モード中も表示を更新するか
jumpwrap = 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を有効にした画面。6行目の行末にdeclared and not used: x、7行目の行末にundefined: yというエラーの文面が四角の記号に続いて表示されている
virtual_text = true。行末に文面が付く
virtual_linesを有効にした画面。6行目と7行目の下にそれぞれ1行ずつ、罫線でエラー位置を指して UnusedVar: declared and not used: x と UndeclaredName: undefined: y が表示され、下の行が押し下げられている
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を有効にし、カーソルを6行目に置いた画面。6行目の行末にだけdeclared and not used: xが表示され、7行目には記号のEだけが付いている
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の新機能 外せるプラグインと外せないもので扱っています。

関連記事

← Vim研究所 トップへ戻る