internal/lsp: watch directories in replace targets and update on changes

This change adds the notion of a "workspace directory", which is
basically the set of directories that contains workspace packages. These
are mainly used for replace targets right now. It's a little trickier
than expected because the set of workspace directories can technically
change on any go.mod change.

At first, I wanted DidModifyFiles to report whether there was a change,
but I don't think it's actually that expensive to check on each call
and it complicates the code a bit. I can change it back if you think
it's worth doing.

The parse mod handle changes are because I needed an unlocked way of
parsing the mod file, but I imagine they'll conflict with CL 244769
anyway.

The next CL will be to "promote" replace targets to the level of
workspace packages, meaning we will be able to find references in them.

Change-Id: I5dd58fe29415473496ca6634a94a3134923228dc
Reviewed-on: https://go-review.googlesource.com/c/tools/+/245327
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/lsp/regtest/env.go b/internal/lsp/regtest/env.go
index 0232558..6fbd877 100644
--- a/internal/lsp/regtest/env.go
+++ b/internal/lsp/regtest/env.go
@@ -47,6 +47,10 @@
 	logs               []*protocol.LogMessageParams
 	showMessage        []*protocol.ShowMessageParams
 	showMessageRequest []*protocol.ShowMessageRequestParams
+
+	registrations   []*protocol.RegistrationParams
+	unregistrations []*protocol.UnregistrationParams
+
 	// outstandingWork is a map of token->work summary. All tokens are assumed to
 	// be string, though the spec allows for numeric tokens as well.  When work
 	// completes, it is deleted from this map.
@@ -129,6 +133,8 @@
 			OnProgress:               env.onProgress,
 			OnShowMessage:            env.onShowMessage,
 			OnShowMessageRequest:     env.onShowMessageRequest,
+			OnRegistration:           env.onRegistration,
+			OnUnregistration:         env.onUnregistration,
 		}
 	}
 	editor, err := fake.NewEditor(sandbox, editorConfig).Connect(ctx, conn, hooks)
@@ -210,6 +216,24 @@
 	return nil
 }
 
+func (e *Env) onRegistration(_ context.Context, m *protocol.RegistrationParams) error {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	e.state.registrations = append(e.state.registrations, m)
+	e.checkConditionsLocked()
+	return nil
+}
+
+func (e *Env) onUnregistration(_ context.Context, m *protocol.UnregistrationParams) error {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	e.state.unregistrations = append(e.state.unregistrations, m)
+	e.checkConditionsLocked()
+	return nil
+}
+
 func (e *Env) checkConditionsLocked() {
 	for id, condition := range e.waiters {
 		if v, _, _ := checkExpectations(e.state, condition.expectations); v != Unmet {
@@ -496,6 +520,86 @@
 	}
 }
 
+// RegistrationExpectation is an expectation on the capability registrations
+// received by the editor from gopls.
+type RegistrationExpectation struct {
+	check       func([]*protocol.RegistrationParams) (Verdict, interface{})
+	description string
+}
+
+// Check implements the Expectation interface.
+func (e RegistrationExpectation) Check(s State) (Verdict, interface{}) {
+	return e.check(s.registrations)
+}
+
+// Description implements the Expectation interface.
+func (e RegistrationExpectation) Description() string {
+	return e.description
+}
+
+// RegistrationMatching asserts that the client has received a capability
+// registration matching the given regexp.
+func RegistrationMatching(re string) RegistrationExpectation {
+	rec, err := regexp.Compile(re)
+	if err != nil {
+		panic(err)
+	}
+	check := func(params []*protocol.RegistrationParams) (Verdict, interface{}) {
+		for _, p := range params {
+			for _, r := range p.Registrations {
+				if rec.Match([]byte(r.Method)) {
+					return Met, r
+				}
+			}
+		}
+		return Unmet, nil
+	}
+	return RegistrationExpectation{
+		check:       check,
+		description: fmt.Sprintf("registration matching %q", re),
+	}
+}
+
+// UnregistrationExpectation is an expectation on the capability
+// unregistrations received by the editor from gopls.
+type UnregistrationExpectation struct {
+	check       func([]*protocol.UnregistrationParams) (Verdict, interface{})
+	description string
+}
+
+// Check implements the Expectation interface.
+func (e UnregistrationExpectation) Check(s State) (Verdict, interface{}) {
+	return e.check(s.unregistrations)
+}
+
+// Description implements the Expectation interface.
+func (e UnregistrationExpectation) Description() string {
+	return e.description
+}
+
+// UnregistrationMatching asserts that the client has received an
+// unregistration whose ID matches the given regexp.
+func UnregistrationMatching(re string) UnregistrationExpectation {
+	rec, err := regexp.Compile(re)
+	if err != nil {
+		panic(err)
+	}
+	check := func(params []*protocol.UnregistrationParams) (Verdict, interface{}) {
+		for _, p := range params {
+			for _, r := range p.Unregisterations {
+				if rec.Match([]byte(r.Method)) {
+					return Met, r
+				}
+			}
+		}
+		return Unmet, nil
+	}
+	return UnregistrationExpectation{
+		check:       check,
+		description: fmt.Sprintf("unregistration matching %q", re),
+	}
+}
+
 // A DiagnosticExpectation is a condition that must be met by the current set
 // of diagnostics for a file.
 type DiagnosticExpectation struct {