Skip to content

Commit fffef88

Browse files
authored
Merge pull request #125 from RayZhao1998/feature/workspace-search
Finding in workspace
2 parents a3583c3 + 5ca9773 commit fffef88

File tree

12 files changed

+594
-11
lines changed

12 files changed

+594
-11
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,19 @@
4242
B673FDAD27E8296A00795864 /* PressActionsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B673FDAC27E8296A00795864 /* PressActionsModifier.swift */; };
4343
B6EE989027E8879A00CDD8AB /* InspectorSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EE988F27E8879A00CDD8AB /* InspectorSidebar.swift */; };
4444
B6EE989227E887C600CDD8AB /* InspectorSidebarToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EE989127E887C600CDD8AB /* InspectorSidebarToolbar.swift */; };
45+
D7012EE827E757850001E1EF /* SidebarSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7012EE727E757850001E1EF /* SidebarSearch.swift */; };
4546
D70F5E2C27E4E8CF004EE4B9 /* WelcomeModule in Frameworks */ = {isa = PBXBuildFile; productRef = D70F5E2B27E4E8CF004EE4B9 /* WelcomeModule */; };
4647
D7211D4327E066CE008F2ED7 /* Localized+Ex.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7211D4227E066CE008F2ED7 /* Localized+Ex.swift */; };
4748
D7211D4727E06BFE008F2ED7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D7211D4927E06BFE008F2ED7 /* Localizable.strings */; };
4849
D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */; };
4950
D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */; };
5051
D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */; };
52+
D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */; };
53+
D7E201B027E8C07300CB86D0 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AF27E8C07300CB86D0 /* SearchBar.swift */; };
54+
D7E201B227E8D50000CB86D0 /* SearchModeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B127E8D50000CB86D0 /* SearchModeSelector.swift */; };
55+
D7E201B427E9989900CB86D0 /* SearchResultList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B327E9989900CB86D0 /* SearchResultList.swift */; };
56+
D7E201BD27EA00E200CB86D0 /* SearchResultFileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201BC27EA00E200CB86D0 /* SearchResultFileItem.swift */; };
57+
D7F72DEB27EA3574000C3064 /* Search in Frameworks */ = {isa = PBXBuildFile; productRef = D7F72DEA27EA3574000C3064 /* Search */; };
5158
/* End PBXBuildFile section */
5259

5360
/* Begin PBXContainerItemProxy section */
@@ -112,12 +119,18 @@
112119
B673FDAC27E8296A00795864 /* PressActionsModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PressActionsModifier.swift; sourceTree = "<group>"; };
113120
B6EE988F27E8879A00CDD8AB /* InspectorSidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorSidebar.swift; sourceTree = "<group>"; };
114121
B6EE989127E887C600CDD8AB /* InspectorSidebarToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorSidebarToolbar.swift; sourceTree = "<group>"; };
122+
D7012EE727E757850001E1EF /* SidebarSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarSearch.swift; sourceTree = "<group>"; };
115123
D7211D4227E066CE008F2ED7 /* Localized+Ex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Localized+Ex.swift"; sourceTree = "<group>"; };
116124
D7211D4827E06BFE008F2ED7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
117125
D7211D4A27E06C01008F2ED7 /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sr; path = sr.lproj/Localizable.strings; sourceTree = "<group>"; };
118126
D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
119127
D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentProjectsView.swift; sourceTree = "<group>"; };
120128
D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeWindowView.swift; sourceTree = "<group>"; };
129+
D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Ranges.swift"; sourceTree = "<group>"; };
130+
D7E201AF27E8C07300CB86D0 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
131+
D7E201B127E8D50000CB86D0 /* SearchModeSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModeSelector.swift; sourceTree = "<group>"; };
132+
D7E201B327E9989900CB86D0 /* SearchResultList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultList.swift; sourceTree = "<group>"; };
133+
D7E201BC27EA00E200CB86D0 /* SearchResultFileItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultFileItem.swift; sourceTree = "<group>"; };
121134
/* End PBXFileReference section */
122135

