mirror of
https://github.com/golang/go
synced 2024-11-05 11:46:12 -07:00
3e7aa9e599
Currently, we generate the TOC for a page dynamically after page load through javascript. This is fine for pages with static content. But for pages with dynamic output like /search, sometimes this causes a noticeable page jump due to extensive DOM traversal. Also, the heuristics to calculate the no. of columns is very rudimentary and fills the entire above-the-fold area if the no. of results is very large. Therefore, we generate the TOC from server side itself. And improve the no. of columns heuristic further to accomodate up to 4 columns. This improves page performance and utilizes real estate appropriately according to the input. Some screenshots at laptop (1366x768) resolution. https://snag.gy/AXz2rP.jpg https://snag.gy/th3Nn8.jpg More can be found in the CL comments. Fixes golang/go#21685 Updates golang/go#21686 Change-Id: Ia9b6dd1e67231d992709e4ba10ebdbedfe38b564 Reviewed-on: https://go-review.googlesource.com/c/129135 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Bryan C. Mills <bcmills@google.com>
187 lines
5.3 KiB
Go
187 lines
5.3 KiB
Go
// 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
|
|
}
|