[tools] internal/lsp: always send diagnostics for updated files

38 views
Skip to first unread message

Ernest Romero Climent (Gerrit)

unread,
Mar 5, 2021, 1:12:03 PM3/5/21
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Ernest Romero Climent has uploaded this change for review.

View Change

internal/lsp: always send diagnostics for updated files

According to the LSP specification, when a file is updated, it is the
server's responsibility to re-compute diagnostics and push them to the
client, even if the diagnostics for the updated file are the same as
before the update.

Currently gopls only sends diagnostics if the computed hash is updated,
however, that only occurs if the update adds or removes diagnostics or
updates any of their positions.

Change-Id: Ic8764efb26ff948a7148cfa45cf1d73069cbc27d
---
M gopls/internal/regtest/diagnostics/diagnostics_test.go
M internal/lsp/diagnostics.go
2 files changed, 41 insertions(+), 8 deletions(-)

diff --git a/gopls/internal/regtest/diagnostics/diagnostics_test.go b/gopls/internal/regtest/diagnostics/diagnostics_test.go
index 75ba7de..8b75c4d 100644
--- a/gopls/internal/regtest/diagnostics/diagnostics_test.go
+++ b/gopls/internal/regtest/diagnostics/diagnostics_test.go
@@ -127,6 +127,22 @@
})
}

+func TestDiagnosticResendOnEditAfterLastDiagnostic(t *testing.T) {
+ Run(t, badPackage, func(t *testing.T, env *Env) {
+ env.OpenFile("b.go")
+ env.Await(env.DiagnosticAtRegexp("b.go", "a = 2"))
+
+ // Edit after the last diagnostic so no diagnostic
+ // positions change after buffer was edited.
+ at := fake.Pos{Line: 2, Column: 11}
+ env.EditBuffer("b.go", fake.Edit{Start: at, End: at, Text: " "})
+ env.Await(OnceMet(
+ env.DoneWithChange(),
+ env.AnyDiagnosticAtCurrentVersion("b.go"),
+ ))
+ })
+}
+
func TestDiagnosticClearingOnDelete_Issue37049(t *testing.T) {
Run(t, badPackage, func(t *testing.T, env *Env) {
env.OpenFile("a.go")
@@ -380,7 +396,7 @@
// completed.
OnceMet(
env.DoneWithChange(),
- NoDiagnostics("a.go"),
+ EmptyDiagnostics("a.go"),
),
)
})
@@ -1703,7 +1719,7 @@
env.Await(
OnceMet(
env.DoneWithOpen(),
- NoDiagnostics("a/a.go"),
+ EmptyDiagnostics("a/a.go"),
),
NoOutstandingWork(),
)
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
index 0368771..21cb52f 100644
--- a/internal/lsp/diagnostics.go
+++ b/internal/lsp/diagnostics.go
@@ -10,6 +10,7 @@
"fmt"
"os"
"path/filepath"
+ "sort"
"strings"
"sync"

@@ -87,7 +88,7 @@
ctx := snapshot.BackgroundContext()
ctx = xcontext.Detach(ctx)
s.diagnose(ctx, snapshot, false)
- s.publishDiagnostics(ctx, true, snapshot)
+ s.publishDiagnostics(ctx, []span.URI{}, true, snapshot)
}

func (s *Server) diagnoseSnapshot(snapshot source.Snapshot, changedURIs []span.URI, onDisk bool) {
@@ -105,17 +106,17 @@
// The second phase does everything, and is debounced by the configured
// delay.
s.diagnoseChangedFiles(ctx, snapshot, changedURIs, onDisk)
- s.publishDiagnostics(ctx, false, snapshot)
+ s.publishDiagnostics(ctx, changedURIs, false, snapshot)
s.debouncer.debounce(snapshot.View().Name(), snapshot.ID(), delay, func() {
s.diagnose(ctx, snapshot, false)
- s.publishDiagnostics(ctx, true, snapshot)
+ s.publishDiagnostics(ctx, changedURIs, true, snapshot)
})
return
}

// Ignore possible workspace configuration warnings in the normal flow.
s.diagnose(ctx, snapshot, false)
- s.publishDiagnostics(ctx, true, snapshot)
+ s.publishDiagnostics(ctx, changedURIs, true, snapshot)
}

func (s *Server) diagnoseChangedFiles(ctx context.Context, snapshot source.Snapshot, uris []span.URI, onDisk bool) {
@@ -414,8 +415,21 @@
}
}

