// Message text as styled text. Inline markdown (bold, italics, inline code, links) goes through // `AttributedString(markdown:)`. Block syntax is rewritten line by line first, since SwiftUI's Text // runs full-parsed paragraphs together: headings turn bold, list markers turn into bullets, quote // markers go, and fenced code keeps its lines in monospace. import Foundation enum Markdown { static func render(_ source: String) -> AttributedString { var result = AttributedString() var fenced = false var first = true for line in source.split(separator: "\n", omittingEmptySubsequences: false).map(String.init) { if line.trimmingCharacters(in: .whitespaces).hasPrefix("```") { fenced.toggle() continue } if !first { result += AttributedString("\n") } first = false if fenced { var code = AttributedString(line) code.inlinePresentationIntent = .code result += code } else { result += inline(block(line)) } } return result } /// One line with its block syntax rewritten as inline markdown or plain text. static func block(_ line: String) -> String { if let heading = line.wholeMatch(of: /\s{0,3}#{1,6}\s+(.+?)[\s#]*/) { return "**\(heading.1)**" } if let item = line.wholeMatch(of: /(\s*)[-*+]\s+(.*)/) { return "\(item.1)• \(item.2)" } if let quote = line.wholeMatch(of: /\s{0,3}>\s?(.*)/) { return String(quote.1) } return line } /// Inline markdown, or the line as plain text when it does not parse. private static func inline(_ line: String) -> AttributedString { let options = AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace) return (try? AttributedString(markdown: line, options: options)) ?? AttributedString(line) } }