internal/lsp: always show errors from running commands
Some failure modes for suggested fixes were not displaying a failure
message. Refactor so that all errors result in a message.
Fixes golang/go#41413
Change-Id: I939d707626c2bd93c19e28f2197f4f1c66c6947c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/255128
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Robert Findley <rfindley@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index c53d4dc..d4a8ba4 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -42,6 +42,10 @@
if !match {
return nil, fmt.Errorf("%s is not a supported command", command.Name)
}
+ title := command.Title
+ if title == "" {
+ title = command.Name
+ }
// Some commands require that all files are saved to disk. If we detect
// unsaved files, warn the user instead of running the commands.
unsaved := false
@@ -55,50 +59,19 @@
switch params.Command {
case source.CommandTest.Name, source.CommandGenerate.Name, source.CommandToggleDetails.Name:
// TODO(PJW): for Toggle, not an error if it is being disabled
- err := fmt.Errorf("cannot run command %s: unsaved files in the view", params.Command)
- s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
- Type: protocol.Error,
- Message: err.Error(),
- })
+ err := errors.New("unsaved files in the view")
+ s.showCommandError(ctx, title, err)
return nil, err
}
}
// If the command has a suggested fix function available, use it and apply
// the edits to the workspace.
if command.IsSuggestedFix() {
- var uri protocol.DocumentURI
- var rng protocol.Range
- if err := source.UnmarshalArgs(params.Arguments, &uri, &rng); err != nil {
- return nil, err
- }
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, uri, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
- edits, err := command.SuggestedFix(ctx, snapshot, fh, rng)
+ err := s.runSuggestedFixCommand(ctx, command, params.Arguments)
if err != nil {
- return nil, err
+ s.showCommandError(ctx, title, err)
}
- r, err := s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
- Edit: protocol.WorkspaceEdit{
- DocumentChanges: edits,
- },
- })
- if err != nil {
- return nil, err
- }
- if !r.Applied {
- return nil, s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
- Type: protocol.Error,
- Message: fmt.Sprintf("%s failed: %v", params.Command, r.FailureReason),
- })
- }
- return nil, nil
- }
- title := command.Title
- if title == "" {
- title = command.Name
+ return nil, err
}
ctx, cancel := context.WithCancel(xcontext.Detach(ctx))
// Start progress prior to spinning off a goroutine specifically so that
@@ -117,12 +90,7 @@
work.end(title + ": failed")
// Show a message when work completes with error, because the progress end
// message is typically dismissed immediately by LSP clients.
- if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
- Type: protocol.Error,
- Message: fmt.Sprintf("%s: An error occurred: %v", title, err),
- }); err != nil {
- event.Error(ctx, title+": failed to show message", err)
- }
+ s.showCommandError(ctx, title, err)
default:
work.end(command.Name + ": completed")
}
@@ -130,6 +98,46 @@
return nil, nil
}
+func (s *Server) runSuggestedFixCommand(ctx context.Context, command *source.Command, args []json.RawMessage) error {
+ var uri protocol.DocumentURI
+ var rng protocol.Range
+ if err := source.UnmarshalArgs(args, &uri, &rng); err != nil {
+ return err
+ }
+ snapshot, fh, ok, release, err := s.beginFileRequest(ctx, uri, source.Go)
+ defer release()
+ if !ok {
+ return err
+ }
+ edits, err := command.SuggestedFix(ctx, snapshot, fh, rng)
+ if err != nil {
+ return err
+ }
+ r, err := s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
+ Edit: protocol.WorkspaceEdit{
+ DocumentChanges: edits,
+ },
+ })
+ if err != nil {
+ return err
+ }
+ if !r.Applied {
+ return errors.New(r.FailureReason)
+ }
+ return nil
+}
+
+func (s *Server) showCommandError(ctx context.Context, title string, err error) {
+ // Command error messages should not be cancelable.
+ ctx = xcontext.Detach(ctx)
+ if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
+ Type: protocol.Error,
+ Message: fmt.Sprintf("%s failed: %v", title, err),
+ }); err != nil {
+ event.Error(ctx, title+": failed to show message", err)
+ }
+}
+
func (s *Server) runCommand(ctx context.Context, work *workDone, command *source.Command, args []json.RawMessage) error {
switch command {
case source.CommandTest: