blob: 29372b76377583817f894225475c4ee9a72b7693 [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cmd
import (
"fmt"
"go/token"
"regexp"
"strconv"
"golang.org/x/tools/internal/lsp/source"
)
type Location struct {
Filename string `json:"file"`
Start Position `json:"start"`
End Position `json:"end"`
}
type Position struct {
Line int `json:"line"`
Column int `json:"column"`
Offset int `json:"offset"`
}
func newLocation(fset *token.FileSet, r source.Range) Location {
start := fset.Position(r.Start)
end := fset.Position(r.End)
// it should not be possible the following line to fail
filename, _ := source.ToURI(start.Filename).Filename()
return Location{
Filename: filename,
Start: Position{
Line: start.Line,
Column: start.Column,
Offset: fset.File(r.Start).Offset(r.Start),
},
End: Position{
Line: end.Line,
Column: end.Column,
Offset: fset.File(r.End).Offset(r.End),
},
}
}
var posRe = regexp.MustCompile(
`(?P<file>.*):(?P<start>(?P<sline>\d+):(?P<scol>\d)+|#(?P<soff>\d+))(?P<end>:(?P<eline>\d+):(?P<ecol>\d+)|#(?P<eoff>\d+))?$`)
const (
posReAll = iota
posReFile
posReStart
posReSLine
posReSCol
posReSOff
posReEnd
posReELine
posReECol
posReEOff
)
func init() {
names := posRe.SubexpNames()
// verify all our submatch offsets are correct
for name, index := range map[string]int{
"file": posReFile,
"start": posReStart,
"sline": posReSLine,
"scol": posReSCol,
"soff": posReSOff,
"end": posReEnd,
"eline": posReELine,
"ecol": posReECol,
"eoff": posReEOff,
} {
if names[index] == name {
continue
}
// try to find it
for test := range names {
if names[test] == name {
panic(fmt.Errorf("Index for %s incorrect, wanted %v have %v", name, index, test))
}
}
panic(fmt.Errorf("Subexp %s does not exist", name))
}
}
// parseLocation parses a string of the form "file:pos" or
// file:start,end" where pos, start, end match either a byte offset in the
// form #%d or a line and column in the form %d,%d.
func parseLocation(value string) (Location, error) {
var loc Location
m := posRe.FindStringSubmatch(value)
if m == nil {
return loc, fmt.Errorf("bad location syntax %q", value)
}
loc.Filename = m[posReFile]
if m[posReSLine] != "" {
v, err := strconv.ParseInt(m[posReSLine], 10, 32)
if err != nil {
return loc, err
}
loc.Start.Line = int(v)
v, err = strconv.ParseInt(m[posReSCol], 10, 32)
if err != nil {
return loc, err
}
loc.Start.Column = int(v)
} else {
v, err := strconv.ParseInt(m[posReSOff], 10, 32)
if err != nil {
return loc, err
}
loc.Start.Offset = int(v)
}
if m[posReEnd] == "" {
loc.End = loc.Start
} else {
if m[posReELine] != "" {
v, err := strconv.ParseInt(m[posReELine], 10, 32)
if err != nil {
return loc, err
}
loc.End.Line = int(v)
v, err = strconv.ParseInt(m[posReECol], 10, 32)
if err != nil {
return loc, err
}
loc.End.Column = int(v)
} else {
v, err := strconv.ParseInt(m[posReEOff], 10, 32)
if err != nil {
return loc, err
}
loc.End.Offset = int(v)
}
}
return loc, nil
}
func (l Location) Format(f fmt.State, c rune) {
// we should always have a filename
fmt.Fprint(f, l.Filename)
// are we in line:column format or #offset format
fmt.Fprintf(f, ":%v", l.Start)
if l.End != l.Start {
fmt.Fprintf(f, ",%v", l.End)
}
}
func (p Position) Format(f fmt.State, c rune) {
// are we in line:column format or #offset format
if p.Line > 0 {
fmt.Fprintf(f, "%d:%d", p.Line, p.Column)
return
}
fmt.Fprintf(f, "#%d", p.Offset)
}