turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

find.go · 119 lines · 3.8 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 21h ago1package buffer
2
3import "strings"
4
5// Find returns the first occurrence of needle at or after from, and whether
6// there was one.
7//
8// The search wraps round the end of the buffer back to its start, so pressing
9// "find next" repeatedly cycles through every match rather than stopping at
10// the bottom of the file.
11//
12// if r, ok := b.Find("func", b.Cursor(), false); ok {
13// b.SetCursor(r.Start)
14// }
15func (b *Buffer) Find(needle string, from Position, matchCase bool) (Range, bool) {
16 if needle == "" {
17 return Range{}, false
18 }
19 from = b.clamp(from)
20
21 if found, ok := b.findBetween(needle, from, len(b.lines)-1, matchCase); ok {
22 return found, true
23 }
24 // Nothing ahead: start again from the top, up to and including the line
25 // the search began on.
26 return b.findBetween(needle, Position{}, from.Line, matchCase)
27}
28
29// FindPrevious returns the last occurrence of needle strictly before from,
30// wrapping round to the end of the buffer.
31func (b *Buffer) FindPrevious(needle string, from Position, matchCase bool) (Range, bool) {
32 if needle == "" {
33 return Range{}, false
34 }
35 from = b.clamp(from)
36
37 if found, ok := b.findBackwardsFrom(needle, from, matchCase); ok {
38 return found, true
39 }
40 return b.findBackwardsFrom(needle, b.wholeRange().End, matchCase)
41}
42
43// findBetween scans forwards from a position to the end of a line.
44func (b *Buffer) findBetween(needle string, from Position, lastLine int, matchCase bool) (Range, bool) {
45 for line := from.Line; line <= lastLine && line < len(b.lines); line++ {
46 start := 0
47 if line == from.Line {
48 start = from.Col
49 }
50 if column, ok := indexFrom(b.Line(line), needle, start, matchCase); ok {
51 return matchRange(line, column, needle), true
52 }
53 }
54 return Range{}, false
55}
56
57// findBackwardsFrom scans backwards from a position to the top of the buffer.
58func (b *Buffer) findBackwardsFrom(needle string, from Position, matchCase bool) (Range, bool) {
59 for line := from.Line; line >= 0; line-- {
60 limit := len(b.lines[line])
61 if line == from.Line {
62 limit = from.Col
63 }
64 if column, ok := lastIndexBefore(b.Line(line), needle, limit, matchCase); ok {
65 return matchRange(line, column, needle), true
66 }
67 }
68 return Range{}, false
69}
70
71// matchRange turns a rune column and a needle into the range it covers.
72func matchRange(line, column int, needle string) Range {
73 return Range{
74 Start: Position{Line: line, Col: column},
75 End: Position{Line: line, Col: column + len([]rune(needle))},
76 }
77}
78
79// indexFrom returns the rune column of the first occurrence of needle in line
80// at or after the rune column start.
81func indexFrom(line, needle string, start int, matchCase bool) (int, bool) {
82 runes := []rune(line)
83 if start > len(runes) {
84 return 0, false
85 }
86
87 haystack, wanted := foldIfNeeded(string(runes[start:]), needle, matchCase)
88 index := strings.Index(haystack, wanted)
89 if index < 0 {
90 return 0, false
91 }
92 return start + len([]rune(haystack[:index])), true
93}
94
95// lastIndexBefore returns the rune column of the last occurrence of needle in
96// line that starts before the rune column limit.
97func lastIndexBefore(line, needle string, limit int, matchCase bool) (int, bool) {
98 runes := []rune(line)
99 limit = min(max(limit, 0), len(runes))
100
101 haystack, wanted := foldIfNeeded(string(runes[:limit]), needle, matchCase)
102 index := strings.LastIndex(haystack, wanted)
103 if index < 0 {
104 return 0, false
105 }
106 return len([]rune(haystack[:index])), true
107}
108
109// foldIfNeeded lowercases both strings for a case-insensitive search.
110//
111// Lowercasing can change a string's byte length, but never its rune count for
112// the alphabets this matters for, and every offset above is converted back
113// through a rune count — so the columns stay right.
114func foldIfNeeded(haystack, needle string, matchCase bool) (string, string) {
115 if matchCase {
116 return haystack, needle
117 }
118 return strings.ToLower(haystack), strings.ToLower(needle)
119}