a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Discovery/GrepViewModel.swift

57 lines · 1738 bytes

 1import Foundation
 2import Observation
 3
 4/// `repo grep <owner/name> <query>`  server-side git grep, literal and
 5/// case-insensitive.
 6@Observable
 7@MainActor
 8final class GrepViewModel {
 9
10    nonisolated struct Match: Decodable, Sendable, Hashable, Identifiable {
11        let path: String
12        let line: Int
13        let text: String
14        var id: String { "\(path):\(line)" }
15    }
16
17    /// nil until the first search; distinct from an empty result.
18    private(set) var state: LoadState<[Match]>?
19    private(set) var lastQuery = ""
20
21    private let client: GitbayClient
22    let repoPath: String
23
24    init(client: GitbayClient, repoPath: String) {
25        self.client = client
26        self.repoPath = repoPath
27    }
28
29    /// Matches grouped by file, in server order.
30    var byFile: [(file: String, matches: [Match])] {
31        guard let matches = state?.value else { return [] }
32        var order: [String] = []
33        var groups: [String: [Match]] = [:]
34        for match in matches {
35            if groups[match.path] == nil { order.append(match.path) }
36            groups[match.path, default: []].append(match)
37        }
38        return order.map { ($0, groups[$0]!) }
39    }
40
41    func search(_ query: String) async {
42        let trimmed = query.trimmingCharacters(in: .whitespaces)
43        guard !trimmed.isEmpty else { return }
44        lastQuery = trimmed
45        state = .loading
46        do {
47            let matches = try await client.readList(
48                ["repo", "grep", repoPath, trimmed], of: Match.self
49            )
50            state = matches.isEmpty
51                ? .empty("No matches for \"\(trimmed)\".")
52                : .loaded(matches)
53        } catch {
54            state = .from(error)
55        }
56    }
57}