From 50bad32373e2446a3040794b6bb9bc3c42e50e39 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 20 May 2026 11:04:52 +0000 Subject: [PATCH] fix(macOS): recompute find match ranges before Replace replaceCurrentMatch used lastSearchHighlightRanges from the last applySearchHighlights pass. Any edit while the find bar stays open left those UTF-16 ranges stale, so Replace could corrupt the note or hit an invalid storage range. Re-scan the live string (same rules as the find UI) before replacing, and align nextMatchIndex with the needle-based scan used for highlighting. Co-authored-by: Danny Peck --- macOS/SynapseNotes/EditorView.swift | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/macOS/SynapseNotes/EditorView.swift b/macOS/SynapseNotes/EditorView.swift index 7617eec..4ed4f4f 100644 --- a/macOS/SynapseNotes/EditorView.swift +++ b/macOS/SynapseNotes/EditorView.swift @@ -2718,9 +2718,23 @@ class LinkAwareTextView: NSTextView { } private func replaceCurrentMatch(query: String, focusIndex: Int, replacement: String, advanceAfter: Bool) { - guard !query.isEmpty, - lastSearchHighlightRanges.indices.contains(focusIndex) else { return } - let range = lastSearchHighlightRanges[focusIndex] + guard !query.isEmpty, let storage = textStorage else { return } + // Recompute match ranges from live text. Cached `lastSearchHighlightRanges` + // becomes stale after any edit while the find bar stays open; using it would + // replace the wrong substring or pass an out-of-bounds range to the storage. + let content = storage.string + let needle = query.lowercased() + var matches: [NSRange] = [] + var searchStart = content.startIndex + while searchStart < content.endIndex, + let r = content.range(of: needle, options: .caseInsensitive, range: searchStart.. 2000 { break } + } + guard matches.indices.contains(focusIndex) else { return } + let range = matches[focusIndex] + guard NSMaxRange(range) <= storage.length else { return } guard shouldChangeText(in: range, replacementString: replacement) else { return } textStorage?.replaceCharacters(in: range, with: replacement) didChangeText() @@ -2773,10 +2787,11 @@ class LinkAwareTextView: NSTextView { private func nextMatchIndex(forQuery query: String, after location: Int) -> Int { guard let storage = textStorage else { return 0 } let content = storage.string + let needle = query.lowercased() var matches: [NSRange] = [] var searchStart = content.startIndex while searchStart < content.endIndex, - let r = content.range(of: query, options: .caseInsensitive, range: searchStart.. 2000 { break }