fork of https://github.com/sourcegraph/zoekt
1package index
2
3import (
4 "log"
5
6 "github.com/sourcegraph/zoekt"
7)
8
9// SortAndTruncateFiles is a convenience around SortFiles and
10// DisplayTruncator. Given an aggregated files it will sort and then truncate
11// based on the search options.
12func SortAndTruncateFiles(files []zoekt.FileMatch, opts *zoekt.SearchOptions) []zoekt.FileMatch {
13 SortFiles(files)
14 truncator, _ := NewDisplayTruncator(opts)
15 files, _ = truncator(files)
16 return files
17}
18
19// DisplayTruncator is a stateful function which enforces Document and Match
20// display limits by truncating and mutating before. hasMore is true until the
21// limits are exhausted. Once hasMore is false each subsequent call will
22// return an empty after and hasMore false.
23type DisplayTruncator func(before []zoekt.FileMatch) (after []zoekt.FileMatch, hasMore bool)
24
25// NewDisplayTruncator will return a DisplayTruncator which enforces the limits in
26// opts. If there are no limits to enforce, hasLimits is false and there is no
27// need to call DisplayTruncator.
28func NewDisplayTruncator(opts *zoekt.SearchOptions) (_ DisplayTruncator, hasLimits bool) {
29 docLimit := opts.MaxDocDisplayCount
30 docLimited := docLimit > 0
31
32 matchLimit := opts.MaxMatchDisplayCount
33 matchLimited := matchLimit > 0
34
35 done := false
36
37 if !docLimited && !matchLimited {
38 return func(fm []zoekt.FileMatch) ([]zoekt.FileMatch, bool) {
39 return fm, true
40 }, false
41 }
42
43 return func(fm []zoekt.FileMatch) ([]zoekt.FileMatch, bool) {
44 if done {
45 return nil, false
46 }
47
48 if docLimited {
49 if len(fm) >= docLimit {
50 done = true
51 fm = fm[:docLimit]
52 }
53 docLimit -= len(fm)
54 }
55
56 if matchLimited {
57 fm, matchLimit = limitMatches(fm, matchLimit, opts.ChunkMatches)
58 if matchLimit <= 0 {
59 done = true
60 }
61 }
62
63 return fm, !done
64 }, true
65}
66
67func limitMatches(files []zoekt.FileMatch, limit int, chunkMatches bool) ([]zoekt.FileMatch, int) {
68 var limiter func(file *zoekt.FileMatch, limit int) int
69 if chunkMatches {
70 limiter = limitChunkMatches
71 } else {
72 limiter = limitLineMatches
73 }
74 for i := range files {
75 limit = limiter(&files[i], limit)
76 if limit <= 0 {
77 return files[:i+1], 0
78 }
79 }
80 return files, limit
81}
82
83// Limit the number of ChunkMatches in the given FileMatch, returning the
84// remaining limit, if any.
85func limitChunkMatches(file *zoekt.FileMatch, limit int) int {
86 for i := range file.ChunkMatches {
87 cm := &file.ChunkMatches[i]
88 if len(cm.Ranges) > limit {
89 // We potentially need to effect the limit upon 3 different fields:
90 // Ranges, SymbolInfo, and Content.
91
92 // Content is the most complicated: we need to remove the last N
93 // lines from it, where N is the difference between the line number
94 // of the end of the old last Range and that of the new last Range.
95 // This calculation is correct in the presence of both context lines
96 // and multiline Ranges, taking into account that Content never has
97 // a trailing newline.
98 n := cm.Ranges[len(cm.Ranges)-1].End.LineNumber - cm.Ranges[limit-1].End.LineNumber
99 if n > 0 {
100 for b := len(cm.Content) - 1; b >= 0; b-- {
101 if cm.Content[b] == '\n' {
102 n -= 1
103 }
104 if n == 0 {
105 cm.Content = cm.Content[:b]
106 break
107 }
108 }
109 if n > 0 {
110 // Should be impossible.
111 log.Panicf("Failed to find enough newlines when truncating Content, %d left over, %d ranges", n, len(cm.Ranges))
112 }
113 }
114
115 cm.Ranges = cm.Ranges[:limit]
116 if cm.SymbolInfo != nil {
117 // When non-nil, SymbolInfo is specified to have the same length
118 // as Ranges.
119 cm.SymbolInfo = cm.SymbolInfo[:limit]
120 }
121 }
122 if len(cm.Ranges) == limit {
123 file.ChunkMatches = file.ChunkMatches[:i+1]
124 limit = 0
125 break
126 }
127 limit -= len(cm.Ranges)
128 }
129 return limit
130}
131
132// Limit the number of LineMatches in the given FileMatch, returning the
133// remaining limit, if any.
134func limitLineMatches(file *zoekt.FileMatch, limit int) int {
135 for i := range file.LineMatches {
136 lm := &file.LineMatches[i]
137 if len(lm.LineFragments) > limit {
138 lm.LineFragments = lm.LineFragments[:limit]
139 }
140 if len(lm.LineFragments) == limit {
141 file.LineMatches = file.LineMatches[:i+1]
142 limit = 0
143 break
144 }
145 limit -= len(lm.LineFragments)
146 }
147 return limit
148}