Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shift-Click to Extend Selection #45

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading