1
0
mirror of https://github.com/golang/go synced 2024-11-13 17:00:22 -07:00

code search for godoc:

- added goroutine to automatically index in the background
- added handler for search requests
- added search box to top-level godoc template
- added search.html template for the display of search results
- changes to spec.go because of name conflicts
- added extra styles to style.css (for shorter .html files)

R=rsc
http://go/go-review/1014011
This commit is contained in:
Robert Griesemer 2009-10-27 10:34:31 -07:00
parent f529224039
commit 32810a5ded
6 changed files with 297 additions and 63 deletions

View File

@ -151,10 +151,54 @@ div#linkList li.navhead {
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/* Styles used by go/printer Styler implementations. */ /* Styles used by go/printer Styler implementations. */
a.noline {
text-decoration: none;
}
span.comment { span.comment {
color: #0000a0; color: #0000a0;
} }
span.highlight { span.highlight {
background-color: #00ff00; background-color: #81F781;
}
/* ------------------------------------------------------------------------- */
/* Styles used by infoClassFmt */
a.import {
text-decoration: none;
background-color: #D8D8D8;
}
a.const {
text-decoration: none;
background-color: #F5A9A9;
}
a.type {
text-decoration: none;
background-color: #F2F5A9;
}
a.var {
text-decoration: none;
background-color: #A9F5A9;
}
a.func {
text-decoration: none;
background-color: #A9D0F5;
}
a.method {
text-decoration: none;
background-color: #D0A9F5;
}
a.use {
text-decoration: none;
color: #FFFFFF;
background-color: #5858FA;
} }

View File

@ -4,7 +4,7 @@
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>{title}</title> <title>{Title}</title>
<link rel="stylesheet" type="text/css" href="/doc/style.css"> <link rel="stylesheet" type="text/css" href="/doc/style.css">
<script type="text/javascript" src="/doc/godocs.js"></script> <script type="text/javascript" src="/doc/godocs.js"></script>
@ -37,29 +37,35 @@
<li><a href="/doc/go_lang_faq.html">Language Design FAQ</a></li> <li><a href="/doc/go_lang_faq.html">Language Design FAQ</a></li>
<li><a href="/doc/go_for_cpp_programmers.html">Go for C++ Programmers</a></li> <li><a href="/doc/go_for_cpp_programmers.html">Go for C++ Programmers</a></li>
<li class="blank">&nbsp;</li>
<li class="navhead">Programming</li>
<li><a href="/pkg">Package documentation</a></li>
<li class="blank">&nbsp;</li> <li class="blank">&nbsp;</li>
<li class="navhead">How To</li> <li class="navhead">How To</li>
<li><a href="/doc/install.html">Install Go</a></li> <li><a href="/doc/install.html">Install Go</a></li>
<li><a href="/doc/contribute.html">Contribute code</a></li> <li><a href="/doc/contribute.html">Contribute code</a></li>
<li class="blank">&nbsp;</li>
<li class="navhead">Programming</li>
<li><a href="/pkg">Package documentation</a></li>
<li class="blank">&nbsp;</li>
<li class="navhead">Go code search</li>
<form method="GET" action="/search" class="search">
<input name="q" value="{Query}" size="25" />
<input type="submit" value="Go" />
<li class="blank">&nbsp;</li> <li class="blank">&nbsp;</li>
<li class="navhead">Last update</li> <li class="navhead">Last update</li>
<li>{timestamp}</li> <li>{Timestamp}</li>
</ul> </ul>
</div> </div>
<div id="content"> <div id="content">
<h1>{title}</h1> <h1>{Title}</h1>
<!-- The Table of Contents is automatically inserted in this <div>. <!-- The Table of Contents is automatically inserted in this <div>.
Do not delete this <div>. --> Do not delete this <div>. -->
<div id="nav"></div> <div id="nav"></div>
{content} {Content}
</div> </div>
<div id="footer"> <div id="footer">

65
lib/godoc/search.html Normal file
View File

