Skip to content

Commit 219d341

Browse files
committed
internal/lsp: batch file changes in didChangeWatchedFiles
Remove the special handling for go.mod file saves. This was only really added to be extra careful, but our cancellation logic should cope with this. Fixes golang/go#31553 Change-Id: I0a69bcdeaf6369697e79aba4689a7b714484ccc2 Reviewed-on: https://go-review.googlesource.com/c/tools/+/215908 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
1 parent e54d0ed commit 219d341

File tree

5 files changed

+75
-107
lines changed

5 files changed

+75
-107
lines changed

internal/lsp/cache/session.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,6 @@ func (s *session) dropView(ctx context.Context, v *view) (int, error) {
254254

255255
func (s *session) DidModifyFiles(ctx context.Context, changes []source.FileModification) ([]source.Snapshot, error) {
256256
views := make(map[*view][]span.URI)
257-
saves := make(map[*view]bool)
258257

259258
for _, c := range changes {
260259
// Only update overlays for in-editor changes.
@@ -275,25 +274,18 @@ func (s *session) DidModifyFiles(ctx context.Context, changes []source.FileModif
275274
if !view.knownFile(c.URI) {
276275
continue
277276
}
278-
case source.Save:
279-
panic("save considered an on-disk change")
280277
}
281278
}
282279
// Make sure that the file is added to the view.
283280
if _, err := view.getFile(c.URI); err != nil {
284281
return nil, err
285282
}
286283
views[view] = append(views[view], c.URI)
287-
saves[view] = len(changes) == 1 && !changes[0].OnDisk && changes[0].Action == source.Save
288284
}
289285
}
290286
var snapshots []source.Snapshot
291287
for view, uris := range views {
292-
containsFileSave, ok := saves[view]
293-
if !ok {
294-
panic("unknown view")
295-
}
296-
snapshots = append(snapshots, view.invalidateContent(ctx, uris, containsFileSave))
288+
snapshots = append(snapshots, view.invalidateContent(ctx, uris))
297289
}
298290
return snapshots, nil
299291
}

internal/lsp/cache/view.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -577,22 +577,13 @@ func (v *view) awaitInitialized(ctx context.Context) error {
577577
// invalidateContent invalidates the content of a Go file,
578578
// including any position and type information that depends on it.
579579
// It returns true if we were already tracking the given file, false otherwise.
580-
func (v *view) invalidateContent(ctx context.Context, uris []span.URI, containsFileSave bool) source.Snapshot {
580+
func (v *view) invalidateContent(ctx context.Context, uris []span.URI) source.Snapshot {
581581
// Detach the context so that content invalidation cannot be canceled.
582582
ctx = xcontext.Detach(ctx)
583583

584-
if containsFileSave && len(uris) > 1 {
585-
panic("file save among multiple content invalidations")
586-
}
587-
588584
// Cancel all still-running previous requests, since they would be
589585
// operating on stale data.
590-
//
591-
// TODO(rstambler): File saves should also lead to cancellation,
592-
// but this will only be possible when they trigger workspace-level diagnostics.
593-
if !containsFileSave {
594-
v.cancelBackground()
595-
}
586+
v.cancelBackground()
596587

597588
// Do not clone a snapshot until its view has finished initializing.
598589
_ = v.awaitInitialized(ctx)

internal/lsp/lsp_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,22 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
5959
if _, _, err := session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options); err != nil {
6060
t.Fatal(err)
6161
}
62+
var modifications []source.FileModification
6263
for filename, content := range data.Config.Overlay {
6364
kind := source.DetectLanguage("", filename)
6465
if kind != source.Go {
6566
continue
6667
}
67-
if _, err := session.DidModifyFiles(ctx, []source.FileModification{
68-
{
69-
URI: span.FileURI(filename),
70-
Action: source.Open,
71-
Version: -1,
72-
Text: content,
73-
LanguageID: "go",
74-
},
75-
}); err != nil {
76-
t.Fatal(err)
77-
}
68+
modifications = append(modifications, source.FileModification{
69+
URI: span.FileURI(filename),
70+
Action: source.Open,
71+
Version: -1,
72+
Text: content,
73+
LanguageID: "go",
74+
})
75+
}
76+
if _, err := session.DidModifyFiles(ctx, modifications); err != nil {
77+
t.Fatal(err)
7878
}
7979
r := &runner{
8080
server: &Server{

internal/lsp/source/source_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,22 @@ func testSource(t *testing.T, exporter packagestest.Exporter) {
6060
data: data,
6161
ctx: ctx,
6262
}
63+
var modifications []source.FileModification
6364
for filename, content := range data.Config.Overlay {
6465
kind := source.DetectLanguage("", filename)
6566
if kind != source.Go {
6667
continue
6768
}
68-
if _, err := session.DidModifyFiles(ctx, []source.FileModification{
69-
{
70-
URI: span.FileURI(filename),
71-
Action: source.Open,
72-
Version: -1,
73-
Text: content,
74-
LanguageID: "go",
75-
},
76-
}); err != nil {
77-
t.Fatal(err)
78-
}
69+
modifications = append(modifications, source.FileModification{
70+
URI: span.FileURI(filename),
71+
Action: source.Open,
72+
Version: -1,
73+
Text: content,
74+
LanguageID: "go",
75+
})
76+
}
77+
if _, err := session.DidModifyFiles(ctx, modifications); err != nil {
78+
t.Fatal(err)
7979
}
8080
tests.Run(t, r, data)
8181
}

internal/lsp/text_synchronization.go

Lines changed: 50 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@ import (
1212
"golang.org/x/tools/internal/jsonrpc2"
1313
"golang.org/x/tools/internal/lsp/protocol"
1414
"golang.org/x/tools/internal/lsp/source"
15-
"golang.org/x/tools/internal/lsp/telemetry"
1615
"golang.org/x/tools/internal/span"
1716
errors "golang.org/x/xerrors"
1817
)
1918

2019
func (s *Server) didOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
21-
_, err := s.didModifyFile(ctx, source.FileModification{
22-
URI: span.NewURI(params.TextDocument.URI),
23-
Action: source.Open,
24-
Version: params.TextDocument.Version,
25-
Text: []byte(params.TextDocument.Text),
26-
LanguageID: params.TextDocument.LanguageID,
20+
_, err := s.didModifyFiles(ctx, []source.FileModification{
21+
{
22+
URI: span.NewURI(params.TextDocument.URI),
23+
Action: source.Open,
24+
Version: params.TextDocument.Version,
25+
Text: []byte(params.TextDocument.Text),
26+
LanguageID: params.TextDocument.LanguageID,
27+
},
2728
})
2829
return err
2930
}
@@ -40,10 +41,14 @@ func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDo
4041
Version: params.TextDocument.Version,
4142
Text: text,
4243
}
43-
snapshot, err := s.didModifyFile(ctx, c)
44+
snapshots, err := s.didModifyFiles(ctx, []source.FileModification{c})
4445
if err != nil {
4546
return err
4647
}
48+
snapshot := snapshots[uri]
49+
if snapshot == nil {
50+
return errors.Errorf("no snapshot for %s", uri)
51+
}
4752
// Ideally, we should be able to specify that a generated file should be opened as read-only.
4853
// Tell the user that they should not be editing a generated file.
4954
if s.wasFirstChange(uri) && source.IsGenerated(ctx, snapshot, uri) {
@@ -58,38 +63,23 @@ func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDo
5863
}
5964

6065
func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error {
61-
// Keep track of each change's view and final snapshot.
62-
views := make(map[source.View]source.Snapshot)
66+
var modifications []source.FileModification
6367
for _, change := range params.Changes {
6468
uri := span.NewURI(change.URI)
65-
ctx := telemetry.File.With(ctx, uri)
6669

6770
// Do nothing if the file is open in the editor.
6871
// The editor is the source of truth.
6972
if s.session.IsOpen(uri) {
7073
continue
7174
}
72-
snapshots, err := s.session.DidModifyFiles(ctx, []source.FileModification{
73-
{
74-
URI: uri,
75-
Action: changeTypeToFileAction(change.Type),
76-
OnDisk: true,
77-
},
75+
modifications = append(modifications, source.FileModification{
76+
URI: uri,
77+
Action: changeTypeToFileAction(change.Type),
78+
OnDisk: true,
7879
})
79-
if err != nil {
80-
return err
81-
}
82-
snapshot, _, err := snapshotOf(s.session, uri, snapshots)
83-
if err != nil {
84-
return err
85-
}
86-
views[snapshot.View()] = snapshot
87-
}
88-
// Diagnose all resulting snapshots.
89-
for _, snapshot := range views {
90-
go s.diagnoseSnapshot(snapshot)
9180
}
92-
return nil
81+
_, err := s.didModifyFiles(ctx, modifications)
82+
return err
9383
}
9484

9585
func (s *Server) didSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
@@ -101,59 +91,54 @@ func (s *Server) didSave(ctx context.Context, params *protocol.DidSaveTextDocume
10191
if params.Text != nil {
10292
c.Text = []byte(*params.Text)
10393
}
104-
_, err := s.didModifyFile(ctx, c)
94+
_, err := s.didModifyFiles(ctx, []source.FileModification{c})
10595
return err
10696
}
10797

10898
func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
109-
_, err := s.didModifyFile(ctx, source.FileModification{
110-
URI: span.NewURI(params.TextDocument.URI),
111-
Action: source.Close,
112-
Version: -1,
113-
Text: nil,
99+
_, err := s.didModifyFiles(ctx, []source.FileModification{
100+
{
101+
URI: span.NewURI(params.TextDocument.URI),
102+
Action: source.Close,
103+
Version: -1,
104+
Text: nil,
105+
},
114106
})
115107
return err
116108
}
117109

118-
func (s *Server) didModifyFile(ctx context.Context, c source.FileModification) (source.Snapshot, error) {
119-
snapshots, err := s.session.DidModifyFiles(ctx, []source.FileModification{c})
110+
func (s *Server) didModifyFiles(ctx context.Context, modifications []source.FileModification) (map[span.URI]source.Snapshot, error) {
111+
snapshots, err := s.session.DidModifyFiles(ctx, modifications)
120112
if err != nil {
121113
return nil, err
122114
}
123-
snapshot, _, err := snapshotOf(s.session, c.URI, snapshots)
124-
if err != nil {
125-
return nil, err
115+
uris := make(map[span.URI]source.Snapshot)
116+
for _, c := range modifications {
117+
uris[c.URI] = nil
126118
}
127-
switch c.Action {
128-
case source.Save:
129-
// If we're saving a go.mod file, all of the metadata has been invalidated,
130-
// so we need to run diagnostics and make sure that they cannot be canceled.
131-
fh, err := snapshot.GetFile(c.URI)
119+
// Avoid diagnosing the same snapshot twice.
120+
snapshotSet := make(map[source.Snapshot]struct{})
121+
for uri := range uris {
122+
view, err := s.session.ViewOf(uri)
132123
if err != nil {
133124
return nil, err
134125
}
135-
if fh.Identity().Kind == source.Mod {
136-
go s.diagnoseDetached(snapshot)
126+
var snapshot source.Snapshot
127+
for _, s := range snapshots {
128+
if s.View() == view {
129+
if snapshot != nil {
130+
return nil, errors.Errorf("duplicate snapshots for the same view")
131+
}
132+
snapshot = s
133+
}
137134
}
138-
default:
139-
go s.diagnoseSnapshot(snapshot)
135+
uris[uri] = snapshot
136+
snapshotSet[snapshot] = struct{}{}
140137
}
141-
142-
return snapshot, nil
143-
}
144-
145-
// snapshotOf returns the snapshot corresponding to the view for the given file URI.
146-
func snapshotOf(session source.Session, uri span.URI, snapshots []source.Snapshot) (source.Snapshot, source.View, error) {
147-
view, err := session.ViewOf(uri)
148-
if err != nil {
149-
return nil, nil, err
150-
}
151-
for _, s := range snapshots {
152-
if s.View() == view {
153-
return s, view, nil
154-
}
138+
for snapshot := range snapshotSet {
139+
go s.diagnoseSnapshot(snapshot)
155140
}
156-
return nil, nil, errors.Errorf("bestSnapshot: no snapshot for %s", uri)
141+
return uris, nil
157142
}
158143

159144
func (s *Server) wasFirstChange(uri span.URI) bool {

0 commit comments

Comments
 (0)