package main
import (
- "bytes";
- "container/vector";
- "flag";
- "fmt";
- "go/ast";
- "go/doc";
- "go/parser";
- "go/printer";
- "go/scanner";
- "go/token";
- "http";
- "io";
- "log";
- "os";
- pathutil "path";
- "sort";
- "strings";
- "sync";
- "template";
- "time";
+ "bytes";
+ "container/vector";
+ "flag";
+ "fmt";
+ "go/ast";
+ "go/doc";
+ "go/parser";
+ "go/printer";
+ "go/scanner";
+ "go/token";
+ "http";
+ "io";
+ "log";
+ "os";
+ pathutil "path";
+ "sort";
+ "strings";
+ "sync";
+ "template";
+ "time";
)
// An RWValue wraps a value and permits mutually exclusive
// access to it and records the time the value was last set.
type RWValue struct {
- mutex sync.RWMutex;
- value interface{};
- timestamp int64; // time of last set(), in seconds since epoch
+ mutex sync.RWMutex;
+ value interface{};
+ timestamp int64; // time of last set(), in seconds since epoch
}
func isGoFile(dir *os.Dir) bool {
return dir.IsRegular() &&
!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
}
}
-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;
// 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)
}
-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;
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);
if s.highlight == id.Value {
tag = printer.HtmlTag{"<span class=highlight>", "</span>"};
}
-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());
return;
}
-
// ----------------------------------------------------------------------------
// Templates
// 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;
if html {
mode |= printer.GenHTML;
}
- (&printer.Config{mode, *tabwidth, style}).Fprint(w, node);
+ (&printer.Config{mode, *tabwidth, styler}).Fprint(w, node);
}
}
+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{
"": textFmt,
"html": htmlFmt,
"html-comment": htmlCommentFmt,
"link": linkFmt,
+ "infoClass": infoClassFmt,
+ "infoLine": infoLineFmt,
+ "infoSnippet": infoSnippetFmt,
}
packageHtml,
packageText,
parseerrorHtml,
- parseerrorText *template.Template;
+ parseerrorText,
+ searchHtml *template.Template;
)
func readTemplates() {
packageText = readTemplate("package.txt");
parseerrorHtml = readTemplate("parseerror.html");
parseerrorText = readTemplate("parseerror.txt");
+ searchHtml = readTemplate("search.html");
}
// ----------------------------------------------------------------------------
// Generic HTML wrapper
-func servePage(c *http.Conn, title, content interface{}) {
+func servePage(c *http.Conn, title, query string, content []byte) {
type Data struct {
- title interface{};
- timestamp string;
- content interface{};
+ Title string;
+ Timestamp string;
+ Query string;
+ Content []byte;
}
_, ts := syncTime.get();
d := Data{
- title: title,
- timestamp: time.SecondsToLocalTime(ts).String(),
- content: content,
+ Title: title,
+ Timestamp: time.SecondsToLocalTime(ts).String(),
+ Query: query,
+ Content: content,
};
if err := godocHtml.Execute(&d, c); err != nil {
}
title := commentText(src);
- servePage(c, title, src);
+ servePage(c, title, "", src);
}
if err := parseerrorHtml.Execute(errors, &buf); err != nil {
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);
prog, errors := parse(path, parser.ParseComments);
if errors != nil {
var buf bytes.Buffer;
fmt.Fprintln(&buf, "<pre>");
- writeNode(&buf, prog, true, style);
+ writeNode(&buf, prog, true, styler);
fmt.Fprintln(&buf, "</pre>");
- servePage(c, "Source file " + filename, buf.Bytes());
+ servePage(c, "Source file " + filename, "", buf.Bytes());
}
var subdirlist vector.Vector;
subdirlist.Init(0);
filter := func(d *os.Dir) bool {
- if isGoFile(d) {
+ if isPkgFile(d) {
// Some directories contain main packages: Only accept
// files that belong to the expected package so that
// parser.ParsePackage doesn't return "multiple packages
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());
}
if *syncCmd != "" {
http.Handle("/debug/sync", http.HandlerFunc(dosync));
}
+ http.Handle("/search", http.HandlerFunc(search));
http.Handle("/", http.HandlerFunc(serveFile));
// The server may have been restarted; always wait 1sec to
}();
}
+ // 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.
if err := http.ListenAndServe(*httpaddr, handler); err != nil {
log.Exitf("ListenAndServe %s: %v", *httpaddr, err);