I'm struggling to figure out how to make an autocmd to call the editor.action.organizeImports command that gopls offers. For coc.nvim they provide the following config https://github.com/golang/tools/blob/master/gopls/doc/vim.md#cocnvim
Not sure if this is the write project to add this type of support or if it is just a lua function that calls the built in vim.lsp functions. Any help would be appreciated though.
Just winging it here, but something like this could be a starting point:
autocmd BufWritePost * lua vim.lsp.buf.formatting()
It's pretty naive. So using the on_attach callback is probably a better approach:
local nvim_lsp = require("nvim_lsp")
local on_attach = function(_, bufnr)
vim.api.nvim_command("au BufWritePost <buffer> lua vim.lsp.buf.formatting()")
end
nvim_lsp.gopls.setup({ on_attach = on_attach })
It works, kind of, but leaves the file a 'modified' state which isn't desirable. So there's at least one missing piece.
So that autocmd works alright, however gopls separates out updating imports as a separate action than formatting. I guess my question is how to call a custom lsp action (not sure the lsp terms here).
So that autocmd works alright, however
goplsseparates out updating imports as a separate action than formatting. I guess my question is how to call a custom lsp action (not sure the lsp terms here).
What you want to run is a code action. "Organize imports" is one of the gopls code actions.
It works, kind of, but leaves the file a 'modified' state which isn't desirable. So there's at least one missing piece.
The formatting params in https://github.com/neovim/neovim/blob/9678fe4cfba9f7a9dacbd6d5a56c58241e98aa60/runtime/lua/vim/lsp/buf.lua#L73-L85 could be extracted into a helper function, and there could be an additional formatting_sync function. Something along the following lines:
local function formatting_params(options)
validate { options = {options, 't', true} }
local sts = vim.bo.softtabstop;
options = vim.tbl_extend('keep', options or {}, {
tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop;
insertSpaces = vim.bo.expandtab;
})
return {
textDocument = { uri = vim.uri_from_bufnr(0) };
options = options;
}
end
function M.formatting(options)
return request('textDocument/formatting', params)
end
function M.formatting_sync(options, timeout)
local params = formatting_params(options)
local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout)
if not result then return end
result = result[1].result
vim.lsp.util.apply_text_edits(result)
end
This would allow formatting to be run synchronously on BufWritePre (possibly increasing the default 100ms timeout to e.g. 1000ms), so that the file isn't left in a modified state:
vim.api.nvim_command("au BufWritePre <buffer> lua vim.lsp.buf.formatting_sync(nil, 1000)")
I know OP actually wants something slightly different, but since I got here looking for the above and got halfway there thanks to @lithammer's snippet, I figured I'd post the rest since it might help other people too :)
Would you be willing to accept a PR adding a formatting_sync function to vim.lsp.buf? Or do you feel that a more general solution is needed, since there are other actions which might need to be run in a synchronous fashion (e.g. on save), like that "organize imports" code action of gopls?
Would you be willing to accept a PR adding a
formatting_syncfunction tovim.lsp.buf? Or do you feel that a more general solution is needed, since there are other actions which might need to be run in a synchronous fashion (e.g. on save), like that "organize imports" code action ofgopls?
I think there's a reason why a formatting_sync equivalent exists in most LSP clients: many people use it. I'm not a Neovim maintainer, but IMO, it would be a nice addition to the official API. Otherwise people will either have to implement the function themselves or use a third-party plugin.
@dlukes I think that would make a good PR where we can talk about where that should live and how we can add improvements like this. If you make the PR and place it somewhere sensible to start, we can chat about it in that PR (which would be a better place).
(As a note, the PR would be on neovim, not nvim-lsp. Just to be clear)
as of neovim/neovim#11607
autocmd BufWritePre *.go lua vim.lsp.buf.code_action({ source = { organizeImports = true } })
this seems to kinda work, except it always prompts you what to do
So i'm still testing right now but this is what I'm trying out. I dont think its working quite right though but it at least doesn't prompt me.
-- organize imports sync
function go_org_imports(options, timeout_ms)
local context = { source = { organizeImports = true } }
vim.validate { context = { context, 't', true } }
local params = vim.lsp.util.make_range_params()
params.context = context
local results = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, timeout_ms)
print(vim.inspect(result))
if not result then return end
vim.lsp.util.apply_text_edits(result[1].result)
-- local params = vim.lsp.util.make_formatting_params(options)
-- local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms)
-- if not result then return end
-- result = result[1].result
-- vim.lsp.util.apply_text_edits(result)
end
vim.api.nvim_command("au BufWritePre *.go lua go_org_imports({}, 1000)")
ah cool thanks for sharing @tkinz27
@tkinz27 your example doesn't work because the return type of textDocument/codeAction is different from textDocument/formatting. Only after figuring this out by trial and error did I realize I should've looked at nvim-lsp's own textDocument/codeAction implementation.
The modified (verified working as of right now) version is:
-- Synchronously organise (Go) imports.
function go_organize_imports_sync(timeout_ms)
local context = { source = { organizeImports = true } }
vim.validate { context = { context, 't', true } }
local params = vim.lsp.util.make_range_params()
params.context = context
local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, timeout_ms)
if not result then return end
result = result[1].result
if not result then return end
edit = result[1].edit
vim.lsp.util.apply_workspace_edit(edit)
end
Though it's not perfect, because we should be emulating the codeAction handler to be resistant to changes in gopls implementation.
@aktau thanks for the update, yeah I hadn't really verified that mine was working. Been stuck workign on terraform stuff and haven't worked in go for a little while. Thanks for sharing.
Thanks for your report. The configs here are best-effort:
It is hoped that these configurations serve as a "source of truth", but they are strictly best effort. If something doesn't work, these configs are useful as a starting point, which you can adjust to fit your environment.
lsp module, the best way to get it fixed is to describe steps to reproduce it without the particular LSP server and other factors particular to your environment. Or better, by adding a failing test case to lsp_spec.lua, which has code to setup a fake LSP server to simulate various scenarios.Sadly using latest neovim HEAD with gopls 0.6.1 and latest version of nvim-lspconfig it seems that while this code works, it doesn't really work when you are trying to import non standard library modules like dependencies in go.mod and vendored.
Executing a print(vim.inspect(result)) of the call returns { {} } if I remove a module, say "go.uber.org/zap", while it returns a nicer full table if I remove "time".
Most helpful comment
@tkinz27 your example doesn't work because the return type of
textDocument/codeActionis different fromtextDocument/formatting. Only after figuring this out by trial and error did I realize I should've looked at nvim-lsp's owntextDocument/codeActionimplementation.The modified (verified working as of right now) version is:
Though it's not perfect, because we should be emulating the
codeActionhandler to be resistant to changes in gopls implementation.