Sources/OrgCore/Commands/Dwim.swift
42 lines · 2356 bytes
1import Foundation
2
3/// Doom's `+org/dwim-at-point` (RET in normal state): on a heading, TODO becomes the first
4/// done keyword of its sequence and a done keyword the first TODO one; on an item, its
5/// checkbox toggles; in a src block, the block runs; on a `#+TBLFM` line or in a table with
6/// formulas, the table recalculates, otherwise aligns.
7public struct OrgDwim: OrgCommand {
8 public init() {}
9 public var id: String { "org.dwim" }
10 public var title: String { "Act at Point" }
11
12 public func applies(in context: EditContext) -> Bool { true }
13
14 public func run(in context: EditContext) -> CommandStep {
15 // `+org/dwim-at-point` follows a link first.
16 if Links.link(at: context.caret, text: context.text, tree: context.tree) != nil { return OpenLinkAtPoint().run(in: context) }
17 if TableRecalculate.tblfmLine(in: context) != nil { return TableRecalculate(all: true).run(in: context) }
18 if KeyContext.holds("src", in: context) { return ExecuteSrcBlock().run(in: context) }
19 if KeyContext.holds("table", in: context) {
20 let buffer = EmacsBuffer(context.text, point: context.caret, settings: context.tree.settings)
21 if buffer.firstTblfm(after: buffer.tableEnd()) != nil { return TableRecalculate(all: true).run(in: context) }
22 return TableCommand(.align).run(in: context)
23 }
24 if KeyContext.holds("item", in: context) {
25 let toggle = ToggleCheckbox()
26 if toggle.applies(in: context) { return toggle.run(in: context) }
27 }
28 guard headingOnLine(at: context.caret, in: context.tree) != nil else { return .failed("Nothing to do here") }
29 let settings = context.tree.settings
30 return commitBuffer(context) { buffer in
31 guard let current = buffer.todoKeyword,
32 let sequence = settings.todoSequences.first(where: { ($0.active + $0.done).contains { $0.name == current } }) else { return }
33 if settings.isDone(current) {
34 buffer.setTodoKeyword(sequence.active.first?.name)
35 } else if let done = sequence.done.first?.name {
36 try buffer.checkTodoBlocking(from: current, to: done)
37 buffer.setTodoKeyword(done)
38 }
39 try buffer.afterTodoChange(previous: current, answers: context.answers)
40 }
41 }
42}