/
search.go
156 lines (127 loc) · 3.64 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package m
import (
"fmt"
"github.com/walles/moar/m/linenumbers"
)
func (p *Pager) scrollToSearchHits() {
if p.searchPattern == nil {
// This is not a search
return
}
firstHitPosition := p.findFirstHit(*p.scrollPosition.lineNumber(p), false)
if firstHitPosition == nil {
// Try again from the top
firstHitPosition = p.findFirstHit(linenumbers.LineNumber{}, false)
}
if firstHitPosition == nil {
// No match, give up
return
}
if firstHitPosition.isVisible(p) {
// Already on-screen, never mind
return
}
p.scrollPosition = *firstHitPosition
}
// NOTE: When we search, we do that by looping over the *input lines*, not
// the screen lines. That's why we're using a line number rather than a
// scrollPosition for searching.
//
// FIXME: We should take startPosition.deltaScreenLines into account as well!
func (p *Pager) findFirstHit(startPosition linenumbers.LineNumber, backwards bool) *scrollPosition {
searchPosition := startPosition
for {
line := p.reader.GetLine(searchPosition)
if line == nil {
// No match, give up
return nil
}
lineText := line.Plain(&searchPosition)
if p.searchPattern.MatchString(lineText) {
return scrollPositionFromLineNumber("findFirstHit", searchPosition)
}
if backwards {
if (searchPosition == linenumbers.LineNumber{}) {
// No match, give up
return nil
}
searchPosition = searchPosition.NonWrappingAdd(-1)
} else {
searchPosition = searchPosition.NonWrappingAdd(1)
}
}
}
func (p *Pager) isViewing() bool {
_, isViewing := p.mode.(PagerModeViewing)
return isViewing
}
func (p *Pager) isNotFound() bool {
_, isNotFound := p.mode.(PagerModeNotFound)
return isNotFound
}
func (p *Pager) scrollToNextSearchHit() {
if p.searchPattern == nil {
// Nothing to search for, never mind
return
}
if p.reader.GetLineCount() == 0 {
// Nothing to search in, never mind
return
}
if p.isViewing() && p.isScrolledToEnd() {
p.mode = PagerModeNotFound{pager: p}
return
}
var firstSearchPosition linenumbers.LineNumber
switch {
case p.isViewing():
// Start searching on the first line below the bottom of the screen
position := p.getLastVisiblePosition().NextLine(1)
firstSearchPosition = *position.lineNumber(p)
case p.isNotFound():
// Restart searching from the top
p.mode = PagerModeViewing{pager: p}
firstSearchPosition = linenumbers.LineNumber{}
default:
panic(fmt.Sprint("Unknown search mode when finding next: ", p.mode))
}
firstHitPosition := p.findFirstHit(firstSearchPosition, false)
if firstHitPosition == nil {
p.mode = PagerModeNotFound{pager: p}
return
}
p.scrollPosition = *firstHitPosition
// Don't let any search hit scroll out of sight
p.TargetLineNumber = nil
}
func (p *Pager) scrollToPreviousSearchHit() {
if p.searchPattern == nil {
// Nothing to search for, never mind
return
}
if p.reader.GetLineCount() == 0 {
// Nothing to search in, never mind
return
}
var firstSearchPosition linenumbers.LineNumber
switch {
case p.isViewing():
// Start searching on the first line above the top of the screen
position := p.scrollPosition.PreviousLine(1)
firstSearchPosition = *position.lineNumber(p)
case p.isNotFound():
// Restart searching from the bottom
p.mode = PagerModeViewing{pager: p}
firstSearchPosition = *linenumbers.LineNumberFromLength(p.reader.GetLineCount())
default:
panic(fmt.Sprint("Unknown search mode when finding previous: ", p.mode))
}
firstHitPosition := p.findFirstHit(firstSearchPosition, true)
if firstHitPosition == nil {
p.mode = PagerModeNotFound{pager: p}
return
}
p.scrollPosition = *firstHitPosition
// Don't let any search hit scroll out of sight
p.TargetLineNumber = nil
}