internal/lsp: reduce usage of column mapper

A mapper is always uniquely tied to a file at a specific version, so
just build it when we get a new *ast.File. We build the mapper using the
*token.File associated with the particular *ast.File, which is why there
is one per ParseGoHandle instead of FileHandle.

Change-Id: Ida40981ef91f6133cdd07e9793337fcd67510fba
Reviewed-on: https://go-review.googlesource.com/c/tools/+/194517
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index b9bae20..2ccf62f 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -258,7 +258,7 @@
 		go func(i int, ph source.ParseGoHandle) {
 			defer wg.Done()
 
-			files[i], parseErrors[i] = ph.Parse(ctx)
+			files[i], _, parseErrors[i] = ph.Parse(ctx)
 		}(i, ph)
 	}
 	wg.Wait()
@@ -350,7 +350,7 @@
 	}
 	gof.pkgs[cph.m.id] = cph
 
-	file, err := ph.Parse(ctx)
+	file, _, err := ph.Parse(ctx)
 	if file == nil {
 		return errors.Errorf("no AST for %s: %v", ph.File().Identity().URI, err)
 	}
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index 1129c75..14b5879 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -190,7 +190,7 @@
 		return true
 	}
 	// Get file content in case we don't already have it.
-	parsed, err := v.session.cache.ParseGoHandle(fh, source.ParseHeader).Parse(ctx)
+	parsed, _, err := v.session.cache.ParseGoHandle(fh, source.ParseHeader).Parse(ctx)
 	if err == context.Canceled {
 		log.Error(ctx, "parsing file header", err, tag.Of("file", f.URI()))
 		return false
diff --git a/internal/lsp/cache/parse.go b/internal/lsp/cache/parse.go
index 328847c..2dda5b6 100644
--- a/internal/lsp/cache/parse.go
+++ b/internal/lsp/cache/parse.go
@@ -13,9 +13,11 @@
 	"go/token"
 	"reflect"
 
+	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
 	"golang.org/x/tools/internal/memoize"
+	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/trace"
 	errors "golang.org/x/xerrors"
@@ -39,8 +41,9 @@
 type parseGoData struct {
 	memoize.NoCopy
 
-	ast *ast.File
-	err error
+	ast    *ast.File
+	mapper *protocol.ColumnMapper
+	err    error
 }
 
 func (c *cache) ParseGoHandle(fh source.FileHandle, mode source.ParseMode) source.ParseGoHandle {
@@ -51,6 +54,17 @@
 	h := c.store.Bind(key, func(ctx context.Context) interface{} {
 		data := &parseGoData{}
 		data.ast, data.err = parseGo(ctx, c, fh, mode)
+		tok := c.FileSet().File(data.ast.Pos())
+		if tok == nil {
+			return data
+		}
+		uri := fh.Identity().URI
+		content, _, err := fh.Read(ctx)
+		if err != nil {
+			data.err = err
+			return data
+		}
+		data.mapper = newColumnMapper(uri, c.FileSet(), tok, content)
 		return data
 	})
 	return &parseGoHandle{
@@ -59,6 +73,13 @@
 		mode:   mode,
 	}
 }
+func newColumnMapper(uri span.URI, fset *token.FileSet, tok *token.File, content []byte) *protocol.ColumnMapper {
+	return &protocol.ColumnMapper{
+		URI:       uri,
+		Converter: span.NewTokenConverter(fset, tok),
+		Content:   content,
+	}
+}
 
 func (h *parseGoHandle) File() source.FileHandle {
 	return h.file
@@ -68,22 +89,22 @@
 	return h.mode
 }
 
-func (h *parseGoHandle) Parse(ctx context.Context) (*ast.File, error) {
+func (h *parseGoHandle) Parse(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error) {
 	v := h.handle.Get(ctx)
 	if v == nil {
-		return nil, ctx.Err()
+		return nil, nil, ctx.Err()
 	}
 	data := v.(*parseGoData)
-	return data.ast, data.err
+	return data.ast, data.mapper, data.err
 }
 
-func (h *parseGoHandle) Cached(ctx context.Context) (*ast.File, error) {
+func (h *parseGoHandle) Cached(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error) {
 	v := h.handle.Cached()
 	if v == nil {
-		return nil, errors.Errorf("no cached value for %s", h.file.Identity().URI)
+		return nil, nil, errors.Errorf("no cached value for %s", h.file.Identity().URI)
 	}
 	data := v.(*parseGoData)
-	return data.ast, data.err
+	return data.ast, data.mapper, data.err
 }
 
 func hashParseKey(ph source.ParseGoHandle) string {
diff --git a/internal/lsp/cache/pkg.go b/internal/lsp/cache/pkg.go
index 69c9a74..757dc15 100644
--- a/internal/lsp/cache/pkg.go
+++ b/internal/lsp/cache/pkg.go
@@ -7,7 +7,6 @@
 import (
 	"context"
 	"go/ast"
-	"go/token"
 	"go/types"
 	"sort"
 	"sync"
@@ -155,7 +154,7 @@
 func (pkg *pkg) GetSyntax(ctx context.Context) []*ast.File {
 	var syntax []*ast.File
 	for _, ph := range pkg.files {
-		file, _ := ph.Cached(ctx)
+		file, _, _ := ph.Cached(ctx)
 		if file != nil {
 			syntax = append(syntax, file)
 		}
@@ -203,7 +202,7 @@
 	return diags
 }
 
-func (p *pkg) FindFile(ctx context.Context, uri span.URI, pos token.Pos) (source.ParseGoHandle, *ast.File, source.Package, error) {
+func (p *pkg) FindFile(ctx context.Context, uri span.URI) (source.ParseGoHandle, *ast.File, source.Package, error) {
 	queue := []*pkg{p}
 	seen := make(map[string]bool)
 
@@ -214,13 +213,11 @@
 
 		for _, ph := range pkg.files {
 			if ph.File().Identity().URI == uri {
-				file, err := ph.Cached(ctx)
+				file, _, err := ph.Cached(ctx)
 				if file == nil {
 					return nil, nil, nil, err
 				}
-				if file.Pos() <= pos && pos <= file.End() {
-					return ph, file, pkg, nil
-				}
+				return ph, file, pkg, nil
 			}
 		}
 		for _, dep := range pkg.imports {
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index f5696e0..a765fab 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -8,7 +8,6 @@
 	"context"
 	"fmt"
 	"go/ast"
-	"go/parser"
 	"go/token"
 	"go/types"
 	"os"
@@ -310,8 +309,11 @@
 	pkg := pkgs[0]
 	files := make(map[string]*ast.File)
 	for _, filename := range pkg.GoFiles {
-		file, err := parser.ParseFile(cfg.Fset, filename, nil, parser.ParseComments)
-		if err != nil {
+		fh := v.session.GetFile(span.FileURI(filename))
+		ph := v.session.cache.ParseGoHandle(fh, source.ParseFull)
+		file, _, err := ph.Parse(ctx)
+		if file == nil {
+			log.Error(ctx, "failed to parse builtin", err, telemetry.File.Of(filename))
 			v.builtinPkg, _ = ast.NewPackage(cfg.Fset, nil, nil, nil)
 			return
 		}
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index ce04ca1..a2e2af8 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -344,7 +344,12 @@
 		}
 		f := c.fset.AddFile(fname, -1, len(content))
 		f.SetLinesForContent(content)
-		file.mapper = protocol.NewColumnMapper(uri, fname, c.fset, f, content)
+		converter := span.NewContentConverter(fname, content)
+		file.mapper = &protocol.ColumnMapper{
+			URI:       uri,
+			Converter: converter,
+			Content:   content,
+		}
 	}
 	return file
 }
diff --git a/internal/lsp/link.go b/internal/lsp/link.go
index 5c42f14..9b24813 100644
--- a/internal/lsp/link.go
+++ b/internal/lsp/link.go
@@ -28,17 +28,10 @@
 		return nil, err
 	}
 	fh := f.Handle(ctx)
-	data, _, err := fh.Read(ctx)
-	if err != nil {
-		return nil, err
-	}
-	file, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx)
+	file, m, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx)
 	if file == nil {
 		return nil, err
 	}
-	tok := view.Session().Cache().FileSet().File(file.Pos())
-	m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), view.Session().Cache().FileSet(), tok, data)
-
 	var links []protocol.DocumentLink
 	ast.Inspect(file, func(node ast.Node) bool {
 		switch n := node.(type) {
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 4bc9e35..1e0c5f2 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -352,7 +352,7 @@
 }
 
 func (r *runner) foldingRanges(t *testing.T, prefix string, uri span.URI, ranges []protocol.FoldingRange) {
-	m, err := r.mapper(uri)
+	m, err := r.data.Mapper(uri)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -484,7 +484,7 @@
 			}
 			continue
 		}
-		m, err := r.mapper(uri)
+		m, err := r.data.Mapper(uri)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -520,7 +520,7 @@
 			}
 			continue
 		}
-		m, err := r.mapper(uri)
+		m, err := r.data.Mapper(uri)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -572,7 +572,7 @@
 			}
 			continue
 		}
