From ef72a20beebe7a99c1dc0cc905b5dd42c9f93be2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 18 May 2026 11:06:47 +0000 Subject: [PATCH] fix(macOS): avoid main-thread memory spike on Replace All Replace All previously collected every match NSRange before mutating the text storage. Notes with huge match counts could allocate unbounded memory and freeze the UI. Use NSMutableString.replaceOccurrences for the same non-overlapping case-insensitive semantics without materializing all ranges. Co-authored-by: Danny Peck --- macOS/SynapseNotes/EditorView.swift | 33 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/macOS/SynapseNotes/EditorView.swift b/macOS/SynapseNotes/EditorView.swift index 7617eec..c379f3a 100644 --- a/macOS/SynapseNotes/EditorView.swift +++ b/macOS/SynapseNotes/EditorView.swift @@ -2740,29 +2740,32 @@ class LinkAwareTextView: NSTextView { private func replaceAllMatches(query: String, replacement: String) { guard !query.isEmpty, let storage = textStorage else { return } let content = storage.string - var matches: [NSRange] = [] - var searchStart = content.startIndex - while searchStart < content.endIndex, - let r = content.range(of: query, options: .caseInsensitive, range: searchStart.. 0 else { + applySearchHighlights(query: query, focusIndex: 0) + return } + let fullRange = NSRange(location: 0, length: storage.length) - guard shouldChangeText(in: fullRange, replacementString: mutable as String) else { return } + guard shouldChangeText(in: fullRange, replacementString: resultString) else { return } // Highlight ranges are about to be invalidated by the full-document replace. // Drop them now so the debounced restyle (which re-applies highlights via // reapplySearchHighlights) can't read out-of-bounds NSRanges and crash. lastSearchHighlightRanges = [] lastSearchFocusIndex = -1 - storage.replaceCharacters(in: fullRange, with: mutable as String) + storage.replaceCharacters(in: fullRange, with: resultString) didChangeText() applySearchHighlights(query: query, focusIndex: 0)