Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / internal / lsp / progress.go
1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package lsp
6
7 import (
8         "context"
9         "math/rand"
10         "strconv"
11         "sync"
12
13         "golang.org/x/tools/internal/event"
14         "golang.org/x/tools/internal/lsp/debug/tag"
15         "golang.org/x/tools/internal/lsp/protocol"
16         "golang.org/x/tools/internal/xcontext"
17         errors "golang.org/x/xerrors"
18 )
19
20 type progressTracker struct {
21         client                   protocol.Client
22         supportsWorkDoneProgress bool
23
24         mu         sync.Mutex
25         inProgress map[protocol.ProgressToken]*workDone
26 }
27
28 func newProgressTracker(client protocol.Client) *progressTracker {
29         return &progressTracker{
30                 client:     client,
31                 inProgress: make(map[protocol.ProgressToken]*workDone),
32         }
33 }
34
35 // start notifies the client of work being done on the server. It uses either
36 // ShowMessage RPCs or $/progress messages, depending on the capabilities of
37 // the client.  The returned WorkDone handle may be used to report incremental
38 // progress, and to report work completion. In particular, it is an error to
39 // call start and not call end(...) on the returned WorkDone handle.
40 //
41 // If token is empty, a token will be randomly generated.
42 //
43 // The progress item is considered cancellable if the given cancel func is
44 // non-nil. In this case, cancel is called when the work done
45 //
46 // Example:
47 //  func Generate(ctx) (err error) {
48 //    ctx, cancel := context.WithCancel(ctx)
49 //    defer cancel()
50 //    work := s.progress.start(ctx, "generate", "running go generate", cancel)
51 //    defer func() {
52 //      if err != nil {
53 //        work.end(ctx, fmt.Sprintf("generate failed: %v", err))
54 //      } else {
55 //        work.end(ctx, "done")
56 //      }
57 //    }()
58 //    // Do the work...
59 //  }
60 //
61 func (t *progressTracker) start(ctx context.Context, title, message string, token protocol.ProgressToken, cancel func()) *workDone {
62         wd := &workDone{
63                 ctx:    xcontext.Detach(ctx),
64                 client: t.client,
65                 token:  token,
66                 cancel: cancel,
67         }
68         if !t.supportsWorkDoneProgress {
69                 // Previous iterations of this fallback attempted to retain cancellation
70                 // support by using ShowMessageCommand with a 'Cancel' button, but this is
71                 // not ideal as the 'Cancel' dialog stays open even after the command
72                 // completes.
73                 //
74                 // Just show a simple message. Clients can implement workDone progress
75                 // reporting to get cancellation support.
76                 if err := wd.client.ShowMessage(wd.ctx, &protocol.ShowMessageParams{
77                         Type:    protocol.Log,
78                         Message: message,
79                 }); err != nil {
80                         event.Error(ctx, "showing start message for "+title, err)
81                 }
82                 return wd
83         }
84         if wd.token == nil {
85                 token = strconv.FormatInt(rand.Int63(), 10)
86                 err := wd.client.WorkDoneProgressCreate(ctx, &protocol.WorkDoneProgressCreateParams{
87                         Token: token,
88                 })
89                 if err != nil {
90                         wd.err = err
91                         event.Error(ctx, "starting work for "+title, err)
92                         return wd
93                 }
94                 wd.token = token
95         }
96         // At this point we have a token that the client knows about. Store the token
97         // before starting work.
98         t.mu.Lock()
99         t.inProgress[wd.token] = wd
100         t.mu.Unlock()
101         wd.cleanup = func() {
102                 t.mu.Lock()
103                 delete(t.inProgress, token)
104                 t.mu.Unlock()
105         }
106         err := wd.client.Progress(ctx, &protocol.ProgressParams{
107                 Token: wd.token,
108                 Value: &protocol.WorkDoneProgressBegin{
109                         Kind:        "begin",
110                         Cancellable: wd.cancel != nil,
111                         Message:     message,
112                         Title:       title,
113                 },
114         })
115         if err != nil {
116                 event.Error(ctx, "generate progress begin", err)
117         }
118         return wd
119 }
120
121 func (t *progressTracker) cancel(ctx context.Context, token protocol.ProgressToken) error {
122         t.mu.Lock()
123         defer t.mu.Unlock()
124         wd, ok := t.inProgress[token]
125         if !ok {
126                 return errors.Errorf("token %q not found in progress", token)
127         }
128         if wd.cancel == nil {
129                 return errors.Errorf("work %q is not cancellable", token)
130         }
131         wd.doCancel()
132         return nil
133 }
134
135 // workDone represents a unit of work that is reported to the client via the
136 // progress API.
137 type workDone struct {
138         // ctx is detached, for sending $/progress updates.
139         ctx    context.Context
140         client protocol.Client
141         // If token is nil, this workDone object uses the ShowMessage API, rather
142         // than $/progress.
143         token protocol.ProgressToken
144         // err is set if progress reporting is broken for some reason (for example,
145         // if there was an initial error creating a token).
146         err error
147
148         cancelMu  sync.Mutex
149         cancelled bool
150         cancel    func()
151
152         cleanup func()
153 }
154
155 func (wd *workDone) doCancel() {
156         wd.cancelMu.Lock()
157         defer wd.cancelMu.Unlock()
158         if !wd.cancelled {
159                 wd.cancel()
160         }
161 }
162
163 // report reports an update on WorkDone report back to the client.
164 func (wd *workDone) report(message string, percentage float64) {
165         if wd == nil {
166                 return
167         }
168         wd.cancelMu.Lock()
169         cancelled := wd.cancelled
170         wd.cancelMu.Unlock()
171         if cancelled {
172                 return
173         }
174         if wd.err != nil || wd.token == nil {
175                 // Not using the workDone API, so we do nothing. It would be far too spammy
176                 // to send incremental messages.
177                 return
178         }
179         err := wd.client.Progress(wd.ctx, &protocol.ProgressParams{
180                 Token: wd.token,
181                 Value: &protocol.WorkDoneProgressReport{
182                         Kind: "report",
183                         // Note that in the LSP spec, the value of Cancellable may be changed to
184                         // control whether the cancel button in the UI is enabled. Since we don't
185                         // yet use this feature, the value is kept constant here.
186                         Cancellable: wd.cancel != nil,
187                         Message:     message,
188                         Percentage:  percentage,
189                 },
190         })
191         if err != nil {
192                 event.Error(wd.ctx, "reporting progress", err)
193         }
194 }
195
196 // end reports a workdone completion back to the client.
197 func (wd *workDone) end(message string) {
198         if wd == nil {
199                 return
200         }
201         var err error
202         switch {
203         case wd.err != nil:
204                 // There is a prior error.
205         case wd.token == nil:
206                 // We're falling back to message-based reporting.
207                 err = wd.client.ShowMessage(wd.ctx, &protocol.ShowMessageParams{
208                         Type:    protocol.Info,
209                         Message: message,
210                 })
211         default:
212                 err = wd.client.Progress(wd.ctx, &protocol.ProgressParams{
213                         Token: wd.token,
214                         Value: &protocol.WorkDoneProgressEnd{
215                                 Kind:    "end",
216                                 Message: message,
217                         },
218                 })
219         }
220         if err != nil {
221                 event.Error(wd.ctx, "ending work", err)
222         }
223         if wd.cleanup != nil {
224                 wd.cleanup()
225         }
226 }
227
228 // eventWriter writes every incoming []byte to
229 // event.Print with the operation=generate tag
230 // to distinguish its logs from others.
231 type eventWriter struct {
232         ctx       context.Context
233         operation string
234 }
235
236 func (ew *eventWriter) Write(p []byte) (n int, err error) {
237         event.Log(ew.ctx, string(p), tag.Operation.Of(ew.operation))
238         return len(p), nil
239 }
240
241 // workDoneWriter wraps a workDone handle to provide a Writer interface,
242 // so that workDone reporting can more easily be hooked into commands.
243 type workDoneWriter struct {
244         wd *workDone
245 }
246
247 func (wdw workDoneWriter) Write(p []byte) (n int, err error) {
248         wdw.wd.report(string(p), 0)
249         // Don't fail just because of a failure to report progress.
250         return len(p), nil
251 }