Untitled
unknown
swift
6 days ago
7.0 kB
4
No Index
import Foundation import Markdown import SwiftUI // A sample string for render let sampleString = #""" This screen demonstrates how you can integrate a 3rd party library to render syntax-highlighted code blocks. First, we create a type that conforms to `CodeSyntaxHighlighter`, using [John Sundell's Splash](https://github.com/JohnSundell/Splash) to highlight code blocks. ```swift import MarkdownUI import Splash import SwiftUI struct SplashCodeSyntaxHighlighter: CodeSyntaxHighlighter { private let syntaxHighlighter: SyntaxHighlighter<TextOutputFormat> init(theme: Splash.Theme) { self.syntaxHighlighter = SyntaxHighlighter(format: TextOutputFormat(theme: theme)) } func highlightCode(_ content: String, language: String?) -> Text { guard language != nil else { return Text(content) } return self.syntaxHighlighter.highlight(content) } } extension CodeSyntaxHighlighter where Self == SplashCodeSyntaxHighlighter { static func splash(theme: Splash.Theme) -> Self { SplashCodeSyntaxHighlighter(theme: theme) } } ``` Then we configure the `Markdown` view to use the `SplashCodeSyntaxHighlighter` that we just created. ```swift var body: some View { Markdown(self.content) .markdownCodeSyntaxHighlighter(.splash(theme: .sunset(withFont: .init(size: 16)))) } ``` More languages to render: ``` A plain code block without the specifying a language name. ``` ```cpp #include <iostream> #include <vector> int main() { std::vector<std::string> fruits = {"apple", "banana", "orange"}; for (const std::string& fruit : fruits) { std::cout << "I love " << fruit << "s!" << std::endl; } return 0; } ``` ```typescript interface Person { name: string; age: number; } const person = Person(); ``` ```ruby fruits = ["apple", "banana", "orange"] fruits.each do |fruit| puts "I love #{fruit}s!" end ``` """# let document = Document(parsing: sampleString) var markString = MarkString(fontSize: 14) Text(markString.from(document)) .textSelection(.enabled) struct MarkString: MarkupVisitor { let fontSize: Double mutating func from(_ document: Document) -> AttributedString { return visit(document) } public mutating func defaultVisit(_ markup: Markup) -> AttributedString { var result = AttributedString() for child in markup.children { result.append(visit(child)) } return result } mutating func visitText(_ text: Markdown.Text) -> AttributedString { var result = AttributedString(text.plainText) result.font = .system(size: fontSize, weight: .regular) return result } mutating func visitEmphasis(_ emphasis: Emphasis) -> AttributedString { var result = AttributedString() for child in emphasis.children { result.append(visit(child)) } result.font = .system(size: fontSize).italic() return result } mutating func visitParagraph(_ paragraph: Paragraph) -> AttributedString { var result = AttributedString() for child in paragraph.children { result.append(visit(child)) } if paragraph.hasSuccessor { result.append(paragraph.isContainedInList ? .singleNewline(withFontSize: fontSize) : .doubleNewline(withFontSize: fontSize)) } return result } mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> AttributedString { var result = AttributedString(codeBlock.code) result.font = .system(size: fontSize - 1, weight: .regular, design: .monospaced) result.foregroundColor = .systemGray // result.backgroundColor = .secondaryBackground if codeBlock.hasSuccessor { result.append(.singleNewline(withFontSize: fontSize)) } return result } } extension Markup { /// Returns true if this element has sibling elements after it. var hasSuccessor: Bool { guard let childCount = parent?.childCount else { return false } return indexInParent < childCount - 1 } var isContainedInList: Bool { var currentElement = parent while currentElement != nil { if currentElement is ListItemContainer { return true } currentElement = currentElement?.parent } return false } } extension AttributedStringProtocol where Self == AttributedString { static func singleNewline(withFontSize fontSize: CGFloat) -> AttributedString { var result = AttributedString("\n") result.font = .system(size: fontSize, weight: .regular) return result } static func doubleNewline(withFontSize fontSize: CGFloat) -> AttributedString { var result = AttributedString("\n\n") result.font = .system(size: fontSize, weight: .regular) return result } } private extension ShapeStyle where Self == Color { static var inlineCodeForeground: Color { Color(rgba: 0xeb57_57ff) } static var secondaryBackground: Color { Color( light: Color(rgba: 0xd1d1_d1ff), dark: Color(rgba: 0x2526_2aff) ) } } public extension Color { /// Creates a constant color from an RGBA value. /// - Parameter rgba: A 32-bit value that represents the red, green, blue, and alpha components of the color. init(rgba: UInt32) { self.init( red: CGFloat((rgba & 0xff00_0000) >> 24) / 255.0, green: CGFloat((rgba & 0x00ff_0000) >> 16) / 255.0, blue: CGFloat((rgba & 0x0000_ff00) >> 8) / 255.0, opacity: CGFloat(rgba & 0x0000_00ff) / 255.0 ) } /// Creates a context-dependent color with different values for light and dark appearances. /// - Parameters: /// - light: The light appearance color value. /// - dark: The dark appearance color value. init(light: @escaping @autoclosure () -> Color, dark: @escaping @autoclosure () -> Color) { #if os(watchOS) self = dark() #elseif canImport(UIKit) self.init( uiColor: .init { traitCollection in switch traitCollection.userInterfaceStyle { case .unspecified, .light: return UIColor(light()) case .dark: return UIColor(dark()) @unknown default: return UIColor(light()) } } ) #elseif canImport(AppKit) self.init( nsColor: .init(name: nil) { appearance in if appearance.bestMatch(from: [.aqua, .darkAqua]) == .aqua { return NSColor(light()) } else { return NSColor(dark()) } } ) #endif } }
Editor is loading...
Leave a Comment