--- /dev/null
+// 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 (
+ "bytes"
+ "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
+ Idents map[SpotKind][]Ident
+}
+
+func (c *Corpus) Lookup(query string) SearchResult {
+ result := &SearchResult{Query: query}
+
+ index, timestamp := c.CurrentIndex()
+ if index != nil {
+ // identifier search
+ if r, err := index.Lookup(query); err == nil {
+ result = r
+ } else if err != nil && !c.IndexFullText {
+ // 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.IndexFullText && 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
+}
+
+// SearchResultDoc optionally specifies a function returning an HTML body
+// displaying search results matching godoc documentation.
+func (p *Presentation) SearchResultDoc(result SearchResult) []byte {
+ return applyTemplate(p.SearchDocHTML, "searchDocHTML", result)
+}
+
+// SearchResultCode optionally specifies a function returning an HTML body
+// displaying search results matching source code.
+func (p *Presentation) SearchResultCode(result SearchResult) []byte {
+ return applyTemplate(p.SearchCodeHTML, "searchCodeHTML", result)
+}
+
+// SearchResultTxt optionally specifies a function returning an HTML body
+// displaying search results of textual matches.
+func (p *Presentation) SearchResultTxt(result SearchResult) []byte {
+ return applyTemplate(p.SearchTxtHTML, "searchTxtHTML", result)
+}
+
+// HandleSearch obtains results for the requested search and returns a page
+// to display them.
+func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) {
+ query := strings.TrimSpace(r.FormValue("q"))
+ result := p.Corpus.Lookup(query)
+
+ var contents bytes.Buffer
+ for _, f := range p.SearchResults {
+ contents.Write(f(p, result))
+ }
+
+ var title string
+ if haveResults := contents.Len() > 0; haveResults {
+ title = fmt.Sprintf(`Results for query: %v`, query)
+ if !p.Corpus.IndexEnabled {
+ result.Alert = ""
+ }
+ } else {
+ title = fmt.Sprintf(`No results found for query %q`, query)
+ }
+
+ body := bytes.NewBuffer(applyTemplate(p.SearchHTML, "searchHTML", result))
+ body.Write(contents.Bytes())
+
+ p.ServePage(w, Page{
+ Title: title,
+ Tabtitle: query,
+ Query: query,
+ Body: body.Bytes(),
+ GoogleCN: googleCN(r),
+ })
+}
+
+func (p *Presentation) serveSearchDesc(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/opensearchdescription+xml")
+ data := map[string]interface{}{
+ "BaseURL": fmt.Sprintf("http://%s", r.Host),
+ }
+ applyTemplateToResponseWriter(w, p.SearchDescXML, &data)
+}
+
+// tocColCount returns the no. of columns
+// to split the toc table to.
+func tocColCount(result SearchResult) int {
+ tocLen := tocLen(result)
+ colCount := 0
+ // Simple heuristic based on visual aesthetic in manual testing.
+ switch {
+ case tocLen <= 10:
+ colCount = 1
+ case tocLen <= 20:
+ colCount = 2
+ case tocLen <= 80:
+ colCount = 3
+ default:
+ colCount = 4
+ }
+ return colCount
+}
+
+// tocLen calculates the no. of items in the toc table
+// by going through various fields in the SearchResult
+// that is rendered in the UI.
+func tocLen(result SearchResult) int {
+ tocLen := 0
+ for _, val := range result.Idents {
+ if len(val) != 0 {
+ tocLen++
+ }
+ }
+ // If no identifiers, then just one item for the header text "Package <result.Query>".
+ // See searchcode.html for further details.
+ if len(result.Idents) == 0 {
+ tocLen++
+ }
+ if result.Hit != nil {
+ if len(result.Hit.Decls) > 0 {
+ tocLen += len(result.Hit.Decls)
+ // We need one extra item for the header text "Package-level declarations".
+ tocLen++
+ }
+ if len(result.Hit.Others) > 0 {
+ tocLen += len(result.Hit.Others)
+ // We need one extra item for the header text "Local declarations and uses".
+ tocLen++
+ }
+ }
+ // For "textual occurrences".
+ tocLen++
+ return tocLen
+}