@@ -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