diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 448273ab0c..4b97ee6366 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -126,7 +126,7 @@ type workspaceInformation struct { } type environmentVariables struct { - gocache, gopath, goprivate, gomodcache, gomod string + gocache, gopath, goprivate, gomodcache string } type workspaceMode int @@ -651,13 +651,15 @@ func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI, tool, _ := exec.LookPath("gopackagesdriver") hasGopackagesDriver := gopackagesdriver != "off" && (gopackagesdriver != "" || tool != "") - var modURI span.URI - if envVars.gomod != os.DevNull && envVars.gomod != "" { - modURI = span.URIFromPath(envVars.gomod) - } root := folder - if options.ExpandWorkspaceToModule && modURI != "" { - root = span.URIFromPath(filepath.Dir(modURI.Filename())) + if options.ExpandWorkspaceToModule { + wsRoot, err := findWorkspaceRoot(ctx, root, s) + if err != nil { + return nil, err + } + if wsRoot != "" { + root = wsRoot + } } return &workspaceInformation{ hasGopackagesDriver: hasGopackagesDriver, @@ -669,6 +671,39 @@ func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI, }, nil } +func findWorkspaceRoot(ctx context.Context, folder span.URI, fs source.FileSource) (span.URI, error) { + for _, basename := range []string{"gopls.mod", "go.mod"} { + dir, err := findRootPattern(ctx, folder, basename, fs) + if err != nil { + return "", errors.Errorf("finding %s: %w", basename, err) + } + if dir != "" { + return dir, nil + } + } + return "", nil +} + +func findRootPattern(ctx context.Context, folder span.URI, basename string, fs source.FileSource) (span.URI, error) { + dir := folder.Filename() + for dir != "" { + target := filepath.Join(dir, basename) + exists, err := fileExists(ctx, span.URIFromPath(target), fs) + if err != nil { + return "", err + } + if exists { + return span.URIFromPath(dir), nil + } + next, _ := filepath.Split(dir) + if next == dir { + break + } + dir = next + } + return "", nil +} + // OS-specific path case check, for case-insensitive filesystems. var checkPathCase = defaultCheckPathCase @@ -710,7 +745,6 @@ func (s *Session) getGoEnv(ctx context.Context, folder string, configEnv []strin "GOPATH": &envVars.gopath, "GOPRIVATE": &envVars.goprivate, "GOMODCACHE": &envVars.gomodcache, - "GOMOD": &envVars.gomod, } // We can save ~200 ms by requesting only the variables we care about. args := append([]string{"-json"}, imports.RequiredGoEnvVars...) diff --git a/internal/lsp/cache/view_test.go b/internal/lsp/cache/view_test.go index 0dad594421..87774e6714 100644 --- a/internal/lsp/cache/view_test.go +++ b/internal/lsp/cache/view_test.go @@ -4,10 +4,14 @@ package cache import ( + "context" "io/ioutil" "os" "path/filepath" "testing" + + "golang.org/x/tools/internal/lsp/fake" + "golang.org/x/tools/internal/span" ) func TestCaseInsensitiveFilesystem(t *testing.T) { @@ -43,3 +47,50 @@ func TestCaseInsensitiveFilesystem(t *testing.T) { } } } + +func TestFindWorkspaceRoot(t *testing.T) { + workspace := ` +-- a/go.mod -- +module a +-- a/x/x.go +package x +-- b/go.mod -- +module b +-- b/c/go.mod -- +module bc +-- d/gopls.mod -- +module d-goplsworkspace +-- d/e/go.mod +module de +` + dir, err := fake.Tempdir(workspace) + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + tests := []struct { + folder, want string + }{ + // no module at root. + {"", ""}, + {"a", "a"}, + {"a/x", "a"}, + {"b/c", "b/c"}, + {"d", "d"}, + {"d/e", "d"}, + } + + for _, test := range tests { + ctx := context.Background() + rel := fake.RelativeTo(dir) + folderURI := span.URIFromPath(rel.AbsPath(test.folder)) + got, err := findWorkspaceRoot(ctx, folderURI, osFileSource{}) + if err != nil { + t.Fatal(err) + } + if rel.RelPath(got.Filename()) != test.want { + t.Errorf("fileWorkspaceRoot(%q) = %q, want %q", test.folder, got, test.want) + } + } +}