1
0
mirror of https://github.com/golang/go synced 2024-11-19 01:54:39 -07:00
go/internal/lsp/generate.go
Marwan Sulaiman 63da46f303 x/tools/gopls: run go generate through CodeLens
This change adds support for recognizing a //go:generate directive
and offering a CodeLens that will then send a "generate" command to
the server to run "go generate" or "go generate ./...". Because
"go generate" can only be executed per package, there is no need to show
the CodeLens on top of every //go:generate comment. Therefore, only the
top directive will be considered.

The stdout/stderr of the go generate command will be piped to the logger
while stderr will also be sent to the editor as a window/showMessage

The user will only know when the process starts and when it ends so that they wouldn't
get bogged with a large number of message windows popping up. However, they can
check the logs for all the details.

If a user wants to cancel the "go generate" command, they will be able
to do so with a "Cancel" ActionItem that the server will offer to the client

Fixes golang/go#37680

Change-Id: I89a9617521eab20859cb2215db133f34fda856c7
Reviewed-on: https://go-review.googlesource.com/c/tools/+/222247
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-03-17 04:34:34 +00:00

126 lines
3.2 KiB
Go

// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lsp
import (
"context"
"io"
"log"
"math/rand"
"strconv"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/telemetry/event"
errors "golang.org/x/xerrors"
)
func (s *Server) runGenerate(ctx context.Context, dir string, recursive bool) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
token := strconv.FormatInt(rand.Int63(), 10)
s.inProgressMu.Lock()
s.inProgress[token] = cancel
s.inProgressMu.Unlock()
defer s.clearInProgress(token)
er := &eventWriter{ctx: ctx}
wc := s.newProgressWriter(ctx, cancel)
defer wc.Close()
args := []string{"-x"}
if recursive {
args = append(args, "./...")
}
inv := &gocommand.Invocation{
Verb: "generate",
Args: args,
Env: s.session.Options().Env,
WorkingDir: dir,
}
stderr := io.MultiWriter(er, wc)
err := inv.RunPiped(ctx, er, stderr)
if err != nil && !errors.Is(ctx.Err(), context.Canceled) {
log.Printf("generate: command error: %v", err)
s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
Type: protocol.Error,
Message: "go generate exited with an error, check gopls logs",
})
}
}
// eventWriter writes every incoming []byte to
// event.Print with the operation=generate tag
// to distinguish its logs from others.
type eventWriter struct {
ctx context.Context
}
func (ew *eventWriter) Write(p []byte) (n int, err error) {
event.Print(ew.ctx, string(p), event.Tag{
Key: &event.Key{
Name: "operation",
},
Value: "generate",
})
return len(p), nil
}
// newProgressWriter returns an io.WriterCloser that can be used
// to report progress on the "go generate" command based on the
// client capabilities.
func (s *Server) newProgressWriter(ctx context.Context, cancel func()) io.WriteCloser {
var wc interface {
io.WriteCloser
start()
}
// TODO(marwan-at-work): add $/progress notifications
wc = &messageWriter{cancel, ctx, s.client}
wc.start()
return wc
}
// messageWriter implements progressWriter
// and only tells the user that "go generate"
// has started through window/showMessage but does not
// report anything afterwards. This is because each
// log shows up as a separate window and therefore
// would be obnoxious to show every incoming line.
type messageWriter struct {
cancel func()
ctx context.Context
client protocol.Client
}
func (lw *messageWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
func (lw *messageWriter) start() {
go func() {
msg, err := lw.client.ShowMessageRequest(lw.ctx, &protocol.ShowMessageRequestParams{
Type: protocol.Log,
Message: "go generate has started, check logs for progress",
Actions: []protocol.MessageActionItem{{
Title: "Cancel",
}},
})
if err != nil {
event.Error(lw.ctx, "error sending initial generate msg", err)
return
}
if msg != nil && msg.Title == "Cancel" {
lw.cancel()
}
}()
}
func (lw *messageWriter) Close() error {
return lw.client.ShowMessage(lw.ctx, &protocol.ShowMessageParams{
Type: protocol.Info,
Message: "go generate has finished",
})
}