Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,34 @@ struct PullRequestView: View {
### View Modifiers

- `.diffTheme(_ theme: DiffTheme)` - Apply a color theme
- `.diffLineNumbers(_ show: Bool)` - Toggle line numbers
- `.diffFileHeaders(_ show: Bool)` - Toggle file headers
- `.diffLineNumbers(_ show: Bool)` - Toggle line numbers (legacy; prefer `.diffLineNumberStyle(_:)`)
- `.diffLineNumberStyle(_ style: LineNumberStyle)` - Gutter style: `.hidden`, `.single` (mobile-friendly compact column), `.dual` (desktop old/new)
- `.diffFileHeaders(_ show: Bool)` - Toggle file headers (the `diff --git` / `---` / `+++` block)
- `.diffHunkHeaders(_ show: Bool)` - Toggle the per-hunk `@@ -a,b +c,d @@` separator
- `.diffFont(size: CGFloat?, weight: Font.Weight?, design: Font.Design?)` - Configure font
- `.diffLineSpacing(_ spacing: LineSpacing)` - Set line spacing
- `.diffWordWrap(_ wrap: Bool)` - Enable word wrapping
- `.diffConfiguration(_ config: DiffConfiguration)` - Apply complete configuration
- `.diffParser(_ parser: any DiffParsing)` - Plug in a custom parser (see below)

### Mobile-friendly defaults

For phones and other narrow viewports the `.dual` gutter is cramped and the `@@` hunk header is rarely useful. A typical mobile renderer looks like:

```swift
DiffRenderer(diffText: text)
.diffLineNumberStyle(.single) // one column, new# for + / old# for -
.diffHunkHeaders(false) // hide @@ -a,b +c,d @@
.diffTheme(.dark)
```

There's also a `.mobile` preset that bundles these:

```swift
DiffRenderer(diffText: text)
.diffConfiguration(.mobile)
```

## Custom Diff Formats

`DiffRenderer` accepts unified-diff text by default. To consume any other format — annotated diffs, server-side payloads, JSON patches, language-server output — implement `DiffParsing` and inject it via the `.diffParser(_:)` modifier:
Expand Down
68 changes: 50 additions & 18 deletions Sources/gitdiff/DiffConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,65 @@ import SwiftUI
///
/// ## Topics
/// ### Creating Configurations
/// - ``init(theme:showLineNumbers:showFileHeaders:fontFamily:fontSize:fontWeight:lineHeight:lineSpacing:wordWrap:contentPadding:)``
/// - ``init(theme:showLineNumbers:lineNumberStyle:showFileHeaders:showHunkHeaders:fontFamily:fontSize:fontWeight:lineHeight:lineSpacing:wordWrap:contentPadding:)``
/// - ``default``
/// - ``compact``
/// - ``comfortable``
/// - ``darkMode``
public struct DiffConfiguration {
/// The theme to use for colors
public let theme: DiffTheme

/// Whether to show line numbers

/// Whether to show line numbers (legacy API — for richer control over
/// the gutter layout, prefer ``lineNumberStyle``).
public let showLineNumbers: Bool


/// How the line-number gutter is rendered. Defaults to ``LineNumberStyle/dual``
/// (the side-by-side `old | new` columns standard for unified diffs).
///
/// - ``LineNumberStyle/hidden``: no gutter at all (equivalent to
/// `showLineNumbers = false`).
/// - ``LineNumberStyle/single``: a single column showing the new line
/// number for added/context lines and the old line number for removed
/// lines — compact and well-suited to narrow viewports (mobile).
/// - ``LineNumberStyle/dual``: two columns, old then new — full context,
/// wider gutter, the desktop convention.
public let lineNumberStyle: LineNumberStyle

/// Whether to show file headers
public let showFileHeaders: Bool


/// Whether to show the per-hunk `@@ -x,y +x,y @@` separator line.
/// Default `true`; turn off for minimal renderers where the hunk
/// boundary is implied by the line backgrounds.
public let showHunkHeaders: Bool

/// Font family for code content
public let fontFamily: Font.Design

/// Font size for code content
public let fontSize: CGFloat

/// Font weight for code content
public let fontWeight: Font.Weight

/// Line height multiplier
public let lineHeight: CGFloat

/// Line spacing mode
public let lineSpacing: LineSpacing

/// Whether to wrap long lines
public let wordWrap: Bool

/// Padding for content
public let contentPadding: EdgeInsets

public enum LineSpacing {
case compact
case comfortable
case spacious

var value: CGFloat {
switch self {
case .compact: return 0
Expand All @@ -53,11 +71,20 @@ public struct DiffConfiguration {
}
}
}


/// How the line-number gutter is rendered. See ``lineNumberStyle``.
public enum LineNumberStyle: Sendable, Hashable {
case hidden
case single
case dual
}

public init(
theme: DiffTheme = .github,
showLineNumbers: Bool = true,
lineNumberStyle: LineNumberStyle? = nil,
showFileHeaders: Bool = true,
showHunkHeaders: Bool = true,
fontFamily: Font.Design = .monospaced,
fontSize: CGFloat = 13,
fontWeight: Font.Weight = .regular,
Expand All @@ -68,7 +95,12 @@ public struct DiffConfiguration {
) {
self.theme = theme
self.showLineNumbers = showLineNumbers
// If the caller didn't ask for a specific style we infer one from
// the legacy `showLineNumbers` flag so existing call sites keep
// their current behaviour.
self.lineNumberStyle = lineNumberStyle ?? (showLineNumbers ? .dual : .hidden)
self.showFileHeaders = showFileHeaders
self.showHunkHeaders = showHunkHeaders
self.fontFamily = fontFamily
self.fontSize = fontSize
self.fontWeight = fontWeight
Expand All @@ -82,23 +114,23 @@ public struct DiffConfiguration {
public extension DiffConfiguration {
/// Default GitHub-style configuration
static let `default` = DiffConfiguration()

/// Compact configuration with minimal spacing
static let compact = DiffConfiguration(
fontSize: 12,
lineSpacing: .compact,
contentPadding: EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)
)

/// Comfortable configuration with more spacing
static let comfortable = DiffConfiguration(
fontSize: 14,
lineSpacing: .comfortable,
contentPadding: EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)
)

/// Dark mode configuration with VS Code theme
static let darkMode = DiffConfiguration(
theme: .vsCodeDark
)
}
}
67 changes: 59 additions & 8 deletions Sources/gitdiff/DiffConfigurationBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ public extension DiffConfiguration {
DiffConfiguration(
theme: theme,
showLineNumbers: showLineNumbers,
lineNumberStyle: lineNumberStyle,
showFileHeaders: showFileHeaders,
showHunkHeaders: showHunkHeaders,
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: fontWeight,
Expand All @@ -17,13 +19,16 @@ public extension DiffConfiguration {
contentPadding: contentPadding
)
}

/// Creates a new configuration with line numbers toggled

/// Creates a new configuration with line numbers toggled (legacy API).
/// Prefer ``withLineNumberStyle(_:)`` for finer control.
func withLineNumbers(_ show: Bool) -> DiffConfiguration {
DiffConfiguration(
theme: theme,
showLineNumbers: show,
lineNumberStyle: show ? .dual : .hidden,
showFileHeaders: showFileHeaders,
showHunkHeaders: showHunkHeaders,
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: fontWeight,
Expand All @@ -33,13 +38,33 @@ public extension DiffConfiguration {
contentPadding: contentPadding
)
}


/// Creates a new configuration with the specified line-number gutter style.
func withLineNumberStyle(_ style: LineNumberStyle) -> DiffConfiguration {
DiffConfiguration(
theme: theme,
showLineNumbers: style != .hidden,
lineNumberStyle: style,
showFileHeaders: showFileHeaders,
showHunkHeaders: showHunkHeaders,
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: fontWeight,
lineHeight: lineHeight,
lineSpacing: lineSpacing,
wordWrap: wordWrap,
contentPadding: contentPadding
)
}

/// Creates a new configuration with the specified font settings
func withFont(size: CGFloat? = nil, weight: Font.Weight? = nil, design: Font.Design? = nil) -> DiffConfiguration {
DiffConfiguration(
theme: theme,
showLineNumbers: showLineNumbers,
lineNumberStyle: lineNumberStyle,
showFileHeaders: showFileHeaders,
showHunkHeaders: showHunkHeaders,
fontFamily: design ?? fontFamily,
fontSize: size ?? fontSize,
fontWeight: weight ?? fontWeight,
Expand All @@ -49,13 +74,15 @@ public extension DiffConfiguration {
contentPadding: contentPadding
)
}

/// Creates a new configuration with the specified line spacing
func withLineSpacing(_ spacing: LineSpacing) -> DiffConfiguration {
DiffConfiguration(
theme: theme,
showLineNumbers: showLineNumbers,
lineNumberStyle: lineNumberStyle,
showFileHeaders: showFileHeaders,
showHunkHeaders: showHunkHeaders,
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: fontWeight,
Expand All @@ -65,13 +92,33 @@ public extension DiffConfiguration {
contentPadding: contentPadding
)
}

/// Creates a new configuration with file headers toggled
func withFileHeaders(_ show: Bool) -> DiffConfiguration {
DiffConfiguration(
theme: theme,
showLineNumbers: showLineNumbers,
lineNumberStyle: lineNumberStyle,
showFileHeaders: show,
showHunkHeaders: showHunkHeaders,
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: fontWeight,
lineHeight: lineHeight,
lineSpacing: lineSpacing,
wordWrap: wordWrap,
contentPadding: contentPadding
)
}

/// Creates a new configuration with hunk headers toggled
func withHunkHeaders(_ show: Bool) -> DiffConfiguration {
DiffConfiguration(
theme: theme,
showLineNumbers: showLineNumbers,
lineNumberStyle: lineNumberStyle,
showFileHeaders: showFileHeaders,
showHunkHeaders: show,
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: fontWeight,
Expand All @@ -87,7 +134,9 @@ public extension DiffConfiguration {
DiffConfiguration(
theme: theme,
showLineNumbers: showLineNumbers,
lineNumberStyle: lineNumberStyle,
showFileHeaders: showFileHeaders,
showHunkHeaders: showHunkHeaders,
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: fontWeight,
Expand All @@ -108,16 +157,18 @@ public extension DiffConfiguration {
.withLineNumbers(true)
.withFont(size: 13)
.withLineSpacing(.comfortable)

/// Configuration optimized for mobile with smaller fonts.
static let mobile = DiffConfiguration.default
.withLineNumberStyle(.single)
.withHunkHeaders(false)
.withFont(size: 12)
.withLineSpacing(.compact)
.withWordWrap(true)

/// Configuration for presentations with larger fonts.
static let presentation = DiffConfiguration.default
.withFont(size: 16, weight: .medium)
.withLineSpacing(.spacious)
.withLineNumbers(false)
}
}
Loading
Loading