Sources/OrgCore/Commands/Properties.swift
44 lines · 2105 bytes
1import Foundation
2
3/// `org-set-property` / `org-entry-put` for one entry.
4public struct SetProperty: OrgCommand {
5 public let key: String
6 public let value: String
7 public init(key: String, value: String) {
8 self.key = key
9 self.value = value
10 }
11 public var id: String { "org.property.set" }
12 public var title: String { "Set Property" }
13 public func applies(in context: EditContext) -> Bool { entryHeading(at: context.caret, in: context.tree) != nil }
14 public func run(in context: EditContext) -> CommandStep {
15 if key.uppercased() == "TODO" { return SetTodo(keyword: value.isEmpty ? nil : value).run(in: context) }
16 guard key.range(of: "^[^\\s:]+$", options: .regularExpression) != nil else { return .failed("Invalid property name: \"\(key)\"") }
17 return commitBuffer(context) { buffer in
18 buffer.saveExcursion { buffer.entryPut(key, value) }
19 }
20 }
21}
22
23/// `org-todo` with a keyword (nil for none), with the statistics and repeat it brings.
24public struct SetTodo: OrgCommand {
25 public let keyword: String?
26 public init(keyword: String?) { self.keyword = keyword }
27 public var id: String { "org.todo.set" }
28 public var title: String { "Set TODO Keyword" }
29 public func applies(in context: EditContext) -> Bool { entryHeading(at: context.caret, in: context.tree) != nil }
30 public func run(in context: EditContext) -> CommandStep {
31 guard entryHeading(at: context.caret, in: context.tree) != nil else { return .failed("Before first headline") }
32 let settings = context.tree.settings
33 if let keyword, !settings.todoSequences.contains(where: { ($0.active + $0.done).contains { $0.name == keyword } }) {
34 return .failed("\"\(keyword)\" is not a valid TODO state")
35 }
36 return commitBuffer(context) { buffer in
37 try buffer.saveExcursion {
38 let previous = buffer.todoKeyword
39 buffer.setTodoKeyword(keyword)
40 try buffer.afterTodoChange(previous: previous, answers: context.answers)
41 }
42 }
43 }
44}