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.

🛟 Updated. 28d5985 · on 28d59854361aeda8541d853093e732126f3d7bff · k33g · 20h ago
find.go · 119 lines · 3.8 KBGo Blame HistoryRaw
  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)
}