gopls/internal/regtest: make RegexpSearch return a Location

...along with various other Editor methods.

Do we need a more convenient way to say
  env.Sandbox.Workdir.URIToPath(loc.URI) ?

Change-Id: I452028db4b99843e07861909ad8cef87cf9fb118
Reviewed-on: https://go-review.googlesource.com/c/tools/+/463655
Run-TryBot: Alan Donovan <adonovan@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/fake/editor.go b/gopls/internal/lsp/fake/editor.go
index c9214a8..9a1f0dd 100644
--- a/gopls/internal/lsp/fake/editor.go
+++ b/gopls/internal/lsp/fake/editor.go
@@ -39,6 +39,7 @@
 	sandbox    *Sandbox
 	defaultEnv map[string]string
 
+	// TODO(adonovan): buffers should be keyed by protocol.DocumentURI.
 	mu                 sync.Mutex
 	config             EditorConfig                // editor configuration
 	buffers            map[string]buffer           // open buffers (relative path -> buffer content)
@@ -597,17 +598,17 @@
 	ErrUnknownBuffer = errors.New("unknown buffer")
 )
 
-// regexpRange returns the start and end of the first occurrence of either re
+// regexpLocation returns the location of the first occurrence of either re
 // or its singular subgroup. It returns ErrNoMatch if the regexp doesn't match.
