Skip to content

Commit 40b7660

Browse files
committed
Fixed SwiftLint errors and split files up.
1 parent 56f37c0 commit 40b7660

File tree

5 files changed

+442
-385
lines changed

5 files changed

+442
-385
lines changed

Sources/CodeEditSourceEditor/CodeEditSourceEditor/CodeEditSourceEditor.swift

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -263,31 +263,32 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
263263
/// Update the parameters of the controller.
264264
/// - Parameter controller: The controller to update.
265265
func updateControllerParams(controller: TextViewController) {
266+
updateTextProperties(controller)
267+
updateEditorProperties(controller)
268+
updateThemeAndLanguage(controller)
269+
updateHighlighting(controller)
270+
}
271+
272+
private func updateTextProperties(_ controller: TextViewController) {
266273
if controller.font != font {
267274
controller.font = font
268275
}
269276

270-
controller.wrapLines = wrapLines
271-
controller.useThemeBackground = useThemeBackground
272-
controller.lineHeightMultiple = lineHeight
273-
controller.editorOverscroll = editorOverscroll
274-
controller.contentInsets = contentInsets
275-
276277
if controller.isEditable != isEditable {
277278
controller.isEditable = isEditable
278279
}
279280

280281
if controller.isSelectable != isSelectable {
281282
controller.isSelectable = isSelectable
282283
}
284+
}
283285

