Skip to content

Commit f38bc67

Browse files
Add Indexing Progress, Index Off Main Thread (#1501)
* Add Indexing Progress, Index Off Main Thread * Add tooltip, increase remove animation delay. * Update CodeEdit/Features/Documents/WorkspaceDocument+Search.swift Co-authored-by: Tom Ludwig <[email protected]> * Fix Indentation, Sep Of Concerns * Finish Merge From Main --------- Co-authored-by: Tom Ludwig <[email protected]>
1 parent 65bef99 commit f38bc67

File tree

7 files changed

+167
-28
lines changed

7 files changed

+167
-28
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@
286286
6C14CEB028777D3C001468FE /* FindNavigatorListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */; };
287287
6C14CEB32877A68F001468FE /* FindNavigatorMatchListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */; };
288288
6C18620A298BF5A800C663EA /* RecentProjectsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C186209298BF5A800C663EA /* RecentProjectsListView.swift */; };
289+
6C1CC9982B1E770B0002349B /* AsyncFileIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1CC9972B1E770B0002349B /* AsyncFileIterator.swift */; };
290+
6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1CC99A2B1E7CBC0002349B /* FindNavigatorIndexBar.swift */; };
289291
6C2149412A1BB9AB00748382 /* LogStream in Frameworks */ = {isa = PBXBuildFile; productRef = 6C2149402A1BB9AB00748382 /* LogStream */; };
290292
6C2C155829B4F49100EA60A5 /* SplitViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C155729B4F49100EA60A5 /* SplitViewItem.swift */; };
291293
6C2C155A29B4F4CC00EA60A5 /* Variadic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C155929B4F4CC00EA60A5 /* Variadic.swift */; };
@@ -803,6 +805,8 @@
803805
6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorListViewController.swift; sourceTree = "<group>"; };
804806
6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorMatchListCell.swift; sourceTree = "<group>"; };
805807
6C186209298BF5A800C663EA /* RecentProjectsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentProjectsListView.swift; sourceTree = "<group>"; };
808+
6C1CC9972B1E770B0002349B /* AsyncFileIterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncFileIterator.swift; sourceTree = "<group>"; };
809+
6C1CC99A2B1E7CBC0002349B /* FindNavigatorIndexBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorIndexBar.swift; sourceTree = "<group>"; };
806810
6C2C155729B4F49100EA60A5 /* SplitViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewItem.swift; sourceTree = "<group>"; };
807811
6C2C155929B4F4CC00EA60A5 /* Variadic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Variadic.swift; sourceTree = "<group>"; };
808812
6C2C155C29B4F4E500EA60A5 /* SplitViewReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewReader.swift; sourceTree = "<group>"; };
@@ -2187,6 +2191,7 @@
21872191
611191F82B08CC8000D4459B /* Indexer */ = {
21882192
isa = PBXGroup;
21892193
children = (
2194+
6C1CC9972B1E770B0002349B /* AsyncFileIterator.swift */,
21902195
611191F92B08CC9000D4459B /* SearchIndexer.swift */,
21912196
611191FB2B08CCB800D4459B /* SearchIndexer+AsyncController.swift */,
21922197
611191FF2B08CCD700D4459B /* SearchIndexer+Memory.swift */,
@@ -2776,6 +2781,7 @@
27762781
isa = PBXGroup;
27772782
children = (
27782783
D7012EE727E757850001E1EF /* FindNavigatorView.swift */,
2784+
6C1CC99A2B1E7CBC0002349B /* FindNavigatorIndexBar.swift */,
27792785
D7E201B127E8D50000CB86D0 /* FindNavigatorForm.swift */,
27802786
6C14CEB12877A5BE001468FE /* FindNavigatorResultList */,
27812787
B67DB0F52AFC2A7A002DC647 /* FindNavigatorToolbarBottom.swift */,
@@ -3290,6 +3296,7 @@
32903296
B6F0517029D9E36800D72287 /* LocationsSettingsView.swift in Sources */,
32913297
B62AEDDC2A27C1B3009A9F52 /* OSLogType+Color.swift in Sources */,
32923298
587B9E6329301D8F00AC7927 /* GitLabAccount.swift in Sources */,
3299+
6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */,
32933300
285FEC7027FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift in Sources */,
32943301
6CB9144B29BEC7F100BC47F2 /* (null) in Sources */,
32953302
587B9E7429301D8F00AC7927 /* URL+URLParameters.swift in Sources */,
@@ -3444,6 +3451,7 @@
34443451
04BA7C242AE2E7CD00584E1C /* SourceControlNavigatorSyncView.swift in Sources */,
34453452
587B9DA529300ABD00AC7927 /* PressActionsModifier.swift in Sources */,
34463453
6C147C4029A328BC0089B630 /* SplitViewData.swift in Sources */,
3454+
6C1CC9982B1E770B0002349B /* AsyncFileIterator.swift in Sources */,
34473455
587B9E9029301D8F00AC7927 /* BitBucketTokenRouter.swift in Sources */,
34483456
B6C6A42E29771A8D00A3D28F /* EditorTabButtonStyle.swift in Sources */,
34493457
58822525292C280D00E83CDE /* StatusBarMenuStyle.swift in Sources */,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// AsyncFileIterator.swift
3+
// CodeEdit
4+
//
5+
// Created by Khan Winter on 12/4/23.
6+
//
7+
8+
import Foundation
9+
10+
/// Given a list of file URLs, asynchronously fetches their contents and returns them iteratively.
11+
/// Returns files as a ``SearchIndexer/AsyncManager/TextFile`` struct, used to index workspaces.
12+
struct AsyncFileIterator: AsyncSequence, AsyncIteratorProtocol {
13+
typealias TextFile = SearchIndexer.AsyncManager.TextFile
14+
typealias Element = (TextFile, Int)
15+
16+
let fileURLs: [URL]
17+
var currentIdx = 0
18+
19+
mutating func next() async -> Element? {
20+
guard !Task.isCancelled else {
21+
return nil
22+
}
23+
24+
defer {
25+
currentIdx += 1
26+
}
27+
28+
// Loop until we either find a loadable file or run out of URLs
29+
var foundContent: TextFile?
30+
while foundContent == nil {
31+
guard currentIdx < fileURLs.count else {
32+
return nil
33+
}
34+
35+
let fileURL = fileURLs[currentIdx]
36+
if let content = try? String(contentsOf: fileURL) {
37+
foundContent = TextFile(url: fileURL.standardizedFileURL, text: content)
38+
} else {
39+
currentIdx += 1
40+
}
41+
}
42+
return (foundContent!, currentIdx)
43+
}
44+
45+
func makeAsyncIterator() -> AsyncFileIterator {
46+
self
47+
}
48+
}

CodeEdit/Features/Documents/Indexer/SearchIndexer+AsyncController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ extension SearchIndexer {
4444

4545
return AsyncStream { configuration in
4646
var moreResultsAvailable = true
47-
while moreResultsAvailable {
47+
while moreResultsAvailable && !Task.isCancelled {
4848
let results = search.getNextSearchResultsChunk(limit: maxResults, timeout: timeout)
4949
moreResultsAvailable = results.moreResultsAvailable
5050
configuration.yield(results)

CodeEdit/Features/Documents/WorkspaceDocument+Search.swift

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,18 @@ import Foundation
99

1010
extension WorkspaceDocument {
1111
final class SearchState: ObservableObject {
12+
enum IndexStatus {
13+
case none
14+
case indexing(progress: Double)
15+
case done
16+
}
17+
1218
@Published var searchResult: [SearchResultModel] = []
1319
@Published var searchResultsFileCount: Int = 0
1420
@Published var searchResultsCount: Int = 0
1521

22+
@Published var indexStatus: IndexStatus = .none
23+
1624
unowned var workspace: WorkspaceDocument
1725
var tempSearchResults = [SearchResultModel]()
1826
var ignoreCase: Bool = true
@@ -29,7 +37,7 @@ extension WorkspaceDocument {
2937
addProjectToIndex()
3038
}
3139

32-
/// Adds the contents of the current worksapce URL to the search index.
40+
/// Adds the contents of the current workspace URL to the search index.
3341
/// That means that the contents of the workspace will be indexed and searchable.
3442
func addProjectToIndex() {
3543
guard let indexer = indexer else {
@@ -40,11 +48,30 @@ extension WorkspaceDocument {
4048
return
4149
}
4250

43-
let filePaths = getFileURLs(at: url)
44-
Task {
45-
let textFiles = await getFileContents(from: filePaths)
51+
indexStatus = .indexing(progress: 0.0)
52+
53+
Task.detached {
54+
let filePaths = self.getFileURLs(at: url)
4655
let asyncController = SearchIndexer.AsyncManager(index: indexer)
47-
_ = await asyncController.addText(files: textFiles, flushWhenComplete: true)
56+
var lastProgress: Double = 0
57+
58+
for await (file, index) in AsyncFileIterator(fileURLs: filePaths) {
59+
_ = await asyncController.addText(files: [file], flushWhenComplete: false)
60+
let progress = Double(index) / Double(filePaths.count)
61+
62+
// Send only if difference is > 0.5%, to keep updates from sending too frequently
63+
if progress - lastProgress > 0.005 || index == filePaths.count - 1 {
64+
lastProgress = progress
65+
await MainActor.run {
66+
self.indexStatus = .indexing(progress: progress)
67+
}
68+
}
69+
}
70+
asyncController.index.flush()
71+
72+
await MainActor.run {
73+
self.indexStatus = .done
74+
}
4875
}
4976
}
5077

@@ -62,23 +89,6 @@ extension WorkspaceDocument {
6289
return enumerator?.allObjects as? [URL] ?? []
6390
}
6491

65-
/// Retrieves the contents of a files from the specified file paths.
66-
///
67-
/// - Parameter filePaths: An array of file URLs representing the paths of the files.
68-
///
69-
/// - Returns: An array of `TextFile` objects containing the standardized file URLs and text content.
70-
func getFileContents(from filePaths: [URL]) async -> [SearchIndexer.AsyncManager.TextFile] {
71-
var textFiles = [SearchIndexer.AsyncManager.TextFile]()
72-
for file in filePaths {
73-
if let content = try? String(contentsOf: file) {
74-
textFiles.append(
75-
SearchIndexer.AsyncManager.TextFile(url: file.standardizedFileURL, text: content)
76-
)
77-
}
78-
}
79-
return textFiles
80-
}
81-
8292
/// Creates a search term based on the given query and search mode.
8393
///
8494
/// - Parameter query: The original user query string.
@@ -115,6 +125,7 @@ extension WorkspaceDocument {
115125
///
116126
/// - Parameter query: The search query to search for.
117127
func search(_ query: String) async {
128+
clearResults()
118129
let searchQuery = getSearchTerm(query)
119130
guard let indexer = indexer else {
120131
return
@@ -151,7 +162,7 @@ extension WorkspaceDocument {
151162
}
152163

153164
evaluateResultGroup.notify(queue: evaluateSearchQueue) {
154-
self.setSearchResults()
165+
self.setSearchResults()
155166
}
156167
}
157168

@@ -179,7 +190,7 @@ extension WorkspaceDocument {
179190
}
180191
}
181192

182-
/// Addes line matchings to a `SearchResultsViewModel` array.
193+
/// Adds line matchings to a `SearchResultsViewModel` array.
183194
/// That means if a search result is a file, and the search term appears in the file,
184195
/// the function will add the line number, line content, and keyword range to the `SearchResultsViewModel`.
185196
///

CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorForm.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,6 @@ struct FindNavigatorForm: View {
265265
}
266266
}
267267
}
268-
.padding(.horizontal, 10)
269-
.padding(.vertical, 5)
270268
.lineLimit(1...5)
271269
}
272270
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// FindNavigatorIndexBar.swift
3+
// CodeEdit
4+
//
5+
// Created by Khan Winter on 12/4/23.
6+
//
7+
8+
import SwiftUI
9+
10+
struct FindNavigatorIndexBar: View {
11+
@ObservedObject private var state: WorkspaceDocument.SearchState
12+
@State private var progress: Double = 0.0
13+
@State private var shouldShow: Bool = false
14+
15+
init(state: WorkspaceDocument.SearchState) {
16+
self.state = state
17+
}
18+
19+
var body: some View {
20+
Group {
21+
if shouldShow {
22+
HStack(alignment: .center) {
23+
ProgressView(value: progress, total: 1.0) {
24+
EmptyView()
25+
} currentValueLabel: {
26+
HStack {
27+
Text("Indexing \(Int(progress * 100))%")
28+
.font(.system(size: 10))
29+
.animation(.none)
30+
}
31+
}
32+
// swiftlint:disable:next line_length
33+
.help("Indexing current workspace files for search. Searches performed while indexing may return incomplete results.")
34+
}
35+
.transition(.asymmetric(insertion: .identity, removal: .move(edge: .top).combined(with: .opacity)))
36+
}
37+
}
38+
.onAppear {
39+
updateWithNewStatus(state.indexStatus)
40+
}
41+
.onReceive(state.$indexStatus) { newStatus in
42+
updateWithNewStatus(newStatus)
43+
}
44+
}
45+
46+
/// Updates the bar with a new status update.
47+
/// - Parameter status: The new status.
48+
private func updateWithNewStatus(_ status: WorkspaceDocument.SearchState.IndexStatus) {
49+
switch status {
50+
case .none:
51+
self.progress = 0.0
52+
shouldShow = false
53+
case .indexing(let progress):
54+
if shouldShow {
55+
withAnimation {
56+
self.progress = progress
57+
}
58+
} else {
59+
shouldShow = true
60+
self.progress = progress
61+
}
62+
case .done:
63+
self.progress = 1.0
64+
withAnimation(.default.delay(0.75)) {
65+
shouldShow = false
66+
}
67+
}
68+
}
69+
}

CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ struct FindNavigatorView: View {
2525

2626
var body: some View {
2727
VStack {
28-
FindNavigatorForm(state: state)
28+
VStack {
29+
FindNavigatorForm(state: state)
30+
FindNavigatorIndexBar(state: state)
31+
}
32+
.padding(.horizontal, 10)
33+
.padding(.vertical, 5)
2934
Divider()
3035
HStack(alignment: .center) {
3136
Text("\(self.searchResultCount) results in \(self.foundFilesCount) files")

0 commit comments

Comments
 (0)