gopls/internal/cmd: move span.{Span,Point} to cmd

These types are only used by the command-line interface.
Only URI remains in the span package.

Mapper exposes the UTF-8 conversions using the more
flexible (line, col8 int) notation without using span.

Details:
- span.Span -> cmd.Span   (and span  -> _span)
- span.Point -> cmd.Point (and point -> _point)
- span.{Compare,Sort}Spans -> cmd.{compare,sort}Spans
- remove SetRange hack in packagestest since spans are no longer used by tests.
- span.Parse -> cmd.parseSpan. Remove ParseInDir (always ".")
- remove global var weirdness from cmd/semantictokens.go.
- fix outer/inner mistake in marker test.
- protocol.Mapper.SpanLocation (et al) are now cmd.cmdfile.spanLocation (etc)

Change-Id: Icd3758f975018cd191e1e54f8ddcd5bc0db18c45
Reviewed-on: https://go-review.googlesource.com/c/tools/+/542155
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Alan Donovan 2023-11-14 01:08:40 -05:00 коммит произвёл Gopher Robot
Родитель 944d4e7334
Коммит 3292b366da
27 изменённых файлов: 291 добавлений и 369 удалений

Просмотреть файл

@ -128,21 +128,6 @@ type Range struct {
Start, End token.Pos // both valid and within range of TokFile
}
// A rangeSetter abstracts a variable that can be set from a Range value.
//
// The parameter conversion machinery will automatically construct a
// variable of type T and call the SetRange method on its address if
// *T implements rangeSetter. This allows alternative notations of
// source ranges to interoperate transparently with this package.
//
// This type intentionally does not mention Range itself, to avoid a
// dependency from the application's range type upon this package.
//
// Currently this is a secret back door for use only by gopls.
type rangeSetter interface {
SetRange(file *token.File, start, end token.Pos)
}
// Mark adds a new marker to the known set.
func (e *Exported) Mark(name string, r Range) {
if e.markers == nil {
@ -243,15 +228,14 @@ func (e *Exported) getMarkers() error {
}
var (
noteType = reflect.TypeOf((*expect.Note)(nil))
identifierType = reflect.TypeOf(expect.Identifier(""))
posType = reflect.TypeOf(token.Pos(0))
positionType = reflect.TypeOf(token.Position{})
rangeType = reflect.TypeOf(Range{})
rangeSetterType = reflect.TypeOf((*rangeSetter)(nil)).Elem()
fsetType = reflect.TypeOf((*token.FileSet)(nil))
regexType = reflect.TypeOf((*regexp.Regexp)(nil))
exportedType = reflect.TypeOf((*Exported)(nil))
noteType = reflect.TypeOf((*expect.Note)(nil))
identifierType = reflect.TypeOf(expect.Identifier(""))
posType = reflect.TypeOf(token.Pos(0))
positionType = reflect.TypeOf(token.Position{})
rangeType = reflect.TypeOf(Range{})
fsetType = reflect.TypeOf((*token.FileSet)(nil))
regexType = reflect.TypeOf((*regexp.Regexp)(nil))
exportedType = reflect.TypeOf((*Exported)(nil))
)
// converter converts from a marker's argument parsed from the comment to
@ -310,17 +294,6 @@ func (e *Exported) buildConverter(pt reflect.Type) (converter, error) {
}
return reflect.ValueOf(r), remains, nil
}, nil
case reflect.PtrTo(pt).AssignableTo(rangeSetterType):
// (*pt).SetRange method exists: call it.
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
r, remains, err := e.rangeConverter(n, args)
if err != nil {
return reflect.Value{}, nil, err
}
v := reflect.New(pt)
v.Interface().(rangeSetter).SetRange(r.TokFile, r.Start, r.End)
return v.Elem(), remains, nil
}, nil
case pt == identifierType:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
if len(args) < 1 {

Просмотреть файл

@ -11,7 +11,6 @@ import (
"strings"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -46,13 +45,13 @@ func (c *callHierarchy) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
file, err := conn.openFile(ctx, from.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(from)
loc, err := file.spanLocation(from)
if err != nil {
return err
}
@ -115,7 +114,7 @@ func callItemPrintString(ctx context.Context, conn *connection, item protocol.Ca
if err != nil {
return "", err
}
itemSpan, err := itemFile.mapper.RangeSpan(item.Range)
itemSpan, err := itemFile.rangeSpan(item.Range)
if err != nil {
return "", err
}
@ -127,7 +126,7 @@ func callItemPrintString(ctx context.Context, conn *connection, item protocol.Ca
return "", err
}
for _, rng := range calls {
call, err := callsFile.mapper.RangeSpan(rng)
call, err := callsFile.rangeSpan(rng)
if err != nil {
return "", err
}

Просмотреть файл

@ -62,7 +62,7 @@ func (c *check) Run(ctx context.Context, args ...string) error {
for _, file := range checking {
for _, d := range file.diagnostics {
spn, err := file.mapper.RangeSpan(d.Range)
spn, err := file.rangeSpan(d.Range)
if err != nil {
return fmt.Errorf("Could not convert position %v for %q", d.Range, d.Message)
}

Просмотреть файл

@ -13,6 +13,7 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
@ -20,6 +21,7 @@ import (
"text/tabwriter"
"time"
bugpkg "golang.org/x/tools/gopls/internal/bug"
"golang.org/x/tools/gopls/internal/lsp"
"golang.org/x/tools/gopls/internal/lsp/browser"
"golang.org/x/tools/gopls/internal/lsp/cache"
@ -800,3 +802,95 @@ func (c *connection) terminate(ctx context.Context) {
func (c *cmdClient) Close() error {
return nil
}
// -- conversions to span (UTF-8) domain --
// locationSpan converts a protocol (UTF-16) Location to a (UTF-8) span.
// Precondition: the URIs of Location and Mapper match.
func (f *cmdFile) locationSpan(loc protocol.Location) (Span, error) {
// TODO(adonovan): check that l.URI matches m.URI.
return f.rangeSpan(loc.Range)
}
// rangeSpan converts a protocol (UTF-16) range to a (UTF-8) span.
// The resulting span has valid Positions and Offsets.
func (f *cmdFile) rangeSpan(r protocol.Range) (Span, error) {
start, end, err := f.mapper.RangeOffsets(r)
if err != nil {
return Span{}, err
}
return f.offsetSpan(start, end)
}
// offsetSpan converts a byte-offset interval to a (UTF-8) span.
// The resulting span contains line, column, and offset information.
func (f *cmdFile) offsetSpan(start, end int) (Span, error) {
if start > end {
return Span{}, fmt.Errorf("start offset (%d) > end (%d)", start, end)
}
startPoint, err := offsetPoint(f.mapper, start)
if err != nil {
return Span{}, fmt.Errorf("start: %v", err)
}
endPoint, err := offsetPoint(f.mapper, end)
if err != nil {
return Span{}, fmt.Errorf("end: %v", err)
}
return newSpan(f.mapper.URI, startPoint, endPoint), nil
}
// offsetPoint converts a byte offset to a span (UTF-8) point.
// The resulting point contains line, column, and offset information.
func offsetPoint(m *protocol.Mapper, offset int) (point, error) {
if !(0 <= offset && offset <= len(m.Content)) {
return point{}, fmt.Errorf("invalid offset %d (want 0-%d)", offset, len(m.Content))
}
line, col8 := m.OffsetLineCol8(offset)
return newPoint(line, col8, offset), nil
}
// -- conversions from span (UTF-8) domain --
// spanLocation converts a (UTF-8) span to a protocol (UTF-16) range.
// Precondition: the URIs of spanLocation and Mapper match.
func (f *cmdFile) spanLocation(s Span) (protocol.Location, error) {
rng, err := f.spanRange(s)
if err != nil {
return protocol.Location{}, err
}
return f.mapper.RangeLocation(rng), nil
}
// spanRange converts a (UTF-8) span to a protocol (UTF-16) range.
// Precondition: the URIs of Span and Mapper match.
func (f *cmdFile) spanRange(s Span) (protocol.Range, error) {
// Assert that we aren't using the wrong mapper.
// We check only the base name, and case insensitively,
// because we can't assume clean paths, no symbolic links,
// case-sensitive directories. The authoritative answer
// requires querying the file system, and we don't want
// to do that.
if !strings.EqualFold(filepath.Base(string(f.mapper.URI)), filepath.Base(string(s.URI()))) {
return protocol.Range{}, bugpkg.Errorf("mapper is for file %q instead of %q", f.mapper.URI, s.URI())
}
start, err := pointPosition(f.mapper, s.Start())
if err != nil {
return protocol.Range{}, fmt.Errorf("start: %w", err)
}
end, err := pointPosition(f.mapper, s.End())
if err != nil {
return protocol.Range{}, fmt.Errorf("end: %w", err)
}
return protocol.Range{Start: start, End: end}, nil
}
// pointPosition converts a valid span (UTF-8) point to a protocol (UTF-16) position.
func pointPosition(m *protocol.Mapper, p point) (protocol.Position, error) {
if p.HasPosition() {
return m.LineCol8Position(p.Line(), p.Column())
}
if p.HasOffset() {
return m.OffsetPosition(p.Offset())
}
return protocol.Position{}, fmt.Errorf("point has neither offset nor line/column")
}

Просмотреть файл

@ -12,7 +12,6 @@ import (
"golang.org/x/tools/gopls/internal/lsp/command"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/source"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -107,12 +106,12 @@ func (r *codelens) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
filespan := span.Parse(filename)
filespan := parseSpan(filename)
file, err := conn.openFile(ctx, filespan.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(filespan)
loc, err := file.spanLocation(filespan)
if err != nil {
return err
}
@ -126,7 +125,7 @@ func (r *codelens) Run(ctx context.Context, args ...string) error {
}
for _, lens := range lenses {
sp, err := file.mapper.RangeSpan(lens.Range)
sp, err := file.rangeSpan(lens.Range)
if err != nil {
return nil
}

Просмотреть файл

@ -14,14 +14,13 @@ import (
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/source"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
// A Definition is the result of a 'definition' query.
type Definition struct {
Span span.Span `json:"span"` // span of the definition
Description string `json:"description"` // description of the denoted object
Span Span `json:"span"` // span of the definition
Description string `json:"description"` // description of the denoted object
}
// These constant is printed in the help, and then used in a test to verify the
@ -79,12 +78,12 @@ func (d *definition) Run(ctx context.Context, args ...string) error {
return err
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
file, err := conn.openFile(ctx, from.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(from)
loc, err := file.spanLocation(from)
if err != nil {
return err
}
@ -103,7 +102,7 @@ func (d *definition) Run(ctx context.Context, args ...string) error {
if err != nil {
return fmt.Errorf("%v: %v", from, err)
}
definition, err := file.mapper.LocationSpan(locs[0])
definition, err := file.locationSpan(locs[0])
if err != nil {
return fmt.Errorf("%v: %v", from, err)
}

Просмотреть файл

@ -10,7 +10,6 @@ import (
"fmt"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -43,7 +42,7 @@ func (r *foldingRanges) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
if _, err := conn.openFile(ctx, from.URI()); err != nil {
return err
}

Просмотреть файл

@ -10,7 +10,6 @@ import (
"fmt"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
)
// format implements the format verb for gopls.
@ -49,12 +48,12 @@ func (c *format) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
for _, arg := range args {
spn := span.Parse(arg)
spn := parseSpan(arg)
file, err := conn.openFile(ctx, spn.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(spn)
loc, err := file.spanLocation(spn)
if err != nil {
return err
}

Просмотреть файл

@ -10,7 +10,6 @@ import (
"fmt"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -45,13 +44,13 @@ func (r *highlight) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
file, err := conn.openFile(ctx, from.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(from)
loc, err := file.spanLocation(from)
if err != nil {
return err
}
@ -64,16 +63,16 @@ func (r *highlight) Run(ctx context.Context, args ...string) error {
return err
}
var results []span.Span
var results []Span
for _, h := range highlights {
s, err := file.mapper.RangeSpan(h.Range)
s, err := file.rangeSpan(h.Range)
if err != nil {
return err
}
results = append(results, s)
}
// Sort results to make tests deterministic since DocumentHighlight uses a map.
span.SortSpans(results)
sortSpans(results)
for _, s := range results {
fmt.Println(s)

Просмотреть файл

@ -11,7 +11,6 @@ import (
"sort"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -46,13 +45,13 @@ func (i *implementation) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
file, err := conn.openFile(ctx, from.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(from)
loc, err := file.spanLocation(from)
if err != nil {
return err
}
@ -71,7 +70,7 @@ func (i *implementation) Run(ctx context.Context, args ...string) error {
if err != nil {
return err
}
span, err := f.mapper.LocationSpan(impl)
span, err := f.locationSpan(impl)
if err != nil {
return err
}

Просмотреть файл

@ -10,7 +10,6 @@ import (
"fmt"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -50,7 +49,7 @@ func (t *imports) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
uri := from.URI()
file, err := conn.openFile(ctx, uri)
if err != nil {

Просмотреть файл

@ -12,7 +12,6 @@ import (
"os"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -51,7 +50,7 @@ func (l *links) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
uri := from.URI()
if _, err := conn.openFile(ctx, uri); err != nil {

Просмотреть файл

@ -2,33 +2,23 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package span
package cmd
import (
"path/filepath"
"strconv"
"strings"
"unicode/utf8"
"golang.org/x/tools/gopls/internal/span"
)
// Parse returns the location represented by the input.
// parseSpan returns the location represented by the input.
// Only file paths are accepted, not URIs.
// The returned span will be normalized, and thus if printed may produce a
// different string.
//
// TODO(adonovan): used only from cmd package; move there and simplify.
func Parse(input string) Span {
return parseInDir(input, ".")
}
func parseSpan(input string) Span {
uri := span.URIFromPath
// parseInDir is like Parse, but interprets paths relative to wd.
func parseInDir(input, wd string) Span {
uri := func(path string) URI {
if !filepath.IsAbs(path) {
path = filepath.Join(wd, path)
}
return URIFromPath(path)
}
// :0:0#0-0:0#0
valid := input
var hold, offset int
@ -46,19 +36,19 @@ func parseInDir(input, wd string) Span {
}
switch {
case suf.sep == ":":
return New(uri(suf.remains), NewPoint(suf.num, hold, offset), Point{})
return newSpan(uri(suf.remains), newPoint(suf.num, hold, offset), point{})
case suf.sep == "-":
// we have a span, fall out of the case to continue
default:
// separator not valid, rewind to either the : or the start
return New(uri(valid), NewPoint(hold, 0, offset), Point{})
return newSpan(uri(valid), newPoint(hold, 0, offset), point{})
}
// only the span form can get here
// at this point we still don't know what the numbers we have mean
// if have not yet seen a : then we might have either a line or a column depending
// on whether start has a column or not
// we build an end point and will fix it later if needed
end := NewPoint(suf.num, hold, offset)
end := newPoint(suf.num, hold, offset)
hold, offset = 0, 0
suf = rstripSuffix(suf.remains)
if suf.sep == "#" {
@ -67,20 +57,20 @@ func parseInDir(input, wd string) Span {
}
if suf.sep != ":" {
// turns out we don't have a span after all, rewind
return New(uri(valid), end, Point{})
return newSpan(uri(valid), end, point{})
}
valid = suf.remains
hold = suf.num
suf = rstripSuffix(suf.remains)
if suf.sep != ":" {
// line#offset only
return New(uri(valid), NewPoint(hold, 0, offset), end)
return newSpan(uri(valid), newPoint(hold, 0, offset), end)
}
// we have a column, so if end only had one number, it is also the column
if !hadCol {
end = NewPoint(suf.num, end.v.Line, end.v.Offset)
end = newPoint(suf.num, end.v.Line, end.v.Offset)
}
return New(uri(suf.remains), NewPoint(suf.num, hold, offset), end)
return newSpan(uri(suf.remains), newPoint(suf.num, hold, offset), end)
}
type suffix struct {

Просмотреть файл

@ -11,7 +11,6 @@ import (
"fmt"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -50,12 +49,12 @@ func (r *prepareRename) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
file, err := conn.openFile(ctx, from.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(from)
loc, err := file.spanLocation(from)
if err != nil {
return err
}
@ -70,7 +69,7 @@ func (r *prepareRename) Run(ctx context.Context, args ...string) error {
return ErrInvalidRenamePosition
}
s, err := file.mapper.RangeSpan(result.Range)
s, err := file.rangeSpan(result.Range)
if err != nil {
return err
}

Просмотреть файл

@ -11,7 +11,6 @@ import (
"sort"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -50,12 +49,12 @@ func (r *references) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
file, err := conn.openFile(ctx, from.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(from)
loc, err := file.spanLocation(from)
if err != nil {
return err
}
@ -77,7 +76,7 @@ func (r *references) Run(ctx context.Context, args ...string) error {
}
// convert location to span for user-friendly 1-indexed line
// and column numbers
span, err := f.mapper.LocationSpan(l)
span, err := f.locationSpan(l)
if err != nil {
return err
}

Просмотреть файл

@ -10,7 +10,6 @@ import (
"fmt"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -52,12 +51,12 @@ func (r *rename) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
file, err := conn.openFile(ctx, from.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(from)
loc, err := file.spanLocation(from)
if err != nil {
return err
}

Просмотреть файл

@ -9,8 +9,6 @@ import (
"context"
"flag"
"fmt"
"go/parser"
"go/token"
"log"
"os"
"unicode/utf8"
@ -47,8 +45,6 @@ type semtok struct {
app *Application
}
var colmap *protocol.Mapper
func (c *semtok) Name() string { return "semtok" }
func (c *semtok) Parent() string { return c.app.Name() }
func (c *semtok) Usage() string { return "<filename>" }
@ -85,11 +81,7 @@ func (c *semtok) Run(ctx context.Context, args ...string) error {
return err
}
buf, err := os.ReadFile(args[0])
if err != nil {
return err
}
lines := bytes.Split(buf, []byte{'\n'})
lines := bytes.Split(file.mapper.Content, []byte{'\n'})
p := &protocol.SemanticTokensRangeParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: protocol.URIFromSpanURI(uri),
@ -104,23 +96,7 @@ func (c *semtok) Run(ctx context.Context, args ...string) error {
if err != nil {
return err
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, args[0], buf, 0)
if err != nil {
log.Printf("parsing %s failed %v", args[0], err)
return err
}
tok := fset.File(f.Pos())
if tok == nil {
// can't happen; just parsed this file
return fmt.Errorf("can't find %s in fset", args[0])
}
colmap = protocol.NewMapper(uri, buf)
err = decorate(file.uri.Filename(), resp.Data)
if err != nil {
return err
}
return nil
return decorate(file, resp.Data)
}
type mark struct {
@ -159,16 +135,12 @@ func markLine(m mark, lines [][]byte) {
lines[m.line-1] = l
}
func decorate(file string, result []uint32) error {
buf, err := os.ReadFile(file)
if err != nil {
return err
}
marks := newMarks(result)
func decorate(file *cmdFile, result []uint32) error {
marks := newMarks(file, result)
if len(marks) == 0 {
return nil
}
lines := bytes.Split(buf, []byte{'\n'})
lines := bytes.Split(file.mapper.Content, []byte{'\n'})
for i := len(marks) - 1; i >= 0; i-- {
mx := marks[i]
markLine(mx, lines)
@ -177,7 +149,7 @@ func decorate(file string, result []uint32) error {
return nil
}
func newMarks(d []uint32) []mark {
func newMarks(file *cmdFile, d []uint32) []mark {
ans := []mark{}
// the following two loops could be merged, at the cost
// of making the logic slightly more complicated to understand
@ -206,7 +178,7 @@ func newMarks(d []uint32) []mark {
Character: lspChar[i] + d[5*i+2],
},
}
spn, err := colmap.RangeSpan(pr)
spn, err := file.rangeSpan(pr)
if err != nil {
log.Fatal(err)
}

Просмотреть файл

@ -10,7 +10,6 @@ import (
"fmt"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -45,13 +44,13 @@ func (r *signature) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
file, err := conn.openFile(ctx, from.URI())
if err != nil {
return err
}
loc, err := file.mapper.SpanLocation(from)
loc, err := file.spanLocation(from)
if err != nil {
return err
}

Просмотреть файл

@ -2,19 +2,18 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package span contains support for representing with positions and ranges in
// text files.
package span
package cmd
// Span and point represent positions and ranges in text files.
import (
"encoding/json"
"fmt"
"go/token"
"path"
"sort"
"strings"
"golang.org/x/tools/gopls/internal/lsp/safetoken"
"golang.org/x/tools/gopls/internal/span"
)
// A Span represents a range of text within a source file. The start
@ -30,54 +29,54 @@ import (
// (UTF-16). The latter requires access to file contents.
//
// See overview comments at ../lsp/protocol/mapper.go.
//
// TODO(adonovan): unexport, once "span" package is renamed.
// And perhaps rename to cmd.{range,point} to match protocol.{Range,Point}?
type Span struct {
v span
v _span
}
// Point represents a single point within a file.
// point represents a single point within a file.
// In general this should only be used as part of a Span, as on its own it
// does not carry enough information.
type Point struct {
v point
type point struct {
v _point
}
// The private span/point types have public fields to support JSON
// encoding, but the public Span/Point types hide these fields by
// encoding, but the public Span/point types hide these fields by
// defining methods that shadow them. (This is used by a few of the
// command-line tool subcommands, which emit spans and have a -json
// flag.)
type span struct {
URI URI `json:"uri"`
Start point `json:"start"`
End point `json:"end"`
// TODO(adonovan): simplify now that it's all internal to cmd.
type _span struct {
URI span.URI `json:"uri"`
Start _point `json:"start"`
End _point `json:"end"`
}
type point struct {
type _point struct {
Line int `json:"line"` // 1-based line number
Column int `json:"column"` // 1-based, UTF-8 codes (bytes)
Offset int `json:"offset"` // 0-based byte offset
}
// Invalid is a span that reports false from IsValid
var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}}
var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}}
func New(uri URI, start, end Point) Span {
s := Span{v: span{URI: uri, Start: start.v, End: end.v}}
func newSpan(uri span.URI, start, end point) Span {
s := Span{v: _span{URI: uri, Start: start.v, End: end.v}}
s.v.clean()
return s
}
func NewPoint(line, col, offset int) Point {
p := Point{v: point{Line: line, Column: col, Offset: offset}}
func newPoint(line, col, offset int) point {
p := point{v: _point{Line: line, Column: col, Offset: offset}}
p.v.clean()
return p
}
// SortSpans sorts spans into a stable but unspecified order.
func SortSpans(spans []Span) {
// sortSpans sorts spans into a stable but unspecified order.
func sortSpans(spans []Span) {
sort.SliceStable(spans, func(i, j int) bool {
return compare(spans[i], spans[j]) < 0
})
@ -97,7 +96,7 @@ func compare(a, b Span) int {
return comparePoint(a.v.End, b.v.End)
}
func comparePoint(a, b point) int {
func comparePoint(a, b _point) int {
if !a.hasPosition() {
if a.Offset < b.Offset {
return -1
@ -126,51 +125,51 @@ func (s Span) HasPosition() bool { return s.v.Start.hasPosition() }
func (s Span) HasOffset() bool { return s.v.Start.hasOffset() }
func (s Span) IsValid() bool { return s.v.Start.isValid() }
func (s Span) IsPoint() bool { return s.v.Start == s.v.End }
func (s Span) URI() URI { return s.v.URI }
func (s Span) Start() Point { return Point{s.v.Start} }
func (s Span) End() Point { return Point{s.v.End} }
func (s Span) URI() span.URI { return s.v.URI }
func (s Span) Start() point { return point{s.v.Start} }
func (s Span) End() point { return point{s.v.End} }
func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) }
func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) }
func (p Point) HasPosition() bool { return p.v.hasPosition() }
func (p Point) HasOffset() bool { return p.v.hasOffset() }
func (p Point) IsValid() bool { return p.v.isValid() }
func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) }
func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) }
func (p Point) Line() int {
func (p point) HasPosition() bool { return p.v.hasPosition() }
func (p point) HasOffset() bool { return p.v.hasOffset() }
func (p point) IsValid() bool { return p.v.isValid() }
func (p *point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) }
func (p *point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) }
func (p point) Line() int {
if !p.v.hasPosition() {
panic(fmt.Errorf("position not set in %v", p.v))
}
return p.v.Line
}
func (p Point) Column() int {
func (p point) Column() int {
if !p.v.hasPosition() {
panic(fmt.Errorf("position not set in %v", p.v))
}
return p.v.Column
}
func (p Point) Offset() int {
func (p point) Offset() int {
if !p.v.hasOffset() {
panic(fmt.Errorf("offset not set in %v", p.v))
}
return p.v.Offset
}
func (p point) hasPosition() bool { return p.Line > 0 }
func (p point) hasOffset() bool { return p.Offset >= 0 }
func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() }
func (p point) isZero() bool {
func (p _point) hasPosition() bool { return p.Line > 0 }
func (p _point) hasOffset() bool { return p.Offset >= 0 }
func (p _point) isValid() bool { return p.hasPosition() || p.hasOffset() }
func (p _point) isZero() bool {
return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0)
}
func (s *span) clean() {
func (s *_span) clean() {
//this presumes the points are already clean
if !s.End.isValid() || (s.End == point{}) {
if !s.End.isValid() || (s.End == _point{}) {
s.End = s.Start
}
}
func (p *point) clean() {
func (p *_point) clean() {
if p.Line < 0 {
p.Line = 0
}
@ -187,7 +186,11 @@ func (p *point) clean() {
}
// Format implements fmt.Formatter to print the Location in a standard form.
// The format produced is one that can be read back in using Parse.
// The format produced is one that can be read back in using parseSpan.
//
// TODO(adonovan): this is esoteric, and the formatting options are
// never used outside of TestFormat. Replace with something simpler
// along the lines of MappedRange.String.
func (s Span) Format(f fmt.State, c rune) {
fullForm := f.Flag('+')
preferOffset := f.Flag('#')
@ -237,13 +240,3 @@ func (s Span) Format(f fmt.State, c rune) {
fmt.Fprintf(f, "#%d", s.v.End.Offset)
}
}
// SetRange implements packagestest.rangeSetter, allowing
// gopls' test suites to use Spans instead of Range in parameters.
func (span *Span) SetRange(file *token.File, start, end token.Pos) {
point := func(pos token.Pos) Point {
posn := safetoken.Position(file, pos)
return NewPoint(posn.Line, posn.Column, posn.Offset)
}
*span = New(URIFromPath(file.Name()), point(start), point(end))
}

Просмотреть файл

@ -2,23 +2,21 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package span_test
package cmd
import (
"fmt"
"path/filepath"
"strings"
"testing"
"golang.org/x/tools/gopls/internal/span"
)
func TestFormat(t *testing.T) {
func TestSpanFormat(t *testing.T) {
formats := []string{"%v", "%#v", "%+v"}
// Element 0 is the input, and the elements 0-2 are the expected
// output in [%v %#v %+v] formats. Thus the first must be in
// canonical form (invariant under span.Parse + fmt.Sprint).
// canonical form (invariant under parseSpan + fmt.Sprint).
// The '#' form displays offsets; the '+' form outputs a URI.
// If len=4, element 0 is a noncanonical input and 1-3 are expected outputs.
for _, test := range [][]string{
@ -35,7 +33,7 @@ func TestFormat(t *testing.T) {
{"C:/file_h:3:7#26-4:8#37", // not canonical
"C:/file_h:3:7-4:8", "C:/file_h:#26-#37", "file:///C:/file_h:3:7#26-4:8#37"}} {
input := test[0]
spn := span.Parse(input)
spn := parseSpan(input)
wants := test[0:3]
if len(test) == 4 {
wants = test[1:4]

Просмотреть файл

@ -81,13 +81,13 @@ func (s *suggestedFix) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
uri := from.URI()
file, err := conn.openFile(ctx, uri)
if err != nil {
return err
}
rng, err := file.mapper.SpanRange(from)
rng, err := file.spanRange(from)
if err != nil {
return err
}

Просмотреть файл

@ -12,7 +12,6 @@ import (
"sort"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -43,7 +42,7 @@ func (r *symbols) Run(ctx context.Context, args ...string) error {
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
from := parseSpan(args[0])
p := protocol.DocumentSymbolParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: protocol.URIFromSpanURI(from.URI()),

Просмотреть файл

@ -78,7 +78,7 @@ func (r *workspaceSymbol) Run(ctx context.Context, args ...string) error {
if err != nil {
return err
}
span, err := f.mapper.LocationSpan(s.Location)
span, err := f.locationSpan(s.Location)
if err != nil {
return err
}

Просмотреть файл

@ -29,10 +29,10 @@ package protocol
// recovery whose computed positions are out of bounds (EOF+1).
// - #41029, whereby the wrong line number is returned for the EOF position.
//
// 3. the span package.
// 3. the cmd package.
//
// span.Point = (line, col8, offset).
// span.Span = (uri URI, start, end span.Point)
// cmd.point = (line, col8, offset).
// cmd.Span = (uri URI, start, end cmd.point)
//
// Line and column are 1-based.
// Columns are measured in bytes (UTF-8 codes).
@ -44,9 +44,6 @@ package protocol
// they are also useful for parsing user-provided positions (e.g. in
// the CLI) before we have access to file contents.
//
// TODO(adonovan): all the span-based methods of Mapper are now
// used only by gopls/internal/cmd. Move them into that package.
//
// 4. protocol, the LSP RPC message format.
//
// protocol.Position = (Line, Character uint32)
@ -57,7 +54,7 @@ package protocol
// Characters (columns) are measured in UTF-16 codes.
//
// protocol.Mapper holds the (URI, Content) of a file, enabling
// efficient mapping between byte offsets, span ranges, and
// efficient mapping between byte offsets, cmd ranges, and
// protocol ranges.
//
// protocol.MappedRange holds a protocol.Mapper and valid (start,
@ -69,13 +66,11 @@ import (
"fmt"
"go/ast"
"go/token"
"path/filepath"
"sort"
"strings"
"sync"
"unicode/utf8"
"golang.org/x/tools/gopls/internal/bug"
"golang.org/x/tools/gopls/internal/lsp/safetoken"
"golang.org/x/tools/gopls/internal/span"
)
@ -84,7 +79,7 @@ import (
// between byte offsets and notations of position such as:
//
// - (line, col8) pairs, where col8 is a 1-based UTF-8 column number
// (bytes), as used by the go/token and span packages.
// (bytes), as used by the go/token and cmd packages.
//
// - (line, col16) pairs, where col16 is a 1-based UTF-16 column
// number, as used by the LSP protocol.
@ -134,73 +129,28 @@ func (m *Mapper) initLines() {
})
}
// -- conversions from span (UTF-8) domain --
// SpanLocation converts a (UTF-8) span to a protocol (UTF-16) range.
// Precondition: the URIs of SpanLocation and Mapper match.
func (m *Mapper) SpanLocation(s span.Span) (Location, error) {
rng, err := m.SpanRange(s)
if err != nil {
return Location{}, err
}
return m.RangeLocation(rng), nil
}
// SpanRange converts a (UTF-8) span to a protocol (UTF-16) range.
// Precondition: the URIs of Span and Mapper match.
func (m *Mapper) SpanRange(s span.Span) (Range, error) {
// Assert that we aren't using the wrong mapper.
// We check only the base name, and case insensitively,
// because we can't assume clean paths, no symbolic links,
// case-sensitive directories. The authoritative answer
// requires querying the file system, and we don't want
// to do that.
if !strings.EqualFold(filepath.Base(string(m.URI)), filepath.Base(string(s.URI()))) {
return Range{}, bug.Errorf("mapper is for file %q instead of %q", m.URI, s.URI())
}
start, err := m.PointPosition(s.Start())
if err != nil {
return Range{}, fmt.Errorf("start: %w", err)
}
end, err := m.PointPosition(s.End())
if err != nil {
return Range{}, fmt.Errorf("end: %w", err)
}
return Range{Start: start, End: end}, nil
}
// PointPosition converts a valid span (UTF-8) point to a protocol (UTF-16) position.
func (m *Mapper) PointPosition(p span.Point) (Position, error) {
if p.HasPosition() {
return m.LineCol8Position(p.Line(), p.Column())
}
if p.HasOffset() {
return m.OffsetPosition(p.Offset())
}
return Position{}, fmt.Errorf("point has neither offset nor line/column")
}
// LineCol8Position converts a valid line and UTF-8 column number,
// both 1-based, to a protocol (UTF-16) position.
func (m *Mapper) LineCol8Position(line, col8 int) (Position, error) {
m.initLines()
if line-1 >= len(m.lineStart) {
line0 := line - 1 // 0-based
if !(0 <= line0 && line0 < len(m.lineStart)) {
return Position{}, fmt.Errorf("line number %d out of range (max %d)", line, len(m.lineStart))
}
// content[start:end] is the preceding partial line.
start := m.lineStart[line-1]
start := m.lineStart[line0]
end := start + col8 - 1
// Validate column.
if end > len(m.Content) {
return Position{}, fmt.Errorf("column is beyond end of file")
} else if line < len(m.lineStart) && end >= m.lineStart[line] {
} else if line0+1 < len(m.lineStart) && end >= m.lineStart[line0+1] {
return Position{}, fmt.Errorf("column is beyond end of line")
}
char := UTF16Len(m.Content[start:end])
return Position{Line: uint32(line - 1), Character: uint32(char)}, nil
return Position{Line: uint32(line0), Character: uint32(char)}, nil
}
// -- conversions from byte offsets --
@ -230,23 +180,6 @@ func (m *Mapper) OffsetRange(start, end int) (Range, error) {
return Range{Start: startPosition, End: endPosition}, nil
}
// OffsetSpan converts a byte-offset interval to a (UTF-8) span.
// The resulting span contains line, column, and offset information.
func (m *Mapper) OffsetSpan(start, end int) (span.Span, error) {
if start > end {
return span.Span{}, fmt.Errorf("start offset (%d) > end (%d)", start, end)
}
startPoint, err := m.OffsetPoint(start)
if err != nil {
return span.Span{}, fmt.Errorf("start: %v", err)
}
endPoint, err := m.OffsetPoint(end)
if err != nil {
return span.Span{}, fmt.Errorf("end: %v", err)
}
return span.New(m.URI, startPoint, endPoint), nil
}
// OffsetPosition converts a byte offset to a protocol (UTF-16) position.
func (m *Mapper) OffsetPosition(offset int) (Position, error) {
if !(0 <= offset && offset <= len(m.Content)) {
@ -275,14 +208,14 @@ func (m *Mapper) lineCol16(offset int) (int, int) {
return line, col16
}
// lineCol8 converts a valid byte offset to line and UTF-8 column numbers, both 0-based.
func (m *Mapper) lineCol8(offset int) (int, int) {
// OffsetLineCol8 converts a valid byte offset to line and UTF-8 column numbers, both 1-based.
func (m *Mapper) OffsetLineCol8(offset int) (int, int) {
line, start, cr := m.line(offset)
col8 := offset - start
if cr {
col8-- // retreat from \r at line end
}
return line, col8
return line + 1, col8 + 1
}
// line returns:
@ -310,16 +243,6 @@ func (m *Mapper) line(offset int) (int, int, bool) {
return line, m.lineStart[line], cr
}
// OffsetPoint converts a byte offset to a span (UTF-8) point.
// The resulting point contains line, column, and offset information.
func (m *Mapper) OffsetPoint(offset int) (span.Point, error) {
if !(0 <= offset && offset <= len(m.Content)) {
return span.Point{}, fmt.Errorf("invalid offset %d (want 0-%d)", offset, len(m.Content))
}
line, col8 := m.lineCol8(offset)
return span.NewPoint(line+1, col8+1, offset), nil
}
// OffsetMappedRange returns a MappedRange for the given byte offsets.
// A MappedRange can be converted to any other form.
func (m *Mapper) OffsetMappedRange(start, end int) (MappedRange, error) {
@ -331,23 +254,6 @@ func (m *Mapper) OffsetMappedRange(start, end int) (MappedRange, error) {
// -- conversions from protocol (UTF-16) domain --
// LocationSpan converts a protocol (UTF-16) Location to a (UTF-8) span.
// Precondition: the URIs of Location and Mapper match.
func (m *Mapper) LocationSpan(l Location) (span.Span, error) {
// TODO(adonovan): check that l.URI matches m.URI.
return m.RangeSpan(l.Range)
}
// RangeSpan converts a protocol (UTF-16) range to a (UTF-8) span.
// The resulting span has valid Positions and Offsets.
func (m *Mapper) RangeSpan(r Range) (span.Span, error) {
start, end, err := m.RangeOffsets(r)
if err != nil {
return span.Span{}, err
}
return m.OffsetSpan(start, end)
}
// RangeOffsets converts a protocol (UTF-16) range to start/end byte offsets.
func (m *Mapper) RangeOffsets(r Range) (int, int, error) {
start, err := m.PositionOffset(r.Start)
@ -405,18 +311,6 @@ func (m *Mapper) PositionOffset(p Position) (int, error) {
return offset + col8, nil
}
// PositionPoint converts a protocol (UTF-16) position to a span (UTF-8) point.
// The resulting point has a valid Position and Offset.
func (m *Mapper) PositionPoint(p Position) (span.Point, error) {
offset, err := m.PositionOffset(p)
if err != nil {
return span.Point{}, err
}
line, col8 := m.lineCol8(offset)
return span.NewPoint(line+1, col8+1, offset), nil
}
// -- go/token domain convenience methods --
// PosPosition converts a token pos to a protocol (UTF-16) position.
@ -478,7 +372,7 @@ func (m *Mapper) NodeMappedRange(tf *token.File, node ast.Node) (MappedRange, er
// A MappedRange represents a valid byte-offset range of a file.
// Through its Mapper it can be converted into other forms such
// as protocol.Range or span.Span.
// as protocol.Range or UTF-8.
//
// Construct one by calling Mapper.OffsetMappedRange with start/end offsets.
// From the go/token domain, call safetoken.Offsets first,
@ -516,18 +410,20 @@ func (mr MappedRange) Location() Location {
return mr.Mapper.RangeLocation(mr.Range())
}
// Span returns the range in span (UTF-8) form.
func (mr MappedRange) Span() span.Span {
spn, err := mr.Mapper.OffsetSpan(mr.start, mr.end)
if err != nil {
panic(err) // can't happen
}
return spn
}
// String formats the range in span (UTF-8) notation.
// String formats the range in UTF-8 notation.
func (mr MappedRange) String() string {
return fmt.Sprint(mr.Span())
var s strings.Builder
startLine, startCol8 := mr.Mapper.OffsetLineCol8(mr.start)
fmt.Fprintf(&s, "%d:%d", startLine, startCol8)
if mr.end != mr.start {
endLine, endCol8 := mr.Mapper.OffsetLineCol8(mr.end)
if endLine == startLine {
fmt.Fprintf(&s, "-%d", endCol8)
} else {
fmt.Fprintf(&s, "-%d:%d", endLine, endCol8)
}
}
return s.String()
}
// LocationTextDocumentPositionParams converts its argument to its result.

Просмотреть файл

@ -13,9 +13,9 @@ import (
"golang.org/x/tools/gopls/internal/span"
)
// This file tests Mapper's logic for converting between
// span.Point and UTF-16 columns. (The strange form attests to an
// earlier abstraction.)
// This file tests Mapper's logic for converting between offsets,
// UTF-8 columns, and UTF-16 columns. (The strange form attests to
// earlier abstractions.)
// 𐐀 is U+10400 = [F0 90 90 80] in UTF-8, [D801 DC00] in UTF-16.
var funnyString = []byte("𐐀23\n𐐀45")
@ -233,9 +233,16 @@ func TestToUTF16(t *testing.T) {
if e.issue != nil && !*e.issue {
t.Skip("expected to fail")
}
p := span.NewPoint(e.line, e.col, e.offset)
m := protocol.NewMapper("", e.input)
pos, err := m.PointPosition(p)
var pos protocol.Position
var err error
if e.line > 0 {
pos, err = m.LineCol8Position(e.line, e.col)
} else if e.offset >= 0 {
pos, err = m.OffsetPosition(e.offset)
} else {
err = fmt.Errorf("point has neither offset nor line/column")
}
if err != nil {
if err.Error() != e.err {
t.Fatalf("expected error %v; got %v", e.err, err)
@ -249,12 +256,12 @@ func TestToUTF16(t *testing.T) {
if got != e.resUTF16col {
t.Fatalf("expected result %v; got %v", e.resUTF16col, got)
}
pre, post := getPrePost(e.input, p.Offset())
pre, post := getPrePost(e.input, e.offset)
if string(pre) != e.pre {
t.Fatalf("expected #%d pre %q; got %q", p.Offset(), e.pre, pre)
t.Fatalf("expected #%d pre %q; got %q", e.offset, e.pre, pre)
}
if string(post) != e.post {
t.Fatalf("expected #%d, post %q; got %q", p.Offset(), e.post, post)
t.Fatalf("expected #%d, post %q; got %q", e.offset, e.post, post)
}
})
}
@ -264,7 +271,7 @@ func TestFromUTF16(t *testing.T) {
for _, e := range fromUTF16Tests {
t.Run(e.scenario, func(t *testing.T) {
m := protocol.NewMapper("", []byte(e.input))
p, err := m.PositionPoint(protocol.Position{
offset, err := m.PositionOffset(protocol.Position{
Line: uint32(e.line - 1),
Character: uint32(e.utf16col - 1),
})
@ -277,18 +284,22 @@ func TestFromUTF16(t *testing.T) {
if e.err != "" {
t.Fatalf("unexpected success; wanted %v", e.err)
}
if p.Column() != e.resCol {
t.Fatalf("expected resulting col %v; got %v", e.resCol, p.Column())
if offset != e.resOffset {
t.Fatalf("expected offset %v; got %v", e.resOffset, offset)
}
if p.Offset() != e.resOffset {
t.Fatalf("expected resulting offset %v; got %v", e.resOffset, p.Offset())
line, col8 := m.OffsetLineCol8(offset)
if line != e.line {
t.Fatalf("expected resulting line %v; got %v", e.line, line)
}
pre, post := getPrePost(e.input, p.Offset())
if col8 != e.resCol {
t.Fatalf("expected resulting col %v; got %v", e.resCol, col8)
}
pre, post := getPrePost(e.input, offset)
if string(pre) != e.pre {
t.Fatalf("expected #%d pre %q; got %q", p.Offset(), e.pre, pre)
t.Fatalf("expected #%d pre %q; got %q", offset, e.pre, pre)
}
if string(post) != e.post {
t.Fatalf("expected #%d post %q; got %q", p.Offset(), e.post, post)
t.Fatalf("expected #%d post %q; got %q", offset, e.post, post)
}
})
}
@ -428,12 +439,12 @@ func TestBytesOffset(t *testing.T) {
fname := fmt.Sprintf("test %d", i)
uri := span.URIFromPath(fname)
mapper := protocol.NewMapper(uri, []byte(test.text))
got, err := mapper.PositionPoint(test.pos)
got, err := mapper.PositionOffset(test.pos)
if err != nil && test.want != -1 {
t.Errorf("%d: unexpected error: %v", i, err)
}
if err == nil && got.Offset() != test.want {
t.Errorf("want %d for %q(Line:%d,Character:%d), but got %d", test.want, test.text, int(test.pos.Line), int(test.pos.Character), got.Offset())
if err == nil && got != test.want {
t.Errorf("want %d for %q(Line:%d,Character:%d), but got %d", test.want, test.text, int(test.pos.Line), int(test.pos.Character), got)
}
}
}

Просмотреть файл

@ -1218,21 +1218,24 @@ func (run *markerTestRun) fmtLocDetails(loc protocol.Location, includeTxtPos boo
run.env.T.Errorf("internal error: %v", err)
return "<invalid location>"
}
s, err := m.LocationSpan(loc)
start, end, err := m.RangeOffsets(loc.Range)
if err != nil {
run.env.T.Errorf("error formatting location %s: %v", loc, err)
return "<invalid location>"
}
innerSpan := fmt.Sprintf("%d:%d", s.Start().Line(), s.Start().Column()) // relative to the embedded file
outerSpan := fmt.Sprintf("%d:%d", lines+s.Start().Line(), s.Start().Column()) // relative to the archive file
if s.Start() != s.End() {
if s.End().Line() == s.Start().Line() {
innerSpan += fmt.Sprintf("-%d", s.End().Column())
outerSpan += fmt.Sprintf("-%d", s.End().Column())
var (
startLine, startCol8 = m.OffsetLineCol8(start)
endLine, endCol8 = m.OffsetLineCol8(end)
)
innerSpan := fmt.Sprintf("%d:%d", startLine, startCol8) // relative to the embedded file
outerSpan := fmt.Sprintf("%d:%d", lines+startLine, startCol8) // relative to the archive file
if start != end {
if endLine == startLine {
innerSpan += fmt.Sprintf("-%d", endCol8)
outerSpan += fmt.Sprintf("-%d", endCol8)
} else {
innerSpan += fmt.Sprintf("-%d:%d", s.End().Line(), s.End().Column())
innerSpan += fmt.Sprintf("-%d:%d", lines+s.End().Line(), s.End().Column())
innerSpan += fmt.Sprintf("-%d:%d", endLine, endCol8)
outerSpan += fmt.Sprintf("-%d:%d", lines+endLine, endCol8)
}
}

Просмотреть файл

@ -4,6 +4,10 @@
package span
// TODO(adonovan): rename this package. Perhaps merge span.URI with
// protocol.DocumentURI and make these methods on it? Or is span.URI
// supposed to establish stronger invariants? urls.FromPath?
import (
"fmt"
"net/url"
@ -104,6 +108,8 @@ func URIFromURI(s string) URI {
// SameExistingFile reports whether two spans denote the
// same existing file by querying the file system.
//
// TODO(adonovan): inline sole use of this in view de-duping.
func SameExistingFile(a, b URI) bool {
fa, err := filename(a)
if err != nil {