Untitled

 avatar
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