Skip to content

Commit 74f2986

Browse files
committed
internal/lsp: show critical error pop-ups as progress reports
We've been looking for a way to show unintrusive error status reports to users--try using progress reports as a way of populating a status bar. This avoids the problem of annoying the user with constant pop-ups. Whenever an error is returns from (*snapshot).WorkspacePackages, we start a progress report with the error message. If the error goes away on the next call to diagnostics, or the error message changes, we will either remove or update the progress report. Screencast: https://drive.google.com/file/d/1tG9pc_tPsLoqkQHiJqdY8b06TzTkPVae/view?usp=sharing&resourcekey=0-CEG_LhGHYiFp9S37ut_kgw Updates golang/go#42250 Change-Id: I8a529a911883092bc08af32246719d883dc5f5a2 Reviewed-on: https://go-review.googlesource.com/c/tools/+/268677 Trust: Rebecca Stambler <[email protected]> Run-TryBot: Rebecca Stambler <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]>
1 parent 1d69943 commit 74f2986

File tree

5 files changed

+113
-23
lines changed

5 files changed

+113
-23
lines changed

gopls/internal/regtest/diagnostics_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -1452,3 +1452,35 @@ package foo_`
14521452
)
14531453
})
14541454
}
1455+
1456+
// TestProgressBarErrors confirms that critical workspace load errors are shown
1457+
// and updated via progress reports.
1458+
func TestProgressBarErrors(t *testing.T) {
1459+
testenv.NeedsGo1Point(t, 14)
1460+
1461+
const pkg = `
1462+
-- go.mod --
1463+
modul mod.com
1464+
1465+
go 1.12
1466+
-- main.go --
1467+
package main
1468+
`
1469+
run(t, pkg, func(t *testing.T, env *Env) {
1470+
env.OpenFile("go.mod")
1471+
env.Await(
1472+
OutstandingWork("Error loading workspace", "unknown directive"),
1473+
)
1474+
env.EditBuffer("go.mod", fake.NewEdit(0, 0, 3, 0, `module mod.com
1475+
1476+
go 1.hello
1477+
`))
1478+
env.Await(
1479+
OutstandingWork("Error loading workspace", "invalid go version"),
1480+
)
1481+
env.RegexpReplace("go.mod", "go 1.hello", "go 1.12")
1482+
env.Await(
1483+
NoOutstandingWork(),
1484+
)
1485+
})
1486+
}

gopls/internal/regtest/env.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ type State struct {
5858
}
5959

6060
type workProgress struct {
61-
title string
62-
percent float64
61+
title, msg string
62+
percent float64
6363
}
6464

6565
func (s State) String() string {
@@ -200,10 +200,16 @@ func (e *Env) onProgress(_ context.Context, m *protocol.ProgressParams) error {
200200
switch kind := v["kind"]; kind {
201201
case "begin":
202202
work.title = v["title"].(string)
203+
if msg, ok := v["message"]; ok {
204+
work.msg = msg.(string)
205+
}
203206
case "report":
204207
if pct, ok := v["percentage"]; ok {
205208
work.percent = pct.(float64)
206209
}
210+
if msg, ok := v["message"]; ok {
211+
work.msg = msg.(string)
212+
}
207213
case "end":
208214
title := e.state.outstandingWork[m.Token].title
209215
e.state.completedWork[title] = e.state.completedWork[title] + 1

gopls/internal/regtest/expectation.go

+18
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,24 @@ func CompletedWork(title string, atLeast int) SimpleExpectation {
202202
}
203203
}
204204

205+
// OutstandingWork expects a work item to be outstanding. The given title must
206+
// be an exact match, whereas the given msg must only be contained in the work
207+
// item's message.
208+
func OutstandingWork(title, msg string) SimpleExpectation {
209+
check := func(s State) Verdict {
210+
for _, work := range s.outstandingWork {
211+
if work.title == title && strings.Contains(work.msg, msg) {
212+
return Met
213+
}
214+
}
215+
return Unmet
216+
}
217+
return SimpleExpectation{
218+
check: check,
219+
description: fmt.Sprintf("outstanding work: %s", title),
220+
}
221+
}
222+
205223
// LogExpectation is an expectation on the log messages received by the editor
206224
// from gopls.
207225
type LogExpectation struct {

internal/lsp/diagnostics.go

+48-20
Original file line numberDiff line numberDiff line change
@@ -182,34 +182,26 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, alwaysA
182182

183183
// Diagnose all of the packages in the workspace.
184184
wsPkgs, err := snapshot.WorkspacePackages(ctx)
185+
if s.shouldIgnoreError(ctx, snapshot, err) {
186+
return nil, nil
187+
}
188+
// Show the error as a progress error report so that it appears in the
189+
// status bar. If a client doesn't support progress reports, the error
190+
// will still be shown as a ShowMessage. If there is no error, any running
191+
// error progress reports will be closed.
192+
s.showCriticalErrorStatus(ctx, err)
193+
185194
if err != nil {
186-
if errors.Is(err, context.Canceled) {
187-
return nil, nil
188-
}
189-
// Some error messages can be displayed as diagnostics.
195+
// Some error messages can also be displayed as diagnostics.
190196
if errList := (*source.ErrorList)(nil); errors.As(err, &errList) {
191197
if err := errorsToDiagnostic(ctx, snapshot, *errList, reports); err == nil {
192198
return reports, nil
193199
}
194200
}
195-
// Try constructing a more helpful error message out of this error.
196-
if s.handleFatalErrors(ctx, snapshot, modErr, err) {
197-
return nil, nil
198-
}
199201
event.Error(ctx, "errors diagnosing workspace", err, tag.Snapshot.Of(snapshot.ID()), tag.Directory.Of(snapshot.View().Folder()))
200-
// Present any `go list` errors directly to the user.
201-
if errors.Is(err, source.PackagesLoadError) {
202-
if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
203-
Type: protocol.Error,
204-
Message: fmt.Sprintf(`The code in the workspace failed to compile (see the error message below).
205-
If you believe this is a mistake, please file an issue: https://github.com/golang/go/issues/new.
206-
%v`, err),
207-
}); err != nil {
208-
event.Error(ctx, "ShowMessage failed", err, tag.Directory.Of(snapshot.View().Folder().Filename()))
209-
}
210-
}
211202
return nil, nil
212203
}
204+
213205
var (
214206
showMsgMu sync.Mutex
215207
showMsg *protocol.ShowMessageParams
@@ -299,6 +291,36 @@ If you believe this is a mistake, please file an issue: https://github.com/golan
299291
return reports, showMsg
300292
}
301293

294+
// showCriticalErrorStatus shows the error as a progress report.
295+
// If the error is nil, it clears any existing error progress report.
296+
func (s *Server) showCriticalErrorStatus(ctx context.Context, err error) {
297+
s.criticalErrorStatusMu.Lock()
298+
defer s.criticalErrorStatusMu.Unlock()
299+
300+
// Remove all newlines so that the error message can be formatted in a
301+
// status bar.
302+
var errMsg string
303+
if err != nil {
304+
errMsg = strings.Replace(err.Error(), "\n", " ", -1)
305+
}
306+
307+
if s.criticalErrorStatus == nil {
308+
if errMsg != "" {
309+
s.criticalErrorStatus = s.progress.start(ctx, "Error loading workspace", errMsg, nil, nil)
310+
}
311+
return
312+
}
313+
314+
// If an error is already shown to the user, update it or mark it as
315+
// resolved.
316+
if errMsg == "" {
317+
s.criticalErrorStatus.end("Done.")
318+
s.criticalErrorStatus = nil
319+
} else {
320+
s.criticalErrorStatus.report(errMsg, 0)
321+
}
322+
}
323+
302324
// checkForOrphanedFile checks that the given URIs can be mapped to packages.
303325
// If they cannot and the workspace is not otherwise unloaded, it also surfaces
304326
// a warning, suggesting that the user check the file for build tags.
@@ -470,7 +492,13 @@ func toProtocolDiagnostics(diagnostics []*source.Diagnostic) []protocol.Diagnost
470492
return reports
471493
}
472494

473-
func (s *Server) handleFatalErrors(ctx context.Context, snapshot source.Snapshot, modErr, loadErr error) bool {
495+
func (s *Server) shouldIgnoreError(ctx context.Context, snapshot source.Snapshot, err error) bool {
496+
if err == nil { // if there is no error at all
497+
return false
498+
}
499+
if errors.Is(err, context.Canceled) {
500+
return true
501+
}
474502
// If the folder has no Go code in it, we shouldn't spam the user with a warning.
475503
var hasGo bool
476504
_ = filepath.Walk(snapshot.View().Folder().Filename(), func(path string, info os.FileInfo, err error) error {

internal/lsp/server.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,19 @@ type Server struct {
9696
gcOptimizationDetailsMu sync.Mutex
9797
gcOptimizationDetails map[span.URI]struct{}
9898

99-
// diagnosticsSema limits the concurrency of diagnostics runs, which can be expensive.
99+
// diagnosticsSema limits the concurrency of diagnostics runs, which can be
100+
// expensive.
100101
diagnosticsSema chan struct{}
101102

102103
progress *progressTracker
103104

104105
// debouncer is used for debouncing diagnostics.
105106
debouncer *debouncer
107+
108+
// When the workspace fails to load, we show its status through a progress
109+
// report with an error message.
110+
criticalErrorStatusMu sync.Mutex
111+
criticalErrorStatus *workDone
106112
}
107113

108114
// sentDiagnostics is used to cache diagnostics that have been sent for a given file.

0 commit comments

Comments
 (0)