blob: 33e4febfaaaad86f1190814f93e8dbd4f5b1885c [file] [log] [blame]
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +10001// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package godoc
6
7import (
Brad Garciaa28efa52014-01-06 09:51:01 -05008 "bytes"
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +10009 "fmt"
10 "net/http"
11 "regexp"
12 "strings"
13)
14
15type SearchResult struct {
16 Query string
17 Alert string // error or warning message
18
19 // identifier matches
20 Pak HitList // packages matching Query
21 Hit *LookupResult // identifier matches of Query
22 Alt *AltWords // alternative identifiers to look for
23
24 // textual matches
25 Found int // number of textual occurrences found
26 Textual []FileLines // textual matches of Query
27 Complete bool // true if all textual occurrences of Query are reported
Brad Garciaf3faf8b2013-11-21 11:55:42 -050028 Idents map[SpotKind][]Ident
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +100029}
30
31func (c *Corpus) Lookup(query string) SearchResult {
Brad Garciaf3faf8b2013-11-21 11:55:42 -050032 result := &SearchResult{Query: query}
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +100033
34 index, timestamp := c.CurrentIndex()
35 if index != nil {
36 // identifier search
Brad Garcia804b9652014-02-20 11:26:05 -050037 if r, err := index.Lookup(query); err == nil {
38 result = r
39 } else if err != nil && !c.IndexFullText {
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +100040 // ignore the error if full text search is enabled
41 // since the query may be a valid regular expression
42 result.Alert = "Error in query string: " + err.Error()
Brad Garciaf3faf8b2013-11-21 11:55:42 -050043 return *result
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +100044 }
45
46 // full text search
Brad Garciaf3faf8b2013-11-21 11:55:42 -050047 if c.IndexFullText && query != "" {
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +100048 rx, err := regexp.Compile(query)
49 if err != nil {
50 result.Alert = "Error in query regular expression: " + err.Error()
Brad Garciaf3faf8b2013-11-21 11:55:42 -050051 return *result
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +100052 }
53 // If we get maxResults+1 results we know that there are more than
54 // maxResults results and thus the result may be incomplete (to be
55 // precise, we should remove one result from the result set, but
56 // nobody is going to count the results on the result page).
57 result.Found, result.Textual = index.LookupRegexp(rx, c.MaxResults+1)
58 result.Complete = result.Found <= c.MaxResults
59 if !result.Complete {
60 result.Found-- // since we looked for maxResults+1
61 }
62 }
63 }
64
65 // is the result accurate?
66 if c.IndexEnabled {
67 if ts := c.FSModifiedTime(); timestamp.Before(ts) {
68 // The index is older than the latest file system change under godoc's observation.
69 result.Alert = "Indexing in progress: result may be inaccurate"
70 }
71 } else {
72 result.Alert = "Search index disabled: no results available"
73 }
74
Brad Garciaf3faf8b2013-11-21 11:55:42 -050075 return *result
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +100076}
77
Brad Garciaa28efa52014-01-06 09:51:01 -050078// SearchResultDoc optionally specifies a function returning an HTML body
79// displaying search results matching godoc documentation.
80func (p *Presentation) SearchResultDoc(result SearchResult) []byte {
81 return applyTemplate(p.SearchDocHTML, "searchDocHTML", result)
82}
83
84// SearchResultCode optionally specifies a function returning an HTML body
85// displaying search results matching source code.
86func (p *Presentation) SearchResultCode(result SearchResult) []byte {
87 return applyTemplate(p.SearchCodeHTML, "searchCodeHTML", result)
88}
89
90// SearchResultTxt optionally specifies a function returning an HTML body
91// displaying search results of textual matches.
92func (p *Presentation) SearchResultTxt(result SearchResult) []byte {
93 return applyTemplate(p.SearchTxtHTML, "searchTxtHTML", result)
94}
95
96// HandleSearch obtains results for the requested search and returns a page
97// to display them.
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +100098func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) {
99 query := strings.TrimSpace(r.FormValue("q"))
100 result := p.Corpus.Lookup(query)
101
Brad Fitzpatricke5fe2892018-10-11 02:21:09 +0000102 var contents bytes.Buffer
Brad Garciaa28efa52014-01-06 09:51:01 -0500103 for _, f := range p.SearchResults {
104 contents.Write(f(p, result))
Brad Garciaf3faf8b2013-11-21 11:55:42 -0500105 }
Brad Garciaa28efa52014-01-06 09:51:01 -0500106
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +1000107 var title string
Brad Garciaa28efa52014-01-06 09:51:01 -0500108 if haveResults := contents.Len() > 0; haveResults {
Andrew Gerrandfe74a412016-02-05 11:04:38 +1100109 title = fmt.Sprintf(`Results for query: %v`, query)
Brad Garciaa28efa52014-01-06 09:51:01 -0500110 if !p.Corpus.IndexEnabled {
111 result.Alert = ""
112 }
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +1000113 } else {
114 title = fmt.Sprintf(`No results found for query %q`, query)
115 }
116
Brad Garciaa28efa52014-01-06 09:51:01 -0500117 body := bytes.NewBuffer(applyTemplate(p.SearchHTML, "searchHTML", result))
118 body.Write(contents.Bytes())
119
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +1000120 p.ServePage(w, Page{
121 Title: title,
122 Tabtitle: query,
123 Query: query,
Brad Garciaa28efa52014-01-06 09:51:01 -0500124 Body: body.Bytes(),
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +1000125 })
126}
Brad Fitzpatrick80d38232013-07-19 14:02:03 +1000127
128func (p *Presentation) serveSearchDesc(w http.ResponseWriter, r *http.Request) {
129 w.Header().Set("Content-Type", "application/opensearchdescription+xml")
130 data := map[string]interface{}{
131 "BaseURL": fmt.Sprintf("http://%s", r.Host),
132 }
Brad Garciaefd232e2014-01-29 10:53:45 -0500133 applyTemplateToResponseWriter(w, p.SearchDescXML, &data)
Brad Fitzpatrick80d38232013-07-19 14:02:03 +1000134}
Agniva De Sarker3e7aa9e2018-08-11 13:33:17 +0530135
136// tocColCount returns the no. of columns
137// to split the toc table to.
138func tocColCount(result SearchResult) int {
139 tocLen := tocLen(result)
140 colCount := 0
141 // Simple heuristic based on visual aesthetic in manual testing.
142 switch {
143 case tocLen <= 10:
144 colCount = 1
145 case tocLen <= 20:
146 colCount = 2
147 case tocLen <= 80:
148 colCount = 3
149 default:
150 colCount = 4
151 }
152 return colCount
153}
154
155// tocLen calculates the no. of items in the toc table
156// by going through various fields in the SearchResult
157// that is rendered in the UI.
158func tocLen(result SearchResult) int {
159 tocLen := 0
160 for _, val := range result.Idents {
161 if len(val) != 0 {
162 tocLen++
163 }
164 }
165 // If no identifiers, then just one item for the header text "Package <result.Query>".
166 // See searchcode.html for further details.
167 if len(result.Idents) == 0 {
168 tocLen++
169 }
170 if result.Hit != nil {
171 if len(result.Hit.Decls) > 0 {
172 tocLen += len(result.Hit.Decls)
173 // We need one extra item for the header text "Package-level declarations".
174 tocLen++
175 }
176 if len(result.Hit.Others) > 0 {
177 tocLen += len(result.Hit.Others)
178 // We need one extra item for the header text "Local declarations and uses".
179 tocLen++
180 }
181 }
182 // For "textual occurrences".
183 tocLen++
184 return tocLen
185}