Skip to content

Commit a543418

Browse files
committed
internal/lsp: only diagnose views affected by a change
For every file change, we want to diagnose the resulting snapshots. However, we also want to diagnose the file in its "best possible" view (for example, for a nested module, we should use the inner module rather than the outer module). Add a bestView function that operates on a set of views, which allows us to a pick a view out of the *views affected* by a file modification, not out of *all* of the views in the session. This means that we can pick the best view, if any, for each file change and then only return the snapshots that we should diagnose, as well as the changed URIs for those snapshots. Fixes golang/go#42890 Change-Id: Iad23d5ed4832dfd857f1424a7244cf3bd8900e3b Reviewed-on: https://go-review.googlesource.com/c/tools/+/274235 Trust: Rebecca Stambler <rstambler@golang.org> Run-TryBot: Rebecca Stambler <rstambler@golang.org> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
1 parent 5cdc274 commit a543418

File tree

5 files changed

+70
-54
lines changed

5 files changed

+70
-54
lines changed

internal/lsp/cache/load.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ func isTestMain(pkg *packages.Package, gocache string) bool {
459459
if len(pkg.GoFiles) > 1 {
460460
return false
461461
}
462-
if !strings.HasPrefix(pkg.GoFiles[0], gocache) {
462+
if !source.InDir(gocache, pkg.GoFiles[0]) {
463463
return false
464464
}
465465
return true

internal/lsp/cache/session.go

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"fmt"
1010
"os"
1111
"strconv"
12-
"strings"
1312
"sync"
1413
"sync/atomic"
1514

@@ -288,12 +287,11 @@ func (s *Session) viewOf(uri span.URI) (*View, error) {
288287
return v, nil
289288
}
290289
// Pick the best view for this file and memoize the result.
291-
v, err := s.bestView(uri)
292-
if err != nil {
293-
return nil, err
290+
if len(s.views) == 0 {
291+
return nil, fmt.Errorf("no views in session")
294292
}
295-
s.viewMap[uri] = v
296-
return v, nil
293+
s.viewMap[uri] = bestViewForURI(uri, s.views)
294+
return s.viewMap[uri], nil
297295
}
298296

299297
func (s *Session) viewsOf(uri span.URI) []*View {
@@ -302,7 +300,7 @@ func (s *Session) viewsOf(uri span.URI) []*View {
302300

303301
var views []*View
304302
for _, view := range s.views {
305-
if strings.HasPrefix(string(uri), string(view.Folder())) {
303+
if source.InDir(view.folder.Filename(), uri.Filename()) {
306304
views = append(views, view)
307305
}
308306
}
@@ -319,15 +317,12 @@ func (s *Session) Views() []source.View {
319317
return result
320318
}
321319

322-
// bestView finds the best view to associate a given URI with.
323-
// viewMu must be held when calling this method.
324-
func (s *Session) bestView(uri span.URI) (*View, error) {
325-
if len(s.views) == 0 {
326-
return nil, errors.Errorf("no views in the session")
327-
}
320+
// bestViewForURI returns the most closely matching view for the given URI
321+
// out of the given set of views.
322+
func bestViewForURI(uri span.URI, views []*View) *View {
328323
// we need to find the best view for this file
329324
var longest *View
330-
for _, view := range s.views {
325+
for _, view := range views {
331326
if longest != nil && len(longest.Folder()) > len(view.Folder()) {
332327
continue
333328
}
@@ -336,16 +331,16 @@ func (s *Session) bestView(uri span.URI) (*View, error) {
336331
}
337332
}
338333
if longest != nil {
339-
return longest, nil
334+
return longest
340335
}
341336
// Try our best to return a view that knows the file.
342-
for _, view := range s.views {
337+
for _, view := range views {
343338
if view.knownFile(uri) {
344-
return view, nil
339+
return view
345340
}
346341
}
347342
// TODO: are there any more heuristics we can use?
348-
return s.views[0], nil
343+
return views[0]
349344
}
350345

351346
func (s *Session) removeView(ctx context.Context, view *View) error {
@@ -405,7 +400,7 @@ func (s *Session) dropView(ctx context.Context, v *View) (int, error) {
405400
}
406401

407402
func (s *Session) ModifyFiles(ctx context.Context, changes []source.FileModification) error {
408-
_, _, releases, err := s.DidModifyFiles(ctx, changes)
403+
_, releases, err := s.DidModifyFiles(ctx, changes)
409404
for _, release := range releases {
410405
release()
411406
}
@@ -418,13 +413,13 @@ type fileChange struct {
418413
fileHandle source.VersionedFileHandle
419414
}
420415

421-
func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) (map[span.URI]source.View, map[source.View]source.Snapshot, []func(), error) {
416+
func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) (map[source.Snapshot][]span.URI, []func(), error) {
422417
views := make(map[*View]map[span.URI]*fileChange)
423-
bestViews := map[span.URI]source.View{}
418+
affectedViews := map[span.URI][]*View{}
424419

425420
overlays, err := s.updateOverlays(ctx, changes)
426421
if err != nil {
427-
return nil, nil, nil, err
422+
return nil, nil, err
428423
}
429424
var forceReloadMetadata bool
430425
for _, c := range changes {
@@ -433,12 +428,6 @@ func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModif
433428
}
434429

435430
// Build the list of affected views.
436-
bestView, err := s.viewOf(c.URI)
437-
if err != nil {
438-
return nil, nil, nil, err
439-
}
440-
bestViews[c.URI] = bestView
441-
442431
var changedViews []*View
443432
for _, view := range s.views {
444433
// Don't propagate changes that are outside of the view's scope
@@ -448,16 +437,25 @@ func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModif
448437
}
449438
changedViews = append(changedViews, view)
450439
}
451-
// If no view matched the change, assign it to the best view.
440+
// If the change is not relevant to any view, but the change is
441+
// happening in the editor, assign it the most closely matching view.
452442
if len(changedViews) == 0 {
443+
if c.OnDisk {
444+
continue
445+
}
446+
bestView, err := s.viewOf(c.URI)
447+
if err != nil {
448+
return nil, nil, err
449+
}
453450
changedViews = append(changedViews, bestView)
454451
}
452+
affectedViews[c.URI] = changedViews
455453

456454
// Apply the changes to all affected views.
457455
for _, view := range changedViews {
458456
// Make sure that the file is added to the view.
459457
if _, err := view.getFile(c.URI); err != nil {
460-
return nil, nil, nil, err
458+
return nil, nil, err
461459
}
462460
if _, ok := views[view]; !ok {
463461
views[view] = make(map[span.URI]*fileChange)
@@ -471,7 +469,7 @@ func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModif
471469
} else {
472470
fsFile, err := s.cache.getFile(ctx, c.URI)
473471
if err != nil {
474-
return nil, nil, nil, err
472+
return nil, nil, err
475473
}
476474
content, err := fsFile.Read()
477475
fh := &closedFile{fsFile}
@@ -484,14 +482,32 @@ func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModif
484482
}
485483
}
486484

487-
snapshots := map[source.View]source.Snapshot{}
488485
var releases []func()
486+
viewToSnapshot := map[*View]*snapshot{}
489487
for view, changed := range views {
490488
snapshot, release := view.invalidateContent(ctx, changed, forceReloadMetadata)
491-
snapshots[view] = snapshot
492489
releases = append(releases, release)
490+
viewToSnapshot[view] = snapshot
491+
}
492+
493+
// We only want to diagnose each changed file once, in the view to which
494+
// it "most" belongs. We do this by picking the best view for each URI,
495+
// and then aggregating the set of snapshots and their URIs (to avoid
496+
// diagnosing the same snapshot multiple times).
497+
snapshotURIs := map[source.Snapshot][]span.URI{}
498+
for _, mod := range changes {
499+
viewSlice, ok := affectedViews[mod.URI]
500+
if !ok || len(viewSlice) == 0 {
501+
continue
502+
}
503+
view := bestViewForURI(mod.URI, viewSlice)
504+
snapshot, ok := viewToSnapshot[view]
505+
if !ok {
506+
panic(fmt.Sprintf("no snapshot for view %s", view.Folder()))
507+
}
508+
snapshotURIs[snapshot] = append(snapshotURIs[snapshot], mod.URI)
493509
}
494-
return bestViews, snapshots, releases, nil
510+
return snapshotURIs, releases, nil
495511
}
496512

497513
func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []source.FileModification) []source.FileModification {

internal/lsp/cache/view.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -353,15 +353,23 @@ func basename(filename string) string {
353353

354354
func (v *View) relevantChange(c source.FileModification) bool {
355355
// If the file is known to the view, the change is relevant.
356-
known := v.knownFile(c.URI)
357-
356+
if v.knownFile(c.URI) {
357+
return true
358+
}
359+
// The gopls.mod may not be "known" because we first access it through the
360+
// session. As a result, treat changes to the view's gopls.mod file as
361+
// always relevant, even if they are only on-disk changes.
362+
// TODO(rstambler): Make sure the gopls.mod is always known to the view.
363+
if c.URI == goplsModURI(v.rootURI) {
364+
return true
365+
}
358366
// If the file is not known to the view, and the change is only on-disk,
359367
// we should not invalidate the snapshot. This is necessary because Emacs
360368
// sends didChangeWatchedFiles events for temp files.
361-
if !known && c.OnDisk && (c.Action == source.Change || c.Action == source.Delete) {
369+
if c.OnDisk && (c.Action == source.Change || c.Action == source.Delete) {
362370
return false
363371
}
364-
return v.contains(c.URI) || known
372+
return v.contains(c.URI)
365373
}
366374

367375
func (v *View) knownFile(uri span.URI) bool {
@@ -578,7 +586,7 @@ func (s *snapshot) initialize(ctx context.Context, firstAttempt bool) {
578586

579587
// invalidateContent invalidates the content of a Go file,
580588
// including any position and type information that depends on it.
581-
func (v *View) invalidateContent(ctx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (source.Snapshot, func()) {
589+
func (v *View) invalidateContent(ctx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, func()) {
582590
// Detach the context so that content invalidation cannot be canceled.
583591
ctx = xcontext.Detach(ctx)
584592

internal/lsp/source/view.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,9 +303,10 @@ type Session interface {
303303
// GetFile returns a handle for the specified file.
304304
GetFile(ctx context.Context, uri span.URI) (FileHandle, error)
305305

306-
// DidModifyFile reports a file modification to the session. It returns the
307-
// resulting snapshots, a guaranteed one per view.
308-
DidModifyFiles(ctx context.Context, changes []FileModification) (map[span.URI]View, map[View]Snapshot, []func(), error)
306+
// DidModifyFile reports a file modification to the session. It returns
307+
// the new snapshots after the modifications have been applied, paired with
308+
// the affected file URIs for those snapshots.
309+
DidModifyFiles(ctx context.Context, changes []FileModification) (map[Snapshot][]span.URI, []func(), error)
309310

310311
// ExpandModificationsToDirectories returns the set of changes with the
311312
// directory changes removed and expanded to include all of the files in

internal/lsp/text_synchronization.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,21 +221,12 @@ func (s *Server) didModifyFiles(ctx context.Context, modifications []source.File
221221
// to their files.
222222
modifications = s.session.ExpandModificationsToDirectories(ctx, modifications)
223223

224-
views, snapshots, releases, err := s.session.DidModifyFiles(ctx, modifications)
224+
snapshots, releases, err := s.session.DidModifyFiles(ctx, modifications)
225225
if err != nil {
226226
return err
227227
}
228228

229-
// Group files by best view and diagnose them.
230-
viewURIs := map[source.View][]span.URI{}
231-
for uri, view := range views {
232-
viewURIs[view] = append(viewURIs[view], uri)
233-
}
234-
for view, uris := range viewURIs {
235-
snapshot := snapshots[view]
236-
if snapshot == nil {
237-
panic(fmt.Sprintf("no snapshot assigned for files %v", uris))
238-
}
229+
for snapshot, uris := range snapshots {
239230
diagnosticWG.Add(1)
240231
go func(snapshot source.Snapshot, uris []span.URI) {
241232
defer diagnosticWG.Done()

0 commit comments

Comments
 (0)