Skip to content

Commit 2619cb9

Browse files
Shift-Click to Extend Selection (#45)
1 parent eb1d382 commit 2619cb9

15 files changed

+181
-93
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// NSRange.swift
3+
// CodeEditTextView
4+
//
5+
// Created by Khan Winter on 8/20/24.
6+
//
7+
8+
import Foundation
9+
10+
extension NSRange {
11+
@inline(__always)
12+
init(start: Int, end: Int) {
13+
self.init(location: start, length: end - start)
14+
}
15+
}

Sources/CodeEditTextView/Extensions/NSTextStorage+getLine.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extension NSString {
1414
var contentsEnd: Int = NSNotFound
1515
self.getLineStart(nil, end: &end, contentsEnd: &contentsEnd, for: range)
1616
if end != NSNotFound && contentsEnd != NSNotFound && end != contentsEnd {
17-
return NSRange(location: contentsEnd, length: end - contentsEnd)
17+
return NSRange(start: contentsEnd, end: end)
1818
} else {
1919
return nil
2020
}

Sources/CodeEditTextView/MarkedTextManager/MarkedTextManager.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,14 @@ class MarkedTextManager {
4949
/// - textSelections: The current text selections.
5050
func updateMarkedRanges(insertLength: Int, textSelections: [NSRange]) {
5151
var cumulativeExistingDiff = 0
52-
let lengthDiff = insertLength
5352
var newRanges = [NSRange]()
5453
let ranges: [NSRange] = if markedRanges.isEmpty {
5554
textSelections.sorted(by: { $0.location < $1.location })
5655
} else {
5756
markedRanges.sorted(by: { $0.location < $1.location })
5857
}
5958

60-
for (idx, range) in ranges.enumerated() {
59+
for range in ranges {
6160
newRanges.append(NSRange(location: range.location + cumulativeExistingDiff, length: insertLength))
6261
cumulativeExistingDiff += insertLength - range.length
6362
}

Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Edits.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ extension TextLayoutManager: NSTextStorageDelegate {
4343
if !string.isEmpty {
4444
var index = 0
4545
while let nextLine = (string as NSString).getNextLine(startingAt: index) {
46-
let lineRange = NSRange(location: index, length: nextLine.max - index)
46+
let lineRange = NSRange(start: index, end: nextLine.max)
4747
applyLineInsert((string as NSString).substring(with: lineRange) as NSString, at: range.location + index)
4848
index = nextLine.max
4949
}

Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Public.swift

+1-4
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,7 @@ extension TextLayoutManager {
188188
let originalHeight = lineStorage.height
189189

190190
for linePosition in lineStorage.linesInRange(
191-
NSRange(
192-
location: startingLinePosition.range.location,
193-
length: linePosition.range.max - startingLinePosition.range.location
194-
)
191+
NSRange(start: startingLinePosition.range.location, end: linePosition.range.max)
195192
) {
196193
let height = ensureLayoutFor(position: linePosition)
197194
if height != linePosition.height {

Sources/CodeEditTextView/TextLine/Typesetter.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ final class Typesetter {
7979
constrainingWidth: maxWidth
8080
)
8181
let lineFragment = typesetLine(
82-
range: NSRange(location: startIndex, length: lineBreak - startIndex),
82+
range: NSRange(start: startIndex, end: lineBreak),
8383
lineHeightMultiplier: lineHeightMultiplier
8484
)
8585
lines.append(.init(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// Destination.swift
3+
// CodeEditTextView
4+
//
5+
// Created by Khan Winter on 8/20/24.
6+
//
7+
8+
public extension TextSelectionManager {
9+
enum Destination {
10+
case character
11+
case word
12+
case line
13+
case visualLine
14+
case page
15+
case document
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// Direction.swift
3+
// CodeEditTextView
4+
//
5+
// Created by Khan Winter on 8/20/24.
6+
//
7+
8+
public extension TextSelectionManager {
9+
enum Direction {
10+
case up
11+
case down
12+
case forward
13+
case backward
14+
}
15+
}

Sources/CodeEditTextView/TextSelectionManager/SelectionManipulation/SelectionManipulation+Horizontal.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ package extension TextSelectionManager {
4242
return extendSelectionVisualLine(string: string, from: offset, delta: delta)
4343
case .document:
4444
if delta > 0 {
45-
return NSRange(location: offset, length: string.length - offset)
45+
return NSRange(start: offset, end: string.length)
4646
} else {
4747
return NSRange(location: 0, length: offset)
4848
}
@@ -194,8 +194,8 @@ package extension TextSelectionManager {
194194
delta: Int
195195
) -> NSRange {
196196
var foundRange = NSRange(
197-
location: min(lineBound, offset),
198-
length: max(lineBound, offset) - min(lineBound, offset)
197+
start: min(lineBound, offset),
198+
end: max(lineBound, offset)
199199
)
200200
let originalFoundRange = foundRange
201201

Sources/CodeEditTextView/TextSelectionManager/SelectionManipulation/SelectionManipulation+Vertical.swift

+5-7
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ package extension TextSelectionManager {
2828
if up && layoutManager?.lineStorage.first?.range.contains(offset) ?? false {
2929
return NSRange(location: 0, length: offset)
3030
} else if !up && layoutManager?.lineStorage.last?.range.contains(offset) ?? false {
31-
return NSRange(location: offset, length: (textStorage?.length ?? 0) - offset)
31+
return NSRange(start: offset, end: (textStorage?.length ?? offset))
3232
}
3333

3434
switch destination {
@@ -42,7 +42,7 @@ package extension TextSelectionManager {
4242
if up {
4343
return NSRange(location: 0, length: offset)
4444
} else {
45-
return NSRange(location: offset, length: (textStorage?.length ?? 0) - offset)
45+
return NSRange(start: offset, end: (textStorage?.length ?? offset))
4646
}
4747
}
4848
}
@@ -107,10 +107,8 @@ package extension TextSelectionManager {
107107
return NSRange(location: offset, length: 0)
108108
}
109109
return NSRange(
110-
location: up ? nextLine.range.location : offset,
111-
length: up
112-
? offset - nextLine.range.location
113-
: nextLine.range.max - offset - (layoutManager?.detectedLineEnding.length ?? 0)
110+
start: up ? nextLine.range.location : offset,
111+
end: up ? offset : nextLine.range.max - (layoutManager?.detectedLineEnding.length ?? 0)
114112
)
115113
}
116114
}
@@ -142,7 +140,7 @@ package extension TextSelectionManager {
142140
}
143141

144142
if delta > 0 {
145-
return NSRange(location: nextPageOffset, length: offset - nextPageOffset)
143+
return NSRange(start: nextPageOffset, end: offset)
146144
} else {
147145
return NSRange(location: offset, length: nextPageOffset - offset)
148146
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// TextSelection.swift
3+
// CodeEditTextView
4+
//
5+
// Created by Khan Winter on 8/20/24.
6+
//
7+
8+
import Foundation
9+
import AppKit
10+
11+
public extension TextSelectionManager {
12+
class TextSelection: Hashable, Equatable {
13+
public var range: NSRange
14+
weak var view: NSView?
15+
var boundingRect: CGRect = .zero
16+
var suggestedXPos: CGFloat?
17+
/// The position this selection should 'rotate' around when modifying selections.
18+
var pivot: Int?
19+
20+
init(range: NSRange, view: CursorView? = nil) {
21+
self.range = range
22+
self.view = view
23+
}
24+
25+
var isCursor: Bool {
26+
range.length == 0
27+
}
28+
29+
public func hash(into hasher: inout Hasher) {
30+
hasher.combine(range)
31+
}
32+
33+
public static func == (lhs: TextSelection, rhs: TextSelection) -> Bool {
34+
lhs.range == rhs.range
35+
}
36+
}
37+
}
38+
39+
private extension TextSelectionManager.TextSelection {
40+
func didInsertText(length: Int, retainLength: Bool = false) {
41+
if !retainLength {
42+
range.length = 0
43+
}
44+
range.location += length
45+
}
46+
}

Sources/CodeEditTextView/TextSelectionManager/TextSelectionManager.swift

+6-55
Original file line numberDiff line numberDiff line change
@@ -19,50 +19,6 @@ public protocol TextSelectionManagerDelegate: AnyObject {
1919
/// Draws selections using a draw method similar to the `TextLayoutManager` class, and adds cursor views when
2020
/// appropriate.
2121
public class TextSelectionManager: NSObject {
22-
// MARK: - TextSelection
23-
24-
public class TextSelection: Hashable, Equatable {
25-
public var range: NSRange
26-
weak var view: NSView?
27-
var boundingRect: CGRect = .zero
28-
var suggestedXPos: CGFloat?
29-
/// The position this selection should 'rotate' around when modifying selections.
30-
var pivot: Int?
31-
32-
init(range: NSRange, view: CursorView? = nil) {
33-
self.range = range
34-
self.view = view
35-
}
36-
37-
var isCursor: Bool {
38-
range.length == 0
39-
}
40-
41-
public func hash(into hasher: inout Hasher) {
42-
hasher.combine(range)
43-
}
44-
45-
public static func == (lhs: TextSelection, rhs: TextSelection) -> Bool {
46-
lhs.range == rhs.range
47-
}
48-
}
49-
50-
public enum Destination {
51-
case character
52-
case word
53-
case line
54-
case visualLine
55-
case page
56-
case document
57-
}
58-
59-
public enum Direction {
60-
case up
61-
case down
62-
case forward
63-
case backward
64-
}
65-
6622
// MARK: - Properties
6723

6824
// swiftlint:disable:next line_length
@@ -108,6 +64,8 @@ public class TextSelectionManager: NSObject {
10864

10965
// MARK: - Selected Ranges
11066

67+
/// Set the selected ranges to a single range. Overrides any existing selections.
68+
/// - Parameter range: The range to set.
11169
public func setSelectedRange(_ range: NSRange) {
11270
textSelections.forEach { $0.view?.removeFromSuperview() }
11371
let selection = TextSelection(range: range)
@@ -119,6 +77,8 @@ public class TextSelectionManager: NSObject {
11977
}
12078
}
12179

80+
/// Set the selected ranges to new ranges. Overrides any existing selections.
81+
/// - Parameter range: The selected ranges to set.
12282
public func setSelectedRanges(_ ranges: [NSRange]) {
12383
textSelections.forEach { $0.view?.removeFromSuperview() }
12484
// Remove duplicates, invalid ranges, update suggested X position.
@@ -138,6 +98,8 @@ public class TextSelectionManager: NSObject {
13898
}
13999
}
140100

101+
/// Append a new selected range to the existing ones.
102+
/// - Parameter range: The new range to add.
141103
public func addSelectedRange(_ range: NSRange) {
142104
let newTextSelection = TextSelection(range: range)
143105
var didHandle = false
@@ -336,14 +298,3 @@ public class TextSelectionManager: NSObject {
336298
context.restoreGState()
337299
}
338300
}
339-
340-
// MARK: - Private TextSelection
341-
342-
private extension TextSelectionManager.TextSelection {
343-
func didInsertText(length: Int, retainLength: Bool = false) {
344-
if !retainLength {
345-
range.length = 0
346-
}
347-
range.location += length
348-
}
349-
}

0 commit comments

Comments
 (0)