internal/lsp: un-export (*snapshot).Config to limit it to cache

Limiting the Config to the view seems reasonable, considering that it is
only used to run the `go` command. I prefer just having the cache run
go commands, so that source doesn't have to deal with the environment.

This also enables CL 237517.

Change-Id: I639c082592de30e9682dc25cdd12c7751ddb4f97
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237600
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index 1068784..64dbf16 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -83,7 +83,7 @@
 	ctx, done := event.Start(ctx, "cache.view.load", tag.Query.Of(query))
 	defer done()
 
-	cfg := s.Config(ctx)
+	cfg := s.config(ctx)
 	pkgs, err := packages.Load(cfg, query...)
 
 	// If the context was canceled, return early. Otherwise, we might be
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
index abbd98b..b9d41a8 100644
--- a/internal/lsp/cache/mod.go
+++ b/internal/lsp/cache/mod.go
@@ -138,7 +138,8 @@
 	}
 	realURI, tempURI := s.view.ModFiles()
 	folder := s.View().Folder().Filename()
-	cfg := s.Config(ctx)
+	cfg := s.config(ctx)
+
 	key := modKey{
 		sessionID: s.view.session.id,
 		cfg:       hashConfig(cfg),
@@ -288,7 +289,7 @@
 	}
 
 	realURI, tempURI := s.view.ModFiles()
