internal/lsp: use options hooks to install diff driver

Change-Id: I2f94c2a68d0036a47ccac3fce07cf9f3b784d443
Reviewed-on: https://go-review.googlesource.com/c/tools/+/200558
Run-TryBot: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/cmd/gopls/main.go b/cmd/gopls/main.go
index d8ab337..6b6e735 100644
--- a/cmd/gopls/main.go
+++ b/cmd/gopls/main.go
@@ -19,5 +19,5 @@
 
 func main() {
 	debug.Version += "-cmd.gopls"
-	tool.Main(context.Background(), cmd.New("gopls-legacy", "", nil), os.Args[1:])
+	tool.Main(context.Background(), cmd.New("gopls-legacy", "", nil, nil), os.Args[1:])
 }
diff --git a/gopls/internal/hooks/analysis.go b/gopls/internal/hooks/analysis.go
index ef385a3..ca437ad 100644
--- a/gopls/internal/hooks/analysis.go
+++ b/gopls/internal/hooks/analysis.go
@@ -5,24 +5,22 @@
 package hooks
 
 import (
-	"golang.org/x/tools/go/analysis"
 	"golang.org/x/tools/internal/lsp/source"
 	"honnef.co/go/tools/simple"
 	"honnef.co/go/tools/staticcheck"
 	"honnef.co/go/tools/stylecheck"
 )
 
