Skip to content
 
 

Repository files navigation

YiTong

YiTong logo

YiTong provides DiffView and DiffViewController for rendering diffs on Apple platforms. It is an Apple-platform wrapper around diffs.com, rendered through WKWebView. It supports both unified patch input and file-based old/new contents input.

YiTong = 异同

析其所异,合其所同。

Preview

YiTong Example App

Integration

YiTong supports:

  • iOS 16+
  • macOS 13+

For released builds, prefer a tagged Swift Package dependency:

dependencies: [
  .package(url: "https://github.com/onevcat/YiTong.git", from: "0.1.0")
]

Then add the product to your target:

targets: [
  .target(
    name: "MyApp",
    dependencies: [
      .product(name: "YiTong", package: "YiTong")
    ]
  )
]

Replace 0.1.0 with the latest tagged release. If you need unreleased changes during development, you can temporarily track branch: "master" instead.

Common Usage

Create a DiffDocument

Files

import YiTong

let document = DiffDocument(
  files: [
    DiffFile(
      oldPath: "Sources/Counter.swift",
      newPath: "Sources/Counter.swift",
      oldContents: "struct Counter { var value: Int }\n",
      newContents: "struct Counter { private(set) var value: Int }\n"
    ),
  ],
  title: "Counter.swift"
)

If you also have the original patch text, pass it as fallbackPatch. YiTong automatically falls back to patch-based rendering when file-based input exceeds supported limits.

Patch

import YiTong

let document = DiffDocument(
  patch: """
  diff --git a/Example.swift b/Example.swift
  --- a/Example.swift
  +++ b/Example.swift
  @@ -1,3 +1,3 @@
  -let value = 1
  +let value = 2
  """
)

Use files when your app already has old/new contents in memory. Use patch when you already have unified diff output, or when patch text is the more natural transport format.

Render the Diff

SwiftUI

import SwiftUI
import YiTong

struct ContentView: View {
  let document: DiffDocument

  var body: some View {
    DiffView(
      document: document,
      configuration: DiffConfiguration(
        appearance: .automatic,
        style: .split,
        indicators: .bars
      )
    )
  }
}

Integrate With Your Own File List

When your app already provides file navigation, render one file at a time and hide YiTong's built-in file header. This is the integration pattern used in supacode.

import SwiftUI
import YiTong

struct DiffDetailView: View {
  let selectedFile: DiffFile
  let selectedTitle: String
  let selectedPatch: String?

  var body: some View {
    DiffView(
      document: DiffDocument(
        files: [selectedFile],
        title: selectedTitle,
        fallbackPatch: selectedPatch
      ),
      configuration: DiffConfiguration(
        style: .split,
        showsFileHeaders: false
      )
    )
  }
}

UIKit / AppKit

import YiTong

let controller = DiffViewController(
  document: document,
  configuration: DiffConfiguration(style: .unified)
)

Handle Events

DiffView(
  document: document,
  onEvent: { event in
    switch event {
    case .didFinishInitialLoad:
      break
    case .didRender(let summary):
      print(summary.fileCount)
    case .didClickLine(let line):
      print(line.fileIndex, line.number)
    case .didChangeSelection(let selection):
      print(selection as Any)
    case .didActivateAnnotation(let action):
      print(action.annotationID, action.action)
    case .didFail(let error):
      print(error.code, error.message)
    }
  }
)

Annotations

Annotations attach host-rendered content beneath a diff line: a review thread, a comment composer, a lint warning. YiTong stays agnostic about what the content means; the host renders it and reacts to clicks.

import YiTong

let thread = DiffAnnotation(
  id: "thread-42",
  fileIndex: 0,
  side: .new,
  lineNumber: 17,
  kind: "thread",
  content: .html(
    """
    <p><strong>Amadeus</strong> Should this be optional?</p>
    <button data-action="reply">Reply</button>
    """
  )
)

DiffView(
  document: document,
  annotations: [thread],
  onEvent: { event in
    if case .didActivateAnnotation(let action) = event {
      // action.annotationID == "thread-42", action.action == "reply"
    }
  }
)
  • side and lineNumber address the line the same way DiffLineReference does. Multi-line annotations anchor to one line (typically the end of the range); draw range information into the content yourself.
  • content is either .html(String) or .text(String). HTML is inserted verbatim, apart from script execution vectors (<script>, on* handlers, javascript: URLs), which the renderer strips. Everything else, including inline styles and <style> blocks, is the host's responsibility.
  • Any element inside the HTML carrying a data-action attribute reports clicks through DiffEvent.didActivateAnnotation, echoing the annotation's id, kind and line coordinates alongside the action string.
  • Changing only annotations on DiffView, or calling DiffViewController.update(annotations:), updates the annotations in place without re-rendering the document.
  • annotations defaults to an empty list, so existing call sites need no changes.

Configuration

DiffConfiguration currently supports:

  • appearance: .automatic, .light, .dark
  • style: .split, .unified
  • indicators: .bars, .classic, .none
  • showsLineNumbers
  • showsChangeBackgrounds
  • wrapsLines
  • showsFileHeaders
  • inlineChangeStyle: .wordAlt, .word, .char, .none
  • allowsSelection
    • On touch devices the line-number gutter opts out of scrolling so a finger drag selects lines; start scroll gestures from the code area instead.

Feature Mapping

YiTong exposes a smaller Swift-facing API than the full diffs vanilla JS surface.

diffs vanilla option YiTong public API Status
diffStyle DiffConfiguration.style Supported
lineDiffType DiffConfiguration.inlineChangeStyle Supported
diffIndicators DiffConfiguration.indicators Supported
disableBackground DiffConfiguration.showsChangeBackgrounds Supported
disableLineNumbers DiffConfiguration.showsLineNumbers Supported
overflow DiffConfiguration.wrapsLines Supported
disableFileHeader DiffConfiguration.showsFileHeaders Supported
enableLineSelection DiffConfiguration.allowsSelection Supported
themeType DiffConfiguration.appearance Supported
theme Not public Hidden
renderHeaderMetadata Not public Not supported
lineAnnotations / renderAnnotation DiffAnnotation, DiffEvent.didActivateAnnotation Supported
worker pool options Not public Not supported
custom DOM / unsafe CSS hooks Not public Not supported

Development

Most common commands:

make verify
make run-example
make update-web-assets

make run-example launches the bundled macOS example app for manual verification.

License

YiTong is licensed under the Apache License 2.0.

This repository also bundles generated web assets derived from @pierre/diffs (Apache-2.0) for local WKWebView rendering.

See:

About

Apple-platform wrapper around @pierre/diffs with WKWebView-backed native diff UI

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages