// // KMNSearchHanddler.swift // PDF Reader Pro // // Created by User-Tangchao on 2024/12/1. // import Cocoa class KMNSearchHanddler: NSObject { weak var pdfView: CPDFView? var type: KMNBotaSearchType = .search var searchKey: String? var replaceKey: String? var showIdx = 0 var resultCount = 0 var searchSectionResults: [KMBotaSearchSectionModel] = [] var searchResults: [KMSearchMode] = [] // 创建一个串行队列 private let searchQueue = DispatchQueue(label: "com.pdfkit.searchQueue") func hideNotes() -> Bool { let listView = pdfView as? CPDFListView return listView?.hideNotes ?? false } func allowsNotes() -> Bool { let listView = pdfView as? CPDFListView return listView?.allowsNotes() ?? false } func pdfDocument() -> CPDFDocument? { return pdfView?.document } func scaleFactor() -> CGFloat? { return pdfView?.scaleFactor } func isEditing() -> Bool { return pdfView?.isEditing() ?? false } private var searchTimer: Timer? private let debounceInterval: TimeInterval = 1.0 // 0.3秒的延迟 func search(keyword: String, isCase: Bool, isWholeWord: Bool, isEdit: Bool = false, callback: @escaping (([KMBotaSearchSectionModel]) -> Void)) { searchTimer?.invalidate() searchTimer = Timer.scheduledTimer(withTimeInterval: debounceInterval, repeats: false) { [weak self] _ in self?.searchQueue.sync { // 在这里执行同步任务 print("Executing task synchronously in searchQueue") self?._search(keyword: keyword, isCase: isCase, isWholeWord: isWholeWord, isEdit: isEdit, callback: callback) } } } func _search(keyword: String, isCase: Bool, isWholeWord: Bool, isEdit: Bool = false, callback: @escaping (([KMBotaSearchSectionModel]) -> Void)) { guard let document = self.pdfView?.document else { NSSound.beep() self.clear() callback([]) return } if document.isFinding { document.cancelFindString() } if keyword.isEmpty { self.clear() callback([]) return } let theKeyword = keyword.decomposedStringWithCompatibilityMapping var opt = CPDFSearchOptions(rawValue: 0) if isCase { opt.insert(.caseSensitive) } if isWholeWord { opt.insert(.matchWholeWord) } DispatchQueue.global().async { var result: [[CPDFSelection]] = [] if isEdit { result = document.findEditAllPageString(theKeyword, with: opt) ?? [] } else { result = document.findString(theKeyword, with: opt) ?? [] } self.clear() for sels in result { let sectionM = KMBotaSearchSectionModel() guard let page = sels.first?.page else { continue } sectionM.pageIndex = Int(page.pageIndex()) for sel in sels { let mode : KMSearchMode = KMSearchMode() mode.selection = sel mode.attributedString = KMOCToolClass.getAttributedString(selection: sel, keyword: theKeyword) mode.selectionPageIndex = document.index(for: sel.page) sectionM.items.append(mode) sel.setColor(KMNColorTools.colorWarning_base().withAlphaComponent(0.5)) self.searchResults.append(mode) } self.searchSectionResults.append(sectionM) } DispatchQueue.main.async { self.searchKey = theKeyword self.showIdx = 0 self.resultCount = self.searchResults.count callback(self.searchSectionResults) } } } func clear() { self.searchResults.removeAll() self.searchSectionResults.removeAll() self.searchKey = "" self.showIdx = 0 self.resultCount = 0 } func startFindEditText(page: CPDFPage, searchString: String, options: CPDFSearchOptions) -> [[CPDFSelection]] { let datas = self.pdfView?.document.startFindEditText(from: page, with: searchString, options: options) ?? [] return datas } func replace(searchS: String, replaceS: String?, sel: CPDFSelection, isEdit: Bool = false, callback: @escaping ((CPDFSelection?)->Void)) { self.pdfView?.document.replace(with: sel, search: searchS, toReplace: replaceS, completionHandler: { newSel in callback(newSel) }) } func showSelection(_ sel: CPDFSelection?) { guard let theSel = sel else { let isEditing = self.pdfView?.isEditing() ?? false if isEditing { self.pdfView?.setHighlightedSelection(nil, animated: true) } else { self.pdfView?.setHighlightedSelections([]) } self.pdfView?.setNeedsDisplayAnnotationViewForVisiblePages() return } guard let document = self.pdfView?.document else { return } let pageIdx = document.index(for: theSel.page) self.pdfView?.go(toPageIndex: Int(pageIdx), animated: false) self.pdfView?.go(to: theSel.bounds, on: theSel.page) let isEditing = self.pdfView?.isEditing() ?? false if isEditing { self.pdfView?.setHighlightedSelection(theSel, animated: true) } else { self.pdfView?.setHighlightedSelections([theSel]) } self.pdfView?.setNeedsDisplayAnnotationViewForVisiblePages() } func clearData() { let isEditing = self.pdfView?.isEditing() ?? false if isEditing { self.pdfView?.setHighlightedSelection(nil, animated: false) } else { self.pdfView?.setHighlightedSelections([]) } self.pdfView?.setNeedsDisplayAnnotationViewForVisiblePages() } } extension KMNSearchHanddler { func next() -> Int { var index = self.showIdx index = index + 1 index = min(index, self.searchResults.count - 1) self.showIdx = index return index } func previous() -> Int { var index = self.showIdx index = index - 1 index = max(index, 0) self.showIdx = index return index } }