Skip to content

Add iOS support and set default text colors#8

Open
iSapozhnik wants to merge 8 commits intomainfrom
feature/ios-support
Open

Add iOS support and set default text colors#8
iSapozhnik wants to merge 8 commits intomainfrom
feature/ios-support

Conversation

@iSapozhnik
Copy link
Copy Markdown
Owner

@iSapozhnik iSapozhnik commented Apr 18, 2026

This pull request restructures the TextDiff package to support both macOS and iOS platforms, modularizes the codebase into platform-specific and shared components, and updates the SwiftUI interface to be cross-platform. The most significant changes include the introduction of new library and target modules, refactoring of the main SwiftUI view to support both platforms, and moving core logic into a new TextDiffCore target.

Cross-platform and modularization updates:

  • The Swift package (Package.swift) now supports both macOS and iOS, and defines new library products and targets: TextDiffCore, TextDiffUICommon, TextDiffMacOSUI, and TextDiffIOSUI, along with platform-conditional dependencies and test targets. [1] [2]
  • The main TextDiff target now re-exports the core and UI modules, and conditionally includes the macOS or iOS UI module depending on the platform. (Sources/TextDiff/TextDiff.swift)

SwiftUI view refactorings:

  • TextDiffView in Sources/TextDiff/TextDiffView.swift is refactored to use platform-conditional logic, delegating to either a macOS (TextDiffMacOSRepresentable) or iOS (TextDiffIOSRepresentable) implementation. This enables the same SwiftUI interface to work seamlessly across platforms. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]
  • Platform-specific types such as PlatformFont and TextDiffEdgeInsets are now used in place of AppKit-only types, making the style configuration cross-platform. [1] [2] [3] [4]

Core logic reorganization:

  • Core diff logic (DiffSegmentIndexer and related types) has been moved from Sources/TextDiff to a new Sources/TextDiffCore target, and access levels have been updated to use Swift's new package visibility.
  • A file-level comment was added to the new Sources/TextDiffCore/TextDiff.swift for clarity.

Cleanup and removals:

  • The macOS-specific default change style extension was removed from TextDiffChangeStyleDefaults.swift, further decoupling platform-specific code.

These changes lay the foundation for a clean, modular, and cross-platform Swift package architecture for text diffing.

Summary by CodeRabbit

  • New Features

    • iOS 18 support now available with complete text diffing functionality
    • New iOS-optimized text diff view component
  • Changes

    • Library restructured into platform-specific modules for improved organization
    • Styling system updated to support both macOS and iOS platforms
    • API surface reorganized for cross-platform consistency

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 18, 2026

Warning

Rate limit exceeded

@iSapozhnik has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 34 minutes and 17 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 34 minutes and 17 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bc121c24-540e-455c-bac9-d73ce8620b54

📥 Commits

Reviewing files that changed from the base of the PR and between 34f69f0 and 4ebeca0.

⛔ Files ignored due to path filters (4)
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/custom_style_spacing_strikethrough.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/hover_pair_affordance.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/narrow_width_wrapping.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/TextDiffSnapshotTests/custom_style_spacing_strikethrough.1.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • README.md
  • Sources/TextDiff/TextDiffView.swift
  • Sources/TextDiffUICommon/TextDiffViewModel.swift
📝 Walkthrough

Walkthrough

The PR restructures the TextDiff package from a monolithic library into platform-specific modules: TextDiffCore (core diffing), TextDiffUICommon (shared cross-platform UI code), TextDiffMacOSUI (macOS-specific UI), and TextDiffIOSUI (iOS-specific UI). It replaces AppKit-specific types with cross-platform abstractions (PlatformColor, PlatformFont, TextDiffEdgeInsets) and adds iOS 18 platform support. Tests are reorganized to align with the new module structure.

Changes

