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:
parent
f529224039
commit
32810a5ded
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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"> </li>
|
|
||||||
<li class="navhead">Programming</li>
|
|
||||||
<li><a href="/pkg">Package documentation</a></li>
|
|
||||||
|
|
||||||
<li class="blank"> </li>
|
<li class="blank"> </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"> </li>
|
||||||
|
<li class="navhead">Programming</li>
|
||||||
|
<li><a href="/pkg">Package documentation</a></li>
|
||||||
|
|
||||||
|
<li class="blank"> </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"> </li>
|
<li class="blank"> </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
65
lib/godoc/search.html
Normal 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}
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user