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