@@ -29,7 +29,7 @@ struct AttributedStringVisitor: MarkupVisitor {
2929 options: FormattingOptions ? = nil ) {
3030 self . markdown = markdown
3131 self . markdownAttributes = attributes ?? MarkdownAttributes . default
32- self . currentAttributes = self . markdownAttributes. baseAttributes
32+ self . currentAttributes = self . markdownAttributes. baseAttributes. deepCopy ( )
3333 self . formattingOptions = options
3434 }
3535
@@ -58,27 +58,27 @@ struct AttributedStringVisitor: MarkupVisitor {
5858
5959 mutating func visitText( _ text: Text ) {
6060 debugLog ( " <open> " , file: " " )
61- debugLog ( " appending: \( text. string) " )
6261 appendToAttrStr ( string: text. string)
6362 debugLog ( " <close> " , file: " " )
6463 }
6564
65+ // We deviate from spec here by inserting newlines for soft breaks. This makes dealing with external input, which may not have been crafted for markdown specifically, better. In the future this could be gated by a `strict` options flag.
6666 mutating func visitSoftBreak( _ softBreak: SoftBreak ) {
6767 debugLog ( " <open> " , file: " " )
68- appendToAttrStr ( string : " \n " )
68+ appendNewline ( )
6969 debugLog ( " <close> " , file: " " )
7070 }
7171
72- // NB: I've never seen this called!
72+ // This is for hard line breaks, but SwiftMarkdown only seems to support the "\\\n" variety, not " \n".
7373 mutating func visitLineBreak( _ lineBreak: LineBreak ) {
7474 debugLog ( " <open> " , file: " " )
75- appendToAttrStr ( string : " \n " )
75+ appendNewline ( )
7676 debugLog ( " <close> " , file: " " )
7777 }
7878
7979 mutating func visitInlineHTML( _ inlineHTML: InlineHTML ) {
8080 if inlineHTML. rawHTML == " <br> " {
81- appendToAttrStr ( string : " \n " )
81+ appendNewline ( )
8282 }
8383 }
8484
@@ -172,7 +172,7 @@ struct AttributedStringVisitor: MarkupVisitor {
172172 }
173173 debugLog ( " <open> " , file: " " )
174174
175- let previousAttributes = currentAttributes
175+ let previousAttributes = currentAttributes. deepCopy ( )
176176 var styleAttrs = markdownAttributes. attributesForType ( . codeBlock)
177177
178178 if shouldAddCustomAttr {
@@ -183,7 +183,7 @@ struct AttributedStringVisitor: MarkupVisitor {
183183
184184 appendToAttrStr ( string: codeBlock. code, attrs: styleAttrs)
185185
186- currentAttributes = previousAttributes
186+ currentAttributes = previousAttributes. deepCopy ( )
187187
188188 if codeBlock. hasSuccessor {
189189 appendNewline ( )
@@ -195,13 +195,13 @@ struct AttributedStringVisitor: MarkupVisitor {
195195 /// 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.)
196196 mutating func visitUnorderedList( _ unorderedList: UnorderedList ) {
197197 guard optionsSupportEl ( . unorderedList) else {
198- appendPlainText ( " \n " )
198+ appendNewline ( )
199199 debugLog ( " Skipping unsupported: unorderedList " ) ; return
200200 }
201201 debugLog ( " <open> " , file: " " )
202202
203203 var styleAttrs = markdownAttributes. attributesForType ( . unorderedList)
204- let previousAttributes = currentAttributes
204+ let previousAttributes = currentAttributes. deepCopy ( )
205205
206206 if shouldAddCustomAttr {
207207 styleAttrs. addMarkdownElementAttr (
@@ -223,19 +223,20 @@ struct AttributedStringVisitor: MarkupVisitor {
223223 appendNewline ( )
224224 }
225225
226- currentAttributes = previousAttributes
226+ currentAttributes = previousAttributes. deepCopy ( )
227227
228228 debugLog ( " <close> " , file: " " )
229229 }
230230
231231 mutating func visitOrderedList( _ orderedList: OrderedList ) {
232232 guard optionsSupportEl ( . orderedList) else {
233- appendPlainText ( " \n " )
234- debugLog ( " Skipping unsupported: orderedList " ) ; return
233+ debugLog ( " Skipping unsupported: orderedList " )
234+ appendNewline ( )
235+ return
235236 }
236237 debugLog ( " <open> " , file: " " )
237238 var styleAttrs = markdownAttributes. attributesForType ( . orderedList)
238- let previousAttributes = currentAttributes
239+ let previousAttributes = currentAttributes. deepCopy ( )
239240
240241 if shouldAddCustomAttr {
241242 styleAttrs. addMarkdownElementAttr (
@@ -255,38 +256,51 @@ struct AttributedStringVisitor: MarkupVisitor {
255256 }
256257 }
257258
258- currentAttributes = previousAttributes
259+ currentAttributes = previousAttributes. deepCopy ( )
259260
260261 debugLog ( " <close> " , file: " " )
261262 }
262263
263264
264265 mutating func visitListItem( _ listItem: ListItem , index: Int ? = nil ) {
265266 guard optionsSupportEl ( . listItem) else {
266- appendPlainText ( " \n " )
267- debugLog ( " Skipping unsupported: listItem " ) ; return
267+ debugLog ( " Skipping unsupported: listItem " )
268+ appendNewline ( )
269+ return
268270 }
269271 debugLog ( " <open> " , file: " " )
270272 var styleAttrs = markdownAttributes. attributesForType ( . listItem)
271- let previousAttributes = currentAttributes
273+ let previousAttributes = currentAttributes. deepCopy ( )
272274
273275 currentAttributes. mergeAttributes ( styleAttrs)
274-
276+
275277 let prefix : String
278+ let renderedDelimiter : String
276279 if let index = index {
277280 prefix = " \( index) . "
281+ renderedDelimiter = " • "
278282 } else {
279283 let bullets = [ " • " , " ◦ " , " ▪ " , " ▫ " ]
284+ renderedDelimiter = bullets [ listItem. listDepth % bullets. count]
280285 let tabs = String ( repeating: " \t " , count: listItem. listDepth + 1 )
281- prefix = tabs + bullets [ listItem . listDepth % bullets . count ] + " "
286+ prefix = tabs + renderedDelimiter + " "
282287 }
283288
284289 if shouldAddCustomAttr {
290+ var typedDelimiter = " - "
291+ if let lowerBound = listItem. range? . lowerBound,
292+ let char = markdown. characterAt ( line: lowerBound. line, col: lowerBound. column)
293+ {
294+ typedDelimiter = String ( char)
295+ }
296+
285297 styleAttrs. addMarkdownElementAttr (
286298 ListItemMarkdownElementAttribute (
287299 listDepth: listItem. listDepth,
288300 indexInParent: listItem. indexInParent,
289- prefix: prefix)
301+ prefix: prefix,
302+ typedDelimiter: typedDelimiter,
303+ renderedDelimiter: renderedDelimiter)
290304 )
291305 }
292306 currentAttributes. mergeAttributes ( styleAttrs)
@@ -299,7 +313,7 @@ struct AttributedStringVisitor: MarkupVisitor {
299313 appendNewline ( )
300314 }
301315
302- currentAttributes = previousAttributes
316+ currentAttributes = previousAttributes. deepCopy ( )
303317 debugLog ( " <close> " , file: " " )
304318 }
305319
@@ -327,7 +341,7 @@ struct AttributedStringVisitor: MarkupVisitor {
327341 }
328342 debugLog ( " <open> " , file: " " )
329343
330- let previousAttributes = currentAttributes
344+ let previousAttributes = currentAttributes. deepCopy ( )
331345
332346 let level = max ( 1 , min ( heading. level, 6 ) )
333347
@@ -358,7 +372,7 @@ struct AttributedStringVisitor: MarkupVisitor {
358372 appendNewline ( )
359373 }
360374
361- currentAttributes = previousAttributes
375+ currentAttributes = previousAttributes. deepCopy ( )
362376 debugLog ( " <close> " , file: " " )
363377 }
364378
@@ -383,12 +397,12 @@ struct AttributedStringVisitor: MarkupVisitor {
383397 styleAttrs [ . link] = url
384398 }
385399
386- let previousAttributes = currentAttributes
400+ let previousAttributes = currentAttributes. deepCopy ( )
387401 currentAttributes. mergeAttributes ( styleAttrs)
388402
389403 visitChildren ( of: link)
390404
391- currentAttributes = previousAttributes
405+ currentAttributes = previousAttributes. deepCopy ( )
392406
393407 debugLog ( " <close> " , file: " " )
394408 }
@@ -423,19 +437,19 @@ struct AttributedStringVisitor: MarkupVisitor {
423437 _ attributes: StringAttrs ,
424438 _ markup: Markup
425439 ) {
426- let previousAttributes = currentAttributes
440+ let previousAttributes = currentAttributes. deepCopy ( )
427441 currentAttributes. mergeAttributes ( attributes)
428442 visitChildren ( of: markup)
429- currentAttributes = previousAttributes
443+ currentAttributes = previousAttributes. deepCopy ( )
430444 }
431445
432446 private mutating func visitWithMergedAttributes(
433447 _ newAttributes: StringAttrs ,
434448 _ markup: Markup ,
435449 markupType: MarkupType
436450 ) {
437- let previousAttributes = currentAttributes
438- var mergedAttributes = currentAttributes
451+ let previousAttributes = currentAttributes. deepCopy ( )
452+ var mergedAttributes = currentAttributes. deepCopy ( )
439453 mergedAttributes. mergeAttributes ( newAttributes) // Merge general attributes
440454
441455 if shouldAddCustomAttr {
@@ -450,9 +464,9 @@ struct AttributedStringVisitor: MarkupVisitor {
450464 mergedAttributes [ . font] = CocoaFont ( descriptor: newDescriptor, size: expectedFont. pointSize)
451465 }
452466
453- currentAttributes = mergedAttributes
467+ currentAttributes = mergedAttributes. deepCopy ( )
454468 visitChildren ( of: markup)
455- currentAttributes = previousAttributes
469+ currentAttributes = previousAttributes. deepCopy ( )
456470 }
457471
458472 private func mergeFontDescriptors( base: FontDescriptor , expected: FontDescriptor ) -> FontDescriptor {
@@ -570,3 +584,77 @@ extension StringAttrs {
570584 }
571585 }
572586}
587+
588+ extension Dictionary where Key == NSAttributedString . Key , Value == Any {
589+ func deepCopy( ) -> StringAttrs {
590+ var copy : StringAttrs = [ : ]
591+
592+ for (key, value) in self {
593+ if let copyable = value as? NSCopying {
594+ copy [ key] = copyable. copy ( )
595+ } else if let array = value as? [ Any ] {
596+ copy [ key] = array. deepCopyArray ( )
597+ } else if let dict = value as? [ AnyHashable : Any ] {
598+ copy [ key] = dict. deepCopyDict ( )
599+ } else {
600+ copy [ key] = value // Assume it's a value type (Int, String, etc.)
601+ }
602+ }
603+
604+ return copy
605+ }
606+ }
607+
608+ private extension Array where Element == Any {
609+ func deepCopyArray( ) -> [ Any ] {
610+ return self . map { element in
611+ if let copyable = element as? NSCopying {
612+ return copyable. copy ( )
613+ } else if let dict = element as? [ AnyHashable : Any ] {
614+ return dict. deepCopyDict ( )
615+ } else {
616+ return element
617+ }
618+ }
619+ }
620+ }
621+
622+ private extension Dictionary where Key == AnyHashable , Value == Any {
623+ func deepCopyDict( ) -> [ AnyHashable : Any ] {
624+ var copy : [ AnyHashable : Any ] = [ : ]
625+ for (key, value) in self {
626+ if let copyable = value as? NSCopying {
627+ copy [ key] = copyable. copy ( )
628+ } else if let array = value as? [ Any ] {
629+ copy [ key] = array. deepCopyArray ( )
630+ } else if let dict = value as? [ AnyHashable : Any ] {
631+ copy [ key] = dict. deepCopyDict ( )
632+ } else {
633+ copy [ key] = value
634+ }
635+ }
636+ return copy
637+ }
638+ }
639+
640+ extension String {
641+ func characterAt( line: Int , col: Int ) -> Character ? {
642+ let lines = self . split ( separator: " \n " , omittingEmptySubsequences: false )
643+
644+ let zeroIndexedLine = line - 1
645+ let zeroIndexedCol = col - 1
646+
647+ guard zeroIndexedLine >= 0 && zeroIndexedLine < lines. count else {
648+ return nil
649+ }
650+
651+ let line = lines [ zeroIndexedLine]
652+
653+ guard zeroIndexedCol >= 0 && zeroIndexedCol < line. count else {
654+ return nil
655+ }
656+
657+ let index = line. index ( line. startIndex, offsetBy: zeroIndexedCol)
658+ return line [ index]
659+ }
660+ }
0 commit comments