1
2
3
4
5
6
7
8
9
10
11 package godoc
12
13 import (
14 "fmt"
15 "go/scanner"
16 "go/token"
17 "io"
18 "regexp"
19 "strconv"
20 "text/template"
21 )
22
23
24
25
26
27
28 type Segment struct {
29 start, end int
30 }
31
32 func (seg *Segment) isEmpty() bool { return seg.start >= seg.end }
33
34
35
36
37
38 type Selection func() Segment
39
40
41
42 type LinkWriter func(w io.Writer, offs int, start bool)
43
44
45
46
47
48
49 type SegmentWriter func(w io.Writer, text []byte, selections int)
50
51
52
53
54
55
56
57 func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection, sw SegmentWriter, selections ...Selection) {
58
59
60 if lw != nil {
61 selections = append(selections, links)
62 }
63
64
65 changes := newMerger(selections)
66
67
68
69 bitset := 0
70 lastOffs := 0
71
72
73
74
75
76 var last struct {
77 begin, end int
78 bitset int
79 }
80
81
82 flush := func() {
83 if last.begin < last.end {
84 sw(w, text[last.begin:last.end], last.bitset)
85 }
86 last.begin = last.end
87 }
88
89
90
91 segment := func(end int) {
92 if lastOffs < end {
93 if last.end != lastOffs || last.bitset != bitset {
94
95
96 flush()
97
98 last.begin = lastOffs
99 }
100 last.end = end
101 last.bitset = bitset
102 }
103 }
104
105 for {
106
107 index, offs, start := changes.next()
108 if index < 0 || offs > len(text) {
109
110
111 break
112 }
113
114 if lw != nil && index == len(selections)-1 {
115
116
117
118 segment(offs)
119 flush()
120 lastOffs = offs
121 lw(w, offs, start)
122 } else {
123
124
125
126 segment(offs)
127 lastOffs = offs
128 mask := 1 << uint(index)
129 if start {
130 bitset |= mask
131 } else {
132 bitset &^= mask
133 }
134 }
135 }
136 segment(len(text))
137 flush()
138 }
139
140
141
142 type merger struct {
143 selections []Selection
144 segments []Segment
145 }
146
147 const infinity int = 2e9
148
149 func newMerger(selections []Selection) *merger {
150 segments := make([]Segment, len(selections))
151 for i, sel := range selections {
152 segments[i] = Segment{infinity, infinity}
153 if sel != nil {
154 if seg := sel(); !seg.isEmpty() {
155 segments[i] = seg
156 }
157 }
158 }
159 return &merger{selections, segments}
160 }
161
162
163
164
165
166 func (m *merger) next() (index, offs int, start bool) {
167
168 offs = infinity
169 index = -1
170 for i, seg := range m.segments {
171 switch {
172 case seg.start < offs:
173 offs = seg.start
174 index = i
175 start = true
176 case seg.end < offs:
177 offs = seg.end
178 index = i
179 start = false
180 }
181 }
182 if index < 0 {
183
184 return
185 }
186
187
188
189
190 m.segments[index].start = infinity
191 if start {
192 return
193 }
194
195 m.segments[index].end = infinity
196
197 seg := m.selections[index]()
198 if !seg.isEmpty() {
199 m.segments[index] = seg
200 }
201 return
202 }
203
204
205
206
207
208 func lineSelection(text []byte) Selection {
209 i, j := 0, 0
210 return func() (seg Segment) {
211
212 for j < len(text) {
213 j++
214 if text[j-1] == '\n' {
215 break
216 }
217 }
218 if i < j {
219
220 seg = Segment{i, j}
221 i = j
222 }
223 return
224 }
225 }
226
227
228
229 func tokenSelection(src []byte, sel token.Token) Selection {
230 var s scanner.Scanner
231 fset := token.NewFileSet()
232 file := fset.AddFile("", fset.Base(), len(src))
233 s.Init(file, src, nil, scanner.ScanComments)
234 return func() (seg Segment) {
235 for {
236 pos, tok, lit := s.Scan()
237 if tok == token.EOF {
238 break
239 }
240 offs := file.Offset(pos)
241 if tok == sel {
242 seg = Segment{offs, offs + len(lit)}
243 break
244 }
245 }
246 return
247 }
248 }
249
250
251
252 func makeSelection(matches [][]int) Selection {
253 i := 0
254 return func() Segment {
255 for i < len(matches) {
256 m := matches[i]
257 i++
258 if m[0] < m[1] {
259
260 return Segment{m[0], m[1]}
261 }
262 }
263 return Segment{}
264 }
265 }
266
267
268 func regexpSelection(text []byte, expr string) Selection {
269 var matches [][]int
270 if rx, err := regexp.Compile(expr); err == nil {
271 matches = rx.FindAllIndex(text, -1)
272 }
273 return makeSelection(matches)
274 }
275
276 var selRx = regexp.MustCompile(`^([0-9]+):([0-9]+)`)
277
278
279
280
281 func RangeSelection(str string) Selection {
282 m := selRx.FindStringSubmatch(str)
283 if len(m) >= 2 {
284 from, _ := strconv.Atoi(m[1])
285 to, _ := strconv.Atoi(m[2])
286 if from < to {
287 return makeSelection([][]int{{from, to}})
288 }
289 }
290 return nil
291 }
292
293
294
295
296
297
298
299
300 var startTags = [][]byte{
301 []byte(``),
302 []byte(`<span class="comment">`),
303 []byte(`<span class="highlight">`),
304 []byte(`<span class="highlight-comment">`),
305 []byte(`<span class="selection">`),
306 []byte(`<span class="selection-comment">`),
307 []byte(`<span class="selection-highlight">`),
308 []byte(`<span class="selection-highlight-comment">`),
309 }
310
311 var endTag = []byte(`</span>`)
312
313 func selectionTag(w io.Writer, text []byte, selections int) {
314 if selections < len(startTags) {
315 if tag := startTags[selections]; len(tag) > 0 {
316 w.Write(tag)
317 template.HTMLEscape(w, text)
318 w.Write(endTag)
319 return
320 }
321 }
322 template.HTMLEscape(w, text)
323 }
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338 func FormatText(w io.Writer, text []byte, line int, goSource bool, pattern string, selection Selection) {
339 var comments, highlights Selection
340 if goSource {
341 comments = tokenSelection(text, token.COMMENT)
342 }
343 if pattern != "" {
344 highlights = regexpSelection(text, pattern)
345 }
346 if line >= 0 || comments != nil || highlights != nil || selection != nil {
347 var lineTag LinkWriter
348 if line >= 0 {
349 lineTag = func(w io.Writer, _ int, start bool) {
350 if start {
351 fmt.Fprintf(w, "<span id=\"L%d\" class=\"ln\">%6d</span>", line, line)
352 line++
353 }
354 }
355 }
356 FormatSelections(w, text, lineTag, lineSelection(text), selectionTag, comments, highlights, selection)
357 } else {
358 template.HTMLEscape(w, text)
359 }
360 }
361
View as plain text