@@ -95,6 +95,7 @@ public class TextSelectionManager: NSObject {
9595 ( 0 ... ( textStorage? . length ?? 0 ) ) . contains ( $0. location)
9696 && ( 0 ... ( textStorage? . length ?? 0 ) ) . contains ( $0. max)
9797 }
98+ . sorted ( by: { $0. location < $1. location } )
9899 . map {
99100 let selection = TextSelection ( range: $0)
100101 selection. suggestedXPos = layoutManager? . rectForOffset ( $0. location) ? . minX
@@ -127,6 +128,7 @@ public class TextSelectionManager: NSObject {
127128 }
128129 if !didHandle {
129130 textSelections. append ( newTextSelection)
131+ textSelections. sort ( by: { $0. range. location < $1. range. location } )
130132 }
131133
132134 updateSelectionViews ( )
@@ -136,72 +138,80 @@ public class TextSelectionManager: NSObject {
136138
137139 // MARK: - Selection Views
138140
139- /// Update all selection cursors. Placing them in the correct position for each text selection and reseting the
140- /// blink timer.
141- func updateSelectionViews( force: Bool = false ) {
141+ /// Update all selection cursors. Placing them in the correct position for each text selection and
142+ /// optionally reseting the blink timer.
143+ func updateSelectionViews( force: Bool = false , skipTimerReset : Bool = false ) {
142144 guard textView? . isFirstResponder ?? false else { return }
143145 var didUpdate : Bool = false
144146
145147 for textSelection in textSelections {
146148 if textSelection. range. isEmpty {
147- guard let cursorRect = layoutManager? . rectForOffset ( textSelection. range. location) else {
148- continue
149- }
150-
151- var doesViewNeedReposition : Bool
152-
153- // If using the system cursor, macOS will change the origin and height by about 0.5, so we do an
154- // approximate equals in that case to avoid extra updates.
155- if useSystemCursor, #available( macOS 14 . 0 , * ) {
156- doesViewNeedReposition = !textSelection. boundingRect. origin. approxEqual ( cursorRect. origin)
157- || !textSelection. boundingRect. height. approxEqual ( layoutManager? . estimateLineHeight ( ) ?? 0 )
158- } else {
159- doesViewNeedReposition = textSelection. boundingRect. origin != cursorRect. origin
160- || textSelection. boundingRect. height != layoutManager? . estimateLineHeight ( ) ?? 0
161- }
162-
163- if textSelection. view == nil || doesViewNeedReposition {
164- let cursorView : NSView
149+ didUpdate = didUpdate || repositionCursorSelection ( textSelection: textSelection)
150+ } else if !textSelection. range. isEmpty && textSelection. view != nil {
151+ textSelection. view? . removeFromSuperview ( )
152+ textSelection. view = nil
153+ didUpdate = true
154+ }
155+ }
165156
166- if let existingCursorView = textSelection. view {
167- cursorView = existingCursorView
168- } else {
169- textSelection. view? . removeFromSuperview ( )
170- textSelection. view = nil
157+ if didUpdate || force {
158+ delegate? . setNeedsDisplay ( )
159+ if !skipTimerReset {
160+ cursorTimer. resetTimer ( )
161+ resetSystemCursorTimers ( )
162+ }
163+ }
164+ }
171165
172- if useSystemCursor, #available( macOS 14 . 0 , * ) {
173- let systemCursorView = NSTextInsertionIndicator ( frame: . zero)
174- cursorView = systemCursorView
175- systemCursorView. displayMode = . automatic
176- } else {
177- let internalCursorView = CursorView ( color: insertionPointColor)
178- cursorView = internalCursorView
179- cursorTimer. register ( internalCursorView)
180- }
166+ private func repositionCursorSelection( textSelection: TextSelection ) -> Bool {
167+ guard let cursorRect = layoutManager? . rectForOffset ( textSelection. range. location) else {
168+ return false
169+ }
181170
182- textView? . addSubview ( cursorView, positioned: . above, relativeTo: nil )
183- }
171+ var doesViewNeedReposition : Bool
184172
185- cursorView. frame. origin = cursorRect. origin
186- cursorView. frame. size. height = cursorRect. height
173+ // If using the system cursor, macOS will change the origin and height by about 0.5, so we do an
174+ // approximate equals in that case to avoid extra updates.
175+ if useSystemCursor, #available( macOS 14 . 0 , * ) {
176+ doesViewNeedReposition = !textSelection. boundingRect. origin. approxEqual ( cursorRect. origin)
177+ || !textSelection. boundingRect. height. approxEqual ( layoutManager? . estimateLineHeight ( ) ?? 0 )
178+ } else {
179+ doesViewNeedReposition = textSelection. boundingRect. origin != cursorRect. origin
180+ || textSelection. boundingRect. height != layoutManager? . estimateLineHeight ( ) ?? 0
181+ }
187182
188- textSelection. view = cursorView
189- textSelection . boundingRect = cursorView. frame
183+ if textSelection. view == nil || doesViewNeedReposition {
184+ let cursorView : NSView
190185
191- didUpdate = true
192- }
193- } else if !textSelection . range . isEmpty && textSelection . view != nil {
186+ if let existingCursorView = textSelection . view {
187+ cursorView = existingCursorView
188+ } else {
194189 textSelection. view? . removeFromSuperview ( )
195190 textSelection. view = nil
196- didUpdate = true
191+
192+ if useSystemCursor, #available( macOS 14 . 0 , * ) {
193+ let systemCursorView = NSTextInsertionIndicator ( frame: . zero)
194+ cursorView = systemCursorView
195+ systemCursorView. displayMode = . automatic
196+ } else {
197+ let internalCursorView = CursorView ( color: insertionPointColor)
198+ cursorView = internalCursorView
199+ cursorTimer. register ( internalCursorView)
200+ }
201+
202+ textView? . addSubview ( cursorView, positioned: . above, relativeTo: nil )
197203 }
198- }
199204
200- if didUpdate || force {
201- delegate? . setNeedsDisplay ( )
202- cursorTimer. resetTimer ( )
203- resetSystemCursorTimers ( )
205+ cursorView. frame. origin = cursorRect. origin
206+ cursorView. frame. size. height = cursorRect. height
207+
208+ textSelection. view = cursorView
209+ textSelection. boundingRect = cursorView. frame
210+
211+ return true
204212 }
213+
214+ return false
205215 }
206216
207217 private func resetSystemCursorTimers( ) {
0 commit comments