| // 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" |
| "math/rand" |
| "strconv" |
| |
| "golang.org/x/tools/internal/gocommand" |
| "golang.org/x/tools/internal/lsp/debug/tag" |
| "golang.org/x/tools/internal/lsp/protocol" |
| "golang.org/x/tools/internal/telemetry/event" |
| "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, token) |
| 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 { |
| event.Error(ctx, "generate: command error: %v", err, tag.Directory.Of(dir)) |
| if !xerrors.Is(err, context.Canceled) { |
| 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), tag.Operation.Of("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(), token string) io.WriteCloser { |
| var wc interface { |
| io.WriteCloser |
| start() |
| } |
| if s.supportsWorkDoneProgress { |
| wc = &workDoneWriter{ctx, token, s.client} |
| } else { |
| wc = &messageWriter{ctx, cancel, 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. |
| // Request cancellation happens synchronously through |
| // the ShowMessageRequest response. |
| type messageWriter struct { |
| ctx context.Context |
| cancel func() |
| 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", |
| }) |
| } |
| |
| // workDoneWriter implements progressWriter |
| // that will send $/progress notifications |
| // to the client. Request cancellations |
| // happens separately through the |
| // window/workDoneProgress/cancel request |
| // in which case the given context will be rendered |
| // done. |
| type workDoneWriter struct { |
| ctx context.Context |
| token string |
| client protocol.Client |
| } |
| |
| func (wdw *workDoneWriter) Write(p []byte) (n int, err error) { |
| return len(p), wdw.client.Progress(wdw.ctx, &protocol.ProgressParams{ |
| Token: wdw.token, |
| Value: &protocol.WorkDoneProgressReport{ |
| Kind: "report", |
| Cancellable: true, |
| Message: string(p), |
| }, |
| }) |
| } |
| |
| func (wdw *workDoneWriter) start() { |
| err := wdw.client.WorkDoneProgressCreate(wdw.ctx, &protocol.WorkDoneProgressCreateParams{ |
| Token: wdw.token, |
| }) |
| if err != nil { |
| event.Error(wdw.ctx, "generate progress create", err) |
| return |
| } |
| err = wdw.client.Progress(wdw.ctx, &protocol.ProgressParams{ |
| Token: wdw.token, |
| Value: &protocol.WorkDoneProgressBegin{ |
| Kind: "begin", |
| Cancellable: true, |
| Message: "running go generate", |
| Title: "generate", |
| }, |
| }) |
| if err != nil { |
| event.Error(wdw.ctx, "generate progress begin", err) |
| } |
| } |
| |
| func (wdw *workDoneWriter) Close() error { |
| return wdw.client.Progress(wdw.ctx, &protocol.ProgressParams{ |
| Token: wdw.token, |
| Value: protocol.WorkDoneProgressEnd{ |
| Kind: "end", |
| Message: "finished", |
| }, |
| }) |
| } |