{.section Accurate}
{.or}
<p>
- <span class="alert" style="font-size:120%">Indexing in progress - result may be inaccurate</span>
+ <span class="alert" style="font-size:120%">Indexing in progress - result may be inaccurate.</span>
</p>
{.end}
{.section Alt}
{.repeated section Files}
{.repeated section Groups}
{.repeated section Infos}
- <a href="/{File.Path|url-src}?h={Query|html-esc}#L{@|infoLine}">{File.Path|url-src}:{@|infoLine}</a>
+ <a href="/{File.Path|url-src}?h={Query|urlquery-esc}#L{@|infoLine}">{File.Path|url-src}:{@|infoLine}</a>
<pre>{@|infoSnippet}</pre>
{.end}
{.end}
{.repeated section @}
<h3 id="Local_{Pak.Path|url-pkg}">package <a href="/{Pak.Path|url-pkg}">{Pak.Name|html-esc}</a></h3>
{.repeated section Files}
- <a href="/{File.Path|url-src}?h={Query|html-esc}">{File.Path|url-src}</a>
+ <a href="/{File.Path|url-src}?h={Query|urlquery-esc}">{File.Path|url-src}</a>
<table class="layout">
{.repeated section Groups}
<tr>
<td align="left" width="4"></td>
<td>
{.repeated section Infos}
- <a href="/{File.Path|url-src}?h={Query|html-esc}#L{@|infoLine}">{@|infoLine}</a>
+ <a href="/{File.Path|url-src}?h={Query|urlquery-esc}#L{@|infoLine}">{@|infoLine}</a>
{.end}
</td>
</tr>
{.end}
{.end}
{.end}
-{.section Illegal}
- <p>
- <span class="alert" style="font-size:120%">Illegal query syntax</span>
- </p>
- <p>
- A legal query is a single identifier (such as <a href="search?q=ToLower">ToLower</a>)
- or a qualified identifier (such as <a href="search?q=math.Sin">math.Sin</a>).
- </p>
-{.end}
{.section Textual}
- <h2 id="Textual">Textual occurences</h2>
+ {.section Complete}
+ <h2 id="Textual">{Found|html-esc} textual occurences</h2>
+ {.or}
+ <h2 id="Textual">More than {Found|html-esc} textual occurences</h2>
+ <p>
+ <span class="alert" style="font-size:120%">Not all files or lines containing {Query|html-esc} are shown.</span>
+ </p>
+ {.end}
+ <p>
<table class="layout">
- <tr>
- <th align=left>File</th>
- <th align=left>Occurences</th>
- <th align=left>Lines</th>
- </tr>
{.repeated section @}
<tr>
- <td>
- <a href="/{Filename|url-src}?h={Query|html-esc}">{Filename|url-src}</a>:
+ <td align="left" valign="top">
+ <a href="/{Filename|url-src}?g={Query|urlquery-esc}">{Filename|url-src}</a>:
</td>
- {Lines|linelist}
+ <td align="left" width="4"></td>
+ <th align="left" valign="top">{Lines|numlines}</th>
+ <td align="left" width="4"></td>
+ <td align="left">{Lines Complete|linelist}</td>
</tr>
{.end}
+ {.section Complete}
+ {.or}
+ <tr><td align="left">...</td></tr>
+ {.end}
</table>
-{.end}
-{.section Complete}
-{.or}
- <p>
- <span class="alert" style="font-size:120%">Incomplete list of results</span>
</p>
{.end}
// for 0 <= i < s.idcount.
func (s *Styler) idList(fset *token.FileSet) []byte {
var buf bytes.Buffer
- fmt.Fprintln(&buf, "[")
+ buf.WriteString("[\n")
if s.idcount > 0 {
// invert objmap: create an array [id]obj from map[obj]id
}
}
- fmt.Fprintln(&buf, "]")
+ buf.WriteString("]\n")
return buf.Bytes()
}
}
-// Template formatter for the various "url-xxx" formats.
+// Template formatter for "urlquery-esc" format.
+func urlQueryEscFmt(w io.Writer, format string, x ...interface{}) {
+ var buf bytes.Buffer
+ writeAny(&buf, fileset(x), false, x[0])
+ template.HTMLEscape(w, []byte(http.URLEscape(string(buf.Bytes()))))
+}
+
+
+// Template formatter for the various "url-xxx" formats excluding url-esc.
func urlFmt(w io.Writer, format string, x ...interface{}) {
var path string
var line int
}
+// Template formatter for "numlines" format.
+func numlinesFmt(w io.Writer, format string, x ...interface{}) {
+ list := x[0].([]int)
+ fmt.Fprintf(w, "%d", len(list))
+}
+
+
// Template formatter for "linelist" format.
func linelistFmt(w io.Writer, format string, x ...interface{}) {
- const max = 20 // show at most this many lines
list := x[0].([]int)
- // print number of occurences
- fmt.Fprintf(w, "<td>%d</td>", len(list))
- // print actual lines
- // TODO(gri) should sort them
- for i, line := range list {
- if i < max {
- fmt.Fprintf(w, "<td>%d</td>", line)
- } else {
- fmt.Fprint(w, "<td>...</td>")
- break
- }
+ complete := x[1].(bool)
+
+ const max = 100 // show at most this many lines
+ if len(list) > max {
+ list = list[0:max]
+ complete = false
+ }
+ sort.SortInts(list)
+
+ for _, line := range list {
+ fmt.Fprintf(w, " %d", line)
+ }
+ if !complete {
+ fmt.Fprintf(w, " ...")
}
}
"html": htmlFmt,
"html-esc": htmlEscFmt,
"html-comment": htmlCommentFmt,
+ "urlquery-esc": urlQueryEscFmt,
"url-pkg": urlFmt,
"url-src": urlFmt,
"url-pos": urlFmt,
"time": timeFmt,
"dir/": dirslashFmt,
"localname": localnameFmt,
+ "numlines": numlinesFmt,
"linelist": linelistFmt,
}
}
+// HTMLSubst replaces all occurences of f in s with r and HTML-escapes
+// everything else in s (but not r). The result is written to w.
+//
+func HTMLSubst(w io.Writer, s, f, r []byte) {
+ for {
+ i := bytes.Index(s, f)
+ if i < 0 {
+ break
+ }
+ template.HTMLEscape(w, s[0:i])
+ w.Write(r)
+ s = s[i+len(f):]
+ }
+ template.HTMLEscape(w, s)
+}
+
+
+// highlight highlights all occurrences of h in s and writes the
+// HTML-escaped result to w.
+//
+func highlight(w io.Writer, s, h []byte) {
+ var r bytes.Buffer
+ r.WriteString(`<span class="highlight">`)
+ template.HTMLEscape(&r, h)
+ r.WriteString(`</span>`)
+ HTMLSubst(w, s, h, r.Bytes())
+}
+
+
func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
src, err := ioutil.ReadFile(abspath)
if err != nil {
}
var buf bytes.Buffer
- fmt.Fprintln(&buf, "<pre>")
- template.HTMLEscape(&buf, src)
- fmt.Fprintln(&buf, "</pre>")
+ buf.WriteString("<pre>\n")
+ g := r.FormValue("g")
+ if g != "" {
+ highlight(&buf, src, []byte(g))
+ } else {
+ template.HTMLEscape(&buf, src)
+ }
+ buf.WriteString("</pre>\n")
servePage(w, "Text file "+relpath, "", "", buf.Bytes())
}
return
case ".go":
+ if r.FormValue("g") != "" {
+ serveTextFile(w, r, abspath, relpath)
+ return
+ }
serveGoSource(w, r, abspath, relpath)
return
}
Query string
Hit *LookupResult // identifier occurences of Query
Alt *AltWords // alternative identifiers to look for
- Illegal bool // true if Query for identifier search has incorrect syntax
+ Found int // number of textual occurences found
Textual []Positions // textual occurences of Query
Complete bool // true if all textual occurences of Query are reported
Accurate bool // true if the index is not older than the indexed files
result.Query = query
if index, timestamp := searchIndex.get(); index != nil {
index := index.(*Index)
- result.Hit, result.Alt, result.Illegal = index.Lookup(query)
+ result.Hit, result.Alt, _ = index.Lookup(query)
// TODO(gri) should max be a flag?
const max = 5000 // show at most this many fulltext results
- result.Textual, result.Complete = index.LookupString(query, max)
+ result.Found, result.Textual, result.Complete = index.LookupString(query, max)
_, ts := fsModified.get()
result.Accurate = timestamp >= ts
}
if *verbose {
secs := float64((stop-start)/1e6) / 1e3
stats := index.Stats()
- log.Printf("index updated (%gs, %d bytes of source, %d files, %d unique words, %d spots)",
- secs, stats.Bytes, stats.Files, stats.Words, stats.Spots)
+ log.Printf("index updated (%gs, %d bytes of source, %d files, %d lines, %d unique words, %d spots)",
+ secs, stats.Bytes, stats.Files, stats.Lines, stats.Words, stats.Spots)
}
log.Printf("before GC: bytes = %d footprint = %d\n", runtime.MemStats.HeapAlloc, runtime.MemStats.Sys)
runtime.GC()
type Statistics struct {
Bytes int // total size of indexed source files
Files int // number of indexed source files
+ Lines int // number of lines (all files)
Words int // number of different identifiers
Spots int // number of identifier occurences
}
// (count real file size as opposed to using the padded x.sources.Len())
x.stats.Bytes += x.current.Size()
x.stats.Files++
+ x.stats.Lines += x.current.LineCount()
}
}
-// LookupString returns a list of positions where a string s is found
-// in the full text index and whether the result is complete or not.
-// At most n positions (filename and line) are returned. The result is
-// not complete if the index is not present or there are more than n
-// occurrences of s.
+// LookupString returns the number and list of positions where a string
+// s is found in the full text index and whether the result is complete
+// or not. At most n positions (filename and line) are returned (and thus
+// found <= n). The result is incomplete if the index is not present or
+// if there are more than n occurrences of s.
//
-func (x *Index) LookupString(s string, n int) (result []Positions, complete bool) {
+func (x *Index) LookupString(s string, n int) (found int, result []Positions, complete bool) {
if x.suffixes == nil {
return
}
} else {
offsets = offsets[0:n]
}
+ found = len(offsets)
// compute file names and lines and sort the list by filename
list := make(positionList, len(offsets))