@ -0,0 +1,65 @@
<!--
Copyright 2009 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
{.section Accurate}
{.or}
<p>
<span class="alert" style="font-size:120%">Indexing in progress - result may be inaccurate</span>
</p>
{.end}
{.section Alt}
<p>
<span class="alert" style="font-size:120%">Did you mean: </span>
{.repeated section Alts}
<a href="search?q={@|html}" style="font-size:120%">{@|html}</a>
{.end}
</p>
{.end}
{.section Hit}
{.section Decls}
<h2>Package-level declarations</h2>
{.repeated section @}
<h3>package {Pak.Name|html}</h3>
{.repeated section Files}
{.repeated section Infos}
<a href="{File.Path|html}?h={Query|html}#L{@|infoLine}">{File.Path|html}:{@|infoLine}</a>
<pre>{@|infoSnippet}</pre>
{.end}
{.end}
{.end}
{.end}
{.section Others}
<h2>Local declarations and uses</h2>
<p>
Legend:
{.repeated section Legend}
<a class="{@}">{@}</a>
{.end}
</p>
{.repeated section @}
<h3>package {Pak.Name|html}</h3>
<table border="0" cellspacing="2">
{.repeated section Files}
<tr>
<td valign="top">
<a href="{File.Path|html}?h={Query|html}" class="noline">{File.Path|html}:</a>
</td>
<td>
{.repeated section Infos}
<a href="{File.Path|html}?h={Query|html}#L{@|infoLine}" class="{@|infoClass}">{@|infoLine}</a>
{.end}
</td>
</tr>
{.end}
</table>
{.end}
{.end}
{.or}
<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}

View File

@ -7,6 +7,8 @@ include $(GOROOT)/src/Make.$(GOARCH)
TARG=godoc TARG=godoc
GOFILES=\ GOFILES=\
godoc.go\ godoc.go\
index.go\
snippet.go\
spec.go\ spec.go\
include $(GOROOT)/src/Make.cmd include $(GOROOT)/src/Make.cmd

View File

