Skip to content

Commit 337b05f

Browse files
authored
Update replaceCharacters function (#87)
1 parent 6fa3516 commit 337b05f

File tree

3 files changed

+50
-12
lines changed

3 files changed

+50
-12
lines changed

Sources/CodeEditTextView/TextSelectionManager/TextSelectionManager+Update.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ extension TextSelectionManager {
3939
}
4040
}
4141

42-
func notifyAfterEdit() {
43-
updateSelectionViews()
42+
public func notifyAfterEdit(force: Bool = false) {
43+
updateSelectionViews(force: force)
4444
NotificationCenter.default.post(Notification(name: Self.selectionChangedNotification, object: self))
4545
}
4646
}

Sources/CodeEditTextView/TextSelectionManager/TextSelectionManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public class TextSelectionManager: NSObject {
138138

139139
/// Update all selection cursors. Placing them in the correct position for each text selection and reseting the
140140
/// blink timer.
141-
func updateSelectionViews() {
141+
func updateSelectionViews(force: Bool = false) {
142142
guard textView?.isFirstResponder ?? false else { return }
143143
var didUpdate: Bool = false
144144

@@ -197,7 +197,7 @@ public class TextSelectionManager: NSObject {
197197
}
198198
}
199199

200-
if didUpdate {
200+
if didUpdate || force {
201201
delegate?.setNeedsDisplay()
202202
cursorTimer.resetTimer()
203203
resetSystemCursorTimers()

Sources/CodeEditTextView/TextView/TextView+ReplaceCharacters.swift

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@ extension TextView {
1313
/// - Parameters:
1414
/// - ranges: The ranges to replace
1515
/// - string: The string to insert in the ranges.
16-
public func replaceCharacters(in ranges: [NSRange], with string: String) {
16+
/// - skipUpdateSelection: Skips the selection update step
17+
public func replaceCharacters(
18+
in ranges: [NSRange],
19+
with string: String,
20+
skipUpdateSelection: Bool = false
21+
) {
1722
guard isEditable else { return }
1823
NotificationCenter.default.post(name: Self.textWillChangeNotification, object: self)
1924
textStorage.beginEditing()
2025

26+
func valid(range: NSRange, string: String) -> Bool {
27+
(!range.isEmpty || !string.isEmpty) &&
28+
(delegate?.textView(self, shouldReplaceContentsIn: range, with: string) ?? true)
29+
}
30+
2131
// Can't insert an empty string into an empty range. One must be not empty
22-
for range in ranges.sorted(by: { $0.location > $1.location }) where
23-
(!range.isEmpty || !string.isEmpty) &&
24-
(delegate?.textView(self, shouldReplaceContentsIn: range, with: string) ?? true) {
32+
for range in ranges.sorted(by: { $0.location > $1.location }) where valid(range: range, string: string) {
2533
delegate?.textView(self, willReplaceContentsIn: range, with: string)
2634

2735
_undoManager?.registerMutation(
@@ -31,13 +39,17 @@ extension TextView {
3139
in: range,
3240
with: NSAttributedString(string: string, attributes: typingAttributes)
3341
)
34-
selectionManager.didReplaceCharacters(in: range, replacementLength: (string as NSString).length)
42+
if !skipUpdateSelection {
43+
selectionManager.didReplaceCharacters(in: range, replacementLength: (string as NSString).length)
44+
}
3545

3646
delegate?.textView(self, didReplaceContentsIn: range, with: string)
3747
}
3848

3949
textStorage.endEditing()
40-
selectionManager.notifyAfterEdit()
50+
if !skipUpdateSelection {
51+
selectionManager.notifyAfterEdit()
52+
}
4153
NotificationCenter.default.post(name: Self.textDidChangeNotification, object: self)
4254

4355
// `scrollSelectionToVisible` is a little expensive to call every time. Instead we just check if the first
@@ -51,7 +63,33 @@ extension TextView {
5163
/// - Parameters:
5264
/// - range: The range to replace.
5365
/// - string: The string to insert in the range.
54-
public func replaceCharacters(in range: NSRange, with string: String) {
55-
replaceCharacters(in: [range], with: string)
66+
/// - skipUpdateSelection: Skips the selection update step
67+
public func replaceCharacters(
68+
in range: NSRange,
69+
with string: String,
70+
skipUpdateSelection: Bool = false
71+
) {
72+
replaceCharacters(in: [range], with: string, skipUpdateSelection: skipUpdateSelection)
73+
}
74+
75+
/// Iterates over all text selections in the `TextView` and applies the provided callback.
76+
///
77+
/// This method is typically used when you need to perform an operation on each text selection in the editor,
78+
/// such as adjusting indentation, or other selection-based operations. The callback
79+
/// is executed for each selection, and you can modify the selection or perform related tasks.
80+
///
81+
/// - Parameters:
82+
/// - callback: A closure that will be executed for each selection in the `TextView`. It takes two parameters:
83+
/// a `TextView` instance, allowing access to the view's properties and methods and a
84+
/// `TextSelectionManager.TextSelection` representing the current selection to operate on.
85+
///
86+
/// - Note: The selections are iterated in reverse order, so modifications to earlier selections won't affect later
87+
/// ones. The method automatically calls `notifyAfterEdit()` on the `selectionManager` after all
88+
/// selections are processed.
89+
public func editSelections(callback: (TextView, TextSelectionManager.TextSelection) -> Void) {
90+
for textSelection in selectionManager.textSelections.reversed() {
91+
callback(self, textSelection)
92+
}
93+
selectionManager.notifyAfterEdit(force: true)
5694
}
5795
}

0 commit comments

Comments
 (0)