LEFT | RIGHT |
1 // Copyright 2010 The Go Authors. All rights reserved. | 1 // Copyright 2010 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
| 4 |
| 5 // The /doc/codewalk/ tree is synthesized from codewalk descriptions, |
| 6 // files named $GOROOT/doc/codewalk/*.xml. |
| 7 // For an example and a description of the format, see |
| 8 // http://golang.org/doc/codewalk/codewalk or run godoc -http=:6060 |
| 9 // and see http://localhost:6060/doc/codewalk/codewalk . |
| 10 // That page is itself a codewalk; the source code for it is |
| 11 // $GOROOT/doc/codewalk/codewalk.xml. |
4 | 12 |
5 package main | 13 package main |
6 | 14 |
7 import ( | 15 import ( |
8 "container/vector" | 16 "container/vector" |
9 "fmt" | 17 "fmt" |
10 "http" | 18 "http" |
| 19 "io" |
11 "io/ioutil" | 20 "io/ioutil" |
12 "log" | 21 "log" |
13 "os" | 22 "os" |
14 pathutil "path" | |
15 "regexp" | 23 "regexp" |
16 "sort" | 24 "sort" |
17 "strconv" | 25 "strconv" |
18 "strings" | 26 "strings" |
19 "template" | 27 "template" |
20 "utf8" | 28 "utf8" |
21 "xml" | 29 "xml" |
22 ) | 30 ) |
23 | 31 |
| 32 |
| 33 // Handler for /doc/codewalk/ and below. |
24 func codewalk(c *http.Conn, r *http.Request) { | 34 func codewalk(c *http.Conn, r *http.Request) { |
25 relpath := r.URL.Path[len("/doc/codewalk/"):] | 35 relpath := r.URL.Path[len("/doc/codewalk/"):] |
26 abspath := absolutePath(r.URL.Path[1:], *goroot) | 36 abspath := absolutePath(r.URL.Path[1:], *goroot) |
27 » | 37 |
28 r.ParseForm() | 38 r.ParseForm() |
29 if f := r.FormValue("fileprint"); f != "" { | 39 if f := r.FormValue("fileprint"); f != "" { |
30 codewalkFileprint(c, r, f) | 40 codewalkFileprint(c, r, f) |
31 return | 41 return |
32 } | 42 } |
33 | 43 |
34 // If directory exists, serve list of code walks. | 44 // If directory exists, serve list of code walks. |
35 dir, err := os.Lstat(abspath) | 45 dir, err := os.Lstat(abspath) |
36 if err == nil && dir.IsDirectory() { | 46 if err == nil && dir.IsDirectory() { |
37 codewalkDir(c, r, relpath, abspath) | 47 codewalkDir(c, r, relpath, abspath) |
38 return | 48 return |
39 } | 49 } |
40 » | 50 |
41 // If file exists, serve using standard file server. | 51 // If file exists, serve using standard file server. |
42 if err == nil { | 52 if err == nil { |
43 serveFile(c, r) | 53 serveFile(c, r) |
44 return | 54 return |
45 } | 55 } |
46 | 56 |
47 » // Otherwise append .codewalk and hope to find | 57 » // Otherwise append .xml and hope to find |
48 // a codewalk description. | 58 // a codewalk description. |
49 » cw, err := loadCodewalk(abspath+".codewalk") | 59 » cw, err := loadCodewalk(abspath + ".xml") |
50 if err != nil { | 60 if err != nil { |
51 log.Stderr(err) | 61 log.Stderr(err) |
52 serveError(c, r, relpath, err) | 62 serveError(c, r, relpath, err) |
53 return | 63 return |
54 } | 64 } |
55 » | 65 |
56 b := applyTemplate(codewalkHTML, "codewalk", cw) | 66 b := applyTemplate(codewalkHTML, "codewalk", cw) |
57 » servePage(c, "Codewalk: " + cw.Title, "", "", b) | 67 » servePage(c, "Codewalk: "+cw.Title, "", "", b) |
58 } | 68 } |
59 | 69 |
60 type codewalkElem struct { | 70 |
61 » Name string | 71 // A Codewalk represents a single codewalk read from an XML file. |
62 » Title string | |
63 } | |
64 | |
65 func codewalkDir(c *http.Conn, r *http.Request, relpath, abspath string) { | |
66 » dir, err := ioutil.ReadDir(abspath) | |
67 » if err != nil { | |
68 » » log.Stderr(err) | |
69 » » serveError(c, r, relpath, err) | |
70 » » return | |
71 » } | |
72 » var v vector.Vector | |
73 » for _, fi := range dir { | |
74 » » _, name := pathutil.Split(fi.Name) | |
75 » » if fi.IsDirectory() { | |
76 » » » v.Push(&codewalkElem{name+"/", ""}) | |
77 » » } else if strings.HasSuffix(name, ".codewalk") { | |
78 » » » cw, err := loadCodewalk(abspath + "/" + fi.Name) | |
79 » » » if err != nil { | |
80 » » » » continue | |
81 » » » } | |
82 » » » v.Push(&codewalkElem{name[0:len(name)-len(".codewalk")],
cw.Title}) | |
83 » » } | |
84 » } | |
85 »······· | |
86 » b := applyTemplate(codewalkdirHTML, "codewalkdir", v) | |
87 » servePage(c, "Codewalks", "", "", b) | |
88 } | |
89 | |
90 func codewalkFileprint(c *http.Conn, r *http.Request, f string) { | |
91 » abspath := absolutePath(f, *goroot) | |
92 » data, err := ioutil.ReadFile(abspath) | |
93 » if err != nil { | |
94 » » serveError(c, r, f, err) | |
95 » » return | |
96 » } | |
97 » lo, _ := strconv.Atoi(r.FormValue("lo")) | |
98 » hi, _ := strconv.Atoi(r.FormValue("hi")) | |
99 » if hi < lo { | |
100 » » hi = lo | |
101 » } | |
102 » lo = lineToByte(data, lo) | |
103 » hi = lineToByte(data, hi+1) | |
104 | |
105 » // Put the mark 4 lines before lo for context. | |
106 » n := 4 | |
107 » mark := lo | |
108 » for ; mark > 0 && n > 0; mark-- { | |
109 » » if data[mark-1] == '\n' { | |
110 » » » if n--; n == 0 { | |
111 » » » » break | |
112 » » » } | |
113 » » } | |
114 » } | |
115 | |
116 » c.Write([]byte(`<style type="text/css">@import "/doc/codewalk/codewalk.c
ss";</style><pre>`)) | |
117 » template.HTMLEscape(c, data[0:mark]) | |
118 » c.Write([]byte("<a name='mark'></a>")) | |
119 » template.HTMLEscape(c, data[mark:lo]) | |
120 » if lo < hi { | |
121 » » c.Write([]byte("<div class='codewalkhighlight'>")) | |
122 » » template.HTMLEscape(c, data[lo:hi]) | |
123 » » c.Write([]byte("</div>")) | |
124 » } | |
125 » template.HTMLEscape(c, data[hi:]) | |
126 » c.Write([]byte("</pre>")) | |
127 } | |
128 | |
129 | |
130 type Codewalk struct { | 72 type Codewalk struct { |
131 Title string "attr" | 73 Title string "attr" |
132 » File []string | 74 » File []string |
133 » Step []*Codestep | 75 » Step []*Codestep |
134 } | 76 } |
135 | 77 |
| 78 |
| 79 // A Codestep is a single step in a codewalk. |
136 type Codestep struct { | 80 type Codestep struct { |
137 // Filled in from XML | 81 // Filled in from XML |
138 » Src string "attr" | 82 » Src string "attr" |
139 Title string "attr" | 83 Title string "attr" |
140 » XML string "innerxml" | 84 » XML string "innerxml" |
141 | 85 |
142 // Derived from Src; not in XML. | 86 // Derived from Src; not in XML. |
143 » Err os.Error | 87 » Err os.Error |
144 » File string | 88 » File string |
145 » Lo int | 89 » Lo int |
146 LoByte int | 90 LoByte int |
147 » Hi int | 91 » Hi int |
148 HiByte int | 92 HiByte int |
149 » Data []byte | 93 » Data []byte |
150 } | 94 } |
| 95 |
151 | 96 |
152 // String method for printing in template. | 97 // String method for printing in template. |
153 // Formats file address nicely. | 98 // Formats file address nicely. |
154 func (st *Codestep) String() string { | 99 func (st *Codestep) String() string { |
155 s := st.File | 100 s := st.File |
156 if st.Lo != 0 || st.Hi != 0 { | 101 if st.Lo != 0 || st.Hi != 0 { |
157 s += fmt.Sprintf(":%d", st.Lo) | 102 s += fmt.Sprintf(":%d", st.Lo) |
158 if st.Lo != st.Hi { | 103 if st.Lo != st.Hi { |
159 s += fmt.Sprintf(",%d", st.Hi) | 104 s += fmt.Sprintf(",%d", st.Hi) |
160 } | 105 } |
161 } | 106 } |
162 return s | 107 return s |
163 } | 108 } |
164 | 109 |
| 110 |
| 111 // loadCodewalk reads a codewalk from the named XML file. |
165 func loadCodewalk(file string) (*Codewalk, os.Error) { | 112 func loadCodewalk(file string) (*Codewalk, os.Error) { |
166 f, err := os.Open(file, os.O_RDONLY, 0) | 113 f, err := os.Open(file, os.O_RDONLY, 0) |
167 if err != nil { | 114 if err != nil { |
168 return nil, err | 115 return nil, err |
169 } | 116 } |
170 defer f.Close() | 117 defer f.Close() |
171 cw := new(Codewalk) | 118 cw := new(Codewalk) |
172 p := xml.NewParser(f) | 119 p := xml.NewParser(f) |
173 p.Entity = xml.HTMLEntity | 120 p.Entity = xml.HTMLEntity |
174 err = p.Unmarshal(cw, nil) | 121 err = p.Unmarshal(cw, nil) |
175 if err != nil { | 122 if err != nil { |
176 return nil, &os.PathError{"parsing", file, err} | 123 return nil, &os.PathError{"parsing", file, err} |
177 } | 124 } |
178 » | 125 |
| 126 » // Compute file list, evaluate line numbers for addresses. |
179 m := make(map[string]bool) | 127 m := make(map[string]bool) |
180 for _, st := range cw.Step { | 128 for _, st := range cw.Step { |
181 i := strings.Index(st.Src, ":") | 129 i := strings.Index(st.Src, ":") |
182 if i < 0 { | 130 if i < 0 { |
183 i = len(st.Src) | 131 i = len(st.Src) |
184 } | 132 } |
185 file := st.Src[0:i] | 133 file := st.Src[0:i] |
186 data, err := ioutil.ReadFile(absolutePath(file, *goroot)) | 134 data, err := ioutil.ReadFile(absolutePath(file, *goroot)) |
187 if err != nil { | 135 if err != nil { |
188 st.Err = err | 136 st.Err = err |
189 continue | 137 continue |
190 } | 138 } |
191 if i < len(st.Src) { | 139 if i < len(st.Src) { |
192 » » » lo, hi, err := addrToByte(st.Src[i+1:], 0, data) | 140 » » » lo, hi, err := addrToByteRange(st.Src[i+1:], 0, data) |
193 if err != nil { | 141 if err != nil { |
194 st.Err = err | 142 st.Err = err |
195 continue | 143 continue |
196 } | 144 } |
197 // Expand match to line boundaries. | 145 // Expand match to line boundaries. |
198 for lo > 0 && data[lo-1] != '\n' { | 146 for lo > 0 && data[lo-1] != '\n' { |
199 lo-- | 147 lo-- |
200 } | 148 } |
201 for hi < len(data) && (hi == 0 || data[hi-1] != '\n') { | 149 for hi < len(data) && (hi == 0 || data[hi-1] != '\n') { |
202 hi++ | 150 hi++ |
203 } | 151 } |
204 st.Lo = byteToLine(data, lo) | 152 st.Lo = byteToLine(data, lo) |
205 st.Hi = byteToLine(data, hi-1) | 153 st.Hi = byteToLine(data, hi-1) |
206 } | 154 } |
207 st.Data = data | 155 st.Data = data |
208 st.File = file | 156 st.File = file |
209 m[file] = true | 157 m[file] = true |
210 } | 158 } |
211 » | 159 |
212 // Make list of files | 160 // Make list of files |
213 cw.File = make([]string, len(m)) | 161 cw.File = make([]string, len(m)) |
214 i := 0 | 162 i := 0 |
215 for f := range m { | 163 for f := range m { |
216 cw.File[i] = f | 164 cw.File[i] = f |
217 i++ | 165 i++ |
218 } | 166 } |
219 sort.SortStrings(cw.File) | 167 sort.SortStrings(cw.File) |
220 | 168 |
221 return cw, nil | 169 return cw, nil |
222 } | 170 } |
223 | 171 |
224 func addrToByte(addr string, start int, data []byte) (lo, hi int, err os.Error)
{ | 172 |
| 173 // codewalkDir serves the codewalk directory listing. |
| 174 // It scans the directory for subdirectories or files named *.xml |
| 175 // and prepares a table. |
| 176 func codewalkDir(c *http.Conn, r *http.Request, relpath, abspath string) { |
| 177 » type elem struct { |
| 178 » » Name string |
| 179 » » Title string |
| 180 » } |
| 181 |
| 182 » dir, err := ioutil.ReadDir(abspath) |
| 183 » if err != nil { |
| 184 » » log.Stderr(err) |
| 185 » » serveError(c, r, relpath, err) |
| 186 » » return |
| 187 » } |
| 188 » var v vector.Vector |
| 189 » for _, fi := range dir { |
| 190 » » if fi.IsDirectory() { |
| 191 » » » v.Push(&elem{fi.Name + "/", ""}) |
| 192 » » } else if strings.HasSuffix(fi.Name, ".xml") { |
| 193 » » » cw, err := loadCodewalk(abspath + "/" + fi.Name) |
| 194 » » » if err != nil { |
| 195 » » » » continue |
| 196 » » » } |
| 197 » » » v.Push(&elem{fi.Name[0 : len(fi.Name)-len(".xml")], cw.T
itle}) |
| 198 » » } |
| 199 » } |
| 200 |
| 201 » b := applyTemplate(codewalkdirHTML, "codewalkdir", v) |
| 202 » servePage(c, "Codewalks", "", "", b) |
| 203 } |
| 204 |
| 205 |
| 206 // codewalkFileprint serves requests with ?fileprint=f&lo=lo&hi=hi. |
| 207 // The filename f has already been retrieved and is passed as an argument. |
| 208 // Lo and hi are the numbers of the first and last line to highlight |
| 209 // in the response. This format is used for the middle window pane |
| 210 // of the codewalk pages. It is a separate iframe and does not get |
| 211 // the usual godoc HTML wrapper. |
| 212 func codewalkFileprint(c *http.Conn, r *http.Request, f string) { |
| 213 » abspath := absolutePath(f, *goroot) |
| 214 » data, err := ioutil.ReadFile(abspath) |
| 215 » if err != nil { |
| 216 » » serveError(c, r, f, err) |
| 217 » » return |
| 218 » } |
| 219 » lo, _ := strconv.Atoi(r.FormValue("lo")) |
| 220 » hi, _ := strconv.Atoi(r.FormValue("hi")) |
| 221 » if hi < lo { |
| 222 » » hi = lo |
| 223 » } |
| 224 » lo = lineToByte(data, lo) |
| 225 » hi = lineToByte(data, hi+1) |
| 226 |
| 227 » // Put the mark 4 lines before lo, so that the iframe |
| 228 » // shows a few lines of context before the highlighted |
| 229 » // section. |
| 230 » n := 4 |
| 231 » mark := lo |
| 232 » for ; mark > 0 && n > 0; mark-- { |
| 233 » » if data[mark-1] == '\n' { |
| 234 » » » if n--; n == 0 { |
| 235 » » » » break |
| 236 » » » } |
| 237 » » } |
| 238 » } |
| 239 |
| 240 » io.WriteString(c, `<style type="text/css">@import "/doc/codewalk/codewal
k.css";</style><pre>`) |
| 241 » template.HTMLEscape(c, data[0:mark]) |
| 242 » io.WriteString(c, "<a name='mark'></a>") |
| 243 » template.HTMLEscape(c, data[mark:lo]) |
| 244 » if lo < hi { |
| 245 » » io.WriteString(c, "<div class='codewalkhighlight'>") |
| 246 » » template.HTMLEscape(c, data[lo:hi]) |
| 247 » » io.WriteString(c, "</div>") |
| 248 » } |
| 249 » template.HTMLEscape(c, data[hi:]) |
| 250 » io.WriteString(c, "</pre>") |
| 251 } |
| 252 |
| 253 |
| 254 // addrToByte evaluates the given address starting at offset start in data. |
| 255 // It returns the lo and hi byte offset of the matched region within data. |
| 256 // See http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II |
| 257 // for details on the syntax. |
| 258 func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err os.Er
ror) { |
225 var ( | 259 var ( |
226 » » dir byte | 260 » » dir byte |
227 » » prevc byte | 261 » » prevc byte |
228 charOffset bool | 262 charOffset bool |
229 ) | 263 ) |
230 lo = start | 264 lo = start |
231 hi = start | 265 hi = start |
232 for addr != "" && err == nil { | 266 for addr != "" && err == nil { |
233 c := addr[0] | 267 c := addr[0] |
234 switch c { | 268 switch c { |
235 default: | 269 default: |
236 err = os.NewError("invalid address syntax near " + strin
g(c)) | 270 err = os.NewError("invalid address syntax near " + strin
g(c)) |
237 case ',': | 271 case ',': |
238 if len(addr) == 1 { | 272 if len(addr) == 1 { |
239 hi = len(data) | 273 hi = len(data) |
240 } else { | 274 } else { |
241 » » » » _, hi, err = addrToByte(addr[1:], hi, data) | 275 » » » » _, hi, err = addrToByteRange(addr[1:], hi, data) |
242 } | 276 } |
243 return | 277 return |
244 | 278 |
245 case '+', '-': | 279 case '+', '-': |
246 if prevc == '+' || prevc == '-' { | 280 if prevc == '+' || prevc == '-' { |
247 lo, hi, err = addrNumber(data, lo, hi, prevc, 1,
charOffset) | 281 lo, hi, err = addrNumber(data, lo, hi, prevc, 1,
charOffset) |
248 } | 282 } |
249 dir = c | 283 dir = c |
250 | 284 |
251 case '$': | 285 case '$': |
(...skipping 17 matching lines...) Expand all Loading... |
269 n, err = strconv.Atoi(addr[0:i]) | 303 n, err = strconv.Atoi(addr[0:i]) |
270 if err != nil { | 304 if err != nil { |
271 break | 305 break |
272 } | 306 } |
273 lo, hi, err = addrNumber(data, lo, hi, dir, n, charOffse
t) | 307 lo, hi, err = addrNumber(data, lo, hi, dir, n, charOffse
t) |
274 dir = 0 | 308 dir = 0 |
275 charOffset = false | 309 charOffset = false |
276 prevc = c | 310 prevc = c |
277 addr = addr[i:] | 311 addr = addr[i:] |
278 continue | 312 continue |
279 » » | 313 |
280 » » case '?': | |
281 » » » dir = '-' | |
282 » » » fallthrough | |
283 case '/': | 314 case '/': |
284 var i, j int | 315 var i, j int |
285 Regexp: | 316 Regexp: |
286 for i = 1; i < len(addr); i++ { | 317 for i = 1; i < len(addr); i++ { |
287 switch addr[i] { | 318 switch addr[i] { |
288 case '\\': | 319 case '\\': |
289 i++ | 320 i++ |
290 case '/': | 321 case '/': |
291 » » » » » j = i+1 | 322 » » » » » j = i + 1 |
292 break Regexp | 323 break Regexp |
293 } | 324 } |
294 } | 325 } |
295 if j == 0 { | 326 if j == 0 { |
296 j = i | 327 j = i |
297 } | 328 } |
298 pattern := addr[1:i] | 329 pattern := addr[1:i] |
299 lo, hi, err = addrRegexp(data, lo, hi, dir, pattern) | 330 lo, hi, err = addrRegexp(data, lo, hi, dir, pattern) |
300 prevc = c | 331 prevc = c |
301 addr = addr[j:] | 332 addr = addr[j:] |
302 continue | 333 continue |
303 } | 334 } |
304 prevc = c | 335 prevc = c |
305 addr = addr[1:] | 336 addr = addr[1:] |
306 } | 337 } |
307 | 338 |
308 if err == nil && dir != 0 { | 339 if err == nil && dir != 0 { |
309 lo, hi, err = addrNumber(data, lo, hi, dir, 1, charOffset) | 340 lo, hi, err = addrNumber(data, lo, hi, dir, 1, charOffset) |
310 } | 341 } |
311 if err != nil { | 342 if err != nil { |
312 return 0, 0, err | 343 return 0, 0, err |
313 } | 344 } |
314 » return lo, hi, nil» | 345 » return lo, hi, nil |
315 } | 346 } |
316 | 347 |
| 348 |
| 349 // addrNumber applies the given dir, n, and charOffset to the address lo, hi. |
| 350 // dir is '+' or '-', n is the count, and charOffset is true if the syntax |
| 351 // used was #n. Applying +n (or +#n) means to advance n lines |
| 352 // (or characters) after hi. Applying -n (or -#n) means to back up n lines |
| 353 // (or characters) before lo. |
| 354 // The return value is the new lo, hi. |
317 func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int,
int, os.Error) { | 355 func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int,
int, os.Error) { |
318 switch dir { | 356 switch dir { |
319 case 0: | 357 case 0: |
320 lo = 0 | 358 lo = 0 |
321 hi = 0 | 359 hi = 0 |
322 fallthrough | 360 fallthrough |
323 | 361 |
324 case '+': | 362 case '+': |
325 if charOffset { | 363 if charOffset { |
326 pos := hi | 364 pos := hi |
(...skipping 15 matching lines...) Expand all Loading... |
342 lo = hi | 380 lo = hi |
343 if n == 0 { | 381 if n == 0 { |
344 return lo, hi, nil | 382 return lo, hi, nil |
345 } | 383 } |
346 for ; hi < len(data); hi++ { | 384 for ; hi < len(data); hi++ { |
347 if data[hi] != '\n' { | 385 if data[hi] != '\n' { |
348 continue | 386 continue |
349 } | 387 } |
350 switch n--; n { | 388 switch n--; n { |
351 case 1: | 389 case 1: |
352 » » » » lo = hi+1 | 390 » » » » lo = hi + 1 |
353 case 0: | 391 case 0: |
354 » » » » return lo, hi+1, nil | 392 » » » » return lo, hi + 1, nil |
355 » » » } | 393 » » » } |
356 » » } | 394 » » } |
357 » | 395 |
358 case '-': | 396 case '-': |
359 if charOffset { | 397 if charOffset { |
360 // Scan backward for bytes that are not UTF-8 continuati
on bytes. | 398 // Scan backward for bytes that are not UTF-8 continuati
on bytes. |
361 pos := lo | 399 pos := lo |
362 for ; pos > 0 && n > 0; pos-- { | 400 for ; pos > 0 && n > 0; pos-- { |
363 if data[pos]&0xc0 != 0x80 { | 401 if data[pos]&0xc0 != 0x80 { |
364 n-- | 402 n-- |
365 } | 403 } |
366 } | 404 } |
367 if n == 0 { | 405 if n == 0 { |
(...skipping 14 matching lines...) Expand all Loading... |
382 continue | 420 continue |
383 } | 421 } |
384 switch n--; n { | 422 switch n--; n { |
385 case 1: | 423 case 1: |
386 hi = lo | 424 hi = lo |
387 case 0: | 425 case 0: |
388 return lo, hi, nil | 426 return lo, hi, nil |
389 } | 427 } |
390 } | 428 } |
391 } | 429 } |
392 » | 430 |
393 return 0, 0, os.NewError("address out of range") | 431 return 0, 0, os.NewError("address out of range") |
394 } | 432 } |
395 | 433 |
| 434 |
| 435 // addrRegexp searches for pattern in the given direction starting at lo, hi. |
| 436 // The direction dir is '+' (search forward from hi) or '-' (search backward fro
m lo). |
| 437 // Backward searches are unimplemented. |
396 func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, os
.Error) { | 438 func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, os
.Error) { |
397 re, err := regexp.Compile(pattern) | 439 re, err := regexp.Compile(pattern) |
398 if err != nil { | 440 if err != nil { |
399 return 0, 0, err | 441 return 0, 0, err |
400 } | 442 } |
401 if dir == '-' { | 443 if dir == '-' { |
| 444 // Could implement reverse search using binary search |
| 445 // through file, but that seems like overkill. |
402 return 0, 0, os.NewError("reverse search not implemented") | 446 return 0, 0, os.NewError("reverse search not implemented") |
403 } | 447 } |
404 m := re.Execute(data[hi:]) | 448 m := re.Execute(data[hi:]) |
405 if len(m) > 0 { | 449 if len(m) > 0 { |
406 m[0] += hi | 450 m[0] += hi |
407 m[1] += hi | 451 m[1] += hi |
408 } else if hi > 0 { | 452 } else if hi > 0 { |
| 453 // No match. Wrap to beginning of data. |
409 m = re.Execute(data) | 454 m = re.Execute(data) |
410 } | 455 } |
411 if len(m) == 0 { | 456 if len(m) == 0 { |
412 return 0, 0, os.NewError("no match for " + pattern) | 457 return 0, 0, os.NewError("no match for " + pattern) |
413 } | 458 } |
414 » return m[0], m[1], nil» | 459 » return m[0], m[1], nil |
415 } | 460 } |
| 461 |
416 | 462 |
417 // lineToByte returns the byte index of the first byte of line n. | 463 // lineToByte returns the byte index of the first byte of line n. |
418 // Line numbers begin at 1. | 464 // Line numbers begin at 1. |
419 func lineToByte(data []byte, n int) int { | 465 func lineToByte(data []byte, n int) int { |
420 if n <= 1 { | 466 if n <= 1 { |
421 return 0 | 467 return 0 |
422 } | 468 } |
423 n-- | 469 n-- |
424 for i, c := range data { | 470 for i, c := range data { |
425 if c == '\n' { | 471 if c == '\n' { |
426 if n--; n == 0 { | 472 if n--; n == 0 { |
427 » » » » return i+1 | 473 » » » » return i + 1 |
428 } | 474 } |
429 } | 475 } |
430 } | 476 } |
431 return len(data) | 477 return len(data) |
432 } | 478 } |
| 479 |
433 | 480 |
434 // byteToLine returns the number of the line containing the byte at index i. | 481 // byteToLine returns the number of the line containing the byte at index i. |
435 func byteToLine(data []byte, i int) int { | 482 func byteToLine(data []byte, i int) int { |
436 l := 1 | 483 l := 1 |
437 for j, c := range data { | 484 for j, c := range data { |
438 if j == i { | 485 if j == i { |
439 return l | 486 return l |
440 } | 487 } |
441 if c == '\n' { | 488 if c == '\n' { |
442 l++ | 489 l++ |
443 } | 490 } |
444 } | 491 } |
445 return l | 492 return l |
446 } | 493 } |
447 | |
LEFT | RIGHT |