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