krz/orgstar

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

Commit 322a87d475

322a87d47526d505d903e05d14e65a5464e1468f

parent: 191ddb506f

Verified · cmc

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

iOS log mode and dependency settings, Emacs word-syntax table, lazier blocked checks

- iOS: Log Mode in the span menu; widgets stay out of log mode.
- iOS Settings: Blocked entries, and the two TODO dependency settings.
- Agenda sources work out blocked flags only when an enforce setting
  is on and dimming isn't off.
- Regexp word boundaries use Emacs 31's word-syntax table for Org
  buffers; the search view treats ' as punctuation, as
  org-search-syntax-table does.

Layout: unified · split

Sources/OrgApp/AgendaModel.swift +4 −3
@@ -114,6 +114,7 @@ public final class AgendaModel {
114 /// while their modification date and size are unchanged. 114 /// while their modification date and size are unchanged.
115 private func sources(open: [String: String]) async -> [AgendaSource] { 115 private func sources(open: [String: String]) async -> [AgendaSource] {
116 let defaults = workspace.settings.org 116 let defaults = workspace.settings.org
117 let blocking = options.dimBlockedTasks != .off && (options.enforceTodoDependencies || options.enforceTodoCheckboxDependencies)
117 var reads: [(path: String, cached: AgendaSource?, mtime: Date, size: Int, text: String?)] = [] 118 var reads: [(path: String, cached: AgendaSource?, mtime: Date, size: Int, text: String?)] = []
118 for path in files { 119 for path in files {
119 if let text = open[path] { 120 if let text = open[path] {
@@ -124,7 +125,7 @@ public final class AgendaModel {
124 let mtime = attributes?[.modificationDate] as? Date ?? .distantPast 125 let mtime = attributes?[.modificationDate] as? Date ?? .distantPast
125 let size = (attributes?[.size] as? NSNumber)?.intValue ?? -1 126 let size = (attributes?[.size] as? NSNumber)?.intValue ?? -1
126 // A file reading setup files is read again: they may have changed. 127 // A file reading setup files is read again: they may have changed.
127 let cached = cache[path].flatMap { $0.mtime == mtime && $0.size == size && !$0.source.readsSetupFiles ? $0.source : nil } 128 let cached = cache[path].flatMap { $0.mtime == mtime && $0.size == size && !$0.source.readsSetupFiles && $0.source.checksBlocking == blocking ? $0.source : nil }
128 reads.append((path, cached, mtime, size, nil)) 129 reads.append((path, cached, mtime, size, nil))
129 } 130 }
130 let loaded = await Task.detached { 131 let loaded = await Task.detached {
@@ -133,10 +134,10 @@ public final class AgendaModel {
133 if let cached = read.cached { 134 if let cached = read.cached {
134 sources.append((read.path, read.mtime, read.size, cached, true)) 135 sources.append((read.path, read.mtime, read.size, cached, true))
135 } else if let text = read.text { 136 } else if let text = read.text {
136 sources.append((read.path, read.mtime, read.size, AgendaSource(path: read.path, text: text, defaults: defaults.resolvingSetupFiles(in: text, path: read.path, read: OrgSettings.readFile)), false)) 137 sources.append((read.path, read.mtime, read.size, AgendaSource(path: read.path, text: text, defaults: defaults.resolvingSetupFiles(in: text, path: read.path, read: OrgSettings.readFile), checksBlocking: blocking), false))
137 } else if let data = FileManager.default.contents(atPath: read.path) { 138 } else if let data = FileManager.default.contents(atPath: read.path) {
138 let text = SourceText(bytes: [UInt8](data)).text 139 let text = SourceText(bytes: [UInt8](data)).text
139 sources.append((read.path, read.mtime, read.size, AgendaSource(path: read.path, text: text, defaults: defaults.resolvingSetupFiles(in: text, path: read.path, read: OrgSettings.readFile)), true)) 140 sources.append((read.path, read.mtime, read.size, AgendaSource(path: read.path, text: text, defaults: defaults.resolvingSetupFiles(in: text, path: read.path, read: OrgSettings.readFile), checksBlocking: blocking), true))
140 } 141 }
141 } 142 }
142 return sources 143 return sources
Sources/OrgCore/Agenda/AgendaSearch.swift +28 −6
@@ -219,7 +219,7 @@ extension Agenda {
219 219
220 /// Case-insensitive, as Org binds `case-fold-search`, with `^` and `$` at each line. 220 /// Case-insensitive, as Org binds `case-fold-search`, with `^` and `$` at each line.
221 static func compile(_ emacs: String) -> NSRegularExpression? { 221 static func compile(_ emacs: String) -> NSRegularExpression? {
222 try? NSRegularExpression(pattern: EmacsRegex.translate(emacs), options: [.caseInsensitive, .anchorsMatchLines]) 222 try? NSRegularExpression(pattern: EmacsRegex.translate(emacs, search: true), options: [.caseInsensitive, .anchorsMatchLines])
223 } 223 }
224 } 224 }
225 225
@@ -480,10 +480,32 @@ public struct TagsMatcher: Sendable {
480/// Emacs regular expressions run through ICU, case-insensitively as the matcher binds 480/// Emacs regular expressions run through ICU, case-insensitively as the matcher binds
481/// `case-fold-search`. 481/// `case-fold-search`.
482enum EmacsRegex { 482enum EmacsRegex {
483 /// Word syntax in an Org buffer: letters, digits, marks, `$` and `%`, but not `_`, `-` or `'`. 483 /// Characters without word syntax in an Org buffer under Emacs 31, as ranges: everything
484 static let word = "[\\p{L}\\p{N}\\p{M}$%\\u00b7]" 484 /// else, `$`, `%` and `'` included, is a word character. Emacs also puts a word boundary
485 485 /// where the script changes, which this doesn't.
486 static func translate(_ pattern: String) -> String { 486 static let nonWord = ""
487 + "\\x{0}-\\x{23}\\x{26}\\x{28}-\\x{2f}\\x{3a}-\\x{40}\\x{5b}-\\x{60}\\x{7b}-\\x{7f}\\x{a0}-\\x{a4}"
488 + "\\x{a6}-\\x{b1}\\x{b4}\\x{b6}\\x{b8}\\x{ba}-\\x{bf}\\x{d7}\\x{f7}\\x{2c7}\\x{2c9}\\x{2d0}"
489 + "\\x{2d8}-\\x{2db}\\x{2dd}\\x{384}-\\x{385}\\x{5be}\\x{5c0}\\x{5c3}\\x{5c6}\\x{e2f}\\x{e3f}\\x{e46}"
490 + "\\x{e4f}\\x{e5a}-\\x{e5b}\\x{eaf}\\x{ec6}\\x{f00}-\\x{f0b}\\x{f0d}-\\x{f18}\\x{f1a}-\\x{f1f}\\x{f34}"
491 + "\\x{f36}\\x{f38}-\\x{f3f}\\x{f7f}\\x{f85}\\x{fbe}-\\x{fcf}\\x{1361}-\\x{1368}\\x{1800}-\\x{180a}"
492 + "\\x{2000}-\\x{2026}\\x{202f}-\\x{205f}\\x{207d}-\\x{207e}\\x{208d}-\\x{208e}\\x{20ac}\\x{2103}"
493 + "\\x{2109}\\x{2116}\\x{2121}-\\x{2122}\\x{2153}-\\x{2154}\\x{215b}-\\x{215e}\\x{2190}-\\x{244f}"
494 + "\\x{2460}-\\x{246e}\\x{2474}-\\x{24b5}\\x{2500}-\\x{254b}\\x{2592}\\x{25a0}-\\x{25a1}"
495 + "\\x{25a3}-\\x{25a9}\\x{25b2}-\\x{25b3}\\x{25b6}-\\x{25b7}\\x{25bc}-\\x{25bd}\\x{25c0}-\\x{25c1}"
496 + "\\x{25c6}-\\x{25c8}\\x{25cb}\\x{25ce}-\\x{25d1}\\x{25ef}\\x{2605}-\\x{2606}\\x{260e}-\\x{260f}"
497 + "\\x{261c}\\x{261e}\\x{2640}\\x{2642}\\x{2660}-\\x{2661}\\x{2663}-\\x{2665}\\x{2667}-\\x{266a}"
498 + "\\x{266c}-\\x{266d}\\x{266f}\\x{2768}-\\x{276d}\\x{2770}-\\x{2775}\\x{27e6}-\\x{27eb}"
499 + "\\x{2983}-\\x{2998}\\x{29fc}-\\x{29fd}\\x{2a00}-\\x{2bff}\\x{2e00}-\\x{2e7f}\\x{3000}-\\x{3003}"
500 + "\\x{3008}-\\x{301c}\\x{30fb}\\x{3200}-\\x{321c}\\x{3220}-\\x{3229}\\x{3260}-\\x{327b}"
501 + "\\x{327e}-\\x{327f}\\x{3380}-\\x{3384}\\x{3388}-\\x{33ca}\\x{33cf}-\\x{33d0}\\x{33d3}\\x{33d6}"
502 + "\\x{33d8}\\x{33db}-\\x{33dd}\\x{aadb}-\\x{aadf}\\x{fd3e}-\\x{fd3f}\\x{fe35}-\\x{fe44}"
503 + "\\x{fe59}-\\x{fe5e}\\x{ff01}-\\x{ff0f}\\x{ff1b}-\\x{ff20}\\x{ff3b}-\\x{ff40}\\x{ff5b}-\\x{ff65}"
504 + "\\x{ffe0}-\\x{ffe3}\\x{ffe5}\\x{11660}-\\x{1166c}\\x{1fb00}-\\x{1fbff}"
505 /// `search` uses `org-search-view`'s syntax table, where `'` is punctuation.
506 static func translate(_ pattern: String, search: Bool = false) -> String {
507 let nonWord = search ? Self.nonWord + "'" : Self.nonWord
508 let word = "[^" + nonWord + "]"
487 var out = "" 509 var out = ""
488 var chars = Array(pattern) 510 var chars = Array(pattern)
489 var i = 0 511 var i = 0
@@ -532,7 +554,7 @@ enum EmacsRegex {
532 case "`": out += "^" 554 case "`": out += "^"
533 case "'": out += "$" 555 case "'": out += "$"
534 case "w": out += word 556 case "w": out += word
535 case "W": out += "[^" + word.dropFirst() 557 case "W": out += "[" + nonWord + "]"
536 default: out += "\\" + String(n) 558 default: out += "\\" + String(n)
537 } 559 }
538 i += 2 560 i += 2
Sources/OrgCore/Agenda/AgendaSource.swift +7 −2
@@ -102,10 +102,15 @@ public struct AgendaSource: Sendable {
102 let notDoneKeywords: [String] 102 let notDoneKeywords: [String]
103 /// The file's text, for the search view. 103 /// The file's text, for the search view.
104 let text: String 104 let text: String
105 /// Whether the headings' blocked flags were worked out.
106 public let checksBlocking: Bool
105 let priorities: Priorities 107 let priorities: Priorities
106 108
107 public init(path: String, text: String, defaults: OrgSettings = .default) { 109 /// `checksBlocking` false leaves every heading unblocked, for when neither enforce
110 /// setting is on.
111 public init(path: String, text: String, defaults: OrgSettings = .default, checksBlocking: Bool = true) {
108 self.path = path 112 self.path = path
113 self.checksBlocking = checksBlocking
109 readsSetupFiles = text.range(of: "#+setupfile:", options: .caseInsensitive) != nil 114 readsSetupFiles = text.range(of: "#+setupfile:", options: .caseInsensitive) != nil
110 let tree = OrgParser.parse(text, defaults: defaults) 115 let tree = OrgParser.parse(text, defaults: defaults)
111 let model = DocumentModel(tree: tree) 116 let model = DocumentModel(tree: tree)
@@ -236,7 +241,7 @@ public struct AgendaSource: Sendable {
236 } 241 }
237 var blockedByCheckboxes = false 242 var blockedByCheckboxes = false
238 var blockedByHierarchy = false 243 var blockedByHierarchy = false
239 if info.todo != nil, !info.isDone, buffer.localProperty("NOBLOCKING", heading: info.headingRange.lowerBound).map({ $0 == "nil" }) ?? true { 244 if checksBlocking, info.todo != nil, !info.isDone, buffer.localProperty("NOBLOCKING", heading: info.headingRange.lowerBound).map({ $0 == "nil" }) ?? true {
240 buffer.point = info.headingRange.lowerBound 245 buffer.point = info.headingRange.lowerBound
241 blockedByCheckboxes = buffer.hasOpenCheckbox() 246 blockedByCheckboxes = buffer.hasOpenCheckbox()
242 blockedByHierarchy = buffer.hierarchyBlocker() != nil 247 blockedByHierarchy = buffer.hierarchyBlocker() != nil
Sources/OrgstarMobile/AgendaScreen.swift +6 −1
@@ -38,6 +38,8 @@ struct AgendaScreen: View {
38 @AppStorage("agendaOverdueExpanded") private var overdueExpanded = true 38 @AppStorage("agendaOverdueExpanded") private var overdueExpanded = true
39 @State private var done = 0 39 @State private var done = 0
40 @State private var moved = 0 40 @State private var moved = 0
41 /// `org-agenda-log-mode`: closed entries and clocked time on each day.
42 @State private var logMode = false
41 43
42 /// A match typed for the tag and property view, or a search string. 44 /// A match typed for the tag and property view, or a search string.
43 private enum Asking: Identifiable { 45 private enum Asking: Identifiable {
@@ -62,6 +64,7 @@ struct AgendaScreen: View {
62 var startOffset: Int 64 var startOffset: Int
63 var subfolders: Bool 65 var subfolders: Bool
64 var settings: Int 66 var settings: Int
67 var logMode: Bool
65 } 68 }
66 69
67 private var isDark: Bool { colorScheme == .dark } 70 private var isDark: Bool { colorScheme == .dark }
@@ -202,7 +205,8 @@ struct AgendaScreen: View {
202 } 205 }
203 .task(id: Key(files: workspace.files.count, changed: workspace.changedFile?.version, edits: session.editVersion, tick: tick, 206 .task(id: Key(files: workspace.files.count, changed: workspace.changedFile?.version, edits: session.editVersion, tick: tick,
204 mode: agenda.mode, start: agenda.start, span: agenda.span, startOffset: agenda.startOffset, 207 mode: agenda.mode, start: agenda.start, span: agenda.span, startOffset: agenda.startOffset,
205 subfolders: agenda.includeSubfolders, settings: model.settingsVersion)) { 208 subfolders: agenda.includeSubfolders, settings: model.settingsVersion, logMode: logMode)) {
209 agenda.options.logMode = logMode
206 await agenda.refresh(open: session.openTexts) 210 await agenda.refresh(open: session.openTexts)
207 } 211 }
208 } 212 }
@@ -381,6 +385,7 @@ struct AgendaScreen: View {
381 ForEach(SpanRange.allCases) { Text($0.title).tag(Optional($0)) } 385 ForEach(SpanRange.allCases) { Text($0.title).tag(Optional($0)) }
382 Text("\(agenda.span) Days").tag(SpanRange?.none) 386 Text("\(agenda.span) Days").tag(SpanRange?.none)
383 } 387 }
388 Toggle("Log Mode", systemImage: "clock.arrow.circlepath", isOn: $logMode)
384 } label: { 389 } label: {
385 Text(range?.title ?? "\(agenda.span) Days") 390 Text(range?.title ?? "\(agenda.span) Days")
386 .font(.footnote.weight(.semibold).monospacedDigit()) 391 .font(.footnote.weight(.semibold).monospacedDigit())
Sources/OrgstarMobile/MobileModel.swift +1
@@ -220,6 +220,7 @@ final class MobileModel {
220 if let agenda { 220 if let agenda {
221 days.includeSubfolders = agenda.includeSubfolders 221 days.includeSubfolders = agenda.includeSubfolders
222 days.options = agenda.options 222 days.options = agenda.options
223 days.options.logMode = false
223 days.calendarEvents = agenda.calendarEvents 224 days.calendarEvents = agenda.calendarEvents
224 days.calendarNames = agenda.calendarNames 225 days.calendarNames = agenda.calendarNames
225 } 226 }
Sources/OrgstarMobile/SettingsScreen.swift +20
@@ -37,6 +37,26 @@ struct SettingsScreen: View {
37 Toggle("Show days without entries", isOn: Binding { 37 Toggle("Show days without entries", isOn: Binding {
38 OrgPreferences.agendaShowAllDates() 38 OrgPreferences.agendaShowAllDates()
39 } set: { model.set("agendaShowAllDates", to: .bool($0)) }) 39 } set: { model.set("agendaShowAllDates", to: .bool($0)) })
40 Picker("Blocked entries", selection: Binding {
41 UserDefaults.standard.string(forKey: "agendaDimBlockedTasks") ?? "t"
42 } set: { model.set("agendaDimBlockedTasks", to: .string($0)) }) {
43 Text("Dimmed").tag("t")
44 Text("Hidden").tag("invisible")
45 Text("Shown as usual").tag("nil")
46 }
47 }
48 Section {
49 let _ = model.settingsVersion
50 Toggle("Not done before TODO children", isOn: Binding {
51 UserDefaults.standard.bool(forKey: "enforceTodoDependencies")
52 } set: { model.set("enforceTodoDependencies", to: .bool($0)) })
53 Toggle("Not done with unchecked boxes", isOn: Binding {
54 UserDefaults.standard.bool(forKey: "enforceTodoCheckboxDependencies")
55 } set: { model.set("enforceTodoCheckboxDependencies", to: .bool($0)) })
56 } header: {
57 Text("TODO Dependencies")
58 } footer: {
59 Text("org-enforce-todo-dependencies and org-enforce-todo-checkbox-dependencies.")
40 } 60 }
41 calendarSection 61 calendarSection
42 Section("In use") { 62 Section("In use") {
Tests/OrgCoreTests/AgendaSearchTests.swift +3 −2
@@ -29,6 +29,7 @@ struct AgendaSearchTests {
29 The phrase two words 29 The phrase two words
30 spans here; foo_bar and foo-bar. 30 spans here; foo_bar and foo-bar.
31 Costs $5 or 50% (don't) for the café. 31 Costs $5 or 50% (don't) for the café.
32 Marked ©2026, «quoted», a·b, x→y and 😀smile.
32 * The last 33 * The last
33 word ends this entry with last 34 word ends this entry with last
34 * word starts the next heading 35 * word starts the next heading
@@ -55,9 +56,9 @@ struct AgendaSearchTests {
55 "+alpha", "+alpha -beta", "+alpha +gamma", "-alpha", "+beta -nested", "{alph?a}", "+{^Gam}", "-{b[eo]}", 56 "+alpha", "+alpha -beta", "+alpha +gamma", "-alpha", "+beta -nested", "{alph?a}", "+{^Gam}", "-{b[eo]}",
56 "+{\\<rent\\>}", "+\"two words\"", "+\"two words\"", "+\"the line\"", "{boiler\\b}", "+alpha +\"in the body\"", 57 "+{\\<rent\\>}", "+\"two words\"", "+\"two words\"", "+\"the line\"", "{boiler\\b}", "+alpha +\"in the body\"",
57 "+{foo\\s-bar}", "+boiler -plate", "+{a b}", "+{alpha} -{x y}", 58 "+{foo\\s-bar}", "+boiler -plate", "+{a b}", "+{alpha} -{x y}",
58 // Emacs word syntax in Org buffers: `_`, `-` and `'` are not word characters; `$` and `%` are. 59 // Emacs word syntax in the search view: `_`, `-` and `'` are not word characters; `$` and `%` are.
59 "+{\\<bar\\>}", "+{foo\\b}", "+{o\\B_}", "+{\\<5\\>}", "+{\\b50\\b}", "+{don\\>}", "+{\\w+_\\w+}", "+{\\W5}", 60 "+{\\<bar\\>}", "+{foo\\b}", "+{o\\B_}", "+{\\<5\\>}", "+{\\b50\\b}", "+{don\\>}", "+{\\w+_\\w+}", "+{\\W5}",
60 "+{caf\\w\\>}", "+{\\<\\$5}", 61 "+{caf\\w\\>}", "+{\\<\\$5}", "+{\\<2026}", "+{\\<quoted\\>}", "+{a\\b·}", "+{\\<b,}", "+{x\\>}",
61 // Headlines only. 62 // Headlines only.
62 "*rent", "*alpha", "*+alpha -beta", "*+section", "*-alpha", "*pay rent", "*with alpha and", 63 "*rent", "*alpha", "*+alpha -beta", "*+section", "*-alpha", "*pay rent", "*with alpha and",
63 // TODO only and whole words. 64 // TODO only and whole words.
Tests/OrgCoreTests/TodoDependencyTests.swift +7
@@ -192,6 +192,13 @@ struct TodoDependencyTests {
192 #expect(blocked(true, true, .invisible) == ["TODO B", "~TODO C", "TODO D"]) 192 #expect(blocked(true, true, .invisible) == ["TODO B", "~TODO C", "TODO D"])
193 } 193 }
194 194
195 @Test func sourcesCanSkipTheBlockingCheck() {
196 let source = AgendaSource(path: "/tmp/a.org", text: "* TODO A\n** TODO B\n", checksBlocking: false)
197 var options = AgendaOptions()
198 options.enforceTodoDependencies = true
199 #expect(Agenda.todoList([source], options: options).allSatisfy { !$0.isBlocked })
200 }
201
195 @Test func dwimIsBlocked() { 202 @Test func dwimIsBlocked() {
196 let text = "* TODO A\n** TODO B\n" 203 let text = "* TODO A\n** TODO B\n"
197 var options = EditingOptions.org 204 var options = EditingOptions.org
docs/manual/guide/05-todos-and-tags.org +2 −2
@@ -336,7 +336,7 @@ The =COOKIE_DATA= property changes what the parent's cookie counts:
336| =org-todo-keywords= | Settings ▸ Editing, =config.toml= | See "The default keywords" above | 336| =org-todo-keywords= | Settings ▸ Editing, =config.toml= | See "The default keywords" above |
337| =org-log-done= | =config.toml= | =nil= | 337| =org-log-done= | =config.toml= | =nil= |
338| =org-log-into-drawer= | =config.toml= | =""= (no drawer) | 338| =org-log-into-drawer= | =config.toml= | =""= (no drawer) |
339| =org-enforce-todo-dependencies= | Settings ▸ Editing, =config.toml= | =false= | 339| =org-enforce-todo-dependencies= | Settings ▸ Editing (iOS: Settings ▸ TODO Dependencies), =config.toml= | =false= |
340| =org-enforce-todo-checkbox-dependencies= | Settings ▸ Editing, =config.toml= | =false= | 340| =org-enforce-todo-checkbox-dependencies= | Settings ▸ Editing (iOS: Settings ▸ TODO Dependencies), =config.toml= | =false= |
341| =org-tags-column= | Settings ▸ Editing, =config.toml= | =-77= | 341| =org-tags-column= | Settings ▸ Editing, =config.toml= | =-77= |
342| =org-use-speed-commands= | =config.toml= | =false= | 342| =org-use-speed-commands= | =config.toml= | =false= |
docs/manual/guide/07-agenda.org +4 −3
@@ -122,7 +122,7 @@ Days are sorted as =org-agenda-sorting-strategy= sorts them by default for the a
122 122
123** Log mode 123** Log mode
124 124
125Press =l= to turn log mode (=org-agenda-log-mode=) on or off. Each day then also shows: 125Press =l= to turn log mode (=org-agenda-log-mode=) on or off; on iOS, use Log Mode in the span menu. Each day then also shows:
126 126
127- =Closed= entries whose =CLOSED:= timestamp is on that day; 127- =Closed= entries whose =CLOSED:= timestamp is on that day;
128- =Clocked= entries for each =CLOCK:= line that starts on that day, with the clock's start and end times in the left column and the time clocked, such as =1:30=, after the title. 128- =Clocked= entries for each =CLOCK:= line that starts on that day, with the clock's start and end times in the left column and the time clocked, such as =1:30=, after the title.
@@ -153,7 +153,7 @@ Ages and distances are written compactly: days up to 13 (=3d=), then whole weeks
153 153
154The keyword's color comes from =[theme.todo]=, else from its class: =todo-next= for keywords of work under way such as =NEXT= and =STRT=, =todo-waiting= for =WAIT=, =HOLD= and similar, =todo-cancelled= for done keywords such as =KILL= and =CANCELLED=, and otherwise =todo= or =done=. Priorities =A=, =B= and =C= use =priority-a=, =priority-b= and =priority-c=; others use =priority=. A category's dot takes its color from =[theme.category]=, else a hue worked out from the category's name, the same on every device. See [[file:13-configuration.org][Configuration]] for the keyword lists and the theme keys. 154The keyword's color comes from =[theme.todo]=, else from its class: =todo-next= for keywords of work under way such as =NEXT= and =STRT=, =todo-waiting= for =WAIT=, =HOLD= and similar, =todo-cancelled= for done keywords such as =KILL= and =CANCELLED=, and otherwise =todo= or =done=. Priorities =A=, =B= and =C= use =priority-a=, =priority-b= and =priority-c=; others use =priority=. A category's dot takes its color from =[theme.category]=, else a hue worked out from the category's name, the same on every device. See [[file:13-configuration.org][Configuration]] for the keyword lists and the theme keys.
155 155
156With =org-enforce-todo-dependencies= or =org-enforce-todo-checkbox-dependencies= on, an entry that can't be marked done yet is dimmed in every view (=org-agenda-dim-blocked-tasks= =t=). Settings ▸ Agenda ▸ Blocked entries can hide such entries instead (=invisible=), though one held back only by its own checkboxes stays dimmed, or show them as usual (=nil=). Marking a blocked entry done from the agenda is refused with the same message as in the editor; see [[file:05-todos-and-tags.org][TODOs and tags]]. 156With =org-enforce-todo-dependencies= or =org-enforce-todo-checkbox-dependencies= on, an entry that can't be marked done yet is dimmed in every view (=org-agenda-dim-blocked-tasks= =t=). Settings ▸ Agenda ▸ Blocked entries (on iOS, Settings ▸ Agenda) can hide such entries instead (=invisible=), though one held back only by its own checkboxes stays dimmed, or show them as usual (=nil=). Marking a blocked entry done from the agenda is refused with the same message as in the editor; see [[file:05-todos-and-tags.org][TODOs and tags]].
157 157
158* Calendar events 158* Calendar events
159 159
@@ -571,7 +571,8 @@ The Agenda tab shows the same views from the same files, using the settings in t
571- Swipe right on a scheduled entry, a deadline, a habit or a TODO entry, when it isn't done, for Tomorrow, Next Week and Pick Date. Tomorrow is the day after today and Next Week is next Monday. On a deadline row these move the deadline; on other rows they set the scheduled date. A timed row keeps its times. 571- Swipe right on a scheduled entry, a deadline, a habit or a TODO entry, when it isn't done, for Tomorrow, Next Week and Pick Date. Tomorrow is the day after today and Next Week is next Monday. On a deadline row these move the deadline; on other rows they set the scheduled date. A timed row keeps its times.
572- Touch and hold an entry for TODO State…, Reschedule (Tomorrow, Next Week, Pick Date…), Schedule…, Deadline…, Tags…, Priority…, Clock In, Refile… and Archive…. 572- Touch and hold an entry for TODO State…, Reschedule (Tomorrow, Next Week, Pick Date…), Schedule…, Deadline…, Tags…, Priority…, Clock In, Refile… and Archive….
573- Marking an entry done and rescheduling one give haptic feedback. 573- Marking an entry done and rescheduling one give haptic feedback.
574- There are no keyboard commands, log mode or bulk marks. 574- Log Mode, in the span menu, turns log mode on and off. The widgets never show log mode's entries.
575- There are no keyboard commands or bulk marks.
575- Reminders are set while the app is open. iOS allows at most 64 pending notifications and the app can't add more while it isn't running, so the agenda's footer says how far ahead they go and asks you to open Orgstar to set later ones. 576- Reminders are set while the app is open. iOS allows at most 64 pending notifications and the app can't add more while it isn't running, so the agenda's footer says how far ahead they go and asks you to open Orgstar to set later ones.
576 577
577The Today and Up Next widgets show the agenda on the Home Screen and the Lock Screen; see [[file:14-ios.org][iPhone and iPad]]. 578The Today and Up Next widgets show the agenda on the Home Screen and the Lock Screen; see [[file:14-ios.org][iPhone and iPad]].
docs/manual/guide/14-ios.org +4 −2
@@ -155,7 +155,7 @@ Tap a result to open it in the reader. A heading result opens at that heading.
155The Agenda tab shows the agenda over all your folders, with the span and start day from the settings (10 days from 3 days ago by default). Each day lists its entries in rows: the time or a status such as =Due today= or =3d late= on the left, the category and the keyword above the title. Days without entries are hidden, except today. Today's overdue entries are in a group of their own, and a line marks the current time between timed entries. Pull down to refresh. 155The Agenda tab shows the agenda over all your folders, with the span and start day from the settings (10 days from 3 days ago by default). Each day lists its entries in rows: the time or a status such as =Due today= or =3d late= on the left, the category and the keyword above the title. Days without entries are hidden, except today. Today's overdue entries are in a group of their own, and a line marks the current time between timed entries. Pull down to refresh.
156 156
157- Views (top left) :: the built-in views and those in =views.toml=, Tags and Properties… (a match such as =+work-home= or ~TODO="WAIT"~), and Search… (a phrase, or snippets such as =+invoice -paid=). 157- Views (top left) :: the built-in views and those in =views.toml=, Tags and Properties… (a match such as =+work-home= or ~TODO="WAIT"~), and Search… (a phrase, or snippets such as =+invoice -paid=).
158- The bar at the bottom :: Earlier, Filter, Today, Go to Date, the span (Day, Week, Month or the configured span) and Later. 158- The bar at the bottom :: Earlier, Filter, Today, Go to Date, the span (Day, Week, Month or the configured span, and Log Mode, which adds closed entries and clocked time) and Later.
159- Filter :: keep or leave out tags and categories of the entries shown, or type a filter as Org's =/= takes it: =+keep= and =-drop= tags or categories, =<0:30= for effort, =/regexp/=. The filter in use shows above the bar. 159- Filter :: keep or leave out tags and categories of the entries shown, or type a filter as Org's =/= takes it: =+keep= and =-drop= tags or categories, =<0:30= for effort, =/regexp/=. The filter in use shows above the bar.
160- + on a day's header :: captures an entry scheduled on that day (see [[file:08-capture.org][Capture]]). 160- + on a day's header :: captures an entry scheduled on that day (see [[file:08-capture.org][Capture]]).
161- Tap :: an entry to read it, at its heading. 161- Tap :: an entry to read it, at its heading.
@@ -311,9 +311,11 @@ Changes to the files apply as they sync, and again each time you return to the a
311 311
312Stop Using This Folder returns every setting to its default. 312Stop Using This Folder returns every setting to its default.
313 313
314The Agenda and Calendar sections are the only places the app changes a setting itself: 314The Agenda, TODO Dependencies and Calendar sections are the only places the app changes a setting itself:
315 315
316- Show days without entries :: turns =org-agenda-show-all-dates= on or off. 316- Show days without entries :: turns =org-agenda-show-all-dates= on or off.
317- Blocked entries :: Dimmed, Hidden or Shown as usual (=org-agenda-dim-blocked-tasks=).
318- TODO Dependencies :: Not done before TODO children (=org-enforce-todo-dependencies=) and Not done with unchecked boxes (=org-enforce-todo-checkbox-dependencies=).
317- Show Calendar Events :: turns on =calendar-events=, and asks for access to your calendars. iOS asks for full access, as Calendar has no read-only level; Orgstar only reads. While access is missing the section shows Allow Access to Calendars…; if you declined, allow it in the Settings app under Privacy & Security ▸ Calendars. 319- Show Calendar Events :: turns on =calendar-events=, and asks for access to your calendars. iOS asks for full access, as Calendar has no read-only level; Orgstar only reads. While access is missing the section shows Allow Access to Calendars…; if you declined, allow it in the Settings app under Privacy & Security ▸ Calendars.
318- The calendars :: tap one to show or hide its events (=calendar-event-calendars=). With every calendar checked the setting is empty, so calendars added later show too. 320- The calendars :: tap one to show or hide its events (=calendar-event-calendars=). With every calendar checked the setting is empty, so calendars added later show too.
319 321
docs/manual/guide/15-alongside-emacs.org +1
@@ -247,5 +247,6 @@ Other:
247- Most =#+STARTUP= options for footnotes, entities, LaTeX previews, numbering and odd levels are accepted and ignored; see [[file:13-configuration.org][Configuration]]. 247- Most =#+STARTUP= options for footnotes, entities, LaTeX previews, numbering and odd levels are accepted and ignored; see [[file:13-configuration.org][Configuration]].
248- =display-line-numbers-type= has no relative or visual mode. 248- =display-line-numbers-type= has no relative or visual mode.
249- The agenda's text search (see [[file:07-agenda.org][The agenda]]) covers the agenda files only: =org-agenda-text-search-extra-files= and =org-agenda-search-view-max-outline-level= aren't supported. It runs from the view menu rather than the dispatcher's =s= and =S=, and the search view's =[=, =]=, ={= and =}= keys, which add words and regexps to the search, aren't supported. 249- The agenda's text search (see [[file:07-agenda.org][The agenda]]) covers the agenda files only: =org-agenda-text-search-extra-files= and =org-agenda-search-view-max-outline-level= aren't supported. It runs from the view menu rather than the dispatcher's =s= and =S=, and the search view's =[=, =]=, ={= and =}= keys, which add words and regexps to the search, aren't supported.
250- In regexps, =\<=, =\>= and =\b= use Org's word syntax but don't find a word boundary where the script changes between two word characters, as Emacs does (for example between an emoji and a letter).
250- =org-track-ordered-property-with-tag= isn't supported; Toggle ORDERED Property sets only the property. 251- =org-track-ordered-property-with-tag= isn't supported; Toggle ORDERED Property sets only the property.
251- When TODO dependencies refuse a change on a =COMMENT= heading, the heading keeps =COMMENT=. Org removes it before the check and doesn't put it back. 252- When TODO dependencies refuse a change on a =COMMENT= heading, the heading keeps =COMMENT=. Org removes it before the check and doesn't put it back.