internal/lsp: call load in (*session).NewView
Add a source.Scope type that can be used to refer to directories or
files, and modify (*snapshot).load to take source.Scope.
Then call load in NewView.
Change-Id: I8f03c7b271d700b162100d2890d23219ef9578c2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/204822
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cache/gofile.go b/internal/lsp/cache/gofile.go
index 0aa19e9..f55a818 100644
--- a/internal/lsp/cache/gofile.go
+++ b/internal/lsp/cache/gofile.go
@@ -39,7 +39,7 @@
// We only need to this if it has been invalidated, and is therefore unvailable.
if load {
var err error
- m, err = s.load(ctx, f.URI())
+ m, err = s.load(ctx, source.FileURI(f.URI()))
if err != nil {
return nil, err
}
@@ -61,7 +61,7 @@
cphs = results
}
if len(cphs) == 0 {
- return nil, errors.Errorf("no CheckPackageHandles for %s", f.URI())
+ return nil, errors.Errorf("no CheckPackageHandles for %s", f)
}
return cphs, nil
}
@@ -86,7 +86,7 @@
}
// We expect to see a checked package for each package ID,
// and it should be parsed in full mode.
- cphs = s.getPackages(fh.Identity().URI, source.ParseFull)
+ cphs = s.getPackages(source.FileURI(fh.Identity().URI), source.ParseFull)
if len(cphs) < len(m) {
return m, nil, load, true
}
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index 3408d3f..285f6a0 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -33,27 +33,43 @@
config *packages.Config
}
-func (s *snapshot) load(ctx context.Context, uri span.URI) ([]*metadata, error) {
+func (s *snapshot) load(ctx context.Context, scope source.Scope) ([]*metadata, error) {
+ uri := scope.URI()
+ var query string
+ switch scope.(type) {
+ case source.FileURI:
+ query = fmt.Sprintf("file=%s", scope.URI().Filename())
+ case source.DirectoryURI:
+ query = fmt.Sprintf("%s/...", scope.URI().Filename())
+ // Simplify the query if it will be run in the requested directory.
+ // This ensures compatibility with Go 1.12 that doesn't allow
+ // <directory>/... in GOPATH mode.
+ if s.view.folder.Filename() == scope.URI().Filename() {
+ query = "./..."
+ }
+ default:
+ panic(fmt.Errorf("unsupported scope type %T", scope))
+ }
ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.URI.Of(uri))
defer done()
cfg := s.view.Config(ctx)
- pkgs, err := packages.Load(cfg, fmt.Sprintf("file=%s", uri.Filename()))
+ pkgs, err := packages.Load(cfg, query)
// If the context was canceled, return early.
// Otherwise, we might be type-checking an incomplete result.
if err == context.Canceled {
- return nil, errors.Errorf("no metadata for %s: %v", uri.Filename(), err)
+ return nil, errors.Errorf("no metadata for %s: %v", uri, err)
}
log.Print(ctx, "go/packages.Load", tag.Of("packages", len(pkgs)))
if len(pkgs) == 0 {
if err == nil {
- err = errors.Errorf("go/packages.Load: no packages found for %s", uri)
+ err = errors.Errorf("go/packages.Load: no packages found for %s", query)
}
// Return this error as a diagnostic to the user.
return nil, err
}
- m, prevMissingImports, err := s.updateMetadata(ctx, uri, pkgs, cfg)
+ m, prevMissingImports, err := s.updateMetadata(ctx, scope, pkgs, cfg)
if err != nil {
return nil, err
}
@@ -65,7 +81,7 @@
}
func validateMetadata(ctx context.Context, metadata []*metadata, prevMissingImports map[packageID]map[packagePath]struct{}) ([]*metadata, error) {
- // If we saw incorrect metadata for this package previously, don't both rechecking it.
+ // If we saw incorrect metadata for this package previously, don't bother rechecking it.
for _, m := range metadata {
if len(m.missingDeps) > 0 {
prev, ok := prevMissingImports[m.id]
@@ -132,11 +148,22 @@
return false
}
-func (s *snapshot) updateMetadata(ctx context.Context, uri span.URI, pkgs []*packages.Package, cfg *packages.Config) ([]*metadata, map[packageID]map[packagePath]struct{}, error) {
+func (s *snapshot) updateMetadata(ctx context.Context, uri source.Scope, pkgs []*packages.Package, cfg *packages.Config) ([]*metadata, map[packageID]map[packagePath]struct{}, error) {
// Clear metadata since we are re-running go/packages.
+ var m []*metadata
+ switch uri.(type) {
+ case source.FileURI:
+ m = s.getMetadataForURI(uri.URI())
+ case source.DirectoryURI:
+ for _, pkg := range pkgs {
+ if pkgMetadata := s.getMetadata(packageID(pkg.ID)); pkgMetadata != nil {
+ m = append(m, pkgMetadata)
+ }
+ }
+ default:
+ panic(fmt.Errorf("unsupported Scope type %T", uri))
+ }
prevMissingImports := make(map[packageID]map[packagePath]struct{})
- m := s.getMetadataForURI(uri)
-
for _, m := range m {
if len(m.missingDeps) > 0 {
prevMissingImports[m.id] = m.missingDeps
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index f2eaa7a..d2b3b13 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -78,7 +78,7 @@
return s.cache
}
-func (s *session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) source.View {
+func (s *session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) (source.View, error) {
index := atomic.AddInt64(&viewIndex, 1)
s.viewMu.Lock()
defer s.viewMu.Unlock()
@@ -118,11 +118,32 @@
// so we immediately add builtin.go to the list of ignored files.
v.buildBuiltinPackage(ctx)
+ // Preemptively load everything in this directory.
+ // TODO(matloob): Determine if this can be done in parallel with something else.
+ // Perhaps different calls to NewView can be run in parallel?
+ // TODO(matloob): By default when a new file is opened, its data is invalidated
+ // and it's loaded again. Determine if the redundant reload can be avoided.
+ v.snapshotMu.Lock()
+ defer v.snapshotMu.Unlock() // The code after the snapshot is used isn't expensive.
+ m, err := v.snapshot.load(ctx, source.DirectoryURI(folder))
+ if err != nil {
+ return nil, err
+ }
+ // Prepare CheckPackageHandles for every package that's been loaded.
+ // (*snapshot).CheckPackageHandle makes the assumption that every package that's
+ // been loaded has an existing checkPackageHandle.
+ for _, m := range m {
+ _, err := v.snapshot.checkPackageHandle(ctx, m.id, source.ParseFull)
+ if err != nil {
+ return nil, err
+ }
+ }
+
s.views = append(s.views, v)
// we always need to drop the view map
s.viewMap = make(map[span.URI]source.View)
debug.AddView(debugView{v})
- return v
+ return v, nil
}
// View returns the view by name.
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index f409d73..766877f 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -79,11 +79,11 @@
s.packages[cph.packageKey()] = cph
}
-func (s *snapshot) getPackages(uri span.URI, m source.ParseMode) (cphs []source.CheckPackageHandle) {
+func (s *snapshot) getPackages(uri source.FileURI, m source.ParseMode) (cphs []source.CheckPackageHandle) {
s.mu.Lock()
defer s.mu.Unlock()
- if ids, ok := s.ids[uri]; ok {
+ if ids, ok := s.ids[uri.URI()]; ok {
for _, id := range ids {
key := packageKey{
id: id,
@@ -178,6 +178,8 @@
}
func (s *snapshot) getMetadataForURI(uri span.URI) (metadata []*metadata) {
+ // TODO(matloob): uri can be a file or directory. Should we update the mappings
+ // to map directories to their contained packages?
s.mu.Lock()
defer s.mu.Unlock()
@@ -340,6 +342,9 @@
ids[id] = struct{}{}
}
+ // Get the original FileHandle for the URI, if it exists.
+ originalFH := v.snapshot.getFile(f.URI())
+
switch changeType {
case protocol.Created:
// If this is a file we don't yet know about,
@@ -358,6 +363,8 @@
}
}
}
+ // If a file has been explicitly created, make sure that its original file handle is nil.
+ originalFH = nil
}
if len(ids) == 0 {
@@ -369,9 +376,6 @@
v.snapshot.reverseDependencies(id, withoutTypes, map[packageID]struct{}{})
}
- // Get the original FileHandle for the URI, if it exists.
- originalFH := v.snapshot.getFile(f.URI())
-
// Make sure to clear out the content if there has been a deletion.
if changeType == protocol.Deleted {
v.session.clearOverlay(f.URI())
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index 42b7472..a18fdd2 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -167,7 +167,7 @@
log.Print(ctx, buf.String())
for _, folder := range s.pendingFolders {
- if err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil {
+ if _, err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil {
return err
}
}
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 36be0fd..de92703 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -53,7 +53,10 @@
options := tests.DefaultOptions()
session.SetOptions(options)
options.Env = data.Config.Env
- session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options)
+ _, err := session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options)
+ if err != nil {
+ t.Fatal(err)
+ }
for filename, content := range data.Config.Overlay {
session.SetOverlay(span.FileURI(filename), source.DetectLanguage("", filename), content)
}
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 65a7371..b124674 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -51,8 +51,12 @@
session := cache.NewSession(ctx)
options := tests.DefaultOptions()
options.Env = data.Config.Env
+ view, err := session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir), options)
+ if err != nil {
+ t.Fatal(err)
+ }
r := &runner{
- view: session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir), options),
+ view: view,
data: data,
ctx: ctx,
}
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 747c923..dcbd1b8 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -142,7 +142,7 @@
// A session may have many active views at any given time.
type Session interface {
// NewView creates a new View and returns it.
- NewView(ctx context.Context, name string, folder span.URI, options Options) View
+ NewView(ctx context.Context, name string, folder span.URI, options Options) (View, error)
// Cache returns the cache that created this session.
Cache() Cache
@@ -287,6 +287,22 @@
Kind() FileKind
}
+type FileURI span.URI
+
+func (f FileURI) URI() span.URI {
+ return span.URI(f)
+}
+
+type DirectoryURI span.URI
+
+func (d DirectoryURI) URI() span.URI {
+ return span.URI(d)
+}
+
+type Scope interface {
+ URI() span.URI
+}
+
// Package represents a Go package that has been type-checked. It maintains
// only the relevant fields of a *go/packages.Package.
type Package interface {
diff --git a/internal/lsp/telemetry/telemetry.go b/internal/lsp/telemetry/telemetry.go
index ae4c246..d0c0f2e 100644
--- a/internal/lsp/telemetry/telemetry.go
+++ b/internal/lsp/telemetry/telemetry.go
@@ -20,6 +20,7 @@
RPCID = tag.Key("id")
RPCDirection = tag.Key("direction")
File = tag.Key("file")
+ Directory = tag.Key("directory")
URI = tag.Key("URI")
Package = tag.Key("package")
PackagePath = tag.Key("package_path")
diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go
index 42a252a..d20b9d3 100644
--- a/internal/lsp/workspace.go
+++ b/internal/lsp/workspace.go
@@ -8,6 +8,7 @@
"context"
"golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
@@ -23,23 +24,24 @@
}
for _, folder := range event.Added {
- if err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil {
+ if _, err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil {
return err
}
}
return nil
}
-func (s *Server) addView(ctx context.Context, name string, uri span.URI) error {
+func (s *Server) addView(ctx context.Context, name string, uri span.URI) (source.View, error) {
s.stateMu.Lock()
state := s.state
s.stateMu.Unlock()
if state < serverInitialized {
- return errors.Errorf("addView called before server initialized")
+ return nil, errors.Errorf("addView called before server initialized")
}
options := s.session.Options()
s.fetchConfig(ctx, name, uri, &options)
- s.session.NewView(ctx, name, uri, options)
- return nil
+
+ return s.session.NewView(ctx, name, uri, options)
+
}