Sapling/Sources/SaplingUI/SaplingInspectorView.swift

122 lines
3.9 KiB
Swift
Raw Permalink Normal View History

2026-05-29 15:19:33 +02:00
import SwiftUI
import SaplingCore
public struct SaplingInspectorView: View {
private let project: Project?
private let document: MarkdownDocument?
private let statuses: [GitFileStatus]
public init(
project: Project?,
document: MarkdownDocument?,
statuses: [GitFileStatus]
) {
self.project = project
self.document = document
self.statuses = statuses
}
public var body: some View {
List {
projectSection
gitStatusSection
outlineSection
}
.listStyle(.inset)
.navigationTitle("Inspector")
}
@ViewBuilder
private var projectSection: some View {
Section("Project") {
if let project {
LabeledContent("Name", value: project.name)
LabeledContent("Branch", value: project.gitRepository.currentBranch?.name ?? "Unknown")
LabeledContent("Remotes", value: "\(project.remotes.count)")
LabeledContent("LFS", value: project.usesGitLFS ? "Enabled" : "Disabled")
LabeledContent("Subprojects", value: "\(project.subprojects.count)")
} else {
Text("No project selected")
.foregroundStyle(.secondary)
}
}
}
private var gitStatusSection: some View {
Section("Git Status") {
if statuses.isEmpty {
Label("Clean", systemImage: "checkmark.circle")
.foregroundStyle(.green)
} else {
ForEach(statuses) { status in
HStack {
Image(systemName: iconName(for: status.state))
.foregroundStyle(color(for: status.state))
Text(status.path)
.lineLimit(1)
Spacer()
Text(status.state.rawValue)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
}
private var outlineSection: some View {
Section("Outline") {
if let document {
let headings = outlineHeadings(in: document.content)
if headings.isEmpty {
Text("No headings")
.foregroundStyle(.secondary)
} else {
ForEach(headings, id: \.self) { heading in
Text(heading)
.lineLimit(1)
}
}
} else {
Text("No document selected")
.foregroundStyle(.secondary)
}
}
}
private func outlineHeadings(in markdown: String) -> [String] {
markdown
.split(separator: "\n")
.map(String.init)
.compactMap { line in
let trimmed = line.trimmingCharacters(in: .whitespaces)
guard trimmed.hasPrefix("#") else { return nil }
let hashes = trimmed.prefix { $0 == "#" }.count
guard hashes <= 6, trimmed.dropFirst(hashes).first == " " else { return nil }
return String(trimmed.dropFirst(hashes + 1))
}
}
private func iconName(for state: GitFileState) -> String {
switch state {
case .untracked: "questionmark.circle"
case .added: "plus.circle"
case .modified: "pencil.circle"
case .deleted: "minus.circle"
case .renamed: "arrow.triangle.2.circlepath"
case .conflicted: "exclamationmark.triangle"
}
}
private func color(for state: GitFileState) -> Color {
switch state {
case .untracked: .secondary
case .added: .green
case .modified: .orange
case .deleted: .red
case .renamed: .blue
case .conflicted: .red
}
}
}