11//
2- // TextSelectionManager+ SelectionManipulation.swift
2+ // SelectionManipulation+Horizontal .swift
33// CodeEditTextView
44//
5- // Created by Khan Winter on 8/26/23 .
5+ // Created by Khan Winter on 5/11/24 .
66//
77
8- import AppKit
9-
10- public extension TextSelectionManager {
11- // MARK: - Range Of Selection
12-
13- /// Creates a range for a new selection given a starting point, direction, and destination.
14- /// - Parameters:
15- /// - offset: The location to start the selection from.
16- /// - direction: The direction the selection should be created in.
17- /// - destination: Determines how far the selection is.
18- /// - decomposeCharacters: Set to `true` to treat grapheme clusters as individual characters.
19- /// - suggestedXPos: The suggested x position to stick to.
20- /// - Returns: A range of a new selection based on the direction and destination.
21- func rangeOfSelection(
22- from offset: Int ,
23- direction: Direction ,
24- destination: Destination ,
25- decomposeCharacters: Bool = false ,
26- suggestedXPos: CGFloat ? = nil
27- ) -> NSRange {
28- switch direction {
29- case . backward:
30- guard offset > 0 else { return NSRange ( location: offset, length: 0 ) } // Can't go backwards beyond 0
31- return extendSelection (
32- from: offset,
33- destination: destination,
34- delta: - 1 ,
35- decomposeCharacters: decomposeCharacters
36- )
37- case . forward:
38- return extendSelection (
39- from: offset,
40- destination: destination,
41- delta: 1 ,
42- decomposeCharacters: decomposeCharacters
43- )
44- case . up:
45- return extendSelectionVertical (
46- from: offset,
47- destination: destination,
48- up: true ,
49- suggestedXPos: suggestedXPos
50- )
51- case . down:
52- return extendSelectionVertical (
53- from: offset,
54- destination: destination,
55- up: false ,
56- suggestedXPos: suggestedXPos
57- )
58- }
59- }
8+ import Foundation
609
10+ package extension TextSelectionManager {
6111 /// Extends a selection from the given offset determining the length by the destination.
6212 ///
6313 /// Returns a new range that needs to be merged with an existing selection range using `NSRange.formUnion`
@@ -68,7 +18,7 @@ public extension TextSelectionManager {
6818 /// - delta: The direction the selection should be extended. `1` for forwards, `-1` for backwards.
6919 /// - decomposeCharacters: Set to `true` to treat grapheme clusters as individual characters.
7020 /// - Returns: A new range to merge with a selection.
71- private func extendSelection (
21+ func extendSelectionHorizontal (
7222 from offset: Int ,
7323 destination: Destination ,
7424 delta: Int ,
@@ -278,122 +228,4 @@ public extension TextSelectionManager {
278228 }
279229 return foundRange
280230 }
281-
282- // MARK: - Vertical Methods
283-
284- /// Extends a selection from the given offset vertically to the destination.
285- /// - Parameters:
286- /// - offset: The offset to extend from.
287- /// - destination: The destination to extend to.
288- /// - up: Set to true if extending up.
289- /// - suggestedXPos: The suggested x position to stick to.
290- /// - Returns: The range of the extended selection.
291- private func extendSelectionVertical(
292- from offset: Int ,
293- destination: Destination ,
294- up: Bool ,
295- suggestedXPos: CGFloat ?
296- ) -> NSRange {
297- switch destination {
298- case . character:
299- return extendSelectionVerticalCharacter ( from: offset, up: up, suggestedXPos: suggestedXPos)
300- case . word, . line, . visualLine:
301- return extendSelectionVerticalLine ( from: offset, up: up)
302- case . container:
303- return extendSelectionContainer ( from: offset, delta: up ? 1 : - 1 )
304- case . document:
305- if up {
306- return NSRange ( location: 0 , length: offset)
307- } else {
308- return NSRange ( location: offset, length: ( textStorage? . length ?? 0 ) - offset)
309- }
310- }
311- }
312-
313- /// Extends the selection to the nearest character vertically.
314- /// - Parameters:
315- /// - offset: The offset to extend from.
316- /// - up: Set to true if extending up.
317- /// - suggestedXPos: The suggested x position to stick to.
318- /// - Returns: The range of the extended selection.
319- private func extendSelectionVerticalCharacter(
320- from offset: Int ,
321- up: Bool ,
322- suggestedXPos: CGFloat ?
323- ) -> NSRange {
324- guard let point = layoutManager? . rectForOffset ( offset) ? . origin,
325- let newOffset = layoutManager? . textOffsetAtPoint (
326- CGPoint (
327- x: suggestedXPos == nil ? point. x : suggestedXPos!,
328- y: point. y - ( layoutManager? . estimateLineHeight ( ) ?? 2.0 ) / 2 * ( up ? 1 : - 3 )
329- )
330- ) else {
331- return NSRange ( location: offset, length: 0 )
332- }
333-
334- return NSRange (
335- location: up ? newOffset : offset,
336- length: up ? offset - newOffset : newOffset - offset
337- )
338- }
339-
340- /// Extends the selection to the nearest line vertically.
341- ///
342- /// If moving up and the offset is in the middle of the line, it first extends it to the beginning of the line.
343- /// On the second call, it will extend it to the beginning of the previous line. When moving down, the
344- /// same thing will happen in the opposite direction.
345- ///
346- /// - Parameters:
347- /// - offset: The offset to extend from.
348- /// - up: Set to true if extending up.
349- /// - suggestedXPos: The suggested x position to stick to.
350- /// - Returns: The range of the extended selection.
351- private func extendSelectionVerticalLine(
352- from offset: Int ,
353- up: Bool
354- ) -> NSRange {
355- // Important distinction here, when moving up/down on a line and in the middle of the line, we move to the
356- // beginning/end of the *entire* line, not the line fragment.
357- guard let line = layoutManager? . textLineForOffset ( offset) else {
358- return NSRange ( location: offset, length: 0 )
359- }
360- if up && line. range. location != offset {
361- return NSRange ( location: line. range. location, length: offset - line. index)
362- } else if !up && line. range. max - ( layoutManager? . detectedLineEnding. length ?? 0 ) != offset {
363- return NSRange (
364- location: offset,
365- length: line. range. max - offset - ( layoutManager? . detectedLineEnding. length ?? 0 )
366- )
367- } else {
368- let nextQueryIndex = up ? max ( line. range. location - 1 , 0 ) : min ( line. range. max, ( textStorage? . length ?? 0 ) )
369- guard let nextLine = layoutManager? . textLineForOffset ( nextQueryIndex) else {
370- return NSRange ( location: offset, length: 0 )
371- }
372- return NSRange (
373- location: up ? nextLine. range. location : offset,
374- length: up
375- ? offset - nextLine. range. location
376- : nextLine. range. max - offset - ( layoutManager? . detectedLineEnding. length ?? 0 )
377- )
378- }
379- }
380-
381- /// Extends a selection one "container" long.
382- /// - Parameters:
383- /// - offset: The location to start extending the selection from.
384- /// - delta: The direction the selection should be extended. `1` for forwards, `-1` for backwards.
385- /// - Returns: The range of the extended selection.
386- private func extendSelectionContainer( from offset: Int , delta: Int ) -> NSRange {
387- guard let textView, let endOffset = layoutManager? . textOffsetAtPoint (
388- CGPoint (
389- x: delta > 0 ? textView. frame. maxX : textView. frame. minX,
390- y: delta > 0 ? textView. frame. maxY : textView. frame. minY
391- )
392- ) else {
393- return NSRange ( location: offset, length: 0 )
394- }
395- return endOffset > offset
396- ? NSRange ( location: offset, length: endOffset - offset)
397- : NSRange ( location: endOffset, length: offset - endOffset)
398- }
399231}
0 commit comments