Clock resolution keys and import as Org !174

merged merged by cmc on 2026-10-08 08:07 UTC · krz/orgstar:clock-resolution-fixes into main

7 files changed, +30 −4

Layout: unified · split

Sources/OrgApp/ClockModel.swift +1
@@ -286,6 +286,7 @@ public final class ClockModel {
286286 clock, description: "Dangling clock started \(minutes) mins ago", lastValid: clock.startedAt, clockingIn: true, session, ask: ask, now: now
287287 )
288288 outcome.messages += result.messages
289 if let jump = result.jump { outcome.jump = jump }
289290 }
290291 }
291292 let location: (path: String, offset: Int)
Sources/OrgApp/EmacsImport.swift +6
@@ -512,6 +512,12 @@ public struct EmacsImport: Sendable {
512512 let days: Int? = value.integer ?? ["day": 1, "week": 7, "fortnight": 14, "month": 30, "year": 365][value.symbol ?? ""]
513513 guard let days else { return note("unknown span \(value.description)") }
514514 add(name, .integer(days))
515 case "org-clock-idle-time":
516 // nil is never; a number is minutes.
517 if value.symbol == "nil" { add(name, .integer(0)) } else if let minutes = value.integer, minutes > 0 { add(name, .integer(minutes)) } else { note("not a number of minutes") }
518 case "org-clock-history-length":
519 guard let length = value.integer, length > 0 else { return note("not a positive number") }
520 add(name, .integer(length))
515521 case "org-agenda-start-day":
516522 if value.symbol == "nil" { add(name, .string("+0d")) } else if let s = value.string, UserConfig.dayOffset(s) != nil { add(name, .string(s)) } else { note("only day offsets such as \"-3d\" are supported") }
517523 case "org-todo-keywords":
Sources/OrgCore/Commands/ClockResolution.swift +1 −1
@@ -6,7 +6,7 @@ import Foundation
66
77public enum ClockResolution {
88 /// The keys `org-clock-resolve` reads.
9 public static let keys: [Character] = ["j", "k", "K", "t", "T", "g", "G", "S", "s", "c", "C", "i", "q"]
9 public static let keys: [Character] = ["j", "J", "k", "K", "t", "T", "g", "G", "S", "s", "C", "i", "q"]
1010
1111 /// The question's suffix, after the clock's description.
1212 public static let keyHint = " [jkKtTgGSscCiq]? "
Tests/OrgAppTests/ClockHistoryModelTests.swift +12
@@ -111,6 +111,18 @@ struct ClockHistoryModelTests {
111111 #expect(clock.leftover == nil)
112112 }
113113
114 @Test func jumpingFromADanglingClockShowsItAfterClockingIn() async throws {
115 let other = try notes.write("other.org", "* Old\n:LOGBOOK:\nCLOCK: [2026-09-09 Wed 09:00]\n:END:\n")
116 let clock = ClockModel(file: stateFile, defaults: defaults)
117 let started = clock.openClocks(session, paths: [other.path])[0].startedAt
118 // `c` isn't a key org-clock-resolve reads; `J` clocks out and jumps.
119 let answers = ScriptedAnswers(["c", "J"])
120 let outcome = await clock.clockIn(session, target: .caret, paths: [other.path], ask: answers.ask, now: { started.addingTimeInterval(600) })
121 #expect(answers.asked.count == 2)
122 #expect(outcome.jump?.path == other.path)
123 #expect(clock.running?.heading == "A")
124 }
125
114126 @Test func idleTimeIsSubtractedAndOfferedToTheNextClock() async throws {
115127 let clock = ClockModel(file: stateFile, defaults: defaults)
116128 _ = clock.clockIn(session, path: work.path, at: offset("* A"), now: start)
Tests/OrgAppTests/EmacsImportTests.swift +5 −1
@@ -40,6 +40,8 @@ struct EmacsImportTests {
4040 (setq org-tags-column 0
4141 org-agenda-span 10
4242 org-agenda-start-day "-3d"
43 org-clock-idle-time 15
44 org-clock-history-length 10
4345 org-todo-keywords '((sequence "TODO(t)" "WAIT(w)" "|" "DONE(d)") (sequence "[ ](T)" "|" "[X](D)") (sequence "BUG" "|" "FIXED"))))
4446 (after! org (setq org-M-RET-may-split-line nil org-list-allow-alphabetical t))
4547 (map! :map org-mode-map "C-c q" #'org-set-tags-command)
@@ -79,6 +81,8 @@ struct EmacsImportTests {
7981 #expect(s["org-tags-column"] == .integer(-80))
8082 #expect(s["org-agenda-span"] == .integer(10))
8183 #expect(s["org-agenda-start-day"] == .string("-3d"))
84 #expect(s["org-clock-idle-time"] == .integer(15))
85 #expect(s["org-clock-history-length"] == .integer(10))
8286 #expect(s["org-M-RET-may-split-line"] == .bool(false))
8387 #expect(s["org-todo-keywords"] == .string("TODO(t) WAIT(w) | DONE(d)\nBUG | FIXED"))
8488 #expect(s["fill-column"] == nil)
@@ -167,7 +171,7 @@ struct EmacsImportTests {
167171 let keymap = folder.appendingPathComponent("keymap.toml")
168172 #expect(KeymapLoader.load(.doom, userFile: keymap).problems.isEmpty)
169173 #expect(KeymapLoader.load(.doom, userFile: keymap).keymap.keyLabels(for: "app.agenda", modes: ["normal"]).contains("C-c a"))
170 #expect(summary.hasPrefix("Imported 17 settings, 3 capture templates, 3 key bindings."), "\(summary)")
174 #expect(summary.hasPrefix("Imported 19 settings, 3 capture templates, 3 key bindings."), "\(summary)")
171175 #expect(defaults.string(forKey: "themeFont") == "JetBrains Mono")
172176 #expect(defaults.integer(forKey: "themeFontSize") == 14)
173177
docs/manual/guide/06-dates-and-clocking.org +3 −2
@@ -302,7 +302,8 @@ The answer is a key, as in =org-clock-resolve=. The idle time is the time since
302302
303303| Key | What happens to the =CLOCK:= line |
304304|----------------+-----------------------------------------------------------------------------------------------------|
305| =j= | Nothing; after Resolve Open Clocks… or the idle question, the line opens in the editor to adjust by hand. |
305| =j= | Nothing; the line opens in the editor to adjust by hand (after the clock-in, when you were clocking in). |
306| =J= | Ends the line now, then opens it in the editor. |
306307| =k= | Asks how many minutes of the idle time to keep, all of them by default. With all, the clock keeps running. With fewer, the line ends that many minutes after the idle time began, and a new clock starts now. |
307308| =K= | Asks the same, then ends the line after the minutes kept and leaves you clocked out. |
308309| =t= | Like =k=, but asks for the date and time you got distracted; the line ends then. |
@@ -312,7 +313,7 @@ The answer is a key, as in =org-clock-resolve=. The idle time is the time since
312313| =s= | Subtracts the idle time: the line ends when the idle time began, and a new clock starts now. |
313314| =S= | Subtracts the idle time and leaves you clocked out. |
314315| =C= | Removes the line, as though you never clocked in. |
315| =c=, =i=, =q=, =Esc= | Leaves the line as it is, keeping all the idle time. |
316| =i=, =q=, =Esc= | Leaves the line as it is, keeping all the idle time. |
316317
317318- =k=, =K=, =g= and =G= ask for minutes with the default in the question, as =Keep how many minutes (default 95):=; Return with an empty field takes the default.
318319- =t= and =T= ask =Date+time:= with Org's date syntax, as =14:00= or =-1h=.
docs/manual/guide/13-configuration.org +2
@@ -416,6 +416,8 @@ Only variables whose names start with =org-=, =appt-=, =display-line-numbers=, =
416416| =org-log-into-drawer= | A string as given; =t= becomes ="LOGBOOK"=; =nil= becomes =""= |
417417| =display-line-numbers-type= | =true= unless =nil=; =relative= and =visual= become absolute numbers |
418418| =org-agenda-span= | A number, or =day= (1), =week= (7), =fortnight= (14), =month= (30), =year= (365) |
419| =org-clock-idle-time= | Minutes; =nil= becomes =0= (never) |
420| =org-clock-history-length= | The same key, when the value is a positive number |
419421| =org-agenda-start-day= | A day offset such as ="-3d"=; =nil= becomes ="+0d"=. Other forms aren't supported. |
420422| =org-todo-keywords= | =org-todo-keywords=, one line per sequence. Keywords with spaces are left out; =type= sequences are read as sequences. |
421423| =org-directory= | A folder to add |