gitbay/Views/Repos/OrgSyntaxStyler.swift

v1.14.0
gitbay-ios/gitbay/Views/Repos/OrgSyntaxStyler.swift history · blame · raw

59 lines · 2677 bytes

 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}