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
10 changes: 10 additions & 0 deletions Sources/MarkdownUI/DSL/Inlines/SoftBreak.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,13 @@ public struct SoftBreak: InlineContentProtocol {
.init(inlines: [.softBreak])
}
}

extension SoftBreak {
public enum Mode {
/// Treat a soft break as a space
case space

/// Treat a soft break as a line break
case lineBreak
}
}
18 changes: 15 additions & 3 deletions Sources/MarkdownUI/Renderer/AttributedStringInlineRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ extension InlineNode {
func renderAttributedString(
baseURL: URL?,
textStyles: InlineTextStyles,
softBreakMode: SoftBreak.Mode,
attributes: AttributeContainer
) -> AttributedString {
var renderer = AttributedStringInlineRenderer(
baseURL: baseURL,
textStyles: textStyles,
softBreakMode: softBreakMode,
attributes: attributes
)
renderer.render(self)
Expand All @@ -21,12 +23,19 @@ private struct AttributedStringInlineRenderer {

private let baseURL: URL?
private let textStyles: InlineTextStyles
private let softBreakMode: SoftBreak.Mode
private var attributes: AttributeContainer
private var shouldSkipNextWhitespace = false

init(baseURL: URL?, textStyles: InlineTextStyles, attributes: AttributeContainer) {
init(
baseURL: URL?,
textStyles: InlineTextStyles,
softBreakMode: SoftBreak.Mode,
attributes: AttributeContainer
) {
self.baseURL = baseURL
self.textStyles = textStyles
self.softBreakMode = softBreakMode
self.attributes = attributes
}

Expand Down Expand Up @@ -67,10 +76,13 @@ private struct AttributedStringInlineRenderer {
}

private mutating func renderSoftBreak() {
if self.shouldSkipNextWhitespace {
switch softBreakMode {
case .space where self.shouldSkipNextWhitespace:
self.shouldSkipNextWhitespace = false
} else {
case .space:
self.result += .init(" ", attributes: self.attributes)
case .lineBreak:
self.renderLineBreak()
}
}

Expand Down
14 changes: 12 additions & 2 deletions Sources/MarkdownUI/Renderer/TextInlineRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ extension Sequence where Element == InlineNode {
baseURL: URL?,
textStyles: InlineTextStyles,
images: [String: Image],
softBreakMode: SoftBreak.Mode,
attributes: AttributeContainer
) -> Text {
var renderer = TextInlineRenderer(
baseURL: baseURL,
textStyles: textStyles,
images: images,
softBreakMode: softBreakMode,
attributes: attributes
)
renderer.render(self)
Expand All @@ -24,18 +26,21 @@ private struct TextInlineRenderer {
private let baseURL: URL?
private let textStyles: InlineTextStyles
private let images: [String: Image]
private let softBreakMode: SoftBreak.Mode
private let attributes: AttributeContainer
private var shouldSkipNextWhitespace = false

init(
baseURL: URL?,
textStyles: InlineTextStyles,
images: [String: Image],
softBreakMode: SoftBreak.Mode,
attributes: AttributeContainer
) {
self.baseURL = baseURL
self.textStyles = textStyles
self.images = images
self.softBreakMode = softBreakMode
self.attributes = attributes
}

Expand Down Expand Up @@ -72,10 +77,14 @@ private struct TextInlineRenderer {
}

private mutating func renderSoftBreak() {
if self.shouldSkipNextWhitespace {
switch self.softBreakMode {
case .space where self.shouldSkipNextWhitespace:
self.shouldSkipNextWhitespace = false
} else {
case .space:
self.defaultRender(.softBreak)
case .lineBreak:
self.shouldSkipNextWhitespace = true
self.defaultRender(.lineBreak)
}
}

Expand Down Expand Up @@ -104,6 +113,7 @@ private struct TextInlineRenderer {
inline.renderAttributedString(
baseURL: self.baseURL,
textStyles: self.textStyles,
softBreakMode: self.softBreakMode,
attributes: self.attributes
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import SwiftUI

extension View {
/// Sets the soft break mode for inline texts in a view hierarchy.
///
/// - parameter softBreakMode: If set to `space`, treats all soft breaks as spaces, keeping sentences whole. If set to `lineBreak`, treats soft breaks as full line breaks
///
/// - Returns: A view that uses the specified soft break mode for itself and its child views.
public func markdownSoftBreakMode(_ softBreakMode: SoftBreak.Mode) -> some View {
self.environment(\.softBreakMode, softBreakMode)
}
}

extension EnvironmentValues {
var softBreakMode: SoftBreak.Mode {
get { self[SoftBreakModeKey.self] }
set { self[SoftBreakModeKey.self] = newValue }
}
}

private struct SoftBreakModeKey: EnvironmentKey {
static let defaultValue: SoftBreak.Mode = .space
}
2 changes: 2 additions & 0 deletions Sources/MarkdownUI/Views/Inlines/InlineText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ struct InlineText: View {
@Environment(\.inlineImageProvider) private var inlineImageProvider
@Environment(\.baseURL) private var baseURL
@Environment(\.imageBaseURL) private var imageBaseURL
@Environment(\.softBreakMode) private var softBreakMode
@Environment(\.theme) private var theme

@State private var inlineImages: [String: Image] = [:]
Expand All @@ -26,6 +27,7 @@ struct InlineText: View {
link: self.theme.link
),
images: self.inlineImages,
softBreakMode: self.softBreakMode,
attributes: attributes
)
}
Expand Down
42 changes: 42 additions & 0 deletions Tests/MarkdownUITests/MarkdownTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -300,5 +300,47 @@

assertSnapshot(of: view, as: .image(layout: layout))
}

func testSoftBreakModeSpace() {
let view = Markdown {
#"""
# This is a heading

Item 1
Item 2
Item 3
Item 4

I would **very much** like to write
A long paragraph that spans _multiple lines_
But should ~~render differently~~ based on
soft break mode
"""#
}
.markdownSoftBreakMode(.space)

assertSnapshot(of: view, as: .image(layout: layout))
}

func testSoftBreakModeLineBreak() {
let view = Markdown {
#"""
# This is a heading

Item 1
Item 2
Item 3
Item 4

I would **very much** like to write
A long paragraph that spans _multiple lines_
But should ~~render differently~~ based on
soft break mode
"""#
}
.markdownSoftBreakMode(.lineBreak)

assertSnapshot(of: view, as: .image(layout: layout))
}
}
#endif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading