Files
ios-gitea-prs/GiteaPR/Detail/DiffView.swift
T
Hermes 1025634978
Build iOS App / Build .ipa and publish source (push) Failing after 35s
GiteaPR: native iOS app to manage Gitea pull requests
Multi-repo PR inbox, review (approve/request changes/comment), merge with
gating toggle, read-only diff viewer, and Gitea Actions check status with
one-tap re-run. Ships unsigned via the iMac runner to the SideStore source.
2026-08-12 17:07:58 +00:00

80 lines
2.8 KiB
Swift

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() }
}