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 { |
| 250 | 250 | } |
| 251 | 251 | case .failed(let text): |
| 252 | 252 | 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) |
| 255 | 255 | case .commit: |
| 256 | 256 | message = nil |
| 257 | 257 | } |
Sources/OrgstarMobile/CaptureScreen.swift
+1
| @@ -65,6 +65,7 @@ struct CaptureScreen: View { |
| 65 | 65 | templates = loaded.templates |
| 66 | 66 | problem = loaded.problems.first |
| 67 | 67 | 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)\"" } |
| 68 | 69 | } |
| 69 | 70 | .onChange(of: key, initial: true) { choose() } |
| 70 | 71 | } |
Sources/OrgstarMobile/MobileEditor.swift
+11 −6
| @@ -184,14 +184,18 @@ final class MobileEditor: NSObject, DocumentBuffer { |
| 184 | 184 | onTangle?(scope) |
| 185 | 185 | case .external(.agenda(let start, let count)) where onAgenda != nil: |
| 186 | 186 | 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)) |
| 191 | 189 | } |
| 192 | 190 | return step |
| 193 | 191 | } |
| 194 | 192 | |
| 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 | |
| 195 | 199 | // MARK: - Commands by name and key |
| 196 | 200 | |
| 197 | 201 | /// 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 { |
| 288 | 292 | let runnable = candidates.first { binding in |
| 289 | 293 | (binding.when.map { KeyContext.holds($0, in: context) } ?? true) && applies(binding.command, in: context) |
| 290 | 294 | } |
| 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 } |
| 293 | 298 | run(binding.command) |
| 294 | 299 | return true |
| 295 | 300 | } |
Sources/OrgstarMobile/SettingsScreen.swift
+9 −1
| @@ -26,7 +26,7 @@ struct SettingsScreen: View { |
| 26 | 26 | Section("In use") { |
| 27 | 27 | let _ = model.settingsVersion |
| 28 | 28 | LabeledContent("TODO keywords", value: todoKeywords) |
| 29 | | LabeledContent("Agenda", value: "\(OrgPreferences.agendaSpan()) days from \(OrgPreferences.agendaStartOffset())d") |
| 29 | LabeledContent("Agenda", value: agenda) |
| 30 | 30 | LabeledContent("Reminders", value: OrgPreferences.reminders() ? "\(OrgPreferences.reminderLead()) min before" : "Off") |
| 31 | 31 | LabeledContent("Capture templates", value: CaptureTemplates.load().templates.map(\.key).joined(separator: " ")) |
| 32 | 32 | } |
| @@ -37,6 +37,14 @@ struct SettingsScreen: View { |
| 37 | 37 | } |
| 38 | 38 | } |
| 39 | 39 | |
| 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 | |
| 40 | 48 | private var todoKeywords: String { |
| 41 | 49 | OrgPreferences.orgSettings().todoSequences.map { ($0.active.map(\.name) + ["|"] + $0.done.map(\.name)).joined(separator: " ") } |
| 42 | 50 | .joined(separator: "; ") |