gitbay/Views/Repos/SymbolSearchView.swift

v1.13.0
gitbay-ios/gitbay/Views/Repos/SymbolSearchView.swift history · blame · raw

84 lines · 3411 bytes

 1import SwiftUI
 2
 3/// Where a name is defined, from the repository's symbol index. A result
 4/// opens the file at its line on the default branch, the one indexed.
 5struct SymbolSearchView: View {
 6
 7    @State private var model: SymbolSearchViewModel
 8    @State private var query: String
 9    private let initialQuery: String?
10
11    init(client: GitbayClient, repo: String, query: String? = nil) {
12        _model = State(initialValue: SymbolSearchViewModel(client: client, repoPath: repo))
13        _query = State(initialValue: query ?? "")
14        initialQuery = query
15    }
16
17    var body: some View {
18        List {
19            ForEach(model.state?.value ?? []) { symbol in
20                NavigationLink(value: RepoRoute.file(
21                    repo: model.repoPath, path: symbol.path, ref: nil, line: symbol.line
22                )) {
23                    VStack(alignment: .leading, spacing: 3) {
24                        HStack(spacing: 6) {
25                            Text(symbol.name)
26                                .font(.gbMono(.subheadline).weight(.medium))
27                                .lineLimit(1)
28                            GBChip(symbol.kind, .secondary)
29                        }
30                        Text("\(symbol.path):\(symbol.line)")
31                            .font(.gbMono(.caption))
32                            .foregroundStyle(.secondary)
33                            .lineLimit(1)
34                            .truncationMode(.head)
35                    }
36                    .accessibilityElement(children: .combine)
37                }
38                .accessibilityIdentifier("symbol-\(symbol.name)")
39            }
40        }
41        .overlay {
42            if let state = model.state {
43                LoadStateOverlay(state: state)
44            } else {
45                ContentUnavailableView {
46                    Label("Find a definition", systemImage: "function")
47                } description: {
48                    Text("A name or its start, on the default branch.")
49                }
50            }
51        }
52        .searchable(text: $query, prompt: "Find a definition")
53        .onSubmit(of: .search) {
54            Task { await model.search(query) }
55        }
56        .toolbar {
57            ToolbarItem(placement: .topBarTrailing) {
58                Menu {
59                    Picker("Kind", selection: Binding(get: { model.kind }, set: { model.kind = $0 })) {
60                        Text("Any kind").tag(String?.none)
61                        ForEach(SymbolSearchViewModel.kinds, id: \.self) { kind in
62                            Text(kind).tag(String?.some(kind))
63                        }
64                    }
65                } label: {
66                    Image(systemName: model.kind == nil
67                          ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill")
68                }
69                .accessibilityLabel(model.kind.map { "Kind: \($0)" } ?? "Kind: any")
70                .accessibilityIdentifier("symbols-kind")
71            }
72        }
73        .onChange(of: model.kind) {
74            if !query.trimmingCharacters(in: .whitespaces).isEmpty {
75                Task { await model.search(query) }
76            }
77        }
78        .task {
79            if let initialQuery, model.state == nil { await model.search(initialQuery) }
80        }
81        .navigationTitle("Definitions")
82        .navigationBarTitleDisplayMode(.inline)
83    }
84}