-func updateAnalyzers(v source.View, analyzers []*analysis.Analyzer) []*analysis.Analyzer {
-	if v.Options().StaticCheck {
+func updateAnalyzers(options *source.Options) {
+	if options.StaticCheck {
 		for _, a := range simple.Analyzers {
-			analyzers = append(analyzers, a)
+			options.Analyzers = append(options.Analyzers, a)
 		}
 		for _, a := range staticcheck.Analyzers {
-			analyzers = append(analyzers, a)
+			options.Analyzers = append(options.Analyzers, a)
 		}
 		for _, a := range stylecheck.Analyzers {
-			analyzers = append(analyzers, a)
+			options.Analyzers = append(options.Analyzers, a)
 		}
 	}
-	return analyzers
 }
diff --git a/gopls/internal/hooks/hooks.go b/gopls/internal/hooks/hooks.go
index f9127dc..9037c3b 100644
--- a/gopls/internal/hooks/hooks.go
+++ b/gopls/internal/hooks/hooks.go
@@ -8,12 +8,12 @@
 package hooks // import "golang.org/x/tools/gopls/internal/hooks"
 
 import (
-	"context"
-
-	"golang.org/x/tools/internal/lsp/cache"
+	"golang.org/x/tools/internal/lsp/source"
 )
 
-func Install(ctx context.Context) context.Context {
-	cache.UpdateAnalyzers = updateAnalyzers
-	return ctx
+func Options(options *source.Options) {
+	if options.GoDiff {
+		options.ComputeEdits = ComputeEdits
+	}
+	updateAnalyzers(options)
 }
diff --git a/gopls/main.go b/gopls/main.go
index 31d356e..0b4d889 100644
--- a/gopls/main.go
+++ b/gopls/main.go
@@ -19,6 +19,5 @@
 
 func main() {
 	ctx := context.Background()
-	ctx = hooks.Install(ctx)
-	tool.Main(ctx, cmd.New("gopls", "", nil), os.Args[1:])
+	tool.Main(ctx, cmd.New("gopls", "", nil, hooks.Options), os.Args[1:])
 }
diff --git a/gopls/test/gopls_test.go b/gopls/test/gopls_test.go
index 5245579..15ecfa4 100644
--- a/gopls/test/gopls_test.go
+++ b/gopls/test/gopls_test.go
@@ -5,7 +5,6 @@
 package gopls_test
 
 import (
-	"context"
 	"os"
 	"testing"
 
@@ -30,9 +29,7 @@
 	if stat, err := os.Stat(testdata); err != nil || !stat.IsDir() {
 		t.Skip("testdata directory not present")
 	}
-	ctx := context.Background()
-	hooks.Install(ctx)
 	data := tests.Load(t, exporter, testdata)
 	defer data.Exported.Cleanup()
-	tests.Run(t, cmdtest.NewRunner(exporter, data, tests.Context(t)), data)
+	tests.Run(t, cmdtest.NewRunner(exporter, data, tests.Context(t), hooks.Options), data)
 }
diff --git a/internal/lsp/cache/analysis.go b/internal/lsp/cache/analysis.go
deleted file mode 100644
index 21a294f..0000000
--- a/internal/lsp/cache/analysis.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package cache
-
-import (
-	"golang.org/x/tools/go/analysis"
-	"golang.org/x/tools/go/analysis/passes/asmdecl"
-	"golang.org/x/tools/go/analysis/passes/assign"
-	"golang.org/x/tools/go/analysis/passes/atomic"
-	"golang.org/x/tools/go/analysis/passes/atomicalign"
-	"golang.org/x/tools/go/analysis/passes/bools"
-	"golang.org/x/tools/go/analysis/passes/buildtag"
-	"golang.org/x/tools/go/analysis/passes/cgocall"
-	"golang.org/x/tools/go/analysis/passes/composite"
-	"golang.org/x/tools/go/analysis/passes/copylock"
-	"golang.org/x/tools/go/analysis/passes/httpresponse"
-	"golang.org/x/tools/go/analysis/passes/loopclosure"
-	"golang.org/x/tools/go/analysis/passes/lostcancel"
-	"golang.org/x/tools/go/analysis/passes/nilfunc"
-	"golang.org/x/tools/go/analysis/passes/printf"
-	"golang.org/x/tools/go/analysis/passes/shift"
-	"golang.org/x/tools/go/analysis/passes/sortslice"
-	"golang.org/x/tools/go/analysis/passes/stdmethods"
-	"golang.org/x/tools/go/analysis/passes/structtag"
-	"golang.org/x/tools/go/analysis/passes/tests"
-	"golang.org/x/tools/go/analysis/passes/unmarshal"
-	"golang.org/x/tools/go/analysis/passes/unreachable"
-	"golang.org/x/tools/go/analysis/passes/unsafeptr"
-	"golang.org/x/tools/go/analysis/passes/unusedresult"
-	"golang.org/x/tools/internal/lsp/source"
-)
-
-var defaultAnalyzers = []*analysis.Analyzer{
-	// The traditional vet suite:
-	asmdecl.Analyzer,
-	assign.Analyzer,
-	atomic.Analyzer,
-	atomicalign.Analyzer,
-	bools.Analyzer,
-	buildtag.Analyzer,
-	cgocall.Analyzer,
-	composite.Analyzer,
-	copylock.Analyzer,
-	httpresponse.Analyzer,
-	loopclosure.Analyzer,
-	lostcancel.Analyzer,
-	nilfunc.Analyzer,
-	printf.Analyzer,
-	shift.Analyzer,
-	stdmethods.Analyzer,
-	structtag.Analyzer,
-	tests.Analyzer,
-	unmarshal.Analyzer,
-	unreachable.Analyzer,
-	unsafeptr.Analyzer,
-	unusedresult.Analyzer,
-	// Non-vet analyzers
-	sortslice.Analyzer,
-}
-
-var UpdateAnalyzers = func(v source.View, analyzers []*analysis.Analyzer) []*analysis.Analyzer {
-	return analyzers
-}
diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
index 764d077..e3a1396 100644
--- a/internal/lsp/cache/cache.go
+++ b/internal/lsp/cache/cache.go
@@ -18,21 +18,23 @@
 	"golang.org/x/tools/internal/span"
 )
 
-func New() source.Cache {
+func New(options func(*source.Options)) source.Cache {
 	index := atomic.AddInt64(&cacheIndex, 1)
 	c := &cache{
-		fs:   &nativeFileSystem{},
-		id:   strconv.FormatInt(index, 10),
-		fset: token.NewFileSet(),
+		fs:      &nativeFileSystem{},
+		id:      strconv.FormatInt(index, 10),
+		fset:    token.NewFileSet(),
+		options: options,
 	}
 	debug.AddCache(debugCache{c})
 	return c
 }
 
 type cache struct {
-	fs   source.FileSystem
-	id   string
-	fset *token.FileSet
+	fs      source.FileSystem
+	id      string
+	fset    *token.FileSet
+	options func(*source.Options)
 
 	store memoize.Store
 }
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 5a41423..805e8b5 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -110,7 +110,10 @@
 	}
 	v.snapshot.view = v
 
-	v.analyzers = UpdateAnalyzers(v, defaultAnalyzers)
+	if v.session.cache.options != nil {
+		v.session.cache.options(&v.options)
+	}
+
 	// Preemptively build the builtin package,
 	// so we immediately add builtin.go to the list of ignored files.
 	v.buildBuiltinPackage(ctx)
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index c840a81..436ae3a 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -15,7 +15,6 @@
 	"strings"
 	"sync"
 
-	"golang.org/x/tools/go/analysis"
 	"golang.org/x/tools/go/packages"
 	"golang.org/x/tools/internal/imports"
 	"golang.org/x/tools/internal/lsp/debug"
@@ -80,8 +79,6 @@
 	// ignoredURIs is the set of URIs of files that we ignore.
 	ignoredURIsMu sync.Mutex
 	ignoredURIs   map[span.URI]struct{}
-
-	analyzers []*analysis.Analyzer
 }
 
 func (v *view) Session() source.Session {
@@ -396,10 +393,6 @@
 	return nil, nil
 }
 
-func (v *view) Analyzers() []*analysis.Analyzer {
-	return v.analyzers
-}
-
 func (f *fileBase) addURI(uri span.URI) int {
 	f.uris = append(f.uris, uri)
 	return len(f.uris)
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index eff20ab..d74a5af 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -65,15 +65,19 @@
 
 	// Control ocagent export of telemetry
 	OCAgent string `flag:"ocagent" help:"The address of the ocagent, or off"`
+
+	// PrepareOptions is called to update the options when a new view is built.
+	// It is primarily to allow the behavior of gopls to be modified by hooks.
+	PrepareOptions func(*source.Options)
 }
 
 // Returns a new Application ready to run.
-func New(name, wd string, env []string) *Application {
+func New(name, wd string, env []string, options func(*source.Options)) *Application {
 	if wd == "" {
 		wd, _ = os.Getwd()
 	}
 	app := &Application{
-		cache:   cache.New(),
+		cache:   cache.New(options),
 		name:    name,
 		wd:      wd,
 		env:     env,
diff --git a/internal/lsp/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go
index 1440459..af04e8d 100644
--- a/internal/lsp/cmd/cmd_test.go
+++ b/internal/lsp/cmd/cmd_test.go
@@ -32,7 +32,7 @@
 func testCommandLine(t *testing.T, exporter packagestest.Exporter) {
 	data := tests.Load(t, exporter, "../testdata")
 	defer data.Exported.Cleanup()
-	tests.Run(t, cmdtest.NewRunner(exporter, data, tests.Context(t)), data)
+	tests.Run(t, cmdtest.NewRunner(exporter, data, tests.Context(t), nil), data)
 }
 
 func TestDefinitionHelpExample(t *testing.T) {
@@ -54,7 +54,7 @@
 		fmt.Sprintf("%v:#%v", thisFile, cmd.ExampleOffset)} {
 		args := append(baseArgs, query)
 		got := cmdtest.CaptureStdOut(t, func() {
-			_ = tool.Run(tests.Context(t), cmd.New("gopls-test", "", nil), args)
+			_ = tool.Run(tests.Context(t), cmd.New("gopls-test", "", nil, nil), args)
 		})
 		if !expect.MatchString(got) {
 			t.Errorf("test with %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
diff --git a/internal/lsp/cmd/test/check.go b/internal/lsp/cmd/test/check.go
index 39dd3fb..c368998 100644
--- a/internal/lsp/cmd/test/check.go
+++ b/internal/lsp/cmd/test/check.go
@@ -22,7 +22,7 @@
 	}
 	fname := uri.Filename()
 	args := []string{"-remote=internal", "check", fname}
-	app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env)
+	app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env, r.options)
 	out := CaptureStdOut(t, func() {
 		_ = tool.Run(r.ctx, app, args)
 	})
diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go
index 13607f2..8aa8638 100644
--- a/internal/lsp/cmd/test/cmdtest.go
+++ b/internal/lsp/cmd/test/cmdtest.go
@@ -26,13 +26,15 @@
 	exporter packagestest.Exporter
 	data     *tests.Data
 	ctx      context.Context
+	options  func(*source.Options)
 }
 
-func NewRunner(exporter packagestest.Exporter, data *tests.Data, ctx context.Context) tests.Tests {
+func NewRunner(exporter packagestest.Exporter, data *tests.Data, ctx context.Context, options func(*source.Options)) tests.Tests {
 	return &runner{
 		exporter: exporter,
 		data:     data,
 		ctx:      ctx,
+		options:  options,
 	}
 }
 
diff --git a/internal/lsp/cmd/test/definition.go b/internal/lsp/cmd/test/definition.go
index 6bbcdd1..e91b511 100644
--- a/internal/lsp/cmd/test/definition.go
+++ b/internal/lsp/cmd/test/definition.go
@@ -54,7 +54,7 @@
 		uri := d.Src.URI()
 		args = append(args, fmt.Sprint(d.Src))
 		got := CaptureStdOut(t, func() {
-			app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env)
+			app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env, r.options)
 			_ = tool.Run(r.ctx, app, args)
 		})
 		got = normalizePaths(r.data, got)
diff --git a/internal/lsp/cmd/test/format.go b/internal/lsp/cmd/test/format.go
index 0418b31..355f0d9 100644
--- a/internal/lsp/cmd/test/format.go
+++ b/internal/lsp/cmd/test/format.go
@@ -33,7 +33,7 @@
 		//TODO: our error handling differs, for now just skip unformattable files
 		t.Skip("Unformattable file")
 	}
-	app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env)
+	app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env, r.options)
 	got := CaptureStdOut(t, func() {
 		_ = tool.Run(r.ctx, app, append([]string{"-remote=internal", "format"}, filename))
 	})
