-
Notifications
You must be signed in to change notification settings - Fork 136
Expand file tree
/
Copy pathFindPanelViewModel+Find.swift
More file actions
119 lines (94 loc) · 3.77 KB
/
FindPanelViewModel+Find.swift
File metadata and controls
119 lines (94 loc) · 3.77 KB
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
//
// FindPanelViewModel+Find.swift
// CodeEditSourceEditor
//
// Created by Khan Winter on 4/18/25.
//
import Foundation
extension FindPanelViewModel {
// MARK: - Find
/// Performs a find operation on the find target and updates both the ``findMatches`` array and the emphasis
/// manager's emphases.
func find() {
// Don't find if target isn't ready or the query is empty
guard let target = target, !findText.isEmpty else {
self.findMatches = []
return
}
// Set case sensitivity based on matchCase property
var findOptions: NSRegularExpression.Options = matchCase ? [] : [.caseInsensitive]
// Add multiline options for regular expressions
if findMethod == .regularExpression {
findOptions.insert(.dotMatchesLineSeparators)
findOptions.insert(.anchorsMatchLines)
}
let pattern: String
switch findMethod {
case .contains:
// Simple substring match, escape special characters
pattern = NSRegularExpression.escapedPattern(for: findText)
case .matchesWord:
// Match whole words only using word boundaries
pattern = "\\b" + NSRegularExpression.escapedPattern(for: findText) + "\\b"
case .startsWith:
// Match at the start of a line or after a word boundary
pattern = "(?:^|\\b)" + NSRegularExpression.escapedPattern(for: findText)
case .endsWith:
// Match at the end of a line or before a word boundary
pattern = NSRegularExpression.escapedPattern(for: findText) + "(?:$|\\b)"
case .regularExpression:
// Use the pattern directly without additional escaping
pattern = findText
}
guard let regex = try? NSRegularExpression(pattern: pattern, options: findOptions) else {
self.findMatches = []
self.currentFindMatchIndex = 0
return
}
let text = target.textView.string
let nsText = text as NSString
let range = NSRange(location: 0, length: nsText.length)
let matches = regex.matches(in: text, range: range)
self.findMatches = matches.map(\.range)
// Find the nearest match to the current cursor position
currentFindMatchIndex = getNearestEmphasisIndex(matchRanges: findMatches) ?? 0
// Only add emphasis layers if the find panel is focused
if isFocused {
addMatchEmphases(flashCurrent: false)
}
}
// MARK: - Get Nearest Emphasis Index
private func getNearestEmphasisIndex(matchRanges: [NSRange]) -> Int? {
// order the array as follows
// Found: 1 -> 2 -> 3 -> 4
// Cursor: |
// Result: 3 -> 4 -> 1 -> 2
guard let cursorPosition = target?.cursorPositions.first else { return nil }
let start = cursorPosition.range.location
var left = 0
var right = matchRanges.count - 1
var bestIndex = -1
var bestDiff = Int.max // Stores the closest difference
while left <= right {
let mid = left + (right - left) / 2
let midStart = matchRanges[mid].location
let diff = abs(midStart - start)
// If it's an exact match, return immediately
if diff == 0 {
return mid
}
// If this is the closest so far, update the best index
if diff < bestDiff {
bestDiff = diff
bestIndex = mid
}
// Move left or right based on the cursor position
if midStart < start {
left = mid + 1
} else {
right = mid - 1
}
}
return bestIndex >= 0 ? bestIndex : nil
}
}