Skip to content

Fix Overlapping Lines Method #326

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

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
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ extension TextViewController {
var selectionIndex = 0
textView.editSelections { textView, selection in
// get lineindex, i.e line-numbers+1
guard let lineIndexes = getHighlightedLines(for: selection.range) else { return }
guard let lineIndexes = getOverlappingLines(for: selection.range) else { return }

adjustIndentation(lineIndexes: lineIndexes, inwards: inwards)

Expand Down Expand Up @@ -129,7 +129,24 @@ extension TextViewController {
return false
}

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

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

return startLineInfo.index...endLineIndex
}

private func adjustIndentation(lineIndexes: ClosedRange<Int>, inwards: Bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,5 +461,31 @@ final class TextViewControllerTests: XCTestCase {
XCTAssertEqual(controller.minimapView.frame.width, MinimapView.maxWidth)
XCTAssertEqual(controller.textViewInsets.right, MinimapView.maxWidth)
}

// MARK: - Get Overlapping Lines

func test_getOverlappingLines() {
controller.setText("A\nB\nC")

// Select the entire first line, shouldn't include the second line
var lines = controller.getOverlappingLines(for: NSRange(location: 0, length: 2))
XCTAssertEqual(0...0, lines)

// Select the first char of the second line
lines = controller.getOverlappingLines(for: NSRange(location: 0, length: 3))
XCTAssertEqual(0...1, lines)

// Select the newline in the first line, and part of the second line
lines = controller.getOverlappingLines(for: NSRange(location: 1, length: 2))
XCTAssertEqual(0...1, lines)

// Select until the end of the document
lines = controller.getOverlappingLines(for: NSRange(location: 3, length: 2))
XCTAssertEqual(1...2, lines)

// Select just the last line of the document
lines = controller.getOverlappingLines(for: NSRange(location: 4, length: 1))
XCTAssertEqual(2...2, lines)
}
}
// swiftlint:enable all