diff --git a/internal/lsp/cmd/test/rename.go b/internal/lsp/cmd/test/rename.go
index c5297b7..9b227b9 100644
--- a/internal/lsp/cmd/test/rename.go
+++ b/internal/lsp/cmd/test/rename.go
@@ -16,7 +16,7 @@
 func (r *runner) Rename(t *testing.T, spn span.Span, newText string) {
 	filename := spn.URI().Filename()
 	goldenTag := newText + "-rename"
-	app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env)
+	app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env, r.options)
 	loc := fmt.Sprintf("%v", spn)
 	var err error
 	got := CaptureStdOut(t, func() {
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 4b2856c..283d458 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -48,7 +48,7 @@
 	data := tests.Load(t, exporter, "testdata")
 	defer data.Exported.Cleanup()
 
-	cache := cache.New()
+	cache := cache.New(nil)
 	session := cache.NewSession(ctx)
 	options := tests.DefaultOptions()
 	session.SetOptions(options)
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 04c4737..a528bac 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -305,7 +305,7 @@
 
 func runAnalyses(ctx context.Context, view View, cph CheckPackageHandle, disabledAnalyses map[string]struct{}, report func(a *analysis.Analyzer, diag analysis.Diagnostic) error) error {
 	var analyzers []*analysis.Analyzer
-	for _, a := range view.Analyzers() {
+	for _, a := range view.Options().Analyzers {
 		if _, ok := disabledAnalyses[a.Name]; ok {
 			continue
 		}
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index c89967d..e1bc782 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -9,6 +9,30 @@
 	"os"
 	"time"
 
+	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/analysis/passes/asmdecl"
+	"golang.org/x/tools/go/analysis/passes/assign"
+	"golang.org/x/tools/go/analysis/passes/atomic"
+	"golang.org/x/tools/go/analysis/passes/atomicalign"
+	"golang.org/x/tools/go/analysis/passes/bools"
+	"golang.org/x/tools/go/analysis/passes/buildtag"
+	"golang.org/x/tools/go/analysis/passes/cgocall"
+	"golang.org/x/tools/go/analysis/passes/composite"
+	"golang.org/x/tools/go/analysis/passes/copylock"
+	"golang.org/x/tools/go/analysis/passes/httpresponse"
+	"golang.org/x/tools/go/analysis/passes/loopclosure"
+	"golang.org/x/tools/go/analysis/passes/lostcancel"
+	"golang.org/x/tools/go/analysis/passes/nilfunc"
+	"golang.org/x/tools/go/analysis/passes/printf"
+	"golang.org/x/tools/go/analysis/passes/shift"
+	"golang.org/x/tools/go/analysis/passes/sortslice"
+	"golang.org/x/tools/go/analysis/passes/stdmethods"
+	"golang.org/x/tools/go/analysis/passes/structtag"
+	"golang.org/x/tools/go/analysis/passes/tests"
+	"golang.org/x/tools/go/analysis/passes/unmarshal"
+	"golang.org/x/tools/go/analysis/passes/unreachable"
+	"golang.org/x/tools/go/analysis/passes/unsafeptr"
+	"golang.org/x/tools/go/analysis/passes/unusedresult"
 	"golang.org/x/tools/internal/lsp/diff"
 	"golang.org/x/tools/internal/lsp/diff/myers"
 	"golang.org/x/tools/internal/lsp/protocol"
@@ -43,6 +67,7 @@
 			Budget:        100 * time.Millisecond,
 		},
 		ComputeEdits: myers.ComputeEdits,
+		Analyzers:    defaultAnalyzers,
 	}
 )
 
@@ -57,6 +82,7 @@
 	DisabledAnalyses map[string]struct{}
 
 	StaticCheck bool
+	GoDiff      bool
 
 	WatchFileChanges              bool
 	InsertTextFormat              protocol.InsertTextFormat
@@ -76,6 +102,8 @@
 	Completion CompletionOptions
 
 	ComputeEdits diff.ComputeEdits
+
+	Analyzers []*analysis.Analyzer
 }
 
 type CompletionOptions struct {
@@ -246,6 +274,9 @@
 	case "staticcheck":
 		result.setBool(&o.StaticCheck)
 
+	case "go-diff":
+		result.setBool(&o.GoDiff)
+
 	// Deprecated settings.
 	case "wantSuggestedFixes":
 		result.State = OptionDeprecated
@@ -290,3 +321,31 @@
 		*b = v
 	}
 }
