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 {