import SwiftUI /// Read-only unified diff viewer for a single changed file. /// /// Parses a Gitea unified diff patch into line hunks so added/deleted lines can /// be colour-coded. Gitea only returns `patch` for small diffs; large files /// fall back to a raw text view. struct DiffView: View { let file: ChangedFile var body: some View { Group { if let patch = file.patch, !patch.isEmpty { ScrollView([.horizontal, .vertical]) { LazyVStack(alignment: .leading, spacing: 0) { ForEach(parse(patch)) { line in diffLine(line) } } .font(.caption.monospaced()) } } else { ContentUnavailableView( "Diff too large to preview", systemImage: "doc.text", description: Text("Gitea didn't return a patch for this file.")) } } .navigationTitle(file.filename) .navigationBarTitleDisplayMode(.inline) } private func diffLine(_ line: DiffLine) -> some View { Text(line.text) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 8) .padding(.vertical, 1) .background(background(for: line.type)) .foregroundColor(foreground(for: line.type)) } private func background(for type: DiffLineType) -> Color { switch type { case .addition: return Color.green.opacity(0.15) case .deletion: return Color.red.opacity(0.15) case .hunk, .meta: return Color(.secondarySystemBackground) case .context: return .clear } } private func foreground(for type: DiffLineType) -> Color { switch type { case .addition: return .green case .deletion: return .red case .hunk: return .secondary case .meta, .context: return .primary } } private func parse(_ patch: String) -> [DiffLine] { patch.components(separatedBy: "\n").map { line in if line.hasPrefix("@@") { return DiffLine(text: line, type: .hunk) } if line.hasPrefix("diff ") || line.hasPrefix("index ") || line.hasPrefix("--- ") || line.hasPrefix("+++ ") { return DiffLine(text: line, type: .meta) } if line.hasPrefix("+") { return DiffLine(text: line, type: .addition) } if line.hasPrefix("-") { return DiffLine(text: line, type: .deletion) } return DiffLine(text: line, type: .context) } } } private enum DiffLineType { case context, addition, deletion, hunk, meta } private struct DiffLine: Identifiable { let text: String let type: DiffLineType var id: UUID { UUID() } }