Skip to content

Commit bc9fc8d

Browse files
committed
internal/lsp: fix flickering analysis diagnostics
Also, change withAnalysis to includesAnalysis to make the name a bit more accurate. Fixes golang/go#42363 Change-Id: If50f7d36b953e6627ed9ba049357a2b86dc3f676 Reviewed-on: https://go-review.googlesource.com/c/tools/+/267817 Trust: Rebecca Stambler <[email protected]> Run-TryBot: Rebecca Stambler <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent bc3cf28 commit bc9fc8d

File tree

3 files changed

+41
-32
lines changed

3 files changed

+41
-32
lines changed

internal/lsp/diagnostics.go

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ import (
2626
// idWithAnalysis is used to track if the diagnostics for a given file were
2727
// computed with analyses.
2828
type idWithAnalysis struct {
29-
id source.VersionedFileIdentity
30-
withAnalysis bool
29+
id source.VersionedFileIdentity
30+
includeAnalysis bool
3131
}
3232

3333
// A reportSet collects diagnostics for publication, sorting them by file and
@@ -38,15 +38,15 @@ type reportSet struct {
3838
reports map[idWithAnalysis]map[string]*source.Diagnostic
3939
}
4040

41-
func (s *reportSet) add(id source.VersionedFileIdentity, withAnalysis bool, diags ...*source.Diagnostic) {
41+
func (s *reportSet) add(id source.VersionedFileIdentity, includeAnalysis bool, diags ...*source.Diagnostic) {
4242
s.mu.Lock()
4343
defer s.mu.Unlock()
4444
if s.reports == nil {
4545
s.reports = make(map[idWithAnalysis]map[string]*source.Diagnostic)
4646
}
4747
key := idWithAnalysis{
48-
id: id,
49-
withAnalysis: withAnalysis,
48+
id: id,
49+
includeAnalysis: includeAnalysis,
5050
}
5151
if _, ok := s.reports[key]; !ok {
5252
s.reports[key] = map[string]*source.Diagnostic{}
@@ -79,7 +79,7 @@ func (s *Server) diagnoseDetached(snapshot source.Snapshot) {
7979
// If a view has been created or the configuration changed, warn the user.
8080
s.client.ShowMessage(ctx, shows)
8181
}
82-
s.publishReports(ctx, snapshot, reports)
82+
s.publishReports(ctx, snapshot, reports, false)
8383
}
8484

8585
func (s *Server) diagnoseSnapshot(snapshot source.Snapshot, changedURIs []span.URI) {
@@ -99,17 +99,17 @@ func (s *Server) diagnoseSnapshot(snapshot source.Snapshot, changedURIs []span.U
9999
event.Error(ctx, "diagnosing changed files", err)
100100
}
101101
}
102-
s.publishReports(ctx, snapshot, reports)
102+
s.publishReports(ctx, snapshot, reports, true)
103103
s.debouncer.debounce(snapshot.View().Name(), snapshot.ID(), delay, func() {
104104
reports, _ := s.diagnose(ctx, snapshot, false)
105-
s.publishReports(ctx, snapshot, reports)
105+
s.publishReports(ctx, snapshot, reports, false)
106106
})
107107
return
108108
}
109109

110110
// Ignore possible workspace configuration warnings in the normal flow.
111111
reports, _ := s.diagnose(ctx, snapshot, false)
112-
s.publishReports(ctx, snapshot, reports)
112+
s.publishReports(ctx, snapshot, reports, false)
113113
}
114114

115115
func (s *Server) diagnoseChangedFiles(ctx context.Context, snapshot source.Snapshot, uris []span.URI) (*reportSet, error) {
@@ -215,11 +215,11 @@ If you believe this is a mistake, please file an issue: https://github.com/golan
215215
go func(pkg source.Package) {
216216
defer wg.Done()
217217

218-
withAnalysis := alwaysAnalyze // only run analyses for packages with open files
219-
var gcDetailsDir span.URI // find the package's optimization details, if available
218+
includeAnalysis := alwaysAnalyze // only run analyses for packages with open files
219+
var gcDetailsDir span.URI // find the package's optimization details, if available
220220
for _, pgf := range pkg.CompiledGoFiles() {
221221
if snapshot.IsOpen(pgf.URI) {
222-
withAnalysis = true
222+
includeAnalysis = true
223223
}
224224
if gcDetailsDir == "" {
225225
dirURI := span.URIFromPath(filepath.Dir(pgf.URI.Filename()))
@@ -232,7 +232,7 @@ If you believe this is a mistake, please file an issue: https://github.com/golan
232232
}
233233
}
234234

235-
pkgReports, warn, err := source.Diagnostics(ctx, snapshot, pkg, withAnalysis)
235+
pkgReports, warn, err := source.Diagnostics(ctx, snapshot, pkg, includeAnalysis)
236236

237237
// Check if might want to warn the user about their build configuration.
238238
// Our caller decides whether to send the message.
@@ -251,7 +251,7 @@ If you believe this is a mistake, please file an issue: https://github.com/golan
251251

252252
// Add all reports to the global map, checking for duplicates.
253253
for id, diags := range pkgReports {
254-
reports.add(id, withAnalysis, diags...)
254+
reports.add(id, includeAnalysis, diags...)
255255
}
256256
// If gc optimization details are available, add them to the
257257
// diagnostic reports.
@@ -261,7 +261,7 @@ If you believe this is a mistake, please file an issue: https://github.com/golan
261261
event.Error(ctx, "warning: gc details", err, tag.Snapshot.Of(snapshot.ID()))
262262
}
263263
for id, diags := range gcReports {
264-
reports.add(id, withAnalysis, diags...)
264+
reports.add(id, includeAnalysis, diags...)
265265
}
266266
}
267267
}(pkg)
@@ -274,10 +274,10 @@ If you believe this is a mistake, please file an issue: https://github.com/golan
274274
// Check if we already have diagnostic reports for the given file,
275275
// meaning that we have already seen its package.
276276
var seen bool
277-
for _, withAnalysis := range []bool{true, false} {
277+
for _, includeAnalysis := range []bool{true, false} {
278278
_, ok := reports.reports[idWithAnalysis{
279-
id: o.VersionedFileIdentity(),
280-
withAnalysis: withAnalysis,
279+
id: o.VersionedFileIdentity(),
280+
includeAnalysis: includeAnalysis,
281281
}]
282282
seen = seen || ok
283283
}
@@ -349,7 +349,7 @@ func errorsToDiagnostic(ctx context.Context, snapshot source.Snapshot, errors []
349349
return nil
350350
}
351351

352-
func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, reports *reportSet) {
352+
func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, reports *reportSet, isFirstPass bool) {
353353
// Check for context cancellation before publishing diagnostics.
354354
if ctx.Err() != nil || reports == nil {
355355
return
@@ -370,10 +370,10 @@ func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, r
370370
}
371371
source.SortDiagnostics(diagnostics)
372372
toSend := sentDiagnostics{
373-
id: key.id,
374-
sorted: diagnostics,
375-
withAnalysis: key.withAnalysis,
376-
snapshotID: snapshot.ID(),
373+
id: key.id,
374+
sorted: diagnostics,
375+
includeAnalysis: key.includeAnalysis,
376+
snapshotID: snapshot.ID(),
377377
}
378378

379379
// We use the zero values if this is an unknown file.
@@ -382,10 +382,11 @@ func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, r
382382
// Snapshot IDs are always increasing, so we use them instead of file
383383
// versions to create the correct order for diagnostics.
384384

385-
// If we've already delivered diagnostics for a future snapshot for this file,
386-
// do not deliver them.
385+
// If we've already delivered diagnostics for a future snapshot for
386+
// this file, do not deliver them.
387387
if delivered.snapshotID > toSend.snapshotID {
388-
// Do not update the delivered map since it already contains newer diagnostics.
388+
// Do not update the delivered map since it already contains newer
389+
// diagnostics.
389390
continue
390391
}
391392

@@ -399,10 +400,18 @@ func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, r
399400
// If we've already delivered diagnostics for this file, at this
400401
// snapshot, with analyses, do not send diagnostics without analyses.
401402
if delivered.snapshotID == toSend.snapshotID && delivered.id == toSend.id &&
402-
delivered.withAnalysis && !toSend.withAnalysis {
403+
delivered.includeAnalysis && !toSend.includeAnalysis {
403404
// Do not update the delivered map since it already contains better diagnostics.
404405
continue
405406
}
407+
408+
// If we've previously delivered non-empty diagnostics and this is a
409+
// first diagnostic pass, wait for a subsequent pass to complete before
410+
// sending empty diagnostics to avoid flickering diagnostics.
411+
if isFirstPass && delivered.includeAnalysis && !toSend.includeAnalysis && len(toSend.sorted) == 0 {
412+
continue
413+
}
414+
406415
if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
407416
Diagnostics: toProtocolDiagnostics(diagnostics),
408417
URI: protocol.URIFromSpanURI(key.id.URI),

internal/lsp/lsp_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1159,7 +1159,7 @@ func (r *runner) collectDiagnostics(view source.View) {
11591159

11601160
// Always run diagnostics with analysis.
11611161
reports, _ := r.server.diagnose(r.ctx, snapshot, true)
1162-
r.server.publishReports(r.ctx, snapshot, reports)
1162+
r.server.publishReports(r.ctx, snapshot, reports, false)
11631163
for uri, sent := range r.server.delivered {
11641164
var diagnostics []*source.Diagnostic
11651165
for _, d := range sent.sorted {

internal/lsp/server.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ type Server struct {
107107

108108
// sentDiagnostics is used to cache diagnostics that have been sent for a given file.
109109
type sentDiagnostics struct {
110-
id source.VersionedFileIdentity
111-
sorted []*source.Diagnostic
112-
withAnalysis bool
113-
snapshotID uint64
110+
id source.VersionedFileIdentity
111+
sorted []*source.Diagnostic
112+
includeAnalysis bool
113+
snapshotID uint64
114114
}
115115

116116
func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {

0 commit comments

Comments
 (0)