-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathsearch.go
142 lines (126 loc) · 3.83 KB
/
search.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package docsite
import (
"bytes"
"context"
"html"
"html/template"
"net/url"
"sort"
"strings"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text"
"github.com/sourcegraph/docsite/internal/search"
"github.com/sourcegraph/docsite/internal/search/index"
"github.com/sourcegraph/docsite/internal/search/query"
"github.com/sourcegraph/docsite/markdown"
)
// Search searches all documents at the version for a query.
func (s *Site) Search(ctx context.Context, contentVersion string, queryStr string) (*search.Result, error) {
pages, err := s.AllContentPages(ctx, contentVersion)
if err != nil {
return nil, err
}
idx, err := index.New()
if err != nil {
return nil, err
}
for _, page := range pages {
if s.SkipIndexURLPattern != nil && s.SkipIndexURLPattern.MatchString(page.Path) {
// this URL matches a pattern that we do not want to index for search, so we will skip it
continue
}
root := markdown.New(markdown.Options{}).Parser().Parse(text.NewReader(page.Data))
data, err := s.renderTextContent(ctx, page, root, contentVersion)
if err != nil {
return nil, err
}
if err := idx.Add(ctx, index.Document{
ID: index.DocID(page.FilePath),
Title: markdown.GetTitle(root, page.Data),
URL: s.Base.ResolveReference(&url.URL{Path: page.Path}).String(),
Data: data,
}); err != nil {
return nil, err
}
}
return search.Search(query.Parse(queryStr), idx)
}
func (s *Site) renderTextContent(ctx context.Context, page *ContentPage, node ast.Node, contentVersion string) ([]byte, error) {
// Evaluate <div markdown-func> elements if present (use a heuristic to determine if
// present, to avoid needless work).
maybeHasMarkdownFunc := bytes.Contains(page.Data, []byte("markdown-func"))
if !maybeHasMarkdownFunc {
return page.Data, nil
}
opt := s.markdownOptions(page.FilePath, contentVersion)
err := ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
switch node.Kind() {
case ast.KindHTMLBlock:
if entering {
s := node.Lines().At(0)
val := s.Value(page.Data)
if v, err := markdown.EvalMarkdownFuncs(ctx, val, opt); err == nil {
page.Data = bytes.Replace(page.Data, val, v, 1)
}
}
}
return ast.WalkContinue, nil
})
return page.Data, err
}
func (s *Site) renderSearchPage(contentVersion, queryStr string, result *search.Result) ([]byte, error) {
query := query.Parse(queryStr)
templates, err := s.GetResources("templates", contentVersion)
if err != nil {
return nil, err
}
tmpl, err := s.getTemplate(templates, searchTemplateName, template.FuncMap{
"highlight": func(text string) template.HTML { return highlight(query, text) },
})
if err != nil {
return nil, err
}
data := struct {
ContentVersion string
Query string
Result *search.Result
}{
ContentVersion: contentVersion,
Query: queryStr,
Result: result,
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// highlight returns an HTML fragment with matches of the pattern in text wrapped in <strong>.
func highlight(query query.Query, text string) template.HTML {
// Highlight longer matches first (among matches starting at the same position).
matches := query.FindAllIndex(text)
sort.Slice(matches, func(i, j int) bool {
return (matches[i][0] < matches[j][0]) || (matches[i][0] == matches[j][0] && matches[i][1] > matches[j][1])
})
var s []string
c := 0
for _, match := range matches {
start, end := match[0], match[1]
if start > c {
s = append(s, html.EscapeString(text[c:start]))
}
if start < c {
start = c
}
if start < end {
s = append(s, "<strong>"+html.EscapeString(text[start:end])+"</strong>")
}
if end > c {
c = end
}
}
if c < len(text) {
s = append(s, html.EscapeString(text[c:]))
}
return template.HTML(strings.Join(s, ""))
}