import SwiftUI /// Where a name is defined, from the repository's symbol index. A result /// opens the file at its line on the default branch, the one indexed. struct SymbolSearchView: View { @State private var model: SymbolSearchViewModel @State private var query: String private let initialQuery: String? init(client: GitbayClient, repo: String, query: String? = nil) { _model = State(initialValue: SymbolSearchViewModel(client: client, repoPath: repo)) _query = State(initialValue: query ?? "") initialQuery = query } var body: some View { List { ForEach(model.state?.value ?? []) { symbol in NavigationLink(value: RepoRoute.file( repo: model.repoPath, path: symbol.path, ref: nil, line: symbol.line )) { VStack(alignment: .leading, spacing: 3) { HStack(spacing: 6) { Text(symbol.name) .font(.gbMono(.subheadline).weight(.medium)) .lineLimit(1) GBChip(symbol.kind, .secondary) } Text("\(symbol.path):\(symbol.line)") .font(.gbMono(.caption)) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.head) } .accessibilityElement(children: .combine) } .accessibilityIdentifier("symbol-\(symbol.name)") } } .overlay { if let state = model.state { LoadStateOverlay(state: state) } else { ContentUnavailableView { Label("Find a definition", systemImage: "function") } description: { Text("A name or its start, on the default branch.") } } } .searchable(text: $query, prompt: "Find a definition") .onSubmit(of: .search) { Task { await model.search(query) } } .toolbar { ToolbarItem(placement: .topBarTrailing) { Menu { Picker("Kind", selection: Binding(get: { model.kind }, set: { model.kind = $0 })) { Text("Any kind").tag(String?.none) ForEach(SymbolSearchViewModel.kinds, id: \.self) { kind in Text(kind).tag(String?.some(kind)) } } } label: { Image(systemName: model.kind == nil ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill") } .accessibilityLabel(model.kind.map { "Kind: \($0)" } ?? "Kind: any") .accessibilityIdentifier("symbols-kind") } } .onChange(of: model.kind) { if !query.trimmingCharacters(in: .whitespaces).isEmpty { Task { await model.search(query) } } } .task { if let initialQuery, model.state == nil { await model.search(initialQuery) } } .navigationTitle("Definitions") .navigationBarTitleDisplayMode(.inline) } }