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 {