@@ -22,6 +22,11 @@ struct AttributedStringVisitor: MarkupVisitor {
2222 private var shouldAddCustomAttr : Bool {
2323 return formattingOptions? . addCustomMarkdownElementAttributes ?? false
2424 }
25+ private var currentUnorderedListEl : MarkdownElementAttribute ?
26+ private var currentUnorderedListLevel : Int = 0
27+ private var currentOrderedListEl : MarkdownElementAttribute ?
28+ private var currentOrderedListLevel : Int = 0
29+
2530 private let loggingQ = DispatchQueue ( label: " MTAS.logging " )
2631
2732 init ( markdown: String ,
@@ -38,7 +43,7 @@ struct AttributedStringVisitor: MarkupVisitor {
3843 let document = Document ( parsing: markdown)
3944 visit ( document)
4045
41- // Apparently something in SwiftMarkdown is sometimes adding a non-breakable space at the end. Remove it.
46+ // Apparently something in SwiftMarkdown is sometimes adding a zero-width space at the end. Remove it.
4247 if attributedString. string. hasSuffix ( " \u{200B} " ) {
4348 attributedString. deleteCharacters ( in: NSRange ( location: attributedString. length - 1 , length: 1 ) )
4449 }
@@ -78,7 +83,15 @@ struct AttributedStringVisitor: MarkupVisitor {
7883
7984 mutating func visitInlineHTML( _ inlineHTML: InlineHTML ) {
8085 if inlineHTML. rawHTML == " <br> " {
81- appendNewline ( )
86+ debugLog ( " <open> " , file: " " )
87+ if shouldAddCustomAttr {
88+ var attrs = markdownStyles. baseAttributes
89+ attrs. mergeAttributes ( [ . forcedLineBreak: true ] )
90+ appendNewline ( attrs: attrs)
91+ } else {
92+ appendNewline ( )
93+ }
94+ debugLog ( " <close> " , file: " " )
8295 }
8396 }
8497
@@ -92,7 +105,15 @@ struct AttributedStringVisitor: MarkupVisitor {
92105 visitChildren ( of: paragraph)
93106
94107 if paragraph. hasSuccessor {
95- appendNewline ( )
108+ var attrs = markdownStyles. baseAttributes
109+
110+ // If this paragraph is part of a container block, be sure to attach the block to the newline's attrs
111+ if paragraph. isChildOfUnorderedList, let currentUnorderedListEl {
112+ attrs [ . markdownElements] = MarkdownElementAttributes ( [ . unorderedList: currentUnorderedListEl] )
113+ } else if paragraph. isChildOfOrderedList, let currentOrderedListEl {
114+ attrs [ . markdownElements] = MarkdownElementAttributes ( [ . orderedList: currentOrderedListEl] )
115+ }
116+ appendNewline ( attrs: attrs)
96117 }
97118
98119 debugLog ( " <close> " , file: " " )
@@ -133,7 +154,6 @@ struct AttributedStringVisitor: MarkupVisitor {
133154 if parent is Strong {
134155 if let baseFont = styleAttrs [ . font] as? CocoaFont {
135156 if shouldAddCustomAttr {
136-
137157 styleAttrs. addMarkdownElementAttr (
138158 MarkdownElementAttribute ( elementType: . strong)
139159 )
@@ -191,22 +211,28 @@ struct AttributedStringVisitor: MarkupVisitor {
191211
192212 debugLog ( " <close> " , file: " " )
193213 }
194-
195- /// NB about lists and SwiftMarkdown: SM considers *each* top level list item a separate list, so you can expect this to be called recursively once for each top level item. (Which yes, makes handling newlines a challenge.)
214+
215+ /// NB about lists and SwiftMarkdown: SM considers nested lists a separate list, i.e. a list can be a child of a list item , so you can expect this to be called recursively for each nested list.
196216 mutating func visitUnorderedList( _ unorderedList: UnorderedList ) {
197217 guard optionsSupportEl ( . unorderedList) else {
198218 appendNewline ( )
199219 debugLog ( " Skipping unsupported: unorderedList " ) ; return
200220 }
201221 debugLog ( " <open> " , file: " " )
202-
203- var styleAttrs = markdownStyles. attributesForType ( . unorderedList)
222+
204223 let previousAttributes = currentAttributes. deepCopy ( )
224+ var styleAttrs = markdownStyles. attributesForType ( . unorderedList)
225+
226+ if ( currentUnorderedListEl == nil ) {
227+ assert ( currentUnorderedListLevel == 0 )
228+ currentUnorderedListEl = MarkdownElementAttribute ( elementType: . unorderedList)
229+ }
230+
231+ currentUnorderedListLevel += 1
205232
206233 if shouldAddCustomAttr {
207- styleAttrs. addMarkdownElementAttr (
208- MarkdownElementAttribute ( elementType: . unorderedList)
209- )
234+ guard let currentUnorderedListEl else { assertionFailure ( ) ; return }
235+ styleAttrs. addMarkdownElementAttr ( currentUnorderedListEl)
210236 }
211237
212238 currentAttributes. mergeAttributes ( styleAttrs)
@@ -222,6 +248,11 @@ struct AttributedStringVisitor: MarkupVisitor {
222248 if unorderedList. hasSuccessor {
223249 appendNewline ( )
224250 }
251+
252+ currentUnorderedListLevel -= 1
253+ if currentUnorderedListLevel == 0 {
254+ currentUnorderedListEl = nil
255+ }
225256
226257 currentAttributes = previousAttributes. deepCopy ( )
227258
@@ -230,39 +261,53 @@ struct AttributedStringVisitor: MarkupVisitor {
230261
231262 mutating func visitOrderedList( _ orderedList: OrderedList ) {
232263 guard optionsSupportEl ( . orderedList) else {
233- debugLog ( " Skipping unsupported: orderedList " )
234264 appendNewline ( )
235- return
265+ debugLog ( " Skipping unsupported: orderedList " ) ; return
236266 }
237267 debugLog ( " <open> " , file: " " )
238- var styleAttrs = markdownStyles . attributesForType ( . orderedList )
268+
239269 let previousAttributes = currentAttributes. deepCopy ( )
270+ var styleAttrs = markdownStyles. attributesForType ( . orderedList)
271+
272+ if ( currentOrderedListEl == nil ) {
273+ assert ( currentOrderedListLevel == 0 )
274+ currentOrderedListEl = MarkdownElementAttribute ( elementType: . orderedList)
275+ }
240276
277+ currentOrderedListLevel += 1
278+
241279 if shouldAddCustomAttr {
242- styleAttrs. addMarkdownElementAttr (
243- MarkdownElementAttribute ( elementType: . orderedList)
244- )
280+ guard let currentOrderedListEl else { assertionFailure ( ) ; return }
281+ styleAttrs. addMarkdownElementAttr ( currentOrderedListEl)
245282 }
246283
247284 currentAttributes. mergeAttributes ( styleAttrs)
248285
249- var itemIndex = 1
250286 for child in orderedList. children {
251287 if let listItem = child as? ListItem {
252- visitListItem ( listItem, index: itemIndex)
253- itemIndex += 1
288+ visitListItem ( listItem, orderedIndex: Int ( orderedList. startIndex) + child. indexInParent)
254289 } else {
255290 visit ( child)
256291 }
257292 }
258293
294+ if orderedList. hasSuccessor {
295+ appendNewline ( )
296+ }
297+
298+ currentOrderedListLevel -= 1
299+ if currentOrderedListLevel == 0 {
300+ currentOrderedListEl = nil
301+ }
302+
259303 currentAttributes = previousAttributes. deepCopy ( )
260304
261305 debugLog ( " <close> " , file: " " )
262306 }
263307
264308
265- mutating func visitListItem( _ listItem: ListItem , index: Int ? = nil ) {
309+ // orderedIndex is non-nil when part of an ordered list
310+ mutating func visitListItem( _ listItem: ListItem , orderedIndex: Int ? = nil ) {
266311 guard optionsSupportEl ( . listItem) else {
267312 debugLog ( " Skipping unsupported: listItem " )
268313 appendNewline ( )
@@ -274,11 +319,22 @@ struct AttributedStringVisitor: MarkupVisitor {
274319
275320 currentAttributes. mergeAttributes ( styleAttrs)
276321
322+ if listItem. isFirst,
323+ let pstyle = markdownStyles. blockStartParagraphStyle
324+ {
325+ currentAttributes. mergeAttributes ( [ . paragraphStyle: pstyle] )
326+ } else if listItem. isLast,
327+ let pstyle = markdownStyles. blockEndParagraphStyle
328+ {
329+ currentAttributes. mergeAttributes ( [ . paragraphStyle: pstyle] )
330+ }
331+
332+
277333 let prefix : String
278334 let renderedDelimiter : String
279- if let index = index {
280- prefix = " \( index ) . "
281- renderedDelimiter = markdownStyles . unorderedListBullets [ 0 ]
335+ if let orderedIndex = orderedIndex {
336+ prefix = " \t \( orderedIndex ) . "
337+ renderedDelimiter = " \( orderedIndex ) "
282338 } else {
283339 let bullets = markdownStyles. unorderedListBullets
284340 renderedDelimiter = bullets [ listItem. listDepth % bullets. count]
@@ -287,7 +343,8 @@ struct AttributedStringVisitor: MarkupVisitor {
287343 }
288344
289345 if shouldAddCustomAttr {
290- var typedDelimiter = " - "
346+ // For ordered lists we can consider the typedDelimiter and renderedDelimiter to be the same.
347+ var typedDelimiter = orderedIndex != nil ? renderedDelimiter : " - "
291348 if let lowerBound = listItem. range? . lowerBound,
292349 let char = markdown. characterAt ( line: lowerBound. line, col: lowerBound. column)
293350 {
@@ -298,6 +355,7 @@ struct AttributedStringVisitor: MarkupVisitor {
298355 ListItemMarkdownElementAttribute (
299356 listDepth: listItem. listDepth,
300357 indexInParent: listItem. indexInParent,
358+ orderedIndex: orderedIndex,
301359 prefix: prefix,
302360 typedDelimiter: typedDelimiter,
303361 renderedDelimiter: renderedDelimiter)
@@ -309,8 +367,9 @@ struct AttributedStringVisitor: MarkupVisitor {
309367
310368 visitChildren ( of: listItem)
311369
312- if listItem. hasSuccessor {
313- appendNewline ( )
370+ // Don't add a newline if this had child lists, because those child list items add their own newlines
371+ if !listItem. hasChildList {
372+ appendNewline ( attrs: currentAttributes)
314373 }
315374
316375 currentAttributes = previousAttributes. deepCopy ( )
@@ -507,15 +566,16 @@ struct AttributedStringVisitor: MarkupVisitor {
507566 attributedString. append ( NSAttributedString ( string: string, attributes: actualAttrs) )
508567 }
509568
510- private mutating func appendNewline( ) {
569+ private mutating func appendNewline( attrs : StringAttrs ? = nil ) {
511570 debugLog ( " appending newline " )
512- appendPlainText ( " \n " )
571+ appendPlainText ( " \n " , attrs : attrs )
513572 }
514573
515- private func appendPlainText( _ plainText: String ) {
574+ private func appendPlainText( _ plainText: String , attrs: StringAttrs ? = nil ) {
575+ let actualAttrs = attrs ?? markdownStyles. baseAttributes
516576 attributedString. append ( NSAttributedString (
517577 string: plainText,
518- attributes: markdownStyles . baseAttributes ) )
578+ attributes: actualAttrs ) )
519579 }
520580
521581 private func debugLog( _ message: String , file: String = #file, line: Int = #line, function: String = #function) {
@@ -533,7 +593,6 @@ struct AttributedStringVisitor: MarkupVisitor {
533593}
534594
535595extension ListItem {
536-
537596 // Nesting depth of the list item; 0 indexed.
538597 var listDepth : Int {
539598 var depth = 0
@@ -546,6 +605,21 @@ extension ListItem {
546605 }
547606 return max ( 0 , depth - 1 )
548607 }
608+
609+ var isFirst : Bool {
610+ return indexInParent == 0 && listDepth == 0
611+ }
612+
613+ var isLast : Bool {
614+ guard let parent = self . parent else {
615+ return false
616+ }
617+ return indexInParent == parent. childCount - 1
618+ }
619+
620+ var hasChildList : Bool {
621+ return children. contains { $0 is OrderedList || $0 is UnorderedList }
622+ }
549623}
550624
551625extension Markup {
@@ -565,19 +639,41 @@ extension Markup {
565639 return NSRange ( location: start,
566640 length: range. upperBound. column - start - 1 )
567641 }
642+
643+ var isChildOfUnorderedList : Bool {
644+ var current : Markup ? = self
645+ while let parent = current? . parent {
646+ if parent is UnorderedList {
647+ return true
648+ }
649+ current = parent
650+ }
651+ return false
652+ }
653+
654+ var isChildOfOrderedList : Bool {
655+ var current : Markup ? = self
656+ while let parent = current? . parent {
657+ if parent is OrderedList {
658+ return true
659+ }
660+ current = parent
661+ }
662+ return false
663+ }
568664}
569665
570666extension StringAttrs {
571667 mutating func mergeAttributes( _ otherAttrs: StringAttrs ) {
572668 for (key, val) in otherAttrs {
573669 if key == . markdownElements, let val = val as? MarkdownElementAttributes {
574- var attrs = ( self [ . markdownElements] as? MarkdownElementAttributes ) ? . copy ( ) as? MarkdownElementAttributes ?? MarkdownElementAttributes ( )
670+ let elAttrs = ( self [ . markdownElements] as? MarkdownElementAttributes ) ? . copy ( ) as? MarkdownElementAttributes ?? MarkdownElementAttributes ( )
575671
576- for (markupType , newAttribute) in val. allAttributes {
577- attrs . set ( markupType , value : newAttribute)
672+ for (_ , newAttribute) in val. allAttributes {
673+ elAttrs . add ( newAttribute)
578674 }
579675
580- self [ . markdownElements] = attrs
676+ self [ . markdownElements] = elAttrs
581677 } else {
582678 self [ key] = val
583679 }
0 commit comments