From 7577870fb112233a5f3e5c4384608539365a72db Mon Sep 17 00:00:00 2001 From: 1024jp <1024jp@wolfrosch.com> Date: Fri, 28 Jun 2024 12:46:30 +0900 Subject: [PATCH] Extract TextFind to package --- CotEditor.xcodeproj/project.pbxproj | 38 +++------- CotEditor/Sources/FindProgressView.swift | 1 + .../MultipleReplaceListViewController.swift | 1 + .../Sources/MultipleReplaceSettingsView.swift | 3 +- .../MultipleReplaceViewController.swift | 1 + .../Sources/NSTextView+MultipleReplace.swift | 1 + CotEditor/Sources/ReplacementManager.swift | 1 + .../Sources/TextFind.Error+Localization.swift | 65 +++++++++++++++++ CotEditor/Sources/TextFinder.swift | 1 + CotEditor/Sources/TextFinderSettings.swift | 4 +- Packages/EditorKit/Package.swift | 5 ++ .../Sources/StringBasics/Collection.swift | 13 +++- .../Sources/TextEditing/Collection.swift | 18 ----- .../Sources/TextFind}/FindProgress.swift | 22 +++--- .../TextFind}/MultipleReplace+Codable.swift | 10 +-- .../Sources/TextFind}/MultipleReplace.swift | 71 +++++++++++++------ .../Sources/TextFind}/TextFind.swift | 70 +++++------------- .../Tests/TextFindTests}/TextFindTests.swift | 23 +----- Tests/TextFinderTests.swift | 50 +++++++++++++ 19 files changed, 237 insertions(+), 161 deletions(-) create mode 100644 CotEditor/Sources/TextFind.Error+Localization.swift rename {CotEditor/Sources => Packages/EditorKit/Sources/TextFind}/FindProgress.swift (76%) rename {CotEditor/Sources => Packages/EditorKit/Sources/TextFind}/MultipleReplace+Codable.swift (94%) rename {CotEditor/Sources => Packages/EditorKit/Sources/TextFind}/MultipleReplace.swift (70%) rename {CotEditor/Sources => Packages/EditorKit/Sources/TextFind}/TextFind.swift (84%) rename {Tests => Packages/EditorKit/Tests/TextFindTests}/TextFindTests.swift (86%) create mode 100644 Tests/TextFinderTests.swift diff --git a/CotEditor.xcodeproj/project.pbxproj b/CotEditor.xcodeproj/project.pbxproj index 31642b1bb..fac569ee6 100644 --- a/CotEditor.xcodeproj/project.pbxproj +++ b/CotEditor.xcodeproj/project.pbxproj @@ -136,12 +136,8 @@ 2A2179F61A07093B002C4AB1 /* SyntaxMap.json in Resources */ = {isa = PBXBuildFile; fileRef = 2A2179F51A07093B002C4AB1 /* SyntaxMap.json */; }; 2A21E6732BB44D5E0054C8A1 /* DonationSettings.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2A21E6722BB44D5E0054C8A1 /* DonationSettings.xcstrings */; }; 2A21E6742BB44D5E0054C8A1 /* DonationSettings.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2A21E6722BB44D5E0054C8A1 /* DonationSettings.xcstrings */; }; - 2A231A251E7B4EDC00C2A909 /* MultipleReplace+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A241E7B4EDC00C2A909 /* MultipleReplace+Codable.swift */; }; - 2A231A261E7B4EDC00C2A909 /* MultipleReplace+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A241E7B4EDC00C2A909 /* MultipleReplace+Codable.swift */; }; 2A231A281E7BD82700C2A909 /* Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A271E7BD82700C2A909 /* Binding.swift */; }; 2A231A291E7BD82700C2A909 /* Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A271E7BD82700C2A909 /* Binding.swift */; }; - 2A231A2D1E7BE8B700C2A909 /* FindProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A2C1E7BE8B700C2A909 /* FindProgress.swift */; }; - 2A231A2E1E7BE8B700C2A909 /* FindProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A2C1E7BE8B700C2A909 /* FindProgress.swift */; }; 2A231A361E7C30F000C2A909 /* MultipleReplaceSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A351E7C30F000C2A909 /* MultipleReplaceSplitViewController.swift */; }; 2A231A371E7C30F000C2A909 /* MultipleReplaceSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A351E7C30F000C2A909 /* MultipleReplaceSplitViewController.swift */; }; 2A231A391E7C31F400C2A909 /* MultipleReplaceListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A381E7C31F400C2A909 /* MultipleReplaceListViewController.swift */; }; @@ -189,8 +185,6 @@ 2A33D07F1D1C75B8005977B9 /* SyntaxValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A33D07D1D1C75B8005977B9 /* SyntaxValidationView.swift */; }; 2A341D1A281EE23C00B85CB6 /* UserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A341D19281EE23C00B85CB6 /* UserActivity.swift */; }; 2A341D1B281EE23C00B85CB6 /* UserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A341D19281EE23C00B85CB6 /* UserActivity.swift */; }; - 2A3581981E597ECE00762AA5 /* MultipleReplace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3581971E597ECE00762AA5 /* MultipleReplace.swift */; }; - 2A3581991E597ECE00762AA5 /* MultipleReplace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3581971E597ECE00762AA5 /* MultipleReplace.swift */; }; 2A359DFE1DAE93EE00FEF7AA /* NSWindow+Responder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A359DFD1DAE93EE00FEF7AA /* NSWindow+Responder.swift */; }; 2A359DFF1DAE93EE00FEF7AA /* NSWindow+Responder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A359DFD1DAE93EE00FEF7AA /* NSWindow+Responder.swift */; }; 2A3643E61E7C3D2400EA3CE8 /* ReplacementManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3643E51E7C3D2400EA3CE8 /* ReplacementManager.swift */; }; @@ -715,9 +709,9 @@ 2AEC48341E641E4F00FB0F89 /* Snippet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC48321E641E4F00FB0F89 /* Snippet.swift */; }; 2AEC69C41D41A1BE0089F96F /* EditorTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC69C31D41A1BE0089F96F /* EditorTextView.swift */; }; 2AEC69C51D41A1BE0089F96F /* EditorTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC69C31D41A1BE0089F96F /* EditorTextView.swift */; }; - 2AED46701E425CD200751C45 /* TextFind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED466F1E425CD200751C45 /* TextFind.swift */; }; - 2AED46711E425CD200751C45 /* TextFind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED466F1E425CD200751C45 /* TextFind.swift */; }; - 2AED46731E43942300751C45 /* TextFindTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED46721E43942300751C45 /* TextFindTests.swift */; }; + 2AED46701E425CD200751C45 /* TextFind.Error+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED466F1E425CD200751C45 /* TextFind.Error+Localization.swift */; }; + 2AED46711E425CD200751C45 /* TextFind.Error+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED466F1E425CD200751C45 /* TextFind.Error+Localization.swift */; }; + 2AED46731E43942300751C45 /* TextFinderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED46721E43942300751C45 /* TextFinderTests.swift */; }; 2AED70EE1D2E36EF006FFBCE /* DocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED70ED1D2E36EF006FFBCE /* DocumentViewController.swift */; }; 2AED70EF1D2E36EF006FFBCE /* DocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED70ED1D2E36EF006FFBCE /* DocumentViewController.swift */; }; 2AEE84B21E8158D700BA7982 /* WriteToConsoleCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AEE84B11E8158D700BA7982 /* WriteToConsoleCommand.swift */; }; @@ -866,9 +860,7 @@ 2A1FAD5720A74D0A00566D7C /* MutableCopying.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableCopying.swift; sourceTree = ""; }; 2A2179F51A07093B002C4AB1 /* SyntaxMap.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = SyntaxMap.json; sourceTree = ""; }; 2A21E6722BB44D5E0054C8A1 /* DonationSettings.xcstrings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json.xcstrings; path = DonationSettings.xcstrings; sourceTree = ""; }; - 2A231A241E7B4EDC00C2A909 /* MultipleReplace+Codable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MultipleReplace+Codable.swift"; sourceTree = ""; }; 2A231A271E7BD82700C2A909 /* Binding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Binding.swift; sourceTree = ""; }; - 2A231A2C1E7BE8B700C2A909 /* FindProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindProgress.swift; sourceTree = ""; }; 2A231A351E7C30F000C2A909 /* MultipleReplaceSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleReplaceSplitViewController.swift; sourceTree = ""; }; 2A231A381E7C31F400C2A909 /* MultipleReplaceListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleReplaceListViewController.swift; sourceTree = ""; }; 2A24F90F2BEDDFEF00CB6CCF /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewView.swift; sourceTree = ""; }; @@ -893,7 +885,6 @@ 2A30C7DA2B1380BE002F6381 /* ShortcutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutView.swift; sourceTree = ""; }; 2A33D07D1D1C75B8005977B9 /* SyntaxValidationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntaxValidationView.swift; sourceTree = ""; }; 2A341D19281EE23C00B85CB6 /* UserActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivity.swift; sourceTree = ""; }; - 2A3581971E597ECE00762AA5 /* MultipleReplace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleReplace.swift; sourceTree = ""; }; 2A359DFD1DAE93EE00FEF7AA /* NSWindow+Responder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSWindow+Responder.swift"; sourceTree = ""; }; 2A3643E51E7C3D2400EA3CE8 /* ReplacementManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplacementManager.swift; sourceTree = ""; }; 2A36CE7B1FF654C000020702 /* NSTextView+Snippet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextView+Snippet.swift"; sourceTree = ""; }; @@ -1175,8 +1166,8 @@ 2AEBD259246BB4C200EC97A3 /* NSAttributedStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAttributedStringTests.swift; sourceTree = ""; }; 2AEC48321E641E4F00FB0F89 /* Snippet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Snippet.swift; sourceTree = ""; }; 2AEC69C31D41A1BE0089F96F /* EditorTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorTextView.swift; sourceTree = ""; }; - 2AED466F1E425CD200751C45 /* TextFind.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFind.swift; sourceTree = ""; }; - 2AED46721E43942300751C45 /* TextFindTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFindTests.swift; sourceTree = ""; }; + 2AED466F1E425CD200751C45 /* TextFind.Error+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TextFind.Error+Localization.swift"; sourceTree = ""; }; + 2AED46721E43942300751C45 /* TextFinderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFinderTests.swift; sourceTree = ""; }; 2AED70ED1D2E36EF006FFBCE /* DocumentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentViewController.swift; sourceTree = ""; }; 2AEE84B11E8158D700BA7982 /* WriteToConsoleCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WriteToConsoleCommand.swift; sourceTree = ""; }; 2AF073E21D33C3AB00770BA6 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; @@ -1479,8 +1470,6 @@ 2A231A2A1E7BD92F00C2A909 /* Models */ = { isa = PBXGroup; children = ( - 2A3581971E597ECE00762AA5 /* MultipleReplace.swift */, - 2A231A241E7B4EDC00C2A909 /* MultipleReplace+Codable.swift */, 2A3A19DE2068A76600516DE4 /* NSTextView+MultipleReplace.swift */, ); name = Models; @@ -1849,8 +1838,7 @@ children = ( 2A18560A1D47FA37008FA79E /* TextFinder.swift */, 2A6876A72963DE38006257A6 /* TextFinderSettings.swift */, - 2AED466F1E425CD200751C45 /* TextFind.swift */, - 2A231A2C1E7BE8B700C2A909 /* FindProgress.swift */, + 2AED466F1E425CD200751C45 /* TextFind.Error+Localization.swift */, 2AB1BD21287D752300C6FEAF /* Views */, 2A009ADB1A5AB96F00C3D542 /* Text View */, 2A3581931E597AFE00762AA5 /* Multiple Replace */, @@ -2107,7 +2095,7 @@ 2ACC65311C98033D000574DC /* ThemeTests.swift */, 2A9C07551CF9F982006D672D /* IncompatibleCharacterTests.swift */, 2A54BE2B1D40EB24000816B0 /* LineEndingTests.swift */, - 2AED46721E43942300751C45 /* TextFindTests.swift */, + 2AED46721E43942300751C45 /* TextFinderTests.swift */, 2AC72EA1253478D5001D3CA0 /* FileDropItemTests.swift */, 2ABEFB6923DC0CA0008769F4 /* EditorCounterTests.swift */, 2A1125C023F180FF006A1DB2 /* LineRangeCacheableTests.swift */, @@ -2745,7 +2733,6 @@ 2AE52F291D176B8500D60A32 /* FindPanelSplitView.swift in Sources */, 2A5D13361D1FC87900D38E6A /* FindPanelTextClipView.swift in Sources */, 2A5D13331D1FB90300D38E6A /* FindPanelTextView.swift in Sources */, - 2A231A2E1E7BE8B700C2A909 /* FindProgress.swift in Sources */, 2A5D13111D1EE66500D38E6A /* FindProgressView.swift in Sources */, 2AF98CAB294B9488009AD47F /* FindSettingsView.swift in Sources */, 2A65EC262B80C01B008096C5 /* FontPicker.swift in Sources */, @@ -2786,8 +2773,6 @@ 2AB857E82B922D7D0079CFA2 /* ModeManager.swift in Sources */, 2AB857EB2B93050E0079CFA2 /* ModeOptions.swift in Sources */, 2A836D9F2AB1528700B4D458 /* ModeSettingsView.swift in Sources */, - 2A3581991E597ECE00762AA5 /* MultipleReplace.swift in Sources */, - 2A231A261E7B4EDC00C2A909 /* MultipleReplace+Codable.swift in Sources */, 2A231A3A1E7C31F400C2A909 /* MultipleReplaceListViewController.swift in Sources */, 2A6876AB29641547006257A6 /* MultipleReplacePanelController.swift in Sources */, 2AF98CAF294C0670009AD47F /* MultipleReplaceSettingsView.swift in Sources */, @@ -2904,7 +2889,7 @@ 2A4714E42093A2D40093E27F /* SyntaxParser.swift in Sources */, 2A33D07F1D1C75B8005977B9 /* SyntaxValidationView.swift in Sources */, 2AFE848722AE71130001C4ED /* TextContainer.swift in Sources */, - 2AED46711E425CD200751C45 /* TextFind.swift in Sources */, + 2AED46711E425CD200751C45 /* TextFind.Error+Localization.swift in Sources */, 2A18560C1D47FA37008FA79E /* TextFinder.swift in Sources */, 2A6876A82963DE38006257A6 /* TextFinderSettings.swift in Sources */, 2AD69B861D3E42F700FBD998 /* TextSelection.swift in Sources */, @@ -2966,7 +2951,7 @@ 2AC71DE21BF0BDBC002E1434 /* StringAdvancedCountTests.swift in Sources */, 2AE12DFE1E7DB7D200681F72 /* StringFilenameTests.swift in Sources */, 2A63CEC41D0B06D800ED8186 /* SyntaxTests.swift in Sources */, - 2AED46731E43942300751C45 /* TextFindTests.swift in Sources */, + 2AED46731E43942300751C45 /* TextFinderTests.swift in Sources */, 2ACC65321C98033D000574DC /* ThemeTests.swift in Sources */, 2A476CAE1D09C8C80088E37A /* URLExtensionsTests.swift in Sources */, 2AFD218D27E0442B00E83E88 /* UTTypeExtensionTests.swift in Sources */, @@ -3065,7 +3050,6 @@ 2AE52F281D176B8500D60A32 /* FindPanelSplitView.swift in Sources */, 2A5D13351D1FC87900D38E6A /* FindPanelTextClipView.swift in Sources */, 2A5D13321D1FB90300D38E6A /* FindPanelTextView.swift in Sources */, - 2A231A2D1E7BE8B700C2A909 /* FindProgress.swift in Sources */, 2A5D13101D1EE66500D38E6A /* FindProgressView.swift in Sources */, 2AF98CAC294B9488009AD47F /* FindSettingsView.swift in Sources */, 2A65EC272B80C01B008096C5 /* FontPicker.swift in Sources */, @@ -3106,8 +3090,6 @@ 2AB857E92B922D7D0079CFA2 /* ModeManager.swift in Sources */, 2AB857EC2B93050E0079CFA2 /* ModeOptions.swift in Sources */, 2A836DA02AB1528700B4D458 /* ModeSettingsView.swift in Sources */, - 2A3581981E597ECE00762AA5 /* MultipleReplace.swift in Sources */, - 2A231A251E7B4EDC00C2A909 /* MultipleReplace+Codable.swift in Sources */, 2A231A391E7C31F400C2A909 /* MultipleReplaceListViewController.swift in Sources */, 2A6876AC29641547006257A6 /* MultipleReplacePanelController.swift in Sources */, 2AF98CB0294C0670009AD47F /* MultipleReplaceSettingsView.swift in Sources */, @@ -3224,7 +3206,7 @@ 2A4714E32093A2D40093E27F /* SyntaxParser.swift in Sources */, 2A33D07E1D1C75B8005977B9 /* SyntaxValidationView.swift in Sources */, 2AFE848622AE71130001C4ED /* TextContainer.swift in Sources */, - 2AED46701E425CD200751C45 /* TextFind.swift in Sources */, + 2AED46701E425CD200751C45 /* TextFind.Error+Localization.swift in Sources */, 2A18560B1D47FA37008FA79E /* TextFinder.swift in Sources */, 2A6876A92963DE38006257A6 /* TextFinderSettings.swift in Sources */, 2AD69B851D3E42F700FBD998 /* TextSelection.swift in Sources */, diff --git a/CotEditor/Sources/FindProgressView.swift b/CotEditor/Sources/FindProgressView.swift index 5b8f27b63..5ed1bc768 100644 --- a/CotEditor/Sources/FindProgressView.swift +++ b/CotEditor/Sources/FindProgressView.swift @@ -24,6 +24,7 @@ // import SwiftUI +import TextFind struct FindProgressView: View { diff --git a/CotEditor/Sources/MultipleReplaceListViewController.swift b/CotEditor/Sources/MultipleReplaceListViewController.swift index 24d3d90e1..1bd4638ed 100644 --- a/CotEditor/Sources/MultipleReplaceListViewController.swift +++ b/CotEditor/Sources/MultipleReplaceListViewController.swift @@ -29,6 +29,7 @@ import Combine import UniformTypeIdentifiers import OSLog import Defaults +import TextFind final class MultipleReplaceListViewController: NSViewController, NSMenuItemValidation { diff --git a/CotEditor/Sources/MultipleReplaceSettingsView.swift b/CotEditor/Sources/MultipleReplaceSettingsView.swift index fe38b2690..cac0f7013 100644 --- a/CotEditor/Sources/MultipleReplaceSettingsView.swift +++ b/CotEditor/Sources/MultipleReplaceSettingsView.swift @@ -8,7 +8,7 @@ // // --------------------------------------------------------------------------- // -// © 2022-2023 1024jp +// © 2022-2024 1024jp // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ // import SwiftUI +import TextFind struct MultipleReplaceSettingsView: View { diff --git a/CotEditor/Sources/MultipleReplaceViewController.swift b/CotEditor/Sources/MultipleReplaceViewController.swift index 7211b40ed..29523964a 100644 --- a/CotEditor/Sources/MultipleReplaceViewController.swift +++ b/CotEditor/Sources/MultipleReplaceViewController.swift @@ -27,6 +27,7 @@ import AppKit import Combine import SwiftUI import Defaults +import TextFind final class MultipleReplaceViewController: NSViewController { diff --git a/CotEditor/Sources/NSTextView+MultipleReplace.swift b/CotEditor/Sources/NSTextView+MultipleReplace.swift index 76994bae8..fbae9027d 100644 --- a/CotEditor/Sources/NSTextView+MultipleReplace.swift +++ b/CotEditor/Sources/NSTextView+MultipleReplace.swift @@ -25,6 +25,7 @@ import AppKit import SwiftUI +import TextFind extension NSTextView { diff --git a/CotEditor/Sources/ReplacementManager.swift b/CotEditor/Sources/ReplacementManager.swift index 2f1bb7d41..9f9a8a48a 100644 --- a/CotEditor/Sources/ReplacementManager.swift +++ b/CotEditor/Sources/ReplacementManager.swift @@ -25,6 +25,7 @@ import Foundation import Observation +import TextFind import UniformTypeIdentifiers @Observable final class ReplacementManager: SettingFileManaging { diff --git a/CotEditor/Sources/TextFind.Error+Localization.swift b/CotEditor/Sources/TextFind.Error+Localization.swift new file mode 100644 index 000000000..c22af2a86 --- /dev/null +++ b/CotEditor/Sources/TextFind.Error+Localization.swift @@ -0,0 +1,65 @@ +// +// TextFind.Error+Localization.swift +// +// CotEditor +// https://coteditor.com +// +// Created by 1024jp on 2017-02-02. +// +// --------------------------------------------------------------------------- +// +// © 2015-2024 1024jp +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import TextFind + +extension TextFind.Error: @retroactive LocalizedError { + + public var errorDescription: String? { + + switch self { + case .regularExpression: + String(localized: "TextFind.Error.regularExpression.errorDescription", + defaultValue: "Invalid regular expression", + table: "TextFind") + case .emptyFindString: + String(localized: "TextFind.Error.emptyFindString.errorDescription", + defaultValue: "Empty find string", + table: "TextFind") + case .emptyInSelectionSearch: + String(localized: "TextFind.Error.emptyInSelectionSearch.errorDescription", + defaultValue: "The option “in selection” is selected, although nothing is selected.", + table: "TextFind") + } + } + + + public var recoverySuggestion: String? { + + switch self { + case .regularExpression(let reason): + reason + case .emptyFindString: + String(localized: "TextFind.Error.emptyFindString.recoverySuggestion", + defaultValue: "Input text to find.", + table: "TextFind") + case .emptyInSelectionSearch: + String(localized: "TextFind.Error.emptyInSelectionSearch.recoverySuggestion", + defaultValue: "Select the search scope in the document or turn off the “in selection” option.", + table: "TextFind") + } + } +} diff --git a/CotEditor/Sources/TextFinder.swift b/CotEditor/Sources/TextFinder.swift index e531c91d3..5f09a9547 100644 --- a/CotEditor/Sources/TextFinder.swift +++ b/CotEditor/Sources/TextFinder.swift @@ -25,6 +25,7 @@ import AppKit import SwiftUI +import TextFind import ValueRange extension NSAttributedString: @retroactive @unchecked Sendable { } diff --git a/CotEditor/Sources/TextFinderSettings.swift b/CotEditor/Sources/TextFinderSettings.swift index 7d5509cab..2c15dc453 100644 --- a/CotEditor/Sources/TextFinderSettings.swift +++ b/CotEditor/Sources/TextFinderSettings.swift @@ -23,8 +23,10 @@ // limitations under the License. // -import AppKit +import Foundation +import AppKit.NSPasteboard import Defaults +import TextFind @MainActor final class TextFinderSettings: NSObject { diff --git a/Packages/EditorKit/Package.swift b/Packages/EditorKit/Package.swift index 05fc8a48c..b22b7ccce 100644 --- a/Packages/EditorKit/Package.swift +++ b/Packages/EditorKit/Package.swift @@ -22,6 +22,7 @@ let package = Package( "Syntax", "TextClipping", "TextEditing", + "TextFind", "UnicodeNormalization", "ValueRange", "Shortcut", @@ -37,6 +38,7 @@ let package = Package( .library(name: "Syntax", targets: ["Syntax"]), .library(name: "TextClipping", targets: ["TextClipping"]), .library(name: "TextEditing", targets: ["TextEditing"]), + .library(name: "TextFind", targets: ["TextFind"]), .library(name: "UnicodeNormalization", targets: ["UnicodeNormalization"]), .library(name: "Shortcut", targets: ["Shortcut"]), @@ -72,6 +74,9 @@ let package = Package( .target(name: "TextEditing", dependencies: ["StringBasics", "Syntax"]), .testTarget(name: "TextEditingTests", dependencies: ["TextEditing"]), + .target(name: "TextFind", dependencies: ["StringBasics", "ValueRange"]), + .testTarget(name: "TextFindTests", dependencies: ["TextFind"]), + .target(name: "UnicodeNormalization"), .testTarget(name: "UnicodeNormalizationTests", dependencies: ["UnicodeNormalization"]), diff --git a/Packages/EditorKit/Sources/StringBasics/Collection.swift b/Packages/EditorKit/Sources/StringBasics/Collection.swift index a7cc6b006..32ad9bf26 100644 --- a/Packages/EditorKit/Sources/StringBasics/Collection.swift +++ b/Packages/EditorKit/Sources/StringBasics/Collection.swift @@ -24,7 +24,7 @@ // limitations under the License. // -extension Sequence where Element: Equatable { +public extension Sequence where Element: Equatable { /// An array consists of unique elements of receiver by keeping ordering. var uniqued: [Element] { @@ -36,3 +36,14 @@ extension Sequence where Element: Equatable { } } } + + + +public extension Array where Element: Equatable { + + /// Removes duplicated elements by keeping ordering. + mutating func unique() { + + self = self.uniqued + } +} diff --git a/Packages/EditorKit/Sources/TextEditing/Collection.swift b/Packages/EditorKit/Sources/TextEditing/Collection.swift index 413637a72..af84dc294 100644 --- a/Packages/EditorKit/Sources/TextEditing/Collection.swift +++ b/Packages/EditorKit/Sources/TextEditing/Collection.swift @@ -24,24 +24,6 @@ // limitations under the License. // -// MARK: - Unique - -extension Sequence where Element: Equatable { - - /// An array consists of unique elements of receiver by keeping ordering. - var uniqued: [Element] { - - self.reduce(into: []) { (unique, element) in - guard !unique.contains(element) else { return } - - unique.append(element) - } - } -} - - -// MARK: - Sort - extension Sequence { /// Returns the elements of the sequence, sorted using the value that the given key path refers as the comparison between elements. diff --git a/CotEditor/Sources/FindProgress.swift b/Packages/EditorKit/Sources/TextFind/FindProgress.swift similarity index 76% rename from CotEditor/Sources/FindProgress.swift rename to Packages/EditorKit/Sources/TextFind/FindProgress.swift index 754b0376e..858cf6999 100644 --- a/CotEditor/Sources/FindProgress.swift +++ b/Packages/EditorKit/Sources/TextFind/FindProgress.swift @@ -8,7 +8,7 @@ // // --------------------------------------------------------------------------- // -// © 2022-2023 1024jp +// © 2022-2024 1024jp // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,13 +26,13 @@ import Foundation import Observation -@Observable final class FindProgress: @unchecked Sendable { +@Observable public final class FindProgress: @unchecked Sendable { - @ObservationIgnored private(set) var count = 0 - @ObservationIgnored var completedUnit = 0 + @ObservationIgnored public private(set) var count = 0 + @ObservationIgnored public var completedUnit = 0 - private(set) var isCancelled = false - private(set) var isFinished = false + public private(set) var isCancelled = false + public private(set) var isFinished = false private let scope: Range @@ -40,14 +40,14 @@ import Observation /// Instantiates a progress. /// /// - Parameter scope: The range of progress unit to work with. - init(scope: Range) { + public init(scope: Range) { self.scope = scope } /// The fraction of task completed in between 0...1.0. - var fractionCompleted: Double { + public var fractionCompleted: Double { if self.isFinished || self.scope.isEmpty { return 1 @@ -60,21 +60,21 @@ import Observation /// Increments count. /// /// - Parameter count: The amount to increment. - func increment(by count: Int = 1) { + public func increment(by count: Int = 1) { self.count += count } /// Raise `isCancelled` flag. - func cancel() { + public func cancel() { self.isCancelled = true } /// Raise `isFinished` flag. - func finish() { + public func finish() { self.isFinished = true } diff --git a/CotEditor/Sources/MultipleReplace+Codable.swift b/Packages/EditorKit/Sources/TextFind/MultipleReplace+Codable.swift similarity index 94% rename from CotEditor/Sources/MultipleReplace+Codable.swift rename to Packages/EditorKit/Sources/TextFind/MultipleReplace+Codable.swift index e3faf3e5f..aa04a8743 100644 --- a/CotEditor/Sources/MultipleReplace+Codable.swift +++ b/Packages/EditorKit/Sources/TextFind/MultipleReplace+Codable.swift @@ -8,7 +8,7 @@ // // --------------------------------------------------------------------------- // -// © 2017-2023 1024jp +// © 2017-2024 1024jp // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ extension MultipleReplace.Replacement: Codable { } - init(from decoder: any Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -51,7 +51,7 @@ extension MultipleReplace.Replacement: Codable { } - func encode(to encoder: any Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -86,7 +86,7 @@ extension MultipleReplace.Settings: Codable { } - init(from decoder: any Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -102,7 +102,7 @@ extension MultipleReplace.Settings: Codable { } - func encode(to encoder: any Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) diff --git a/CotEditor/Sources/MultipleReplace.swift b/Packages/EditorKit/Sources/TextFind/MultipleReplace.swift similarity index 70% rename from CotEditor/Sources/MultipleReplace.swift rename to Packages/EditorKit/Sources/TextFind/MultipleReplace.swift index bad4332f2..23feade07 100644 --- a/CotEditor/Sources/MultipleReplace.swift +++ b/Packages/EditorKit/Sources/TextFind/MultipleReplace.swift @@ -25,30 +25,57 @@ import Foundation -struct MultipleReplace: Codable { +public struct MultipleReplace: Equatable, Sendable, Codable { - struct Replacement: Equatable { + public struct Replacement: Equatable, Sendable { - var findString: String = "" - var replacementString: String = "" - var usesRegularExpression: Bool = false - var ignoresCase: Bool = false - var description: String? - var isEnabled = true + public var findString: String + public var replacementString: String + public var usesRegularExpression: Bool + public var ignoresCase: Bool + public var description: String? + public var isEnabled: Bool + + + public init(findString: String = "", replacementString: String = "", usesRegularExpression: Bool = false, ignoresCase: Bool = false, description: String? = nil, isEnabled: Bool = true) { + + self.findString = findString + self.replacementString = replacementString + self.usesRegularExpression = usesRegularExpression + self.ignoresCase = ignoresCase + self.description = description + self.isEnabled = isEnabled + } } - struct Settings: Equatable { + public struct Settings: Equatable, Sendable { - var textualOptions: String.CompareOptions = [] - var regexOptions: NSRegularExpression.Options = [.anchorsMatchLines] - var matchesFullWord: Bool = false - var unescapesReplacementString: Bool = true + public var textualOptions: String.CompareOptions + public var regexOptions: NSRegularExpression.Options + public var matchesFullWord: Bool + public var unescapesReplacementString: Bool + + + public init(textualOptions: String.CompareOptions = [], regexOptions: NSRegularExpression.Options = [.anchorsMatchLines], matchesFullWord: Bool = false, unescapesReplacementString: Bool = true) { + + self.textualOptions = textualOptions + self.regexOptions = regexOptions + self.matchesFullWord = matchesFullWord + self.unescapesReplacementString = unescapesReplacementString + } } - var replacements: [Replacement] = [] - var settings: Settings = .init() + public var replacements: [Replacement] + public var settings: Settings + + + public init(replacements: [Replacement] = [], settings: Settings = .init()) { + + self.replacements = replacements + self.settings = settings + } } @@ -57,14 +84,14 @@ struct MultipleReplace: Codable { extension MultipleReplace { - struct Result { + public struct Result: Equatable, Sendable { - var string: String - var selectedRanges: [NSRange]? + public var string: String + public var selectedRanges: [NSRange]? } - enum Status { + public enum Status: Equatable, Sendable { case processed case unitChanged @@ -81,7 +108,7 @@ extension MultipleReplace { /// - inSelection: Whether find only in selection. /// - progress: The progress object to observe cancellation by the user and notify the find progress. /// - Returns: The found ranges. This method will return first all search finished. - func find(string: String, ranges: [NSRange], inSelection: Bool, progress: FindProgress? = nil) throws(CancellationError) -> [NSRange] { + public func find(string: String, ranges: [NSRange], inSelection: Bool, progress: FindProgress? = nil) throws(CancellationError) -> [NSRange] { var result: [NSRange] = [] @@ -121,7 +148,7 @@ extension MultipleReplace { /// - inSelection: Whether replace only in selection. /// - progress: The progress object to observe cancellation by the user and notify the replacement progress. /// - Returns: The result of the replacement. This method will return first all replacement finished. - func replace(string: String, ranges: [NSRange], inSelection: Bool, progress: FindProgress? = nil) throws(CancellationError) -> Result { + public func replace(string: String, ranges: [NSRange], inSelection: Bool, progress: FindProgress? = nil) throws(CancellationError) -> Result { var result = Result(string: string, selectedRanges: ranges) @@ -190,7 +217,7 @@ private extension MultipleReplace.Replacement { extension MultipleReplace.Replacement { /// Checks if replacement rule is valid. - func validate(regexOptions: NSRegularExpression.Options = []) throws(TextFind.Error) { + public func validate(regexOptions: NSRegularExpression.Options = []) throws(TextFind.Error) { guard !self.findString.isEmpty else { throw TextFind.Error.emptyFindString diff --git a/CotEditor/Sources/TextFind.swift b/Packages/EditorKit/Sources/TextFind/TextFind.swift similarity index 84% rename from CotEditor/Sources/TextFind.swift rename to Packages/EditorKit/Sources/TextFind/TextFind.swift index 23bb259bb..2e933189e 100644 --- a/CotEditor/Sources/TextFind.swift +++ b/Packages/EditorKit/Sources/TextFind/TextFind.swift @@ -27,71 +27,35 @@ import Foundation import StringBasics import ValueRange -struct TextFind { +public struct TextFind: Equatable, Sendable { - typealias ReplacementItem = ValueRange + public typealias ReplacementItem = ValueRange - enum Mode: Equatable { + public enum Mode: Equatable, Sendable { case textual(options: String.CompareOptions, fullWord: Bool) // don't include .backwards to options case regularExpression(options: NSRegularExpression.Options, unescapesReplacement: Bool) } - enum `Error`: LocalizedError { + public enum `Error`: Swift.Error, Sendable { case regularExpression(reason: String) case emptyFindString case emptyInSelectionSearch - - - var errorDescription: String? { - - switch self { - case .regularExpression: - String(localized: "TextFind.Error.regularExpression.errorDescription", - defaultValue: "Invalid regular expression", - table: "TextFind") - case .emptyFindString: - String(localized: "TextFind.Error.emptyFindString.errorDescription", - defaultValue: "Empty find string", - table: "TextFind") - case .emptyInSelectionSearch: - String(localized: "TextFind.Error.emptyInSelectionSearch.errorDescription", - defaultValue: "The option “in selection” is selected, although nothing is selected.", - table: "TextFind") - } - } - - - var recoverySuggestion: String? { - - switch self { - case .regularExpression(let reason): - reason - case .emptyFindString: - String(localized: "TextFind.Error.emptyFindString.recoverySuggestion", - defaultValue: "Input text to find.", - table: "TextFind") - case .emptyInSelectionSearch: - String(localized: "TextFind.Error.emptyInSelectionSearch.recoverySuggestion", - defaultValue: "Select the search scope in the document or turn off the “in selection” option.", - table: "TextFind") - } - } } // MARK: Public Properties - let findString: String - let mode: TextFind.Mode - let inSelection: Bool + public let findString: String + public let mode: TextFind.Mode + public let inSelection: Bool - let string: String - let selectedRanges: [NSRange] + public let string: String + public let selectedRanges: [NSRange] // MARK: Private Properties @@ -112,7 +76,7 @@ struct TextFind { /// - mode: The settable options for the text search. /// - inSelection: Whether find string only in selectedRanges. /// - selectedRanges: The selected ranges in the text view. - init(for string: String, findString: String, mode: TextFind.Mode, inSelection: Bool = false, selectedRanges: [NSRange] = [NSRange()]) throws(TextFind.Error) { + public init(for string: String, findString: String, mode: TextFind.Mode, inSelection: Bool = false, selectedRanges: [NSRange] = [NSRange()]) throws(TextFind.Error) { assert(!selectedRanges.isEmpty) @@ -151,14 +115,14 @@ struct TextFind { // MARK: Public Methods /// The number of capture groups in the regular expression. - var numberOfCaptureGroups: Int { + public var numberOfCaptureGroups: Int { self.regex?.numberOfCaptureGroups ?? 0 } /// The range large enough to contain all scope ranges. - var scopeRange: Range { + public var scopeRange: Range { self.scopeRanges.map(\.lowerBound).min()!.. (range: NSRange, wrapped: Bool)? { + public func find(in matches: [NSRange], forward: Bool, includingSelection: Bool = false, wraps: Bool) -> (range: NSRange, wrapped: Bool)? { assert(forward || !includingSelection) @@ -231,7 +195,7 @@ struct TextFind { /// - Parameters: /// - replacementString: The string with which to replace. /// - Returns: The struct of a string to replace with and a range to replace if found. Otherwise, nil. - func replace(with replacementString: String) -> ReplacementItem? { + public func replace(with replacementString: String) -> ReplacementItem? { let string = self.string let selectedRange = self.selectedRanges.first! @@ -262,7 +226,7 @@ struct TextFind { /// - block: The block enumerates the matches. /// - matches: The array of matches including group matches. /// - stop: The `block` can set the value to true to stop further processing. - func findAll(using block: (_ matches: [NSRange], _ stop: inout Bool) -> Void) { + public func findAll(using block: (_ matches: [NSRange], _ stop: inout Bool) -> Void) { for range in self.scopeRanges { self.enumerateMatches(in: range) { (matchedRange, match, stop) in @@ -288,7 +252,7 @@ struct TextFind { /// - Returns: /// - replacementItems: ReplacementItem per selectedRange. /// - selectedRanges: New selections for textView only if the replacement is performed within selection. Otherwise, `nil`. - func replaceAll(with replacementString: String, using block: @escaping (_ range: NSRange, _ count: Int, _ stop: inout Bool) -> Void) -> (replacementItems: [ReplacementItem], selectedRanges: [NSRange]?) { + public func replaceAll(with replacementString: String, using block: @escaping (_ range: NSRange, _ count: Int, _ stop: inout Bool) -> Void) -> (replacementItems: [ReplacementItem], selectedRanges: [NSRange]?) { let replacementString = self.replacementString(from: replacementString) var replacementItems: [ReplacementItem] = [] diff --git a/Tests/TextFindTests.swift b/Packages/EditorKit/Tests/TextFindTests/TextFindTests.swift similarity index 86% rename from Tests/TextFindTests.swift rename to Packages/EditorKit/Tests/TextFindTests/TextFindTests.swift index 7cc26851a..7787ede54 100644 --- a/Tests/TextFindTests.swift +++ b/Packages/EditorKit/Tests/TextFindTests/TextFindTests.swift @@ -24,31 +24,12 @@ // limitations under the License. // -import AppKit +import Foundation import Testing -@testable import CotEditor +@testable import TextFind struct TextFindTests { - @Test func finderActions() { - - #expect(TextFinder.Action.showFindInterface.rawValue == NSTextFinder.Action.showFindInterface.rawValue) - #expect(TextFinder.Action.nextMatch.rawValue == NSTextFinder.Action.nextMatch.rawValue) - #expect(TextFinder.Action.previousMatch.rawValue == NSTextFinder.Action.previousMatch.rawValue) - #expect(TextFinder.Action.replaceAll.rawValue == NSTextFinder.Action.replaceAll.rawValue) - #expect(TextFinder.Action.replace.rawValue == NSTextFinder.Action.replace.rawValue) - #expect(TextFinder.Action.replaceAndFind.rawValue == NSTextFinder.Action.replaceAndFind.rawValue) - #expect(TextFinder.Action.setSearchString.rawValue == NSTextFinder.Action.setSearchString.rawValue) - #expect(TextFinder.Action.replaceAllInSelection.rawValue == NSTextFinder.Action.replaceAllInSelection.rawValue) - #expect(TextFinder.Action.selectAll.rawValue == NSTextFinder.Action.selectAll.rawValue) - #expect(TextFinder.Action.selectAllInSelection.rawValue == NSTextFinder.Action.selectAllInSelection.rawValue) - #expect(TextFinder.Action.hideFindInterface.rawValue == NSTextFinder.Action.hideFindInterface.rawValue) - #expect(TextFinder.Action.showReplaceInterface.rawValue == NSTextFinder.Action.showReplaceInterface.rawValue) - #expect(TextFinder.Action.showReplaceInterface.rawValue == NSTextFinder.Action.showReplaceInterface.rawValue) - #expect(TextFinder.Action.hideReplaceInterface.rawValue == NSTextFinder.Action.hideReplaceInterface.rawValue) - } - - @Test func countCaptureGroup() throws { var mode: TextFind.Mode diff --git a/Tests/TextFinderTests.swift b/Tests/TextFinderTests.swift new file mode 100644 index 000000000..6cd0ccd7d --- /dev/null +++ b/Tests/TextFinderTests.swift @@ -0,0 +1,50 @@ +// +// TextFinderTests.swift +// Tests +// +// CotEditor +// https://coteditor.com +// +// Created by 1024jp on 2017-02-03. +// +// --------------------------------------------------------------------------- +// +// © 2017-2024 1024jp +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit +import Testing +@testable import CotEditor + +struct TextFinderTests { + + @Test func finderActions() { + + #expect(TextFinder.Action.showFindInterface.rawValue == NSTextFinder.Action.showFindInterface.rawValue) + #expect(TextFinder.Action.nextMatch.rawValue == NSTextFinder.Action.nextMatch.rawValue) + #expect(TextFinder.Action.previousMatch.rawValue == NSTextFinder.Action.previousMatch.rawValue) + #expect(TextFinder.Action.replaceAll.rawValue == NSTextFinder.Action.replaceAll.rawValue) + #expect(TextFinder.Action.replace.rawValue == NSTextFinder.Action.replace.rawValue) + #expect(TextFinder.Action.replaceAndFind.rawValue == NSTextFinder.Action.replaceAndFind.rawValue) + #expect(TextFinder.Action.setSearchString.rawValue == NSTextFinder.Action.setSearchString.rawValue) + #expect(TextFinder.Action.replaceAllInSelection.rawValue == NSTextFinder.Action.replaceAllInSelection.rawValue) + #expect(TextFinder.Action.selectAll.rawValue == NSTextFinder.Action.selectAll.rawValue) + #expect(TextFinder.Action.selectAllInSelection.rawValue == NSTextFinder.Action.selectAllInSelection.rawValue) + #expect(TextFinder.Action.hideFindInterface.rawValue == NSTextFinder.Action.hideFindInterface.rawValue) + #expect(TextFinder.Action.showReplaceInterface.rawValue == NSTextFinder.Action.showReplaceInterface.rawValue) + #expect(TextFinder.Action.showReplaceInterface.rawValue == NSTextFinder.Action.showReplaceInterface.rawValue) + #expect(TextFinder.Action.hideReplaceInterface.rawValue == NSTextFinder.Action.hideReplaceInterface.rawValue) + } +}