summaryrefslogtreecommitdiff
path: root/Hutch/Views/Repositories/DiffView.swift
blob: b1db4645ee9938ca411959fe9d516043c475240c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import SwiftUI

/// Renders a unified diff string with syntax highlighting:
/// - Green background for added lines (+)
/// - Red background for removed lines (-)
/// - Gray for hunk headers (@@)
/// - File headers (--- / +++ / diff) in bold
struct DiffView: View {
    let diff: String

    var body: some View {
        let lines = diff.components(separatedBy: "\n")

        LazyVStack(alignment: .leading, spacing: 0) {
            ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
                DiffLineView(line: line)
            }
        }
        .font(.caption.monospaced())
    }
}

private struct DiffLineView: View {
    let line: String

    var body: some View {
        Text(line.isEmpty ? " " : line)
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(.horizontal, 8)
            .padding(.vertical, 1)
            .background(backgroundColor)
            .foregroundStyle(foregroundColor)
            .fontWeight(isHeader ? .semibold : .regular)
    }

    private var kind: DiffLineKind {
        if line.hasPrefix("@@") { return .hunk }
        if line.hasPrefix("+++") || line.hasPrefix("---") { return .fileHeader }
        if line.hasPrefix("diff ") { return .fileHeader }
        if line.hasPrefix("index ") { return .meta }
        if line.hasPrefix("+") { return .added }
        if line.hasPrefix("-") { return .removed }
        return .context
    }

    private var backgroundColor: Color {
        switch kind {
        case .added:      .green.opacity(0.15)
        case .removed:    .red.opacity(0.15)
        case .hunk:       .gray.opacity(0.12)
        case .fileHeader: .gray.opacity(0.08)
        case .meta:       .gray.opacity(0.05)
        case .context:    .clear
        }
    }

    private var foregroundColor: Color {
        switch kind {
        case .added:   .green
        case .removed: .red
        case .hunk:    .secondary
        case .meta:    .secondary
        default:       .primary
        }
    }

    private var isHeader: Bool {
        kind == .fileHeader
    }
}

private enum DiffLineKind {
    case added
    case removed
    case hunk
    case fileHeader
    case meta
    case context
}