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  }