diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 7240eddad..d2475b93c 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -42,12 +42,19 @@ B673FDAD27E8296A00795864 /* PressActionsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B673FDAC27E8296A00795864 /* PressActionsModifier.swift */; }; B6EE989027E8879A00CDD8AB /* InspectorSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EE988F27E8879A00CDD8AB /* InspectorSidebar.swift */; }; B6EE989227E887C600CDD8AB /* InspectorSidebarToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EE989127E887C600CDD8AB /* InspectorSidebarToolbar.swift */; }; + D7012EE827E757850001E1EF /* SidebarSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7012EE727E757850001E1EF /* SidebarSearch.swift */; }; D70F5E2C27E4E8CF004EE4B9 /* WelcomeModule in Frameworks */ = {isa = PBXBuildFile; productRef = D70F5E2B27E4E8CF004EE4B9 /* WelcomeModule */; }; D7211D4327E066CE008F2ED7 /* Localized+Ex.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7211D4227E066CE008F2ED7 /* Localized+Ex.swift */; }; D7211D4727E06BFE008F2ED7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D7211D4927E06BFE008F2ED7 /* Localizable.strings */; }; D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */; }; D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */; }; D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */; }; + D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */; }; + D7E201B027E8C07300CB86D0 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AF27E8C07300CB86D0 /* SearchBar.swift */; }; + D7E201B227E8D50000CB86D0 /* SearchModeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B127E8D50000CB86D0 /* SearchModeSelector.swift */; }; + D7E201B427E9989900CB86D0 /* SearchResultList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B327E9989900CB86D0 /* SearchResultList.swift */; }; + D7E201BD27EA00E200CB86D0 /* SearchResultFileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201BC27EA00E200CB86D0 /* SearchResultFileItem.swift */; }; + D7F72DEB27EA3574000C3064 /* Search in Frameworks */ = {isa = PBXBuildFile; productRef = D7F72DEA27EA3574000C3064 /* Search */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -111,12 +118,18 @@ B673FDAC27E8296A00795864 /* PressActionsModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PressActionsModifier.swift; sourceTree = ""; }; B6EE988F27E8879A00CDD8AB /* InspectorSidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorSidebar.swift; sourceTree = ""; }; B6EE989127E887C600CDD8AB /* InspectorSidebarToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorSidebarToolbar.swift; sourceTree = ""; }; + D7012EE727E757850001E1EF /* SidebarSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarSearch.swift; sourceTree = ""; }; D7211D4227E066CE008F2ED7 /* Localized+Ex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Localized+Ex.swift"; sourceTree = ""; }; D7211D4827E06BFE008F2ED7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; D7211D4A27E06C01008F2ED7 /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sr; path = sr.lproj/Localizable.strings; sourceTree = ""; }; D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentProjectsView.swift; sourceTree = ""; }; D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeWindowView.swift; sourceTree = ""; }; + D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Ranges.swift"; sourceTree = ""; }; + D7E201AF27E8C07300CB86D0 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = ""; }; + D7E201B127E8D50000CB86D0 /* SearchModeSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModeSelector.swift; sourceTree = ""; }; + D7E201B327E9989900CB86D0 /* SearchResultList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultList.swift; sourceTree = ""; }; + D7E201BC27EA00E200CB86D0 /* SearchResultFileItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultFileItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -128,6 +141,7 @@ 0485EB2527E7B9C800138301 /* Overlays in Frameworks */, B65E614627E6765D00255275 /* Introspect in Frameworks */, D70F5E2C27E4E8CF004EE4B9 /* WelcomeModule in Frameworks */, + D7F72DEB27EA3574000C3064 /* Search in Frameworks */, 5CFA753B27E896B60002F01B /* GitClient in Frameworks */, 28CE5EA027E6493D0065D29C /* StatusBar in Frameworks */, 5CF38A5E27E48E6C0096A0F7 /* CodeFile in Frameworks */, @@ -158,6 +172,7 @@ 043C321527E3201F006AE443 /* WorkspaceDocument.swift */, 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */, 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */, + D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */, ); path = Documents; sourceTree = ""; @@ -204,6 +219,7 @@ 287776EA27E350A100D46668 /* NavigatorSidebar */ = { isa = PBXGroup; children = ( + D7012EE627E757660001E1EF /* Search */, 287776E627E3413200D46668 /* NavigatorSidebar.swift */, 287776EC27E350D800D46668 /* NavigatorSidebarItem.swift */, 28B0A19727E385C300B73177 /* NavigatorSidebarToolbarTop.swift */, @@ -297,6 +313,18 @@ path = InspectorSidebar; sourceTree = ""; }; + D7012EE627E757660001E1EF /* Search */ = { + isa = PBXGroup; + children = ( + D7012EE727E757850001E1EF /* SidebarSearch.swift */, + D7E201AF27E8C07300CB86D0 /* SearchBar.swift */, + D7E201B127E8D50000CB86D0 /* SearchModeSelector.swift */, + D7E201B327E9989900CB86D0 /* SearchResultList.swift */, + D7E201BC27EA00E200CB86D0 /* SearchResultFileItem.swift */, + ); + path = Search; + sourceTree = ""; + }; D7211D4427E066D4008F2ED7 /* Localization */ = { isa = PBXGroup; children = ( @@ -342,6 +370,7 @@ 28CE5E9F27E6493D0065D29C /* StatusBar */, 0485EB2427E7B9C800138301 /* Overlays */, 5CFA753A27E896B60002F01B /* GitClient */, + D7F72DEA27EA3574000C3064 /* Search */, ); productName = CodeEdit; productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */; @@ -514,7 +543,10 @@ 2B7A583527E4BA0100D25D4E /* AppDelegate.swift in Sources */, 2875A46D27E3BE5B007805F8 /* BreadcrumbsView.swift in Sources */, 0485EB1D27E7338100138301 /* QuickOpenItem.swift in Sources */, + D7012EE827E757850001E1EF /* SidebarSearch.swift in Sources */, + D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */, 04540D5B27DD08C300E91B77 /* SettingsView.swift in Sources */, + D7E201B027E8C07300CB86D0 /* SearchBar.swift in Sources */, D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */, 04540D5C27DD08C300E91B77 /* GeneralSettingsView.swift in Sources */, B6EE989227E887C600CDD8AB /* InspectorSidebarToolbar.swift in Sources */, @@ -524,9 +556,11 @@ 04540D5E27DD08C300E91B77 /* WorkspaceView.swift in Sources */, 34EE19BE27E0469C00F152CE /* BlurView.swift in Sources */, D7211D4327E066CE008F2ED7 /* Localized+Ex.swift in Sources */, + D7E201B227E8D50000CB86D0 /* SearchModeSelector.swift in Sources */, 287776E927E34BC700D46668 /* TabBar.swift in Sources */, 0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */, 0485EB1927E70F4900138301 /* QuickOpenView.swift in Sources */, + D7E201BD27EA00E200CB86D0 /* SearchResultFileItem.swift in Sources */, 286620A527E4AB6900E18C2B /* BreadcrumbsComponent.swift in Sources */, 287776EF27E3515300D46668 /* TabBarItem.swift in Sources */, D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */, @@ -534,6 +568,10 @@ 287776E727E3413200D46668 /* NavigatorSidebar.swift in Sources */, 287776ED27E350D800D46668 /* NavigatorSidebarItem.swift in Sources */, B6EE989027E8879A00CDD8AB /* InspectorSidebar.swift in Sources */, + D7E201B427E9989900CB86D0 /* SearchResultList.swift in Sources */, + D7E201B427E9989900CB86D0 /* SearchResultList.swift in Sources */, + 287776E727E3413200D46668 /* NavigatorSidebar.swift in Sources */, + 287776ED27E350D800D46668 /* NavigatorSidebarItem.swift in Sources */, 289978ED27E4E97E00BB0357 /* FileIconStyle.swift in Sources */, 04660F6627E3ACEF00477777 /* ReopenBehavior.swift in Sources */, D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */, @@ -952,6 +990,10 @@ isa = XCSwiftPackageProductDependency; productName = WelcomeModule; }; + D7F72DEA27EA3574000C3064 /* Search */ = { + isa = XCSwiftPackageProductDependency; + productName = Search; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B658FB2427DA9E0F00EA4DBD /* Project object */; diff --git a/CodeEdit/Documents/String+Ranges.swift b/CodeEdit/Documents/String+Ranges.swift new file mode 100644 index 000000000..3da38912f --- /dev/null +++ b/CodeEdit/Documents/String+Ranges.swift @@ -0,0 +1,26 @@ +// +// String+Ranges.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/21. +// + +import Foundation + +extension StringProtocol where Index == String.Index { + func ranges( + of substring: T, + options: String.CompareOptions = [], + locale: Locale? = nil + ) -> [Range] { + var ranges: [Range] = [] + while let result = range( + of: substring, + options: options, + range: (ranges.last?.upperBound ?? startIndex)..() @@ -132,6 +134,7 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { ignoredFilesAndFolders: ignoredFilesAndDirectory ) directoryURL = url + self.searchState = .init(self) self.quickOpenState = .init(self) workspaceClient? .getFiles @@ -173,6 +176,68 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { } } +// MARK: - Search + +extension WorkspaceDocument { + class SearchState: ObservableObject { + var workspace: WorkspaceDocument + @Published var searchResult: [SearchResultModel] = [] + + init(_ workspace: WorkspaceDocument) { + self.workspace = workspace + } + + func search(_ text: String) { + self.searchResult = [] + if let url = self.workspace.fileURL { + let enumerator = FileManager.default.enumerator(at: url, + includingPropertiesForKeys: [ + .isRegularFileKey + ], + options: [ + .skipsHiddenFiles, + .skipsPackageDescendants + ]) + if let filePaths = enumerator?.allObjects as? [URL] { + filePaths.map { url in + WorkspaceClient.FileItem(url: url, children: nil) + }.forEach { fileItem in + var fileAddedFlag = true + do { + let data = try Data(contentsOf: fileItem.url) + data.withUnsafeBytes { + $0.split(separator: UInt8(ascii: "\n")) + .map { String(decoding: UnsafeRawBufferPointer(rebasing: $0), as: UTF8.self) } + }.enumerated().forEach { (index: Int, line: String) in + let noSpaceLine = line.trimmingCharacters(in: .whitespaces) + if noSpaceLine.contains(text) { + if fileAddedFlag { + searchResult.append(SearchResultModel( + file: fileItem, + lineNumber: nil, + lineContent: nil, + keywordRange: nil) + ) + fileAddedFlag = false + } + noSpaceLine.ranges(of: text).forEach { range in + searchResult.append(SearchResultModel( + file: fileItem, + lineNumber: index, + lineContent: noSpaceLine, + keywordRange: range) + ) + } + } + } + } catch {} + } + } + } + } + } +} + // MARK: - Quick Open extension WorkspaceDocument { diff --git a/CodeEdit/NavigatorSidebar/NavigatorSidebar.swift b/CodeEdit/NavigatorSidebar/NavigatorSidebar.swift index e7bc14c12..c62ab0876 100644 --- a/CodeEdit/NavigatorSidebar/NavigatorSidebar.swift +++ b/CodeEdit/NavigatorSidebar/NavigatorSidebar.swift @@ -14,20 +14,24 @@ struct NavigatorSidebar: View { @State private var selection: Int = 0 var body: some View { - List { + ZStack { switch selection { case 0: - Section(header: Text(workspace.fileURL?.lastPathComponent ?? "Unknown")) { - ForEach( - workspace.fileItems.sortItems(foldersOnTop: workspace.sortFoldersOnTop) - ) { item in // Instead of OutlineGroup - NavigatorSidebarItem( - item: item, - workspace: workspace, - windowController: windowController - ) + List { + Section(header: Text(workspace.fileURL?.lastPathComponent ?? "Unknown")) { + ForEach( + workspace.fileItems.sortItems(foldersOnTop: workspace.sortFoldersOnTop) + ) { item in // Instead of OutlineGroup + NavigatorSidebarItem( + item: item, + workspace: workspace, + windowController: windowController + ) + } } } + case 2: + SidebarSearch(state: workspace.searchState ?? .init(workspace)) default: EmptyView() } } diff --git a/CodeEdit/NavigatorSidebar/Search/SearchBar.swift b/CodeEdit/NavigatorSidebar/Search/SearchBar.swift new file mode 100644 index 000000000..cd0093876 --- /dev/null +++ b/CodeEdit/NavigatorSidebar/Search/SearchBar.swift @@ -0,0 +1,52 @@ +// +// SearchBar.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/21. +// + +import SwiftUI + +struct SearchBar: View { + @ObservedObject var state: WorkspaceDocument.SearchState + let title: String + @Binding var text: String + + var body: some View { + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(Color(nsColor: .secondaryLabelColor)) + textField + if !text.isEmpty { clearButton } + } + .padding(.horizontal, 5) + .padding(.vertical, 3) + .overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.gray, lineWidth: 0.5).cornerRadius(4)) + } + + private var textField: some View { + TextField(title, text: $text) + .disableAutocorrection(true) + .textFieldStyle(PlainTextFieldStyle()) + } + + private var clearButton: some View { + Button { + self.text = "" + state.search("") + } label: { + Image(systemName: "xmark.circle.fill") + } + .foregroundColor(.secondary) + .buttonStyle(PlainButtonStyle()) + } +} + +struct SearchBar_Previews: PreviewProvider { + static var previews: some View { + HStack { + SearchBar(state: .init(WorkspaceDocument.init()), title: "placeholder", text: .constant("value")) + } + .padding() + } +} diff --git a/CodeEdit/NavigatorSidebar/Search/SearchModeSelector.swift b/CodeEdit/NavigatorSidebar/Search/SearchModeSelector.swift new file mode 100644 index 000000000..f9654f3b3 --- /dev/null +++ b/CodeEdit/NavigatorSidebar/Search/SearchModeSelector.swift @@ -0,0 +1,120 @@ +// +// SearchModeSelector.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/21. +// + +import SwiftUI +import Search + +struct SearchModeSelector: View { + @State var selectedMode: [SearchModeModel] = [ + .Find, + .Text, + .Containing + ] + + private func getMenuList(_ index: Int) -> [SearchModeModel] { + return index == 0 ? SearchModeModel.SearchModes : selectedMode[index - 1].children + } + + // TODO: improve this function and remove swiftlint comment + // swiftlint:disable:next cyclomatic_complexity + private func onSelectMenuItem(_ index: Int, searchMode: SearchModeModel) { + var newSelectedMode: [SearchModeModel] = [] + switch index { + case 0: + newSelectedMode.append(searchMode) + if let secondMode = searchMode.children.first { + if let selectedSecondMode = selectedMode.second, searchMode.children.contains(selectedSecondMode) { + newSelectedMode.append(contentsOf: selectedMode.dropFirst()) + } else { + newSelectedMode.append(secondMode) + if let thirdMode = secondMode.children.first, let selectedThirdMode = selectedMode.third { + if secondMode.children.contains(selectedThirdMode) { + newSelectedMode.append(selectedThirdMode) + } else { + newSelectedMode.append(thirdMode) + } + } + } + } + self.selectedMode = newSelectedMode + case 1: + if let firstMode = selectedMode.first { + newSelectedMode.append(contentsOf: [firstMode, searchMode]) + if let thirdMode = searchMode.children.first { + if let selectedThirdMode = selectedMode.third, (searchMode.children.contains(selectedThirdMode)) { + newSelectedMode.append(selectedThirdMode) + } else { + newSelectedMode.append(thirdMode) + } + } + } + self.selectedMode = newSelectedMode + case 2: + if let firstMode = selectedMode.first, let secondMode = selectedMode.second { + newSelectedMode.append(contentsOf: [firstMode, secondMode, searchMode]) + } + self.selectedMode = newSelectedMode + default: + return + } + } + + private var chevron: some View { + Image(systemName: "chevron.compact.right") + .foregroundStyle(.secondary) + .imageScale(.large) + } + + var body: some View { + HStack(spacing: 0) { + ForEach(0.. 0 { + chevron + } + Text(selectedMode[index].title) + .foregroundColor(selectedMode[index].needSelectionHightlight ? Color.accentColor : .primary) + .font(.system(size: 10)) + } + } + .searchModeMenu() + } + Spacer() + } + } +} + +extension Array { + var second: Element? { + return self.count > 1 ? self[1] : nil + } + + var third: Element? { + return self.count > 2 ? self[2] : nil + } +} + +extension View { + func searchModeMenu() -> some View { + menuStyle(.borderlessButton) + .fixedSize() + .menuIndicator(.hidden) + } +} + +struct SearchModeSelector_Previews: PreviewProvider { + static var previews: some View { + SearchModeSelector() + } +} diff --git a/CodeEdit/NavigatorSidebar/Search/SearchResultFileItem.swift b/CodeEdit/NavigatorSidebar/Search/SearchResultFileItem.swift new file mode 100644 index 000000000..8a58cb651 --- /dev/null +++ b/CodeEdit/NavigatorSidebar/Search/SearchResultFileItem.swift @@ -0,0 +1,60 @@ +// +// SearchResultFileItem.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/22. +// + +import SwiftUI +import WorkspaceClient +import Search + +struct SearchResultFileItem: View { + @ObservedObject var state: WorkspaceDocument.SearchState + @State var isExpanded: Bool = true + var fileItem: WorkspaceClient.FileItem + var results: [SearchResultModel] + var jumpToFile: () -> Void + + private func foundLineResult(_ lineContent: String?, keywordRange: Range?) -> some View { + guard let lineContent = lineContent, let keywordRange = keywordRange else { + return AnyView(EmptyView()) + } + return AnyView( + Text(lineContent[lineContent.startIndex.. [SearchResultModel] { + return state.searchResult.filter {$0.file == file && $0.hasKeywordInfo} + } + + var body: some View { + List(selection: $selectedResult) { + ForEach(foundFiles, id: \.self) { (foundFile: SearchResultModel) in + SearchResultFileItem( + state: state, + fileItem: foundFile.file, results: getResultWith(foundFile.file)) { + state.workspace.openFile(item: foundFile.file) + } + } + }.onChange(of: selectedResult) { newValue in + if let file = newValue?.file { + state.workspace.openFile(item: file) + } + } + } +} diff --git a/CodeEdit/NavigatorSidebar/Search/SidebarSearch.swift b/CodeEdit/NavigatorSidebar/Search/SidebarSearch.swift new file mode 100644 index 000000000..7276a0951 --- /dev/null +++ b/CodeEdit/NavigatorSidebar/Search/SidebarSearch.swift @@ -0,0 +1,48 @@ +// +// SidebarSearch.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/20. +// + +import SwiftUI +import WorkspaceClient +import Search + +struct SidebarSearch: View { + @ObservedObject var state: WorkspaceDocument.SearchState + @State private var searchText: String = "" + + private var foundFilesCount: Int { + state.searchResult.filter {!$0.hasKeywordInfo}.count + } + + private var foundResultsCount: Int { + state.searchResult.filter {$0.hasKeywordInfo}.count + } + + var body: some View { + VStack { + VStack { + SearchModeSelector() + SearchBar(state: state, title: "", text: $searchText) + HStack { + Spacer() + } + } + .padding(.horizontal, 10) + .padding(.vertical, 5) + Divider() + HStack(alignment: .center) { + Text( + "\(foundResultsCount) results in \(foundFilesCount) files") + .font(.system(size: 10)) + } + Divider() + SearchResultList(state: state) + } + .onSubmit { + state.search(searchText) + } + } +} diff --git a/CodeEditModules/Modules/Search/src/Model/SearchModeModel.swift b/CodeEditModules/Modules/Search/src/Model/SearchModeModel.swift new file mode 100644 index 000000000..9c237ea96 --- /dev/null +++ b/CodeEditModules/Modules/Search/src/Model/SearchModeModel.swift @@ -0,0 +1,85 @@ +// +// SearchModeModel.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/22. +// + +import Foundation + +public struct SearchModeModel { + public let title: String + public let children: [SearchModeModel] + public let needSelectionHightlight: Bool + + public static let Containing = SearchModeModel(title: "Containing", children: [], needSelectionHightlight: false) + public static let MatchingWord = SearchModeModel(title: "Matching Word", + children: [], + needSelectionHightlight: true) + public static let StartingWith = SearchModeModel(title: "Starting With", + children: [], + needSelectionHightlight: true) + public static let EndingWith = SearchModeModel(title: "Ending With", children: [], needSelectionHightlight: true) + + public static let Text = SearchModeModel(title: "Text", + children: [.Containing, .MatchingWord, .StartingWith, .EndingWith], + needSelectionHightlight: false) + public static let References = SearchModeModel(title: "References", + children: [.Containing, .MatchingWord, .StartingWith, .EndingWith], + needSelectionHightlight: true) + public static let Definitions = SearchModeModel(title: "Definitions", + children: [.Containing, .MatchingWord, .StartingWith, .EndingWith], + needSelectionHightlight: true) + public static let RegularExpression = SearchModeModel(title: "Regular Expression", + children: [], + needSelectionHightlight: true) + public static let CallHierarchy = SearchModeModel(title: "Call Hierarchy", + children: [], + needSelectionHightlight: true) + + public static let Find = SearchModeModel(title: "Find", + children: [.Text, .References, .Definitions, .RegularExpression, .CallHierarchy], + needSelectionHightlight: false) + public static let Replace = SearchModeModel(title: "Replace", + children: [.Text, .RegularExpression], + needSelectionHightlight: true) + + public static let TextMatchingModes: [SearchModeModel] = [.Containing, .MatchingWord, .StartingWith, .EndingWith] + public static let FindModes: [SearchModeModel] = [.Text, + .References, + .Definitions, + .RegularExpression, + .CallHierarchy] + public static let ReplaceModes: [SearchModeModel] = [.Text, .RegularExpression] + public static let SearchModes: [SearchModeModel] = [.Find, .Replace] + + public static func getAllModes(_ index: Int, currentSelected: [SearchModeModel]) -> [SearchModeModel] { + switch index { + case 0: + return SearchModes + case 1: + if let searchMode = currentSelected.first { + if searchMode == SearchModeModel.Find { + return SearchModes + } else if searchMode == searchMode { + return ReplaceModes + } + } else { + return [] + } + case 2: + return TextMatchingModes + default: + return [] + } + return [] + } +} + +extension SearchModeModel: Equatable { + public static func == (lhs: SearchModeModel, rhs: SearchModeModel) -> Bool { + return lhs.title == rhs.title + && lhs.children == rhs.children + && lhs.needSelectionHightlight == rhs.needSelectionHightlight + } +} diff --git a/CodeEditModules/Modules/Search/src/Model/SearchResultModel.swift b/CodeEditModules/Modules/Search/src/Model/SearchResultModel.swift new file mode 100644 index 000000000..1a64b572f --- /dev/null +++ b/CodeEditModules/Modules/Search/src/Model/SearchResultModel.swift @@ -0,0 +1,32 @@ +// +// SearchResultModel.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/22. +// + +import Foundation +import WorkspaceClient + +public struct SearchResultModel: Hashable { + public var file: WorkspaceClient.FileItem + public var lineNumber: Int? + public var lineContent: String? + public var keywordRange: Range? + + public init( + file: WorkspaceClient.FileItem, + lineNumber: Int?, + lineContent: String?, + keywordRange: Range? + ) { + self.file = file + self.lineNumber = lineNumber + self.lineContent = lineContent + self.keywordRange = keywordRange + } + + public var hasKeywordInfo: Bool { + return lineNumber != nil && lineContent != nil && keywordRange != nil + } +} diff --git a/CodeEditModules/Package.swift b/CodeEditModules/Package.swift index 91d100d51..6e86e48f3 100644 --- a/CodeEditModules/Package.swift +++ b/CodeEditModules/Package.swift @@ -32,6 +32,10 @@ let package = Package( .library( name: "GitClient", targets: ["GitClient"] + ), + .library( + name: "Search", + targets: ["Search"] ) ], dependencies: [ @@ -101,7 +105,13 @@ let package = Package( .target( name: "GitClient", path: "Modules/GitClient/src" + ), + .target( + name: "Search", + dependencies: [ + "WorkspaceClient" + ], + path: "Modules/Search/src" ) - ] )