-		m, err := r.mapper(f.URI())
+		m, err := r.data.Mapper(f.URI())
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -595,7 +595,7 @@
 
 func (r *runner) Definition(t *testing.T, data tests.Definitions) {
 	for _, d := range data {
-		sm, err := r.mapper(d.Src.URI())
+		sm, err := r.data.Mapper(d.Src.URI())
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -648,7 +648,7 @@
 			}
 		} else if !d.OnlyHover {
 			locURI := span.NewURI(locs[0].URI)
-			lm, err := r.mapper(locURI)
+			lm, err := r.data.Mapper(locURI)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -665,7 +665,7 @@
 
 func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
 	for name, locations := range data {
-		m, err := r.mapper(locations[0].URI())
+		m, err := r.data.Mapper(locations[0].URI())
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -701,7 +701,7 @@
 
 func (r *runner) Reference(t *testing.T, data tests.References) {
 	for src, itemList := range data {
-		sm, err := r.mapper(src.URI())
+		sm, err := r.data.Mapper(src.URI())
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -712,7 +712,7 @@
 
 		want := make(map[protocol.Location]bool)
 		for _, pos := range itemList {
-			m, err := r.mapper(pos.URI())
+			m, err := r.data.Mapper(pos.URI())
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -749,7 +749,7 @@
 
 		uri := spn.URI()
 		filename := uri.Filename()
-		sm, err := r.mapper(uri)
+		sm, err := r.data.Mapper(uri)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -777,7 +777,7 @@
 
 		var res []string
 		for uri, edits := range *workspaceEdits.Changes {
-			m, err := r.mapper(span.URI(uri))
+			m, err := r.data.Mapper(span.URI(uri))
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -813,7 +813,7 @@
 
 func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) {
 	for src, want := range data {
-		m, err := r.mapper(src.URI())
+		m, err := r.data.Mapper(src.URI())
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -928,7 +928,7 @@
 
 func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
 	for spn, expectedSignatures := range data {
-		m, err := r.mapper(spn.URI())
+		m, err := r.data.Mapper(spn.URI())
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -960,6 +960,9 @@
 			}
 			continue
 		}
+		if gotSignatures == nil {
+			t.Fatalf("expected %v, got nil", expectedSignatures)
+		}
 		if diff := diffSignatures(spn, expectedSignatures, gotSignatures); diff != "" {
 			t.Error(diff)
 		}
@@ -1003,7 +1006,7 @@
 
 func (r *runner) Link(t *testing.T, data tests.Links) {
 	for uri, wantLinks := range data {
-		m, err := r.mapper(uri)
+		m, err := r.data.Mapper(uri)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -1054,27 +1057,6 @@
 	}
 }
 
-func (r *runner) mapper(uri span.URI) (*protocol.ColumnMapper, error) {
-	filename := uri.Filename()
-	fset := r.data.Exported.ExpectFileSet
-	var f *token.File
-	fset.Iterate(func(check *token.File) bool {
-		if check.Name() == filename {
-			f = check
-			return false
-		}
-		return true
-	})
-	if f == nil {
-		return nil, fmt.Errorf("no token.File for %s", uri)
-	}
-	content, err := r.data.Exported.FileContents(f.Name())
-	if err != nil {
-		return nil, err
-	}
-	return protocol.NewColumnMapper(uri, filename, fset, f, content), nil
-}
-
 func TestBytesOffset(t *testing.T) {
 	tests := []struct {
 		text string
@@ -1102,7 +1084,13 @@
 		fset := token.NewFileSet()
 		f := fset.AddFile(fname, -1, len(test.text))
 		f.SetLinesForContent([]byte(test.text))
-		mapper := protocol.NewColumnMapper(span.FileURI(fname), fname, fset, f, []byte(test.text))
+		uri := span.FileURI(fname)
+		converter := span.NewContentConverter(fname, []byte(test.text))
+		mapper := &protocol.ColumnMapper{
+			URI:       uri,
+			Converter: converter,
+			Content:   []byte(test.text),
+		}
 		got, err := mapper.Point(test.pos)
 		if err != nil && test.want != -1 {
 			t.Errorf("unexpected error: %v", err)
diff --git a/internal/lsp/protocol/span.go b/internal/lsp/protocol/span.go
index feb25b0..5c9c4d1 100644
--- a/internal/lsp/protocol/span.go
+++ b/internal/lsp/protocol/span.go
@@ -8,7 +8,6 @@
 
 import (
 	"fmt"
-	"go/token"
 
 	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
@@ -24,20 +23,6 @@
 	return string(uri)
 }
 
-func NewColumnMapper(uri span.URI, filename string, fset *token.FileSet, f *token.File, content []byte) *ColumnMapper {
-	var converter *span.TokenConverter
-	if f == nil {
-		converter = span.NewContentConverter(filename, content)
-	} else {
-		converter = span.NewTokenConverter(fset, f)
-	}
-	return &ColumnMapper{
-		URI:       uri,
-		Converter: converter,
-		Content:   content,
-	}
-}
-
 func (m *ColumnMapper) Location(s span.Span) (Location, error) {
 	rng, err := m.Range(s)
 	if err != nil {
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index 8ece709..c5a85c5 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -388,20 +388,10 @@
 			ph = h
 		}
 	}
-	file, err := ph.Cached(ctx)
+	file, m, err := ph.Cached(ctx)
 	if file == nil {
 		return nil, nil, err
 	}
-	data, _, err := ph.File().Read(ctx)
-	if err != nil {
-		return nil, nil, err
-	}
-	fset := view.Session().Cache().FileSet()
-	tok := fset.File(file.Pos())
-	if tok == nil {
-		return nil, nil, errors.Errorf("no token.File for %s", f.URI())
-	}
-	m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, tok, data)
 	spn, err := m.PointSpan(pos)
 	if err != nil {
 		return nil, nil, err
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index 89d4f2a..138b134 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -18,6 +18,7 @@
 	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/tag"
+	errors "golang.org/x/xerrors"
 )
 
 // formatCompletion creates a completion item for a given candidate.
@@ -124,10 +125,13 @@
 	}
 
 	uri := span.FileURI(pos.Filename)
-	_, file, pkg, err := c.pkg.FindFile(c.ctx, uri, obj.Pos())
+	_, file, pkg, err := c.pkg.FindFile(c.ctx, uri)
 	if err != nil {
 		return CompletionItem{}, err
 	}
+	if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
+		return CompletionItem{}, errors.Errorf("no file for %s", obj.Name())
+	}
 	ident, err := findIdentifier(c.ctx, c.view, []Package{pkg}, file, obj.Pos())
 	if err != nil {
 		return CompletionItem{}, err
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 5cb5559..76f4319 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -190,29 +190,22 @@
 	var (
 		fh   FileHandle
 		file *ast.File
+		m    *protocol.ColumnMapper
 		err  error
 	)
 	for _, ph := range pkg.GetHandles() {
 		if ph.File().Identity().URI == spn.URI() {
 			fh = ph.File()
-			file, err = ph.Cached(ctx)
+			file, m, err = ph.Cached(ctx)
 		}
 	}
 	if file == nil {
 		return protocol.Range{}, err
 	}
-	fset := view.Session().Cache().FileSet()
-	tok := fset.File(file.Pos())
-	if tok == nil {
-		return protocol.Range{}, errors.Errorf("no token.File for %s", spn.URI())
-	}
 	data, _, err := fh.Read(ctx)
 	if err != nil {
 		return protocol.Range{}, err
 	}
-	uri := fh.Identity().URI
-	m := protocol.NewColumnMapper(uri, uri.Filename(), fset, tok, data)
-
 	// Try to get a range for the diagnostic.
 	// TODO: Don't just limit ranges to type errors.
 	if spn.IsPoint() && isTypeError {
diff --git a/internal/lsp/source/folding_range.go b/internal/lsp/source/folding_range.go
index 8a36709..f427979 100644
--- a/internal/lsp/source/folding_range.go
+++ b/internal/lsp/source/folding_range.go
@@ -20,16 +20,11 @@
 	// TODO(suzmue): consider limiting the number of folding ranges returned, and
 	// implement a way to prioritize folding ranges in that case.
 	fh := f.Handle(ctx)
-	file, err := view.Session().Cache().ParseGoHandle(fh, ParseFull).Parse(ctx)
+	ph := view.Session().Cache().ParseGoHandle(fh, ParseFull)
+	file, m, err := ph.Parse(ctx)
 	if err != nil {
 		return nil, err
 	}
-	data, _, err := fh.Read(ctx)
-	if err != nil {
-		return nil, err
-	}
-	fset := view.Session().Cache().FileSet()
-	m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, fset.File(file.Pos()), data)
 
 	// Get folding ranges for comments separately as they are not walked by ast.Inspect.
 	ranges = append(ranges, commentsFoldingRange(view, m, file)...)
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index 033e546..3172224 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -8,9 +8,7 @@
 import (
 	"bytes"
 	"context"
-	"go/ast"
 	"go/format"
-	"go/token"
 
 	"golang.org/x/tools/go/packages"
 	"golang.org/x/tools/internal/imports"
@@ -34,12 +32,16 @@
 	if err != nil {
 		return nil, err
 	}
-	var file *ast.File
-	for _, ph := range pkg.GetHandles() {
-		if ph.File().Identity().URI == f.URI() {
-			file, err = ph.Cached(ctx)
+	var ph ParseGoHandle
+	for _, h := range pkg.GetHandles() {
+		if h.File().Identity().URI == f.URI() {
+			ph = h
 		}
 	}
+	if ph == nil {
+		return nil, err
+	}
+	file, m, err := ph.Parse(ctx)
 	if file == nil {
 		return nil, err
 	}
@@ -52,7 +54,7 @@
 		if err != nil {
 			return nil, err
 		}
-		return computeTextEdits(ctx, view.Session().Cache().FileSet(), f, string(formatted))
+		return computeTextEdits(ctx, ph.File(), m, string(formatted))
 	}
 
 	fset := view.Session().Cache().FileSet()
@@ -65,7 +67,7 @@
 	if err := format.Node(buf, fset, file); err != nil {
 		return nil, err
 	}
-	return computeTextEdits(ctx, view.Session().Cache().FileSet(), f, buf.String())
+	return computeTextEdits(ctx, ph.File(), m, buf.String())
 }
 
 func formatSource(ctx context.Context, file File) ([]byte, error) {
@@ -82,10 +84,6 @@
 func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]protocol.TextEdit, error) {
 	ctx, done := trace.StartSpan(ctx, "source.Imports")
 	defer done()
-	data, _, err := f.Handle(ctx).Read(ctx)
-	if err != nil {
-		return nil, err
-	}
 	pkg, err := f.GetPackage(ctx)
 	if err != nil {
 		return nil, err
@@ -93,7 +91,15 @@
 	if hasListErrors(pkg.GetErrors()) {
 		return nil, errors.Errorf("%s has list errors, not running goimports", f.URI())
 	}
-
+	var ph ParseGoHandle
+	for _, h := range pkg.GetHandles() {
+		if h.File().Identity().URI == f.URI() {
+			ph = h
+		}
+	}
+	if ph == nil {
+		return nil, err
+	}
 	options := &imports.Options{
 		// Defaults.
 		AllErrors:  true,
@@ -105,14 +111,22 @@
 	}
 	var formatted []byte
 	importFn := func(opts *imports.Options) error {
-		formatted, err = imports.Process(f.URI().Filename(), data, opts)
+		data, _, err := ph.File().Read(ctx)
+		if err != nil {
+			return err
+		}
+		formatted, err = imports.Process(ph.File().Identity().URI.Filename(), data, opts)
 		return err
 	}
 	err = view.RunProcessEnvFunc(ctx, importFn, options)
 	if err != nil {
 		return nil, err
 	}
-	return computeTextEdits(ctx, view.Session().Cache().FileSet(), f, string(formatted))
+	_, m, err := ph.Parse(ctx)
+	if m == nil {
+		return nil, err
+	}
+	return computeTextEdits(ctx, ph.File(), m, string(formatted))
 }
 
 type ImportFix struct {
@@ -132,11 +146,6 @@
 	if !ok {
 		return nil, nil, errors.Errorf("no imports fixes for non-Go files: %v", err)
 	}
-
-	data, _, err := f.Handle(ctx).Read(ctx)
-	if err != nil {
-		return nil, nil, err
-	}
 	pkg, err := gof.GetPackage(ctx)
 	if err != nil {
 		return nil, nil, err
@@ -154,6 +163,19 @@
 		TabWidth:   8,
 	}
 	importFn := func(opts *imports.Options) error {
+		var ph ParseGoHandle
+		for _, h := range pkg.GetHandles() {
+			if h.File().Identity().URI == f.URI() {
+				ph = h
+			}
+		}
+		if ph == nil {
+			return errors.Errorf("no ParseGoHandle for %s", f.URI())
+		}
+		data, _, err := ph.File().Read(ctx)
+		if err != nil {
+			return err
+		}
 		fixes, err := imports.FixImports(f.URI().Filename(), data, opts)
 		if err != nil {
 			return err
@@ -163,7 +185,11 @@
 		if err != nil {
 			return err
 		}
-		edits, err = computeTextEdits(ctx, view.Session().Cache().FileSet(), f, string(formatted))
+		_, m, err := ph.Parse(ctx)
+		if m == nil {
+			return err
+		}
+		edits, err = computeTextEdits(ctx, ph.File(), m, string(formatted))
 		if err != nil {
 			return err
 		}
@@ -174,7 +200,7 @@
 			if err != nil {
 				return err
 			}
-			edits, err := computeTextEdits(ctx, view.Session().Cache().FileSet(), f, string(formatted))
+			edits, err := computeTextEdits(ctx, ph.File(), m, string(formatted))
 			if err != nil {
 				return err
 			}
@@ -242,19 +268,16 @@
 	return false
 }
 
-func computeTextEdits(ctx context.Context, fset *token.FileSet, f File, formatted string) ([]protocol.TextEdit, error) {
+func computeTextEdits(ctx context.Context, fh FileHandle, m *protocol.ColumnMapper, formatted string) ([]protocol.TextEdit, error) {
 	ctx, done := trace.StartSpan(ctx, "source.computeTextEdits")
 	defer done()
 
-	data, _, err := f.Handle(ctx).Read(ctx)
+	data, _, err := fh.Read(ctx)
 	if err != nil {
 		return nil, err
 	}
-	edits := diff.ComputeEdits(f.URI(), string(data), formatted)
-	m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, nil, data)
-
+	edits := diff.ComputeEdits(fh.Identity().URI, string(data), formatted)
 	return ToProtocolEdits(m, edits)
-
 }
 
 func ToProtocolEdits(m *protocol.ColumnMapper, edits []diff.TextEdit) ([]protocol.TextEdit, error) {
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index 993b82e..8c63e4b 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -226,10 +226,13 @@
 
 func objToNode(ctx context.Context, view View, pkg Package, obj types.Object) (ast.Decl, error) {
 	uri := span.FileURI(view.Session().Cache().FileSet().Position(obj.Pos()).Filename)
-	_, declAST, _, err := pkg.FindFile(ctx, uri, obj.Pos())
+	_, declAST, _, err := pkg.FindFile(ctx, uri)
 	if declAST == nil {
 		return nil, err
 	}
+	if !(declAST.Pos() <= obj.Pos() && obj.Pos() <= declAST.End()) {
+		return nil, errors.Errorf("no file for %s", obj.Name())
+	}
 	path, _ := astutil.PathEnclosingInterval(declAST, obj.Pos(), obj.Pos())
 	if path == nil {
 		return nil, errors.Errorf("no path for object %v", obj.Name())
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
index e005a2d..6ba3331 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -178,7 +178,7 @@
 	}
 	for _, ph := range pkg.GetHandles() {
 		if ph.File().Identity().URI == i.File.File().Identity().URI {
-			file, err = ph.Cached(ctx)
+			file, _, err = ph.Cached(ctx)
 		}
 	}
 	if file == nil {
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 36de197..57c9a1c 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -488,10 +488,12 @@
 		}
 		data, _, err := f.Handle(ctx).Read(ctx)
 		if err != nil {
-			t.Error(err)
-			continue
+			t.Fatal(err)
 		}
-		m := protocol.NewColumnMapper(uri, filename, r.view.Session().Cache().FileSet(), nil, data)
+		m, err := r.data.Mapper(f.URI())
+		if err != nil {
+			t.Fatal(err)
+		}
 		diffEdits, err := source.FromProtocolEdits(m, edits)
 		if err != nil {
 			t.Error(err)
@@ -535,10 +537,12 @@
 		}
 		data, _, err := fh.Read(ctx)
 		if err != nil {
-			t.Error(err)
-			continue
+			t.Fatal(err)
 		}
-		m := protocol.NewColumnMapper(uri, filename, r.view.Session().Cache().FileSet(), nil, data)
+		m, err := r.data.Mapper(fh.Identity().URI)
+		if err != nil {
+			t.Fatal(err)
+		}
 		diffEdits, err := source.FromProtocolEdits(m, edits)
 		if err != nil {
 			t.Error(err)
@@ -717,12 +721,15 @@
 			if err != nil {
 				t.Fatalf("failed for %v: %v", spn, err)
 			}
-			data, _, err := f.Handle(ctx).Read(ctx)
+			fh := f.Handle(ctx)
+			data, _, err := fh.Read(ctx)
 			if err != nil {
-				t.Error(err)
-				continue
+				t.Fatal(err)
 			}
-			m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), r.data.Exported.ExpectFileSet, nil, data)
+			m, err := r.data.Mapper(fh.Identity().URI)
+			if err != nil {
+				t.Fatal(err)
+			}
 			filename := filepath.Base(editSpn.Filename())
 			diffEdits, err := source.FromProtocolEdits(m, edits)
 			if err != nil {
@@ -922,13 +929,12 @@
 	// This is a pure LSP feature, no source level functionality to be tested.
 }
 
-func spanToRange(data *tests.Data, span span.Span) (*protocol.ColumnMapper, protocol.Range, error) {
-	contents, err := data.Exported.FileContents(span.URI().Filename())
+func spanToRange(data *tests.Data, spn span.Span) (*protocol.ColumnMapper, protocol.Range, error) {
+	m, err := data.Mapper(spn.URI())
 	if err != nil {
 		return nil, protocol.Range{}, err
 	}
-	m := protocol.NewColumnMapper(span.URI(), span.URI().Filename(), data.Exported.ExpectFileSet, nil, contents)
-	srcRng, err := m.Range(span)
+	srcRng, err := m.Range(spn)
 	if err != nil {
 		return nil, protocol.Range{}, err
 	}
diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go
index e0f2f32..1360ea1 100644
--- a/internal/lsp/source/symbols.go
+++ b/internal/lsp/source/symbols.go
@@ -25,7 +25,7 @@
 	var file *ast.File
 	for _, ph := range pkg.GetHandles() {
 		if ph.File().Identity().URI == f.URI() {
-			file, err = ph.Cached(ctx)
+			file, _, err = ph.Cached(ctx)
 		}
 	}
 	if file == nil {
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index 1941598..3bdb955 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -51,7 +51,7 @@
 	return s.m.URI
 }
 
-// bestCheckPackageHandle picks the "narrowest" package for a given file.
+// bestPackage picks the "narrowest" package for a given file.
 //
 // By "narrowest" package, we mean the package with the fewest number of files
 // that includes the given file. This solves the problem of test variants,
@@ -126,20 +126,11 @@
 	if ph == nil {
 		return nil, nil, errors.Errorf("no ParseGoHandle for %s", uri)
 	}
-	file, err := ph.Cached(ctx)
+	file, m, err := ph.Cached(ctx)
 	if file == nil {
 		return nil, nil, err
 	}
-	data, _, err := ph.File().Read(ctx)
-	if err != nil {
-		return nil, nil, err
-	}
-	fset := view.Session().Cache().FileSet()
-	tok := fset.File(file.Pos())
-	if tok == nil {
-		return nil, nil, errors.Errorf("no token.File for %s", uri)
-	}
-	return file, protocol.NewColumnMapper(uri, uri.Filename(), fset, tok, data), nil
+	return file, m, nil
 }
 
 func builtinFileToMapper(ctx context.Context, view View, f GoFile, file *ast.File) (*ast.File, *protocol.ColumnMapper, error) {
@@ -148,12 +139,13 @@
 	if err != nil {
 		return nil, nil, err
 	}
-	fset := view.Session().Cache().FileSet()
-	tok := fset.File(file.Pos())
-	if tok == nil {
-		return nil, nil, errors.Errorf("no token.File for %s", f.URI())
+	converter := span.NewContentConverter(fh.Identity().URI.Filename(), data)
+	m := &protocol.ColumnMapper{
+		URI:       fh.Identity().URI,
+		Content:   data,
+		Converter: converter,
 	}
-	return nil, protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, tok, data), nil
+	return nil, m, nil
 }
 
 func IsGenerated(ctx context.Context, view View, uri span.URI) bool {
@@ -162,7 +154,7 @@
 		return false
 	}
 	ph := view.Session().Cache().ParseGoHandle(f.Handle(ctx), ParseHeader)
-	parsed, err := ph.Parse(ctx)
+	parsed, _, err := ph.Parse(ctx)
 	if parsed == nil {
 		return false
 	}
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 1d8b61d..998a9c9 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -80,10 +80,10 @@
 
 	// Parse returns the parsed AST for the file.
 	// If the file is not available, returns nil and an error.
-	Parse(ctx context.Context) (*ast.File, error)
+	Parse(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error)
 
 	// Cached returns the AST for this handle, if it has already been stored.
-	Cached(ctx context.Context) (*ast.File, error)
+	Cached(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error)
 }
 
 // ParseMode controls the content of the AST produced when parsing a source file.
@@ -321,5 +321,5 @@
 
 	// FindFile returns the AST and type information for a file that may
 	// belong to or be part of a dependency of the given package.
-	FindFile(ctx context.Context, uri span.URI, pos token.Pos) (ParseGoHandle, *ast.File, Package, error)
+	FindFile(ctx context.Context, uri span.URI) (ParseGoHandle, *ast.File, Package, error)
 }
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index ffa4900..9a97b03 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -15,6 +15,7 @@
 	"path/filepath"
 	"sort"
 	"strings"
+	"sync"
 	"testing"
 
 	"golang.org/x/tools/go/expect"
@@ -99,6 +100,9 @@
 	fragments map[string]string
 	dir       string
 	golden    map[string]*Golden
+
+	mappersMu sync.Mutex
+	mappers   map[span.URI]*protocol.ColumnMapper
 }
 
 type Tests interface {
@@ -184,6 +188,7 @@
 		dir:       dir,
 		fragments: map[string]string{},
 		golden:    map[string]*Golden{},
+		mappers:   map[span.URI]*protocol.ColumnMapper{},
 	}
 
 	files := packagestest.MustCopyFileTree(dir)
@@ -426,6 +431,25 @@
 	}
 }
 
+func (data *Data) Mapper(uri span.URI) (*protocol.ColumnMapper, error) {
+	data.mappersMu.Lock()
+	defer data.mappersMu.Unlock()
+
+	if _, ok := data.mappers[uri]; !ok {
+		content, err := data.Exported.FileContents(uri.Filename())
+		if err != nil {
+			return nil, err
+		}
+		converter := span.NewContentConverter(uri.Filename(), content)
+		data.mappers[uri] = &protocol.ColumnMapper{
+			URI:       uri,
+			Converter: converter,
+			Content:   content,
+		}
+	}
+	return data.mappers[uri], nil
+}
+
 func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte {
 	data.t.Helper()
 	fragment, found := data.fragments[target]
@@ -672,20 +696,18 @@
 		// make the range just be the start.
 		rng = span.NewRange(rng.FileSet, rng.Start, rng.Start)
 	}
-	contents, err := data.Exported.FileContents(src.URI().Filename())
+	m, err := data.Mapper(src.URI())
 	if err != nil {
-		return
+		data.t.Fatal(err)
 	}
-	m := protocol.NewColumnMapper(src.URI(), src.URI().Filename(), data.Exported.ExpectFileSet, nil, contents)
-
 	// Convert range to span and then to protocol.Range.
 	spn, err := rng.Span()
 	if err != nil {
-		return
+		data.t.Fatal(err)
 	}
 	prng, err := m.Range(spn)
 	if err != nil {
-		return
+		data.t.Fatal(err)
 	}
 	data.PrepareRenames[src] = &source.PrepareItem{
 		Range: prng,
@@ -694,14 +716,13 @@
 }
 
 func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string) {
-	contents, err := data.Exported.FileContents(spn.URI().Filename())
+	m, err := data.Mapper(spn.URI())
 	if err != nil {
-		return
+		data.t.Fatal(err)
 	}
-	m := protocol.NewColumnMapper(spn.URI(), spn.URI().Filename(), data.Exported.ExpectFileSet, nil, contents)
 	rng, err := m.Range(spn)
 	if err != nil {
-		return
+		data.t.Fatal(err)
 	}
 	sym := protocol.DocumentSymbol{
 		Name:           name,
diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go
index 9f622b5..76ba2e8 100644
--- a/internal/lsp/text_synchronization.go
+++ b/internal/lsp/text_synchronization.go
@@ -109,10 +109,14 @@
 	if err != nil {
 		return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found (%v)", err)
 	}
-	fset := s.session.Cache().FileSet()
 	for _, change := range changes {
 		// Update column mapper along with the content.
-		m := protocol.NewColumnMapper(uri, uri.Filename(), fset, nil, content)
+		converter := span.NewContentConverter(uri.Filename(), content)
+		m := &protocol.ColumnMapper{
+			URI:       uri,
+			Converter: converter,
+			Content:   content,
+		}
 
 		spn, err := m.RangeSpan(*change.Range)
 		if err != nil {