diff --git a/Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Public.swift b/Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Public.swift index ef61dd6ed..e05c01c20 100644 --- a/Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Public.swift +++ b/Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Public.swift @@ -166,7 +166,7 @@ extension TextLayoutManager { /// - Parameter offset: The offset to create the rect for. /// - Returns: The found rect for the given offset. public func rectForOffset(_ offset: Int) -> CGRect? { - guard offset != lineStorage.length else { + guard offset < lineStorage.length else { return rectForEndOffset() } guard let linePosition = determineVisiblePosition(for: lineStorage.getLine(atOffset: offset))?.position else { diff --git a/Sources/CodeEditTextView/TextView/TextView+Mouse.swift b/Sources/CodeEditTextView/TextView/TextView+Mouse.swift index 7ab49d799..0609665f0 100644 --- a/Sources/CodeEditTextView/TextView/TextView+Mouse.swift +++ b/Sources/CodeEditTextView/TextView/TextView+Mouse.swift @@ -12,13 +12,13 @@ extension TextView { // Set cursor guard isSelectable, event.type == .leftMouseDown, - let offset = layoutManager.textOffsetAtPoint(self.convert(event.locationInWindow, from: nil)), - let content = layoutManager.contentRun(at: offset) else { + let offset = layoutManager.textOffsetAtPoint(self.convert(event.locationInWindow, from: nil)) else { super.mouseDown(with: event) return } - if case let .attachment(attachment) = content.data, event.clickCount < 3 { + if let content = layoutManager.contentRun(at: offset), + case let .attachment(attachment) = content.data, event.clickCount < 3 { handleAttachmentClick(event: event, offset: offset, attachment: attachment) return } diff --git a/Tests/CodeEditTextViewTests/LayoutManager/TextLayoutManagerTests.swift b/Tests/CodeEditTextViewTests/LayoutManager/TextLayoutManagerTests.swift index 7306e62dd..3baab6c3a 100644 --- a/Tests/CodeEditTextViewTests/LayoutManager/TextLayoutManagerTests.swift +++ b/Tests/CodeEditTextViewTests/LayoutManager/TextLayoutManagerTests.swift @@ -228,4 +228,26 @@ struct TextLayoutManagerTests { let invalidatedLineIds = layoutManager.layoutLines() #expect(Set(expectedLineIds) == invalidatedLineIds) } + + @Test + func rectForOffsetReturnsValueAfterEndOfDoc() throws { + layoutManager.layoutLines(in: NSRect(x: 0, y: 0, width: 1000, height: 1000)) + + for idx in 0..<10 { + // This should return something even after the end of the document. + #expect(layoutManager.rectForOffset(idx) != nil, "Failed to find rect for offset: \(idx)") + } + } + + @Test + func textOffsetForPointReturnsValuesEverywhere() throws { + layoutManager.layoutLines(in: NSRect(x: 0, y: 0, width: 1000, height: 1000)) + + // textOffsetAtPoint is valid *everywhere*. It should always return something. + for xPos in 0..<1000 { + for yPos in 0..<1000 { + #expect(layoutManager.textOffsetAtPoint(CGPoint(x: xPos, y: yPos)) != nil) + } + } + } } diff --git a/Tests/CodeEditTextViewTests/TextSelectionManagerTests.swift b/Tests/CodeEditTextViewTests/TextSelectionManagerTests.swift index 38ea3983d..ecfa6ab84 100644 --- a/Tests/CodeEditTextViewTests/TextSelectionManagerTests.swift +++ b/Tests/CodeEditTextViewTests/TextSelectionManagerTests.swift @@ -217,4 +217,10 @@ final class TextSelectionManagerTests: XCTestCase { ) } } + + func test_selectionEndOfDocumentHasXPos() { + let selectionManager = selectionManager("1\n2\n3\n") + selectionManager.setSelectedRange(NSRange(location: 6, length: 0)) // Beyond text.length, end of doc + XCTAssertNotNil(selectionManager.textSelections.first?.suggestedXPos) + } }