github.com/v2fly/tools@v0.100.0/godoc/search.go (about) 1 // 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 5 package godoc 6 7 import ( 8 "bytes" 9 "fmt" 10 "net/http" 11 "regexp" 12 "strings" 13 ) 14 15 type 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 28 Idents map[SpotKind][]Ident 29 } 30 31 func (c *Corpus) Lookup(query string) SearchResult { 32 result := &SearchResult{Query: query} 33 34 index, timestamp := c.CurrentIndex() 35 if index != nil { 36 // identifier search 37 if r, err := index.Lookup(query); err == nil { 38 result = r 39 } else if err != nil && !c.IndexFullText { 40 // 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() 43 return *result 44 } 45 46 // full text search 47 if c.IndexFullText && query != "" { 48 rx, err := regexp.Compile(query) 49 if err != nil { 50 result.Alert = "Error in query regular expression: " + err.Error() 51 return *result 52 } 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 75 return *result 76 } 77 78 // SearchResultDoc optionally specifies a function returning an HTML body 79 // displaying search results matching godoc documentation. 80 func (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. 86 func (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. 92 func (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. 98 func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) { 99 query := strings.TrimSpace(r.FormValue("q")) 100 result := p.Corpus.Lookup(query) 101 102 var contents bytes.Buffer 103 for _, f := range p.SearchResults { 104 contents.Write(f(p, result)) 105 } 106 107 var title string 108 if haveResults := contents.Len() > 0; haveResults { 109 title = fmt.Sprintf(`Results for query: %v`, query) 110 if !p.Corpus.IndexEnabled { 111 result.Alert = "" 112 } 113 } else { 114 title = fmt.Sprintf(`No results found for query %q`, query) 115 } 116 117 body := bytes.NewBuffer(applyTemplate(p.SearchHTML, "searchHTML", result)) 118 body.Write(contents.Bytes()) 119 120 p.ServePage(w, Page{ 121 Title: title, 122 Tabtitle: query, 123 Query: query, 124 Body: body.Bytes(), 125 }) 126 } 127 128 func (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 } 133 applyTemplateToResponseWriter(w, p.SearchDescXML, &data) 134 } 135 136 // tocColCount returns the no. of columns 137 // to split the toc table to. 138 func 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. 158 func 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 }