From 59ae353e8ece4b1950a22216843eb1ba5abe7905 Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Thu, 23 Jan 2020 13:58:25 -0500 Subject: [PATCH] internal/lsp: recreate the view when needed If the initial workspace load fails (due to a lack of a go.mod file or an invalid go.mod file), we should try to re-load as changes to the go.mod come in. Rather than retrying within the view, we just drop the view entirely and try to recreate it. This shouldn't lead to any noticeable lag, as anything that has been cached can still be reused. Fixes golang/go#36531 Change-Id: I6e157075e8b3665f0ceef35e051e56ac3c29f286 Reviewed-on: https://go-review.googlesource.com/c/tools/+/216037 Run-TryBot: Rebecca Stambler TryBot-Result: Gobot Gobot Reviewed-by: Heschi Kreinick --- internal/lsp/cache/view.go | 10 ++++++++ internal/lsp/source/view.go | 7 +++++ internal/lsp/text_synchronization.go | 38 ++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 31678fb26d..0cfff16b08 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -180,6 +180,11 @@ func (v *view) SetOptions(ctx context.Context, options source.Options) (source.V return newView, err } +func (v *view) Rebuild(ctx context.Context) (source.Snapshot, error) { + _, snapshot, err := v.session.updateView(ctx, v, v.options) + return snapshot, err +} + func (v *view) LookupBuiltin(ctx context.Context, name string) (*ast.Object, error) { if err := v.awaitInitialized(ctx); err != nil { return nil, err @@ -565,6 +570,11 @@ func (v *view) initialize(ctx context.Context, s *snapshot) { }) } +func (v *view) Initialized(ctx context.Context) bool { + err := v.awaitInitialized(ctx) + return err == nil +} + func (v *view) awaitInitialized(ctx context.Context) error { select { case <-ctx.Done(): diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index ade81d42f1..cdae1fb07c 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -130,6 +130,12 @@ type View interface { // Snapshot returns the current snapshot for the view. Snapshot() Snapshot + + // Initialized returns true if the view has been initialized without errors. + Initialized(ctx context.Context) bool + + // Rebuild rebuilds the current view, replacing the original view in its session. + Rebuild(ctx context.Context) (Snapshot, error) } // Session represents a single connection from a client. @@ -164,6 +170,7 @@ type Session interface { IsOpen(uri span.URI) bool // DidModifyFile reports a file modification to the session. + // It returns the resulting snapshots, a guaranteed one per view. DidModifyFiles(ctx context.Context, changes []FileModification) ([]Snapshot, error) // Options returns a copy of the SessionOptions for this session. diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go index f11ccb6be4..e5505d9ebd 100644 --- a/internal/lsp/text_synchronization.go +++ b/internal/lsp/text_synchronization.go @@ -112,13 +112,13 @@ func (s *Server) didModifyFiles(ctx context.Context, modifications []source.File if err != nil { return nil, err } - uris := make(map[span.URI]source.Snapshot) + snapshotByURI := make(map[span.URI]source.Snapshot) for _, c := range modifications { - uris[c.URI] = nil + snapshotByURI[c.URI] = nil } // Avoid diagnosing the same snapshot twice. - snapshotSet := make(map[source.Snapshot]struct{}) - for uri := range uris { + snapshotSet := make(map[source.Snapshot][]span.URI) + for uri := range snapshotByURI { view, err := s.session.ViewOf(uri) if err != nil { return nil, err @@ -132,13 +132,35 @@ func (s *Server) didModifyFiles(ctx context.Context, modifications []source.File snapshot = s } } - uris[uri] = snapshot - snapshotSet[snapshot] = struct{}{} + if snapshot == nil { + return nil, errors.Errorf("no snapshot for %s", uri) + } + snapshotByURI[uri] = snapshot + snapshotSet[snapshot] = append(snapshotSet[snapshot], uri) } - for snapshot := range snapshotSet { + for snapshot, uris := range snapshotSet { + for _, uri := range uris { + fh, err := snapshot.GetFile(uri) + if err != nil { + return nil, err + } + // If a modification comes in for a go.mod file, + // and the view was never properly initialized, + // try to recreate the associated view. + switch fh.Identity().Kind { + case source.Mod: + newSnapshot, err := snapshot.View().Rebuild(ctx) + if err != nil { + return nil, err + } + // Update the snapshot to the rebuilt one. + snapshot = newSnapshot + snapshotByURI[uri] = snapshot + } + } go s.diagnoseSnapshot(snapshot) } - return uris, nil + return snapshotByURI, nil } func (s *Server) wasFirstChange(uri span.URI) bool {