diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index 76fda99d27..eb8d18d2b1 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -663,14 +663,17 @@ func (c *completer) wantTypeName() bool { } // See https://golang.org/issue/36001. Unimported completions are expensive. -const unimportedTarget = 100 +const ( + maxUnimportedPackageNames = 5 + unimportedMemberTarget = 100 +) // selector finds completions for the specified selector expression. func (c *completer) selector(sel *ast.SelectorExpr) error { // Is sel a qualified identifier? if id, ok := sel.X.(*ast.Ident); ok { - if pkgname, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok { - c.packageMembers(pkgname.Imported(), stdScore, nil) + if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok { + c.packageMembers(pkgName.Imported(), stdScore, nil) return nil } } @@ -682,7 +685,7 @@ func (c *completer) selector(sel *ast.SelectorExpr) error { } // Try unimported packages. - if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported && len(c.items) < unimportedTarget { + if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported { if err := c.unimportedMembers(id); err != nil { return err } @@ -696,6 +699,7 @@ func (c *completer) unimportedMembers(id *ast.Ident) error { if err != nil { return err } + var paths []string for path, pkg := range known { if pkg.GetTypes().Name() != id.Name { @@ -703,6 +707,7 @@ func (c *completer) unimportedMembers(id *ast.Ident) error { } paths = append(paths, path) } + var relevances map[string]int if len(paths) != 0 { c.snapshot.View().RunProcessEnvFunc(c.ctx, func(opts *imports.Options) error { @@ -710,6 +715,7 @@ func (c *completer) unimportedMembers(id *ast.Ident) error { return nil }) } + for path, pkg := range known { if pkg.GetTypes().Name() != id.Name { continue @@ -722,7 +728,7 @@ func (c *completer) unimportedMembers(id *ast.Ident) error { imp.name = pkg.GetTypes().Name() } c.packageMembers(pkg.GetTypes(), stdScore+.01*float64(relevances[path]), imp) - if len(c.items) >= unimportedTarget { + if len(c.items) >= unimportedMemberTarget { return nil } } @@ -750,7 +756,7 @@ func (c *completer) unimportedMembers(id *ast.Ident) error { }, }) } - if len(c.items) >= unimportedTarget { + if len(c.items) >= unimportedMemberTarget { cancel() } } @@ -930,7 +936,7 @@ func (c *completer) lexical() error { } } - if c.opts.unimported && len(c.items) < unimportedTarget { + if c.opts.unimported { ctx, cancel := c.deepCompletionContext() defer cancel() // Suggest packages that have not been imported yet. @@ -938,13 +944,22 @@ func (c *completer) lexical() error { if c.surrounding != nil { prefix = c.surrounding.Prefix() } - var mu sync.Mutex + var ( + mu sync.Mutex + initialItemCount = len(c.items) + ) add := func(pkg imports.ImportFix) { mu.Lock() defer mu.Unlock() if _, ok := seen[pkg.IdentName]; ok { return } + + if len(c.items)-initialItemCount >= maxUnimportedPackageNames { + cancel() + return + } + // Rank unimported packages significantly lower than other results. score := 0.01 * float64(pkg.Relevance) @@ -960,18 +975,6 @@ func (c *completer) lexical() error { name: pkg.StmtInfo.Name, }, }) - - if len(c.items) >= unimportedTarget { - cancel() - } - c.found(candidate{ - obj: obj, - score: score, - imp: &importInfo{ - importPath: pkg.StmtInfo.ImportPath, - name: pkg.StmtInfo.Name, - }, - }) } if err := c.snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error { return imports.GetAllCandidates(ctx, add, prefix, c.filename, c.pkg.GetTypes().Name(), opts) diff --git a/internal/lsp/testdata/lsp/primarymod/unimported/unimported.go.in b/internal/lsp/testdata/lsp/primarymod/unimported/unimported.go.in index ef5dc4caec..d9f109c6ff 100644 --- a/internal/lsp/testdata/lsp/primarymod/unimported/unimported.go.in +++ b/internal/lsp/testdata/lsp/primarymod/unimported/unimported.go.in @@ -1,19 +1,17 @@ package unimported func _() { - //@unimported("", hashslashadler32, goslashast, encodingslashbase64, bytes) - pkg //@unimported("g", externalpackage) + http //@unimported("p", nethttp, nethttptest) + pkg //@unimported("g", externalpackage) // container/ring is extremely unlikely to be imported by anything, so shouldn't have type information. ring.Ring //@unimported("Ring", ringring) signature.Foo //@unimported("Foo", signaturefoo) - context.Bac //@unimported("Bac", contextBackground, contextBackgroundErr) + context.Bac //@unimported("Bac", contextBackground, contextBackgroundErr) } // Create markers for unimported std lib packages. Only for use by this test. -/* adler32 */ //@item(hashslashadler32, "adler32", "\"hash/adler32\"", "package") -/* ast */ //@item(goslashast, "ast", "\"go/ast\"", "package") -/* base64 */ //@item(encodingslashbase64, "base64", "\"encoding/base64\"", "package") -/* bytes */ //@item(bytes, "bytes", "\"bytes\"", "package") +/* http */ //@item(nethttp, "http", "\"net/http\"", "package") +/* httptest */ //@item(nethttptest, "httptest", "\"net/http/httptest\"", "package") /* pkg */ //@item(externalpackage, "pkg", "\"example.com/extramodule/pkg\"", "package") /* ring.Ring */ //@item(ringring, "Ring", "(from \"container/ring\")", "var")