@@ -83,6 +83,9 @@ public class CEUndoManager: UndoManager {
83
83
textView. replaceCharacters ( in: mutation. inverse. range, with: mutation. inverse. string)
84
84
}
85
85
textView. textStorage. endEditing ( )
86
+
87
+ updateSelectionsForMutations ( mutations: item. mutations. map { $0. mutation } )
88
+
86
89
NotificationCenter . default. post ( name: . NSUndoManagerDidUndoChange, object: self )
87
90
redoStack. append ( item)
88
91
_isUndoing = false
@@ -101,16 +104,36 @@ public class CEUndoManager: UndoManager {
101
104
102
105
_isRedoing = true
103
106
NotificationCenter . default. post ( name: . NSUndoManagerWillRedoChange, object: self )
107
+ textView. selectionManager. removeCursors ( )
104
108
textView. textStorage. beginEditing ( )
105
109
for mutation in item. mutations {
106
110
textView. replaceCharacters ( in: mutation. mutation. range, with: mutation. mutation. string)
107
111
}
108
112
textView. textStorage. endEditing ( )
113
+
114
+ updateSelectionsForMutations ( mutations: item. mutations. map { $0. inverse } )
115
+
109
116
NotificationCenter . default. post ( name: . NSUndoManagerDidRedoChange, object: self )
110
117
undoStack. append ( item)
111
118
_isRedoing = false
112
119
}
113
120
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
+
114
137
/// Clears the undo/redo stacks.
115
138
public func clearStack( ) {
116
139
undoStack. removeAll ( )
0 commit comments