x/tools/gopls: add willSaveWaitUntil support for formatting and goimports
#57,281 opened on Dec 13, 2022
Repository metrics
- Stars
- (133,883 stars)
- PR merge metrics
- (No merged PRs in 30d)
Description
gopls version
$ gopls -v version
Build info
----------
golang.org/x/tools/gopls v0.10.1
golang.org/x/tools/gopls@v0.10.1 h1:JoHe17pdZ8Vsa24/GUO8iTVTKPh0EOBiWpPop7XJybI=
github.com/BurntSushi/toml@v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
github.com/google/go-cmp@v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/sergi/go-diff@v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
golang.org/x/exp@v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
golang.org/x/exp/typeparams@v0.0.0-20220722155223-a9213eeb770e h1:7Xs2YCOpMlNqSQSmrrnhlzBXIE/bpMecZplbLePTJvE=
golang.org/x/mod@v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/sync@v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sys@v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/text@v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/tools@v0.2.1-0.20221101170700-b5bc717366b2 h1:KBm+UwBaO/tdQ35tfGvxH1FUCiXRg4MoTzkznsdeab8=
golang.org/x/vuln@v0.0.0-20221010193109-563322be2ea9 h1:KaYZQUtEEaV8aVADIHAuYBTjo77aUcCvC7KTGKM3J1I=
honnef.co/go/tools@v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA=
mvdan.cc/gofumpt@v0.3.1 h1:avhhrOmv0IuvQVK7fvwV91oFSGAk5/6Po8GXTzICeu8=
mvdan.cc/xurls/v2@v2.4.0 h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc=
go: go1.20-dev +a813be86df
go env
If this is relevant, I'll fill it in, just ask. For now I consider this spam.
What did you do?
After Neovim added support for textDocument/willSaveWaitUntil (https://github.com/neovim/neovim/pull/21315). I started gopls to see if it supports this as it can save a roundtrip and would allow me to get rid of my gopls-specific goimports stanza. It currently looks like this:
-- on_attach handler
-- ...
-- Enable format-on-save when available (see :help lsp-config).
--
-- TODO: When gopls implements willSaveWaitUntil, remove this.
if client.server_capabilities.documentFormattingProvider then
-- With gopls, textDocument/formatting only runs gofmt. If we also want
-- goimports a specific code action. See
-- https://github.com/Microsoft/language-server-protocol/issues/726.
if vim.bo[bufnr].filetype == "go" then
aucmd("BufWritePre", function() goimports() end)
end
aucmd("BufWritePre", function() vim.lsp.buf.format({timeout_ms=1000}) end)
end
-- ...
local function goimports()
local params = vim.lsp.util.make_range_params()
params.context = { only = { "source.organizeImports" } }
local response = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, 1000)
for _, r in pairs(response or {}) do
for _, action in pairs(r.result or {}) do
-- textDocument/codeAction can return either Command[] or CodeAction[]. If
-- it is a CodeAction, it can have either an edit, a command or both.
-- Edits should be executed first.
if action.edit then
vim.lsp.util.apply_workspace_edit(action.edit, "UTF-8")
end
if action.command then
-- If the response was a Command[], then the inner "command' is a
-- string, if the response was a CodeAction, then the inner command is a
-- Command.
local command = type(action.command) == "table" and action.command or action
vim.lsp.buf.execute_command(command)
end
end
end
end
What did you expect to see?
I expected the server_capabilities to contain:
server_capabilities = {
-- ...
textDocumentSync = {
-- ...
willSaveWaitUntil = true,
},
}
So that Neovim (and other LSP clients that support this) can notify gopls before saving and apply last-minute edits returned from gopls.
What did you see instead?
The server_capabilities does not contain what I expected.
Editor and settings
Neovim 0.9.0-dev (HEAD as of today). The client capabilities are:
{
callHierarchy = {
dynamicRegistration = false
},
textDocument = {
codeAction = {
codeActionLiteralSupport = {
codeActionKind = {
valueSet = { "", "quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", "source", "source.organizeImports" }
}
},
dataSupport = true,
dynamicRegistration = false,
isPreferredSupport = true,
resolveSupport = {
properties = { "edit" }
}
},
completion = {
completionItem = {
commitCharactersSupport = true,
deprecatedSupport = true,
documentationFormat = { "markdown", "plaintext" },
insertReplaceSupport = true,
insertTextModeSupport = {
valueSet = { 1, 2 }
},
labelDetailsSupport = true,
preselectSupport = true,
resolveSupport = {
properties = { "documentation", "detail", "additionalTextEdits" }
},
snippetSupport = true,
tagSupport = {
valueSet = { 1 }
}
},
completionItemKind = {
valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }
},
completionList = {
itemDefaults = { "commitCharacters", "editRange", "insertTextFormat", "insertTextMode", "data" }
},
contextSupport = true,
dynamicRegistration = false,
insertTextMode = 1
},
declaration = {
linkSupport = true
},
definition = {
linkSupport = true
},
documentHighlight = {
dynamicRegistration = false
},
documentSymbol = {
dynamicRegistration = false,
hierarchicalDocumentSymbolSupport = true,
symbolKind = {
valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }
}
},
hover = {
contentFormat = { "markdown", "plaintext" },
dynamicRegistration = false
},
implementation = {
linkSupport = true
},
publishDiagnostics = {
relatedInformation = true,
tagSupport = {
valueSet = { 1, 2 }
}
},
references = {
dynamicRegistration = false
},
rename = {
dynamicRegistration = false,
prepareSupport = true
},
semanticTokens = {
augmentsSyntaxTokens = true,
dynamicRegistration = false,
formats = { "relative" },
multilineTokenSupport = false,
overlappingTokenSupport = true,
requests = {
full = {
delta = true
},
range = false
},
serverCancelSupport = false,
tokenModifiers = { "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary" },
tokenTypes = { "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator" }
},
signatureHelp = {
dynamicRegistration = false,
signatureInformation = {
activeParameterSupport = true,
documentationFormat = { "markdown", "plaintext" },
parameterInformation = {
labelOffsetSupport = true
}
}
},
synchronization = {
didSave = true,
dynamicRegistration = false,
willSave = true,
willSaveWaitUntil = true
},
typeDefinition = {
linkSupport = true
}
},
window = {
showDocument = {
support = true
},
showMessage = {
messageActionItem = {
additionalPropertiesSupport = false
}
},
workDoneProgress = true
},
workspace = {
applyEdit = true,
configuration = true,
semanticTokens = {
refreshSupport = true
},
symbol = {
dynamicRegistration = false,
hierarchicalWorkspaceSymbolSupport = true,
symbolKind = {
valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }
}
},
workspaceEdit = {
resourceOperations = { "rename", "create", "delete" }
},
workspaceFolders = true
}
}