diff --git a/gopls/internal/golang/completion/unimported.go b/gopls/internal/golang/completion/unimported.go
index ea92afa..0f8beef 100644
--- a/gopls/internal/golang/completion/unimported.go
+++ b/gopls/internal/golang/completion/unimported.go
@@ -28,6 +28,7 @@
"go/ast"
"go/printer"
"go/token"
+ "log"
"path"
"slices"
"strings"
@@ -72,6 +73,7 @@
// look in the module cache
items, err := c.modcacheMatches(pkgname, prefix)
+ items = c.filterGoMod(ctx, items)
if err == nil && c.scoreList(items) {
return
}
@@ -79,6 +81,49 @@
// out of things to do
}
+func init() {
+ log.SetFlags(log.Lshortfile)
+}
+
+// prefer completion items that are referenced in the go.mod file
+func (c *completer) filterGoMod(ctx context.Context, items []CompletionItem) []CompletionItem {
+ gomod := c.pkg.Metadata().Module.GoMod
+ uri := protocol.URIFromPath(gomod)
+ fh, err := c.snapshot.ReadFile(ctx, uri)
+ if err != nil {
+ return items
+ }
+ pm, err := c.snapshot.ParseMod(ctx, fh)
+ if err != nil || pm == nil {
+ return items
+ }
+ // if any of the items match any of the req, just return those
+ reqnames := []string{}
+ for _, req := range pm.File.Require {
+ reqnames = append(reqnames, req.Mod.Path)
+ }
+ better := []CompletionItem{}
+ for _, compl := range items {
+ if len(compl.AdditionalTextEdits) == 0 {
+ continue
+ }
+ // import "foof/pkg"
+ flds := strings.FieldsFunc(compl.AdditionalTextEdits[0].NewText, func(r rune) bool {
+ return r == '"' || r == '/'
+ })
+ if len(flds) < 3 {
+ continue
+ }
+ if slices.Contains(reqnames, flds[1]) {
+ better = append(better, compl)
+ }
+ }
+ if len(better) > 0 {
+ return better
+ }
+ return items
+}
+
// see if some file in the current package satisfied a foo. import
// because foo is an explicit package name (import foo "a.b.c")
func (c *completer) explicitPkgName(ctx context.Context, pkgname metadata.PackageName, prefix string) bool {
diff --git a/gopls/internal/test/integration/completion/completion_test.go b/gopls/internal/test/integration/completion/completion_test.go
index 2ca9626..69a0635 100644
--- a/gopls/internal/test/integration/completion/completion_test.go
+++ b/gopls/internal/test/integration/completion/completion_test.go
@@ -7,6 +7,7 @@
import (
"fmt"
"os"
+ "path/filepath"
"slices"
"sort"
"strings"
@@ -366,6 +367,7 @@
_ = blah.Hello
}
`
+
WithOptions(
WriteGoSum("."),
ProxyFiles(proxy),
@@ -1464,6 +1466,71 @@
})
}
+// when completing using the module cache, prefer things mentioned
+// in the go.mod file.
+func TestIssue61208(t *testing.T) {
+
+ const cache = `
+-- examp...@v1.2.3/go.mod --
+module example.com
+
+go 1.22
+-- examp...@v1.2.3/blah/blah.go --
+package blah
+
+const Name = "Blah"
+-- rando...@v1.2.3/go.mod --
+module random.org
+
+go 1.22
+-- rando...@v1.2.3/blah/blah.go --
+package blah
+
+const Name = "Hello"
+`
+ const files = `
+-- go.mod --
+module mod.com
+go 1.22
+require random.org v1.2.3
+-- main.go --
+package main
+var _ = blah.
+`
+ modcache := t.TempDir()
+ defer CleanModCache(t, modcache)
+ mx := fake.UnpackTxt(cache)
+ for k, v := range mx {
+ fname := filepath.Join(modcache, k)
+ dir := filepath.Dir(fname)
+ os.MkdirAll(dir, 0777) // ignore error
+ if err := os.WriteFile(fname, v, 0644); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ WithOptions(
+ EnvVars{"GOMODCACHE": modcache},
+ WriteGoSum("."),
+ Settings{"importsSource": settings.ImportsSourceGopls},
+ NoLogsOnError(),
+ ).Run(t, files, func(t *testing.T, env *Env) {
+ env.OpenFile("main.go")
+ env.Await(env.DoneWithOpen())
+ loc := env.RegexpSearch("main.go", "blah.()")
+ completions := env.Completion(loc)
+ if len(completions.Items) != 1 {
+ t.Errorf("got %d, expected 1", len(completions.Items))
+ for _, x := range completions.Items {
+ t.Logf("%#v", x.AdditionalTextEdits[0].NewText)
+ }
+ }
+ if got := completions.Items[0].AdditionalTextEdits[0].NewText; !strings.Contains(got, `"random.org`) {
+ t.Errorf("got %q, expected a `random.org`", got)
+ }
+ })
+}
+
// show that the efficacy counters get exercised. Fortuntely a small program
// exercises them all
func TestCounters(t *testing.T) {