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

Delta Between Two Patch Sets: src/cmd/godoc/format.go

Issue 3699041: code review 3699041: godoc: support for regular expression full text search (Closed)
Left Patch Set: code review 3699041: godoc: initial support for regular expression full text... Created 14 years, 3 months ago
Right 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:
Right: Side by side diff | Download
« no previous file with change/comment | « src/cmd/godoc/doc.go ('k') | src/cmd/godoc/godoc.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
(no file at all)
1 // Copyright 2011 The Go Authors. All rights reserved.
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 // Span tags for all the possible selection combinations that may
264 // be generated by FormatText. Selections are indicated by a bitset,
265 // and the value of the bitset specifies the tag to be used.
266 //
267 // bit 0: comments
268 // bit 1: highlights
269 // bit 2: selections
270 //
271 var startTags = [][]byte{
272 /* 000 */ []byte(``),
273 /* 001 */ []byte(`<span class ="comment">`),
274 /* 010 */ []byte(`<span class="highlight">`),
275 /* 011 */ []byte(`<span class="highlight-comment">`),
276 /* 100 */ []byte(`<span class="selection">`),
277 /* 101 */ []byte(`<span class="selection-comment">`),
278 /* 110 */ []byte(`<span class="selection-highlight">`),
279 /* 111 */ []byte(`<span class="selection-highlight-comment">`),
280 }
281
282 var endTag = []byte(`</span>`)
283
284
285 func selectionTag(w io.Writer, text []byte, selections int) {
286 if len(text) > 0 {
287 if selections < len(startTags) {
288 if tag := startTags[selections]; len(tag) > 0 {
289 w.Write(tag)
290 template.HTMLEscape(w, text)
291 w.Write(endTag)
292 return
293 }
294 }
295 template.HTMLEscape(w, text)
296 }
297 }
298
299
300 // FormatText HTML-escapes text and returns it wrapped in <pre> tags.
301 // Conscutive text segments are wrapped in HTML spans (with tags as
302 // defined by startTags and endTag) as follows:
303 //
304 // - if line >= 0, line numbers are printed before each line, starting
305 // with the value of line
306 // - if the text is Go source, comments get the "comment" span class
307 // - each occurrence of the regular expression pattern gets the "highlight"
308 // span class
309 // - text segments covered by selection get the "selection" span class
310 //
311 // Comments, highlights, and selections may overlap arbitrarily; the respective
312 // HTML span classes are specified in the startTags variable.
313 //
314 func FormatText(text []byte, line int, goSource bool, pattern string, selection Selection) []byte {
315 var buf bytes.Buffer
316 buf.WriteString("<pre>\n")
317
318 var comments, highlights Selection
319 if goSource {
320 comments = commentSelection(text)
321 }
322 if pattern != "" {
323 highlights = regexpSelection(text, pattern)
324 }
325 if comments != nil || highlights != nil || selection != nil {
326 var lineTag LinkWriter
327 if line >= 0 {
328 lineTag = func(w io.Writer, _ int, start bool) {
329 if start {
330 fmt.Fprintf(w, "<a id=\"L%d\"></a>%5d\t" , line, line)
331 line++
332 }
333 }
334 }
335 FormatSelections(&buf, text, lineTag, lineSelection(text), selec tionTag, comments, highlights, selection)
336 } else {
337 template.HTMLEscape(&buf, text)
338 }
339
340 buf.WriteString("</pre>\n")
341 return buf.Bytes()
342 }
LEFTRIGHT

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