org-choose.el 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. ;;;_ org-choose.el --- decision management for org-mode
  2. ;;;_. Headers
  3. ;;;_ , License
  4. ;; Copyright (C) 2009 Tom Breton (Tehom)
  5. ;; Author: Tom Breton (Tehom)
  6. ;; Keywords:
  7. ;; This file is free software; you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation; either version 2, or (at your option)
  10. ;; any later version.
  11. ;; This file is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs; see the file COPYING. If not, write to
  17. ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  18. ;; Boston, MA 02111-1307, USA.
  19. ;;;_ , Commentary:
  20. ;;
  21. ;;;_ , Requires
  22. (require 'org)
  23. (eval-when-compile
  24. (require 'cl))
  25. ;;;_. Body
  26. ;;;_ , The variables
  27. (defstruct (org-choose-mark-data. (:type list))
  28. "The format of an entry in org-choose-mark-data.
  29. Indexes are 0-based or `nil'.
  30. "
  31. keyword
  32. bot-lower-range
  33. top-upper-range
  34. range-length
  35. static-default
  36. all-keywords)
  37. (defvar org-choose-mark-data
  38. ()
  39. "Alist of information for choose marks.
  40. Each entry is an `org-choose-mark-data.'" )
  41. (make-variable-buffer-local 'org-choose-mark-data)
  42. ;;;_ , For setup
  43. ;;;_ . org-choose-filter-one
  44. (defun org-choose-filter-one (i)
  45. "Return a list of
  46. * a canonized version of the string
  47. * optionally one symbol"
  48. (if
  49. (not
  50. (string-match "(.*)" i))
  51. (list i i)
  52. (let*
  53. (
  54. (end-text (match-beginning 0))
  55. (vanilla-text (substring i 0 end-text))
  56. ;;Get the parenthesized part.
  57. (match (match-string 0 i))
  58. ;;Remove the parentheses.
  59. (args (substring match 1 -1))
  60. ;;Split it
  61. (arglist
  62. (let
  63. ((arglist-x (split-string args ",")))
  64. ;;When string starts with "," `split-string' doesn't
  65. ;;make a first arg, so in that case make one
  66. ;;manually.
  67. (if
  68. (string-match "^," args)
  69. (cons nil arglist-x)
  70. arglist-x)))
  71. (decision-arg (second arglist))
  72. (type
  73. (cond
  74. ((string= decision-arg "0")
  75. 'default-mark)
  76. ((string= decision-arg "+")
  77. 'top-upper-range)
  78. ((string= decision-arg "-")
  79. 'bot-lower-range)
  80. (t nil)))
  81. (vanilla-arg (first arglist))
  82. (vanilla-mark
  83. (if vanilla-arg
  84. (concat vanilla-text "("vanilla-arg")")
  85. vanilla-text)))
  86. (if type
  87. (list vanilla-text vanilla-mark type)
  88. (list vanilla-text vanilla-mark)))))
  89. ;;;_ . org-choose-setup-vars
  90. (defun org-choose-setup-vars (bot-lower-range top-upper-range
  91. static-default num-items all-mark-texts)
  92. "Add to org-choose-mark-data according to arguments"
  93. (let*
  94. (
  95. (tail
  96. ;;If there's no bot-lower-range or no default, we don't
  97. ;;have ranges.
  98. (cdr
  99. (if (and static-default bot-lower-range)
  100. (let*
  101. (
  102. ;;If there's no top-upper-range, use the last
  103. ;;item.
  104. (top-upper-range
  105. (or top-upper-range (1- num-items)))
  106. (lower-range-length
  107. (1+ (- static-default bot-lower-range)))
  108. (upper-range-length
  109. (- top-upper-range static-default))
  110. (range-length
  111. (min upper-range-length lower-range-length)))
  112. (make-org-choose-mark-data.
  113. :keyword nil
  114. :bot-lower-range bot-lower-range
  115. :top-upper-range top-upper-range
  116. :range-length range-length
  117. :static-default static-default
  118. :all-keywords all-mark-texts))
  119. (make-org-choose-mark-data.
  120. :keyword nil
  121. :bot-lower-range nil
  122. :top-upper-range nil
  123. :range-length nil
  124. :static-default (or static-default 0)
  125. :all-keywords all-mark-texts)))))
  126. (dolist (text all-mark-texts)
  127. (pushnew (cons text tail)
  128. org-choose-mark-data
  129. :test
  130. #'(lambda (a b)
  131. (equal (car a) (car b)))))))
  132. ;;;_ . org-choose-filter-tail
  133. (defun org-choose-filter-tail (raw)
  134. "Return a translation of RAW to vanilla and set appropriate
  135. buffer-local variables.
  136. RAW is a list of strings representing the input text of a choose
  137. interpretation."
  138. (let
  139. ((vanilla-list nil)
  140. (all-mark-texts nil)
  141. (index 0)
  142. bot-lower-range top-upper-range range-length static-default)
  143. (dolist (i raw)
  144. (destructuring-bind
  145. (vanilla-text vanilla-mark &optional type)
  146. (org-choose-filter-one i)
  147. (cond
  148. ((eq type 'bot-lower-range)
  149. (setq bot-lower-range index))
  150. ((eq type 'top-upper-range)
  151. (setq top-upper-range index))
  152. ((eq type 'default-mark)
  153. (setq static-default index)))
  154. (incf index)
  155. (push vanilla-text all-mark-texts)
  156. (push vanilla-mark vanilla-list)))
  157. (org-choose-setup-vars bot-lower-range top-upper-range
  158. static-default index (reverse all-mark-texts))
  159. (nreverse vanilla-list)))
  160. ;;;_ . org-choose-setup-filter
  161. (defun org-choose-setup-filter (raw)
  162. "A setup filter for choose interpretations."
  163. (when (eq (car raw) 'choose)
  164. (cons
  165. 'choose
  166. (org-choose-filter-tail (cdr raw)))))
  167. ;;;_ . org-choose-conform-after-promotion
  168. (defun org-choose-conform-after-promotion (entry-pos keywords highest-ok-ix)
  169. ""
  170. (unless
  171. ;;Skip the entry that triggered this by skipping any entry with
  172. ;;the same starting position. Both map and plist use the start
  173. ;;of the header line as the position, so we can just compare
  174. ;;them with `='
  175. (= (point) entry-pos)
  176. (let
  177. ((ix
  178. (org-choose-get-entry-index keywords)))
  179. ;;If the index of the entry exceeds the highest allowable
  180. ;;index, change it to that.
  181. (when (and ix
  182. (> ix highest-ok-ix))
  183. (org-todo
  184. (nth highest-ok-ix keywords))))))
  185. ;;;_ . org-choose-conform-after-demotion
  186. (defun org-choose-conform-after-demotion (entry-pos keywords
  187. raise-to-ix
  188. old-highest-ok-ix)
  189. ""
  190. (unless
  191. ;;Skip the entry that triggered this.
  192. (= (point) entry-pos)
  193. (let
  194. ((ix
  195. (org-choose-get-entry-index keywords)))
  196. ;;If the index of the entry was at or above the old allowable
  197. ;;position, change it to the new mirror position if there is
  198. ;;one.
  199. (when (and
  200. ix
  201. raise-to-ix
  202. (>= ix old-highest-ok-ix))
  203. (org-todo
  204. (nth raise-to-ix keywords))))))
  205. ;;;_ , org-choose-keep-sensible (the trigger-hook function)
  206. (defun org-choose-keep-sensible (change-plist)
  207. ""
  208. (let*
  209. ( (from (plist-get change-plist :from))
  210. (to (plist-get change-plist :to))
  211. (entry-pos
  212. (set-marker
  213. (make-marker)
  214. (plist-get change-plist :position)))
  215. (kwd-data
  216. (assoc to org-todo-kwd-alist)))
  217. (when
  218. (eq (nth 1 kwd-data) 'choose)
  219. (let*
  220. (
  221. (data
  222. (assoc to org-choose-mark-data))
  223. (keywords
  224. (org-choose-mark-data.-all-keywords data))
  225. (old-index
  226. (org-choose-get-index-in-keywords
  227. from
  228. keywords))
  229. (new-index
  230. (org-choose-get-index-in-keywords
  231. to
  232. keywords))
  233. (highest-ok-ix
  234. (org-choose-highest-other-ok
  235. new-index
  236. data))
  237. (funcdata
  238. (cond
  239. ;;The entry doesn't participate in conformance,
  240. ;;so give `nil' which does nothing.
  241. ((not highest-ok-ix) nil)
  242. ;;The entry was created or promoted
  243. ((or
  244. (not old-index)
  245. (> new-index old-index))
  246. (list
  247. #'org-choose-conform-after-promotion
  248. entry-pos keywords
  249. highest-ok-ix))
  250. (t ;;Otherwise the entry was demoted.
  251. (let
  252. (
  253. (raise-to-ix
  254. (min
  255. highest-ok-ix
  256. (org-choose-mark-data.-static-default
  257. data)))
  258. (old-highest-ok-ix
  259. (org-choose-highest-other-ok
  260. old-index
  261. data)))
  262. (list
  263. #'org-choose-conform-after-demotion
  264. entry-pos
  265. keywords
  266. raise-to-ix
  267. old-highest-ok-ix))))))
  268. (if funcdata
  269. ;;The funny-looking names are to make variable capture
  270. ;;unlikely. (Poor-man's lexical bindings).
  271. (destructuring-bind (func-d473 . args-46k) funcdata
  272. (let
  273. ((map-over-entries
  274. (org-choose-get-fn-map-group))
  275. ;;We may call `org-todo', so let various hooks
  276. ;;`nil' so we don't cause loops.
  277. org-after-todo-state-change-hook
  278. org-trigger-hook
  279. org-blocker-hook
  280. org-todo-get-default-hook
  281. ;;Also let this alist `nil' so we don't log
  282. ;;secondary transitions.
  283. org-todo-log-states)
  284. ;;Map over group
  285. (funcall map-over-entries
  286. #'(lambda ()
  287. (apply func-d473 args-46k))))))))
  288. ;;Remove the marker
  289. (set-marker entry-pos nil)))
  290. ;;;_ , Getting the default mark
  291. ;;;_ . org-choose-get-index-in-keywords
  292. (defun org-choose-get-index-in-keywords (ix all-keywords)
  293. "Return index of current entry."
  294. (if ix
  295. (position ix all-keywords
  296. :test #'equal)))
  297. ;;;_ . org-choose-get-entry-index
  298. (defun org-choose-get-entry-index (all-keywords)
  299. "Return index of current entry."
  300. (let*
  301. ((state (org-entry-get (point) "TODO")))
  302. (org-choose-get-index-in-keywords state all-keywords)))
  303. ;;;_ . org-choose-get-fn-map-group
  304. (defun org-choose-get-fn-map-group ()
  305. "Return a function to map over the group"
  306. #'(lambda (fn)
  307. (save-excursion
  308. (outline-up-heading-all 1)
  309. (save-restriction
  310. (org-map-entries fn nil 'tree)))))
  311. ;;;_ . org-choose-get-highest-mark-index
  312. (defun org-choose-get-highest-mark-index (keywords)
  313. "Get the index of the highest current mark in the group.
  314. If there is none, return 0"
  315. (let*
  316. (
  317. ;;Func maps over applicable entries.
  318. (map-over-entries
  319. (org-choose-get-fn-map-group))
  320. (indexes-list
  321. (remove nil
  322. (funcall map-over-entries
  323. #'(lambda ()
  324. (org-choose-get-entry-index keywords))))))
  325. (if
  326. indexes-list
  327. (apply #'max indexes-list)
  328. 0)))
  329. ;;;_ . org-choose-highest-ok
  330. (defun org-choose-highest-other-ok (ix data)
  331. ""
  332. (let
  333. (
  334. (bot-lower-range
  335. (org-choose-mark-data.-bot-lower-range data))
  336. (top-upper-range
  337. (org-choose-mark-data.-top-upper-range data))
  338. (range-length
  339. (org-choose-mark-data.-range-length data)))
  340. (when (and ix bot-lower-range)
  341. (let*
  342. ((delta
  343. (- top-upper-range ix)))
  344. (unless
  345. (< range-length delta)
  346. (+ bot-lower-range delta))))))
  347. ;;;_ . org-choose-get-default-mark-index
  348. (defun org-choose-get-default-mark-index (data)
  349. "Get the index of the default mark in a choose interpretation.
  350. Args are in the same order as the fields of
  351. `org-choose-mark-data.' and have the same meaning."
  352. (or
  353. (let
  354. ((highest-mark-index
  355. (org-choose-get-highest-mark-index
  356. (org-choose-mark-data.-all-keywords data))))
  357. (org-choose-highest-other-ok
  358. highest-mark-index data))
  359. (org-choose-mark-data.-static-default data)))
  360. ;;;_ . org-choose-get-mark-N
  361. (defun org-choose-get-mark-N (n data)
  362. "Get the text of the nth mark in a choose interpretation."
  363. (let*
  364. ((l (org-choose-mark-data.-all-keywords data)))
  365. (nth n l)))
  366. ;;;_ . org-choose-get-default-mark
  367. (defun org-choose-get-default-mark (new-mark old-mark)
  368. "Get the default mark IFF in a choose interpretation.
  369. NEW-MARK and OLD-MARK are the text of the new and old marks."
  370. (let*
  371. (
  372. (old-kwd-data
  373. (assoc old-mark org-todo-kwd-alist))
  374. (new-kwd-data
  375. (assoc new-mark org-todo-kwd-alist))
  376. (becomes-choose
  377. (and
  378. (or
  379. (not old-kwd-data)
  380. (not
  381. (eq (nth 1 old-kwd-data) 'choose)))
  382. (eq (nth 1 new-kwd-data) 'choose))))
  383. (when
  384. becomes-choose
  385. (let
  386. ((new-mark-data
  387. (assoc new-mark org-choose-mark-data)))
  388. (if
  389. new-mark
  390. (org-choose-get-mark-N
  391. (org-choose-get-default-mark-index
  392. new-mark-data)
  393. new-mark-data)
  394. (error "Somehow got an unrecognizable mark"))))))
  395. ;;;_ , Setting it all up
  396. (eval-after-load 'org
  397. '(progn
  398. (add-to-list 'org-todo-setup-filter-hook
  399. #'org-choose-setup-filter)
  400. (add-to-list 'org-todo-get-default-hook
  401. #'org-choose-get-default-mark)
  402. (add-to-list 'org-trigger-hook
  403. #'org-choose-keep-sensible)
  404. (add-to-list 'org-todo-interpretation-widgets
  405. '(:tag "Choose (to record decisions)" choose))
  406. ; CD (add-to-list 'org-todo-normal-interpretations 'choose))
  407. ))
  408. ;;;_. Footers
  409. ;;;_ , Provides
  410. (provide 'org-choose)
  411. ;;;_ * Local emacs vars.
  412. ;;;_ + Local variables:
  413. ;;;_ + End:
  414. ;;;_ , End
  415. ;;; org-choose.el ends here