Hutch/Views/Repositories/DiffView.swift

b5af561d68e7535726b79d4ceec0d0a45abf0042
hutch/Hutch/Views/Repositories/DiffView.swift history · blame · raw

79 lines · 2203 bytes

 1import SwiftUI
 2
 3/// Renders a unified diff string with syntax highlighting:
 4/// - Green background for added lines (+)
 5/// - Red background for removed lines (-)
 6/// - Gray for hunk headers (@@)
 7/// - File headers (--- / +++ / diff) in bold
 8struct DiffView: View {
 9    let diff: String
10
11    var body: some View {
12        let lines = diff.components(separatedBy: "\n")
13
14        LazyVStack(alignment: .leading, spacing: 0) {
15            ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
16                DiffLineView(line: line)
17            }
18        }
19        .font(.caption.monospaced())
20    }
21}
22
23private struct DiffLineView: View {
24    let line: String
25
26    var body: some View {
27        Text(line.isEmpty ? " " : line)
28            .frame(maxWidth: .infinity, alignment: .leading)
29            .padding(.horizontal, 8)
30            .padding(.vertical, 1)
31            .background(backgroundColor)
32            .foregroundStyle(foregroundColor)
33            .fontWeight(isHeader ? .semibold : .regular)
34    }
35
36    private var kind: DiffLineKind {
37        if line.hasPrefix("@@") { return .hunk }
38        if line.hasPrefix("+++") || line.hasPrefix("---") { return .fileHeader }
39        if line.hasPrefix("diff ") { return .fileHeader }
40        if line.hasPrefix("index ") { return .meta }
41        if line.hasPrefix("+") { return .added }
42        if line.hasPrefix("-") { return .removed }
43        return .context
44    }
45
46    private var backgroundColor: Color {
47        switch kind {
48        case .added:      .green.opacity(0.15)
49        case .removed:    .red.opacity(0.15)
50        case .hunk:       .gray.opacity(0.12)
51        case .fileHeader: .gray.opacity(0.08)
52        case .meta:       .gray.opacity(0.05)
53        case .context:    .clear
54        }
55    }
56
57    private var foregroundColor: Color {
58        switch kind {
59        case .added:   .green
60        case .removed: .red
61        case .hunk:    .secondary
62        case .meta:    .secondary
63        default:       .primary
64        }
65    }
66
67    private var isHeader: Bool {
68        kind == .fileHeader
69    }
70}
71
72private enum DiffLineKind {
73    case added
74    case removed
75    case hunk
76    case fileHeader
77    case meta
78    case context
79}