+func containsURI(uris []span.URI, u span.URI) bool {
+ i := sort.Search(len(uris), func(i int) bool {
+ return u <= uris[i]
+ })
+ return i < len(uris) && uris[i] == u
+}
+
+func sortURIs(uris []span.URI) {
+ sort.Slice(uris, func(i int, j int) bool {
+ return uris[i] < uris[j]
+ })
+}
+
// publishDiagnostics collects and publishes any unpublished diagnostic reports.
-func (s *Server) publishDiagnostics(ctx context.Context, final bool, snapshot source.Snapshot) {
+func (s *Server) publishDiagnostics(ctx context.Context, changedURIs []span.URI, final bool, snapshot source.Snapshot) {
ctx, done := event.Start(ctx, "Server.publishDiagnostics", tag.Snapshot.Of(snapshot.ID()))
defer done()
s.diagnosticsMu.Lock()
@@ -426,6 +440,8 @@
log.Trace.Logf(ctx, "published %d diagnostics", published)
}()

+ sortURIs(changedURIs)
+
for uri, r := range s.diagnostics {
// Snapshot IDs are always increasing, so we use them instead of file
// versions to create the correct order for diagnostics.
@@ -461,7 +477,8 @@
}
source.SortDiagnostics(diags)
hash := hashDiagnostics(diags...)
- if hash == r.publishedHash {
+ // Make sure that we re-send diagnostics for files that have been modified.
+ if hash == r.publishedHash && !containsURI(changedURIs, uri) {
// Update snapshotID to be the latest snapshot for which this diagnostic
// hash is valid.
r.snapshotID = snapshot.ID()

To view, visit change 298853. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic8764efb26ff948a7148cfa45cf1d73069cbc27d
Gerrit-Change-Number: 298853
Gerrit-PatchSet: 1
Gerrit-Owner: Ernest Romero Climent <ern...@unstable.build>
Gerrit-MessageType: newchange

Rebecca Stambler (Gerrit)

unread,
Mar 5, 2021, 1:56:13 PM3/5/21
to Ernest Romero Climent, goph...@pubsubhelper.golang.org, Ian Cottrell, Go Bot, golang-co...@googlegroups.com

Attention is currently required from: Ernest Romero Climent.

View Change

1 comment:

    • According to the LSP specification, when a file is updated, it is the
      server's responsibility to re-compute diagnostics and push them to the
      client, even if the diagnostics for the updated file are the same as
      before the update.

    • Do you mind sharing where it says this in the LSP specification? From https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocument_publishDiagnostics:

      When a file changes it is the server’s responsibility to re-compute diagnostics and push them to the client. If the computed set is empty it has to push the empty array to clear former diagnostics. Newly pushed diagnostics always replace previously pushed diagnostics. There is no merging that happens on the client side.

      This only mentions clearing old diagnostics, not maintaining the same diagnostics.

To view, visit change 298853. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic8764efb26ff948a7148cfa45cf1d73069cbc27d
Gerrit-Change-Number: 298853
Gerrit-PatchSet: 1
Gerrit-Owner: Ernest Romero Climent <ern...@unstable.build>
Gerrit-Reviewer: Rebecca Stambler <rsta...@golang.org>
Gerrit-CC: Go Bot <go...@golang.org>
Gerrit-CC: Ian Cottrell <ianco...@google.com>
Gerrit-Attention: Ernest Romero Climent <ern...@unstable.build>
Gerrit-Comment-Date: Fri, 05 Mar 2021 18:56:07 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Gerrit-MessageType: comment

Ernest Romero Climent (Gerrit)

unread,
Mar 5, 2021, 7:41:14 PM3/5/21
to goph...@pubsubhelper.golang.org, Rebecca Stambler, Ian Cottrell, Go Bot, golang-co...@googlegroups.com

Attention is currently required from: Rebecca Stambler.

View Change

1 comment:

  • Commit Message:

    • Patch Set #1, Line 9:

      According to the LSP specification, when a file is updated, it is the
      server's responsibility to re-compute diagnostics and push them to the
      client, even if the diagnostics for the updated file are the same as
      before the update.

    • Do you mind sharing where it says this in the LSP specification? From https://microsoft.github. […]

      I didn't realize that this requirement could be up for interpretation.

      To prevent attributes (location lists, etc.) from getting stale, editor implementations might clear them upon receiving an update to the buffer. Thus from an LSP client's standpoint, it's quite hard to know whether to wait for the next publishDiagnostics once an update to the buffer is detected or to send the last known diagnostics.

      FWIW I've checked the behavior in other LSP servers (clangd, tsserver and rls) and all re-send the diagnostics for a file, if such file is updated, whether the update changed the diagnostics or not.

To view, visit change 298853. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic8764efb26ff948a7148cfa45cf1d73069cbc27d
Gerrit-Change-Number: 298853
Gerrit-PatchSet: 1
Gerrit-Owner: Ernest Romero Climent <ern...@unstable.build>
Gerrit-Reviewer: Rebecca Stambler <rsta...@golang.org>
Gerrit-CC: Go Bot <go...@golang.org>
Gerrit-CC: Ian Cottrell <ianco...@google.com>
Gerrit-Attention: Rebecca Stambler <rsta...@golang.org>
Gerrit-Comment-Date: Sat, 06 Mar 2021 00:41:09 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Rebecca Stambler <rsta...@golang.org>
Gerrit-MessageType: comment

Rebecca Stambler (Gerrit)

unread,
Mar 8, 2021, 5:11:04 PM3/8/21
to Ernest Romero Climent, goph...@pubsubhelper.golang.org, Ian Cottrell, Go Bot, golang-co...@googlegroups.com

Attention is currently required from: Ernest Romero Climent.

View Change

1 comment:

  • Commit Message:

    • Patch Set #1, Line 9:

      According to the LSP specification, when a file is updated, it is the
      server's responsibility to re-compute diagnostics and push them to the
      client, even if the diagnostics for the updated file are the same as
      before the update.

    • FWIW I've checked the behavior in other LSP servers (clangd, tsserver and rls) and all re-send the diagnostics for a file, if such file is updated, whether the update changed the diagnostics or not.

    • Other servers, like Microsoft's Python language server, and the Haskell language server, do not resend unchanged diagnostics, so I'm not sure that the spec is entirely clear here. I'm happy to file an issue to clear up this ambiguity, but I think clients should be able to handle both scenarios.

    • editor implementations might clear them upon receiving an update to the buffer.

    • If an editor clears diagnostics on updates to the buffer, but then the server just sends the same diagnostics, won't this look like "flickering" diagnostics to the user?

To view, visit change 298853. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic8764efb26ff948a7148cfa45cf1d73069cbc27d
Gerrit-Change-Number: 298853
Gerrit-PatchSet: 1
Gerrit-Owner: Ernest Romero Climent <ern...@unstable.build>
Gerrit-Reviewer: Rebecca Stambler <rsta...@golang.org>
Gerrit-CC: Go Bot <go...@golang.org>
Gerrit-CC: Ian Cottrell <ianco...@google.com>
Gerrit-Attention: Ernest Romero Climent <ern...@unstable.build>
Gerrit-Comment-Date: Mon, 08 Mar 2021 22:10:58 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Rebecca Stambler <rsta...@golang.org>
Comment-In-Reply-To: Ernest Romero Climent <ern...@unstable.build>
Gerrit-MessageType: comment

Rebecca Stambler (Gerrit)

unread,
Mar 9, 2021, 3:31:31 PM3/9/21
to Ernest Romero Climent, goph...@pubsubhelper.golang.org, Ian Cottrell, Go Bot, golang-co...@googlegroups.com

Attention is currently required from: Ernest Romero Climent.

Patch set 1:Code-Review -2

View Change

1 comment:

  • Commit Message:

    • Patch Set #1, Line 9:

      According to the LSP specification, when a file is updated, it is the
      server's responsibility to re-compute diagnostics and push them to the
      client, even if the diagnostics for the updated file are the same as
      before the update.

To view, visit change 298853. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic8764efb26ff948a7148cfa45cf1d73069cbc27d
Gerrit-Change-Number: 298853
Gerrit-PatchSet: 1
Gerrit-Owner: Ernest Romero Climent <ern...@unstable.build>
Gerrit-Reviewer: Rebecca Stambler <rsta...@golang.org>
Gerrit-CC: Go Bot <go...@golang.org>
Gerrit-CC: Ian Cottrell <ianco...@google.com>
Gerrit-Attention: Ernest Romero Climent <ern...@unstable.build>
Gerrit-Comment-Date: Tue, 09 Mar 2021 20:31:25 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: Yes

Ernest Romero Climent (Gerrit)

unread,
Mar 10, 2021, 1:02:03 PM3/10/21
to goph...@pubsubhelper.golang.org, Rebecca Stambler, Ian Cottrell, Go Bot, golang-co...@googlegroups.com

View Change

1 comment:

  • Patchset:

    • Patch Set #1:

      Thanks Rebecca for clearing the ambiguity with the LSP team. I will handle it on the client side.

To view, visit change 298853. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic8764efb26ff948a7148cfa45cf1d73069cbc27d
Gerrit-Change-Number: 298853
Gerrit-PatchSet: 1
Gerrit-Owner: Ernest Romero Climent <ern...@unstable.build>
Gerrit-Reviewer: Rebecca Stambler <rsta...@golang.org>
Gerrit-CC: Go Bot <go...@golang.org>
Gerrit-CC: Ian Cottrell <ianco...@google.com>
Gerrit-Comment-Date: Wed, 10 Mar 2021 18:01:59 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Gerrit-MessageType: comment

Ernest Romero Climent (Gerrit)

unread,
Mar 10, 2021, 1:02:15 PM3/10/21
to goph...@pubsubhelper.golang.org, Rebecca Stambler, Ian Cottrell, Go Bot, golang-co...@googlegroups.com

Ernest Romero Climent abandoned this change.

View Change

Abandoned

To view, visit change 298853. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic8764efb26ff948a7148cfa45cf1d73069cbc27d
Gerrit-Change-Number: 298853
Gerrit-PatchSet: 1
Gerrit-Owner: Ernest Romero Climent <ern...@unstable.build>
Gerrit-Reviewer: Rebecca Stambler <rsta...@golang.org>
Gerrit-CC: Go Bot <go...@golang.org>
Gerrit-CC: Ian Cottrell <ianco...@google.com>
Gerrit-MessageType: abandon
Reply all
Reply to author
Forward
0 new messages