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

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

Issue 1008042: code review 1008042: godoc: add codewalk support (Closed)
Left Patch Set: Created 14 years, 10 months ago
Right Patch Set: code review 1008042: godoc: add codewalk support Created 14 years, 10 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:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « src/cmd/godoc/Makefile ('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
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
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
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
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
LEFTRIGHT

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