Cohort / File(s) Summary
Package Configuration
Package.swift
Restructured from single library into modular products (TextDiffCore, TextDiffUICommon, TextDiffMacOSUI, TextDiffIOSUI, TextDiff aggregate). Added iOS 18 support. Updated test targets and inter-target dependencies.
Cross-Platform Type Abstractions
Sources/TextDiffUICommon/TextDiffPlatformTypes.swift, TextDiffStyle.swift, TextDiffChangeStyle.swift, TextDiffStyling.swift
Introduced platform-conditional type aliases (PlatformColor, PlatformFont) and TextDiffEdgeInsets struct. Updated public APIs to use platform-agnostic types instead of AppKit-specific NSColor, NSFont, NSEdgeInsets.
Core Module
Sources/TextDiffCore/TextDiff.swift, DiffSegmentIndexer.swift
Added core module namespace file. Changed visibility of IndexedDiffSegment and DiffSegmentIndexer from internal to package scope.
UI Common Module
Sources/TextDiffUICommon/DiffTokenLayouter.swift, DiffTextLayoutMetrics.swift, TextDiffChangeStyleDefaults.swift, TextDiffViewModel.swift
Updated types to package visibility. Replaced NSEdgeInsets with TextDiffEdgeInsets and NSColor/NSFont with PlatformColor/PlatformFont. Added default style presets using platform-conditional colors. Added cross-platform color component extraction helper.
macOS UI Module
Sources/TextDiffMacOSUI/AppKit/NSTextDiffView.swift, DiffTextViewRepresentable.swift, DiffRevertActionResolver.swift, NSTextDiffContentSource.swift
Added imports for TextDiffCore and TextDiffUICommon. Updated NSTextDiffView to use TextDiffEdgeInsets instead of NSEdgeInsets.
iOS UI Module
Sources/TextDiffIOSUI/UITextDiffView.swift
Added new UITextDiffView UIKit class for iOS rendering with diff computation, layout caching, and platform-specific draw methods. Supports text/style/mode updates with batching optimization.
Root API Layer
Sources/TextDiff/TextDiff.swift, TextDiffView.swift, Sources/TextDiff/TextDiffChangeStyleDefaults.swift
Added @_exported import statements for core and platform-specific modules. Refactored TextDiffView with platform-gated properties and macOS-only initializer variant. Removed consolidated default styles (moved to UICommon module). Dispatches to platform-specific representables (NSViewRepresentable for macOS, UIViewRepresentable for iOS).
Test Reorganization
Tests/TextDiffCoreTests/TextDiffEngineTests.swift, Tests/TextDiffMacOSUITests/*.swift
Updated test imports to match new module structure. Removed AppKit-dependent tests from core tests. Added new TextDiffMacOSUITests with comprehensive layout/styling/default verification coverage. Consolidated test helpers.

Sequence Diagram

sequenceDiagram
    participant SwiftUI as SwiftUI View
    participant TextDiffView as TextDiffView<br/>(Root API)
    participant Core as TextDiffCore<br/>(Engine)
    participant UICommon as TextDiffUICommon<br/>(Layout)
    participant macOS as macOS Path<br/>(NSViewRep)
    participant iOS as iOS Path<br/>(UIViewRep)

    SwiftUI->>TextDiffView: Provide text/style
    TextDiffView->>TextDiffView: Detect platform
    
    alt macOS Path
        TextDiffView->>macOS: Render NSViewRepresentable
        macOS->>Core: Request diff segments
        Core-->>macOS: DiffSegments
        macOS->>UICommon: Layout segments with style
        UICommon-->>macOS: DiffLayout (runs with colors/attrs)
        macOS->>macOS: Draw attributed text + chip backgrounds
    else iOS Path
        TextDiffView->>iOS: Render UIViewRepresentable
        iOS->>Core: Request diff segments
        Core-->>iOS: DiffSegments
        iOS->>UICommon: Layout segments with style
        UICommon-->>iOS: DiffLayout (runs with colors/attrs)
        iOS->>iOS: Draw via CoreGraphics
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #7: Direct predecessor that introduces the same module-split architecture, TextDiffResult precomputation, and result-driven rendering consumed by these platform-specific views.
  • PR #5: Related work on interactive revert feature and the layout/run-to-segment mapping logic touched in this restructuring.

Poem

🎨 One package split into four,
Cross-platform abstractions at the core,
macOS here, iOS there—
Platform gates everywhere,
Modules dance in harmony's store. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add iOS support and set default text colors' accurately reflects the main changes: cross-platform iOS/macOS refactoring and new default text color handling via TextDiffChangeStyle presets.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ios-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
Package.swift (1)

43-72: TESTING flag scope makes #if !TESTING assert(...) blocks effectively dead.

TESTING is defined for every debug build, not just test runs. Since assert is a no-op in release, the guarded assertions in DiffSegmentIndexer (and any similar guards) never execute in any configuration. If the intent is to suppress assertions only while running tests, consider moving the TESTING define to the test target via an -D unsafe flag / test plan, or use #if DEBUG && !TESTING with the flag set only in a dedicated configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Package.swift` around lines 43 - 72, The TESTING build define is applied to
all debug targets making any guards like `#if !TESTING` in DiffSegmentIndexer
effectively dead; update the build settings so TESTING is only defined for
actual test runs (e.g., remove `.define("TESTING", .when(configuration:
.debug))` from the targets and instead add the flag only to the test target via
an unsafe `-DTESTING` test-only flag or a dedicated test configuration), or
adjust the conditionals to `#if DEBUG && !TESTING` and ensure TESTING is set
only in the test configuration so assertions in DiffSegmentIndexer behave as
intended.
Sources/TextDiffCore/TextDiff.swift (1)

1-1: Consider deleting this placeholder file.

A file containing only a comment adds noise without carrying anything the compiler or reader uses. If TextDiffCore needs at least one source file to compile, keep it; otherwise drop it. If kept, a brief note pointing to the actual entry points (e.g. DiffSegmentIndexer.swift, TextDiffEngine.swift) would be more useful than the current one-liner.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TextDiffCore/TextDiff.swift` at line 1, This file is a no-op
placeholder; either remove TextDiff.swift to eliminate noise (if TextDiffCore
builds without it) or replace the comment with a concise guide pointing to the
real entry points (e.g., reference DiffSegmentIndexer.swift and
TextDiffEngine.swift) so readers know where the public API lives; update the
repository accordingly and ensure the module still compiles after deleting or
editing TextDiff.swift.
Sources/TextDiff/TextDiffView.swift (1)

363-401: Add iOS previews for the new SwiftUI path.

The macOS branch has previews, but the new iOS representable path has none. Add a couple of iOS #Previews, for example default rendering and precomputed-result rendering.

As per coding guidelines, SwiftUI views should always include #Preview with a few state configurations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TextDiff/TextDiffView.swift` around lines 363 - 401, Add SwiftUI
previews for the iOS representable by creating one or more `#Preview` blocks that
instantiate TextDiffIOSRepresentable with (1) live inputs using
original/updated/mode/style to show default rendering and (2) a precomputed
TextDiffResult passed to the result parameter to show the precomputed-result
rendering; reference TextDiffIOSRepresentable (the makeUIView/updateUIView
paths) and ensure the previews compile under `#if` os(iOS) and exercise both
branches (result != nil and result == nil).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/TextDiffIOSUI/UITextDiffView.swift`:
- Around line 6-57: Add three public properties to
UITextDiffView—isRevertActionsEnabled: Bool, showsInvisibleCharacters: Bool, and
onRevertAction: ((DiffSegment) -> Void)?—with sensible defaults and didSet
observers that mirror NSTextDiffView semantics: when changed, set any relevant
internal state and call the same update/invalidation flow used elsewhere (e.g.
set contentSource = .text and call updateSegmentsIfNeeded() or call
invalidateCachedLayout()/mark pendingStyleInvalidation as appropriate). Ensure
the onRevertAction is stored and invoked by whatever revert-action UI/path the
view exposes, and reference existing helpers like updateSegmentsIfNeeded(),
invalidateCachedLayout(), contentSource, pendingStyleInvalidation, and segments
to locate where to hook the new behavior.

In `@Sources/TextDiffUICommon/TextDiffChangeStyleDefaults.swift`:
- Around line 9-14: The defaultRemoval TextDiffChangeStyle currently sets
strikethrough to false causing deletions to render without a strike; update the
defaultRemoval initializer (TextDiffChangeStyle defaultRemoval) to set
strikethrough: true so deleted text uses the strikethrough style by default,
leaving other properties (fillColor, strokeColor, textColorOverride) unchanged.

In `@Sources/TextDiffUICommon/TextDiffViewModel.swift`:
- Around line 3-4: Remove the duplicate import by deleting one of the repeated
"import TextDiffCore" statements in TextDiffViewModel.swift so the module is
only imported once; keep a single "import TextDiffCore" line and remove the
redundant one.

---

Nitpick comments:
In `@Package.swift`:
- Around line 43-72: The TESTING build define is applied to all debug targets
making any guards like `#if !TESTING` in DiffSegmentIndexer effectively dead;
update the build settings so TESTING is only defined for actual test runs (e.g.,
remove `.define("TESTING", .when(configuration: .debug))` from the targets and
instead add the flag only to the test target via an unsafe `-DTESTING` test-only
flag or a dedicated test configuration), or adjust the conditionals to `#if
DEBUG && !TESTING` and ensure TESTING is set only in the test configuration so
assertions in DiffSegmentIndexer behave as intended.

In `@Sources/TextDiff/TextDiffView.swift`:
- Around line 363-401: Add SwiftUI previews for the iOS representable by
creating one or more `#Preview` blocks that instantiate TextDiffIOSRepresentable
with (1) live inputs using original/updated/mode/style to show default rendering
and (2) a precomputed TextDiffResult passed to the result parameter to show the
precomputed-result rendering; reference TextDiffIOSRepresentable (the
makeUIView/updateUIView paths) and ensure the previews compile under `#if` os(iOS)
and exercise both branches (result != nil and result == nil).

In `@Sources/TextDiffCore/TextDiff.swift`:
- Line 1: This file is a no-op placeholder; either remove TextDiff.swift to
eliminate noise (if TextDiffCore builds without it) or replace the comment with
a concise guide pointing to the real entry points (e.g., reference
DiffSegmentIndexer.swift and TextDiffEngine.swift) so readers know where the
public API lives; update the repository accordingly and ensure the module still
compiles after deleting or editing TextDiff.swift.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9add5936-e6ca-4644-b093-7beb1377b8f9

📥 Commits

Reviewing files that changed from the base of the PR and between b5815ad and 34f69f0.

⛔ Files ignored due to path filters (18)
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/character_mode_no_affordance.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/character_suffix_refinement.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/custom_style_spacing_strikethrough.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/hover_pair_affordance.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/hover_single_addition_affordance.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/hover_single_deletion_affordance.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/invisible_characters_debug_overlay.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/multiline_insertion_wrap.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/narrow_width_wrapping.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/punctuation_replacement.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/NSTextDiffSnapshotTests/token_basic_replacement.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/TextDiffSnapshotTests/character_suffix_refinement.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/TextDiffSnapshotTests/custom_style_spacing_strikethrough.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/TextDiffSnapshotTests/multiline_insertion_wrap.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/TextDiffSnapshotTests/precomputed_result_rendering.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/TextDiffSnapshotTests/punctuation_replacement.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/TextDiffSnapshotTests/token_basic_replacement.1.png is excluded by !**/*.png
  • Tests/TextDiffMacOSUITests/__Snapshots__/TextDiffSnapshotTests/whitespace_only_layout_change.1.png is excluded by !**/*.png
📒 Files selected for processing (34)
  • Package.swift
  • Sources/TextDiff/TextDiff.swift
  • Sources/TextDiff/TextDiffChangeStyleDefaults.swift
  • Sources/TextDiff/TextDiffView.swift
  • Sources/TextDiffCore/DiffSegmentIndexer.swift
  • Sources/TextDiffCore/DiffTypes.swift
  • Sources/TextDiffCore/MyersDiff.swift
  • Sources/TextDiffCore/TextDiff.swift
  • Sources/TextDiffCore/TextDiffEngine.swift
  • Sources/TextDiffCore/TextDiffResult.swift
  • Sources/TextDiffCore/Tokenizer.swift
  • Sources/TextDiffIOSUI/UITextDiffView.swift
  • Sources/TextDiffMacOSUI/AppKit/DiffRevertActionResolver.swift
  • Sources/TextDiffMacOSUI/AppKit/DiffTextViewRepresentable.swift
  • Sources/TextDiffMacOSUI/AppKit/NSTextDiffContentSource.swift
  • Sources/TextDiffMacOSUI/AppKit/NSTextDiffView.swift
  • Sources/TextDiffUICommon/DiffTextLayoutMetrics.swift
  • Sources/TextDiffUICommon/DiffTokenLayouter.swift
  • Sources/TextDiffUICommon/TextDiffChangeStyle.swift
  • Sources/TextDiffUICommon/TextDiffChangeStyleDefaults.swift
  • Sources/TextDiffUICommon/TextDiffGroupStrokeStyle.swift
  • Sources/TextDiffUICommon/TextDiffPlatformTypes.swift
  • Sources/TextDiffUICommon/TextDiffStyle.swift
  • Sources/TextDiffUICommon/TextDiffStyling.swift
  • Sources/TextDiffUICommon/TextDiffViewModel.swift
  • Tests/TextDiffCoreTests/TextDiffEngineTests.swift
  • Tests/TextDiffMacOSUITests/DiffLayouterPerformanceTests.swift
  • Tests/TextDiffMacOSUITests/DiffRevertActionResolverTests.swift
  • Tests/TextDiffMacOSUITests/NSTextDiffSnapshotTests.swift
  • Tests/TextDiffMacOSUITests/NSTextDiffViewTests.swift
  • Tests/TextDiffMacOSUITests/SnapshotTestSupport.swift
  • Tests/TextDiffMacOSUITests/TextDiffSnapshotTests.swift
  • Tests/TextDiffMacOSUITests/TextDiffStyleAndLayouterTests.swift
  • Tests/TextDiffMacOSUITests/TextDiffViewModelTests.swift
💤 Files with no reviewable changes (1)
  • Sources/TextDiff/TextDiffChangeStyleDefaults.swift

Comment thread Sources/TextDiffIOSUI/UITextDiffView.swift
Comment thread Sources/TextDiffUICommon/TextDiffChangeStyleDefaults.swift
Comment thread Sources/TextDiffUICommon/TextDiffViewModel.swift Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant