gopls/internal/test/integration: style tweaks to CodeAction

Details:
- Pass TriggerKind by value, not pointer.
  Define nil and zero as equivalent, always.
  Name the zero value: UnknownTrigger.
  Omit "kind" suffix in var names for brevity.
- Add Workdir.EntireFile(path string) Location helper.
- Env.CodeAction renamed CodeActionForFile.
  Env.CodeAction0 unsuffixed.
  Editor.CodeAction deleted.
  Editor.CodeAction0 unsuffixed.
- Restore lost commentary.

(Sorry, I was late to the code review of CL 590935.)

Change-Id: I3fbe3e4e7567366b0742dcec44dc50539b9e9621
Reviewed-on: https://go-review.googlesource.com/c/tools/+/591176
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/internal/golang/codeaction.go b/gopls/internal/golang/codeaction.go
index 8e8158c..2208822 100644
--- a/gopls/internal/golang/codeaction.go
+++ b/gopls/internal/golang/codeaction.go
@@ -26,9 +26,14 @@
 	"golang.org/x/tools/internal/imports"
 )
 
-// CodeActions returns all code actions (edits and other commands)
-// available for the selected range.
-func CodeActions(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range, diagnostics []protocol.Diagnostic, want map[protocol.CodeActionKind]bool, triggerKind protocol.CodeActionTriggerKind) (actions []protocol.CodeAction, _ error) {
+// CodeActions returns all wanted code actions (edits and other
+// commands) available for the selected range.
+//
+// Depending on how the request was triggered, fewer actions may be
+// offered, e.g. to avoid UI distractions after mere cursor motion.
+//
+// See ../protocol/codeactionkind.go for some code action theory.
+func CodeActions(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range, diagnostics []protocol.Diagnostic, want map[protocol.CodeActionKind]bool, trigger protocol.CodeActionTriggerKind) (actions []protocol.CodeAction, _ error) {
 	// Only compute quick fixes if there are any diagnostics to fix.
 	wantQuickFixes := want[protocol.QuickFix] && len(diagnostics) > 0
 
@@ -138,7 +143,9 @@
 			actions = append(actions, rewrites...)
 		}
 
-		if want[protocol.RefactorInline] && (triggerKind != protocol.CodeActionAutomatic || rng.Start != rng.End) {
+		// To avoid distraction (e.g. VS Code lightbulb), offer "inline"
+		// only after a selection or explicit menu operation.
+		if want[protocol.RefactorInline] && (trigger != protocol.CodeActionAutomatic || rng.Start != rng.End) {
 			rewrites, err := getInlineCodeActions(pkg, pgf, rng, snapshot.Options())
 			if err != nil {
 				return nil, err
diff --git a/gopls/internal/protocol/codeactionkind.go b/gopls/internal/protocol/codeactionkind.go
index 8acba0e..f3d953f 100644
--- a/gopls/internal/protocol/codeactionkind.go
+++ b/gopls/internal/protocol/codeactionkind.go
@@ -79,6 +79,11 @@
 	GoFreeSymbols CodeActionKind = "source.freesymbols"
 )
 
+// CodeActionUnknownTrigger indicates that the trigger for a
+// CodeAction request is unknown. A missing
+// CodeActionContext.TriggerKind should be treated as equivalent.
+const CodeActionUnknownTrigger CodeActionTriggerKind = 0
+
 // A CodeLensSource identifies an (algorithmic) source of code lenses.
 type CodeLensSource string
 
diff --git a/gopls/internal/server/code_action.go b/gopls/internal/server/code_action.go
index e79ae68..45f230e 100644
--- a/gopls/internal/server/code_action.go
+++ b/gopls/internal/server/code_action.go
@@ -109,7 +109,7 @@
 		return actions, nil
 
 	case file.Go:
-		// diagnostic-associated code actions (problematic code)
+		// diagnostic-bundled code actions
 		//
 		// The diagnostics already have a UI presence (e.g. squiggly underline);
 		// the associated action may additionally show (in VS Code) as a lightbulb.
@@ -120,11 +120,13 @@
 		if err != nil {
 			return nil, err
 		}
-		var triggerKind protocol.CodeActionTriggerKind
-		if k := params.Context.TriggerKind; k != nil {
-			triggerKind = *k
+
+		// computed code actions (may include quickfixes from diagnostics)
+		trigger := protocol.CodeActionUnknownTrigger
+		if k := params.Context.TriggerKind; k != nil { // (some clients omit it)
+			trigger = *k
 		}
-		moreActions, err := golang.CodeActions(ctx, snapshot, fh, params.Range, params.Context.Diagnostics, want, triggerKind)
+		moreActions, err := golang.CodeActions(ctx, snapshot, fh, params.Range, params.Context.Diagnostics, want, trigger)
 		if err != nil {
 			return nil, err
 		}
diff --git a/gopls/internal/test/integration/bench/codeaction_test.go b/gopls/internal/test/integration/bench/codeaction_test.go
index fe89500..679f2d4 100644
--- a/gopls/internal/test/integration/bench/codeaction_test.go
+++ b/gopls/internal/test/integration/bench/codeaction_test.go
@@ -20,7 +20,7 @@
 			defer closeBuffer(b, env, test.file)
 			env.AfterChange()
 
-			env.CodeAction(test.file, nil) // pre-warm
+			env.CodeActionForFile(test.file, nil) // pre-warm
 
 			b.ResetTimer()
 
@@ -29,7 +29,7 @@
 			}
 
 			for i := 0; i < b.N; i++ {
-				env.CodeAction(test.file, nil)
+				env.CodeActionForFile(test.file, nil)
 			}
 		})
 	}
