LSP formatting doesn't always work

As I shared previously on reddit, I was using GitHub - mhartington/formatter.nvim (works great) & then decided to give LSP formatting a try, instead of having too many configs LSP, LSP for linters (efm, null-ls, etc…) & formatting.

After setting it up autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync(), it doesn’t always run & I tried it with both efm & null-ls so I don’t think it’s a plugin issue.

I tried all of these or variants of them

vim.lsp.buf.formatting()
vim.lsp.buf.formatting_sync()
vim.lsp.buf.formatting_seq_sync()

Also tried with timeouts like vim.lsp.buf.formatting_sync(nil, 2000) etc…

The only way was to always run LspRestart which is a bit annoying to do every time you want to save a file. So I wonder how to solve this issue? because it’s a deal-breaker for me.

When you see the issue, what’s the output of :LspInfo? Perhaps it’s the bug where clients are randomly getting detached from buffers? (See Prevent detaching LSP Clients)

What do you see in the logs? Can you try setting the log level to trace and see if you see the requests going out to the server?

Looks like it’s already attached LspInfo shows this info & also null-ls.log too

 Language client log: /Users/ahmed/.cache/nvim/lsp.log
 Detected filetype:   typescript
 
 4 client(s) attached to this buffer: 
 
 Client: null-ls (id: 2, pid: 5001, bufnr: [14, 16, 20])
 	filetypes:       reason, rescript, nix, mdx, typescriptreact, typescript.tsx, javascriptreact, javascript.jsx, scss, yaml, markdown, graphql, html, json, javascript, css, less, typescript, vue, lua, go, bash, sh, rust, python, dockerfile, vim, tex
 	autostart:       false
 	root directory:  /Users/ahmed/foo
 	cmd:             nvim
 
 Client: tsserver (id: 3, pid: 51342, bufnr: [16])
 	filetypes:       javascript, javascriptreact, javascript.jsx, typescript, typescriptreact, typescript.tsx
 	autostart:       true
 	root directory:  /Users/ahmed/foo
 	cmd:             typescript-language-server --stdio
 
 Client: eslint (id: 4, pid: 51343, bufnr: [16])
 	filetypes:       javascript, javascriptreact, javascript.jsx, typescript, typescriptreact, typescript.tsx, vue
 	autostart:       true
 	root directory:  /Users/ahmed/foo
 	cmd:             vscode-eslint-language-server --stdio
 
 Client: tailwindcss (id: 5, pid: 51344, bufnr: [20, 16])
 	filetypes:       aspnetcorerazor, astro, astro-markdown, blade, django-html, edge, eelixir, ejs, erb, eruby, gohtml, haml, handlebars, hbs, html, html-eex, heex, jade, leaf, liquid, markdown, mdx, mustache, njk, nunjucks, php, razor, slim, twig, css, less, postcss, sass, scss, stylus, sugarss, javascript, javascriptreact, reason, rescript, typescript, typescriptreact, vue, svelte
 	autostart:       true
 	root directory:  /Users/ahmed/foo
 	cmd:             tailwindcss-language-server --stdio
 
 3 active client(s) not attached to this buffer: 
 
 Client: jsonls (id: 1, pid: 51238, bufnr: [14])
 	filetypes:       json, jsonc
 	autostart:       true
 	root directory:  /Users/ahmed/foo
 	cmd:             vscode-json-language-server --stdio
 
 Client: tsserver (id: 6, pid: 52167, bufnr: [20])
 	filetypes:       javascript, javascriptreact, javascript.jsx, typescript, typescriptreact, typescript.tsx
 	autostart:       true
 	root directory:  /Users/ahmed/foo
 	cmd:             typescript-language-server --stdio
 
 Client: eslint (id: 7, pid: 52168, bufnr: [20])
 	filetypes:       javascript, javascriptreact, javascript.jsx, typescript, typescriptreact, typescript.tsx, vue
 	autostart:       true
 	root directory:  /Users/ahmed/foo
 	cmd:             vscode-eslint-language-server --stdio
 
 Other clients that match the filetype: typescript
 
 Config: denols
 	filetypes:         javascript, javascriptreact, javascript.jsx, typescript, typescriptreact, typescript.tsx
 	root directory:    NA
 	cmd:               deno lsp
 	cmd is executable: true
 	autostart:         true
 	custom handlers:   textDocument/hover, textDocument/signatureHelp
 
 Configured servers list: tsserver, yamlls, denols, html, rnix, jsonls, cssls, bashls, vimls, pyright, dockerls, clojure_lsp, eslint, tailwindcss, sumneko_lua, rust_analyzer, gopls

