Hutch/Views/Repositories/DiffView.swift
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}