krz/orgstar

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

Commit 745599e8e9

745599e8e905766da00da4d49c9b867ba4d6efb5

parent: 4c75f0eea5

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-09 01:09 UTC

Highlight src blocks in the iOS editor

OrgstarMobile depends on OrgHighlight; the tree-sitter targets build for
iOS as they are. The syntax category to theme key mapping moves to
OrgPresentation for both editors.

Layout: unified · split

Package.swift +1 −1
@@ -84,7 +84,7 @@ let package = Package(
8484 .target(name: "OrgApp", dependencies: ["OrgWorkspace", "OrgDocument", "OrgPresentation"]),
8585 .executableTarget(name: "Orgstar", dependencies: ["OrgApp", "OrgEditorAppKit", "OrgHighlight"]),
8686 // The iOS app, bundled by scripts/build-ios-app.sh.
87 .executableTarget(name: "OrgstarMobile", dependencies: ["OrgApp"]),
87 .executableTarget(name: "OrgstarMobile", dependencies: ["OrgApp", "OrgHighlight"]),
8888 // App extensions, bundled into Orgstar.app by scripts/build-app.sh.
8989 .executableTarget(name: "OrgstarQuickLook", dependencies: ["OrgCore"], linkerSettings: extensionEntry),
9090 .executableTarget(name: "OrgstarSpotlight", dependencies: ["OrgCore"], linkerSettings: extensionEntry),
Sources/OrgEditorAppKit/Theme.swift +1 −15
@@ -118,21 +118,7 @@ struct Theme {
118118 case .block:
119119 break
120120 case .syntax(let category):
121 if let key = Self.slot(category) { fg(key) }
122 }
123 }
124
125 static func slot(_ category: SyntaxCategory) -> String? {
126 switch category {
127 case .keyword: "syntax-keyword"
128 case .string: "syntax-string"
129 case .comment: "syntax-comment"
130 case .function: "syntax-function"
131 case .type: "syntax-type"
132 case .number, .constant, .escape: "syntax-number"
133 case .property, .attribute, .tag: "syntax-property"
134 case .label: "syntax-label"
135 case .variable, .operator, .punctuation: nil
121 if let key = category.themeKey { fg(key) }
136122 }
137123 }
138124
Sources/OrgPresentation/Presentation.swift +18 −1
@@ -51,7 +51,24 @@ public enum SyntaxCategory: String, Hashable, Sendable, CaseIterable {
5151 }
5252}
5353
54/// Highlights source code; the platform supplies one (tree-sitter on macOS; iOS supplies none).
54extension SyntaxCategory {
55 /// The theme color for this kind of token; nil for those drawn in the text color.
56 public var themeKey: String? {
57 switch self {
58 case .keyword: "syntax-keyword"
59 case .string: "syntax-string"
60 case .comment: "syntax-comment"
61 case .function: "syntax-function"
62 case .type: "syntax-type"
63 case .number, .constant, .escape: "syntax-number"
64 case .property, .attribute, .tag: "syntax-property"
65 case .label: "syntax-label"
66 case .variable, .operator, .punctuation: nil
67 }
68 }
69}
70
71/// Highlights source code; the apps supply tree-sitter's (OrgHighlight).
5572public protocol CodeHighlighter: Sendable {
5673 /// Categories for ranges of `code`, in UTF-16 offsets; later runs win over earlier ones.
5774 /// Empty for languages it doesn't know.
Sources/OrgstarMobile/MobileEditor.swift +4 −1
@@ -1,6 +1,7 @@
11#if os(iOS)
22import OrgCore
33import OrgDocument
4import OrgHighlight
45import OrgPresentation
56import UIKit
67
@@ -96,6 +97,8 @@ final class KeyedTextView: UITextView {
9697/// fold as TAB cycles them.
9798@MainActor
9899final class MobileEditor: NSObject, DocumentBuffer {
100 /// One highlighter for every editor and the reader: it caches compiled grammars and results.
101 static let highlighter = TreeSitterHighlighter()
99102 private(set) var document: DocumentState
100103 let textView: UITextView
101104 var options: EditingOptions = .org {
@@ -555,7 +558,7 @@ final class MobileEditor: NSObject, DocumentBuffer {
555558 }
556559 let revealed = caretLine
557560 var scriptExclusions: [Range<Int>] = []
558 for run in Presentation.styles(document.tree, in: whole) {
561 for run in Presentation.styles(document.tree, in: whole, highlighter: Self.highlighter) {
559562 let clipped = NSIntersectionRange(NSRange(run.range), lines)
560563 guard clipped.length > 0 else { continue }
561564 if Presentation.skipsLeadingStars(run.role, indented: indented, hideStars: hideStars) { continue }
Sources/OrgstarMobile/MobileTheme.swift +2 −1
@@ -115,7 +115,8 @@ struct MobileTheme {
115115 case .footnote, .cookie, .target, .macro, .latex: fg("special")
116116 case .table, .rule: fg("table")
117117 case .block: break
118 case .syntax: break
118 case .syntax(let category):
119 if let key = category.themeKey { fg(key) }
119120 }
120121 }
121122