Created
July 5, 2024 18:10
-
-
Save jamesrochabrun/d5f4892b0b84134051158b3a628117ca to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import AppKit | |
import Down | |
import Highlightr | |
struct MarkdownTextView: NSViewRepresentable { | |
let markdown: String | |
func makeNSView(context: Context) -> NSScrollView { | |
let scrollView = NSScrollView() | |
scrollView.hasVerticalScroller = true | |
scrollView.autoresizingMask = [.width, .height] | |
let textView = context.coordinator.textView | |
textView.minSize = NSSize(width: 0, height: 0) | |
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) | |
textView.isVerticallyResizable = true | |
textView.isHorizontallyResizable = false | |
textView.autoresizingMask = [.width] | |
textView.textContainer?.widthTracksTextView = true | |
scrollView.documentView = textView | |
return scrollView | |
} | |
func updateNSView(_ nsView: NSScrollView, context: Context) { | |
context.coordinator.updateText(markdown: markdown) | |
} | |
func makeCoordinator() -> Coordinator { | |
Coordinator() | |
} | |
class Coordinator: NSObject { | |
let textView = NSTextView() | |
let highlightr = Highlightr() | |
override init() { | |
super.init() | |
textView.isEditable = false | |
textView.isSelectable = true | |
textView.textContainerInset = NSSize(width: 10, height: 10) | |
highlightr?.setTheme(to: "atom-one-dark") | |
} | |
func updateText(markdown: String) { | |
DispatchQueue.global(qos: .userInitiated).async { | |
do { | |
let down = Down(markdownString: markdown) | |
let attributedString = try down.toAttributedString() | |
let highlightedAttributedString = NSMutableAttributedString(attributedString: attributedString) | |
self.applySyntaxHighlighting(to: highlightedAttributedString) | |
DispatchQueue.main.async { | |
self.textView.textStorage?.setAttributedString(highlightedAttributedString) | |
self.textView.scrollRangeToVisible(NSRange(location: self.textView.string.count, length: 0)) | |
} | |
} catch { | |
DispatchQueue.main.async { | |
self.textView.string = "Failed to parse markdown: \(error)" | |
} | |
} | |
} | |
} | |
func applySyntaxHighlighting(to attributedString: NSMutableAttributedString) { | |
let fullRange = NSRange(location: 0, length: attributedString.length) | |
attributedString.enumerateAttribute(.font, in: fullRange, options: []) { value, range, _ in | |
if let font = value as? NSFont, font.fontDescriptor.symbolicTraits.contains(.monoSpace) { | |
let code = attributedString.attributedSubstring(from: range).string | |
if let highlightedCode = highlightr?.highlight(code, as: "swift") { | |
attributedString.replaceCharacters(in: range, with: highlightedCode) | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment