| 🛟 Updated. 28d5985 k33g 18h ago | 1 | package buffer |
| 2 | |
| 3 | import "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 | // } |
| 15 | func (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. |
| 31 | func (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. |
| 44 | func (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. |
| 58 | func (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. |
| 72 | func 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. |
| 81 | func 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. |
| 97 | func 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. |
| 114 | func 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 | } |