@ -27,26 +27,26 @@
package main package main
import ( import (
"bytes"; "bytes";
"container/vector"; "container/vector";
"flag"; "flag";
"fmt"; "fmt";
"go/ast"; "go/ast";
"go/doc"; "go/doc";
"go/parser"; "go/parser";
"go/printer"; "go/printer";
"go/scanner"; "go/scanner";
"go/token"; "go/token";
"http"; "http";
"io"; "io";
"log"; "log";
"os"; "os";
pathutil "path"; pathutil "path";
"sort"; "sort";
"strings"; "strings";
"sync"; "sync";
"template"; "template";
"time"; "time";
) )
@ -59,9 +59,9 @@ const Pkg = "/pkg/" // name for auto-generated package documentation tree
// An RWValue wraps a value and permits mutually exclusive // An RWValue wraps a value and permits mutually exclusive
// access to it and records the time the value was last set. // access to it and records the time the value was last set.
type RWValue struct { type RWValue struct {
mutex sync.RWMutex; mutex sync.RWMutex;
value interface{}; value interface{};
timestamp int64; // time of last set(), in seconds since epoch timestamp int64; // time of last set(), in seconds since epoch
} }
@ -138,7 +138,12 @@ func init() {
func isGoFile(dir *os.Dir) bool { func isGoFile(dir *os.Dir) bool {
return dir.IsRegular() && return dir.IsRegular() &&
!strings.HasPrefix(dir.Name, ".") && // ignore .files !strings.HasPrefix(dir.Name, ".") && // ignore .files
pathutil.Ext(dir.Name) == ".go" && pathutil.Ext(dir.Name) == ".go";
}
func isPkgFile(dir *os.Dir) bool {
return isGoFile(dir) &&
!strings.HasSuffix(dir.Name, "_test.go"); // ignore test files !strings.HasSuffix(dir.Name, "_test.go"); // ignore test files
} }
@ -231,7 +236,7 @@ func (s *Styler) LineTag(line int) (text []byte, tag printer.HtmlTag) {
} }
func (s *Styler) Comment(c *ast.Comment, line []byte) (text []byte, tag printer.HtmlTag) { func (s *Styler) Comment(c *ast.Comment, line []byte) (text []byte, tag printer.HtmlTag) {
text = line; text = line;
// minimal syntax-coloring of comments for now - people will want more // minimal syntax-coloring of comments for now - people will want more
// (don't do anything more until there's a button to turn it on/off) // (don't do anything more until there's a button to turn it on/off)
@ -240,13 +245,13 @@ func (s *Styler) Comment(c *ast.Comment, line []byte) (text []byte, tag printer
} }
func (s *Styler) BasicLit(x *ast.BasicLit) (text []byte, tag printer.HtmlTag) { func (s *Styler) BasicLit(x *ast.BasicLit) (text []byte, tag printer.HtmlTag) {
text = x.Value; text = x.Value;
return; return;
} }
func (s *Styler) Ident(id *ast.Ident) (text []byte, tag printer.HtmlTag) { func (s *Styler) Ident(id *ast.Ident) (text []byte, tag printer.HtmlTag) {
text = strings.Bytes(id.Value); text = strings.Bytes(id.Value);
if s.highlight == id.Value { if s.highlight == id.Value {
tag = printer.HtmlTag{"<span class=highlight>", "</span>"}; tag = printer.HtmlTag{"<span class=highlight>", "</span>"};
@ -255,23 +260,22 @@ func (s *Styler) Ident(id *ast.Ident) (text []byte, tag printer.HtmlTag) {
} }
func (s *Styler) Token(tok token.Token) (text []byte, tag printer.HtmlTag) { func (s *Styler) Token(tok token.Token) (text []byte, tag printer.HtmlTag) {
text = strings.Bytes(tok.String()); text = strings.Bytes(tok.String());
return; return;
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Templates // Templates
// Write an AST-node to w; optionally html-escaped. // Write an AST-node to w; optionally html-escaped.
func writeNode(w io.Writer, node interface{}, html bool, style printer.Styler) { func writeNode(w io.Writer, node interface{}, html bool, styler printer.Styler) {
mode := printer.UseSpaces; mode := printer.UseSpaces;
if html { if html {
mode |= printer.GenHTML; mode |= printer.GenHTML;
} }
(&printer.Config{mode, *tabwidth, style}).Fprint(w, node); (&printer.Config{mode, *tabwidth, styler}).Fprint(w, node);
} }
@ -344,11 +348,55 @@ func linkFmt(w io.Writer, x interface{}, format string) {
} }
var infoClasses = [nKinds]string{
"import", // ImportDecl
"const", // ConstDecl
"type", // TypeDecl
"var", // VarDecl
"func", // FuncDecl
"method", // MethodDecl
"use", // Use
}
// Template formatter for "infoClass" format.
func infoClassFmt(w io.Writer, x interface{}, format string) {
fmt.Fprintf(w, infoClasses[x.(SpotInfo).Kind()]);
}
// Template formatter for "infoLine" format.
func infoLineFmt(w io.Writer, x interface{}, format string) {
info := x.(SpotInfo);
line := info.Lori();
if info.IsIndex() {
index, _ := searchIndex.get();
line = index.(*Index).Snippet(line).Line;
}
fmt.Fprintf(w, "%d", line);
}
// Template formatter for "infoSnippet" format.
func infoSnippetFmt(w io.Writer, x interface{}, format string) {
info := x.(SpotInfo);
text := `<span class="alert">no snippet text available</span>`;
if info.IsIndex() {
index, _ := searchIndex.get();
text = index.(*Index).Snippet(info.Lori()).Text;
}
fmt.Fprintf(w, "%s", text);
}
var fmap = template.FormatterMap{ var fmap = template.FormatterMap{
"": textFmt, "": textFmt,
"html": htmlFmt, "html": htmlFmt,
"html-comment": htmlCommentFmt, "html-comment": htmlCommentFmt,
"link": linkFmt, "link": linkFmt,
"infoClass": infoClassFmt,
"infoLine": infoLineFmt,
"infoSnippet": infoSnippetFmt,
} }
@ -371,7 +419,8 @@ var (
packageHtml, packageHtml,
packageText, packageText,
parseerrorHtml, parseerrorHtml,
parseerrorText *template.Template; parseerrorText,
searchHtml *template.Template;
) )
func readTemplates() { func readTemplates() {
@ -382,24 +431,27 @@ func readTemplates() {
packageText = readTemplate("package.txt"); packageText = readTemplate("package.txt");
parseerrorHtml = readTemplate("parseerror.html"); parseerrorHtml = readTemplate("parseerror.html");
parseerrorText = readTemplate("parseerror.txt"); parseerrorText = readTemplate("parseerror.txt");
searchHtml = readTemplate("search.html");
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Generic HTML wrapper // Generic HTML wrapper
func servePage(c *http.Conn, title, content interface{}) { func servePage(c *http.Conn, title, query string, content []byte) {
type Data struct { type Data struct {
title interface{}; Title string;
timestamp string; Timestamp string;
content interface{}; Query string;
Content []byte;
} }
_, ts := syncTime.get(); _, ts := syncTime.get();
d := Data{ d := Data{
title: title, Title: title,
timestamp: time.SecondsToLocalTime(ts).String(), Timestamp: time.SecondsToLocalTime(ts).String(),
content: content, Query: query,
Content: content,
}; };
if err := godocHtml.Execute(&d, c); err != nil { if err := godocHtml.Execute(&d, c); err != nil {
@ -451,7 +503,7 @@ func serveHtmlDoc(c *http.Conn, r *http.Request, filename string) {
} }
title := commentText(src); title := commentText(src);
servePage(c, title, src); servePage(c, title, "", src);
} }
@ -461,11 +513,11 @@ func serveParseErrors(c *http.Conn, errors *parseErrors) {
if err := parseerrorHtml.Execute(errors, &buf); err != nil { if err := parseerrorHtml.Execute(errors, &buf); err != nil {
log.Stderrf("parseerrorHtml.Execute: %s", err); log.Stderrf("parseerrorHtml.Execute: %s", err);
} }
servePage(c, "Parse errors in source file " + errors.filename, buf.Bytes()); servePage(c, "Parse errors in source file " + errors.filename, "", buf.Bytes());
} }
func serveGoSource(c *http.Conn, filename string, style printer.Styler) { func serveGoSource(c *http.Conn, filename string, styler printer.Styler) {
path := pathutil.Join(goroot, filename); path := pathutil.Join(goroot, filename);
prog, errors := parse(path, parser.ParseComments); prog, errors := parse(path, parser.ParseComments);
if errors != nil { if errors != nil {
@ -475,10 +527,10 @@ func serveGoSource(c *http.Conn, filename string, style printer.Styler) {
var buf bytes.Buffer; var buf bytes.Buffer;
fmt.Fprintln(&buf, "<pre>"); fmt.Fprintln(&buf, "<pre>");
writeNode(&buf, prog, true, style); writeNode(&buf, prog, true, styler);
fmt.Fprintln(&buf, "</pre>"); fmt.Fprintln(&buf, "</pre>");
servePage(c, "Source file " + filename, buf.Bytes()); servePage(c, "Source file " + filename, "", buf.Bytes());
} }
@ -560,7 +612,7 @@ func getPageInfo(path string) PageInfo {
var subdirlist vector.Vector; var subdirlist vector.Vector;
subdirlist.Init(0); subdirlist.Init(0);
filter := func(d *os.Dir) bool { filter := func(d *os.Dir) bool {
if isGoFile(d) { if isPkgFile(d) {
// Some directories contain main packages: Only accept // Some directories contain main packages: Only accept
// files that belong to the expected package so that // files that belong to the expected package so that
// parser.ParsePackage doesn't return "multiple packages // parser.ParsePackage doesn't return "multiple packages
@ -634,7 +686,48 @@ func servePkg(c *http.Conn, r *http.Request) {
title = "Package " + info.PDoc.PackageName; title = "Package " + info.PDoc.PackageName;
} }
servePage(c, title, buf.Bytes()); servePage(c, title, "", buf.Bytes());
}
// ----------------------------------------------------------------------------
// Search
var searchIndex RWValue
type SearchResult struct {
Query string;
Hit *LookupResult;
Alt *AltWords;
Accurate bool;
Legend []string;
}
func search(c *http.Conn, r *http.Request) {
query := r.FormValue("q");
var result SearchResult;
if index, timestamp := searchIndex.get(); index != nil {
result.Query = query;
result.Hit, result.Alt = index.(*Index).Lookup(query);
_, ts := syncTime.get();
result.Accurate = timestamp >= ts;
result.Legend = &infoClasses;
}
var buf bytes.Buffer;
if err := searchHtml.Execute(result, &buf); err != nil {
log.Stderrf("searchHtml.Execute: %s", err);
}
var title string;
if result.Hit != nil {
title = fmt.Sprintf(`Results for query %q`, query);
} else {
title = fmt.Sprintf(`No results found for query %q`, query);
}
servePage(c, title, query, buf.Bytes());
} }
@ -754,6 +847,7 @@ func main() {
if *syncCmd != "" { if *syncCmd != "" {
http.Handle("/debug/sync", http.HandlerFunc(dosync)); http.Handle("/debug/sync", http.HandlerFunc(dosync));
} }
http.Handle("/search", http.HandlerFunc(search));
http.Handle("/", http.HandlerFunc(serveFile)); http.Handle("/", http.HandlerFunc(serveFile));
// The server may have been restarted; always wait 1sec to // The server may have been restarted; always wait 1sec to
@ -776,6 +870,29 @@ func main() {
}(); }();
} }
// Start indexing goroutine.
go func() {
for {
_, ts := syncTime.get();
if _, timestamp := searchIndex.get(); timestamp < ts {
// index possibly out of date - make a new one
// (could use a channel to send an explicit signal
// from the sync goroutine, but this solution is
// more decoupled, trivial, and works well enough)
start := time.Nanoseconds();
index := NewIndex(".");
stop := time.Nanoseconds();
searchIndex.set(index);
if *verbose {
secs := float64((stop-start)/1e6)/1e3;
nwords, nspots := index.Size();
log.Stderrf("index updated (%gs, %d unique words, %d spots)", secs, nwords, nspots);
}
}
time.Sleep(1*60e9); // try once a minute
}
}();
// Start http server. // Start http server.
if err := http.ListenAndServe(*httpaddr, handler); err != nil { if err := http.ListenAndServe(*httpaddr, handler); err != nil {
log.Exitf("ListenAndServe %s: %v", *httpaddr, err); log.Exitf("ListenAndServe %s: %v", *httpaddr, err);

View File

@ -49,7 +49,7 @@ func (p *ebnfParser) next() {
func (p *ebnfParser) Error(pos token.Position, msg string) { func (p *ebnfParser) Error(pos token.Position, msg string) {
fmt.Fprintf(p.out, "<font color=red>error: %s</font>", msg); fmt.Fprintf(p.out, `<span class="alert">error: %s</span>`, msg);
} }
@ -83,7 +83,7 @@ func (p *ebnfParser) parseIdentifier(def bool) {
if def { if def {
fmt.Fprintf(p.out, `<a id="%s">%s</a>`, name, name); fmt.Fprintf(p.out, `<a id="%s">%s</a>`, name, name);
} else { } else {
fmt.Fprintf(p.out, `<a href="#%s" style="text-decoration: none;">%s</a>`, name, name); fmt.Fprintf(p.out, `<a href="#%s" class="noline">%s</a>`, name, name);
} }
p.prev += len(name); // skip identifier when calling flush p.prev += len(name); // skip identifier when calling flush
} }
@ -165,8 +165,8 @@ func (p *ebnfParser) parse(out io.Writer, src []byte) {
// Markers around EBNF sections // Markers around EBNF sections
var ( var (
open = strings.Bytes(`<pre class="ebnf">`); openTag = strings.Bytes(`<pre class="ebnf">`);
close = strings.Bytes(`</pre>`); closeTag = strings.Bytes(`</pre>`);
) )
@ -175,14 +175,14 @@ func linkify(out io.Writer, src []byte) {
n := len(src); n := len(src);
// i: beginning of EBNF text (or end of source) // i: beginning of EBNF text (or end of source)
i := bytes.Index(src, open); i := bytes.Index(src, openTag);
if i < 0 { if i < 0 {
i = n-len(open); i = n-len(openTag);
} }
i += len(open); i += len(openTag);
// j: end of EBNF text (or end of source) // j: end of EBNF text (or end of source)
j := bytes.Index(src[i:n], close); // close marker j := bytes.Index(src[i:n], closeTag); // close marker
if j < 0 { if j < 0 {
j = n-i; j = n-i;
} }