Skip to content

Commit 5544181

Browse files
authored
Allow customizing soft break mode (#342)
* Allow customizing soft break mode Resolved #341 * Attributed renderer * Tests * Update snapshots
1 parent 9a8119b commit 5544181

File tree

8 files changed

+104
-5
lines changed

8 files changed

+104
-5
lines changed

Sources/MarkdownUI/DSL/Inlines/SoftBreak.swift

+10
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,13 @@ public struct SoftBreak: InlineContentProtocol {
1111
.init(inlines: [.softBreak])
1212
}
1313
}
14+
15+
extension SoftBreak {
16+
public enum Mode {
17+
/// Treat a soft break as a space
18+
case space
19+
20+
/// Treat a soft break as a line break
21+
case lineBreak
22+
}
23+
}

Sources/MarkdownUI/Renderer/AttributedStringInlineRenderer.swift

+15-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ extension InlineNode {
44
func renderAttributedString(
55
baseURL: URL?,
66
textStyles: InlineTextStyles,
7+
softBreakMode: SoftBreak.Mode,
78
attributes: AttributeContainer
89
) -> AttributedString {
910
var renderer = AttributedStringInlineRenderer(
1011
baseURL: baseURL,
1112
textStyles: textStyles,
13+
softBreakMode: softBreakMode,
1214
attributes: attributes
1315
)
1416
renderer.render(self)
@@ -21,12 +23,19 @@ private struct AttributedStringInlineRenderer {
2123

2224
private let baseURL: URL?
2325
private let textStyles: InlineTextStyles
26+
private let softBreakMode: SoftBreak.Mode
2427
private var attributes: AttributeContainer
2528
private var shouldSkipNextWhitespace = false
2629

27-
init(baseURL: URL?, textStyles: InlineTextStyles, attributes: AttributeContainer) {
30+
init(
31+
baseURL: URL?,
32+
textStyles: InlineTextStyles,
33+
softBreakMode: SoftBreak.Mode,
34+
attributes: AttributeContainer
35+
) {
2836
self.baseURL = baseURL
2937
self.textStyles = textStyles
38+
self.softBreakMode = softBreakMode
3039
self.attributes = attributes
3140
}
3241

@@ -67,10 +76,13 @@ private struct AttributedStringInlineRenderer {
6776
}
6877

6978
private mutating func renderSoftBreak() {
70-
if self.shouldSkipNextWhitespace {
79+
switch softBreakMode {
80+
case .space where self.shouldSkipNextWhitespace:
7181
self.shouldSkipNextWhitespace = false
72-
} else {
82+
case .space:
7383
self.result += .init(" ", attributes: self.attributes)
84+
case .lineBreak:
85+
self.renderLineBreak()
7486
}
7587
}
7688

Sources/MarkdownUI/Renderer/TextInlineRenderer.swift

+12-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ extension Sequence where Element == InlineNode {
55
baseURL: URL?,
66
textStyles: InlineTextStyles,
77
images: [String: Image],
8+
softBreakMode: SoftBreak.Mode,
89
attributes: AttributeContainer
910
) -> Text {
1011
var renderer = TextInlineRenderer(
1112
baseURL: baseURL,
1213
textStyles: textStyles,
1314
images: images,
15+
softBreakMode: softBreakMode,
1416
attributes: attributes
1517
)
1618
renderer.render(self)
@@ -24,18 +26,21 @@ private struct TextInlineRenderer {
2426
private let baseURL: URL?
2527
private let textStyles: InlineTextStyles
2628
private let images: [String: Image]
29+
private let softBreakMode: SoftBreak.Mode
2730
private let attributes: AttributeContainer
2831
private var shouldSkipNextWhitespace = false
2932

3033
init(
3134
baseURL: URL?,
3235
textStyles: InlineTextStyles,
3336
images: [String: Image],
37+
softBreakMode: SoftBreak.Mode,
3438
attributes: AttributeContainer
3539
) {
3640
self.baseURL = baseURL
3741
self.textStyles = textStyles
3842
self.images = images
43+
self.softBreakMode = softBreakMode
3944
self.attributes = attributes
4045
}
4146

@@ -72,10 +77,14 @@ private struct TextInlineRenderer {
7277
}
7378

7479
private mutating func renderSoftBreak() {
75-
if self.shouldSkipNextWhitespace {
80+
switch self.softBreakMode {
81+
case .space where self.shouldSkipNextWhitespace:
7682
self.shouldSkipNextWhitespace = false
77-
} else {
83+
case .space:
7884
self.defaultRender(.softBreak)
85+
case .lineBreak:
86+
self.shouldSkipNextWhitespace = true
87+
self.defaultRender(.lineBreak)
7988
}
8089
}
8190

@@ -104,6 +113,7 @@ private struct TextInlineRenderer {
104113
inline.renderAttributedString(
105114
baseURL: self.baseURL,
106115
textStyles: self.textStyles,
116+
softBreakMode: self.softBreakMode,
107117
attributes: self.attributes
108118
)
109119
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import SwiftUI
2+
3+
extension View {
4+
/// Sets the soft break mode for inline texts in a view hierarchy.
5+
///
6+
/// - 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
7+
///
8+
/// - Returns: A view that uses the specified soft break mode for itself and its child views.
9+
public func markdownSoftBreakMode(_ softBreakMode: SoftBreak.Mode) -> some View {
10+
self.environment(\.softBreakMode, softBreakMode)
11+
}
12+
}
13+
14+
extension EnvironmentValues {
15+
var softBreakMode: SoftBreak.Mode {
16+
get { self[SoftBreakModeKey.self] }
17+
set { self[SoftBreakModeKey.self] = newValue }
18+
}
19+
}
20+
21+
private struct SoftBreakModeKey: EnvironmentKey {
22+
static let defaultValue: SoftBreak.Mode = .space
23+
}

Sources/MarkdownUI/Views/Inlines/InlineText.swift

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ struct InlineText: View {
44
@Environment(\.inlineImageProvider) private var inlineImageProvider
55
@Environment(\.baseURL) private var baseURL
66
@Environment(\.imageBaseURL) private var imageBaseURL
7+
@Environment(\.softBreakMode) private var softBreakMode
78
@Environment(\.theme) private var theme
89

910
@State private var inlineImages: [String: Image] = [:]
@@ -26,6 +27,7 @@ struct InlineText: View {
2627
link: self.theme.link
2728
),
2829
images: self.inlineImages,
30+
softBreakMode: self.softBreakMode,
2931
attributes: attributes
3032
)
3133
}

Tests/MarkdownUITests/MarkdownTests.swift

+42
Original file line numberDiff line numberDiff line change
@@ -300,5 +300,47 @@
300300

301301
assertSnapshot(of: view, as: .image(layout: layout))
302302
}
303+
304+
func testSoftBreakModeSpace() {
305+
let view = Markdown {
306+
#"""
307+
# This is a heading
308+
309+
Item 1
310+
Item 2
311+
Item 3
312+
Item 4
313+
314+
I would **very much** like to write
315+
A long paragraph that spans _multiple lines_
316+
But should ~~render differently~~ based on
317+
soft break mode
318+
"""#
319+
}
320+
.markdownSoftBreakMode(.space)
321+
322+
assertSnapshot(of: view, as: .image(layout: layout))
323+
}
324+
325+
func testSoftBreakModeLineBreak() {
326+
let view = Markdown {
327+
#"""
328+
# This is a heading
329+
330+
Item 1
331+
Item 2
332+
Item 3
333+
Item 4
334+
335+
I would **very much** like to write
336+
A long paragraph that spans _multiple lines_
337+
But should ~~render differently~~ based on
338+
soft break mode
339+
"""#
340+
}
341+
.markdownSoftBreakMode(.lineBreak)
342+
343+
assertSnapshot(of: view, as: .image(layout: layout))
344+
}
303345
}
304346
#endif
Loading
Loading

0 commit comments

Comments
 (0)