@@ -13,7 +13,12 @@ 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 ( )
@@ -34,13 +39,17 @@ extension TextView {
3439 in: range,
3540 with: NSAttributedString ( string: string, attributes: typingAttributes)
3641 )
37- selectionManager. didReplaceCharacters ( in: range, replacementLength: ( string as NSString ) . length)
42+ if !skipUpdateSelection {
43+ selectionManager. didReplaceCharacters ( in: range, replacementLength: ( string as NSString ) . length)
44+ }
3845
3946 delegate? . textView ( self , didReplaceContentsIn: range, with: string)
4047 }
4148
4249 textStorage. endEditing ( )
43- selectionManager. notifyAfterEdit ( )
50+ if !skipUpdateSelection {
51+ selectionManager. notifyAfterEdit ( )
52+ }
4453 NotificationCenter . default. post ( name: Self . textDidChangeNotification, object: self )
4554
4655 // `scrollSelectionToVisible` is a little expensive to call every time. Instead we just check if the first
@@ -54,7 +63,33 @@ extension TextView {
5463 /// - Parameters:
5564 /// - range: The range to replace.
5665 /// - string: The string to insert in the range.
57- public func replaceCharacters( in range: NSRange , with string: String ) {
58- 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 )
5994 }
6095}
0 commit comments