284-
if controller.language.id != language.id {
285-
controller.language = language
286-
}
287-
288-
if controller.theme != theme {
289-
controller.theme = theme
290-
}
286+
private func updateEditorProperties(_ controller: TextViewController) {
287+
controller.wrapLines = wrapLines
288+
controller.useThemeBackground = useThemeBackground
289+
controller.lineHeightMultiple = lineHeight
290+
controller.editorOverscroll = editorOverscroll
291+
controller.contentInsets = contentInsets
291292

292293
if controller.indentOption != indentOption {
293294
controller.indentOption = indentOption
@@ -304,7 +305,19 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
304305
if controller.useSystemCursor != useSystemCursor {
305306
controller.useSystemCursor = useSystemCursor
306307
}
308+
}
309+
310+
private func updateThemeAndLanguage(_ controller: TextViewController) {
311+
if controller.language.id != language.id {
312+
controller.language = language
313+
}
314+
315+
if controller.theme != theme {
316+
controller.theme = theme
317+
}
318+
}
307319

320+
private func updateHighlighting(_ controller: TextViewController) {
308321
if !areHighlightProvidersEqual(controller: controller) {
309322
controller.setHighlightProviders(highlightProviders)
310323
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//
2+
// FindViewController+Delegate.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Austin Condiff on 4/3/25.
6+
//
7+
8+
import AppKit
9+
import CodeEditTextView
10+
11+
extension FindViewController: FindPanelDelegate {
12+
func findPanelOnSubmit() {
13+
findPanelNextButtonClicked()
14+
}
15+
16+
func findPanelOnDismiss() {
17+
if isShowingFindPanel {
18+
hideFindPanel()
19+
// Ensure text view becomes first responder after hiding
20+
if let textViewController = target as? TextViewController {
21+
DispatchQueue.main.async {
22+
_ = textViewController.textView.window?.makeFirstResponder(textViewController.textView)
23+
}
24+
}
25+
}
26+
}
27+
28+
func findPanelDidUpdate(_ text: String) {
29+
// Check if this update was triggered by a return key without shift
30+
if let currentEvent = NSApp.currentEvent,
31+
currentEvent.type == .keyDown,
32+
currentEvent.keyCode == 36, // Return key
33+
!currentEvent.modifierFlags.contains(.shift) {
34+
return // Skip find for regular return key
35+
}
36+
37+
// Only perform find if we're focusing the text view
38+
if let textViewController = target as? TextViewController,
39+
textViewController.textView.window?.firstResponder === textViewController.textView {
40+
// If the text view has focus, just clear visual emphases but keep matches in memory
41+
target?.emphasisManager?.removeEmphases(for: "find")
42+
// Re-add the current active emphasis without visual emphasis
43+
if let emphases = target?.emphasisManager?.getEmphases(for: "find"),
44+
let activeEmphasis = emphases.first(where: { !$0.inactive }) {
45+
target?.emphasisManager?.addEmphasis(
46+
Emphasis(
47+
range: activeEmphasis.range,
48+
style: .standard,
49+
flash: false,
50+
inactive: false,
51+
select: true
52+
),
53+
for: "find"
54+
)
55+
}
56+
return
57+
}
58+
59+
// Clear existing emphases before performing new find
60+
target?.emphasisManager?.removeEmphases(for: "find")
61+
find(text: text)
62+
}
63+
64+
func findPanelPrevButtonClicked() {
65+
guard let textViewController = target as? TextViewController,
66+
let emphasisManager = target?.emphasisManager else { return }
67+
68+
// Check if there are any matches
69+
if findMatches.isEmpty {
70+
return
71+
}
72+
73+
// Update to previous match
74+
let oldIndex = currentFindMatchIndex
75+
currentFindMatchIndex = (currentFindMatchIndex - 1 + findMatches.count) % findMatches.count
76+
77+
// Show bezel notification if we cycled from first to last match
78+
if oldIndex == 0 && currentFindMatchIndex == findMatches.count - 1 {
79+
BezelNotification.show(
80+
symbolName: "arrow.trianglehead.bottomleft.capsulepath.clockwise",
81+
over: textViewController.textView
82+
)
83+
}
84+
85+
// If the text view has focus, show a flash animation for the current match
86+
if textViewController.textView.window?.firstResponder === textViewController.textView {
87+
let newActiveRange = findMatches[currentFindMatchIndex]
88+
89+
// Clear existing emphases before adding the flash
90+
emphasisManager.removeEmphases(for: "find")
91+
92+
emphasisManager.addEmphasis(
93+
Emphasis(
94+
range: newActiveRange,
95+
style: .standard,
96+
flash: true,
97+
inactive: false,
98+
select: true
99+
),
100+
for: "find"
101+
)
102+
103+
return
104+
}
105+
106+
// Create updated emphases with new active state
107+
let updatedEmphases = findMatches.enumerated().map { index, range in
108+
Emphasis(
109+
range: range,
110+
style: .standard,
111+
flash: false,
112+
inactive: index != currentFindMatchIndex,
113+
select: index == currentFindMatchIndex
114+
)
115+
}
116+
117+
// Replace all emphases to update state
118+
emphasisManager.replaceEmphases(updatedEmphases, for: "find")
119+
}
120+
121+
func findPanelNextButtonClicked() {
122+
guard let textViewController = target as? TextViewController,
123+
let emphasisManager = target?.emphasisManager else { return }
124+
125+
// Check if there are any matches
126+
if findMatches.isEmpty {
127+
// Show "no matches" bezel notification and play beep
128+
NSSound.beep()
129+
BezelNotification.show(
130+
symbolName: "arrow.down.to.line",
131+
over: textViewController.textView
132+
)
133+
return
134+
}
135+
136+
// Update to next match
137+
let oldIndex = currentFindMatchIndex
138+
currentFindMatchIndex = (currentFindMatchIndex + 1) % findMatches.count
139+
140+
// Show bezel notification if we cycled from last to first match
141+
if oldIndex == findMatches.count - 1 && currentFindMatchIndex == 0 {
142+
BezelNotification.show(
143+
symbolName: "arrow.triangle.capsulepath",
144+
over: textViewController.textView
145+
)
146+
}
147+
148+
// If the text view has focus, show a flash animation for the current match
149+
if textViewController.textView.window?.firstResponder === textViewController.textView {
150+
let newActiveRange = findMatches[currentFindMatchIndex]
151+
152+
// Clear existing emphases before adding the flash
153+
emphasisManager.removeEmphases(for: "find")
154+
155+
emphasisManager.addEmphasis(
156+
Emphasis(
157+
range: newActiveRange,
158+
style: .standard,
159+
flash: true,
160+
inactive: false,
161+
select: true
162+
),
163+
for: "find"
164+
)
165+
166+
return
167+
}
168+
169+
// Create updated emphases with new active state
170+
let updatedEmphases = findMatches.enumerated().map { index, range in
171+
Emphasis(
172+
range: range,
173+
style: .standard,
174+
flash: false,
175+
inactive: index != currentFindMatchIndex,
176+
select: index == currentFindMatchIndex
177+
)
178+
}
179+
180+
// Replace all emphases to update state
181+
emphasisManager.replaceEmphases(updatedEmphases, for: "find")
182+
}
183+
184+
func findPanelUpdateMatchCount(_ count: Int) {
185+
findPanel.updateMatchCount(count)
186+
}
187+
188+
func findPanelClearEmphasis() {
189+
target?.emphasisManager?.removeEmphases(for: "find")
190+
}
191+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//
2+
// FindViewController+Operations.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Austin Condiff on 4/3/25.
6+
//
7+
8+
import AppKit
9+
import CodeEditTextView
10+
11+
extension FindViewController {
12+
func find(text: String) {
13+
findText = text
14+
performFind(query: text)
15+
addEmphases()
16+
}
17+
18+
func performFind(query: String) {
19+
// Don't find if target or emphasisManager isn't ready
20+
guard let target = target else {
21+
findPanel.findDelegate?.findPanelUpdateMatchCount(0)
22+
findMatches = []
23+
currentFindMatchIndex = 0
24+
return
25+
}
26+
27+
// Clear emphases and return if query is empty
28+
if query.isEmpty {
29+
findPanel.findDelegate?.findPanelUpdateMatchCount(0)
30+
findMatches = []
31+
currentFindMatchIndex = 0
32+
return
33+
}
34+
35+
let findOptions: NSRegularExpression.Options = smartCase(str: query) ? [] : [.caseInsensitive]
36+
let escapedQuery = NSRegularExpression.escapedPattern(for: query)
37+
38+
guard let regex = try? NSRegularExpression(pattern: escapedQuery, options: findOptions) else {
39+
findPanel.findDelegate?.findPanelUpdateMatchCount(0)
40+
findMatches = []
41+
currentFindMatchIndex = 0
42+
return
43+
}
44+
45+
let text = target.text
46+
let matches = regex.matches(in: text, range: NSRange(location: 0, length: text.utf16.count))
47+
48+
findMatches = matches.map { $0.range }
49+
findPanel.findDelegate?.findPanelUpdateMatchCount(findMatches.count)
50+
51+
// Find the nearest match to the current cursor position
52+
currentFindMatchIndex = getNearestEmphasisIndex(matchRanges: findMatches) ?? 0
53+
}
54+
55+
private func addEmphases() {
56+
guard let target = target,
57+
let emphasisManager = target.emphasisManager else { return }
58+
59+
// Clear existing emphases
60+
emphasisManager.removeEmphases(for: "find")
61+
62+
// Create emphasis with the nearest match as active
63+
let emphases = findMatches.enumerated().map { index, range in
64+
Emphasis(
65+
range: range,
66+
style: .standard,
67+
flash: false,
68+
inactive: index != currentFindMatchIndex,
69+
select: index == currentFindMatchIndex
70+
)
71+
}
72+
73+
// Add all emphases
74+
emphasisManager.addEmphases(emphases, for: "find")
75+
}
76+
77+
private func getNearestEmphasisIndex(matchRanges: [NSRange]) -> Int? {
78+
// order the array as follows
79+
// Found: 1 -> 2 -> 3 -> 4
80+
// Cursor: |
81+
// Result: 3 -> 4 -> 1 -> 2
82+
guard let cursorPosition = target?.cursorPositions.first else { return nil }
83+
let start = cursorPosition.range.location
84+
85+
var left = 0
86+
var right = matchRanges.count - 1
87+
var bestIndex = -1
88+
var bestDiff = Int.max // Stores the closest difference
89+
90+
while left <= right {
91+
let mid = left + (right - left) / 2
92+
let midStart = matchRanges[mid].location
93+
let diff = abs(midStart - start)
94+
95+
// If it's an exact match, return immediately
96+
if diff == 0 {
97+
return mid
98+
}
99+
100+
// If this is the closest so far, update the best index
101+
if diff < bestDiff {
102+
bestDiff = diff
103+
bestIndex = mid
104+
}
105+
106+
// Move left or right based on the cursor position
107+
if midStart < start {
108+
left = mid + 1
109+
} else {
110+
right = mid - 1
111+
}
112+
}
113+
114+
return bestIndex >= 0 ? bestIndex : nil
115+
}
116+
117+
// Only re-find the part of the file that changed upwards
118+
private func reFind() { }
119+
120+
// Returns true if string contains uppercase letter
121+
// used for: ignores letter case if the find text is all lowercase
122+
private func smartCase(str: String) -> Bool {
123+
return str.range(of: "[A-Z]", options: .regularExpression) != nil
124+
}
125+
}

0 commit comments

Comments
 (0)