@@ -41,10 +41,11 @@ extension TextView {
4141 super. mouseDown ( with: event)
4242 return
4343 }
44- if event. modifierFlags. intersection ( . deviceIndependentFlagsMask) . isSuperset ( of: [ . control, . shift] ) {
44+ let eventFlags = event. modifierFlags. intersection ( . deviceIndependentFlagsMask)
45+ if eventFlags == [ . control, . shift] {
4546 unmarkText ( )
4647 selectionManager. addSelectedRange ( NSRange ( location: offset, length: 0 ) )
47- } else if event . modifierFlags . intersection ( . deviceIndependentFlagsMask ) . contains ( . shift) {
48+ } else if eventFlags . contains ( . shift) {
4849 unmarkText ( )
4950 shiftClickExtendSelection ( to: offset)
5051 } else {
@@ -96,40 +97,11 @@ extension TextView {
9697 return
9798 }
9899
99- switch cursorSelectionMode {
100- case . character:
101- selectionManager. setSelectedRange (
102- NSRange (
103- location: min ( startPosition, endPosition) ,
104- length: max ( startPosition, endPosition) - min( startPosition, endPosition)
105- )
106- )
107-
108- case . word:
109- let startWordRange = findWordBoundary ( at: startPosition)
110- let endWordRange = findWordBoundary ( at: endPosition)
111-
112- selectionManager. setSelectedRange (
113- NSRange (
114- location: min ( startWordRange. location, endWordRange. location) ,
115- length: max ( startWordRange. location + startWordRange. length,
116- endWordRange. location + endWordRange. length) -
117- min( startWordRange. location, endWordRange. location)
118- )
119- )
120-
121- case . line:
122- let startLineRange = findLineBoundary ( at: startPosition)
123- let endLineRange = findLineBoundary ( at: endPosition)
124-
125- selectionManager. setSelectedRange (
126- NSRange (
127- location: min ( startLineRange. location, endLineRange. location) ,
128- length: max ( startLineRange. location + startLineRange. length,
129- endLineRange. location + endLineRange. length) -
130- min( startLineRange. location, endLineRange. location)
131- )
132- )
100+ let modifierFlags = event. modifierFlags. intersection ( . deviceIndependentFlagsMask)
101+ if modifierFlags. contains ( . option) {
102+ dragColumnSelection ( mouseDragAnchor: mouseDragAnchor, event: event)
103+ } else {
104+ dragSelection ( startPosition: startPosition, endPosition: endPosition, mouseDragAnchor: mouseDragAnchor)
133105 }
134106
135107 setNeedsDisplay ( )
@@ -182,4 +154,83 @@ extension TextView {
182154 mouseDragTimer? . invalidate ( )
183155 mouseDragTimer = nil
184156 }
157+
158+ private func dragSelection( startPosition: Int , endPosition: Int , mouseDragAnchor: CGPoint ) {
159+ switch cursorSelectionMode {
160+ case . character:
161+ selectionManager. setSelectedRange (
162+ NSRange (
163+ location: min ( startPosition, endPosition) ,
164+ length: max ( startPosition, endPosition) - min( startPosition, endPosition)
165+ )
166+ )
167+
168+ case . word:
169+ let startWordRange = findWordBoundary ( at: startPosition)
170+ let endWordRange = findWordBoundary ( at: endPosition)
171+
172+ selectionManager. setSelectedRange (
173+ NSRange (
174+ location: min ( startWordRange. location, endWordRange. location) ,
175+ length: max ( startWordRange. location + startWordRange. length,
176+ endWordRange. location + endWordRange. length) -
177+ min( startWordRange. location, endWordRange. location)
178+ )
179+ )
180+
181+ case . line:
182+ let startLineRange = findLineBoundary ( at: startPosition)
183+ let endLineRange = findLineBoundary ( at: endPosition)
184+
185+ selectionManager. setSelectedRange (
186+ NSRange (
187+ location: min ( startLineRange. location, endLineRange. location) ,
188+ length: max ( startLineRange. location + startLineRange. length,
189+ endLineRange. location + endLineRange. length) -
190+ min( startLineRange. location, endLineRange. location)
191+ )
192+ )
193+ }
194+ }
195+
196+ private func dragColumnSelection( mouseDragAnchor: CGPoint , event: NSEvent ) {
197+ // Drag the selection and select in columns
198+ let eventLocation = convert ( event. locationInWindow, from: nil )
199+
200+ let start = CGPoint (
201+ x: min ( mouseDragAnchor. x, eventLocation. x) ,
202+ y: min ( mouseDragAnchor. y, eventLocation. y)
203+ )
204+ let end = CGPoint (
205+ x: max ( mouseDragAnchor. x, eventLocation. x) ,
206+ y: max ( mouseDragAnchor. y, eventLocation. y)
207+ )
208+
209+ // Collect all overlapping text ranges
210+ var selectedRanges : [ NSRange ] = layoutManager. linesStartingAt ( start. y, until: end. y) . flatMap { textLine in
211+ // Collect fragment ranges
212+ return textLine. data. lineFragments. compactMap { lineFragment -> NSRange ? in
213+ let startOffset = self . layoutManager. textOffsetAtPoint (
214+ start,
215+ fragmentPosition: lineFragment,
216+ linePosition: textLine
217+ )
218+ let endOffset = self . layoutManager. textOffsetAtPoint (
219+ end,
220+ fragmentPosition: lineFragment,
221+ linePosition: textLine
222+ )
223+ guard let startOffset, let endOffset else { return nil }
224+
225+ return NSRange ( start: startOffset, end: endOffset)
226+ }
227+ }
228+
229+ // If we have some non-cursor selections, filter out any cursor selections
230+ if selectedRanges. contains ( where: { !$0. isEmpty } ) {
231+ selectedRanges = selectedRanges. filter ( { !$0. isEmpty } )
232+ }
233+
234+ selectionManager. setSelectedRanges ( selectedRanges)
235+ }
185236}
0 commit comments