godoc: more cleanup

cmd/godoc/godoc.go is now merged into main.go, which is now
only 530 lines.

App Engine mode is still broken, but should be easy to fix up.
(just needs a global *godoc.Presentation created in init)

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/11498044
diff --git a/godoc/search.go b/godoc/search.go
new file mode 100644
index 0000000..8d5b4e3
--- /dev/null
+++ b/godoc/search.go
@@ -0,0 +1,99 @@
+// 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.
+
+package godoc
+
+import (
+	"fmt"
+	"net/http"
+	"regexp"
+	"strings"
+)
+
+type SearchResult struct {
+	Query string
+	Alert string // error or warning message
+
+	// identifier matches
+	Pak HitList       // packages matching Query
+	Hit *LookupResult // identifier matches of Query
+	Alt *AltWords     // alternative identifiers to look for
+
+	// textual matches
+	Found    int         // number of textual occurrences found
+	Textual  []FileLines // textual matches of Query
+	Complete bool        // true if all textual occurrences of Query are reported
+}
+
+func (c *Corpus) Lookup(query string) SearchResult {
+	var result SearchResult
+	result.Query = query
+
+	index, timestamp := c.CurrentIndex()
+	if index != nil {
+		// identifier search
+		var err error
+		result.Pak, result.Hit, result.Alt, err = index.Lookup(query)
+		if err != nil && c.MaxResults <= 0 {
+			// ignore the error if full text search is enabled
+			// since the query may be a valid regular expression
+			result.Alert = "Error in query string: " + err.Error()
+			return result
+		}
+
+		// full text search
+		if c.MaxResults > 0 && query != "" {
+			rx, err := regexp.Compile(query)
+			if err != nil {
+				result.Alert = "Error in query regular expression: " + err.Error()
+				return result
+			}
+			// If we get maxResults+1 results we know that there are more than
+			// maxResults results and thus the result may be incomplete (to be
+			// precise, we should remove one result from the result set, but
+			// nobody is going to count the results on the result page).
+			result.Found, result.Textual = index.LookupRegexp(rx, c.MaxResults+1)
+			result.Complete = result.Found <= c.MaxResults
+			if !result.Complete {
+				result.Found-- // since we looked for maxResults+1
+			}
+		}
+	}
+
+	// is the result accurate?
+	if c.IndexEnabled {
+		if ts := c.FSModifiedTime(); timestamp.Before(ts) {
+			// The index is older than the latest file system change under godoc's observation.
+			result.Alert = "Indexing in progress: result may be inaccurate"
+		}
+	} else {
+		result.Alert = "Search index disabled: no results available"
+	}
+
+	return result
+}
+
+func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) {
+	query := strings.TrimSpace(r.FormValue("q"))
+	result := p.Corpus.Lookup(query)
+
+	if GetPageInfoMode(r)&NoHTML != 0 {
+		p.ServeText(w, applyTemplate(p.SearchText, "searchText", result))
+		return
+	}
+
+	var title string
+	if result.Hit != nil || len(result.Textual) > 0 {
+		title = fmt.Sprintf(`Results for query %q`, query)
+	} else {
+		title = fmt.Sprintf(`No results found for query %q`, query)
+	}
+
+	p.ServePage(w, Page{
+		Title:    title,
+		Tabtitle: query,
+		Query:    query,
+		Body:     applyTemplate(p.SearchHTML, "searchHTML", result),
+	})
+}