Untitled
unknown
swift
7 months ago
7.0 kB
26
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