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
|
package buffer
import "strings"
// Find returns the first occurrence of needle at or after from, and whether
// there was one.
//
// The search wraps round the end of the buffer back to its start, so pressing
// "find next" repeatedly cycles through every match rather than stopping at
// the bottom of the file.
//
// if r, ok := b.Find("func", b.Cursor(), false); ok {
// b.SetCursor(r.Start)
// }
func (b *Buffer) Find(needle string, from Position, matchCase bool) (Range, bool) {
if needle == "" {
return Range{}, false
}
from = b.clamp(from)
if found, ok := b.findBetween(needle, from, len(b.lines)-1, matchCase); ok {
return found, true
}
// Nothing ahead: start again from the top, up to and including the line
// the search began on.
return b.findBetween(needle, Position{}, from.Line, matchCase)
}
// FindPrevious returns the last occurrence of needle strictly before from,
// wrapping round to the end of the buffer.
func (b *Buffer) FindPrevious(needle string, from Position, matchCase bool) (Range, bool) {
if needle == "" {
return Range{}, false
}
from = b.clamp(from)
if found, ok := b.findBackwardsFrom(needle, from, matchCase); ok {
return found, true
}
return b.findBackwardsFrom(needle, b.wholeRange().End, matchCase)
}
// findBetween scans forwards from a position to the end of a line.
func (b *Buffer) findBetween(needle string, from Position, lastLine int, matchCase bool) (Range, bool) {
for line := from.Line; line <= lastLine && line < len(b.lines); line++ {
start := 0
if line == from.Line {
start = from.Col
}
if column, ok := indexFrom(b.Line(line), needle, start, matchCase); ok {
return matchRange(line, column, needle), true
}
}
return Range{}, false
}
// findBackwardsFrom scans backwards from a position to the top of the buffer.
func (b *Buffer) findBackwardsFrom(needle string, from Position, matchCase bool) (Range, bool) {
for line := from.Line; line >= 0; line-- {
limit := len(b.lines[line])
if line == from.Line {
limit = from.Col
}
if column, ok := lastIndexBefore(b.Line(line), needle, limit, matchCase); ok {
return matchRange(line, column, needle), true
}
}
return Range{}, false
}
// matchRange turns a rune column and a needle into the range it covers.
func matchRange(line, column int, needle string) Range {
return Range{
Start: Position{Line: line, Col: column},
End: Position{Line: line, Col: column + len([]rune(needle))},
}
}
// indexFrom returns the rune column of the first occurrence of needle in line
// at or after the rune column start.
func indexFrom(line, needle string, start int, matchCase bool) (int, bool) {
runes := []rune(line)
if start > len(runes) {
return 0, false
}
haystack, wanted := foldIfNeeded(string(runes[start:]), needle, matchCase)
index := strings.Index(haystack, wanted)
if index < 0 {
return 0, false
}
return start + len([]rune(haystack[:index])), true
}
// lastIndexBefore returns the rune column of the last occurrence of needle in
// line that starts before the rune column limit.
func lastIndexBefore(line, needle string, limit int, matchCase bool) (int, bool) {
runes := []rune(line)
limit = min(max(limit, 0), len(runes))
haystack, wanted := foldIfNeeded(string(runes[:limit]), needle, matchCase)
index := strings.LastIndex(haystack, wanted)
if index < 0 {
return 0, false
}
return len([]rune(haystack[:index])), true
}
// foldIfNeeded lowercases both strings for a case-insensitive search.
//
// Lowercasing can change a string's byte length, but never its rune count for
// the alphabets this matters for, and every offset above is converted back
// through a rune count — so the columns stay right.
func foldIfNeeded(haystack, needle string, matchCase bool) (string, string) {
if matchCase {
return haystack, needle
}
return strings.ToLower(haystack), strings.ToLower(needle)
}
|