and this is null-ls.log

[TRACE Tue 18 Jan 15:32:46 2022] .../site/pack/packer/start/null-ls.nvim/lua/null-ls/rpc.lua:143: received LSP notification for method textDocument/didChange
[TRACE Tue 18 Jan 15:32:46 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:21: running generators for method NULL_LS_DIAGNOSTICS
[DEBUG Tue 18 Jan 15:32:46 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:24: no generators available
[TRACE Tue 18 Jan 15:32:48 2022] .../site/pack/packer/start/null-ls.nvim/lua/null-ls/rpc.lua:143: received LSP notification for method textDocument/didSave
[TRACE Tue 18 Jan 15:32:48 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:21: running generators for method NULL_LS_DIAGNOSTICS_ON_SAVE
[DEBUG Tue 18 Jan 15:32:48 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:24: no generators available
[TRACE Tue 18 Jan 15:33:21 2022] .../site/pack/packer/start/null-ls.nvim/lua/null-ls/rpc.lua:143: received LSP notification for method textDocument/didSave
[DEBUG Tue 18 Jan 15:33:21 2022] ...ck/packer/start/null-ls.nvim/lua/null-ls/diagnostics.lua:142: buffer unchanged; ignoring didSave notification
[TRACE Tue 18 Jan 15:34:07 2022] .../site/pack/packer/start/null-ls.nvim/lua/null-ls/rpc.lua:143: received LSP notification for method textDocument/didSave
[DEBUG Tue 18 Jan 15:34:07 2022] ...ck/packer/start/null-ls.nvim/lua/null-ls/diagnostics.lua:142: buffer unchanged; ignoring didSave notification
[TRACE Tue 18 Jan 15:34:18 2022] .../site/pack/packer/start/null-ls.nvim/lua/null-ls/rpc.lua:143: received LSP notification for method textDocument/didChange
[TRACE Tue 18 Jan 15:34:18 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:21: running generators for method NULL_LS_DIAGNOSTICS
[DEBUG Tue 18 Jan 15:34:18 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:24: no generators available
[TRACE Tue 18 Jan 15:34:18 2022] .../site/pack/packer/start/null-ls.nvim/lua/null-ls/rpc.lua:143: received LSP notification for method textDocument/didChange
[TRACE Tue 18 Jan 15:34:18 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:21: running generators for method NULL_LS_DIAGNOSTICS
[DEBUG Tue 18 Jan 15:34:18 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:24: no generators available
[TRACE Tue 18 Jan 15:34:18 2022] .../site/pack/packer/start/null-ls.nvim/lua/null-ls/rpc.lua:143: received LSP notification for method textDocument/didSave
[TRACE Tue 18 Jan 15:34:18 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:21: running generators for method NULL_LS_DIAGNOSTICS_ON_SAVE
[DEBUG Tue 18 Jan 15:34:18 2022] ...ack/packer/start/null-ls.nvim/lua/null-ls/generators.lua:24: no generators available

While it receives the events, it doesn’t actually run anything even if I change the file contents to force a reformat.

Oh interesting, does it format the code if you manually invoke vim.lsp.buf.formatting()? (or whatever the function name is, sorry I’m afk right now).

Can you share the lsp logs too (the client logs)? It’s usually at ~/.cache/nvim/lsp.log.

yes, it works when I manually trigger any of those

vim.lsp.buf.formatting()
vim.lsp.buf.formatting_sync()
vim.lsp.buf.formatting_seq_sync()

Can you share the lsp logs too (the client logs)? It’s usually at ~/.cache/nvim/lsp.log .

There is nothing related to null-ls or formatting there at all, unless I’m doing something wrong.

Oh weird. How are you setting up the autocmd? What’s the output of autocmd BufWritePre? :thinking:

This is how I’m setting the autocmd dotfiles/lsp.lua at 9dac9de67d9104884428bd95a3decc49901d700e · ahmedelgabri/dotfiles · GitHub

And this is the output of autocmd BufWritePre

:autocmd BufWritePre
--- Autocommands ---
repeatPlugin  BufWritePre
    *         let g:repeat_tick = (g:repeat_tick == b:changedtick || g:repeat_tick == 0) ? 0 : -1
__my_whitespace__  BufWritePre
    *         lua _.g.autocommand_callbacks._9()
easydir  BufWritePre
    *         call <SID>create_and_save_directory()
eunuch  BufWritePre
    *         if exists('b:eunuch_new_file') && getline(1) =~ '^#!\s*/' |   let b:chmod_post = '+x' | endif
fugitive_dummy_events  BufWritePre
    *         "
__LSP_FORMATTING__  BufWritePre
    <buffer=3>
              lua _.g.autocommand_callbacks._13()
Press ENTER or type command to continue

Does formatting work if you manually invoke lua _.g.autocommand_callbacks._9()? Trying to think exactly where things are falling apart :thinking:

I guess you meant lua _.g.autocommand_callbacks._13(), calling it works fine.

Oh boy, not really sure what’s going on. Did you have any luck with print debugging? :grimacing: like maybe the function is being called but mkview is failing silently idk

I’ve had this exact issue ever since I moved to the native LSP over a year ago now, I’ve never been able to pin it down. I might try using a very minimal config and seeing if it still causes this issue

Formatting requests are pretty simple, I can’t imagine that we are doing anything wrong client side if you are running a single, “true” language server that supports formatting (not null-ls, efm, diagnostic-ls, etc.)

I dunno about @akinsho but these are exactly the ones I was testing (I tested null-ls & efm, the issue happens with both)

My assumption is that most people will be using these LSP for formatting more than the formatting which comes with the “true” LSPs, because formatters are usually different. I have never worked in a single project where had tsserver formatting used or enabled, but all used prettier, even languages like Go or Rust the formatters which are usually used are not what the LSP provides.

I dunno about @akinsho but these are exactly the ones I was testing (I tested null-ls & efm, the issue happens with both)

Yeah, while I would say EFM is officially supported (although it frequently seems brittle since it relies on user configuration of the formatter), null-ls is not supported, since it runs in-process and overrides the RPC loop.

In my case I see this issue in all file types I see it when working with dart using the official LSP to format which is the standard for dart, however it still occasionally fails to run unless I run it manually. It always works when I use the direct command/function, but the autocommand sporadically fails. I also see the same in Lua files, which I format using stylua via null-ls. It’s also worth mentioning that I had this issue before I set up null-ls (although I did use EFM before that) not sure if there could be some sort of conflict, but my dart setup doesn’t use EFM or null ls to format only the dart language server

1 Like

One thing to note, is that certain formatters will not format under some conditions. IE, if there is a syntax error in a python file, then Black will not format it.

But that’s expected behavior most of the time, but I don’t think it should kill the whole LSP, right? I expect that when I fix the syntax error that the formatter will run again.

Yes, it should be fine the next time the autocommand is invoked. Anyways, this is going to be very hard to diagnose without a reproduction but I am extremely skeptical the error is in how we are handling formatting requests.

Understood, it’s hard to pin down without a minimal repro. I’ll try and see if I can get something together. FWIW, I think it has something to do with the timeout for the function. I’ve noticed that I can reproduce this behaviour by dropping the number. I think in the case of relatively slow language servers such as dartls which is where I see this most the default timeout_ms of 1000 is definitely too quick and 2 is usually good on a fast machine but on a slower machine it’s still too quick (for context by fast and slow I mean an i7 2019 mac and an M1 mac, so not really slow, but more of an issue for dartls probs).

I experience the same behavior when trying to format my source code.

It just does nothing.

I use the commands:

:lua vim.lsp.buf.formatting_sync()
:lua vim.lsp.buf.formatting()

I do not have the null-ls plugin installed.

Here is my full configuration

The LSP seems to be okay:

Detected filetype:   typescript
1 client(s) attached to this buffer:
Client: tsserver (id: 1, pid: 212918, bufnr: [1])