Skip to content

Commit

Permalink
Shift-Click to Extend Selection (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecoolwinter authored Aug 21, 2024
1 parent eb1d382 commit 2619cb9
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 93 deletions.
15 changes: 15 additions & 0 deletions Sources/CodeEditTextView/Extensions/NSRange+/NSRange+init.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// NSRange.swift
// CodeEditTextView
//
// Created by Khan Winter on 8/20/24.
//

import Foundation

extension NSRange {
@inline(__always)
init(start: Int, end: Int) {
self.init(location: start, length: end - start)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension NSString {
var contentsEnd: Int = NSNotFound
self.getLineStart(nil, end: &end, contentsEnd: &contentsEnd, for: range)
if end != NSNotFound && contentsEnd != NSNotFound && end != contentsEnd {
return NSRange(location: contentsEnd, length: end - contentsEnd)
return NSRange(start: contentsEnd, end: end)
} else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@ class MarkedTextManager {
/// - textSelections: The current text selections.
func updateMarkedRanges(insertLength: Int, textSelections: [NSRange]) {
var cumulativeExistingDiff = 0
let lengthDiff = insertLength
var newRanges = [NSRange]()
let ranges: [NSRange] = if markedRanges.isEmpty {
textSelections.sorted(by: { $0.location < $1.location })
} else {
markedRanges.sorted(by: { $0.location < $1.location })
}

for (idx, range) in ranges.enumerated() {
for range in ranges {
newRanges.append(NSRange(location: range.location + cumulativeExistingDiff, length: insertLength))
cumulativeExistingDiff += insertLength - range.length
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extension TextLayoutManager: NSTextStorageDelegate {
if !string.isEmpty {
var index = 0
while let nextLine = (string as NSString).getNextLine(startingAt: index) {
let lineRange = NSRange(location: index, length: nextLine.max - index)
let lineRange = NSRange(start: index, end: nextLine.max)
applyLineInsert((string as NSString).substring(with: lineRange) as NSString, at: range.location + index)
index = nextLine.max
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,7 @@ extension TextLayoutManager {
let originalHeight = lineStorage.height

for linePosition in lineStorage.linesInRange(
NSRange(
location: startingLinePosition.range.location,
length: linePosition.range.max - startingLinePosition.range.location
)
NSRange(start: startingLinePosition.range.location, end: linePosition.range.max)
) {
let height = ensureLayoutFor(position: linePosition)
if height != linePosition.height {
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodeEditTextView/TextLine/Typesetter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ final class Typesetter {
constrainingWidth: maxWidth
)
let lineFragment = typesetLine(
range: NSRange(location: startIndex, length: lineBreak - startIndex),
range: NSRange(start: startIndex, end: lineBreak),
lineHeightMultiplier: lineHeightMultiplier
)
lines.append(.init(
Expand Down
17 changes: 17 additions & 0 deletions Sources/CodeEditTextView/TextSelectionManager/Destination.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Destination.swift
// CodeEditTextView
//
// Created by Khan Winter on 8/20/24.
//

public extension TextSelectionManager {
enum Destination {
case character
case word
case line
case visualLine
case page
case document
}
}
15 changes: 15 additions & 0 deletions Sources/CodeEditTextView/TextSelectionManager/Direction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Direction.swift
// CodeEditTextView
//
// Created by Khan Winter on 8/20/24.
//

public extension TextSelectionManager {
enum Direction {
case up
case down
case forward
case backward
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ package extension TextSelectionManager {
return extendSelectionVisualLine(string: string, from: offset, delta: delta)
case .document:
if delta > 0 {
return NSRange(location: offset, length: string.length - offset)
return NSRange(start: offset, end: string.length)
} else {
return NSRange(location: 0, length: offset)
}
Expand Down Expand Up @@ -194,8 +194,8 @@ package extension TextSelectionManager {
delta: Int
) -> NSRange {
var foundRange = NSRange(
location: min(lineBound, offset),
length: max(lineBound, offset) - min(lineBound, offset)
start: min(lineBound, offset),
end: max(lineBound, offset)
)
let originalFoundRange = foundRange

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ package extension TextSelectionManager {
if up && layoutManager?.lineStorage.first?.range.contains(offset) ?? false {
return NSRange(location: 0, length: offset)
} else if !up && layoutManager?.lineStorage.last?.range.contains(offset) ?? false {
return NSRange(location: offset, length: (textStorage?.length ?? 0) - offset)
return NSRange(start: offset, end: (textStorage?.length ?? offset))
}

switch destination {
Expand All @@ -42,7 +42,7 @@ package extension TextSelectionManager {
if up {
return NSRange(location: 0, length: offset)
} else {
return NSRange(location: offset, length: (textStorage?.length ?? 0) - offset)
return NSRange(start: offset, end: (textStorage?.length ?? offset))
}
}
}
Expand Down Expand Up @@ -107,10 +107,8 @@ package extension TextSelectionManager {
return NSRange(location: offset, length: 0)
}
return NSRange(
location: up ? nextLine.range.location : offset,
length: up
? offset - nextLine.range.location
: nextLine.range.max - offset - (layoutManager?.detectedLineEnding.length ?? 0)
start: up ? nextLine.range.location : offset,
end: up ? offset : nextLine.range.max - (layoutManager?.detectedLineEnding.length ?? 0)
)
}
}
Expand Down Expand Up @@ -142,7 +140,7 @@ package extension TextSelectionManager {
}

if delta > 0 {
return NSRange(location: nextPageOffset, length: offset - nextPageOffset)
return NSRange(start: nextPageOffset, end: offset)
} else {
return NSRange(location: offset, length: nextPageOffset - offset)
}
Expand Down
46 changes: 46 additions & 0 deletions Sources/CodeEditTextView/TextSelectionManager/TextSelection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// TextSelection.swift
// CodeEditTextView
//
// Created by Khan Winter on 8/20/24.
//

import Foundation
import AppKit

public extension TextSelectionManager {
class TextSelection: Hashable, Equatable {
public var range: NSRange
weak var view: NSView?
var boundingRect: CGRect = .zero
var suggestedXPos: CGFloat?
/// The position this selection should 'rotate' around when modifying selections.
var pivot: Int?

init(range: NSRange, view: CursorView? = nil) {
self.range = range
self.view = view
}

var isCursor: Bool {
range.length == 0
}

public func hash(into hasher: inout Hasher) {
hasher.combine(range)
}

public static func == (lhs: TextSelection, rhs: TextSelection) -> Bool {
lhs.range == rhs.range
}
}
}

private extension TextSelectionManager.TextSelection {
func didInsertText(length: Int, retainLength: Bool = false) {
if !retainLength {
range.length = 0
}
range.location += length
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,50 +19,6 @@ public protocol TextSelectionManagerDelegate: AnyObject {
/// Draws selections using a draw method similar to the `TextLayoutManager` class, and adds cursor views when
/// appropriate.
public class TextSelectionManager: NSObject {
// MARK: - TextSelection

public class TextSelection: Hashable, Equatable {
public var range: NSRange
weak var view: NSView?
var boundingRect: CGRect = .zero
var suggestedXPos: CGFloat?
/// The position this selection should 'rotate' around when modifying selections.
var pivot: Int?

init(range: NSRange, view: CursorView? = nil) {
self.range = range
self.view = view
}

var isCursor: Bool {
range.length == 0
}

public func hash(into hasher: inout Hasher) {
hasher.combine(range)
}

public static func == (lhs: TextSelection, rhs: TextSelection) -> Bool {
lhs.range == rhs.range
}
}

public enum Destination {
case character
case word
case line
case visualLine
case page
case document
}

public enum Direction {
case up
case down
case forward
case backward
}

// MARK: - Properties

// swiftlint:disable:next line_length
Expand Down Expand Up @@ -108,6 +64,8 @@ public class TextSelectionManager: NSObject {

// MARK: - Selected Ranges

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

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

/// Append a new selected range to the existing ones.
/// - Parameter range: The new range to add.
public func addSelectedRange(_ range: NSRange) {
let newTextSelection = TextSelection(range: range)
var didHandle = false
Expand Down Expand Up @@ -336,14 +298,3 @@ public class TextSelectionManager: NSObject {
context.restoreGState()
}
}

// MARK: - Private TextSelection

private extension TextSelectionManager.TextSelection {
func didInsertText(length: Int, retainLength: Bool = false) {
if !retainLength {
range.length = 0
}
range.location += length
}
}
Loading

0 comments on commit 2619cb9

Please sign in to comment.