From d8f9a215dd3f240bab879788b1e518f3c4339cc3 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:52:25 -0500 Subject: [PATCH 1/2] Fix End of Doc Bugs --- .../TextLayoutManager/TextLayoutManager+Public.swift | 2 +- Sources/CodeEditTextView/TextView/TextView+Mouse.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Public.swift b/Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Public.swift index ef61dd6e..e05c01c2 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 7ab49d79..0609665f 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 } From 0f8f721f2ccc6c82f0d1f9381221dd20c618e3bb Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:11:24 -0500 Subject: [PATCH 2/2] Add Test Cases --- .../TextLayoutManagerTests.swift | 22 +++++++++++++++++++ .../TextSelectionManagerTests.swift | 6 +++++ 2 files changed, 28 insertions(+) diff --git a/Tests/CodeEditTextViewTests/LayoutManager/TextLayoutManagerTests.swift b/Tests/CodeEditTextViewTests/LayoutManager/TextLayoutManagerTests.swift index 7306e62d..3baab6c3 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 38ea3983..ecfa6ab8 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) + } }