internal/lsp: run source.organizeImports on all codeActions

This change addresses the additional information we received in
https://github.com/Microsoft/language-server-protocol/issues/726.

Fixes golang/go#31359

Change-Id: Ied7c97ac1f9e429e28cc44563b93acabd80921a3
Reviewed-on: https://go-review.googlesource.com/c/tools/+/172406
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-04-16 17:28:20 -04:00
parent e2848a0e7d
commit dd61c98fae
1 changed files with 44 additions and 55 deletions

View File

@ -26,57 +26,42 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
return nil, err return nil, err
} }
var codeActions []protocol.CodeAction var codeActions []protocol.CodeAction
// Determine what code actions we should take based on the diagnostics. // TODO(rstambler): Handle params.Context.Only when VSCode-Go uses a
if findImportErrors(params.Context.Diagnostics) { // version of vscode-languageclient that fixes
edits, err := organizeImports(ctx, view, spn) // https://github.com/Microsoft/vscode-languageserver-node/issues/442.
if err != nil { edits, err := organizeImports(ctx, view, spn)
return nil, err if err != nil {
} return nil, err
if len(edits) > 0 { }
// TODO(rstambler): Handle params.Context.Only when VSCode-Go uses a if len(edits) > 0 {
// version of vscode-languageclient that fixes codeActions = append(codeActions, protocol.CodeAction{
// https://github.com/Microsoft/vscode-languageserver-node/issues/442. Title: "Organize Imports",
Kind: protocol.SourceOrganizeImports,
Edit: &protocol.WorkspaceEdit{
Changes: &map[string][]protocol.TextEdit{
string(spn.URI()): edits,
},
},
})
// If we also have diagnostics, we can associate them with quick fixes.
if findImportErrors(params.Context.Diagnostics) {
// TODO(rstambler): Separate this into a set of codeActions per diagnostic,
// where each action is the addition or removal of one import.
// This can only be done when https://golang.org/issue/31493 is resolved.
codeActions = append(codeActions, protocol.CodeAction{ codeActions = append(codeActions, protocol.CodeAction{
Title: "Organize Imports", Title: "Organize All Imports", // clarify that all imports will change
Kind: protocol.SourceOrganizeImports, Kind: protocol.QuickFix,
Edit: &protocol.WorkspaceEdit{ Edit: &protocol.WorkspaceEdit{
Changes: &map[string][]protocol.TextEdit{ Changes: &map[string][]protocol.TextEdit{
string(spn.URI()): edits, string(uri): edits,
}, },
}, },
}) })
// Add any quick fixes for each import-related diagnostic that we see.
fixes, err := quickFixes(spn.URI(), params.Context.Diagnostics, edits)
if err != nil {
return nil, err
}
codeActions = append(codeActions, fixes...)
} }
} }
return codeActions, nil return codeActions, nil
} }
// findImports determines if a given diagnostic represents an error that could
// be fixed by organizing imports.
// TODO(rstambler): We need a better way to check this than string matching.
func findImportErrors(diagnostics []protocol.Diagnostic) bool {
for _, diagnostic := range diagnostics {
// "undeclared name: X" may be an unresolved import.
if strings.HasPrefix(diagnostic.Message, "undeclared name: ") {
return true
}
// "could not import: X" may be an invalid import.
if strings.HasPrefix(diagnostic.Message, "could not import: ") {
return true
}
// "X imported but not used" is an unused import.
if strings.HasSuffix(diagnostic.Message, " imported but not used") {
return true
}
}
return false
}
func organizeImports(ctx context.Context, v source.View, s span.Span) ([]protocol.TextEdit, error) { func organizeImports(ctx context.Context, v source.View, s span.Span) ([]protocol.TextEdit, error) {
f, m, err := newColumnMap(ctx, v, s.URI()) f, m, err := newColumnMap(ctx, v, s.URI())
if err != nil { if err != nil {
@ -101,19 +86,23 @@ func organizeImports(ctx context.Context, v source.View, s span.Span) ([]protoco
return ToProtocolEdits(m, edits) return ToProtocolEdits(m, edits)
} }
// TODO(rstambler): Separate this into a set of codeActions per diagnostic, // findImports determines if a given diagnostic represents an error that could
// where each action is the addition or removal of one import. // be fixed by organizing imports.
// This can only be done when https://golang.org/issue/31493 is resolved. // TODO(rstambler): We need a better way to check this than string matching.
func quickFixes(uri span.URI, diagnostics []protocol.Diagnostic, edits []protocol.TextEdit) ([]protocol.CodeAction, error) { func findImportErrors(diagnostics []protocol.Diagnostic) bool {
return []protocol.CodeAction{ for _, diagnostic := range diagnostics {
{ // "undeclared name: X" may be an unresolved import.
Title: "Organize All Imports", if strings.HasPrefix(diagnostic.Message, "undeclared name: ") {
Kind: protocol.QuickFix, return true
Edit: &protocol.WorkspaceEdit{ }
Changes: &map[string][]protocol.TextEdit{ // "could not import: X" may be an invalid import.
string(uri): edits, if strings.HasPrefix(diagnostic.Message, "could not import: ") {
}, return true
}, }
}, // "X imported but not used" is an unused import.
}, nil if strings.HasSuffix(diagnostic.Message, " imported but not used") {
return true
}
}
return false
} }