...
1
2 package file
3
4 import (
5 "fmt"
6 "net/url"
7 "path"
8 "sort"
9 "strings"
10 "sync"
11
12 "github.com/go-sourcemap/sourcemap"
13 )
14
15
16
17
18 type Idx int
19
20
21
22 type Position struct {
23 Filename string
24 Line int
25 Column int
26
27 }
28
29
30
31 func (self *Position) isValid() bool {
32 return self.Line > 0
33 }
34
35
36
37
38
39
40
41 func (self Position) String() string {
42 str := self.Filename
43 if self.isValid() {
44 if str != "" {
45 str += ":"
46 }
47 str += fmt.Sprintf("%d:%d", self.Line, self.Column)
48 }
49 if str == "" {
50 str = "-"
51 }
52 return str
53 }
54
55
56
57
58 type FileSet struct {
59 files []*File
60 last *File
61 }
62
63
64
65
66 func (self *FileSet) AddFile(filename, src string) int {
67 base := self.nextBase()
68 file := &File{
69 name: filename,
70 src: src,
71 base: base,
72 }
73 self.files = append(self.files, file)
74 self.last = file
75 return base
76 }
77
78 func (self *FileSet) nextBase() int {
79 if self.last == nil {
80 return 1
81 }
82 return self.last.base + len(self.last.src) + 1
83 }
84
85 func (self *FileSet) File(idx Idx) *File {
86 for _, file := range self.files {
87 if idx <= Idx(file.base+len(file.src)) {
88 return file
89 }
90 }
91 return nil
92 }
93
94
95 func (self *FileSet) Position(idx Idx) Position {
96 for _, file := range self.files {
97 if idx <= Idx(file.base+len(file.src)) {
98 return file.Position(int(idx) - file.base)
99 }
100 }
101 return Position{}
102 }
103
104 type File struct {
105 mu sync.Mutex
106 name string
107 src string
108 base int
109 sourceMap *sourcemap.Consumer
110 lineOffsets []int
111 lastScannedOffset int
112 }
113
114 func NewFile(filename, src string, base int) *File {
115 return &File{
116 name: filename,
117 src: src,
118 base: base,
119 }
120 }
121
122 func (fl *File) Name() string {
123 return fl.name
124 }
125
126 func (fl *File) Source() string {
127 return fl.src
128 }
129
130 func (fl *File) Base() int {
131 return fl.base
132 }
133
134 func (fl *File) SetSourceMap(m *sourcemap.Consumer) {
135 fl.sourceMap = m
136 }
137
138 func (fl *File) Position(offset int) Position {
139 var line int
140 var lineOffsets []int
141 fl.mu.Lock()
142 if offset > fl.lastScannedOffset {
143 line = fl.scanTo(offset)
144 lineOffsets = fl.lineOffsets
145 fl.mu.Unlock()
146 } else {
147 lineOffsets = fl.lineOffsets
148 fl.mu.Unlock()
149 line = sort.Search(len(lineOffsets), func(x int) bool { return lineOffsets[x] > offset }) - 1
150 }
151
152 var lineStart int
153 if line >= 0 {
154 lineStart = lineOffsets[line]
155 }
156
157 row := line + 2
158 col := offset - lineStart + 1
159
160 if fl.sourceMap != nil {
161 if source, _, row, col, ok := fl.sourceMap.Source(row, col); ok {
162 sourceUrlStr := source
163 sourceURL := ResolveSourcemapURL(fl.Name(), source)
164 if sourceURL != nil {
165 sourceUrlStr = sourceURL.String()
166 }
167
168 return Position{
169 Filename: sourceUrlStr,
170 Line: row,
171 Column: col,
172 }
173 }
174 }
175
176 return Position{
177 Filename: fl.name,
178 Line: row,
179 Column: col,
180 }
181 }
182
183 func ResolveSourcemapURL(basename, source string) *url.URL {
184
185 smURL, err := url.Parse(strings.TrimSpace(source))
186 if err == nil && !smURL.IsAbs() {
187 baseURL, err1 := url.Parse(strings.TrimSpace(basename))
188 if err1 == nil && path.IsAbs(baseURL.Path) {
189 smURL = baseURL.ResolveReference(smURL)
190 } else {
191
192
193 smURL, _ = url.Parse(path.Join(path.Dir(basename), smURL.Path))
194 }
195 }
196 return smURL
197 }
198
199 func findNextLineStart(s string) int {
200 for pos, ch := range s {
201 switch ch {
202 case '\r':
203 if pos < len(s)-1 && s[pos+1] == '\n' {
204 return pos + 2
205 }
206 return pos + 1
207 case '\n':
208 return pos + 1
209 case '\u2028', '\u2029':
210 return pos + 3
211 }
212 }
213 return -1
214 }
215
216 func (fl *File) scanTo(offset int) int {
217 o := fl.lastScannedOffset
218 for o < offset {
219 p := findNextLineStart(fl.src[o:])
220 if p == -1 {
221 fl.lastScannedOffset = len(fl.src)
222 return len(fl.lineOffsets) - 1
223 }
224 o = o + p
225 fl.lineOffsets = append(fl.lineOffsets, o)
226 }
227 fl.lastScannedOffset = o
228
229 if o == offset {
230 return len(fl.lineOffsets) - 1
231 }
232
233 return len(fl.lineOffsets) - 2
234 }
235
View as plain text