123136
/* Begin PBXFrameworksBuildPhase section */
@@ -129,6 +142,7 @@
129142
0485EB2527E7B9C800138301 /* Overlays in Frameworks */,
130143
B65E614627E6765D00255275 /* Introspect in Frameworks */,
131144
D70F5E2C27E4E8CF004EE4B9 /* WelcomeModule in Frameworks */,
145+
D7F72DEB27EA3574000C3064 /* Search in Frameworks */,
132146
5CFA753B27E896B60002F01B /* GitClient in Frameworks */,
133147
28CE5EA027E6493D0065D29C /* StatusBar in Frameworks */,
134148
5CF38A5E27E48E6C0096A0F7 /* CodeFile in Frameworks */,
@@ -159,6 +173,7 @@
159173
043C321527E3201F006AE443 /* WorkspaceDocument.swift */,
160174
04660F6927E51E5C00477777 /* CodeEditWindowController.swift */,
161175
0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */,
176+
D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */,
162177
);
163178
path = Documents;
164179
sourceTree = "<group>";
@@ -205,6 +220,7 @@
205220
287776EA27E350A100D46668 /* NavigatorSidebar */ = {
206221
isa = PBXGroup;
207222
children = (
223+
D7012EE627E757660001E1EF /* Search */,
208224
287776E627E3413200D46668 /* NavigatorSidebar.swift */,
209225
287776EC27E350D800D46668 /* NavigatorSidebarItem.swift */,
210226
28B0A19727E385C300B73177 /* NavigatorSidebarToolbarTop.swift */,
@@ -298,6 +314,18 @@
298314
path = InspectorSidebar;
299315
sourceTree = "<group>";
300316
};
317+
D7012EE627E757660001E1EF /* Search */ = {
318+
isa = PBXGroup;
319+
children = (
320+
D7012EE727E757850001E1EF /* SidebarSearch.swift */,
321+
D7E201AF27E8C07300CB86D0 /* SearchBar.swift */,
322+
D7E201B127E8D50000CB86D0 /* SearchModeSelector.swift */,
323+
D7E201B327E9989900CB86D0 /* SearchResultList.swift */,
324+
D7E201BC27EA00E200CB86D0 /* SearchResultFileItem.swift */,
325+
);
326+
path = Search;
327+
sourceTree = "<group>";
328+
};
301329
D7211D4427E066D4008F2ED7 /* Localization */ = {
302330
isa = PBXGroup;
303331
children = (
@@ -343,6 +371,7 @@
343371
28CE5E9F27E6493D0065D29C /* StatusBar */,
344372
0485EB2427E7B9C800138301 /* Overlays */,
345373
5CFA753A27E896B60002F01B /* GitClient */,
374+
D7F72DEA27EA3574000C3064 /* Search */,
346375
);
347376
productName = CodeEdit;
348377
productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */;
@@ -516,7 +545,10 @@
516545
2B7A583527E4BA0100D25D4E /* AppDelegate.swift in Sources */,
517546
2875A46D27E3BE5B007805F8 /* BreadcrumbsView.swift in Sources */,
518547
0485EB1D27E7338100138301 /* QuickOpenItem.swift in Sources */,
548+
D7012EE827E757850001E1EF /* SidebarSearch.swift in Sources */,
549+
D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */,
519550
04540D5B27DD08C300E91B77 /* SettingsView.swift in Sources */,
551+
D7E201B027E8C07300CB86D0 /* SearchBar.swift in Sources */,
520552
D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */,
521553
04540D5C27DD08C300E91B77 /* GeneralSettingsView.swift in Sources */,
522554
B6EE989227E887C600CDD8AB /* InspectorSidebarToolbar.swift in Sources */,
@@ -526,16 +558,22 @@
526558
04540D5E27DD08C300E91B77 /* WorkspaceView.swift in Sources */,
527559
34EE19BE27E0469C00F152CE /* BlurView.swift in Sources */,
528560
D7211D4327E066CE008F2ED7 /* Localized+Ex.swift in Sources */,
561+
D7E201B227E8D50000CB86D0 /* SearchModeSelector.swift in Sources */,
529562
287776E927E34BC700D46668 /* TabBar.swift in Sources */,
530563
0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */,
531564
0485EB1927E70F4900138301 /* QuickOpenView.swift in Sources */,
565+
D7E201BD27EA00E200CB86D0 /* SearchResultFileItem.swift in Sources */,
532566
286620A527E4AB6900E18C2B /* BreadcrumbsComponent.swift in Sources */,
533567
287776EF27E3515300D46668 /* TabBarItem.swift in Sources */,
534568
D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */,
535569
04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */,
536570
287776E727E3413200D46668 /* NavigatorSidebar.swift in Sources */,
537571
287776ED27E350D800D46668 /* NavigatorSidebarItem.swift in Sources */,
538572
B6EE989027E8879A00CDD8AB /* InspectorSidebar.swift in Sources */,
573+
D7E201B427E9989900CB86D0 /* SearchResultList.swift in Sources */,
574+
D7E201B427E9989900CB86D0 /* SearchResultList.swift in Sources */,
575+
287776E727E3413200D46668 /* NavigatorSidebar.swift in Sources */,
576+
287776ED27E350D800D46668 /* NavigatorSidebarItem.swift in Sources */,
539577
289978ED27E4E97E00BB0357 /* FileIconStyle.swift in Sources */,
540578
04660F6627E3ACEF00477777 /* ReopenBehavior.swift in Sources */,
541579
D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */,
@@ -955,6 +993,10 @@
955993
isa = XCSwiftPackageProductDependency;
956994
productName = WelcomeModule;
957995
};
996+
D7F72DEA27EA3574000C3064 /* Search */ = {
997+
isa = XCSwiftPackageProductDependency;
998+
productName = Search;
999+
};
9581000
/* End XCSwiftPackageProductDependency section */
9591001
};
9601002
rootObject = B658FB2427DA9E0F00EA4DBD /* Project object */;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// String+Ranges.swift
3+
// CodeEdit
4+
//
5+
// Created by Ziyuan Zhao on 2022/3/21.
6+
//
7+
8+
import Foundation
9+
10+
extension StringProtocol where Index == String.Index {
11+
func ranges<T: StringProtocol>(
12+
of substring: T,
13+
options: String.CompareOptions = [],
14+
locale: Locale? = nil
15+
) -> [Range<Index>] {
16+
var ranges: [Range<Index>] = []
17+
while let result = range(
18+
of: substring,
19+
options: options,
20+
range: (ranges.last?.upperBound ?? startIndex)..<endIndex,
21+
locale: locale) {
22+
ranges.append(result)
23+
}
24+
return ranges
25+
}
26+
}

