Bug: Async formatting causing buffer content to mix up

I’m using EFM + prettier for formatting my files. When I save multiple files at once :wa and use async formatting, the content of those buffers will get replaced with the wrong buffer. E.g. line 5 from buffer X will go into line 5 of buffer Y, while line 20 of buffer Y will go into line 20 of buffer X. This doesn’t happen when I use sync formatting. Any idea why this is happening?

Here is the relevant config for reference

-- lets us use efm formatting instead of other servers like tsserver
local prioritize_efm_formatting
function custom_formatting()
	if not prioritize_efm_formatting then
		local clients = vim.lsp.buf_get_clients(0)
		if #clients > 1 then
			-- check if multiple clients, and if efm is setup
			for _,c1 in pairs(clients) do
				if c1.name == "efm" then
					-- if efm then disable others
					for _,c2 in pairs(clients) do
						if c2.name ~= "efm" then c2.resolved_capabilities.document_formatting = false end
					end
					-- no need to contunue first loop
					break
				end
			end
		end
	end
	-- no need to do above check again
	prioritize_efm_formatting = true
	-- seems like async mode will mess up the content if multiple buffers are saved at once
        vim.lsp.buf.formatting()
end

-- Use an on_attach function to only map the following keys after the language server attaches to the current buffer
local on_attach = function(client, bufnr)
	local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end
	local function buf_set_option(...) vim.api.nvim_buf_set_option(bufnr, ...) end

	--Enable completion triggered by <c-x><c-o>
	buf_set_option('omnifunc', 'v:lua.vim.lsp.omnifunc')
	-- reference highlighting
	require 'illuminate'.on_attach(client)

	-- Mappings.
	local opts = { noremap=true, silent=true }

	-- See `:help vim.lsp.*` for documentation on any of the below functions
	buf_set_keymap('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<cr>', opts)
	buf_set_keymap('n', 'gD', '<cmd>lua vim.lsp.buf.type_definition()<cr>', opts)opts)
	buf_set_keymap('n', '<leader>D', '<cmd>lua vim.lsp.diagnostic.set_loclist()<cr>', opts)
	buf_set_keymap('n', 'g=', '<cmd>lua custom_formatting()<cr>', opts)
	buf_set_keymap('n', '<leader>oi', '<cmd>lua organize_imports()<cr>', opts)print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<cr>', opts)

	-- auto format
	if client.resolved_capabilities.document_formatting then
		vim.cmd [[augroup AutoFormat]]
		vim.cmd [[autocmd! * <buffer>]]
		vim.cmd [[autocmd BufWritePre <buffer> lua custom_formatting()]]
		vim.cmd [[augroup END]]
	end
end

The buffer utility functions are meant to be used on the current buffer. You can verify the behavior vim.api.nvim_get_current_buf() by adding the following to your init.lua:

vim.cmd [[autocmd BufEnter * autocmd BufWritePre <buffer> lua print(vim.api.nvim_get_current_buf())]]

You should be able to make an autocommand which calls the following
lua vim.lsp.get_client_by_id(id_from_on_attach).request("textDocument/formatting", {}, nil, buf_from_on_attach) with some string substitution in on_attach (you can also use vim.fn.bufnr())

I think this should behave the way you want it to, if not LMK

Thanks for the help. I’m a little confused about how to go about that.

I tried adding the autocmd and saw multiple buffer numbers printed sequentially.
Then I tried updating the format function like so. I don’t know how to pass the id from the on_attach so I’m improvising. Now it’s not even formatting.

local clients = vim.lsp.buf_get_clients(vim.fn.bufnr())
--- c1 is from clients
c1.request("textDocument/formatting", vim.lsp.util.make_formatting_params(), nil, vim.fn.bufnr())

Can you try fix(lsp): pass bufnr for async formatting by mjlbach · Pull Request #15084 · neovim/neovim · GitHub

Now I’m getting another error:

0: invalid uri: only file URIs are supported, got d: D:\[...path-to-file...]

Here’s my function for reference:

function custom_formatting()
		local clients = vim.lsp.buf_get_clients(0)
		if #clients > 1 then
			for _,c1 in pairs(clients) do
				if c1.name == "efm" then
					c1.request("textDocument/formatting", vim.lsp.util.make_formatting_params(), nil, vim.fn.bufnr())
					break
				end
			end
		end
end