package file
import (
"fmt"
"net/url"
"path"
"sort"
"strings"
"sync"
"github.com/go-sourcemap/sourcemap"
)
type Idx int
type Position struct {
Filename string
Line int
Column int
}
func (self *Position ) isValid () bool {
return self .Line > 0
}
func (self Position ) String () string {
str := self .Filename
if self .isValid () {
if str != "" {
str += ":"
}
str += fmt .Sprintf ("%d:%d" , self .Line , self .Column )
}
if str == "" {
str = "-"
}
return str
}
type FileSet struct {
files []*File
last *File
}
func (self *FileSet ) AddFile (filename , src string ) int {
base := self .nextBase ()
file := &File {
name : filename ,
src : src ,
base : base ,
}
self .files = append (self .files , file )
self .last = file
return base
}
func (self *FileSet ) nextBase () int {
if self .last == nil {
return 1
}
return self .last .base + len (self .last .src ) + 1
}
func (self *FileSet ) File (idx Idx ) *File {
for _ , file := range self .files {
if idx <= Idx (file .base +len (file .src )) {
return file
}
}
return nil
}
func (self *FileSet ) Position (idx Idx ) Position {
for _ , file := range self .files {
if idx <= Idx (file .base +len (file .src )) {
return file .Position (int (idx ) - file .base )
}
}
return Position {}
}
type File struct {
mu sync .Mutex
name string
src string
base int
sourceMap *sourcemap .Consumer
lineOffsets []int
lastScannedOffset int
}
func NewFile (filename , src string , base int ) *File {
return &File {
name : filename ,
src : src ,
base : base ,
}
}
func (fl *File ) Name () string {
return fl .name
}
func (fl *File ) Source () string {
return fl .src
}
func (fl *File ) Base () int {
return fl .base
}
func (fl *File ) SetSourceMap (m *sourcemap .Consumer ) {
fl .sourceMap = m
}
func (fl *File ) Position (offset int ) Position {
var line int
var lineOffsets []int
fl .mu .Lock ()
if offset > fl .lastScannedOffset {
line = fl .scanTo (offset )
lineOffsets = fl .lineOffsets
fl .mu .Unlock ()
} else {
lineOffsets = fl .lineOffsets
fl .mu .Unlock ()
line = sort .Search (len (lineOffsets ), func (x int ) bool { return lineOffsets [x ] > offset }) - 1
}
var lineStart int
if line >= 0 {
lineStart = lineOffsets [line ]
}
row := line + 2
col := offset - lineStart + 1
if fl .sourceMap != nil {
if source , _ , row , col , ok := fl .sourceMap .Source (row , col ); ok {
sourceUrlStr := source
sourceURL := ResolveSourcemapURL (fl .Name (), source )
if sourceURL != nil {
sourceUrlStr = sourceURL .String ()
}
return Position {
Filename : sourceUrlStr ,
Line : row ,
Column : col ,
}
}
}
return Position {
Filename : fl .name ,
Line : row ,
Column : col ,
}
}
func ResolveSourcemapURL (basename , source string ) *url .URL {
smURL , err := url .Parse (strings .TrimSpace (source ))
if err == nil && !smURL .IsAbs () {
baseURL , err1 := url .Parse (strings .TrimSpace (basename ))
if err1 == nil && path .IsAbs (baseURL .Path ) {
smURL = baseURL .ResolveReference (smURL )
} else {
smURL , _ = url .Parse (path .Join (path .Dir (basename ), smURL .Path ))
}
}
return smURL
}
func findNextLineStart(s string ) int {
for pos , ch := range s {
switch ch {
case '\r' :
if pos < len (s )-1 && s [pos +1 ] == '\n' {
return pos + 2
}
return pos + 1
case '\n' :
return pos + 1
case '\u2028' , '\u2029' :
return pos + 3
}
}
return -1
}
func (fl *File ) scanTo (offset int ) int {
o := fl .lastScannedOffset
for o < offset {
p := findNextLineStart (fl .src [o :])
if p == -1 {
fl .lastScannedOffset = len (fl .src )
return len (fl .lineOffsets ) - 1
}
o = o + p
fl .lineOffsets = append (fl .lineOffsets , o )
}
fl .lastScannedOffset = o
if o == offset {
return len (fl .lineOffsets ) - 1
}
return len (fl .lineOffsets ) - 2
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .