Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(2341)

Side by Side Diff: src/cmd/godoc/format.go

Issue 3699041: code review 3699041: godoc: support for regular expression full text search (Closed)
Patch Set: code review 3699041: godoc: support for regular expression full text search Created 14 years, 2 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2010 The Go Authors. All rights reserved.
r 2011/01/09 00:08:55 2011?
gri 2011/01/10 23:15:01 Done.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // This file implements FormatSelections and FormatText.
6 // FormatText is used to HTML-format Go and non-Go source
7 // text with line numbers and highlighted sections. It is
8 // built on top of FormatSelections, a generic formatter
9 // for "selected" text.
10
11 package main
12
13 import (
14 "bytes"
15 "fmt"
16 "go/scanner"
17 "go/token"
18 "io"
19 "regexp"
20 "strconv"
21 "template"
22 )
23
24
25 // ----------------------------------------------------------------------------
26 // Implementation of FormatSelections
27
28 // A Selection is a function returning offset pairs []int{a, b}
29 // describing consecutive non-overlapping text segments [a, b).
30 // If there are no more segments, a Selection must return nil.
31 //
32 // TODO It's more efficient to return a pair (a, b int) instead
33 // of creating lots of slices. Need to determine how to
34 // indicate the end of a Selection.
35 //
36 type Selection func() []int
37
38
39 // A LinkWriter writes some start or end "tag" to w for the text offset offs.
40 // It is called by FormatSelections at the start or end of each link segment.
41 //
42 type LinkWriter func(w io.Writer, offs int, start bool)
43
44
45 // A SegmentWriter formats a text according to selections and writes it to w.
46 // The selections parameter is a bit set indicating which selections provided
47 // to FormatSelections overlap with the text segment: If the n'th bit is set
48 // in selections, the n'th selection provided to FormatSelections is overlapping
49 // with the text.
50 //
51 type SegmentWriter func(w io.Writer, text []byte, selections int)
52
53
54 // FormatSelections takes a text and writes it to w using link and segment
55 // writers lw and sw as follows: lw is invoked for consecutive segment starts
56 // and ends as specified through the links selection, and sw is invoked for
57 // consecutive segments of text overlapped by the same selections as specified
58 // by selections. The link writer lw may be nil, in which case the links
59 // Selection is ignored.
60 //
61 func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection, sw SegmentWriter, selections ...Selection) {
62 if lw != nil {
63 selections = append(selections, links)
64 }
65 // compute the sequence of consecutive segment changes
66 changes := newMerger(selections)
67 // The i'th bit in bitset indicates that the text
68 // at the current offset is covered by selections[i].
69 bitset := 0
70 lastOffs := 0
71 for {
72 // get the next segment change
73 index, offs, start := changes.next()
74 if index < 0 || offs > len(text) {
75 // no more segment changes or the next change
76 // is past the end of the text - we're done
77 break
78 }
79 // determine the kind of segment change
80 if index == len(selections)-1 {
81 // we have a link segment change:
82 // format the previous selection segment, write the
83 // link tag and start a new selection segment
84 sw(w, text[lastOffs:offs], bitset)
85 lastOffs = offs
86 lw(w, offs, start)
87 } else {
88 // we have a selection change:
89 // format the previous selection segment, determine
90 // the new selection bitset and start a new segment·
91 sw(w, text[lastOffs:offs], bitset)
92 lastOffs = offs
93 mask := 1 << uint(index)
94 if start {
95 bitset |= mask
96 } else {
97 bitset &^= mask
98 }
99 }
100 }
101 sw(w, text[lastOffs:], bitset)
102 }
103
104
105 // A merger merges a slice of Selections and produces a sequence of
106 // consecutive segment change events through repeated next() calls.
107 //
108 type merger struct {
109 selections []Selection
110 segments [][]int // segments[i] is the next segment of selections[i]
111 }
112
113
114 const infinity int = 2e9
115
116 func newMerger(selections []Selection) *merger {
117 segments := make([][]int, len(selections))
118 for i, sel := range selections {
119 segments[i] = []int{infinity, infinity}
120 if sel != nil {
121 if seg := sel(); seg != nil {
122 segments[i] = seg
123 }
124 }
125 }
126 return &merger{selections, segments}
127 }
128
129
130 // next returns the next segment change: index specifies the Selection
131 // to which the segment belongs, offs is the segment start or end offset
132 // as determined by the start value. If there are no more segment changes,
133 // next returns an index value < 0.
134 //
135 func (m *merger) next() (index, offs int, start bool) {
136 // find the next smallest offset where a segment starts or ends
137 offs = infinity
138 index = -1
139 for i, seg := range m.segments {
140 switch {
141 case seg[0] < offs:
142 offs = seg[0]
143 index = i
144 start = true
145 case seg[1] < offs:
146 offs = seg[1]
147 index = i
148 start = false
149 }
150 }
151 if index < 0 {
152 // no offset found => all selections merged
153 return
154 }
155 // offset found - it's either the start or end offset but
156 // either way it is ok to consume the start offset: set it
157 // to infinity so it won't be considered in the following
158 // next call
159 m.segments[index][0] = infinity
160 if start {
161 return
162 }
163 // end offset found - consume it
164 m.segments[index][1] = infinity
165 // advance to the next segment for that selection
166 seg := m.selections[index]()
167 if seg == nil {
168 return
169 }
170 m.segments[index] = seg
171 return
172 }
173
174
175 // ----------------------------------------------------------------------------
176 // Implementation of FormatText
177
178 // lineSelection returns the line segments for text as a Selection.
179 func lineSelection(text []byte) Selection {
180 i, j := 0, 0
181 return func() (seg []int) {
182 // find next newline, if any
183 for j < len(text) {
184 j++
185 if text[j-1] == '\n' {
186 break
187 }
188 }
189 if i < j {
190 // text[i:j] constitutes a line
191 seg = []int{i, j}
192 i = j
193 }
194 return
195 }
196 }
197
198
199 // commentSelection returns the sequence of consecutive comments
200 // in the Go src text as a Selection.
201 //
202 func commentSelection(src []byte) Selection {
203 var s scanner.Scanner
204 file := s.Init(token.NewFileSet(), "", src, nil, scanner.ScanComments+sc anner.InsertSemis)
205 return func() (seg []int) {
206 for {
207 pos, tok, lit := s.Scan()
208 if tok == token.EOF {
209 break
210 }
211 offs := file.Offset(pos)
212 if tok == token.COMMENT {
213 seg = []int{offs, offs + len(lit)}
214 break
215 }
216 }
217 return
218 }
219 }
220
221
222 // makeSelection is a helper function to make a Selection from a slice of pairs.
223 func makeSelection(matches [][]int) Selection {
224 return func() (seg []int) {
225 if len(matches) > 0 {
226 seg = matches[0]
227 matches = matches[1:]
228 }
229 return
230 }
231 }
232
233
234 // regexpSelection computes the Selection for the regular expression expr in tex t.
235 func regexpSelection(text []byte, expr string) Selection {
236 var matches [][]int
237 if rx, err := regexp.Compile(expr); err == nil {
238 matches = rx.FindAllIndex(text, -1)
239 }
240 return makeSelection(matches)
241 }
242
243
244 var selRx = regexp.MustCompile(`^([0-9]+):([0-9]+)`)
245
246 // rangeSelection computes the Selection for a text range described
247 // by the argument str; the range description must match the selRx
248 // regular expression.
249 //
250 func rangeSelection(str string) Selection {
251 m := selRx.FindStringSubmatch(str)
252 if len(m) >= 2 {
253 from, _ := strconv.Atoi(m[1])
254 to, _ := strconv.Atoi(m[2])
255 if from < to {
256 return makeSelection([][]int{[]int{from, to}})
257 }
258 }
259 return nil
260 }
261
262
263 // bit 0: comments
264 // bit 1: highlights
265 // bit 2: selections
266 //
267 var startTags = [][]byte{
268 /* 000 */ []byte(``),
269 /* 001 */ []byte(`<span class ="comment">`),
270 /* 010 */ []byte(`<span class="highlight">`),
271 /* 011 */ []byte(`<span class="highlight-comment">`),
272 /* 100 */ []byte(`<span class="selection">`),
273 /* 101 */ []byte(`<span class="selection-comment">`),
274 /* 110 */ []byte(`<span class="selection-highlight">`),
275 /* 111 */ []byte(`<span class="selection-highlight-comment">`),
276 }
277
278 var endTag = []byte(`</span>`)
279
280
281 func selectionTag(w io.Writer, text []byte, selections int) {
282 if len(text) > 0 {
283 if selections < len(startTags) {
284 if tag := startTags[selections]; len(tag) > 0 {
285 w.Write(tag)
286 template.HTMLEscape(w, text)
287 w.Write(endTag)
288 return
289 }
290 }
291 template.HTMLEscape(w, text)
292 }
293 }
294
295
296 // FormatText HTML-escapes text and returns it wrapped in <pre> tags.
297 // Conscutive text segments are wrapped in HTML spans as follows:
298 //
299 // - if line >= 0, line numbers are printed before each line, starting
300 // with the value of line
301 // - if the text is Go source, comments get the "comment" span class
302 // - each occurence of the regular expression pattern gets the "highlight"
r 2011/01/09 00:08:55 s/r/rr/
gri 2011/01/10 23:15:01 Done.
303 // span class
304 // - text segments covered by selection get the "selection" span class
305 //
306 // Comments, highlights, and selections may overlap arbitrarily; the respective
307 // HTML span classes are specified in the startTags variable.
r 2011/01/09 00:08:55 this comment should appear or at least be tied to
gri 2011/01/10 23:15:01 Done.
308 //
309 func FormatText(text []byte, line int, goSource bool, pattern string, selection Selection) []byte {
310 var buf bytes.Buffer
311 buf.WriteString("<pre>\n")
312
313 var comments, highlights Selection
314 if goSource {
315 comments = commentSelection(text)
316 }
317 if pattern != "" {
318 highlights = regexpSelection(text, pattern)
319 }
320 if comments != nil || highlights != nil || selection != nil {
321 var lineTag LinkWriter
322 if line >= 0 {
323 lineTag = func(w io.Writer, _ int, start bool) {
324 if start {
325 fmt.Fprintf(w, "<a id=\"L%d\"></a>%5d\t" , line, line)
326 line++
327 }
328 }
329 }
330 FormatSelections(&buf, text, lineTag, lineSelection(text), selec tionTag, comments, highlights, selection)
331 } else {
332 template.HTMLEscape(&buf, text)
333 }
334
335 buf.WriteString("</pre>\n")
336 return buf.Bytes()
337 }
OLDNEW

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b