Commit 778058aafc
778058aafc7ddacee4a613b1da87c60aa0c8e113
parent: c1e47f7751
Verified · cmc
cmc <hello@cleberg.net> · 2026-10-08 08:07 UTC
Clock resolution keys and import as Org
The resolution question reads J (clock out now and jump) and not c, as
org-clock-resolve does; j or J while clocking in shows the open clock
afterwards; Emacs import reads org-clock-idle-time and
org-clock-history-length.
Layout: unified · split
Sources/OrgApp/ClockModel.swift
+1
| @@ -286,6 +286,7 @@ public final class ClockModel { |
| 286 | 286 | clock, description: "Dangling clock started \(minutes) mins ago", lastValid: clock.startedAt, clockingIn: true, session, ask: ask, now: now |
| 287 | 287 | ) |
| 288 | 288 | outcome.messages += result.messages |
| 289 | if let jump = result.jump { outcome.jump = jump } |
| 289 | 290 | } |
| 290 | 291 | } |
| 291 | 292 | let location: (path: String, offset: Int) |
Sources/OrgApp/EmacsImport.swift
+6
| @@ -512,6 +512,12 @@ public struct EmacsImport: Sendable { |
| 512 | 512 | let days: Int? = value.integer ?? ["day": 1, "week": 7, "fortnight": 14, "month": 30, "year": 365][value.symbol ?? ""] |
| 513 | 513 | guard let days else { return note("unknown span \(value.description)") } |
| 514 | 514 | 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)) |
| 515 | 521 | case "org-agenda-start-day": |
| 516 | 522 | 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") } |
| 517 | 523 | case "org-todo-keywords": |
Sources/OrgCore/Commands/ClockResolution.swift
+1 −1
| @@ -6,7 +6,7 @@ import Foundation |
| 6 | 6 | |
| 7 | 7 | public enum ClockResolution { |
| 8 | 8 | /// 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"] |
| 10 | 10 | |
| 11 | 11 | /// The question's suffix, after the clock's description. |
| 12 | 12 | public static let keyHint = " [jkKtTgGSscCiq]? " |
Tests/OrgAppTests/ClockHistoryModelTests.swift
+12
| @@ -111,6 +111,18 @@ struct ClockHistoryModelTests { |
| 111 | 111 | #expect(clock.leftover == nil) |
| 112 | 112 | } |
| 113 | 113 | |
| 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 | |
| 114 | 126 | @Test func idleTimeIsSubtractedAndOfferedToTheNextClock() async throws { |
| 115 | 127 | let clock = ClockModel(file: stateFile, defaults: defaults) |
| 116 | 128 | _ = clock.clockIn(session, path: work.path, at: offset("* A"), now: start) |
Tests/OrgAppTests/EmacsImportTests.swift
+5 −1
| @@ -40,6 +40,8 @@ struct EmacsImportTests { |
| 40 | 40 | (setq org-tags-column 0 |
| 41 | 41 | org-agenda-span 10 |
| 42 | 42 | org-agenda-start-day "-3d" |
| 43 | org-clock-idle-time 15 |
| 44 | org-clock-history-length 10 |
| 43 | 45 | org-todo-keywords '((sequence "TODO(t)" "WAIT(w)" "|" "DONE(d)") (sequence "[ ](T)" "|" "[X](D)") (sequence "BUG" "|" "FIXED")))) |
| 44 | 46 | (after! org (setq org-M-RET-may-split-line nil org-list-allow-alphabetical t)) |
| 45 | 47 | (map! :map org-mode-map "C-c q" #'org-set-tags-command) |
| @@ -79,6 +81,8 @@ struct EmacsImportTests { |
| 79 | 81 | #expect(s["org-tags-column"] == .integer(-80)) |
| 80 | 82 | #expect(s["org-agenda-span"] == .integer(10)) |
| 81 | 83 | #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)) |
| 82 | 86 | #expect(s["org-M-RET-may-split-line"] == .bool(false)) |
| 83 | 87 | #expect(s["org-todo-keywords"] == .string("TODO(t) WAIT(w) | DONE(d)\nBUG | FIXED")) |
| 84 | 88 | #expect(s["fill-column"] == nil) |
| @@ -167,7 +171,7 @@ struct EmacsImportTests { |
| 167 | 171 | let keymap = folder.appendingPathComponent("keymap.toml") |
| 168 | 172 | #expect(KeymapLoader.load(.doom, userFile: keymap).problems.isEmpty) |
| 169 | 173 | #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)") |
| 171 | 175 | #expect(defaults.string(forKey: "themeFont") == "JetBrains Mono") |
| 172 | 176 | #expect(defaults.integer(forKey: "themeFontSize") == 14) |
| 173 | 177 | |
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 |
| 302 | 302 | |
| 303 | 303 | | Key | What happens to the =CLOCK:= line | |
| 304 | 304 | |----------------+-----------------------------------------------------------------------------------------------------| |
| 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. | |
| 306 | 307 | | =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. | |
| 307 | 308 | | =K= | Asks the same, then ends the line after the minutes kept and leaves you clocked out. | |
| 308 | 309 | | =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 |
| 312 | 313 | | =s= | Subtracts the idle time: the line ends when the idle time began, and a new clock starts now. | |
| 313 | 314 | | =S= | Subtracts the idle time and leaves you clocked out. | |
| 314 | 315 | | =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. | |
| 316 | 317 | |
| 317 | 318 | - =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. |
| 318 | 319 | - =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=, = |
| 416 | 416 | | =org-log-into-drawer= | A string as given; =t= becomes ="LOGBOOK"=; =nil= becomes =""= | |
| 417 | 417 | | =display-line-numbers-type= | =true= unless =nil=; =relative= and =visual= become absolute numbers | |
| 418 | 418 | | =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 | |
| 419 | 421 | | =org-agenda-start-day= | A day offset such as ="-3d"=; =nil= becomes ="+0d"=. Other forms aren't supported. | |
| 420 | 422 | | =org-todo-keywords= | =org-todo-keywords=, one line per sequence. Keywords with spaces are left out; =type= sequences are read as sequences. | |
| 421 | 423 | | =org-directory= | A folder to add | |