krz/orgstar

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

Commit 37a20f818f

37a20f818f27acd77d128e2287d6291b43bc0de9

parent: bbff3cc2a0

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-08 03:34 UTC

Give accurate iOS messages

Only table recalculation says it needs Emacs on the Mac; other requests
iOS doesn't handle say the command isn't available on iPhone and iPad.
A key sequence bound to a command that doesn't apply or isn't supported
runs it anyway, so it reports why, as on the Mac. An org-protocol capture
naming a missing template reports it. Settings shows the agenda start day
in words.

Layout: unified · split

Sources/OrgstarMobile/AgendaScreen.swift +2 −2
@@ -250,8 +250,8 @@ struct AgendaScreen: View {
250250 }
251251 case .failed(let text):
252252 message = text
253 case .external:
254 message = "\(command.title) needs Emacs, which runs on the Mac."
253 case .external(let request):
254 message = MobileEditor.unavailable(request, title: command.title)
255255 case .commit:
256256 message = nil
257257 }
Sources/OrgstarMobile/CaptureScreen.swift +1
@@ -65,6 +65,7 @@ struct CaptureScreen: View {
6565 templates = loaded.templates
6666 problem = loaded.problems.first
6767 key = request.template.flatMap { wanted in templates.first { $0.key == wanted }?.key } ?? templates.first?.key
68 if let wanted = request.template, !templates.contains(where: { $0.key == wanted }) { problem = "No capture template \"\(wanted)\"" }
6869 }
6970 .onChange(of: key, initial: true) { choose() }
7071 }
Sources/OrgstarMobile/MobileEditor.swift +11 −6
@@ -184,14 +184,18 @@ final class MobileEditor: NSObject, DocumentBuffer {
184184 onTangle?(scope)
185185 case .external(.agenda(let start, let count)) where onAgenda != nil:
186186 onAgenda?(start, count)
187 case .external(.emacsTable(_, _, _, let reason, _)):
188 onMessage?("This table needs Emacs, which runs on the Mac: \(reason).")
189 case .external:
190 onMessage?("\(command.title) needs Emacs, which runs on the Mac.")
187 case .external(let request):
188 onMessage?(Self.unavailable(request, title: command.title))
191189 }
192190 return step
193191 }
194192
193 /// Why a request the app doesn't handle did nothing: only table recalculation needs Emacs.
194 static func unavailable(_ request: ExternalRequest, title: String) -> String {
195 if case .emacsTable(_, _, _, let reason, _) = request { return "This table needs Emacs, which runs on the Mac: \(reason)." }
196 return "\(title) isn't available on iPhone and iPad."
197 }
198
195199 // MARK: - Commands by name and key
196200
197201 /// Runs what `keys` (as `C-c C-t`) is bound to in the Emacs keymap, for the caret's context.
@@ -288,8 +292,9 @@ final class MobileEditor: NSObject, DocumentBuffer {
288292 let runnable = candidates.first { binding in
289293 (binding.when.map { KeyContext.holds($0, in: context) } ?? true) && applies(binding.command, in: context)
290294 }
291 guard let binding = runnable else { return keys.count > 1 }
292 if binding.command.hasPrefix("edit."), !["edit.undo", "edit.redo"].contains(binding.command) { return keys.count > 1 }
295 // A sequence runs its command even where it doesn't apply, which says why, as on the Mac.
296 guard let binding = runnable ?? (keys.count > 1 ? candidates.first : nil) else { return false }
297 if keys.count == 1, binding.command.hasPrefix("edit."), !["edit.undo", "edit.redo"].contains(binding.command) { return false }
293298 run(binding.command)
294299 return true
295300 }
Sources/OrgstarMobile/SettingsScreen.swift +9 −1
@@ -26,7 +26,7 @@ struct SettingsScreen: View {
2626 Section("In use") {
2727 let _ = model.settingsVersion
2828 LabeledContent("TODO keywords", value: todoKeywords)
29 LabeledContent("Agenda", value: "\(OrgPreferences.agendaSpan()) days from \(OrgPreferences.agendaStartOffset())d")
29 LabeledContent("Agenda", value: agenda)
3030 LabeledContent("Reminders", value: OrgPreferences.reminders() ? "\(OrgPreferences.reminderLead()) min before" : "Off")
3131 LabeledContent("Capture templates", value: CaptureTemplates.load().templates.map(\.key).joined(separator: " "))
3232 }
@@ -37,6 +37,14 @@ struct SettingsScreen: View {
3737 }
3838 }
3939
40 /// The span and start day, worded as the Mac's Settings word the start.
41 private var agenda: String {
42 func days(_ count: Int) -> String { count == 1 ? "1 day" : "\(count) days" }
43 let offset = OrgPreferences.agendaStartOffset()
44 let start = offset == 0 ? "today" : offset < 0 ? "\(days(-offset)) before today" : "\(days(offset)) after today"
45 return "\(days(OrgPreferences.agendaSpan())), starting \(start)"
46 }
47
4048 private var todoKeywords: String {
4149 OrgPreferences.orgSettings().todoSequences.map { ($0.active.map(\.name) + ["|"] + $0.done.map(\.name)).joined(separator: " ") }
4250 .joined(separator: "; ")