@@ -44,7 +44,7 @@
 			env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"})
 			env.AfterChange()
 
-			env.CodeAction(test.file, nil) // pre-warm
+			env.CodeActionForFile(test.file, nil) // pre-warm
 
 			b.ResetTimer()
 
@@ -62,7 +62,7 @@
 					// Increment the placeholder text, to ensure cache misses.
 					NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits),
 				})
-				env.CodeAction(test.file, nil)
+				env.CodeActionForFile(test.file, nil)
 			}
 		})
 	}
diff --git a/gopls/internal/test/integration/fake/editor.go b/gopls/internal/test/integration/fake/editor.go
index f5d62b0..487a255 100644
--- a/gopls/internal/test/integration/fake/editor.go
+++ b/gopls/internal/test/integration/fake/editor.go
@@ -933,7 +933,7 @@
 
 // OrganizeImports requests and performs the source.organizeImports codeAction.
 func (e *Editor) OrganizeImports(ctx context.Context, path string) error {
-	loc := protocol.Location{URI: e.sandbox.Workdir.URI(path)} // zero Range => whole file
+	loc := e.sandbox.Workdir.EntireFile(path)
 	_, err := e.applyCodeActions(ctx, loc, nil, protocol.SourceOrganizeImports)
 	return err
 }
@@ -1558,11 +1558,9 @@
 
 // CodeAction executes a codeAction request on the server.
 // If loc.Range is zero, the whole file is implied.
-func (e *Editor) CodeAction(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
-	return e.CodeAction0(ctx, loc, diagnostics, nil)
-}
-
-func (e *Editor) CodeAction0(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, triggerKind *protocol.CodeActionTriggerKind) ([]protocol.CodeAction, error) {
+// To reduce distraction, the trigger action (unknown, automatic, invoked)
+// may affect what actions are offered.
+func (e *Editor) CodeAction(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, trigger protocol.CodeActionTriggerKind) ([]protocol.CodeAction, error) {
 	if e.Server == nil {
 		return nil, nil
 	}
@@ -1577,7 +1575,7 @@
 		TextDocument: e.TextDocumentIdentifier(path),
 		Context: protocol.CodeActionContext{
 			Diagnostics: diagnostics,
-			TriggerKind: triggerKind,
+			TriggerKind: &trigger,
 		},
 		Range: loc.Range, // may be zero
 	}
diff --git a/gopls/internal/test/integration/fake/workdir.go b/gopls/internal/test/integration/fake/workdir.go
index 4d21554..977bf54 100644
--- a/gopls/internal/test/integration/fake/workdir.go
+++ b/gopls/internal/test/integration/fake/workdir.go
@@ -149,6 +149,11 @@
 	return w.RelPath(uri.Path())
 }
 