-	cfg := s.Config(ctx)
+	cfg := s.config(ctx)
 	options := s.View().Options()
 	folder := s.View().Folder().Filename()
 	gocmdRunner := s.view.gocmdRunner
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index cc975bb..0d400c8 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -5,11 +5,13 @@
 package cache
 
 import (
+	"bytes"
 	"context"
 	"fmt"
 	"go/ast"
 	"go/token"
 	"go/types"
+	"io"
 	"os"
 	"path/filepath"
 	"sort"
@@ -19,6 +21,7 @@
 	"golang.org/x/tools/go/analysis"
 	"golang.org/x/tools/go/packages"
 	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/lsp/debug/tag"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/packagesinternal"
@@ -91,7 +94,7 @@
 
 // Config returns the configuration used for the snapshot's interaction with the
 // go/packages API.
-func (s *snapshot) Config(ctx context.Context) *packages.Config {
+func (s *snapshot) config(ctx context.Context) *packages.Config {
 	s.view.optionsMu.Lock()
 	env, buildFlags := s.view.envLocked()
 	verboseOutput := s.view.options.VerboseOutput
@@ -130,6 +133,32 @@
 	return cfg
 }
 
+func (s *snapshot) RunGoCommand(ctx context.Context, verb string, args []string) (*bytes.Buffer, error) {
+	cfg := s.config(ctx)
+	inv := gocommand.Invocation{
+		Verb:       verb,
+		Args:       args,
+		Env:        cfg.Env,
+		BuildFlags: cfg.BuildFlags,
+		WorkingDir: s.view.folder.Filename(),
+	}
+	runner := packagesinternal.GetGoCmdRunner(cfg)
+	return runner.Run(ctx, inv)
+}
+
+func (s *snapshot) RunGoCommandPiped(ctx context.Context, verb string, args []string, stdout, stderr io.Writer) error {
+	cfg := s.config(ctx)
+	inv := gocommand.Invocation{
+		Verb:       verb,
+		Args:       args,
+		Env:        cfg.Env,
+		BuildFlags: cfg.BuildFlags,
+		WorkingDir: s.view.folder.Filename(),
+	}
+	runner := packagesinternal.GetGoCmdRunner(cfg)
+	return runner.RunPiped(ctx, inv, stdout, stderr)
+}
+
 func (s *snapshot) buildOverlay() map[string][]byte {
 	s.mu.Lock()
 	defer s.mu.Unlock()
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index a5bf043..5b17f7c 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -417,6 +417,8 @@
 	return processEnv, nil
 }
 
+// envLocked returns the environment and build flags for the current view.
+// It assumes that the caller is holding the view's optionsMu.
 func (v *View) envLocked() ([]string, []string) {
 	// We want to run the go commands with the -modfile flag if the version of go
 	// that we are using supports it.
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index 159df2a..6693e30 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -7,15 +7,11 @@
 import (
 	"context"
 	"io"
-	"path/filepath"
 	"strings"
 
-	"golang.org/x/tools/internal/event"
-	"golang.org/x/tools/internal/gocommand"
-	"golang.org/x/tools/internal/lsp/debug/tag"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/packagesinternal"
+	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/xcontext"
 	errors "golang.org/x/xerrors"
 )
@@ -40,12 +36,11 @@
 		if err != nil {
 			return nil, err
 		}
-		snapshot, fh, ok, err := s.beginFileRequest(ctx, protocol.DocumentURI(uri), source.Go)
-		if !ok {
+		view, err := s.session.ViewOf(uri)
+		if err != nil {
 			return nil, err
 		}
-		dir := filepath.Dir(fh.URI().Filename())
-		go s.runTest(ctx, funcName, dir, snapshot)
+		go s.runTest(ctx, view.Snapshot(), funcName)
 	case source.CommandGenerate:
 		dir, recursive, err := getGenerateRequest(params.Arguments)
 		if err != nil {
@@ -90,44 +85,26 @@
 	if err != nil {
 		return err
 	}
-	snapshot := view.Snapshot()
-	cfg := snapshot.Config(ctx)
-	inv := gocommand.Invocation{
-		Verb:       verb,
-		Args:       args,
-		Env:        cfg.Env,
-		WorkingDir: view.Folder().Filename(),
-	}
-	gocmdRunner := packagesinternal.GetGoCmdRunner(cfg)
-	_, err = gocmdRunner.Run(ctx, inv)
+	_, err = view.Snapshot().RunGoCommand(ctx, verb, args)
 	return err
 }
 
-func (s *Server) runTest(ctx context.Context, funcName string, dir string, snapshot source.Snapshot) error {
+func (s *Server) runTest(ctx context.Context, snapshot source.Snapshot, funcName string) error {
 	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
 
-	er := &eventWriter{ctx: ctx, operation: "test"}
+	ew := &eventWriter{ctx: ctx, operation: "test"}
 	wc := s.newProgressWriter(ctx, "test", "running "+funcName, cancel)
 	defer wc.Close()
 
 	messageType := protocol.Info
 	message := "test passed"
-	stderr := io.MultiWriter(er, wc)
+	stderr := io.MultiWriter(ew, wc)
 
-	cfg := snapshot.Config(ctx)
-	inv := gocommand.Invocation{
-		Verb:       "test",
-		Args:       []string{"-run", funcName, dir},
-		Env:        cfg.Env,
-		WorkingDir: dir,
-	}
-	runner := packagesinternal.GetGoCmdRunner(cfg)
-	if err := runner.RunPiped(ctx, inv, er, stderr); err != nil {
+	if err := snapshot.RunGoCommandPiped(ctx, "test", []string{"-run", funcName}, ew, stderr); err != nil {
 		if errors.Is(err, context.Canceled) {
-			return nil
+			return err
 		}
-		event.Error(ctx, "test: command error", err, tag.Directory.Of(dir))
 		messageType = protocol.Error
 		message = "test failed"
 	}
@@ -137,22 +114,19 @@
 	})
 }
 
-func getRunTestArguments(args []interface{}) (string, string, error) {
+func getRunTestArguments(args []interface{}) (string, span.URI, error) {
 	if len(args) != 2 {
 		return "", "", errors.Errorf("expected one test func name and one file path, got %v", args)
 	}
-
 	funcName, ok := args[0].(string)
 	if !ok {
 		return "", "", errors.Errorf("expected func name to be a string, got %T", args[0])
 	}
-
-	file, ok := args[1].(string)
+	filename, ok := args[1].(string)
 	if !ok {
 		return "", "", errors.Errorf("expected file to be a string, got %T", args[1])
 	}
-
-	return funcName, file, nil
+	return funcName, span.URIFromPath(filename), nil
 }
 
 func getGenerateRequest(args []interface{}) (string, bool, error) {
diff --git a/internal/lsp/generate.go b/internal/lsp/generate.go
index 970142e..d850dec 100644
--- a/internal/lsp/generate.go
+++ b/internal/lsp/generate.go
@@ -9,10 +9,8 @@
 	"io"
 
 	"golang.org/x/tools/internal/event"
-	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/lsp/debug/tag"
 	"golang.org/x/tools/internal/lsp/protocol"
-	"golang.org/x/tools/internal/packagesinternal"
 	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
 )
@@ -40,15 +38,7 @@
 		return err
 	}
 	snapshot := view.Snapshot()
-	cfg := snapshot.Config(ctx)
-	inv := gocommand.Invocation{
-		Verb:       "generate",
-		Args:       args,
-		Env:        cfg.Env,
-		WorkingDir: dir,
-	}
-	runner := packagesinternal.GetGoCmdRunner(cfg)
-	if err := runner.RunPiped(ctx, inv, er, stderr); err != nil {
+	if err := snapshot.RunGoCommandPiped(ctx, "generate", args, er, stderr); err != nil {
 		if errors.Is(err, context.Canceled) {
 			return nil
 		}
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index cf0b4c5..e75deae 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -12,7 +12,6 @@
 	"os/exec"
 	"path/filepath"
 	"sort"
-	"strings"
 	"testing"
 
 	"golang.org/x/tools/go/packages/packagestest"
@@ -69,11 +68,9 @@
 		// Check to see if the -modfile flag is available, this is basically a check
 		// to see if the go version >= 1.14. Otherwise, the modfile specific tests
 		// will always fail if this flag is not available.
-		for _, flag := range view.Snapshot().Config(ctx).BuildFlags {
-			if strings.Contains(flag, "-modfile=") {
-				datum.ModfileFlagAvailable = true
-				break
-			}
+		if _, tmp := view.ModFiles(); tmp != "" {
+			datum.ModfileFlagAvailable = true
+			break
 		}
 		var modifications []source.FileModification
 		for filename, content := range datum.Config.Overlay {
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index f1649e5..385000f 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -5,6 +5,7 @@
 package source
 
 import (
+	"bytes"
 	"context"
 	"fmt"
 	"go/ast"
@@ -28,9 +29,6 @@
 	// View returns the View associated with this snapshot.
 	View() View
 
-	// Config returns the configuration for the view.
-	Config(ctx context.Context) *packages.Config
-
 	// FindFile returns the FileHandle for the given URI, if it is already
 	// in the given snapshot.
 	FindFile(uri span.URI) FileHandle
@@ -48,6 +46,13 @@
 	// Analyze runs the analyses for the given package at this snapshot.
 	Analyze(ctx context.Context, pkgID string, analyzers ...*analysis.Analyzer) ([]*Error, error)
 
+	// RunGoCommandPiped runs the given `go` command in the view, using the
+	// provided stdout and stderr.
+	RunGoCommandPiped(ctx context.Context, verb string, args []string, stdout, stderr io.Writer) error
+
+	// RunGoCommand runs the given `go` command in the view.
+	RunGoCommand(ctx context.Context, verb string, args []string) (*bytes.Buffer, error)
+
 	// ModTidyHandle returns a ModTidyHandle for the given go.mod file handle.
 	// This function can have no data or error if there is no modfile detected.
 	ModTidyHandle(ctx context.Context, fh FileHandle) (ModTidyHandle, error)
@@ -110,7 +115,8 @@
 	// Folder returns the root folder for this view.
 	Folder() span.URI
 
-	// ModFiles returns the URIs of the go.mod files attached to the view associated with this snapshot.
+	// ModFiles returns the URIs of the go.mod files attached to the view
+	// associated with this snapshot.
 	ModFiles() (span.URI, span.URI)
 
 	// BuiltinPackage returns the go/ast.Object for the given name in the builtin package.