Is there any way to force a particular lsp server to delay its diagnostics, or not run them during insert mode?
gopls is tends to be quite chatty when i'm writing and i see a lot of temporary diagnostics virtual texts. Worse yet, at some point in time, the following messages (in a different buffer) start being printed out, causing pauses in the ui:
LSP[gopls] 2020/02/17 09:01:01 failed to fix AST: unable to parse defer or go from *ast.BadStmt: no defer or go statement found
sorry about the bug label, this is more of a question/documentation issue
Ideally I would like an option to suspend all textDocument/publishDiagnostics communication in insert mode (i.e. both inbound and outbound). Or at the very least add some kind of debounce.
It's possible to mess around with a custom vim.lsp.callbacks['textDocument/publishDiagnostics'] callback to remove most of the noise. But that just means throwing away the response, the LSP will still perform a lot of (needless) processing on every keypress. Which can be a huge drain on CPU and battery.
@urandom Would you show me a sample codes or project?
@h-michael
It seems that the code really doesn't matter. Any partial statement will obviously trigger diagnostics, and some will cause that blocking error as well, though I'm not exactly sure what causes the later.
I've noticed it mostly happening when I start writing struct literals, like:
https://github.com/globusdigital/feature-toggles/blob/7ab6dd1a839c3a58354f95fb689d4cc558232de9/toggle/toggle_test.go#L37
the cursor position would be at:
{name:|CURSOR|}
and the error will popup, freezing nvim for a second.
though it also sometimes occurs when i write if statements.
@lithammer I would ignore the cost of the processing on the keypress if you're using an LSP at all on a battery and just disable it if that is a concern. There's no way to use an LSP without sending the textDocument updates, which, currently, send the whole text document due to a bug preventing incremental updates in the core LSP (but to be fair, a lot of LSP servers don't support incremental as well).
So I'd say the cost of parsing the json is pretty insignificant compared to the cost of displaying virtual text and updating it etc, so a custom callback which returns early if you're in insert mode for the LSP server of your choice (or all of them) is a solid idea.
If you want help with that, let me know and I can show an example, but it should be relatively easy (if you're not familiar with Lua, then it's still a pretty basic Lua task).
LSP[gopls] 2020/02/17 09:01:01 failed to fix AST: unable to parse defer or go from *ast.BadStmt: no defer or go statement found
This is not diagnostic information but gopls internal error message.
So there may be room for a little better nvim's lsp error handling. I'll fix that later.
@norcalli other lsp plugins for nvim supports it and the experience overall is so much better. Specially if you prefer to run your own diagnostic tools with neomake
@dbarrosop
The LSP notifies the server of the text change each time the text changes. The server will notify you if there are any diagnostics at that time. Neomake runs at any time, so comparing is nonsense.
Anyway, as mentioned above, there is room for improvement, so we will fix that point.
You may disagree and it may or may not be feasible to disable diagnostics client side but it's certainly not nonsense. I'd rather run diagnostics/notify the server when I know I am done typing than having it tell me my syntax is incorrect as I type.
I also don't think disabling the LSP is a suitable workaround to avoid battery drain due to excessive diagnostics. The LSP does way more things than just provide diagnostics.
A better compromise might be to delay notifying the server as the original submitter suggested.
Anyway, I really appreciate the work being done here and hence why I migrated from my previous plugin, to help out testing and provide feedback.
I also don't think disabling the LSP is a suitable workaround to avoid battery drain due to excessive diagnostics. The LSP does way more things than just provide diagnostics.
A better compromise might be to delay notifying the server as the original submitter suggested.
Though this is contradictory. We don't notify the server to get diagnostics specifically, rather _all_ functionality of the server requires frequent updates of buffer to function properly. Especially completion requires the latest text of the line being edited while insert mode is still in progress. Servers are suppose throttle reparsing etc themselves for a reasonable tradeoff between speedy diagnostics and CPU usage. Servers might have their specific options to configure, like clangd allows you to explicitly request or suppress reparsing (though the default works perfectly fine to me), perhaps gopls has something similar?
If you want help with that, let me know and I can show an example, but it should be relatively easy (if you're not familiar with Lua, then it's still a pretty basic Lua task).
I managed to scramble something like this together. Not sure it's the most idiomatic approach though.
do
local default_callback = vim.lsp.callbacks["textDocument/publishDiagnostics"]
local err, method, params, client_id
vim.lsp.callbacks["textDocument/publishDiagnostics"] = function(...)
err, method, params, client_id = ...
if vim.api.nvim_get_mode().mode ~= "i" then
publish_diagnostics()
end
end
function publish_diagnostics()
default_callback(err, method, params, client_id)
end
end
local on_attach = function(_, bufnr)
vim.api.nvim_command [[autocmd InsertLeave <buffer> lua publish_diagnostics()]]
end
nvim_lsp.gopls.setup({on_attach=on_attach})
A response regarding this issue from the gopls maintainer:
https://github.com/golang/go/issues/37259#issuecomment-587584588
The biggest problem with this issue is that nvim-lsp will display internal lsp server errors as blocking messages, thus interrupting typing. Having diagnostics on incomplete statements, while annoying, is not disruptive.
@urandom Could you call :lua vim.lsp.set_log_level('trace') when the issue appears and then take a look at the logs in ~/.local/share/nvim/vim-lsp.log ? (or wherever lua vim.lsp.set_log_level('trace') points to).
I suspect the error message you get that interrupts typing isn't related to diagnostics at all, but rather is something sent to window/showMessage - which is then calling https://github.com/neovim/neovim/blob/c036e24f39481f2b7872659c076b53435192003d/runtime/lua/vim/lsp/callbacks.lua#L208
There's quite a log of output being generated:
https://gist.github.com/urandom/53bccd1a28b0ef865fd82cad244b90af
The output is for when i start typing to some point after the error is displayed. From what I can tell, the error is a result of a didChange message
Well, my suspicion was almost right, it's window/logMessage, not window/showMessage:
[ DEBUG ] 2020-02-23T20:18:35Z+0100 ] /usr/share/nvim/runtime/lua/vim/lsp.lua:367 ] "notification" "window/logMessage" { message = "2020/02/23 20:18:35 failed to fix AST: unable to parse defer or go from *ast.BadStmt: no defer or go statement found", type = 1}
type = 1 is Error.
As a workaround you could set
vim.lsp.callbacks["window/logMessage"] = function log_message(_, _, result, client_id)
local message_type = result.type
local message = result.message
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format("id=%d", client_id)
if not client then
err_message("LSP[", client_name, "] client has shut down after sending the message")
end
local message_type_name = vim.lsp.protocol.MessageType[message_type]
vim.api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
return result
end
It doesn't seem like the workaround is working. It is still sending in a trailing newline, so it will be displayed immediately in a blocking manner, from what I understand.
EDIT: actually, the error is different. It's complaining that it doesn't know what this protocol is
It doesn't seem like the workaround is working. It is still sending in a trailing newline, so it will be displayed immediately in a blocking manner, from what I understand.
EDIT: actually, the error is different. It's complaining that it doesn't know what this
protocolis
Can you change the 2 lines to:
local message_type_name = vim.lsp.protocol.MessageType[message_type]
vim.api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
I missed initially that protocol and api are probably not assigned to some local variable.
With that change I'm no longer seeing the error.
@lithammer, made your solution works for me by changing this line:
if vim.api.nvim_get_mode().mode ~= "i" then
into this:
if vim.api.nvim_get_mode().mode ~= "i" and vim.api.nvim_get_mode().mode ~= "ic" then
Thank you! Would be nice to see such option built-in.
@urandom Would you try this branch? https://github.com/neovim/neovim/pull/11942
@urandom Would you try this branch? neovim/neovim#11942
Would that be with or without the other fix?
Would that be with or without the other fix?
Yes.
Would that be with or without the other fix?
Yes.
Yes is definitely not an appropriate answer for that question ;)
An initial test (overriding the previous fix) doesn't show any errors. I'll keep using it throughout the day to see if anything turns up
@urandom
Previously, both window/logMessage and window/showMessage were displayed on the screen.
The branch simply doesn't display window/logMessage on the screen.
The first problem presented was the above, regardless of textDocument/publishDiagnostics.
What should I add to my configuration if I want to hide all diagnostics? I'm having the same issue where clangd is warning me about syntax errors on every character typed.
To answer my own question, setting textDocument/publishDiagnostics to a function that does nothing seems to do the trick.
setting
textDocument/publishDiagnosticsto a function that does nothing seems to do the trick.
That's actually not a bad way to do it. Guess we can mention it in the docs.
Alternatively use can set message_level = vim.lsp.protocol.MessageType.Error ( https://github.com/neovim/nvim-lspconfig/pull/145 ) and that is as far as we should go (i.e. we don't need https://github.com/neovim/nvim-lspconfig/pull/148 ).
If _errors_ are being printed a lot that's a different problem...
@urandom Is this an issue anymore? see :help vim.lsp.diagnostic
You should be able to add
:lua << EOF
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
vim.lsp.diagnostic.on_publish_diagnostics, {
-- delay update diagnostics
update_in_insert = false,
}
)
EOF
To get what you want.
@mjlbach
It looks like the issue is solved
Most helpful comment
You may disagree and it may or may not be feasible to disable diagnostics client side but it's certainly not nonsense. I'd rather run diagnostics/notify the server when I know I am done typing than having it tell me my syntax is incorrect as I type.
I also don't think disabling the LSP is a suitable workaround to avoid battery drain due to excessive diagnostics. The LSP does way more things than just provide diagnostics.
A better compromise might be to delay notifying the server as the original submitter suggested.
Anyway, I really appreciate the work being done here and hence why I migrated from my previous plugin, to help out testing and provide feedback.