Skip to content

Commit 9938a07

Browse files
committed
internal/lsp: factor out progress reporting to a new WorkDone handle
Our current usage of WorkDone progress reporting (new in v3.15 of the LSP spec) is in reporting progress on `go generate` commands. In preparation for using this API more widely, factor out the reporting API from the current io.WriteCloser wrapper (workDoneWriter). Change-Id: Ib528093d81d4fc065528df90e100859e850b10df Reviewed-on: https://go-review.googlesource.com/c/tools/+/229459 Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent e9a00ec commit 9938a07

File tree

4 files changed

+152
-71
lines changed

4 files changed

+152
-71
lines changed

internal/lsp/general.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitializ
3131
s.stateMu.Unlock()
3232

3333
s.supportsWorkDoneProgress = params.Capabilities.Window.WorkDoneProgress
34-
s.inProgress = map[string]func(){}
34+
s.inProgress = map[string]*WorkDone{}
3535

3636
options := s.session.Options()
3737
defer func() { s.session.SetOptions(options) }()

internal/lsp/generate.go

Lines changed: 15 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ package lsp
77
import (
88
"context"
99
"io"
10-
"math/rand"
11-
"strconv"
1210

1311
"golang.org/x/tools/internal/event"
1412
"golang.org/x/tools/internal/gocommand"
@@ -21,14 +19,8 @@ func (s *Server) runGenerate(ctx context.Context, dir string, recursive bool) {
2119
ctx, cancel := context.WithCancel(ctx)
2220
defer cancel()
2321

24-
token := strconv.FormatInt(rand.Int63(), 10)
25-
s.inProgressMu.Lock()
26-
s.inProgress[token] = cancel
27-
s.inProgressMu.Unlock()
28-
defer s.clearInProgress(token)
29-
3022
er := &eventWriter{ctx: ctx}
31-
wc := s.newProgressWriter(ctx, cancel, token)
23+
wc := s.newProgressWriter(ctx, cancel)
3224
defer wc.Close()
3325
args := []string{"-x"}
3426
if recursive {
@@ -68,18 +60,14 @@ func (ew *eventWriter) Write(p []byte) (n int, err error) {
6860
// newProgressWriter returns an io.WriterCloser that can be used
6961
// to report progress on the "go generate" command based on the
7062
// client capabilities.
71-
func (s *Server) newProgressWriter(ctx context.Context, cancel func(), token string) io.WriteCloser {
72-
var wc interface {
73-
io.WriteCloser
74-
start()
75-
}
63+
func (s *Server) newProgressWriter(ctx context.Context, cancel func()) io.WriteCloser {
7664
if s.supportsWorkDoneProgress {
77-
wc = &workDoneWriter{ctx, token, s.client}
78-
} else {
79-
wc = &messageWriter{ctx, cancel, s.client}
65+
wd := s.StartWork(ctx, "generate", "running go generate", cancel)
66+
return &workDoneWriter{ctx, wd}
8067
}
81-
wc.start()
82-
return wc
68+
mw := &messageWriter{ctx, cancel, s.client}
69+
mw.start()
70+
return mw
8371
}
8472

8573
// messageWriter implements progressWriter
@@ -126,58 +114,19 @@ func (lw *messageWriter) Close() error {
126114
})
127115
}
128116

129-
// workDoneWriter implements progressWriter
130-
// that will send $/progress notifications
131-
// to the client. Request cancellations
132-
// happens separately through the
133-
// window/workDoneProgress/cancel request
134-
// in which case the given context will be rendered
135-
// done.
117+
// workDoneWriter implements progressWriter by sending $/progress notifications
118+
// to the client. Request cancellations happens separately through the
119+
// window/workDoneProgress/cancel request, in which case the given context will
120+
// be rendered done.
136121
type workDoneWriter struct {
137-
ctx context.Context
138-
token string
139-
client protocol.Client
122+
ctx context.Context
123+
wd *WorkDone
140124
}
141125

142126
func (wdw *workDoneWriter) Write(p []byte) (n int, err error) {
143-
return len(p), wdw.client.Progress(wdw.ctx, &protocol.ProgressParams{
144-
Token: wdw.token,
145-
Value: &protocol.WorkDoneProgressReport{
146-
Kind: "report",
147-
Cancellable: true,
148-
Message: string(p),
149-
},
150-
})
151-
}
152-
153-
func (wdw *workDoneWriter) start() {
154-
err := wdw.client.WorkDoneProgressCreate(wdw.ctx, &protocol.WorkDoneProgressCreateParams{
155-
Token: wdw.token,
156-
})
157-
if err != nil {
158-
event.Error(wdw.ctx, "generate progress create", err)
159-
return
160-
}
161-
err = wdw.client.Progress(wdw.ctx, &protocol.ProgressParams{
162-
Token: wdw.token,
163-
Value: &protocol.WorkDoneProgressBegin{
164-
Kind: "begin",
165-
Cancellable: true,
166-
Message: "running go generate",
167-
Title: "generate",
168-
},
169-
})
170-
if err != nil {
171-
event.Error(wdw.ctx, "generate progress begin", err)
172-
}
127+
return len(p), wdw.wd.Progress(wdw.ctx, string(p), 0)
173128
}
174129

175130
func (wdw *workDoneWriter) Close() error {
176-
return wdw.client.Progress(wdw.ctx, &protocol.ProgressParams{
177-
Token: wdw.token,
178-
Value: protocol.WorkDoneProgressEnd{
179-
Kind: "end",
180-
Message: "finished",
181-
},
182-
})
131+
return wdw.wd.End(wdw.ctx, "finished")
183132
}

internal/lsp/progress.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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+
"errors"
10+
"math/rand"
11+
"strconv"
12+
13+
"golang.org/x/tools/internal/event"
14+
"golang.org/x/tools/internal/lsp/protocol"
15+
)
16+
17+
// WorkDone represents a unit of work that is reported to the client via the
18+
// progress API.
19+
type WorkDone struct {
20+
client protocol.Client
21+
startErr error
22+
token string
23+
cancel func()
24+
cleanup func()
25+
}
26+
27+
// StartWork creates a unique token and issues a $/progress notification to
28+
// begin a unit of work on the server. The returned WorkDone handle may be used
29+
// to report incremental progress, and to report work completion. In
30+
// particular, it is an error to call StartWork and not call End(...) on the
31+
// returned WorkDone handle.
32+
//
33+
// The progress item is considered cancellable if the given cancel func is
34+
// non-nil.
35+
//
36+
// Example:
37+
// func Generate(ctx) (err error) {
38+
// ctx, cancel := context.WithCancel(ctx)
39+
// defer cancel()
40+
// work := s.StartWork(ctx, "generate", "running go generate", cancel)
41+
// defer func() {
42+
// if err != nil {
43+
// work.End(ctx, fmt.Sprintf("generate failed: %v", err))
44+
// } else {
45+
// work.End(ctx, "done")
46+
// }
47+
// }()
48+
// // Do the work...
49+
// }
50+
//
51+
func (s *Server) StartWork(ctx context.Context, title, message string, cancel func()) *WorkDone {
52+
wd := &WorkDone{
53+
client: s.client,
54+
token: strconv.FormatInt(rand.Int63(), 10),
55+
cancel: cancel,
56+
}
57+
if !s.supportsWorkDoneProgress {
58+
wd.startErr = errors.New("workdone reporting is not supported")
59+
return wd
60+
}
61+
err := wd.client.WorkDoneProgressCreate(ctx, &protocol.WorkDoneProgressCreateParams{
62+
Token: wd.token,
63+
})
64+
if err != nil {
65+
wd.startErr = err
66+
event.Error(ctx, "starting work for "+title, err)
67+
return wd
68+
}
69+
s.addInProgress(wd)
70+
wd.cleanup = func() {
71+
s.removeInProgress(wd.token)
72+
}
73+
err = wd.client.Progress(ctx, &protocol.ProgressParams{
74+
Token: wd.token,
75+
Value: &protocol.WorkDoneProgressBegin{
76+
Kind: "begin",
77+
Cancellable: wd.cancel != nil,
78+
Message: message,
79+
Title: title,
80+
},
81+
})
82+
if err != nil {
83+
event.Error(ctx, "generate progress begin", err)
84+
}
85+
return wd
86+
}
87+
88+
// Progress reports an update on WorkDone progress back to the client.
89+
func (wd *WorkDone) Progress(ctx context.Context, message string, percentage float64) error {
90+
if wd.startErr != nil {
91+
return wd.startErr
92+
}
93+
return wd.client.Progress(ctx, &protocol.ProgressParams{
94+
Token: wd.token,
95+
Value: &protocol.WorkDoneProgressReport{
96+
Kind: "report",
97+
// Note that in the LSP spec, the value of Cancellable may be changed to
98+
// control whether the cancel button in the UI is enabled. Since we don't
99+
// yet use this feature, the value is kept constant here.
100+
Cancellable: wd.cancel != nil,
101+
Message: message,
102+
Percentage: percentage,
103+
},
104+
})
105+
}
106+
107+
// End reports a workdone completion back to the client.
108+
func (wd *WorkDone) End(ctx context.Context, message string) error {
109+
if wd.startErr != nil {
110+
return wd.startErr
111+
}
112+
err := wd.client.Progress(ctx, &protocol.ProgressParams{
113+
Token: wd.token,
114+
Value: protocol.WorkDoneProgressEnd{
115+
Kind: "end",
116+
Message: message,
117+
},
118+
})
119+
if wd.cleanup != nil {
120+
wd.cleanup()
121+
}
122+
return err
123+
}

internal/lsp/server.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ type Server struct {
8181
// to determine if the client can support progress notifications
8282
supportsWorkDoneProgress bool
8383
inProgressMu sync.Mutex
84-
inProgress map[string]func()
84+
inProgress map[string]*WorkDone
8585
}
8686

8787
// sentDiagnostics is used to cache diagnostics that have been sent for a given file.
@@ -146,15 +146,24 @@ func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.Wo
146146
}
147147
s.inProgressMu.Lock()
148148
defer s.inProgressMu.Unlock()
149-
cancel, ok := s.inProgress[token]
149+
wd, ok := s.inProgress[token]
150150
if !ok {
151151
return errors.Errorf("token %q not found in progress", token)
152152
}
153-
cancel()
153+
if wd.cancel == nil {
154+
return errors.Errorf("work %q is not cancellable", token)
155+
}
156+
wd.cancel()
154157
return nil
155158
}
156159

157-
func (s *Server) clearInProgress(token string) {
160+
func (s *Server) addInProgress(wd *WorkDone) {
161+
s.inProgressMu.Lock()
162+
s.inProgress[wd.token] = wd
163+
s.inProgressMu.Unlock()
164+
}
165+
166+
func (s *Server) removeInProgress(token string) {
158167
s.inProgressMu.Lock()
159168
delete(s.inProgress, token)
160169
s.inProgressMu.Unlock()

0 commit comments

Comments
 (0)