CodeEdit/Documents/WorkspaceDocument.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import SwiftUI
1111
import WorkspaceClient
1212
import Combine
1313
import CodeFile
14+
import Search
1415

1516
@objc(WorkspaceDocument)
1617
class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
@@ -26,6 +27,7 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
2627
guard let selectedId = selectedId else { return nil }
2728
return fileItems.first(where: { $0.id == selectedId })
2829
}
30+
var searchState: SearchState?
2931
var quickOpenState: QuickOpenState?
3032
var openedCodeFiles: [WorkspaceClient.FileItem: CodeFileDocument] = [:]
3133
private var cancellables = Set<AnyCancellable>()
@@ -132,6 +134,7 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
132134
ignoredFilesAndFolders: ignoredFilesAndDirectory
133135
)
134136
directoryURL = url
137+
self.searchState = .init(self)
135138
self.quickOpenState = .init(self)
136139
workspaceClient?
137140
.getFiles
@@ -173,6 +176,68 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
173176
}
174177
}
175178

179+
// MARK: - Search
180+
181+
extension WorkspaceDocument {
182+
class SearchState: ObservableObject {
183+
var workspace: WorkspaceDocument
184+
@Published var searchResult: [SearchResultModel] = []
185+
186+
init(_ workspace: WorkspaceDocument) {
187+
self.workspace = workspace
188+
}
189+
190+
func search(_ text: String) {
191+
self.searchResult = []
192+
if let url = self.workspace.fileURL {
193+
let enumerator = FileManager.default.enumerator(at: url,
194+
includingPropertiesForKeys: [
195+
.isRegularFileKey
196+
],
197+
options: [
198+
.skipsHiddenFiles,
199+
.skipsPackageDescendants
200+
])
201+
if let filePaths = enumerator?.allObjects as? [URL] {
202+
filePaths.map { url in
203+
WorkspaceClient.FileItem(url: url, children: nil)
204+
}.forEach { fileItem in
205+
var fileAddedFlag = true
206+
do {
207+
let data = try Data(contentsOf: fileItem.url)
208+
data.withUnsafeBytes {
209+
$0.split(separator: UInt8(ascii: "\n"))
210+
.map { String(decoding: UnsafeRawBufferPointer(rebasing: $0), as: UTF8.self) }
211+
}.enumerated().forEach { (index: Int, line: String) in
212+
let noSpaceLine = line.trimmingCharacters(in: .whitespaces)
213+
if noSpaceLine.contains(text) {
214+
if fileAddedFlag {
215+
searchResult.append(SearchResultModel(
216+
file: fileItem,
217+
lineNumber: nil,
218+
lineContent: nil,
219+
keywordRange: nil)
220+
)
221+
fileAddedFlag = false
222+
}
223+
noSpaceLine.ranges(of: text).forEach { range in
224+
searchResult.append(SearchResultModel(
225+
file: fileItem,
226+
lineNumber: index,
227+
lineContent: noSpaceLine,
228+
keywordRange: range)
229+
)
230+
}
231+
}
232+
}
233+
} catch {}
234+
}
235+
}
236+
}
237+
}
238+
}
239+
}
240+
176241
// MARK: - Quick Open
177242

178243
extension WorkspaceDocument {

CodeEdit/NavigatorSidebar/NavigatorSidebar.swift

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,24 @@ struct NavigatorSidebar: View {
1414
@State private var selection: Int = 0
1515

1616
var body: some View {
17-
List {
17+
ZStack {
1818
switch selection {
1919
case 0:
20-
Section(header: Text(workspace.fileURL?.lastPathComponent ?? "Unknown")) {
21-
ForEach(
22-
workspace.fileItems.sortItems(foldersOnTop: workspace.sortFoldersOnTop)
23-
) { item in // Instead of OutlineGroup
24-
NavigatorSidebarItem(
25-
item: item,
26-
workspace: workspace,
27-
windowController: windowController
28-
)
20+
List {
21+
Section(header: Text(workspace.fileURL?.lastPathComponent ?? "Unknown")) {
22+
ForEach(
23+
workspace.fileItems.sortItems(foldersOnTop: workspace.sortFoldersOnTop)
24+
) { item in // Instead of OutlineGroup
25+
NavigatorSidebarItem(
26+
item: item,
27+
workspace: workspace,
28+
windowController: windowController
29+
)
30+
}
2931
}
3032
}
33+
case 2:
34+
SidebarSearch(state: workspace.searchState ?? .init(workspace))
3135
default: EmptyView()
3236
}
3337
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//
2+
// SearchBar.swift
3+
// CodeEdit
4+
//
5+
// Created by Ziyuan Zhao on 2022/3/21.
6+
//
7+
8+
import SwiftUI
9+
10+
struct SearchBar: View {
11+
@ObservedObject var state: WorkspaceDocument.SearchState
12+
let title: String
13+
@Binding var text: String
14+
15+
var body: some View {
16+
HStack {
17+
Image(systemName: "magnifyingglass")
18+
.foregroundColor(Color(nsColor: .secondaryLabelColor))
19+
textField
20+
if !text.isEmpty { clearButton }
21+
}
22+
.padding(.horizontal, 5)
23+
.padding(.vertical, 3)
24+
.overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.gray, lineWidth: 0.5).cornerRadius(4))
25+
}
26+
27+
private var textField: some View {
28+
TextField(title, text: $text)
29+
.disableAutocorrection(true)
30+
.textFieldStyle(PlainTextFieldStyle())
31+
}
32+
33+
private var clearButton: some View {
34+
Button {
35+
self.text = ""
36+
state.search("")
37+
} label: {
38+
Image(systemName: "xmark.circle.fill")
39+
}
40+
.foregroundColor(.secondary)
41+
.buttonStyle(PlainButtonStyle())
42+
}
43+
}
44+
45+
struct SearchBar_Previews: PreviewProvider {
46+
static var previews: some View {
47+
HStack {
48+
SearchBar(state: .init(WorkspaceDocument.init()), title: "placeholder", text: .constant("value"))
49+
}
50+
.padding()
51+
}
52+
}

0 commit comments

Comments
 (0)