aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp/progress/progress.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp/progress/progress.go')
-rw-r--r--internal/lsp/progress/progress.go269
1 files changed, 0 insertions, 269 deletions
diff --git a/internal/lsp/progress/progress.go b/internal/lsp/progress/progress.go
deleted file mode 100644
index 18e1bd0f1..000000000
--- a/internal/lsp/progress/progress.go
+++ /dev/null
@@ -1,269 +0,0 @@
-// 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 progress
-
-import (
- "context"
- "math/rand"
- "strconv"
- "strings"
- "sync"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-type Tracker struct {
- client protocol.Client
- supportsWorkDoneProgress bool
-
- mu sync.Mutex
- inProgress map[protocol.ProgressToken]*WorkDone
-}
-
-func NewTracker(client protocol.Client) *Tracker {
- return &Tracker{
- client: client,
- inProgress: make(map[protocol.ProgressToken]*WorkDone),
- }
-}
-
-func (tracker *Tracker) SetSupportsWorkDoneProgress(b bool) {
- tracker.supportsWorkDoneProgress = b
-}
-
-// Start notifies the client of work being done on the server. It uses either
-// ShowMessage RPCs or $/progress messages, depending on the capabilities of
-// the client. The returned WorkDone handle may be used to report incremental
-// progress, and to report work completion. In particular, it is an error to
-// call start and not call end(...) on the returned WorkDone handle.
-//
-// If token is empty, a token will be randomly generated.
-//
-// The progress item is considered cancellable if the given cancel func is
-// non-nil. In this case, cancel is called when the work done
-//
-// Example:
-// func Generate(ctx) (err error) {
-// ctx, cancel := context.WithCancel(ctx)
-// defer cancel()
-// work := s.progress.start(ctx, "generate", "running go generate", cancel)
-// defer func() {
-// if err != nil {
-// work.end(ctx, fmt.Sprintf("generate failed: %v", err))
-// } else {
-// work.end(ctx, "done")
-// }
-// }()
-// // Do the work...
-// }
-//
-func (t *Tracker) Start(ctx context.Context, title, message string, token protocol.ProgressToken, cancel func()) *WorkDone {
- wd := &WorkDone{
- ctx: xcontext.Detach(ctx),
- client: t.client,
- token: token,
- cancel: cancel,
- }
- if !t.supportsWorkDoneProgress {
- // Previous iterations of this fallback attempted to retain cancellation
- // support by using ShowMessageCommand with a 'Cancel' button, but this is
- // not ideal as the 'Cancel' dialog stays open even after the command
- // completes.
- //
- // Just show a simple message. Clients can implement workDone progress
- // reporting to get cancellation support.
- if err := wd.client.ShowMessage(wd.ctx, &protocol.ShowMessageParams{
- Type: protocol.Log,
- Message: message,
- }); err != nil {
- event.Error(ctx, "showing start message for "+title, err)
- }
- return wd
- }
- if wd.token == nil {
- token = strconv.FormatInt(rand.Int63(), 10)
- err := wd.client.WorkDoneProgressCreate(ctx, &protocol.WorkDoneProgressCreateParams{
- Token: token,
- })
- if err != nil {
- wd.err = err
- event.Error(ctx, "starting work for "+title, err)
- return wd
- }
- wd.token = token
- }
- // At this point we have a token that the client knows about. Store the token
- // before starting work.
- t.mu.Lock()
- t.inProgress[wd.token] = wd
- t.mu.Unlock()
- wd.cleanup = func() {
- t.mu.Lock()
- delete(t.inProgress, token)
- t.mu.Unlock()
- }
- err := wd.client.Progress(ctx, &protocol.ProgressParams{
- Token: wd.token,
- Value: &protocol.WorkDoneProgressBegin{
- Kind: "begin",
- Cancellable: wd.cancel != nil,
- Message: message,
- Title: title,
- },
- })
- if err != nil {
- event.Error(ctx, "generate progress begin", err)
- }
- return wd
-}
-
-func (t *Tracker) Cancel(ctx context.Context, token protocol.ProgressToken) error {
- t.mu.Lock()
- defer t.mu.Unlock()
- wd, ok := t.inProgress[token]
- if !ok {
- return errors.Errorf("token %q not found in progress", token)
- }
- if wd.cancel == nil {
- return errors.Errorf("work %q is not cancellable", token)
- }
- wd.doCancel()
- return nil
-}
-
-// WorkDone represents a unit of work that is reported to the client via the
-// progress API.
-type WorkDone struct {
- // ctx is detached, for sending $/progress updates.
- ctx context.Context
- client protocol.Client
- // If token is nil, this workDone object uses the ShowMessage API, rather
- // than $/progress.
- token protocol.ProgressToken
- // err is set if progress reporting is broken for some reason (for example,
- // if there was an initial error creating a token).
- err error
-
- cancelMu sync.Mutex
- cancelled bool
- cancel func()
-
- cleanup func()
-}
-
-func (wd *WorkDone) Token() protocol.ProgressToken {
- return wd.token
-}
-
-func (wd *WorkDone) doCancel() {
- wd.cancelMu.Lock()
- defer wd.cancelMu.Unlock()
- if !wd.cancelled {
- wd.cancel()
- }
-}
-
-// report reports an update on WorkDone report back to the client.
-func (wd *WorkDone) Report(message string, percentage float64) {
- if wd == nil {
- return
- }
- wd.cancelMu.Lock()
- cancelled := wd.cancelled
- wd.cancelMu.Unlock()
- if cancelled {
- return
- }
- if wd.err != nil || wd.token == nil {
- // Not using the workDone API, so we do nothing. It would be far too spammy
- // to send incremental messages.
- return
- }
- message = strings.TrimSuffix(message, "\n")
- err := wd.client.Progress(wd.ctx, &protocol.ProgressParams{
- Token: wd.token,
- Value: &protocol.WorkDoneProgressReport{
- Kind: "report",
- // Note that in the LSP spec, the value of Cancellable may be changed to
- // control whether the cancel button in the UI is enabled. Since we don't
- // yet use this feature, the value is kept constant here.
- Cancellable: wd.cancel != nil,
- Message: message,
- Percentage: uint32(percentage),
- },
- })
- if err != nil {
- event.Error(wd.ctx, "reporting progress", err)
- }
-}
-
-// end reports a workdone completion back to the client.
-func (wd *WorkDone) End(message string) {
- if wd == nil {
- return
- }
- var err error
- switch {
- case wd.err != nil:
- // There is a prior error.
- case wd.token == nil:
- // We're falling back to message-based reporting.
- err = wd.client.ShowMessage(wd.ctx, &protocol.ShowMessageParams{
- Type: protocol.Info,
- Message: message,
- })
- default:
- err = wd.client.Progress(wd.ctx, &protocol.ProgressParams{
- Token: wd.token,
- Value: &protocol.WorkDoneProgressEnd{
- Kind: "end",
- Message: message,
- },
- })
- }
- if err != nil {
- event.Error(wd.ctx, "ending work", err)
- }
- if wd.cleanup != nil {
- wd.cleanup()
- }
-}
-
-// 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
- operation string
-}
-
-func NewEventWriter(ctx context.Context, operation string) *EventWriter {
- return &EventWriter{ctx: ctx, operation: operation}
-}
-
-func (ew *EventWriter) Write(p []byte) (n int, err error) {
- event.Log(ew.ctx, string(p), tag.Operation.Of(ew.operation))
- return len(p), nil
-}
-
-// WorkDoneWriter wraps a workDone handle to provide a Writer interface,
-// so that workDone reporting can more easily be hooked into commands.
-type WorkDoneWriter struct {
- wd *WorkDone
-}
-
-func NewWorkDoneWriter(wd *WorkDone) *WorkDoneWriter {
- return &WorkDoneWriter{wd: wd}
-}
-
-func (wdw WorkDoneWriter) Write(p []byte) (n int, err error) {
- wdw.wd.Report(string(p), 0)
- // Don't fail just because of a failure to report progress.
- return len(p), nil
-}