krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgEditorAppKit/Theme.swift

b8d091bdcbf2889c220b12b2d0640d50cea17c24
orgstar/Sources/OrgEditorAppKit/Theme.swift history · blame · raw

91 lines · 4287 bytes

 1#if os(macOS)
 2import AppKit
 3import OrgPresentation
 4
 5/// Attributes for each style role. A fixed theme for now; the app's theme settings replace it.
 6@MainActor
 7struct Theme {
 8    let font = NSFont.monospacedSystemFont(ofSize: 13, weight: .regular)
 9
10    var base: [NSAttributedString.Key: Any] {
11        [.font: font, .foregroundColor: NSColor.textColor]
12    }
13
14    /// Width of one column of the monospaced font.
15    var column: CGFloat {
16        ("0" as NSString).size(withAttributes: [.font: font]).width
17    }
18
19    func paragraphStyle(firstLine: Int, wrapped: Int) -> NSParagraphStyle {
20        let style = NSMutableParagraphStyle()
21        style.firstLineHeadIndent = CGFloat(firstLine) * column
22        style.headIndent = CGFloat(wrapped) * column
23        return style
24    }
25
26    func apply(_ role: StyleRole, to storage: NSTextStorage, range: NSRange) {
27        switch role {
28        case .heading(let level):
29            let size = font.pointSize + CGFloat(max(0, 4 - level))
30            storage.addAttribute(.font, value: NSFont.monospacedSystemFont(ofSize: size, weight: .semibold), range: range)
31        case .leadingStars:
32            storage.addAttribute(.foregroundColor, value: NSColor.clear, range: range)
33        case .todo:
34            storage.addAttributes([.foregroundColor: NSColor.systemRed, .font: bold(storage, range)], range: range)
35        case .done:
36            storage.addAttributes([.foregroundColor: NSColor.systemGreen, .font: bold(storage, range)], range: range)
37        case .priority:
38            storage.addAttribute(.foregroundColor, value: NSColor.systemOrange, range: range)
39        case .tags, .comment, .keyword, .metadata:
40            storage.addAttribute(.foregroundColor, value: NSColor.secondaryLabelColor, range: range)
41        case .bold:
42            storage.addAttribute(.font, value: bold(storage, range), range: range)
43        case .italic:
44            storage.addAttribute(.font, value: NSFontManager.shared.convert(currentFont(storage, range), toHaveTrait: .italicFontMask), range: range)
45        case .underline:
46            storage.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
47        case .strikeThrough:
48            storage.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: range)
49        case .verbatim, .code, .inlineSource:
50            storage.addAttribute(.backgroundColor, value: NSColor.quaternarySystemFill, range: range)
51        case .markup, .linkMarkup, .blockDelimiter:
52            storage.addAttribute(.foregroundColor, value: NSColor.tertiaryLabelColor, range: range)
53        case .link:
54            storage.addAttributes([.foregroundColor: NSColor.linkColor, .underlineStyle: NSUnderlineStyle.single.rawValue], range: range)
55        case .timestamp:
56            storage.addAttribute(.foregroundColor, value: NSColor.systemPurple, range: range)
57        case .footnote, .cookie, .target, .macro, .latex:
58            storage.addAttribute(.foregroundColor, value: NSColor.systemTeal, range: range)
59        case .block:
60            storage.addAttribute(.backgroundColor, value: NSColor.quaternarySystemFill, range: range)
61        case .table, .rule:
62            break
63        case .syntax(let category):
64            if let color = Self.color(category) { storage.addAttribute(.foregroundColor, value: color, range: range) }
65        }
66    }
67
68    /// Code colors, close to Xcode's default theme.
69    static func color(_ category: SyntaxCategory) -> NSColor? {
70        switch category {
71        case .keyword: .systemPink
72        case .string: .systemRed
73        case .comment: .secondaryLabelColor
74        case .function: .systemBlue
75        case .type: .systemTeal
76        case .number, .constant, .escape: .systemOrange
77        case .property, .attribute, .tag: .systemIndigo
78        case .label: .systemPurple
79        case .variable, .operator, .punctuation: nil
80        }
81    }
82
83    private func currentFont(_ storage: NSTextStorage, _ range: NSRange) -> NSFont {
84        storage.attribute(.font, at: range.location, effectiveRange: nil) as? NSFont ?? font
85    }
86
87    private func bold(_ storage: NSTextStorage, _ range: NSRange) -> NSFont {
88        NSFontManager.shared.convert(currentFont(storage, range), toHaveTrait: .boldFontMask)
89    }
90}
91#endif