Skip to content

Commit 7830486

Browse files
Fix Overlapping Lines Method (#326)
1 parent 9339c42 commit 7830486

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

Sources/CodeEditSourceEditor/Controller/TextViewController+IndentLines.swift

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ extension TextViewController {
6767
var selectionIndex = 0
6868
textView.editSelections { textView, selection in
6969
// get lineindex, i.e line-numbers+1
70-
guard let lineIndexes = getHighlightedLines(for: selection.range) else { return }
70+
guard let lineIndexes = getOverlappingLines(for: selection.range) else { return }
7171

7272
adjustIndentation(lineIndexes: lineIndexes, inwards: inwards)
7373

@@ -129,7 +129,24 @@ extension TextViewController {
129129
return false
130130
}
131131

132-
private func getHighlightedLines(for range: NSRange) -> ClosedRange<Int>? {
132+
/// Find the range of lines overlapping a text range.
133+
///
134+
/// Use this method to determine what lines to apply a text transformation on using a text selection. For instance,
135+
/// when indenting a selected line.
136+
///
137+
/// Does not determine the *visible* lines, which is a very slight change from most
138+
/// ``CodeEditTextView/TextLayoutManager`` APIs.
139+
/// Given the text:
140+
/// ```
141+
/// A
142+
/// B
143+
/// ```
144+
/// This method will return lines `0...0` for the text range `0..<2`. The layout manager might return lines
145+
/// `0...1`, as the text range contains the newline, which appears *visually* in line index `1`.
146+
///
147+
/// - Parameter range: The text range in the document to find contained lines for.
148+
/// - Returns: A closed range of line indexes (0-indexed) where each line is overlapping the given text range.
149+
func getOverlappingLines(for range: NSRange) -> ClosedRange<Int>? {
133150
guard let startLineInfo = textView.layoutManager.textLineForOffset(range.lowerBound) else {
134151
return nil
135152
}
@@ -139,7 +156,16 @@ extension TextViewController {
139156
return startLineInfo.index...startLineInfo.index
140157
}
141158

142-
return startLineInfo.index...endLineInfo.index
159+
// If we've selected up to the start of a line (just over the newline character), the layout manager tells us
160+
// we've selected the next line. However, we aren't overlapping the *text line* with that range, so we
161+
// decrement it if it's not the end of the document
162+
var endLineIndex = endLineInfo.index
163+
if endLineInfo.range.lowerBound == range.upperBound
164+
&& endLineInfo.index != textView.layoutManager.lineCount - 1 {
165+
endLineIndex -= 1
166+
}
167+
168+
return startLineInfo.index...endLineIndex
143169
}
144170

145171
private func adjustIndentation(lineIndexes: ClosedRange<Int>, inwards: Bool) {

Tests/CodeEditSourceEditorTests/Controller/TextViewControllerTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,5 +461,31 @@ final class TextViewControllerTests: XCTestCase {
461461
XCTAssertEqual(controller.minimapView.frame.width, MinimapView.maxWidth)
462462
XCTAssertEqual(controller.textViewInsets.right, MinimapView.maxWidth)
463463
}
464+
465+
// MARK: - Get Overlapping Lines
466+
467+
func test_getOverlappingLines() {
468+
controller.setText("A\nB\nC")
469+
470+
// Select the entire first line, shouldn't include the second line
471+
var lines = controller.getOverlappingLines(for: NSRange(location: 0, length: 2))
472+
XCTAssertEqual(0...0, lines)
473+
474+
// Select the first char of the second line
475+
lines = controller.getOverlappingLines(for: NSRange(location: 0, length: 3))
476+
XCTAssertEqual(0...1, lines)
477+
478+
// Select the newline in the first line, and part of the second line
479+
lines = controller.getOverlappingLines(for: NSRange(location: 1, length: 2))
480+
XCTAssertEqual(0...1, lines)
481+
482+
// Select until the end of the document
483+
lines = controller.getOverlappingLines(for: NSRange(location: 3, length: 2))
484+
XCTAssertEqual(1...2, lines)
485+
486+
// Select just the last line of the document
487+
lines = controller.getOverlappingLines(for: NSRange(location: 4, length: 1))
488+
XCTAssertEqual(2...2, lines)
489+
}
464490
}
465491
// swiftlint:enable all

0 commit comments

Comments
 (0)