diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/AppDelegate.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/AppDelegate.swift index dcb983423..105b669e2 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/AppDelegate.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/AppDelegate.swift @@ -69,6 +69,8 @@ public extension AppDelegate { SpeechSputnik.shared.refreshStatus() // 根據現狀條件決定是否初期化語音引擎。 + CandidateTextService.enableFinalSanityCheck() + // 一旦發現與使用者半衰模組的觀察行為有關的崩潰標記被開啟: // 如果有開啟 Debug 模式的話,就將既有的半衰記憶資料檔案更名+打上當時的時間戳。 // 如果沒有開啟 Debug 模式的話,則將半衰記憶資料直接清空。 diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/CandidateTextService_SelectorImpl.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/CandidateTextService_SelectorImpl.swift index 4b420bbeb..24bd3cc46 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/CandidateTextService_SelectorImpl.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/CandidateTextService_SelectorImpl.swift @@ -12,6 +12,28 @@ import Shared import Tekkon public extension CandidateTextService { + // MARK: - Final Sanity Check Implementation. + + static func enableFinalSanityCheck() { + finalSanityCheck = finalSanityCheckImplemented + } + + private static func finalSanityCheckImplemented(_ target: CandidateTextService) -> Bool { + switch target.value { + case .url: return true + case let .selector(strSelector): + guard target.candidateText != "%s" else { return true } // 防止誤傷到編輯器。 + switch strSelector { + case "copyUnicodeMetadata:": return true + case _ where strSelector.hasPrefix("copyRuby"), + _ where strSelector.hasPrefix("copyBraille"), + _ where strSelector.hasPrefix("copyInline"): + return !target.reading.joined().isEmpty // 以便應對 [""] 的情況。 + default: return true + } + } + } + // MARK: - Selector Methods, CandidatePairServicable, and the Coordinator. var responseFromSelector: String? { diff --git a/Packages/vChewing_MainAssembly/Tests/MainAssemblyTests/CandidateServiceCoordinatorTests.swift b/Packages/vChewing_MainAssembly/Tests/MainAssemblyTests/CandidateServiceCoordinatorTests.swift index cf639e138..1e7a64c32 100644 --- a/Packages/vChewing_MainAssembly/Tests/MainAssemblyTests/CandidateServiceCoordinatorTests.swift +++ b/Packages/vChewing_MainAssembly/Tests/MainAssemblyTests/CandidateServiceCoordinatorTests.swift @@ -24,6 +24,21 @@ class CandidateServiceCoordinatorTests: XCTestCase { #"Braille 2018: %s"# + "\t" + #"@SEL:copyBraille2018:"#, ] + func testSelector_FinalSanityCheck() throws { + var stacked = Self.testDataMap.parseIntoCandidateTextServiceStack( + candidate: "胡桃", reading: [] // 故意使用空 Reading + ) + let count1 = stacked.count + print("Current Count before Sanity Check ON: \(stacked.count)") + CandidateTextService.enableFinalSanityCheck() + stacked = Self.testDataMap.parseIntoCandidateTextServiceStack( + candidate: "胡桃", reading: [] // 故意使用空 Reading + ) + let count2 = stacked.count + print("Current Count after Sanity Check ON: \(stacked.count)") + XCTAssertGreaterThan(count1, count2) + } + func testSelector_UnicodeMetadata() throws { let stacked = Self.testDataMap.parseIntoCandidateTextServiceStack( candidate: "胡桃", reading: ["ㄏㄨˊ", "ㄊㄠˊ"] diff --git a/Packages/vChewing_Shared/Sources/Shared/CandidateTextService.swift b/Packages/vChewing_Shared/Sources/Shared/CandidateTextService.swift index b11f693a8..5f93b1fcb 100644 --- a/Packages/vChewing_Shared/Sources/Shared/CandidateTextService.swift +++ b/Packages/vChewing_Shared/Sources/Shared/CandidateTextService.swift @@ -26,6 +26,8 @@ public struct CandidateTextService: Codable { public let value: ServiceValue public let candidateText: String + public static var finalSanityCheck: ((CandidateTextService) -> Bool)? + public init?(key: String, definedValue: String, param: String = #"%s"#, reading: [String] = []) { guard !key.isEmpty, !definedValue.isEmpty, definedValue.first != "#" else { return nil } candidateText = param @@ -62,6 +64,8 @@ public struct CandidateTextService: Codable { } guard let finalServiceValue = finalServiceValue else { return nil } value = finalServiceValue + let finalSanityCheckResult = Self.finalSanityCheck?(self) ?? true + if !finalSanityCheckResult { return nil } } }