File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/search/search.go
122 lines · 3.1kb · 133728eaa05486504991b230f9d4c7e986b7defc
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
// Package search reads one index, filtered by read access inside the query. Chapter 17.
133728e barerepo 1mo
2
package search
133728e barerepo 1mo
3
133728e barerepo 1mo
4
import (
133728e barerepo 1mo
5
"context"
133728e barerepo 1mo
6
"strings"
133728e barerepo 1mo
7
133728e barerepo 1mo
8
"github.com/barerepo/server/internal/store"
133728e barerepo 1mo
9
)
133728e barerepo 1mo
10
133728e barerepo 1mo
11
// Kind orders the set: code first, because a barerepo is usually searched for a symbol.
133728e barerepo 1mo
12
type Kind int
133728e barerepo 1mo
13
133728e barerepo 1mo
14
const (
133728e barerepo 1mo
15
Code Kind = iota
133728e barerepo 1mo
16
Thread
133728e barerepo 1mo
17
Repo
133728e barerepo 1mo
18
)
133728e barerepo 1mo
19
133728e barerepo 1mo
20
func (k Kind) String() string {
133728e barerepo 1mo
21
switch k {
133728e barerepo 1mo
22
case Code:
133728e barerepo 1mo
23
return "code"
133728e barerepo 1mo
24
case Thread:
133728e barerepo 1mo
25
return "thread"
133728e barerepo 1mo
26
default:
133728e barerepo 1mo
27
return "repo"
133728e barerepo 1mo
28
}
133728e barerepo 1mo
29
}
133728e barerepo 1mo
30
133728e barerepo 1mo
31
// Result is one row of one set, so every row names the repository it came from.
133728e barerepo 1mo
32
type Result struct {
133728e barerepo 1mo
33
Kind Kind
133728e barerepo 1mo
34
Owner string
133728e barerepo 1mo
35
Name string
133728e barerepo 1mo
36
Path string // code: the file. thread: the number.
133728e barerepo 1mo
37
Line int
133728e barerepo 1mo
38
Text string // the matching line, or the thread title
133728e barerepo 1mo
39
Context string
133728e barerepo 1mo
40
Href string
133728e barerepo 1mo
41
}
133728e barerepo 1mo
42
133728e barerepo 1mo
43
// Finder is the index query, an interface so the page depends on the question and not the schema.
133728e barerepo 1mo
44
type Finder interface {
133728e barerepo 1mo
45
FindDocs(ctx context.Context, viewer, query string, limit int) ([]store.Doc, error)
133728e barerepo 1mo
46
}
133728e barerepo 1mo
47
133728e barerepo 1mo
48
// Search asks the index once. The read filter is inside that query, never applied to the answer.
133728e barerepo 1mo
49
func Search(ctx context.Context, db Finder, viewer, query string, limit int) []Result {
133728e barerepo 1mo
50
query = strings.TrimSpace(query)
133728e barerepo 1mo
51
if query == "" || limit <= 0 || db == nil {
133728e barerepo 1mo
52
return nil
133728e barerepo 1mo
53
}
133728e barerepo 1mo
54
docs, err := db.FindDocs(ctx, viewer, query, limit)
133728e barerepo 1mo
55
if err != nil {
133728e barerepo 1mo
56
return nil
133728e barerepo 1mo
57
}
133728e barerepo 1mo
58
out := make([]Result, 0, len(docs))
133728e barerepo 1mo
59
for _, d := range docs {
133728e barerepo 1mo
60
if len(out) >= limit {
133728e barerepo 1mo
61
break
133728e barerepo 1mo
62
}
133728e barerepo 1mo
63
owner, name, ok := strings.Cut(d.Repo, "/")
133728e barerepo 1mo
64
if !ok {
133728e barerepo 1mo
65
continue
133728e barerepo 1mo
66
}
133728e barerepo 1mo
67
switch d.Kind {
133728e barerepo 1mo
68
case store.Code:
133728e barerepo 1mo
69
for _, m := range matches(d.Body, query, linesPerFile) {
133728e barerepo 1mo
70
out = append(out, Result{Kind: Code, Owner: owner, Name: name, Path: d.Path,
133728e barerepo 1mo
71
Line: m.n, Text: m.text,
133728e barerepo 1mo
72
Href: "/" + d.Repo + "/file/" + defaultRef + "/" + d.Path})
133728e barerepo 1mo
73
}
133728e barerepo 1mo
74
case store.Thread:
133728e barerepo 1mo
75
line, _ := match(d.Body, query)
133728e barerepo 1mo
76
out = append(out, Result{Kind: Thread, Owner: owner, Name: name, Path: d.Path,
133728e barerepo 1mo
77
Text: d.Title, Context: line,
133728e barerepo 1mo
78
Href: "/" + d.Repo + "/thread/" + d.Path})
133728e barerepo 1mo
79
default:
133728e barerepo 1mo
80
out = append(out, Result{Kind: Repo, Owner: owner, Name: name,
133728e barerepo 1mo
81
Text: d.Title, Context: d.Body, Href: "/" + d.Repo})
133728e barerepo 1mo
82
}
133728e barerepo 1mo
83
}
133728e barerepo 1mo
84
return out
133728e barerepo 1mo
85
}
133728e barerepo 1mo
86
133728e barerepo 1mo
87
// defaultRef is what a file link resolves through, because HEAD is the branch this index was built from.
133728e barerepo 1mo
88
const defaultRef = "HEAD"
133728e barerepo 1mo
89
133728e barerepo 1mo
90
// linesPerFile bounds one file's share of a result set, so a common word cannot fill the page.
133728e barerepo 1mo
91
const linesPerFile = 3
133728e barerepo 1mo
92
133728e barerepo 1mo
93
// hit is one matching line and the number it sits on.
133728e barerepo 1mo
94
type hit struct {
133728e barerepo 1mo
95
text string
133728e barerepo 1mo
96
n int
133728e barerepo 1mo
97
}
133728e barerepo 1mo
98
133728e barerepo 1mo
99
// matches finds where the query is, so a reader sees the occurrences and not only the first.
133728e barerepo 1mo
100
func matches(body, query string, limit int) []hit {
133728e barerepo 1mo
101
want := strings.ToLower(query)
133728e barerepo 1mo
102
lines := strings.Split(body, "\n")
133728e barerepo 1mo
103
var out []hit
133728e barerepo 1mo
104
for i, line := range lines {
133728e barerepo 1mo
105
if !strings.Contains(strings.ToLower(line), want) {
133728e barerepo 1mo
106
continue
133728e barerepo 1mo
107
}
133728e barerepo 1mo
108
out = append(out, hit{strings.TrimRight(line, "\r"), i + 1})
133728e barerepo 1mo
109
if len(out) == limit {
133728e barerepo 1mo
110
break
133728e barerepo 1mo
111
}
133728e barerepo 1mo
112
}
133728e barerepo 1mo
113
return out
133728e barerepo 1mo
114
}
133728e barerepo 1mo
115
133728e barerepo 1mo
116
// match is one line, for a thread, where the excerpt is context rather than a result of its own.
133728e barerepo 1mo
117
func match(body, query string) (string, int) {
133728e barerepo 1mo
118
if m := matches(body, query, 1); len(m) == 1 {
133728e barerepo 1mo
119
return m[0].text, m[0].n
133728e barerepo 1mo
120
}
133728e barerepo 1mo
121
return "", 0
133728e barerepo 1mo
122
}
history · rawbarerepo 0.1.0