Skip to content

Commit 4ccab5d

Browse files
committed
Clean Up
1 parent dd109ec commit 4ccab5d

File tree

4 files changed

+61
-38
lines changed

4 files changed

+61
-38
lines changed

Sources/CodeEditTextView/TextSelectionManager/TextSelectionManager.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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()
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// TextView+ColumnSelection.swift
3+
// CodeEditTextView
4+
//
5+
// Created by Khan Winter on 6/19/25.
6+
//
7+
8+
import AppKit
9+
10+
extension TextView {
11+
/// Set the user's selection to a square region in the editor.
12+
///
13+
/// This method will automatically determine a valid region from the provided two points.
14+
/// - Parameters:
15+
/// - pointA: The first point.
16+
/// - pointB: The second point.
17+
public func selectColumns(betweenPointA pointA: CGPoint, pointB: CGPoint) {
18+
let start = CGPoint(x: min(pointA.x, pointB.x), y: min(pointA.y, pointB.y))
19+
let end = CGPoint(x: max(pointA.x, pointB.x), y: max(pointA.y, pointB.y))
20+
21+
// Collect all overlapping text ranges
22+
var selectedRanges: [NSRange] = layoutManager.linesStartingAt(start.y, until: end.y).flatMap { textLine in
23+
// Collect fragment ranges
24+
return textLine.data.lineFragments.compactMap { lineFragment -> NSRange? in
25+
let startOffset = self.layoutManager.textOffsetAtPoint(
26+
start,
27+
fragmentPosition: lineFragment,
28+
linePosition: textLine
29+
)
30+
let endOffset = self.layoutManager.textOffsetAtPoint(
31+
end,
32+
fragmentPosition: lineFragment,
33+
linePosition: textLine
34+
)
35+
guard let startOffset, let endOffset else { return nil }
36+
37+
return NSRange(start: startOffset, end: endOffset)
38+
}
39+
}
40+
41+
// If we have some non-cursor selections, filter out any cursor selections
42+
if selectedRanges.contains(where: { !$0.isEmpty }) {
43+
selectedRanges = selectedRanges.filter({
44+
!$0.isEmpty || (layoutManager.rectForOffset($0.location)?.origin.x.approxEqual(start.x) ?? false)
45+
})
46+
}
47+
48+
selectionManager.setSelectedRanges(selectedRanges)
49+
}
50+
}

Sources/CodeEditTextView/TextView/TextView+KeyDown.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ extension TextView {
5252
super.flagsChanged(with: event)
5353

5454
let modifierFlags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
55-
if modifierFlags.contains(.option) != isOptionPressed {
56-
isOptionPressed = modifierFlags.contains(.option)
55+
let modifierFlagsIsOption = modifierFlags == [.option]
56+
57+
if modifierFlagsIsOption != isOptionPressed {
58+
isOptionPressed = modifierFlagsIsOption
5759
resetCursorRects()
5860
}
5961
}

Sources/CodeEditTextView/TextView/TextView+Mouse.swift

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ extension TextView {
136136
setNeedsDisplay()
137137
}
138138

139+
// MARK: - Mouse Autoscroll
140+
139141
/// Sets up a timer that fires at a predetermined period to autoscroll the text view.
140142
/// Ensure the timer is disabled using ``disableMouseAutoscrollTimer``.
141143
func setUpMouseAutoscrollTimer() {
@@ -155,6 +157,8 @@ extension TextView {
155157
mouseDragTimer = nil
156158
}
157159

160+
// MARK: - Drag Selection
161+
158162
private func dragSelection(startPosition: Int, endPosition: Int, mouseDragAnchor: CGPoint) {
159163
switch cursorSelectionMode {
160164
case .character:
@@ -196,41 +200,6 @@ extension TextView {
196200
private func dragColumnSelection(mouseDragAnchor: CGPoint, event: NSEvent) {
197201
// Drag the selection and select in columns
198202
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)
203+
selectColumns(betweenPointA: eventLocation, pointB: mouseDragAnchor)
235204
}
236205
}

0 commit comments

Comments
 (0)