+// EntireFile returns the entire extent of the file named by the workdir-relative path.
+func (w *Workdir) EntireFile(path string) protocol.Location {
+	return protocol.Location{URI: w.URI(path)}
+}
+
 // ReadFile reads a text file specified by a workdir-relative path.
 func (w *Workdir) ReadFile(path string) ([]byte, error) {
 	backoff := 1 * time.Millisecond
diff --git a/gopls/internal/test/integration/misc/codeactions_test.go b/gopls/internal/test/integration/misc/codeactions_test.go
index a6c2a8b..1a169c6 100644
--- a/gopls/internal/test/integration/misc/codeactions_test.go
+++ b/gopls/internal/test/integration/misc/codeactions_test.go
@@ -40,7 +40,7 @@
 		check := func(filename string, wantKind ...protocol.CodeActionKind) {
 			env.OpenFile(filename)
 			loc := env.RegexpSearch(filename, `g\(\)`)
-			actions, err := env.Editor.CodeAction(env.Ctx, loc, nil)
+			actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -87,23 +87,21 @@
 
 	Run(t, "", func(t *testing.T, env *Env) {
 		env.CreateBuffer("main.go", vim1)
-		for _, triggerKind := range []protocol.CodeActionTriggerKind{0, protocol.CodeActionInvoked, protocol.CodeActionAutomatic} {
-			triggerKindPtr := &triggerKind
-			if triggerKind == 0 {
-				triggerKindPtr = nil
-			}
-			t.Run(fmt.Sprintf("trigger=%v", triggerKind), func(t *testing.T) {
+		for _, trigger := range []protocol.CodeActionTriggerKind{
+			protocol.CodeActionUnknownTrigger,
+			protocol.CodeActionInvoked,
+			protocol.CodeActionAutomatic,
+		} {
+			t.Run(fmt.Sprintf("trigger=%v", trigger), func(t *testing.T) {
 				for _, selectedRange := range []bool{false, true} {
 					t.Run(fmt.Sprintf("range=%t", selectedRange), func(t *testing.T) {
-						pattern := env.RegexpSearch("main.go", "Func")
-						rng := pattern.Range
+						loc := env.RegexpSearch("main.go", "Func")
 						if !selectedRange {
 							// assume the cursor is placed at the beginning of `Func`, so end==start.
-							rng.End = rng.Start
+							loc.Range.End = loc.Range.Start
 						}
-						loc := protocol.Location{URI: pattern.URI, Range: rng}
-						actions := env.CodeAction0("main.go", loc, nil, triggerKindPtr)
-						want := triggerKind != protocol.CodeActionAutomatic || selectedRange
+						actions := env.CodeAction(loc, nil, trigger)
+						want := trigger != protocol.CodeActionAutomatic || selectedRange
 						if got := slices.ContainsFunc(actions, func(act protocol.CodeAction) bool {
 							return act.Kind == protocol.RefactorInline
 						}); got != want {
diff --git a/gopls/internal/test/integration/misc/extract_test.go b/gopls/internal/test/integration/misc/extract_test.go
index 86afb45..ec13856 100644
--- a/gopls/internal/test/integration/misc/extract_test.go
+++ b/gopls/internal/test/integration/misc/extract_test.go
@@ -30,7 +30,7 @@
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
 		loc := env.RegexpSearch("main.go", `a := 5\n.*return a`)
-		actions, err := env.Editor.CodeAction(env.Ctx, loc, nil)
+		actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger)
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/gopls/internal/test/integration/misc/fix_test.go b/gopls/internal/test/integration/misc/fix_test.go
index b3d86e1..acf896a 100644
--- a/gopls/internal/test/integration/misc/fix_test.go
+++ b/gopls/internal/test/integration/misc/fix_test.go
@@ -115,7 +115,7 @@
 			ReadDiagnostics("main.go", &d),
 		)
 		var quickFixes []*protocol.CodeAction
-		for _, act := range env.CodeAction("main.go", d.Diagnostics) {
+		for _, act := range env.CodeActionForFile("main.go", d.Diagnostics) {
 			if act.Kind == protocol.QuickFix {
 				act := act // remove in go1.22
 				quickFixes = append(quickFixes, &act)
@@ -152,7 +152,7 @@
 	`
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("external.go")
-		_, err := env.Editor.CodeAction(env.Ctx, env.RegexpSearch("external.go", "z"), nil)
+		_, err := env.Editor.CodeAction(env.Ctx, env.RegexpSearch("external.go", "z"), nil, protocol.CodeActionUnknownTrigger)
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/gopls/internal/test/integration/misc/imports_test.go b/gopls/internal/test/integration/misc/imports_test.go
index 0e85a96..ebc1c0d 100644
--- a/gopls/internal/test/integration/misc/imports_test.go
+++ b/gopls/internal/test/integration/misc/imports_test.go
@@ -149,7 +149,7 @@
 		env.OrganizeImports("main.go")
 
 		// Assert no quick fixes.
-		for _, act := range env.CodeAction("main.go", nil) {
+		for _, act := range env.CodeActionForFile("main.go", nil) {
 			if act.Kind == protocol.QuickFix {
 				t.Errorf("unexpected quick fix action: %#v", act)
 			}
@@ -187,7 +187,7 @@
 		env.OrganizeImports("main.go")
 
 		// Assert no quick fixes.
-		for _, act := range env.CodeAction("main.go", nil) {
+		for _, act := range env.CodeActionForFile("main.go", nil) {
 			if act.Kind == protocol.QuickFix {
 				t.Errorf("unexpected quick-fix action: %#v", act)
 			}
diff --git a/gopls/internal/test/integration/misc/vuln_test.go b/gopls/internal/test/integration/misc/vuln_test.go
index 6ba1ec8..1ed54a7 100644
--- a/gopls/internal/test/integration/misc/vuln_test.go
+++ b/gopls/internal/test/integration/misc/vuln_test.go
@@ -519,7 +519,7 @@
 		for pattern, want := range wantVulncheckDiagnostics {
 			modPathDiagnostics := testVulnDiagnostics(t, env, pattern, want, gotDiagnostics)
 
-			gotActions := env.CodeAction("go.mod", modPathDiagnostics)
+			gotActions := env.CodeActionForFile("go.mod", modPathDiagnostics)
 			if diff := diffCodeActions(gotActions, want.codeActions); diff != "" {
 				t.Errorf("code actions for %q do not match, got %v, want %v\n%v\n", pattern, gotActions, want.codeActions, diff)
 				continue
@@ -716,7 +716,7 @@
 			modPathDiagnostics := testVulnDiagnostics(t, env, mod, want, gotDiagnostics)
 
 			// Check that the actions we get when including all diagnostics at a location return the same result
-			gotActions := env.CodeAction("go.mod", modPathDiagnostics)
+			gotActions := env.CodeActionForFile("go.mod", modPathDiagnostics)
 			if diff := diffCodeActions(gotActions, want.codeActions); diff != "" {
 				t.Errorf("code actions for %q do not match, expected %v, got %v\n%v\n", mod, want.codeActions, gotActions, diff)
 				continue
@@ -839,7 +839,7 @@
 		for mod, want := range wantDiagnostics {
 			modPathDiagnostics := testVulnDiagnostics(t, env, mod, want, gotDiagnostics)
 			// Check that the actions we get when including all diagnostics at a location return the same result
-			gotActions := env.CodeAction("go.mod", modPathDiagnostics)
+			gotActions := env.CodeActionForFile("go.mod", modPathDiagnostics)
 			allActions = append(allActions, gotActions...)
 			if diff := diffCodeActions(gotActions, want.codeActions); diff != "" {
 				t.Errorf("code actions for %q do not match, expected %v, got %v\n%v\n", mod, want.codeActions, gotActions, diff)
@@ -889,7 +889,7 @@
 			t.Errorf("incorrect (severity, source) for %q, want (%s, %s) got (%s, %s)\n", w.msg, w.severity, w.source, diag.Severity, diag.Source)
 		}
 		// Check expected code actions appear.
-		gotActions := env.CodeAction("go.mod", []protocol.Diagnostic{*diag})
+		gotActions := env.CodeActionForFile("go.mod", []protocol.Diagnostic{*diag})
 		if diff := diffCodeActions(gotActions, w.codeActions); diff != "" {
 			t.Errorf("code actions for %q do not match, want %v, got %v\n%v\n", w.msg, w.codeActions, gotActions, diff)
 			continue
diff --git a/gopls/internal/test/integration/misc/webserver_test.go b/gopls/internal/test/integration/misc/webserver_test.go
index c4af449..851cc68 100644
--- a/gopls/internal/test/integration/misc/webserver_test.go
+++ b/gopls/internal/test/integration/misc/webserver_test.go
@@ -198,7 +198,7 @@
 	// Invoke the "View package documentation" code
 	// action to start the server.
 	var docAction *protocol.CodeAction
-	actions := env.CodeAction(filename, nil)
+	actions := env.CodeActionForFile(filename, nil)
 	for _, act := range actions {
 		if act.Title == "View package documentation" {
 			docAction = &act
@@ -254,7 +254,7 @@
 		// Invoke the "Show free symbols" code
 		// action to start the server.
 		loc := env.RegexpSearch("a/a.go", "«((?:.|\n)*)»")
-		actions, err := env.Editor.CodeAction(env.Ctx, loc, nil)
+		actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger)
 		if err != nil {
 			t.Fatalf("CodeAction: %v", err)
 		}
diff --git a/gopls/internal/test/integration/wrappers.go b/gopls/internal/test/integration/wrappers.go
index 0982f8a..8c5e35e 100644
--- a/gopls/internal/test/integration/wrappers.go
+++ b/gopls/internal/test/integration/wrappers.go
@@ -207,7 +207,7 @@
 // ApplyQuickFixes processes the quickfix codeAction, calling t.Fatal on any error.
 func (e *Env) ApplyQuickFixes(path string, diagnostics []protocol.Diagnostic) {
 	e.T.Helper()
-	loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // zero Range => whole file
+	loc := e.Sandbox.Workdir.EntireFile(path)
 	if err := e.Editor.ApplyQuickFixes(e.Ctx, loc, diagnostics); err != nil {
 		e.T.Fatal(err)
 	}
@@ -224,7 +224,7 @@
 // GetQuickFixes returns the available quick fix code actions.
 func (e *Env) GetQuickFixes(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction {
 	e.T.Helper()
-	loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // zero Range => whole file
+	loc := e.Sandbox.Workdir.EntireFile(path)
 	actions, err := e.Editor.GetQuickFixes(e.Ctx, loc, diagnostics)
 	if err != nil {
 		e.T.Fatal(err)
@@ -533,16 +533,17 @@
 	}
 }
 
-// CodeAction calls textDocument/codeAction for the given path, and calls
-// t.Fatal if there are errors.
-func (e *Env) CodeAction(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction {
-	loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // no Range => whole file
-	return e.CodeAction0(path, loc, diagnostics, nil)
+// CodeActionForFile calls textDocument/codeAction for the entire
+// file, and calls t.Fatal if there were errors.
+func (e *Env) CodeActionForFile(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction {
+	return e.CodeAction(e.Sandbox.Workdir.EntireFile(path), diagnostics, protocol.CodeActionUnknownTrigger)
 }
 
-func (e *Env) CodeAction0(path string, loc protocol.Location, diagnostics []protocol.Diagnostic, triggerKind *protocol.CodeActionTriggerKind) []protocol.CodeAction {
+// CodeAction calls textDocument/codeAction for a selection,
+// and calls t.Fatal if there were errors.
+func (e *Env) CodeAction(loc protocol.Location, diagnostics []protocol.Diagnostic, trigger protocol.CodeActionTriggerKind) []protocol.CodeAction {
 	e.T.Helper()
-	actions, err := e.Editor.CodeAction0(e.Ctx, loc, diagnostics, triggerKind)
+	actions, err := e.Editor.CodeAction(e.Ctx, loc, diagnostics, trigger)
 	if err != nil {
 		e.T.Fatal(err)
 	}