-func regexpRange(mapper *protocol.Mapper, re string) (protocol.Range, error) {
+func regexpLocation(mapper *protocol.Mapper, re string) (protocol.Location, error) {
 	var start, end int
 	rec, err := regexp.Compile(re)
 	if err != nil {
-		return protocol.Range{}, err
+		return protocol.Location{}, err
 	}
 	indexes := rec.FindSubmatchIndex(mapper.Content)
 	if indexes == nil {
-		return protocol.Range{}, ErrNoMatch
+		return protocol.Location{}, ErrNoMatch
 	}
 	switch len(indexes) {
 	case 2:
@@ -617,33 +618,26 @@
 		// one subgroup: return its range
 		start, end = indexes[2], indexes[3]
 	default:
-		return protocol.Range{}, fmt.Errorf("invalid search regexp %q: expect either 0 or 1 subgroups, got %d", re, len(indexes)/2-1)
+		return protocol.Location{}, fmt.Errorf("invalid search regexp %q: expect either 0 or 1 subgroups, got %d", re, len(indexes)/2-1)
 	}
-	return mapper.OffsetRange(start, end)
+	return mapper.OffsetLocation(start, end)
 }
 
-// RegexpRange returns the first range in the buffer bufName matching re. See
-// RegexpSearch for more information on matching.
-func (e *Editor) RegexpRange(bufName, re string) (protocol.Range, error) {
-	e.mu.Lock()
-	defer e.mu.Unlock()
-	buf, ok := e.buffers[bufName]
-	if !ok {
-		return protocol.Range{}, ErrUnknownBuffer
-	}
-	return regexpRange(buf.mapper, re)
-}
-
-// RegexpSearch returns the position of the first match for re in the buffer
+// RegexpSearch returns the Location of the first match for re in the buffer
 // bufName. For convenience, RegexpSearch supports the following two modes:
 //  1. If re has no subgroups, return the position of the match for re itself.
 //  2. If re has one subgroup, return the position of the first subgroup.
 //
 // It returns an error re is invalid, has more than one subgroup, or doesn't
 // match the buffer.
-func (e *Editor) RegexpSearch(bufName, re string) (protocol.Position, error) {
-	rng, err := e.RegexpRange(bufName, re)
-	return rng.Start, err
+func (e *Editor) RegexpSearch(bufName, re string) (protocol.Location, error) {
+	e.mu.Lock()
+	buf, ok := e.buffers[bufName]
+	e.mu.Unlock()
+	if !ok {
+		return protocol.Location{}, ErrUnknownBuffer
+	}
+	return regexpLocation(buf.mapper, re)
 }
 
 // RegexpReplace edits the buffer corresponding to path by replacing the first
@@ -658,12 +652,12 @@
 	if !ok {
 		return ErrUnknownBuffer
 	}
-	rng, err := regexpRange(buf.mapper, re)
+	loc, err := regexpLocation(buf.mapper, re)
 	if err != nil {
 		return err
 	}
 	edits := []protocol.TextEdit{{
-		Range:   rng,
+		Range:   loc.Range,
 		NewText: replace,
 	}}
 	patched, err := applyEdits(buf.mapper, edits, e.config.WindowsLineEndings)
@@ -765,53 +759,53 @@
 }
 
 // GoToDefinition jumps to the definition of the symbol at the given position
-// in an open buffer. It returns the path and position of the resulting jump.
-func (e *Editor) GoToDefinition(ctx context.Context, path string, pos protocol.Position) (string, protocol.Position, error) {
-	if err := e.checkBufferPosition(path, pos); err != nil {
-		return "", protocol.Position{}, err
+// in an open buffer. It returns the location of the resulting jump.
+func (e *Editor) GoToDefinition(ctx context.Context, loc protocol.Location) (protocol.Location, error) {
+	if err := e.checkBufferLocation(loc); err != nil {
+		return protocol.Location{}, err
 	}
 	params := &protocol.DefinitionParams{}
-	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
-	params.Position = pos
+	params.TextDocument.URI = loc.URI
+	params.Position = loc.Range.Start
 
 	resp, err := e.Server.Definition(ctx, params)
 	if err != nil {
-		return "", protocol.Position{}, fmt.Errorf("definition: %w", err)
+		return protocol.Location{}, fmt.Errorf("definition: %w", err)
 	}
-	return e.extractFirstPathAndPos(ctx, resp)
+	return e.extractFirstLocation(ctx, resp)
 }
 
-// GoToTypeDefinition jumps to the type definition of the symbol at the given position
+// GoToTypeDefinition jumps to the type definition of the symbol at the given location
 // in an open buffer.
-func (e *Editor) GoToTypeDefinition(ctx context.Context, path string, pos protocol.Position) (string, protocol.Position, error) {
-	if err := e.checkBufferPosition(path, pos); err != nil {
-		return "", protocol.Position{}, err
+func (e *Editor) GoToTypeDefinition(ctx context.Context, loc protocol.Location) (protocol.Location, error) {
+	if err := e.checkBufferLocation(loc); err != nil {
+		return protocol.Location{}, err
 	}
 	params := &protocol.TypeDefinitionParams{}
-	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
-	params.Position = pos
+	params.TextDocument.URI = loc.URI
+	params.Position = loc.Range.Start
 
 	resp, err := e.Server.TypeDefinition(ctx, params)
 	if err != nil {
-		return "", protocol.Position{}, fmt.Errorf("type definition: %w", err)
+		return protocol.Location{}, fmt.Errorf("type definition: %w", err)
 	}
-	return e.extractFirstPathAndPos(ctx, resp)
+	return e.extractFirstLocation(ctx, resp)
 }
 
-// extractFirstPathAndPos returns the path and the position of the first location.
+// extractFirstPathAndPos returns the first location.
 // It opens the file if needed.
-func (e *Editor) extractFirstPathAndPos(ctx context.Context, locs []protocol.Location) (string, protocol.Position, error) {
+func (e *Editor) extractFirstLocation(ctx context.Context, locs []protocol.Location) (protocol.Location, error) {
 	if len(locs) == 0 {
-		return "", protocol.Position{}, nil
+		return protocol.Location{}, nil
 	}
 
 	newPath := e.sandbox.Workdir.URIToPath(locs[0].URI)
 	if !e.HasBuffer(newPath) {
 		if err := e.OpenFile(ctx, newPath); err != nil {
-			return "", protocol.Position{}, fmt.Errorf("OpenFile: %w", err)
+			return protocol.Location{}, fmt.Errorf("OpenFile: %w", err)
 		}
 	}
-	return newPath, locs[0].Range.Start, nil
+	return locs[0], nil
 }
 
 // Symbol performs a workspace symbol search using query
@@ -822,13 +816,14 @@
 
 // OrganizeImports requests and performs the source.organizeImports codeAction.
 func (e *Editor) OrganizeImports(ctx context.Context, path string) error {
-	_, err := e.applyCodeActions(ctx, path, nil, nil, protocol.SourceOrganizeImports)
+	loc := protocol.Location{URI: e.sandbox.Workdir.URI(path)} // zero Range => whole file
+	_, err := e.applyCodeActions(ctx, loc, nil, protocol.SourceOrganizeImports)
 	return err
 }
 
 // RefactorRewrite requests and performs the source.refactorRewrite codeAction.
-func (e *Editor) RefactorRewrite(ctx context.Context, path string, rng *protocol.Range) error {
-	applied, err := e.applyCodeActions(ctx, path, rng, nil, protocol.RefactorRewrite)
+func (e *Editor) RefactorRewrite(ctx context.Context, loc protocol.Location) error {
+	applied, err := e.applyCodeActions(ctx, loc, nil, protocol.RefactorRewrite)
 	if err != nil {
 		return err
 	}
@@ -839,8 +834,8 @@
 }
 
 // ApplyQuickFixes requests and performs the quickfix codeAction.
-func (e *Editor) ApplyQuickFixes(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) error {
-	applied, err := e.applyCodeActions(ctx, path, rng, diagnostics, protocol.SourceFixAll, protocol.QuickFix)
+func (e *Editor) ApplyQuickFixes(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic) error {
+	applied, err := e.applyCodeActions(ctx, loc, diagnostics, protocol.SourceFixAll, protocol.QuickFix)
 	if applied == 0 {
 		return fmt.Errorf("no quick fixes were applied")
 	}
@@ -876,12 +871,12 @@
 }
 
 // GetQuickFixes returns the available quick fix code actions.
-func (e *Editor) GetQuickFixes(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
-	return e.getCodeActions(ctx, path, rng, diagnostics, protocol.QuickFix, protocol.SourceFixAll)
+func (e *Editor) GetQuickFixes(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
+	return e.getCodeActions(ctx, loc, diagnostics, protocol.QuickFix, protocol.SourceFixAll)
 }
 
-func (e *Editor) applyCodeActions(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) (int, error) {
-	actions, err := e.getCodeActions(ctx, path, rng, diagnostics, only...)
+func (e *Editor) applyCodeActions(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) (int, error) {
+	actions, err := e.getCodeActions(ctx, loc, diagnostics, only...)
 	if err != nil {
 		return 0, err
 	}
@@ -908,19 +903,17 @@
 	return applied, nil
 }
 
-func (e *Editor) getCodeActions(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) ([]protocol.CodeAction, error) {
+func (e *Editor) getCodeActions(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) ([]protocol.CodeAction, error) {
 	if e.Server == nil {
 		return nil, nil
 	}
 	params := &protocol.CodeActionParams{}
-	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
+	params.TextDocument.URI = loc.URI
 	params.Context.Only = only
+	params.Range = loc.Range // may be zero => whole file
 	if diagnostics != nil {
 		params.Context.Diagnostics = diagnostics
 	}
-	if rng != nil {
-		params.Range = *rng
-	}
 	return e.Server.CodeAction(ctx, params)
 }
 
@@ -976,15 +969,16 @@
 	return e.editBufferLocked(ctx, path, edits)
 }
 
-func (e *Editor) checkBufferPosition(path string, pos protocol.Position) error {
+func (e *Editor) checkBufferLocation(loc protocol.Location) error {
 	e.mu.Lock()
 	defer e.mu.Unlock()
+	path := e.sandbox.Workdir.URIToPath(loc.URI)
 	buf, ok := e.buffers[path]
 	if !ok {
 		return fmt.Errorf("buffer %q is not open", path)
 	}
 
-	_, err := buf.mapper.PositionOffset(pos)
+	_, _, err := buf.mapper.RangeOffsets(loc.Range)
 	return err
 }
 
@@ -1041,10 +1035,11 @@
 }
 
 // Completion executes a completion request on the server.
-func (e *Editor) Completion(ctx context.Context, path string, pos protocol.Position) (*protocol.CompletionList, error) {
+func (e *Editor) Completion(ctx context.Context, loc protocol.Location) (*protocol.CompletionList, error) {
 	if e.Server == nil {
 		return nil, nil
 	}
+	path := e.sandbox.Workdir.URIToPath(loc.URI)
 	e.mu.Lock()
 	_, ok := e.buffers[path]
 	e.mu.Unlock()
@@ -1054,7 +1049,7 @@
 	params := &protocol.CompletionParams{
 		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
 			TextDocument: e.TextDocumentIdentifier(path),
-			Position:     pos,
+			Position:     loc.Range.Start,
 		},
 	}
 	completions, err := e.Server.Completion(ctx, params)
@@ -1066,12 +1061,13 @@
 
 // AcceptCompletion accepts a completion for the given item at the given
 // position.
-func (e *Editor) AcceptCompletion(ctx context.Context, path string, pos protocol.Position, item protocol.CompletionItem) error {
+func (e *Editor) AcceptCompletion(ctx context.Context, loc protocol.Location, item protocol.CompletionItem) error {
 	if e.Server == nil {
 		return nil
 	}
 	e.mu.Lock()
 	defer e.mu.Unlock()
+	path := e.sandbox.Workdir.URIToPath(loc.URI)
 	_, ok := e.buffers[path]
 	if !ok {
 		return fmt.Errorf("buffer %q is not open", path)
@@ -1112,12 +1108,13 @@
 	return hints, nil
 }
 
-// References returns references to the object at (path, pos), as returned by
+// References returns references to the object at loc, as returned by
 // the connected LSP server. If no server is connected, it returns (nil, nil).
-func (e *Editor) References(ctx context.Context, path string, pos protocol.Position) ([]protocol.Location, error) {
+func (e *Editor) References(ctx context.Context, loc protocol.Location) ([]protocol.Location, error) {
 	if e.Server == nil {
 		return nil, nil
 	}
+	path := e.sandbox.Workdir.URIToPath(loc.URI)
 	e.mu.Lock()
 	_, ok := e.buffers[path]
 	e.mu.Unlock()
@@ -1127,7 +1124,7 @@
 	params := &protocol.ReferenceParams{
 		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
 			TextDocument: e.TextDocumentIdentifier(path),
-			Position:     pos,
+			Position:     loc.Range.Start,
 		},
 		Context: protocol.ReferenceContext{
 			IncludeDeclaration: true,
@@ -1140,24 +1137,25 @@
 	return locations, nil
 }
 
-// Rename performs a rename of the object at (path, pos) to newName, using the
+// Rename performs a rename of the object at loc to newName, using the
 // connected LSP server. If no server is connected, it returns nil.
-func (e *Editor) Rename(ctx context.Context, path string, pos protocol.Position, newName string) error {
+func (e *Editor) Rename(ctx context.Context, loc protocol.Location, newName string) error {
 	if e.Server == nil {
 		return nil
 	}
+	path := e.sandbox.Workdir.URIToPath(loc.URI)
 
 	// Verify that PrepareRename succeeds.
 	prepareParams := &protocol.PrepareRenameParams{}
 	prepareParams.TextDocument = e.TextDocumentIdentifier(path)
-	prepareParams.Position = pos
+	prepareParams.Position = loc.Range.Start
 	if _, err := e.Server.PrepareRename(ctx, prepareParams); err != nil {
 		return fmt.Errorf("preparing rename: %v", err)
 	}
 
 	params := &protocol.RenameParams{
 		TextDocument: e.TextDocumentIdentifier(path),
-		Position:     pos,
+		Position:     loc.Range.Start,
 		NewName:      newName,
 	}
 	wsEdits, err := e.Server.Rename(ctx, params)
@@ -1172,13 +1170,14 @@
 	return nil
 }
 
-// Implementations returns implementations for the object at (path, pos), as
+// Implementations returns implementations for the object at loc, as
 // returned by the connected LSP server. If no server is connected, it returns
 // (nil, nil).
-func (e *Editor) Implementations(ctx context.Context, path string, pos protocol.Position) ([]protocol.Location, error) {
+func (e *Editor) Implementations(ctx context.Context, loc protocol.Location) ([]protocol.Location, error) {
 	if e.Server == nil {
 		return nil, nil
 	}
+	path := e.sandbox.Workdir.URIToPath(loc.URI)
 	e.mu.Lock()
 	_, ok := e.buffers[path]
 	e.mu.Unlock()
@@ -1188,7 +1187,7 @@
 	params := &protocol.ImplementationParams{
 		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
 			TextDocument: e.TextDocumentIdentifier(path),
-			Position:     pos,
+			Position:     loc.Range.Start,
 		},
 	}
 	return e.Server.Implementation(ctx, params)
@@ -1368,10 +1367,12 @@
 }
 
 // CodeAction executes a codeAction request on the server.
-func (e *Editor) CodeAction(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
+// 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) {
 	if e.Server == nil {
 		return nil, nil
 	}
+	path := e.sandbox.Workdir.URIToPath(loc.URI)
 	e.mu.Lock()
 	_, ok := e.buffers[path]
 	e.mu.Unlock()
@@ -1383,9 +1384,7 @@
 		Context: protocol.CodeActionContext{
 			Diagnostics: diagnostics,
 		},
-	}
-	if rng != nil {
-		params.Range = *rng
+		Range: loc.Range, // may be zero
 	}
 	lens, err := e.Server.CodeAction(ctx, params)
 	if err != nil {
@@ -1395,22 +1394,22 @@
 }
 
 // Hover triggers a hover at the given position in an open buffer.
-func (e *Editor) Hover(ctx context.Context, path string, pos protocol.Position) (*protocol.MarkupContent, protocol.Position, error) {
-	if err := e.checkBufferPosition(path, pos); err != nil {
-		return nil, protocol.Position{}, err
+func (e *Editor) Hover(ctx context.Context, loc protocol.Location) (*protocol.MarkupContent, protocol.Location, error) {
+	if err := e.checkBufferLocation(loc); err != nil {
+		return nil, protocol.Location{}, err
 	}
 	params := &protocol.HoverParams{}
-	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
-	params.Position = pos
+	params.TextDocument.URI = loc.URI
+	params.Position = loc.Range.Start
 
 	resp, err := e.Server.Hover(ctx, params)
 	if err != nil {
-		return nil, protocol.Position{}, fmt.Errorf("hover: %w", err)
+		return nil, protocol.Location{}, fmt.Errorf("hover: %w", err)
 	}
 	if resp == nil {
-		return nil, protocol.Position{}, nil
+		return nil, protocol.Location{}, nil
 	}
-	return &resp.Contents, resp.Range.Start, nil
+	return &resp.Contents, protocol.Location{URI: loc.URI, Range: resp.Range}, nil
 }
 
 func (e *Editor) DocumentLink(ctx context.Context, path string) ([]protocol.DocumentLink, error) {
@@ -1422,16 +1421,16 @@
 	return e.Server.DocumentLink(ctx, params)
 }
 
-func (e *Editor) DocumentHighlight(ctx context.Context, path string, pos protocol.Position) ([]protocol.DocumentHighlight, error) {
+func (e *Editor) DocumentHighlight(ctx context.Context, loc protocol.Location) ([]protocol.DocumentHighlight, error) {
 	if e.Server == nil {
 		return nil, nil
 	}
-	if err := e.checkBufferPosition(path, pos); err != nil {
+	if err := e.checkBufferLocation(loc); err != nil {
 		return nil, err
 	}
 	params := &protocol.DocumentHighlightParams{}
-	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
-	params.Position = pos
+	params.TextDocument.URI = loc.URI
+	params.Position = loc.Range.Start
 
 	return e.Server.DocumentHighlight(ctx, params)
 }
diff --git a/gopls/internal/lsp/fake/workdir.go b/gopls/internal/lsp/fake/workdir.go
index 2470e74..97d70b9 100644
--- a/gopls/internal/lsp/fake/workdir.go
+++ b/gopls/internal/lsp/fake/workdir.go
@@ -196,25 +196,15 @@
 	}
 }
 
-func (w *Workdir) RegexpRange(path, re string) (protocol.Range, error) {
-	content, err := w.ReadFile(path)
-	if err != nil {
-		return protocol.Range{}, err
-	}
-	mapper := protocol.NewMapper(w.URI(path).SpanURI(), content)
-	return regexpRange(mapper, re)
-}
-
 // RegexpSearch searches the file corresponding to path for the first position
 // matching re.
-func (w *Workdir) RegexpSearch(path string, re string) (protocol.Position, error) {
+func (w *Workdir) RegexpSearch(path string, re string) (protocol.Location, error) {
 	content, err := w.ReadFile(path)
 	if err != nil {
-		return protocol.Position{}, err
+		return protocol.Location{}, err
 	}
 	mapper := protocol.NewMapper(w.URI(path).SpanURI(), content)
-	rng, err := regexpRange(mapper, re)
-	return rng.Start, err
+	return regexpLocation(mapper, re)
 }
 
 // RemoveFile removes a workdir-relative file path and notifies watchers of the
diff --git a/gopls/internal/lsp/regtest/expectation.go b/gopls/internal/lsp/regtest/expectation.go
index 6d3f334..cba4c60 100644
--- a/gopls/internal/lsp/regtest/expectation.go
+++ b/gopls/internal/lsp/regtest/expectation.go
@@ -712,11 +712,11 @@
 // TODO(rfindley): pass in the editor to expectations, so that they may depend
 // on editor state and AtRegexp can be a function rather than a method.
 func (e *Env) AtRegexp(name, pattern string) DiagnosticFilter {
-	pos := e.RegexpSearch(name, pattern)
+	loc := e.RegexpSearch(name, pattern)
 	return DiagnosticFilter{
 		desc: fmt.Sprintf("at the first position matching %#q in %q", pattern, name),
 		check: func(diagName string, d protocol.Diagnostic) bool {
-			return diagName == name && d.Range.Start == pos
+			return diagName == name && d.Range.Start == loc.Range.Start
 		},
 	}
 }
diff --git a/gopls/internal/lsp/regtest/wrappers.go b/gopls/internal/lsp/regtest/wrappers.go
index c206296..1207a14 100644
--- a/gopls/internal/lsp/regtest/wrappers.go
+++ b/gopls/internal/lsp/regtest/wrappers.go
@@ -113,37 +113,19 @@
 	}
 }
 
-// RegexpRange returns the range of the first match for re in the buffer
-// specified by name, calling t.Fatal on any error. It first searches for the
-// position in open buffers, then in workspace files.
-func (e *Env) RegexpRange(name, re string) protocol.Range {
-	e.T.Helper()
-	rng, err := e.Editor.RegexpRange(name, re)
-	if err == fake.ErrUnknownBuffer {
-		rng, err = e.Sandbox.Workdir.RegexpRange(name, re)
-	}
-	if err != nil {
-		e.T.Fatalf("RegexpRange: %v, %v", name, err)
-	}
-	return rng
-}
-
 // RegexpSearch returns the starting position of the first match for re in the
 // buffer specified by name, calling t.Fatal on any error. It first searches
 // for the position in open buffers, then in workspace files.
-//
-// TODO(rfindley): RegexpSearch should return a protocol.Location (but that is
-// a large change).
-func (e *Env) RegexpSearch(name, re string) protocol.Position {
+func (e *Env) RegexpSearch(name, re string) protocol.Location {
 	e.T.Helper()
-	pos, err := e.Editor.RegexpSearch(name, re)
+	loc, err := e.Editor.RegexpSearch(name, re)
 	if err == fake.ErrUnknownBuffer {
-		pos, err = e.Sandbox.Workdir.RegexpSearch(name, re)
+		loc, err = e.Sandbox.Workdir.RegexpSearch(name, re)
 	}
 	if err != nil {
 		e.T.Fatalf("RegexpSearch: %v, %v for %q", name, err, re)
 	}
-	return pos
+	return loc
 }
 
 // RegexpReplace replaces the first group in the first match of regexpStr with
@@ -172,13 +154,13 @@
 
 // GoToDefinition goes to definition in the editor, calling t.Fatal on any
 // error. It returns the path and position of the resulting jump.
-func (e *Env) GoToDefinition(name string, pos protocol.Position) (string, protocol.Position) {
+func (e *Env) GoToDefinition(loc protocol.Location) protocol.Location {
 	e.T.Helper()
-	n, p, err := e.Editor.GoToDefinition(e.Ctx, name, pos)
+	loc, err := e.Editor.GoToDefinition(e.Ctx, loc)
 	if err != nil {
 		e.T.Fatal(err)
 	}
-	return n, p
+	return loc
 }
 
 // FormatBuffer formats the editor buffer, calling t.Fatal on any error.
@@ -201,7 +183,8 @@
 // ApplyQuickFixes processes the quickfix codeAction, calling t.Fatal on any error.
 func (e *Env) ApplyQuickFixes(path string, diagnostics []protocol.Diagnostic) {
 	e.T.Helper()
-	if err := e.Editor.ApplyQuickFixes(e.Ctx, path, nil, diagnostics); err != nil {
+	loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // zero Range => whole file
+	if err := e.Editor.ApplyQuickFixes(e.Ctx, loc, diagnostics); err != nil {
 		e.T.Fatal(err)
 	}
 }
@@ -217,7 +200,8 @@
 // GetQuickFixes returns the available quick fix code actions.
 func (e *Env) GetQuickFixes(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction {
 	e.T.Helper()
-	actions, err := e.Editor.GetQuickFixes(e.Ctx, path, nil, diagnostics)
+	loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // zero Range => whole file
+	actions, err := e.Editor.GetQuickFixes(e.Ctx, loc, diagnostics)
 	if err != nil {
 		e.T.Fatal(err)
 	}
@@ -225,13 +209,13 @@
 }
 
 // Hover in the editor, calling t.Fatal on any error.
-func (e *Env) Hover(name string, pos protocol.Position) (*protocol.MarkupContent, protocol.Position) {
+func (e *Env) Hover(loc protocol.Location) (*protocol.MarkupContent, protocol.Location) {
 	e.T.Helper()
-	c, p, err := e.Editor.Hover(e.Ctx, name, pos)
+	c, loc, err := e.Editor.Hover(e.Ctx, loc)
 	if err != nil {
 		e.T.Fatal(err)
 	}
-	return c, p
+	return c, loc
 }
 
 func (e *Env) DocumentLink(name string) []protocol.DocumentLink {
@@ -243,9 +227,9 @@
 	return links
 }
 
-func (e *Env) DocumentHighlight(name string, pos protocol.Position) []protocol.DocumentHighlight {
+func (e *Env) DocumentHighlight(loc protocol.Location) []protocol.DocumentHighlight {
 	e.T.Helper()
-	highlights, err := e.Editor.DocumentHighlight(e.Ctx, name, pos)
+	highlights, err := e.Editor.DocumentHighlight(e.Ctx, loc)
 	if err != nil {
 		e.T.Fatal(err)
 	}
@@ -398,9 +382,9 @@
 }
 
 // References wraps Editor.References, calling t.Fatal on any error.
-func (e *Env) References(path string, pos protocol.Position) []protocol.Location {
+func (e *Env) References(loc protocol.Location) []protocol.Location {
 	e.T.Helper()
-	locations, err := e.Editor.References(e.Ctx, path, pos)
+	locations, err := e.Editor.References(e.Ctx, loc)
 	if err != nil {
 		e.T.Fatal(err)
 	}
@@ -408,17 +392,17 @@
 }
 
 // Rename wraps Editor.Rename, calling t.Fatal on any error.
-func (e *Env) Rename(path string, pos protocol.Position, newName string) {
+func (e *Env) Rename(loc protocol.Location, newName string) {
 	e.T.Helper()
-	if err := e.Editor.Rename(e.Ctx, path, pos, newName); err != nil {
+	if err := e.Editor.Rename(e.Ctx, loc, newName); err != nil {
 		e.T.Fatal(err)
 	}
 }
 
 // Implementations wraps Editor.Implementations, calling t.Fatal on any error.
-func (e *Env) Implementations(path string, pos protocol.Position) []protocol.Location {
+func (e *Env) Implementations(loc protocol.Location) []protocol.Location {
 	e.T.Helper()
-	locations, err := e.Editor.Implementations(e.Ctx, path, pos)
+	locations, err := e.Editor.Implementations(e.Ctx, loc)
 	if err != nil {
 		e.T.Fatal(err)
 	}
@@ -434,9 +418,9 @@
 }
 
 // Completion executes a completion request on the server.
-func (e *Env) Completion(path string, pos protocol.Position) *protocol.CompletionList {
+func (e *Env) Completion(loc protocol.Location) *protocol.CompletionList {
 	e.T.Helper()
-	completions, err := e.Editor.Completion(e.Ctx, path, pos)
+	completions, err := e.Editor.Completion(e.Ctx, loc)
 	if err != nil {
 		e.T.Fatal(err)
 	}
@@ -445,9 +429,9 @@
 
 // AcceptCompletion accepts a completion for the given item at the given
 // position.
-func (e *Env) AcceptCompletion(path string, pos protocol.Position, item protocol.CompletionItem) {
+func (e *Env) AcceptCompletion(loc protocol.Location, item protocol.CompletionItem) {
 	e.T.Helper()
-	if err := e.Editor.AcceptCompletion(e.Ctx, path, pos, item); err != nil {
+	if err := e.Editor.AcceptCompletion(e.Ctx, loc, item); err != nil {
 		e.T.Fatal(err)
 	}
 }
@@ -456,7 +440,8 @@
 // t.Fatal if there are errors.
 func (e *Env) CodeAction(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction {
 	e.T.Helper()
-	actions, err := e.Editor.CodeAction(e.Ctx, path, nil, diagnostics)
+	loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // no Range => whole file
+	actions, err := e.Editor.CodeAction(e.Ctx, loc, diagnostics)
 	if err != nil {
 		e.T.Fatal(err)
 	}
diff --git a/gopls/internal/regtest/bench/completion_test.go b/gopls/internal/regtest/bench/completion_test.go
index 1a464ca..ffccf34 100644
--- a/gopls/internal/regtest/bench/completion_test.go
+++ b/gopls/internal/regtest/bench/completion_test.go
@@ -57,8 +57,8 @@
 	}
 
 	// Run a completion to make sure the system is warm.
-	pos := env.RegexpSearch(options.file, options.locationRegexp)
-	completions := env.Completion(options.file, pos)
+	loc := env.RegexpSearch(options.file, options.locationRegexp)
+	completions := env.Completion(loc)
 
 	if testing.Verbose() {
 		fmt.Println("Results:")
@@ -77,7 +77,7 @@
 			if options.beforeCompletion != nil {
 				options.beforeCompletion(env)
 			}
-			env.Completion(options.file, pos)
+			env.Completion(loc)
 		}
 	})
 }
diff --git a/gopls/internal/regtest/bench/editor_features_test.go b/gopls/internal/regtest/bench/editor_features_test.go
index bba6baa..ea6727b 100644
--- a/gopls/internal/regtest/bench/editor_features_test.go
+++ b/gopls/internal/regtest/bench/editor_features_test.go
@@ -13,14 +13,14 @@
 	env := benchmarkEnv(b)
 
 	env.OpenFile("internal/imports/mod.go")
-	pos := env.RegexpSearch("internal/imports/mod.go", "ModuleJSON")
-	env.GoToDefinition("internal/imports/mod.go", pos)
+	loc := env.RegexpSearch("internal/imports/mod.go", "ModuleJSON")
+	env.GoToDefinition(loc)
 	env.Await(env.DoneWithOpen())
 
 	b.ResetTimer()
 
 	for i := 0; i < b.N; i++ {
-		env.GoToDefinition("internal/imports/mod.go", pos)
+		env.GoToDefinition(loc)
 	}
 }
 
@@ -28,14 +28,14 @@
 	env := benchmarkEnv(b)
 
 	env.OpenFile("internal/imports/mod.go")
-	pos := env.RegexpSearch("internal/imports/mod.go", "gopathwalk")
-	env.References("internal/imports/mod.go", pos)
+	loc := env.RegexpSearch("internal/imports/mod.go", "gopathwalk")
+	env.References(loc)
 	env.Await(env.DoneWithOpen())
 
 	b.ResetTimer()
 
 	for i := 0; i < b.N; i++ {
-		env.References("internal/imports/mod.go", pos)
+		env.References(loc)
 	}
 }
 
@@ -48,9 +48,9 @@
 	b.ResetTimer()
 
 	for i := 1; i < b.N; i++ {
-		pos := env.RegexpSearch("internal/imports/mod.go", "gopathwalk")
+		loc := env.RegexpSearch("internal/imports/mod.go", "gopathwalk")
 		newName := fmt.Sprintf("%s%d", "gopathwalk", i)
-		env.Rename("internal/imports/mod.go", pos, newName)
+		env.Rename(loc, newName)
 	}
 }
 
@@ -58,13 +58,13 @@
 	env := benchmarkEnv(b)
 
 	env.OpenFile("internal/imports/mod.go")
-	pos := env.RegexpSearch("internal/imports/mod.go", "initAllMods")
+	loc := env.RegexpSearch("internal/imports/mod.go", "initAllMods")
 	env.Await(env.DoneWithOpen())
 
 	b.ResetTimer()
 
 	for i := 0; i < b.N; i++ {
-		env.Implementations("internal/imports/mod.go", pos)
+		env.Implementations(loc)
 	}
 }
 
@@ -72,12 +72,12 @@
 	env := benchmarkEnv(b)
 
 	env.OpenFile("internal/imports/mod.go")
-	pos := env.RegexpSearch("internal/imports/mod.go", "bytes")
+	loc := env.RegexpSearch("internal/imports/mod.go", "bytes")
 	env.Await(env.DoneWithOpen())
 
 	b.ResetTimer()
 
 	for i := 0; i < b.N; i++ {
-		env.Hover("internal/imports/mod.go", pos)
+		env.Hover(loc)
 	}
 }
diff --git a/gopls/internal/regtest/completion/completion18_test.go b/gopls/internal/regtest/completion/completion18_test.go
index b9edf06..18e81bc 100644
--- a/gopls/internal/regtest/completion/completion18_test.go
+++ b/gopls/internal/regtest/completion/completion18_test.go
@@ -42,9 +42,9 @@
 		env.OpenFile("main.go")
 		env.Await(env.DoneWithOpen())
 		for _, tst := range tests {
-			pos := env.RegexpSearch("main.go", tst.pat)
-			pos.Character += uint32(protocol.UTF16Len([]byte(tst.pat)))
-			completions := env.Completion("main.go", pos)
+			loc := env.RegexpSearch("main.go", tst.pat)
+			loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte(tst.pat)))
+			completions := env.Completion(loc)
 			result := compareCompletionLabels(tst.want, completions.Items)
 			if result != "" {
 				t.Errorf("%s: wanted %v", result, tst.want)
@@ -109,9 +109,9 @@
 		for _, test := range tests {
 			env.OpenFile(test.file)
 			env.Await(env.DoneWithOpen())
-			pos := env.RegexpSearch(test.file, test.pat)
-			pos.Character += test.offset // character user just typed? will type?
-			completions := env.Completion(test.file, pos)
+			loc := env.RegexpSearch(test.file, test.pat)
+			loc.Range.Start.Character += test.offset // character user just typed? will type?
+			completions := env.Completion(loc)
 			result := compareCompletionLabels(test.want, completions.Items)
 			if result != "" {
 				t.Errorf("pat %q %q", test.pat, result)
diff --git a/gopls/internal/regtest/completion/completion_test.go b/gopls/internal/regtest/completion/completion_test.go
index 6d0bfc5..7d9214f 100644
--- a/gopls/internal/regtest/completion/completion_test.go
+++ b/gopls/internal/regtest/completion/completion_test.go
@@ -176,7 +176,7 @@
 					env.Await(env.DoneWithChangeWatchedFiles())
 				}
 				env.OpenFile(tc.filename)
-				completions := env.Completion(tc.filename, env.RegexpSearch(tc.filename, tc.triggerRegexp))
+				completions := env.Completion(env.RegexpSearch(tc.filename, tc.triggerRegexp))
 
 				// Check that the completion item suggestions are in the range
 				// of the file. {Start,End}.Line are zero-based.
@@ -191,12 +191,12 @@
 				}
 
 				if tc.want != nil {
-					expectedRng := env.RegexpRange(tc.filename, tc.editRegexp)
+					expectedLoc := env.RegexpSearch(tc.filename, tc.editRegexp)
 					for _, item := range completions.Items {
 						gotRng := item.TextEdit.Range
-						if expectedRng != gotRng {
+						if expectedLoc.Range != gotRng {
 							t.Errorf("unexpected completion range for completion item %s: got %v, want %v",
-								item.Label, gotRng, expectedRng)
+								item.Label, gotRng, expectedLoc.Range)
 						}
 					}
 				}
@@ -223,7 +223,7 @@
 	want := []string{"ma", "ma_test", "main", "math", "math_test"}
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("math/add.go")
-		completions := env.Completion("math/add.go", env.RegexpSearch("math/add.go", "package ma()"))
+		completions := env.Completion(env.RegexpSearch("math/add.go", "package ma()"))
 
 		diff := compareCompletionLabels(want, completions.Items)
 		if diff != "" {
@@ -293,19 +293,19 @@
 		// Trigger unimported completions for the example.com/blah package.
 		env.OpenFile("main.go")
 		env.Await(env.DoneWithOpen())
-		pos := env.RegexpSearch("main.go", "ah")
-		completions := env.Completion("main.go", pos)
+		loc := env.RegexpSearch("main.go", "ah")
+		completions := env.Completion(loc)
 		if len(completions.Items) == 0 {
 			t.Fatalf("no completion items")
 		}
-		env.AcceptCompletion("main.go", pos, completions.Items[0])
+		env.AcceptCompletion(loc, completions.Items[0])
 		env.Await(env.DoneWithChange())
 
 		// Trigger completions once again for the blah.<> selector.
 		env.RegexpReplace("main.go", "_ = blah", "_ = blah.")
 		env.Await(env.DoneWithChange())
-		pos = env.RegexpSearch("main.go", "\n}")
-		completions = env.Completion("main.go", pos)
+		loc = env.RegexpSearch("main.go", "\n}")
+		completions = env.Completion(loc)
 		if len(completions.Items) != 1 {
 			t.Fatalf("expected 1 completion item, got %v", len(completions.Items))
 		}
@@ -313,7 +313,7 @@
 		if item.Label != "Name" {
 			t.Fatalf("expected completion item blah.Name, got %v", item.Label)
 		}
-		env.AcceptCompletion("main.go", pos, item)
+		env.AcceptCompletion(loc, item)
 
 		// Await the diagnostics to add example.com/blah to the go.mod file.
 		env.AfterChange(
@@ -381,7 +381,7 @@
 
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("foo.go")
-		completions := env.Completion("foo.go", env.RegexpSearch("foo.go", `if s\.()`))
+		completions := env.Completion(env.RegexpSearch("foo.go", `if s\.()`))
 		diff := compareCompletionLabels([]string{"i"}, completions.Items)
 		if diff != "" {
 			t.Fatal(diff)
@@ -441,7 +441,7 @@
 			{`var _ e = xxxx()`, []string{"xxxxc", "xxxxd", "xxxxe"}},
 		}
 		for _, tt := range tests {
-			completions := env.Completion("main.go", env.RegexpSearch("main.go", tt.re))
+			completions := env.Completion(env.RegexpSearch("main.go", tt.re))
 			diff := compareCompletionLabels(tt.want, completions.Items)
 			if diff != "" {
 				t.Errorf("%s: %s", tt.re, diff)
@@ -474,9 +474,9 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("prog.go")
-		pos := env.RegexpSearch("prog.go", "if fooF")
-		pos.Character += uint32(protocol.UTF16Len([]byte("if fooF")))
-		completions := env.Completion("prog.go", pos)
+		loc := env.RegexpSearch("prog.go", "if fooF")
+		loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte("if fooF")))
+		completions := env.Completion(loc)
 		diff := compareCompletionLabels([]string{"fooFunc"}, completions.Items)
 		if diff != "" {
 			t.Error(diff)
@@ -484,9 +484,9 @@
 		if completions.Items[0].Tags == nil {
 			t.Errorf("expected Tags to show deprecation %#v", diff[0])
 		}
-		pos = env.RegexpSearch("prog.go", "= badP")
-		pos.Character += uint32(protocol.UTF16Len([]byte("= badP")))
-		completions = env.Completion("prog.go", pos)
+		loc = env.RegexpSearch("prog.go", "= badP")
+		loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte("= badP")))
+		completions = env.Completion(loc)
 		diff = compareCompletionLabels([]string{"badPi"}, completions.Items)
 		if diff != "" {
 			t.Error(diff)
@@ -520,12 +520,12 @@
 		// Trigger unimported completions for the example.com/blah package.
 		env.OpenFile("main.go")
 		env.Await(env.DoneWithOpen())
-		pos := env.RegexpSearch("main.go", "Sqr()")
-		completions := env.Completion("main.go", pos)
+		loc := env.RegexpSearch("main.go", "Sqr()")
+		completions := env.Completion(loc)
 		if len(completions.Items) == 0 {
 			t.Fatalf("no completion items")
 		}
-		env.AcceptCompletion("main.go", pos, completions.Items[0])
+		env.AcceptCompletion(loc, completions.Items[0])
 		env.Await(env.DoneWithChange())
 		got := env.BufferText("main.go")
 		want := "package main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"math\"\r\n)\r\n\r\nfunc main() {\r\n\tfmt.Println(\"a\")\r\n\tmath.Sqrt(${1:})\r\n}\r\n"
@@ -574,9 +574,9 @@
 		env.Await(env.DoneWithOpen())
 		for _, tst := range tests {
 			env.SetBufferContent(fname, "package foo\n"+tst.line)
-			pos := env.RegexpSearch(fname, tst.pat)
-			pos.Character += uint32(protocol.UTF16Len([]byte(tst.pat)))
-			completions := env.Completion(fname, pos)
+			loc := env.RegexpSearch(fname, tst.pat)
+			loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte(tst.pat)))
+			completions := env.Completion(loc)
 			result := compareCompletionLabels(tst.want, completions.Items)
 			if result != "" {
 				t.Errorf("\npat:%q line:%q failed: %s:%q", tst.pat, tst.line, result, tst.want)
@@ -656,14 +656,14 @@
 			tst.after = strings.Trim(tst.after, "\n")
 			env.SetBufferContent("foo_test.go", tst.before)
 
-			pos := env.RegexpSearch("foo_test.go", tst.name)
-			pos.Character = uint32(protocol.UTF16Len([]byte(tst.name)))
-			completions := env.Completion("foo_test.go", pos)
+			loc := env.RegexpSearch("foo_test.go", tst.name)
+			loc.Range.Start.Character = uint32(protocol.UTF16Len([]byte(tst.name)))
+			completions := env.Completion(loc)
 			if len(completions.Items) == 0 {
 				t.Fatalf("no completion items")
 			}
 
-			env.AcceptCompletion("foo_test.go", pos, completions.Items[0])
+			env.AcceptCompletion(loc, completions.Items[0])
 			env.Await(env.DoneWithChange())
 			if buf := env.BufferText("foo_test.go"); buf != tst.after {
 				t.Errorf("%s:incorrect completion: got %q, want %q", tst.name, buf, tst.after)
@@ -708,7 +708,7 @@
 			{`use ./dir/foobar/()`, []string{}},
 		}
 		for _, tt := range tests {
-			completions := env.Completion("go.work", env.RegexpSearch("go.work", tt.re))
+			completions := env.Completion(env.RegexpSearch("go.work", tt.re))
 			diff := compareCompletionLabels(tt.want, completions.Items)
 			if diff != "" {
 				t.Errorf("%s: %s", tt.re, diff)
diff --git a/gopls/internal/regtest/completion/postfix_snippet_test.go b/gopls/internal/regtest/completion/postfix_snippet_test.go
index 6d2be05..df69703 100644
--- a/gopls/internal/regtest/completion/postfix_snippet_test.go
+++ b/gopls/internal/regtest/completion/postfix_snippet_test.go
@@ -447,13 +447,13 @@
 
 				env.SetBufferContent("foo.go", c.before)
 
-				pos := env.RegexpSearch("foo.go", "\n}")
-				completions := env.Completion("foo.go", pos)
+				loc := env.RegexpSearch("foo.go", "\n}")
+				completions := env.Completion(loc)
 				if len(completions.Items) != 1 {
 					t.Fatalf("expected one completion, got %v", completions.Items)
 				}
 
-				env.AcceptCompletion("foo.go", pos, completions.Items[0])
+				env.AcceptCompletion(loc, completions.Items[0])
 
 				if buf := env.BufferText("foo.go"); buf != c.after {
 					t.Errorf("\nGOT:\n%s\nEXPECTED:\n%s", buf, c.after)
diff --git a/gopls/internal/regtest/diagnostics/builtin_test.go b/gopls/internal/regtest/diagnostics/builtin_test.go
index 193bbe0..935a7f9 100644
--- a/gopls/internal/regtest/diagnostics/builtin_test.go
+++ b/gopls/internal/regtest/diagnostics/builtin_test.go
@@ -26,9 +26,9 @@
 `
 	Run(t, src, func(t *testing.T, env *Env) {
 		env.OpenFile("a.go")
-		name, _ := env.GoToDefinition("a.go", env.RegexpSearch("a.go", "iota"))
-		if !strings.HasSuffix(name, "builtin.go") {
-			t.Fatalf("jumped to %q, want builtin.go", name)
+		loc := env.GoToDefinition(env.RegexpSearch("a.go", "iota"))
+		if !strings.HasSuffix(string(loc.URI), "builtin.go") {
+			t.Fatalf("jumped to %q, want builtin.go", loc.URI)
 		}
 		env.AfterChange(NoDiagnostics(ForFile("builtin.go")))
 	})
diff --git a/gopls/internal/regtest/misc/call_hierarchy_test.go b/gopls/internal/regtest/misc/call_hierarchy_test.go
index b807ee9..f0f5d4a 100644
--- a/gopls/internal/regtest/misc/call_hierarchy_test.go
+++ b/gopls/internal/regtest/misc/call_hierarchy_test.go
@@ -23,11 +23,11 @@
 	// TODO(rfindley): this could probably just be a marker test.
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("p.go")
-		pos := env.RegexpSearch("p.go", "pkg")
+		loc := env.RegexpSearch("p.go", "pkg")
 
 		var params protocol.CallHierarchyPrepareParams
-		params.TextDocument.URI = env.Sandbox.Workdir.URI("p.go")
-		params.Position = pos
+		params.TextDocument.URI = loc.URI
+		params.Position = loc.Range.Start
 
 		// Check that this doesn't panic.
 		env.Editor.Server.PrepareCallHierarchy(env.Ctx, &params)
diff --git a/gopls/internal/regtest/misc/definition_test.go b/gopls/internal/regtest/misc/definition_test.go
index 0186d50..cad69c1 100644
--- a/gopls/internal/regtest/misc/definition_test.go
+++ b/gopls/internal/regtest/misc/definition_test.go
@@ -38,12 +38,13 @@
 func TestGoToInternalDefinition(t *testing.T) {
 	Run(t, internalDefinition, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		name, pos := env.GoToDefinition("main.go", env.RegexpSearch("main.go", "message"))
+		loc := env.GoToDefinition(env.RegexpSearch("main.go", "message"))
+		name := env.Sandbox.Workdir.URIToPath(loc.URI)
 		if want := "const.go"; name != want {
 			t.Errorf("GoToDefinition: got file %q, want %q", name, want)
 		}
-		if want := env.RegexpSearch("const.go", "message"); pos != want {
-			t.Errorf("GoToDefinition: got position %v, want %v", pos, want)
+		if want := env.RegexpSearch("const.go", "message"); loc != want {
+			t.Errorf("GoToDefinition: got location %v, want %v", loc, want)
 		}
 	})
 }
@@ -65,19 +66,21 @@
 func TestGoToStdlibDefinition_Issue37045(t *testing.T) {
 	Run(t, stdlibDefinition, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		name, pos := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `fmt.(Printf)`))
+		loc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt.(Printf)`))
+		name := env.Sandbox.Workdir.URIToPath(loc.URI)
 		if got, want := path.Base(name), "print.go"; got != want {
 			t.Errorf("GoToDefinition: got file %q, want %q", name, want)
 		}
 
 		// Test that we can jump to definition from outside our workspace.
 		// See golang.org/issues/37045.
-		newName, newPos := env.GoToDefinition(name, pos)
+		newLoc := env.GoToDefinition(loc)
+		newName := env.Sandbox.Workdir.URIToPath(newLoc.URI)
 		if newName != name {
 			t.Errorf("GoToDefinition is not idempotent: got %q, want %q", newName, name)
 		}
-		if newPos != pos {
-			t.Errorf("GoToDefinition is not idempotent: got %v, want %v", newPos, pos)
+		if newLoc != loc {
+			t.Errorf("GoToDefinition is not idempotent: got %v, want %v", newLoc, loc)
 		}
 	})
 }
@@ -85,23 +88,24 @@
 func TestUnexportedStdlib_Issue40809(t *testing.T) {
 	Run(t, stdlibDefinition, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		name, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `fmt.(Printf)`))
+		loc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt.(Printf)`))
+		name := env.Sandbox.Workdir.URIToPath(loc.URI)
 
-		pos := env.RegexpSearch(name, `:=\s*(newPrinter)\(\)`)
+		loc = env.RegexpSearch(name, `:=\s*(newPrinter)\(\)`)
 
 		// Check that we can find references on a reference
-		refs := env.References(name, pos)
+		refs := env.References(loc)
 		if len(refs) < 5 {
 			t.Errorf("expected 5+ references to newPrinter, found: %#v", refs)
 		}
 
-		name, pos = env.GoToDefinition(name, pos)
-		content, _ := env.Hover(name, pos)
+		loc = env.GoToDefinition(loc)
+		content, _ := env.Hover(loc)
 		if !strings.Contains(content.Value, "newPrinter") {
 			t.Fatal("definition of newPrinter went to the incorrect place")
 		}
 		// And on the definition too.
-		refs = env.References(name, pos)
+		refs = env.References(loc)
 		if len(refs) < 5 {
 			t.Errorf("expected 5+ references to newPrinter, found: %#v", refs)
 		}
@@ -125,7 +129,7 @@
 }`
 	Run(t, mod, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		content, _ := env.Hover("main.go", env.RegexpSearch("main.go", "Error"))
+		content, _ := env.Hover(env.RegexpSearch("main.go", "Error"))
 		if content == nil {
 			t.Fatalf("nil hover content for Error")
 		}
@@ -163,10 +167,10 @@
 				Settings{"importShortcut": tt.importShortcut},
 			).Run(t, mod, func(t *testing.T, env *Env) {
 				env.OpenFile("main.go")
-				file, pos := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `"fmt"`))
-				if !tt.wantDef && (file != "" || pos != (protocol.Position{})) {
-					t.Fatalf("expected no definition, got one: %s:%v", file, pos)
-				} else if tt.wantDef && file == "" && pos == (protocol.Position{}) {
+				loc := env.GoToDefinition(env.RegexpSearch("main.go", `"fmt"`))
+				if !tt.wantDef && (loc != (protocol.Location{})) {
+					t.Fatalf("expected no definition, got one: %v", loc)
+				} else if tt.wantDef && loc == (protocol.Location{}) {
 					t.Fatalf("expected definition, got none")
 				}
 				links := env.DocumentLink("main.go")
@@ -213,7 +217,7 @@
 			Run(t, mod, func(t *testing.T, env *Env) {
 				env.OpenFile("main.go")
 
-				_, pos, err := env.Editor.GoToTypeDefinition(env.Ctx, "main.go", env.RegexpSearch("main.go", tt.re))
+				loc, err := env.Editor.GoToTypeDefinition(env.Ctx, env.RegexpSearch("main.go", tt.re))
 				if tt.wantError {
 					if err == nil {
 						t.Fatal("expected error, got nil")
@@ -224,9 +228,9 @@
 					t.Fatalf("expected nil error, got %s", err)
 				}
 
-				typePos := env.RegexpSearch("main.go", tt.wantTypeRe)
-				if pos != typePos {
-					t.Errorf("invalid pos: want %+v, got %+v", typePos, pos)
+				typeLoc := env.RegexpSearch("main.go", tt.wantTypeRe)
+				if loc != typeLoc {
+					t.Errorf("invalid pos: want %+v, got %+v", typeLoc, loc)
 				}
 			})
 		})
@@ -269,7 +273,7 @@
 `
 	Run(t, mod, func(t *testing.T, env *Env) {
 		env.OpenFile("client/client_role_test.go")
-		env.GoToDefinition("client/client_role_test.go", env.RegexpSearch("client/client_role_test.go", "RoleSetup"))
+		env.GoToDefinition(env.RegexpSearch("client/client_role_test.go", "RoleSetup"))
 	})
 }
 
@@ -327,10 +331,11 @@
 		}
 
 		env.OpenFile("a.go")
-		refPos := env.RegexpSearch("a.go", "K") // find "b.K" reference
+		refLoc := env.RegexpSearch("a.go", "K") // find "b.K" reference
 
 		// Initially, b.K is defined in the module cache.
-		gotFile, _ := env.GoToDefinition("a.go", refPos)
+		gotLoc := env.GoToDefinition(refLoc)
+		gotFile := env.Sandbox.Workdir.URIToPath(gotLoc.URI)
 		wantCache := filepath.ToSlash(env.Sandbox.GOPATH()) + "/pkg/mod/other.com/b@v1.0.0/b.go"
 		if gotFile != wantCache {
 			t.Errorf("GoToDefinition, before: got file %q, want %q", gotFile, wantCache)
@@ -345,7 +350,7 @@
 		env.Await(env.DoneWithChangeWatchedFiles())
 
 		// Now, b.K is defined in the vendor tree.
-		gotFile, _ = env.GoToDefinition("a.go", refPos)
+		gotLoc = env.GoToDefinition(refLoc)
 		wantVendor := "vendor/other.com/b/b.go"
 		if gotFile != wantVendor {
 			t.Errorf("GoToDefinition, after go mod vendor: got file %q, want %q", gotFile, wantVendor)
@@ -364,7 +369,8 @@
 		env.Await(env.DoneWithChangeWatchedFiles())
 
 		// b.K is once again defined in the module cache.
-		gotFile, _ = env.GoToDefinition("a.go", refPos)
+		gotLoc = env.GoToDefinition(gotLoc)
+		gotFile = env.Sandbox.Workdir.URIToPath(gotLoc.URI)
 		if gotFile != wantCache {
 			t.Errorf("GoToDefinition, after rm -rf vendor: got file %q, want %q", gotFile, wantCache)
 		}
diff --git a/gopls/internal/regtest/misc/extract_test.go b/gopls/internal/regtest/misc/extract_test.go
index f159e5e..23efffb 100644
--- a/gopls/internal/regtest/misc/extract_test.go
+++ b/gopls/internal/regtest/misc/extract_test.go
@@ -28,11 +28,8 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-
-		start := env.RegexpSearch("main.go", "a := 5")
-		end := env.RegexpSearch("main.go", "return a")
-
-		actions, err := env.Editor.CodeAction(env.Ctx, "main.go", &protocol.Range{Start: start, End: end}, nil)
+		loc := env.RegexpSearch("main.go", `a := 5\n.*return a`)
+		actions, err := env.Editor.CodeAction(env.Ctx, loc, nil)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -53,8 +50,7 @@
 		want := `package main
 
 func Foo() int {
-	a := newFunction()
-	return a
+	return newFunction()
 }
 
 func newFunction() int {
diff --git a/gopls/internal/regtest/misc/failures_test.go b/gopls/internal/regtest/misc/failures_test.go
index 9ab456d..42aa372 100644
--- a/gopls/internal/regtest/misc/failures_test.go
+++ b/gopls/internal/regtest/misc/failures_test.go
@@ -34,7 +34,7 @@
 }`
 	Run(t, mod, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		content, _ := env.Hover("main.go", env.RegexpSearch("main.go", "Error"))
+		content, _ := env.Hover(env.RegexpSearch("main.go", "Error"))
 		if content == nil {
 			t.Fatalf("Hover('Error') returned nil")
 		}
diff --git a/gopls/internal/regtest/misc/fix_test.go b/gopls/internal/regtest/misc/fix_test.go
index 42096a8..7a5e530 100644
--- a/gopls/internal/regtest/misc/fix_test.go
+++ b/gopls/internal/regtest/misc/fix_test.go
@@ -34,11 +34,7 @@
 `
 	Run(t, basic, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		pos := env.RegexpSearch("main.go", "Info{}")
-		if err := env.Editor.RefactorRewrite(env.Ctx, "main.go", &protocol.Range{
-			Start: pos,
-			End:   pos,
-		}); err != nil {
+		if err := env.Editor.RefactorRewrite(env.Ctx, env.RegexpSearch("main.go", "Info{}")); err != nil {
 			t.Fatal(err)
 		}
 		want := `package main
diff --git a/gopls/internal/regtest/misc/highlight_test.go b/gopls/internal/regtest/misc/highlight_test.go
index 587e7d6..8835d60 100644
--- a/gopls/internal/regtest/misc/highlight_test.go
+++ b/gopls/internal/regtest/misc/highlight_test.go
@@ -30,9 +30,9 @@
 	Run(t, mod, func(t *testing.T, env *Env) {
 		const file = "main.go"
 		env.OpenFile(file)
-		_, pos := env.GoToDefinition(file, env.RegexpSearch(file, `var (A) string`))
+		loc := env.GoToDefinition(env.RegexpSearch(file, `var (A) string`))
 
-		checkHighlights(env, file, pos, 3)
+		checkHighlights(env, loc, 3)
 	})
 }
 
@@ -53,10 +53,11 @@
 
 	Run(t, mod, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		file, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `fmt\.(Printf)`))
-		pos := env.RegexpSearch(file, `func Printf\((format) string`)
+		defLoc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt\.(Printf)`))
+		file := env.Sandbox.Workdir.URIToPath(defLoc.URI)
+		loc := env.RegexpSearch(file, `func Printf\((format) string`)
 
-		checkHighlights(env, file, pos, 2)
+		checkHighlights(env, loc, 2)
 	})
 }
 
@@ -112,26 +113,28 @@
 	).Run(t, mod, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
 
-		file, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `"example.com/global"`))
-		pos := env.RegexpSearch(file, `const (A)`)
-		checkHighlights(env, file, pos, 4)
+		defLoc := env.GoToDefinition(env.RegexpSearch("main.go", `"example.com/global"`))
+		file := env.Sandbox.Workdir.URIToPath(defLoc.URI)
+		loc := env.RegexpSearch(file, `const (A)`)
+		checkHighlights(env, loc, 4)
 
-		file, _ = env.GoToDefinition("main.go", env.RegexpSearch("main.go", `"example.com/local"`))
-		pos = env.RegexpSearch(file, `const (b)`)
-		checkHighlights(env, file, pos, 5)
+		defLoc = env.GoToDefinition(env.RegexpSearch("main.go", `"example.com/local"`))
+		file = env.Sandbox.Workdir.URIToPath(defLoc.URI)
+		loc = env.RegexpSearch(file, `const (b)`)
+		checkHighlights(env, loc, 5)
 	})
 }
 
-func checkHighlights(env *Env, file string, pos protocol.Position, highlightCount int) {
+func checkHighlights(env *Env, loc protocol.Location, highlightCount int) {
 	t := env.T
 	t.Helper()
 
-	highlights := env.DocumentHighlight(file, pos)
+	highlights := env.DocumentHighlight(loc)
 	if len(highlights) != highlightCount {
 		t.Fatalf("expected %v highlight(s), got %v", highlightCount, len(highlights))
 	}
 
-	references := env.References(file, pos)
+	references := env.References(loc)
 	if len(highlights) != len(references) {
 		t.Fatalf("number of highlights and references is expected to be equal: %v != %v", len(highlights), len(references))
 	}
diff --git a/gopls/internal/regtest/misc/hover_test.go b/gopls/internal/regtest/misc/hover_test.go
index 013c4d1..6b1d243 100644
--- a/gopls/internal/regtest/misc/hover_test.go
+++ b/gopls/internal/regtest/misc/hover_test.go
@@ -59,21 +59,22 @@
 		ProxyFiles(proxy),
 	).Run(t, mod, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		mixedPos := env.RegexpSearch("main.go", "Mixed")
-		got, _ := env.Hover("main.go", mixedPos)
+		mixedLoc := env.RegexpSearch("main.go", "Mixed")
+		got, _ := env.Hover(mixedLoc)
 		if !strings.Contains(got.Value, "unexported") {
 			t.Errorf("Workspace hover: missing expected field 'unexported'. Got:\n%q", got.Value)
 		}
 
-		cacheFile, _ := env.GoToDefinition("main.go", mixedPos)
-		argPos := env.RegexpSearch(cacheFile, "printMixed.*(Mixed)")
-		got, _ = env.Hover(cacheFile, argPos)
+		cacheLoc := env.GoToDefinition(mixedLoc)
+		cacheFile := env.Sandbox.Workdir.URIToPath(cacheLoc.URI)
+		argLoc := env.RegexpSearch(cacheFile, "printMixed.*(Mixed)")
+		got, _ = env.Hover(argLoc)
 		if !strings.Contains(got.Value, "unexported") {
 			t.Errorf("Non-workspace hover: missing expected field 'unexported'. Got:\n%q", got.Value)
 		}
 
-		exportedFieldPos := env.RegexpSearch("main.go", "Exported")
-		got, _ = env.Hover("main.go", exportedFieldPos)
+		exportedFieldLoc := env.RegexpSearch("main.go", "Exported")
+		got, _ = env.Hover(exportedFieldLoc)
 		if !strings.Contains(got.Value, "comment") {
 			t.Errorf("Workspace hover: missing comment for field 'Exported'. Got:\n%q", got.Value)
 		}
@@ -97,13 +98,13 @@
 	Run(t, source, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
 		hexExpected := "58190"
-		got, _ := env.Hover("main.go", env.RegexpSearch("main.go", "hex"))
+		got, _ := env.Hover(env.RegexpSearch("main.go", "hex"))
 		if got != nil && !strings.Contains(got.Value, hexExpected) {
 			t.Errorf("Hover: missing expected field '%s'. Got:\n%q", hexExpected, got.Value)
 		}
 
 		binExpected := "73"
-		got, _ = env.Hover("main.go", env.RegexpSearch("main.go", "bigBin"))
+		got, _ = env.Hover(env.RegexpSearch("main.go", "bigBin"))
 		if got != nil && !strings.Contains(got.Value, binExpected) {
 			t.Errorf("Hover: missing expected field '%s'. Got:\n%q", binExpected, got.Value)
 		}
@@ -119,7 +120,7 @@
 type Example struct`
 	Run(t, source, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		env.Editor.Hover(env.Ctx, "main.go", env.RegexpSearch("main.go", "Example"))
+		env.Editor.Hover(env.Ctx, env.RegexpSearch("main.go", "Example"))
 	})
 }
 
@@ -135,7 +136,7 @@
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
 		env.EditBuffer("main.go", fake.NewEdit(0, 0, 1, 0, "package main\nfunc main() {\nconst x = `\nfoo\n`\n}"))
-		env.Editor.Hover(env.Ctx, "main.go", env.RegexpSearch("main.go", "foo"))
+		env.Editor.Hover(env.Ctx, env.RegexpSearch("main.go", "foo"))
 	})
 }
 
@@ -203,7 +204,7 @@
 	Run(t, source, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
 		for _, test := range tests {
-			got, _ := env.Hover("main.go", env.RegexpSearch("main.go", test.hoverPackage))
+			got, _ := env.Hover(env.RegexpSearch("main.go", test.hoverPackage))
 			if got == nil {
 				t.Error("nil hover for", test.hoverPackage)
 				continue
@@ -213,7 +214,7 @@
 			}
 		}
 
-		got, _ := env.Hover("main.go", env.RegexpSearch("main.go", "mod.com/lib4"))
+		got, _ := env.Hover(env.RegexpSearch("main.go", "mod.com/lib4"))
 		if got != nil {
 			t.Errorf("Hover: got:\n%q\nwant:\n%v", got.Value, nil)
 		}
@@ -250,7 +251,7 @@
 			env.OpenFile("a.go")
 			z := env.RegexpSearch("a.go", "lib")
 			t.Logf("%#v", z)
-			got, _ := env.Hover("a.go", env.RegexpSearch("a.go", "lib"))
+			got, _ := env.Hover(env.RegexpSearch("a.go", "lib"))
 			if strings.Contains(got.Value, "{#hdr-") {
 				t.Errorf("Hover: got {#hdr- tag:\n%q", got)
 			}
@@ -266,6 +267,6 @@
 `
 	Run(t, source, func(t *testing.T, env *Env) {
 		env.OpenFile("go.mod")
-		env.Hover("go.mod", env.RegexpSearch("go.mod", "go")) // no panic
+		env.Hover(env.RegexpSearch("go.mod", "go")) // no panic
 	})
 }
diff --git a/gopls/internal/regtest/misc/imports_test.go b/gopls/internal/regtest/misc/imports_test.go
index 513171a..bea9552 100644
--- a/gopls/internal/regtest/misc/imports_test.go
+++ b/gopls/internal/regtest/misc/imports_test.go
@@ -159,7 +159,8 @@
 		env.AfterChange(Diagnostics(env.AtRegexp("main.go", `y.Y`)))
 		env.SaveBuffer("main.go")
 		env.AfterChange(NoDiagnostics(ForFile("main.go")))
-		path, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `y.(Y)`))
+		loc := env.GoToDefinition(env.RegexpSearch("main.go", `y.(Y)`))
+		path := env.Sandbox.Workdir.URIToPath(loc.URI)
 		if !strings.HasPrefix(path, filepath.ToSlash(modcache)) {
 			t.Errorf("found module dependency outside of GOMODCACHE: got %v, wanted subdir of %v", path, filepath.ToSlash(modcache))
 		}
diff --git a/gopls/internal/regtest/misc/link_test.go b/gopls/internal/regtest/misc/link_test.go
index b782fff..8a64c54 100644
--- a/gopls/internal/regtest/misc/link_test.go
+++ b/gopls/internal/regtest/misc/link_test.go
@@ -53,11 +53,11 @@
 		pkgLink := "https://pkg.go.dev/import.test@v1.2.3/pkg"
 
 		// First, check that we get the expected links via hover and documentLink.
-		content, _ := env.Hover("main.go", env.RegexpSearch("main.go", "pkg.Hello"))
+		content, _ := env.Hover(env.RegexpSearch("main.go", "pkg.Hello"))
 		if content == nil || !strings.Contains(content.Value, pkgLink) {
 			t.Errorf("hover: got %v in main.go, want contains %q", content, pkgLink)
 		}
-		content, _ = env.Hover("go.mod", env.RegexpSearch("go.mod", "import.test"))
+		content, _ = env.Hover(env.RegexpSearch("go.mod", "import.test"))
 		if content == nil || !strings.Contains(content.Value, pkgLink) {
 			t.Errorf("hover: got %v in go.mod, want contains %q", content, pkgLink)
 		}
@@ -76,11 +76,11 @@
 		env.ChangeConfiguration(cfg)
 
 		// Finally, verify that the links are gone.
-		content, _ = env.Hover("main.go", env.RegexpSearch("main.go", "pkg.Hello"))
+		content, _ = env.Hover(env.RegexpSearch("main.go", "pkg.Hello"))
 		if content == nil || strings.Contains(content.Value, pkgLink) {
 			t.Errorf("hover: got %v in main.go, want non-empty hover without %q", content, pkgLink)
 		}
-		content, _ = env.Hover("go.mod", env.RegexpSearch("go.mod", "import.test"))
+		content, _ = env.Hover(env.RegexpSearch("go.mod", "import.test"))
 		if content == nil || strings.Contains(content.Value, modLink) {
 			t.Errorf("hover: got %v in go.mod, want contains %q", content, modLink)
 		}
diff --git a/gopls/internal/regtest/misc/multiple_adhoc_test.go b/gopls/internal/regtest/misc/multiple_adhoc_test.go
index 400e784..981b74e 100644
--- a/gopls/internal/regtest/misc/multiple_adhoc_test.go
+++ b/gopls/internal/regtest/misc/multiple_adhoc_test.go
@@ -30,14 +30,14 @@
 }
 `, func(t *testing.T, env *Env) {
 		env.OpenFile("a/a.go")
-		if list := env.Completion("a/a.go", env.RegexpSearch("a/a.go", "Println")); list == nil || len(list.Items) == 0 {
+		if list := env.Completion(env.RegexpSearch("a/a.go", "Println")); list == nil || len(list.Items) == 0 {
 			t.Fatal("expected completions, got none")
 		}
 		env.OpenFile("a/b.go")
-		if list := env.Completion("a/b.go", env.RegexpSearch("a/b.go", "Println")); list == nil || len(list.Items) == 0 {
+		if list := env.Completion(env.RegexpSearch("a/b.go", "Println")); list == nil || len(list.Items) == 0 {
 			t.Fatal("expected completions, got none")
 		}
-		if list := env.Completion("a/a.go", env.RegexpSearch("a/a.go", "Println")); list == nil || len(list.Items) == 0 {
+		if list := env.Completion(env.RegexpSearch("a/a.go", "Println")); list == nil || len(list.Items) == 0 {
 			t.Fatal("expected completions, got none")
 		}
 	})
diff --git a/gopls/internal/regtest/misc/references_test.go b/gopls/internal/regtest/misc/references_test.go
index 8e99a1c..e1f5d8e 100644
--- a/gopls/internal/regtest/misc/references_test.go
+++ b/gopls/internal/regtest/misc/references_test.go
@@ -34,8 +34,8 @@
 
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		file, pos := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `fmt.(Print)`))
-		refs, err := env.Editor.References(env.Ctx, file, pos)
+		loc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt.(Print)`))
+		refs, err := env.Editor.References(env.Ctx, loc)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -79,8 +79,8 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		file, pos := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `Error`))
-		refs, err := env.Editor.References(env.Ctx, file, pos)
+		loc := env.GoToDefinition(env.RegexpSearch("main.go", `Error`))
+		refs, err := env.Editor.References(env.Ctx, loc)
 		if err != nil {
 			t.Fatalf("references on (*s).Error failed: %v", err)
 		}
@@ -157,10 +157,10 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		for _, test := range tests {
-			f := fmt.Sprintf("%s/a.go", test.packageName)
-			env.OpenFile(f)
-			pos := env.RegexpSearch(f, test.packageName)
-			refs := env.References(fmt.Sprintf("%s/a.go", test.packageName), pos)
+			file := fmt.Sprintf("%s/a.go", test.packageName)
+			env.OpenFile(file)
+			loc := env.RegexpSearch(file, test.packageName)
+			refs := env.References(loc)
 			if len(refs) != test.wantRefCount {
 				// TODO(adonovan): make this assertion less maintainer-hostile.
 				t.Fatalf("got %v reference(s), want %d", len(refs), test.wantRefCount)
@@ -277,8 +277,8 @@
 		}
 
 		for _, test := range refTests {
-			pos := env.RegexpSearch("foo/foo.go", test.re)
-			refs := env.References("foo/foo.go", pos)
+			loc := env.RegexpSearch("foo/foo.go", test.re)
+			refs := env.References(loc)
 
 			got := fileLocations(refs)
 			if diff := cmp.Diff(test.wantRefs, got); diff != "" {
@@ -301,8 +301,8 @@
 		}
 
 		for _, test := range implTests {
-			pos := env.RegexpSearch("foo/foo.go", test.re)
-			impls := env.Implementations("foo/foo.go", pos)
+			loc := env.RegexpSearch("foo/foo.go", test.re)
+			impls := env.Implementations(loc)
 
 			got := fileLocations(impls)
 			if diff := cmp.Diff(test.wantImpls, got); diff != "" {
@@ -364,11 +364,11 @@
 		}
 
 		env.OpenFile("a.go")
-		refPos := env.RegexpSearch("a.go", "I") // find "I" reference
+		refLoc := env.RegexpSearch("a.go", "I") // find "I" reference
 
 		// Initially, a.I has one implementation b.B in
 		// the module cache, not the vendor tree.
-		checkVendor(env.Implementations("a.go", refPos), false)
+		checkVendor(env.Implementations(refLoc), false)
 
 		// Run 'go mod vendor' outside the editor.
 		if err := env.Sandbox.RunGoCommand(env.Ctx, ".", "mod", []string{"vendor"}, true); err != nil {
@@ -379,7 +379,7 @@
 		env.Await(env.DoneWithChangeWatchedFiles())
 
 		// Now, b.B is found in the vendor tree.
-		checkVendor(env.Implementations("a.go", refPos), true)
+		checkVendor(env.Implementations(refLoc), true)
 
 		// Delete the vendor tree.
 		if err := os.RemoveAll(env.Sandbox.Workdir.AbsPath("vendor")); err != nil {
@@ -394,6 +394,6 @@
 		env.Await(env.DoneWithChangeWatchedFiles())
 
 		// b.B is once again defined in the module cache.
-		checkVendor(env.Implementations("a.go", refPos), false)
+		checkVendor(env.Implementations(refLoc), false)
 	})
 }
diff --git a/gopls/internal/regtest/misc/rename_test.go b/gopls/internal/regtest/misc/rename_test.go
index 86fd567..833d3db 100644
--- a/gopls/internal/regtest/misc/rename_test.go
+++ b/gopls/internal/regtest/misc/rename_test.go
@@ -34,10 +34,11 @@
 	const wantErr = "can't rename package \"main\""
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		pos := env.RegexpSearch("main.go", `main`)
+		loc := env.RegexpSearch("main.go", `main`)
+		// TODO(adonovan): define a helper from Location to TextDocumentPositionParams.
 		tdpp := protocol.TextDocumentPositionParams{
 			TextDocument: env.Editor.TextDocumentIdentifier("main.go"),
-			Position:     pos,
+			Position:     loc.Range.Start,
 		}
 		params := &protocol.PrepareRenameParams{
 			TextDocumentPositionParams: tdpp,
@@ -79,7 +80,7 @@
 
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("p.go")
-		env.Rename("p.go", env.RegexpSearch("p.go", "M"), "N") // must not panic
+		env.Rename(env.RegexpSearch("p.go", "M"), "N") // must not panic
 	})
 }
 
@@ -107,9 +108,7 @@
 	const wantErr = "no object found"
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "fmt")
-
-		err := env.Editor.Rename(env.Ctx, "lib/a.go", pos, "fmt1")
+		err := env.Editor.Rename(env.Ctx, env.RegexpSearch("lib/a.go", "fmt"), "fmt1")
 		if err == nil {
 			t.Errorf("missing no object found from Rename")
 		}
@@ -142,10 +141,10 @@
 `
 	const wantErr = "can't rename package: missing module information for package"
 	Run(t, files, func(t *testing.T, env *Env) {
-		pos := env.RegexpSearch("lib/a.go", "lib")
+		loc := env.RegexpSearch("lib/a.go", "lib")
 		tdpp := protocol.TextDocumentPositionParams{
 			TextDocument: env.Editor.TextDocumentIdentifier("lib/a.go"),
-			Position:     pos,
+			Position:     loc.Range.Start,
 		}
 		params := &protocol.PrepareRenameParams{
 			TextDocumentPositionParams: tdpp,
@@ -194,8 +193,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "lib")
-		env.Rename("lib/a.go", pos, "nested")
+		env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested")
 
 		// Check if the new package name exists.
 		env.RegexpSearch("nested/a.go", "package nested")
@@ -236,8 +234,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "lib")
-		env.Rename("lib/a.go", pos, "nested")
+		env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested")
 
 		// Check if the new package name exists.
 		env.RegexpSearch("nested/a.go", "package nested")
@@ -277,8 +274,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "lib")
-		env.Rename("lib/a.go", pos, "nested")
+		env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested")
 
 		// Check if the new package name exists.
 		env.RegexpSearch("nested/a.go", "package nested")
@@ -323,8 +319,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "lib")
-		env.Rename("lib/a.go", pos, "lib1")
+		env.Rename(env.RegexpSearch("lib/a.go", "lib"), "lib1")
 
 		// Check if the new package name exists.
 		env.RegexpSearch("lib1/a.go", "package lib1")
@@ -371,8 +366,7 @@
 
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		pos := env.RegexpSearch("main.go", `stringutil\.(Identity)`)
-		env.Rename("main.go", pos, "Identityx")
+		env.Rename(env.RegexpSearch("main.go", `stringutil\.(Identity)`), "Identityx")
 		text := env.BufferText("stringutil/stringutil_test.go")
 		if !strings.Contains(text, "Identityx") {
 			t.Errorf("stringutil/stringutil_test.go: missing expected token `Identityx` after rename:\n%s", text)
@@ -495,8 +489,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "lib")
-		env.Rename("lib/a.go", pos, "lib1")
+		env.Rename(env.RegexpSearch("lib/a.go", "lib"), "lib1")
 
 		// Check if the new package name exists.
 		env.RegexpSearch("lib1/a.go", "package lib1")
@@ -577,8 +570,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("foo/foo.go")
-		pos := env.RegexpSearch("foo/foo.go", "foo")
-		env.Rename("foo/foo.go", pos, "foox")
+		env.Rename(env.RegexpSearch("foo/foo.go", "foo"), "foox")
 
 		env.RegexpSearch("foox/foo.go", "package foox")
 		env.OpenFile("foox/bar/bar.go")
@@ -625,8 +617,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "lib")
-		env.Rename("lib/a.go", pos, "nested")
+		env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested")
 
 		// Check if the new package name exists.
 		env.RegexpSearch("nested/a.go", "package nested")
@@ -668,8 +659,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "lib")
-		env.Rename("lib/a.go", pos, "nested")
+		env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested")
 
 		// Check if the new package name exists.
 		env.RegexpSearch("nested/a.go", "package nested")
@@ -716,7 +706,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("foo/foo.go")
-		env.Rename("foo/foo.go", env.RegexpSearch("foo/foo.go", "package (foo)"), "foox")
+		env.Rename(env.RegexpSearch("foo/foo.go", "package (foo)"), "foox")
 
 		checkTestdata(t, env)
 	})
@@ -796,7 +786,7 @@
 
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("foo/foo.go")
-		env.Rename("foo/foo.go", env.RegexpSearch("foo/foo.go", "package (foo)"), "foox")
+		env.Rename(env.RegexpSearch("foo/foo.go", "package (foo)"), "foox")
 
 		checkTestdata(t, env)
 	})
@@ -846,8 +836,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "package (lib)")
-		env.Rename("lib/a.go", pos, "libx")
+		env.Rename(env.RegexpSearch("lib/a.go", "package (lib)"), "libx")
 
 		checkTestdata(t, env)
 	})
@@ -870,10 +859,10 @@
 
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/a.go")
-		pos := env.RegexpSearch("lib/a.go", "package (lib)")
+		loc := env.RegexpSearch("lib/a.go", "package (lib)")
 
 		for _, badName := range []string{"$$$", "lib_test"} {
-			if err := env.Editor.Rename(env.Ctx, "lib/a.go", pos, badName); err == nil {
+			if err := env.Editor.Rename(env.Ctx, loc, badName); err == nil {
 				t.Errorf("Rename(lib, libx) succeeded, want non-nil error")
 			}
 		}
@@ -917,8 +906,7 @@
 `
 	Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("lib/internal/x/a.go")
-		pos := env.RegexpSearch("lib/internal/x/a.go", "x")
-		env.Rename("lib/internal/x/a.go", pos, "utils")
+		env.Rename(env.RegexpSearch("lib/internal/x/a.go", "x"), "utils")
 
 		// Check if the new package name exists.
 		env.RegexpSearch("lib/a.go", "mod.com/lib/internal/utils")
@@ -928,8 +916,7 @@
 		env.RegexpSearch("lib/internal/utils/a.go", "package utils")
 
 		env.OpenFile("lib/a.go")
-		pos = env.RegexpSearch("lib/a.go", "lib")
-		env.Rename("lib/a.go", pos, "lib1")
+		env.Rename(env.RegexpSearch("lib/a.go", "lib"), "lib1")
 
 		// Check if the new package name exists.
 		env.RegexpSearch("lib1/a.go", "package lib1")
diff --git a/gopls/internal/regtest/misc/signature_help_test.go b/gopls/internal/regtest/misc/signature_help_test.go
index 05f5830..fd9f4f0 100644
--- a/gopls/internal/regtest/misc/signature_help_test.go
+++ b/gopls/internal/regtest/misc/signature_help_test.go
@@ -49,10 +49,10 @@
 		env.OpenFile("a/a/a.go")
 		env.OpenFile("b/b/b.go")
 		signatureHelp := func(filename string) *protocol.SignatureHelp {
-			pos := env.RegexpSearch(filename, `DoSomething\(()\)`)
+			loc := env.RegexpSearch(filename, `DoSomething\(()\)`)
 			var params protocol.SignatureHelpParams
-			params.Position = pos
-			params.TextDocument.URI = env.Sandbox.Workdir.URI(filename)
+			params.TextDocument.URI = loc.URI
+			params.Position = loc.Range.Start
 			help, err := env.Editor.Server.SignatureHelp(env.Ctx, &params)
 			if err != nil {
 				t.Fatal(err)
diff --git a/gopls/internal/regtest/misc/vuln_test.go b/gopls/internal/regtest/misc/vuln_test.go
index 9d73272..bbf99b3 100644
--- a/gopls/internal/regtest/misc/vuln_test.go
+++ b/gopls/internal/regtest/misc/vuln_test.go
@@ -644,9 +644,9 @@
 			"go.mod": {IDs: []string{"GO-2022-01", "GO-2022-02", "GO-2022-03"}, Mode: govulncheck.ModeGovulncheck},
 		})
 		env.OpenFile("x/x.go")
-		lineX := env.RegexpSearch("x/x.go", `c\.C1\(\)\.Vuln1\(\)`)
+		lineX := env.RegexpSearch("x/x.go", `c\.C1\(\)\.Vuln1\(\)`).Range.Start
 		env.OpenFile("y/y.go")
-		lineY := env.RegexpSearch("y/y.go", `c\.C2\(\)\(\)`)
+		lineY := env.RegexpSearch("y/y.go", `c\.C2\(\)\(\)`).Range.Start
 		wantDiagnostics := map[string]vulnDiagExpectation{
 			"golang.org/amod": {
 				applyAction: "Upgrade to v1.0.6",
@@ -862,14 +862,14 @@
 // and runs checks if diagnostics and code actions associated with the line match expectation.
 func testVulnDiagnostics(t *testing.T, env *Env, pattern string, want vulnDiagExpectation, got *protocol.PublishDiagnosticsParams) []protocol.Diagnostic {
 	t.Helper()
-	pos := env.RegexpSearch("go.mod", pattern)
+	loc := env.RegexpSearch("go.mod", pattern)
 	var modPathDiagnostics []protocol.Diagnostic
 	for _, w := range want.diagnostics {
-		// Find the diagnostics at pos.
+		// Find the diagnostics at loc.start.
 		var diag *protocol.Diagnostic
 		for _, g := range got.Diagnostics {
 			g := g
-			if g.Range.Start == pos && w.msg == g.Message {
+			if g.Range.Start == loc.Range.Start && w.msg == g.Message {
 				modPathDiagnostics = append(modPathDiagnostics, g)
 				diag = &g
 				break
@@ -895,7 +895,7 @@
 	}
 	// Check that useful info is supplemented as hover.
 	if len(want.hover) > 0 {
-		hover, _ := env.Hover("go.mod", pos)
+		hover, _ := env.Hover(loc)
 		for _, part := range want.hover {
 			if !strings.Contains(hover.Value, part) {
 				t.Errorf("hover contents for %q do not match, want %v, got %v\n", pattern, strings.Join(want.hover, ","), hover.Value)
diff --git a/gopls/internal/regtest/modfile/modfile_test.go b/gopls/internal/regtest/modfile/modfile_test.go
index f44eefa..483118d 100644
--- a/gopls/internal/regtest/modfile/modfile_test.go
+++ b/gopls/internal/regtest/modfile/modfile_test.go
@@ -913,13 +913,13 @@
 		env.RegexpReplace("go.mod", "module", "modul")
 		// Confirm that we still have metadata with only on-disk edits.
 		env.OpenFile("main.go")
-		file, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", "hello"))
-		if filepath.Base(file) != "hello.go" {
-			t.Fatalf("expected definition in hello.go, got %s", file)
+		loc := env.GoToDefinition(env.RegexpSearch("main.go", "hello"))
+		if filepath.Base(string(loc.URI)) != "hello.go" {
+			t.Fatalf("expected definition in hello.go, got %s", loc.URI)
 		}
 		// Confirm that we no longer have metadata when the file is saved.
 		env.SaveBufferWithoutActions("go.mod")
-		_, _, err := env.Editor.GoToDefinition(env.Ctx, "main.go", env.RegexpSearch("main.go", "hello"))
+		_, err := env.Editor.GoToDefinition(env.Ctx, env.RegexpSearch("main.go", "hello"))
 		if err == nil {
 			t.Fatalf("expected error, got none")
 		}
@@ -1017,7 +1017,7 @@
 		).Run(t, mod, func(t *testing.T, env *Env) {
 			d := &protocol.PublishDiagnosticsParams{}
 			env.OpenFile("go.mod")
-			pos := env.RegexpSearch("go.mod", "require hasdep.com v1.2.3")
+			pos := env.RegexpSearch("go.mod", "require hasdep.com v1.2.3").Range.Start
 			env.AfterChange(
 				Diagnostics(AtPosition("go.mod", pos.Line, pos.Character)),
 				ReadDiagnostics("go.mod", d),
diff --git a/gopls/internal/regtest/template/template_test.go b/gopls/internal/regtest/template/template_test.go
index c81bf1a..4863564 100644
--- a/gopls/internal/regtest/template/template_test.go
+++ b/gopls/internal/regtest/template/template_test.go
@@ -191,8 +191,8 @@
 	).Run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("a.tmpl")
 		x := env.RegexpSearch("a.tmpl", `A`)
-		file, pos := env.GoToDefinition("a.tmpl", x)
-		refs := env.References(file, pos)
+		loc := env.GoToDefinition(x)
+		refs := env.References(loc)
 		if len(refs) != 2 {
 			t.Fatalf("got %v reference(s), want 2", len(refs))
 		}
@@ -205,9 +205,9 @@
 			}
 		}
 
-		content, npos := env.Hover(file, pos)
-		if pos != npos {
-			t.Errorf("pos? got %v, wanted %v", npos, pos)
+		content, nloc := env.Hover(loc)
+		if loc != nloc {
+			t.Errorf("loc? got %v, wanted %v", nloc, loc)
 		}
 		if content.Value != "template A defined" {
 			t.Errorf("got %s, wanted 'template A defined", content.Value)
diff --git a/gopls/internal/regtest/workspace/broken_test.go b/gopls/internal/regtest/workspace/broken_test.go
index 5c5a68a..005a7e9 100644
--- a/gopls/internal/regtest/workspace/broken_test.go
+++ b/gopls/internal/regtest/workspace/broken_test.go
@@ -113,7 +113,7 @@
 		env.Await(NoOutstandingWork())
 
 		// Check that definitions in package1 go to the copy vendored in package2.
-		location, _ := env.GoToDefinition("package1/main.go", env.RegexpSearch("package1/main.go", "CompleteMe"))
+		location := env.GoToDefinition(env.RegexpSearch("package1/main.go", "CompleteMe")).URI.SpanURI().Filename()
 		const wantLocation = "package2/vendor/example.com/foo/foo.go"
 		if !strings.HasSuffix(location, wantLocation) {
 			t.Errorf("got definition of CompleteMe at %q, want %q", location, wantLocation)
diff --git a/gopls/internal/regtest/workspace/metadata_test.go b/gopls/internal/regtest/workspace/metadata_test.go
index 11dfe67..ac64b07 100644
--- a/gopls/internal/regtest/workspace/metadata_test.go
+++ b/gopls/internal/regtest/workspace/metadata_test.go
@@ -172,7 +172,8 @@
 		// Now, to satisfy a definition request, gopls will try to reload moda. But
 		// without access to the proxy (because this is no longer a
 		// reinitialization), this loading will fail.
-		got, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
+		got := env.Sandbox.Workdir.URIToPath(loc.URI)
 		if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(got, want) {
 			t.Errorf("expected %s, got %v", want, got)
 		}
diff --git a/gopls/internal/regtest/workspace/standalone_test.go b/gopls/internal/regtest/workspace/standalone_test.go
index c9618eb..e1021df 100644
--- a/gopls/internal/regtest/workspace/standalone_test.go
+++ b/gopls/internal/regtest/workspace/standalone_test.go
@@ -83,10 +83,10 @@
 		env.RegexpReplace("lib/lib.go", "D", "C")
 		env.AfterChange(NoDiagnostics())
 
-		refs := env.References("lib/lib.go", env.RegexpSearch("lib/lib.go", "C"))
+		refs := env.References(env.RegexpSearch("lib/lib.go", "C"))
 		checkLocations("References", refs, "lib/lib.go")
 
-		impls := env.Implementations("lib/lib.go", env.RegexpSearch("lib/lib.go", "I"))
+		impls := env.Implementations(env.RegexpSearch("lib/lib.go", "I"))
 		checkLocations("Implementations", impls) // no implementations
 
 		// Opening the standalone file should not result in any diagnostics.
@@ -113,19 +113,17 @@
 		}
 
 		// We should resolve workspace definitions in the standalone file.
-		file, _ := env.GoToDefinition("lib/ignore.go", env.RegexpSearch("lib/ignore.go", "lib.(C)"))
+		fileLoc := env.GoToDefinition(env.RegexpSearch("lib/ignore.go", "lib.(C)"))
+		file := env.Sandbox.Workdir.URIToPath(fileLoc.URI)
 		if got, want := file, "lib/lib.go"; got != want {
 			t.Errorf("GoToDefinition(lib.C) = %v, want %v", got, want)
 		}
 
 		// ...as well as intra-file definitions
-		file, pos := env.GoToDefinition("lib/ignore.go", env.RegexpSearch("lib/ignore.go", "\\+ (C)"))
-		if got, want := file, "lib/ignore.go"; got != want {
-			t.Errorf("GoToDefinition(C) = %v, want %v", got, want)
-		}
-		wantPos := env.RegexpSearch("lib/ignore.go", "const (C)")
-		if pos != wantPos {
-			t.Errorf("GoToDefinition(C) = %v, want %v", pos, wantPos)
+		loc := env.GoToDefinition(env.RegexpSearch("lib/ignore.go", "\\+ (C)"))
+		wantLoc := env.RegexpSearch("lib/ignore.go", "const (C)")
+		if loc != wantLoc {
+			t.Errorf("GoToDefinition(C) = %v, want %v", loc, wantLoc)
 		}
 
 		// Renaming "lib.C" to "lib.D" should cause a diagnostic in the standalone
@@ -139,14 +137,14 @@
 
 		// Now that our workspace has no errors, we should be able to find
 		// references and rename.
-		refs = env.References("lib/lib.go", env.RegexpSearch("lib/lib.go", "C"))
+		refs = env.References(env.RegexpSearch("lib/lib.go", "C"))
 		checkLocations("References", refs, "lib/lib.go", "lib/ignore.go")
 
-		impls = env.Implementations("lib/lib.go", env.RegexpSearch("lib/lib.go", "I"))
+		impls = env.Implementations(env.RegexpSearch("lib/lib.go", "I"))
 		checkLocations("Implementations", impls, "lib/ignore.go")
 
 		// Renaming should rename in the standalone package.
-		env.Rename("lib/lib.go", env.RegexpSearch("lib/lib.go", "C"), "D")
+		env.Rename(env.RegexpSearch("lib/lib.go", "C"), "D")
 		env.RegexpSearch("lib/ignore.go", "lib.D")
 	})
 }
diff --git a/gopls/internal/regtest/workspace/workspace_test.go b/gopls/internal/regtest/workspace/workspace_test.go
index 3699fa8..0aff471 100644
--- a/gopls/internal/regtest/workspace/workspace_test.go
+++ b/gopls/internal/regtest/workspace/workspace_test.go
@@ -131,7 +131,7 @@
 			WithOptions(opts...).Run(t, workspaceModule, func(t *testing.T, env *Env) {
 				f := "pkg/inner/inner.go"
 				env.OpenFile(f)
-				locations := env.References(f, env.RegexpSearch(f, `SaySomething`))
+				locations := env.References(env.RegexpSearch(f, `SaySomething`))
 				want := 3
 				if got := len(locations); got != want {
 					t.Fatalf("expected %v locations, got %v", want, got)
@@ -378,7 +378,8 @@
 		env.OpenFile("moda/a/a.go")
 		env.Await(env.DoneWithOpen())
 
-		original, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		originalLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
+		original := env.Sandbox.Workdir.URIToPath(originalLoc.URI)
 		if want := "modb/b/b.go"; !strings.HasSuffix(original, want) {
 			t.Errorf("expected %s, got %v", want, original)
 		}
@@ -390,7 +391,8 @@
 		env.WriteWorkspaceFile("go.work", "go 1.18\nuse moda/a")
 		env.AfterChange()
 
-		got, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		gotLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
+		got := env.Sandbox.Workdir.URIToPath(gotLoc.URI)
 		if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(got, want) {
 			t.Errorf("expected %s, got %v", want, got)
 		}
@@ -431,7 +433,8 @@
 		ProxyFiles(workspaceModuleProxy),
 	).Run(t, multiModule, func(t *testing.T, env *Env) {
 		env.OpenFile("moda/a/a.go")
-		original, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
+		original := env.Sandbox.Workdir.URIToPath(loc.URI)
 		if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(original, want) {
 			t.Errorf("expected %s, got %v", want, original)
 		}
@@ -453,7 +456,8 @@
 `,
 		})
 		env.AfterChange(Diagnostics(env.AtRegexp("modb/b/b.go", "x")))
-		got, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		gotLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
+		got := env.Sandbox.Workdir.URIToPath(gotLoc.URI)
 		if want := "modb/b/b.go"; !strings.HasSuffix(got, want) {
 			t.Errorf("expected %s, got %v", want, original)
 		}
@@ -581,9 +585,10 @@
 		// To verify which modules are loaded, we'll jump to the definition of
 		// b.Hello.
 		checkHelloLocation := func(want string) error {
-			location, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
-			if !strings.HasSuffix(location, want) {
-				return fmt.Errorf("expected %s, got %v", want, location)
+			loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
+			file := env.Sandbox.Workdir.URIToPath(loc.URI)
+			if !strings.HasSuffix(file, want) {
+				return fmt.Errorf("expected %s, got %v", want, file)
 			}
 			return nil
 		}
@@ -749,8 +754,7 @@
 		}
 
 		for hoverRE, want := range tcs {
-			pos := env.RegexpSearch("go.work", hoverRE)
-			got, _ := env.Hover("go.work", pos)
+			got, _ := env.Hover(env.RegexpSearch("go.work", hoverRE))
 			if got.Value != want {
 				t.Errorf(`hover on %q: got %q, want %q`, hoverRE, got, want)
 			}
@@ -799,21 +803,22 @@
 	).Run(t, workspace, func(t *testing.T, env *Env) {
 		env.OpenFile("moda/a/a.go")
 		env.Await(env.DoneWithOpen())
-		location, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
+		file := env.Sandbox.Workdir.URIToPath(loc.URI)
 		want := "modb/b/b.go"
-		if !strings.HasSuffix(location, want) {
-			t.Errorf("expected %s, got %v", want, location)
+		if !strings.HasSuffix(file, want) {
+			t.Errorf("expected %s, got %v", want, file)
 		}
 	})
 }
 
 func TestNonWorkspaceFileCreation(t *testing.T) {
 	const files = `
--- go.mod --
+-- work/go.mod --
 module mod.com
 
 go 1.12
--- x.go --
+-- work/x.go --
 package x
 `
 
@@ -822,10 +827,12 @@
 import "fmt"
 var _ = fmt.Printf
 `
-	Run(t, files, func(t *testing.T, env *Env) {
-		env.CreateBuffer("/tmp/foo.go", "")
-		env.EditBuffer("/tmp/foo.go", fake.NewEdit(0, 0, 0, 0, code))
-		env.GoToDefinition("/tmp/foo.go", env.RegexpSearch("/tmp/foo.go", `Printf`))
+	WithOptions(
+		WorkspaceFolders("work"), // so that outside/... is outside the workspace
+	).Run(t, files, func(t *testing.T, env *Env) {
+		env.CreateBuffer("outside/foo.go", "")
+		env.EditBuffer("outside/foo.go", fake.NewEdit(0, 0, 0, 0, code))
+		env.GoToDefinition(env.RegexpSearch("outside/foo.go", `Printf`))
 	})
 }
 
@@ -1124,7 +1131,7 @@
 		)
 
 		// This will cause a test failure if other_test.go is not in any package.
-		_, _ = env.GoToDefinition("other_test.go", env.RegexpSearch("other_test.go", "Server"))
+		_ = env.GoToDefinition(env.RegexpSearch("other_test.go", "Server"))
 	})
 }