Sources/OrgCore/Capture/CaptureSchedule.swift
33 lines · 2013 bytes
1 symbol in this file
1import Foundation
2
3extension Capture {
4 /// A filled entry scheduled on `day` (as `Days` numbers it), for capturing from an agenda
5 /// day: `SCHEDULED: <2026-10-09 Fri>` goes on the planning line after the heading. Text that
6 /// is not an entry, or already has an active timestamp (`%t`, `%^t`), is left as it is.
7 /// `cursor` is a UTF-16 offset and moves with the text after it.
8 public static func scheduling(_ text: String, cursor: Int, on day: Int) -> (text: String, cursor: Int) {
9 let ns = text as NSString
10 guard text.range(of: "^\\*+ ", options: .regularExpression) != nil,
11 text.range(of: "<[0-9]{4}-[0-9]{2}-[0-9]{2}", options: .regularExpression) == nil else { return (text, cursor) }
12 let d = Days.date(day)
13 let name = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][Days.weekday(day)]
14 let stamp = String(format: "SCHEDULED: <%04d-%02d-%02d %@>", d.year, d.month, d.day, name)
15 let headingEnd = ns.range(of: "\n").location
16 let insertion: (at: Int, text: String)
17 if headingEnd == NSNotFound {
18 insertion = (ns.length, "\n" + stamp)
19 } else {
20 let rest = ns.substring(from: headingEnd + 1)
21 let lineEnd = (rest as NSString).range(of: "\n").location
22 let next = lineEnd == NSNotFound ? rest : (rest as NSString).substring(to: lineEnd)
23 if next.range(of: "^\\s*(CLOSED|DEADLINE|SCHEDULED):", options: .regularExpression) != nil {
24 insertion = (headingEnd + 1 + (next as NSString).length, " " + stamp)
25 } else {
26 insertion = (headingEnd + 1, stamp + "\n")
27 }
28 }
29 let result = ns.replacingCharacters(in: NSRange(location: insertion.at, length: 0), with: insertion.text)
30 let moved = cursor > insertion.at || (cursor == insertion.at && headingEnd != NSNotFound && cursor > headingEnd)
31 return (result, moved ? cursor + (insertion.text as NSString).length : cursor)
32 }
33}