@@ -83,6 +83,9 @@ public class CEUndoManager: UndoManager {
8383 textView. replaceCharacters ( in: mutation. inverse. range, with: mutation. inverse. string)
8484 }
8585 textView. textStorage. endEditing ( )
86+
87+ updateSelectionsForMutations ( mutations: item. mutations. map { $0. mutation } )
88+
8689 NotificationCenter . default. post ( name: . NSUndoManagerDidUndoChange, object: self )
8790 redoStack. append ( item)
8891 _isUndoing = false
@@ -101,16 +104,36 @@ public class CEUndoManager: UndoManager {
101104
102105 _isRedoing = true
103106 NotificationCenter . default. post ( name: . NSUndoManagerWillRedoChange, object: self )
107+ textView. selectionManager. removeCursors ( )
104108 textView. textStorage. beginEditing ( )
105109 for mutation in item. mutations {
106110 textView. replaceCharacters ( in: mutation. mutation. range, with: mutation. mutation. string)
107111 }
108112 textView. textStorage. endEditing ( )
113+
114+ updateSelectionsForMutations ( mutations: item. mutations. map { $0. inverse } )
115+
109116 NotificationCenter . default. post ( name: . NSUndoManagerDidRedoChange, object: self )
110117 undoStack. append ( item)
111118 _isRedoing = false
112119 }
113120
121+ /// We often undo/redo a group of mutations that contain updated ranges that are next to each other but for a user
122+ /// should be one continuous range. This merges those ranges into a set of disjoint ranges before updating the
123+ /// selection manager.
124+ private func updateSelectionsForMutations( mutations: [ TextMutation ] ) {
125+ if mutations. reduce ( 0 , { $0 + $1. range. length } ) == 0 , let last = mutations. last {
126+ // If the mutations are only deleting text (no replacement), we just place the cursor at the last range,
127+ // since all the ranges are the same but the other method will return no ranges (empty range).
128+ textView? . selectionManager. setSelectedRange ( last. range)
129+ } else {
130+ let mergedRanges = mutations. reduce ( into: IndexSet ( ) , { set, mutation in
131+ set. insert ( range: mutation. range)
132+ } )
133+ textView? . selectionManager. setSelectedRanges ( mergedRanges. rangeView. map { NSRange ( $0) } )
134+ }
135+ }
136+
114137 /// Clears the undo/redo stacks.
115138 public func clearStack( ) {
116139 undoStack. removeAll ( )
0 commit comments