40 lines
1.8 KiB
Swift
40 lines
1.8 KiB
Swift
import AppKit
|
|
|
|
// MARK: - Code Block Syntax Highlighting
|
|
//
|
|
// Colors a fenced code block's content from `CodeHighlighter` tokens, using the
|
|
// Tomorrow palette in light appearance and One Dark in dark. Only foregrounds
|
|
// are themed — the block keeps the editor's background — so each palette is
|
|
// paired with the appearance whose background it's legible on.
|
|
|
|
extension EditorTextView {
|
|
|
|
private var prefersDarkCodeTheme: Bool {
|
|
effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua
|
|
}
|
|
|
|
/// The `NSColor` for a token kind (`nil` = plain code) in the current
|
|
/// appearance, derived from the shared `CodeSyntaxPalette` hexes so the
|
|
/// editor and Read mode / PDF export color tokens identically.
|
|
private func codeColor(_ type: CodeHighlighter.TokenType?) -> NSColor {
|
|
NSColor(hex: CodeSyntaxPalette.hex(type, dark: prefersDarkCodeTheme)) ?? .textColor
|
|
}
|
|
|
|
/// Applies syntax colors to a code block's content range in place.
|
|
func highlightCodeBlock(_ result: NSMutableAttributedString,
|
|
contentRange: NSRange, language: String?) {
|
|
guard contentRange.length > 0, contentRange.upperBound <= result.length else { return }
|
|
|
|
// Plain code text first; token colors paint over it.
|
|
result.addAttribute(.foregroundColor, value: codeColor(nil), range: contentRange)
|
|
|
|
let code = (result.string as NSString).substring(with: contentRange)
|
|
for token in CodeHighlighter.tokenize(code, language: language) {
|
|
let abs = NSRange(location: contentRange.location + token.range.location,
|
|
length: token.range.length)
|
|
guard abs.upperBound <= result.length else { continue }
|
|
result.addAttribute(.foregroundColor, value: codeColor(token.type), range: abs)
|
|
}
|
|
}
|
|
}
|