import UIKit enum DiffSource { case commit(owner: String, repository: String, sha: String, path: String) case pull(owner: String, repository: String, number: Int64, path: String) } @MainActor final class DiffViewController: UIViewController { private let context: AppContext private let source: DiffSource private let codeView = CodeScrollView() private let spinner = UIActivityIndicatorView(style: .medium) private var loadingTask: Task? init(context: AppContext, source: DiffSource) { self.context = context self.source = source super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground codeView.refreshControl = UIRefreshControl() codeView.refreshControl?.addTarget(self, action: #selector(reload), for: .valueChanged) view.addSubview(codeView) NSLayoutConstraint.activate([ codeView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), codeView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), codeView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), codeView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), ]) beginNavigationLoading(spinner) reload() } deinit { loadingTask?.cancel() } @objc private func reload() { loadingTask?.cancel() loadingTask = Task { do { let page: DiffPage switch source { case let .commit(owner, repository, sha, path): page = try await context.core.commitDiff( owner: owner, repository: repository, sha: sha, path: path ) case let .pull(owner, repository, number, path): page = try await context.core.pullDiff( owner: owner, repository: repository, number: number, path: path ) } title = page.title codeView.display(diffText(page)) } catch { if !Task.isCancelled { show(error: error) } } endNavigationLoading(spinner) codeView.refreshControl?.endRefreshing() } } private func diffText(_ page: DiffPage) -> NSAttributedString { let output = NSMutableAttributedString() let font = UIFont.monospacedSystemFont(ofSize: 12, weight: .regular) for line in page.lines { let text = String(format: "%4@ %4@ %@\n", line.oldNumber, line.newNumber, line.text) let color: UIColor switch line.kind { case .addition: color = UIColor.systemGreen.withAlphaComponent(0.16) case .removal: color = UIColor.systemRed.withAlphaComponent(0.16) case .hunk: color = UIColor.systemBlue.withAlphaComponent(0.14) case .header: color = UIColor.systemGray.withAlphaComponent(0.14) case .context: color = .clear } output.append(NSAttributedString(string: text, attributes: [ .font: font, .foregroundColor: UIColor.label, .backgroundColor: color, ])) } return output } }