+
+var defaultAnalyzers = []*analysis.Analyzer{
+	// The traditional vet suite:
+	asmdecl.Analyzer,
+	assign.Analyzer,
+	atomic.Analyzer,
+	atomicalign.Analyzer,
+	bools.Analyzer,
+	buildtag.Analyzer,
+	cgocall.Analyzer,
+	composite.Analyzer,
+	copylock.Analyzer,
+	httpresponse.Analyzer,
+	loopclosure.Analyzer,
+	lostcancel.Analyzer,
+	nilfunc.Analyzer,
+	printf.Analyzer,
+	shift.Analyzer,
+	stdmethods.Analyzer,
+	structtag.Analyzer,
+	tests.Analyzer,
+	unmarshal.Analyzer,
+	unreachable.Analyzer,
+	unsafeptr.Analyzer,
+	unusedresult.Analyzer,
+	// Non-vet analyzers
+	sortslice.Analyzer,
+}
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 360c019..933dcb4 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -48,7 +48,7 @@
 	data := tests.Load(t, exporter, "../testdata")
 	defer data.Exported.Cleanup()
 
-	cache := cache.New()
+	cache := cache.New(nil)
 	session := cache.NewSession(ctx)
 	options := tests.DefaultOptions()
 	options.Env = data.Config.Env
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index fa85bc0..d23d7cb 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -240,9 +240,6 @@
 	// This function does not correctly invalidate the view when needed.
 	SetOptions(Options)
 
-	// Analyzers returns the set of Analyzers active for this view.
-	Analyzers() []*analysis.Analyzer
-
 	// CheckPackageHandles returns the CheckPackageHandles for the packages
 	// that this file belongs to.
 	CheckPackageHandles(ctx context.Context, f File) (Snapshot, []CheckPackageHandle, error)