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 <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Rebecca Stambler 2020-01-23 13:58:25 -05:00
parent e873952e15
commit 59ae353e8e
3 changed files with 47 additions and 8 deletions

View File

@ -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():

View File

@ -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.

View File

@ -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 {