gitbay/Views/Repos/OrgSyntaxStyler.swift
59 lines · 2677 bytes
4 symbols in this file
1import Highlightr
2import OrgSwiftUI
3import SwiftUI
4import UIKit
5
6/// Highlights `#+begin_src` blocks in org content, bridging Highlightr to `OrgCodeStyler`.
7///
8/// Highlightr already returns an `NSAttributedString`, so this walks its color runs straight
9/// into an `AttributedString`. Colors are baked in, so a new instance is made per theme.
10@MainActor
11final class OrgSyntaxStyler: OrgCodeStyler {
12 private let highlightr: Highlightr?
13 private let supported: Set<String>
14
15 init(colorScheme: ColorScheme) {
16 let engine = Highlightr()
17 engine?.setTheme(to: colorScheme == .dark ? "atom-one-dark" : "xcode")
18 highlightr = engine
19 supported = Set(engine?.supportedLanguages() ?? [])
20 }
21
22 func highlighted(code: String, language: String?) -> AttributedString? {
23 guard let highlightr, let language = resolvedLanguage(language) else { return nil }
24 highlightr.theme.setCodeFont(.monospacedSystemFont(ofSize: 13, weight: .regular))
25 guard let attributed = highlightr.highlight(code, as: language, fastRender: true) else {
26 return nil
27 }
28 return Self.attributedString(from: attributed)
29 }
30
31 private func resolvedLanguage(_ raw: String?) -> String? {
32 guard let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(),
33 !trimmed.isEmpty else { return nil }
34 let mapped = Self.aliases[trimmed] ?? trimmed
35 return supported.contains(mapped) ? mapped : nil
36 }
37
38 private static let aliases: [String: String] = [
39 "js": "javascript", "jsx": "javascript", "ts": "typescript", "tsx": "typescript",
40 "py": "python", "rb": "ruby", "rs": "rust", "sh": "bash", "shell": "bash", "zsh": "bash",
41 "yml": "yaml", "c++": "cpp", "cc": "cpp", "kt": "kotlin", "cs": "csharp",
42 "objc": "objectivec", "objective-c": "objectivec", "html": "xml", "htm": "xml",
43 ]
44
45 /// Carry Highlightr's colors across, in a monospaced font at the block's own size.
46 private static func attributedString(from attributed: NSAttributedString) -> AttributedString {
47 var result = AttributedString()
48 let range = NSRange(location: 0, length: attributed.length)
49 attributed.enumerateAttribute(.foregroundColor, in: range) { value, subrange, _ in
50 var fragment = AttributedString((attributed.string as NSString).substring(with: subrange))
51 fragment.font = .system(.footnote, design: .monospaced)
52 if let color = value as? UIColor {
53 fragment.foregroundColor = Color(uiColor: color)
54 }
55 result += fragment
56 }
57 return result
58 }
59}