Skip to content

Commit c6d5bf2

Browse files
authored
Merge pull request #2 from iSapozhnik/codex/nstextdiffview-appkit-parity
Add AppKit NSTextDiffView API and unify SwiftUI rendering
2 parents 6050666 + df5ea5b commit c6d5bf2

15 files changed

Lines changed: 586 additions & 155 deletions

Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ let package = Package(
2424
// Targets are the basic building blocks of a package, defining a module or a test suite.
2525
// Targets can depend on other targets in this package and products from dependencies.
2626
.target(
27-
name: "TextDiff"),
27+
name: "TextDiff",
28+
swiftSettings: [
29+
.define("TESTING", .when(configuration: .debug))
30+
]
31+
),
2832
.testTarget(
2933
name: "TextDiffTests",
3034
dependencies: [

README.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# TextDiff
22

3-
TextDiff is a macOS Swift package that computes token-level diffs and renders a merged, display-only SwiftUI view backed by a custom AppKit renderer.
3+
TextDiff is a macOS Swift package that computes token-level diffs and renders a merged, display-only diff view for both SwiftUI (`TextDiffView`) and AppKit (`NSTextDiffView`) via the same custom AppKit renderer.
44

55
![TextDiff preview](Resources/textdiff-preview.png)
66

@@ -43,6 +43,30 @@ struct DemoView: View {
4343
}
4444
```
4545

46+
## AppKit Usage
47+
48+
```swift
49+
import AppKit
50+
import TextDiff
51+
52+
let diffView = NSTextDiffView(
53+
original: "This is teh old sentence.",
54+
updated: "This is the updated sentence!",
55+
mode: .token
56+
)
57+
58+
// Constrain width in your layout. Height is intrinsic and computed from width.
59+
diffView.translatesAutoresizingMaskIntoConstraints = false
60+
```
61+
62+
You can update content in place:
63+
64+
```swift
65+
diffView.mode = .character
66+
diffView.original = "Add a diff"
67+
diffView.updated = "Added a diff"
68+
```
69+
4670
## Comparison Modes
4771

4872
```swift
@@ -101,14 +125,15 @@ struct StyledDemoView: View {
101125
- No synthetic spacer characters are inserted into the rendered text stream.
102126
- Chip top/bottom clipping is prevented internally via explicit line-height and vertical content insets.
103127
- Moved text is not detected as a move; it appears as delete + insert.
104-
- Rendering uses a custom AppKit draw view bridged into SwiftUI.
128+
- Rendering uses a custom AppKit draw view shared by both `TextDiffView` and `NSTextDiffView`.
105129

106130
## Snapshot Testing
107131

108132
Snapshot coverage uses [Point-Free SnapshotTesting](https://github.com/pointfreeco/swift-snapshot-testing) with `swift-testing`.
109133

110134
- Snapshot tests live in `Tests/TextDiffTests/TextDiffSnapshotTests.swift`.
111-
- Baselines are stored under `Tests/TextDiffTests/__Snapshots__/TextDiffSnapshotTests/`.
135+
- AppKit snapshot tests live in `Tests/TextDiffTests/NSTextDiffSnapshotTests.swift`.
136+
- Baselines are stored under `Tests/TextDiffTests/__Snapshots__/TextDiffSnapshotTests/` and `Tests/TextDiffTests/__Snapshots__/NSTextDiffSnapshotTests/`.
112137
- The suite uses `@Suite(.snapshots(record: .missing))` to record only missing baselines.
113138

114139
Run all tests:
@@ -119,7 +144,7 @@ swift test 2>&1 | xcsift --quiet
119144

120145
Update baselines intentionally:
121146

122-
1. Temporarily switch the suite trait in `Tests/TextDiffTests/TextDiffSnapshotTests.swift` from `.missing` to `.all`.
147+
1. Temporarily switch the suite trait in snapshot suites (for example, `Tests/TextDiffTests/TextDiffSnapshotTests.swift` and `Tests/TextDiffTests/NSTextDiffSnapshotTests.swift`) from `.missing` to `.all`.
123148
2. Run `swift test 2>&1 | xcsift --quiet` once to rewrite baselines.
124149
3. Switch the suite trait back to `.missing`.
125150
4. Review snapshot image diffs in your PR before merging.

Sources/TextDiff/AppKit/DiffCanvasView.swift

Lines changed: 0 additions & 104 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import AppKit
2+
3+
enum DiffTextLayoutMetrics {
4+
static func verticalTextInset(for style: TextDiffStyle) -> CGFloat {
5+
ceil(max(2, style.chipInsets.top + 2, style.chipInsets.bottom + 2))
6+
}
7+
8+
static func lineHeight(for style: TextDiffStyle) -> CGFloat {
9+
let textHeight = ceil(style.font.ascender - style.font.descender + style.font.leading)
10+
let chipHeight = textHeight + style.chipInsets.top + style.chipInsets.bottom
11+
return ceil(chipHeight + max(0, style.lineSpacing))
12+
}
13+
}
Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
11
import AppKit
22
import SwiftUI
33

4-
enum DiffTextLayoutMetrics {
5-
static func verticalTextInset(for style: TextDiffStyle) -> CGFloat {
6-
ceil(max(2, style.chipInsets.top + 2, style.chipInsets.bottom + 2))
7-
}
8-
9-
static func lineHeight(for style: TextDiffStyle) -> CGFloat {
10-
let textHeight = ceil(style.font.ascender - style.font.descender + style.font.leading)
11-
let chipHeight = textHeight + style.chipInsets.top + style.chipInsets.bottom
12-
return ceil(chipHeight + max(0, style.lineSpacing))
13-
}
14-
}
15-
164
struct DiffTextViewRepresentable: NSViewRepresentable {
17-
let segments: [DiffSegment]
5+
let original: String
6+
let updated: String
187
let style: TextDiffStyle
8+
let mode: TextDiffComparisonMode
199

20-
func makeNSView(context: Context) -> DiffCanvasView {
21-
let view = DiffCanvasView()
10+
func makeNSView(context: Context) -> NSTextDiffView {
11+
let view = NSTextDiffView(
12+
original: original,
13+
updated: updated,
14+
style: style,
15+
mode: mode
16+
)
2217
view.setContentCompressionResistancePriority(.required, for: .vertical)
2318
view.setContentHuggingPriority(.required, for: .vertical)
24-
view.update(segments: segments, style: style)
2519
return view
2620
}
2721

28-
func updateNSView(_ view: DiffCanvasView, context: Context) {
29-
view.update(segments: segments, style: style)
22+
func updateNSView(_ view: NSTextDiffView, context: Context) {
23+
view.setContent(
24+
original: original,
25+
updated: updated,
26+
style: style,
27+
mode: mode
28+
)
3029
}
3130
}

0 commit comments

Comments
 (0)