org-interactive-query.patch.txt 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. --- org-vendor/org.el 2008-01-06 10:30:26.000000000 -0500
  2. +++ org/org.el 2008-01-12 17:19:15.000000000 -0500
  3. @@ -15078,7 +15078,8 @@
  4. (let ((org-last-tags-completion-table
  5. (org-global-tags-completion-table)))
  6. (setq match (completing-read
  7. - "Match: " 'org-tags-completion-function nil nil nil
  8. + "Match: " 'org-tags-completion-function nil nil
  9. + org-agenda-query-string
  10. 'org-tags-history))))
  11. ;; Parse the string and create a lisp form
  12. @@ -18812,6 +18813,7 @@
  13. (defvar org-agenda-follow-mode nil)
  14. (defvar org-agenda-show-log nil)
  15. (defvar org-agenda-redo-command nil)
  16. +(defvar org-agenda-query-string nil)
  17. (defvar org-agenda-mode-hook nil)
  18. (defvar org-agenda-type nil)
  19. (defvar org-agenda-force-single-file nil)
  20. @@ -18947,6 +18949,10 @@
  21. (org-defkey org-agenda-mode-map [(right)] 'org-agenda-later)
  22. (org-defkey org-agenda-mode-map [(left)] 'org-agenda-earlier)
  23. (org-defkey org-agenda-mode-map "\C-c\C-x\C-c" 'org-agenda-columns)
  24. +(org-defkey org-agenda-mode-map "=" 'org-agenda-query-clear-cmd)
  25. +(org-defkey org-agenda-mode-map "/" 'org-agenda-query-and-cmd)
  26. +(org-defkey org-agenda-mode-map ";" 'org-agenda-query-or-cmd)
  27. +(org-defkey org-agenda-mode-map "\\" 'org-agenda-query-not-cmd)
  28. (defvar org-agenda-keymap (copy-keymap org-agenda-mode-map)
  29. "Local keymap for agenda entries from Org-mode.")
  30. @@ -20423,9 +20429,10 @@
  31. (setq matcher (org-make-tags-matcher match)
  32. match (car matcher) matcher (cdr matcher))
  33. (org-prepare-agenda (concat "TAGS " match))
  34. + (setq org-agenda-query-string match)
  35. (setq org-agenda-redo-command
  36. (list 'org-tags-view (list 'quote todo-only)
  37. - (list 'if 'current-prefix-arg nil match)))
  38. + (list 'if 'current-prefix-arg nil 'org-agenda-query-string)))
  39. (setq files (org-agenda-files)
  40. rtnall nil)
  41. (while (setq file (pop files))
  42. @@ -20461,7 +20468,7 @@
  43. (add-text-properties pos (1- (point)) (list 'face 'org-warning))
  44. (setq pos (point))
  45. (unless org-agenda-multi
  46. - (insert "Press `C-u r' to search again with new search string\n"))
  47. + (insert "Press `C-u r' to enter new search string; use `/;\\=' to adjust interactively\n"))
  48. (add-text-properties pos (1- (point)) (list 'face 'org-agenda-structure)))
  49. (when rtnall
  50. (insert (org-finalize-agenda-entries rtnall) "\n"))
  51. @@ -20471,6 +20478,275 @@
  52. (org-finalize-agenda)
  53. (setq buffer-read-only t)))
  54. +;;; Agenda interactive query manipulation
  55. +
  56. +(defcustom org-agenda-query-selection-single-key t
  57. + "Non-nil means, query manipulation exits after first change.
  58. +When nil, you have to press RET to exit it.
  59. +During query selection, you can toggle this flag with `C-c'.
  60. +This variable can also have the value `expert'. In this case, the window
  61. +displaying the tags menu is not even shown, until you press C-c again."
  62. + :group 'org-agenda
  63. + :type '(choice
  64. + (const :tag "No" nil)
  65. + (const :tag "Yes" t)
  66. + (const :tag "Expert" expert)))
  67. +
  68. +(defun org-agenda-query-selection (current op table &optional todo-table)
  69. + "Fast query manipulation with single keys.
  70. +CURRENT is the current query string, OP is the initial
  71. +operator (one of \"+|-=\"), TABLE is an alist of tags and
  72. +corresponding keys, possibly with grouping information.
  73. +TODO-TABLE is a similar table with TODO keywords, should these
  74. +have keys assigned to them. If the keys are nil, a-z are
  75. +automatically assigned. Returns the new query string, or nil to
  76. +not change the current one."
  77. + (let* ((fulltable (append table todo-table))
  78. + (maxlen (apply 'max (mapcar
  79. + (lambda (x)
  80. + (if (stringp (car x)) (string-width (car x)) 0))
  81. + fulltable)))
  82. + (fwidth (+ maxlen 3 1 3))
  83. + (ncol (/ (- (window-width) 4) fwidth))
  84. + (expert (eq org-agenda-query-selection-single-key 'expert))
  85. + (exit-after-next org-agenda-query-selection-single-key)
  86. + (done-keywords org-done-keywords)
  87. + tbl char cnt e groups ingroup
  88. + tg c2 c c1 ntable rtn)
  89. + (save-window-excursion
  90. + (if expert
  91. + (set-buffer (get-buffer-create " *Org tags*"))
  92. + (delete-other-windows)
  93. + (split-window-vertically)
  94. + (org-switch-to-buffer-other-window (get-buffer-create " *Org tags*")))
  95. + (erase-buffer)
  96. + (org-set-local 'org-done-keywords done-keywords)
  97. + (insert "Query: " current "\n")
  98. + (org-agenda-query-op-line op)
  99. + (insert "\n\n")
  100. + (org-fast-tag-show-exit exit-after-next)
  101. + (setq tbl fulltable char ?a cnt 0)
  102. + (while (setq e (pop tbl))
  103. + (cond
  104. + ((equal e '(:startgroup))
  105. + (push '() groups) (setq ingroup t)
  106. + (when (not (= cnt 0))
  107. + (setq cnt 0)
  108. + (insert "\n"))
  109. + (insert "{ "))
  110. + ((equal e '(:endgroup))
  111. + (setq ingroup nil cnt 0)
  112. + (insert "}\n"))
  113. + (t
  114. + (setq tg (car e) c2 nil)
  115. + (if (cdr e)
  116. + (setq c (cdr e))
  117. + ;; automatically assign a character.
  118. + (setq c1 (string-to-char
  119. + (downcase (substring
  120. + tg (if (= (string-to-char tg) ?@) 1 0)))))
  121. + (if (or (rassoc c1 ntable) (rassoc c1 table))
  122. + (while (or (rassoc char ntable) (rassoc char table))
  123. + (setq char (1+ char)))
  124. + (setq c2 c1))
  125. + (setq c (or c2 char)))
  126. + (if ingroup (push tg (car groups)))
  127. + (setq tg (org-add-props tg nil 'face
  128. + (cond
  129. + ((not (assoc tg table))
  130. + (org-get-todo-face tg))
  131. + (t nil))))
  132. + (if (and (= cnt 0) (not ingroup)) (insert " "))
  133. + (insert "[" c "] " tg (make-string
  134. + (- fwidth 4 (length tg)) ?\ ))
  135. + (push (cons tg c) ntable)
  136. + (when (= (setq cnt (1+ cnt)) ncol)
  137. + (insert "\n")
  138. + (if ingroup (insert " "))
  139. + (setq cnt 0)))))
  140. + (setq ntable (nreverse ntable))
  141. + (insert "\n")
  142. + (goto-char (point-min))
  143. + (if (and (not expert) (fboundp 'fit-window-to-buffer))
  144. + (fit-window-to-buffer))
  145. + (setq rtn
  146. + (catch 'exit
  147. + (while t
  148. + (message "[a-z..]:Toggle [SPC]:clear [RET]:accept [TAB]:free%s%s"
  149. + (if groups " [!] no groups" " [!]groups")
  150. + (if expert " [C-c]:window" (if exit-after-next " [C-c]:single" " [C-c]:multi")))
  151. + (setq c (let ((inhibit-quit t)) (read-char-exclusive)))
  152. + (cond
  153. + ((= c ?\r) (throw 'exit t))
  154. + ((= c ?!)
  155. + (setq groups (not groups))
  156. + (goto-char (point-min))
  157. + (while (re-search-forward "[{}]" nil t) (replace-match " ")))
  158. + ((= c ?\C-c)
  159. + (if (not expert)
  160. + (org-fast-tag-show-exit
  161. + (setq exit-after-next (not exit-after-next)))
  162. + (setq expert nil)
  163. + (delete-other-windows)
  164. + (split-window-vertically)
  165. + (org-switch-to-buffer-other-window " *Org tags*")
  166. + (and (fboundp 'fit-window-to-buffer)
  167. + (fit-window-to-buffer))))
  168. + ((or (= c ?\C-g)
  169. + (and (= c ?q) (not (rassoc c ntable))))
  170. + (setq quit-flag t))
  171. + ((= c ?\ )
  172. + (setq current "")
  173. + (if exit-after-next (setq exit-after-next 'now)))
  174. + ((= c ?\[) ; clear left
  175. + (org-agenda-query-decompose current)
  176. + (setq current (concat "/" (match-string 2 current)))
  177. + (if exit-after-next (setq exit-after-next 'now)))
  178. + ((= c ?\]) ; clear right
  179. + (org-agenda-query-decompose current)
  180. + (setq current (match-string 1 current))
  181. + (if exit-after-next (setq exit-after-next 'now)))
  182. + ((= c ?\t)
  183. + (condition-case nil
  184. + (setq current (read-string "Query: " current))
  185. + (quit))
  186. + (if exit-after-next (setq exit-after-next 'now)))
  187. + ;; operators
  188. + ((or (= c ?/) (= c ?+)) (setq op "+"))
  189. + ((or (= c ?\;) (= c ?|)) (setq op "|"))
  190. + ((or (= c ?\\) (= c ?-)) (setq op "-"))
  191. + ((= c ?=) (setq op "="))
  192. + ;; todos
  193. + ((setq e (rassoc c todo-table) tg (car e))
  194. + (setq current (org-agenda-query-manip
  195. + current op groups 'todo tg))
  196. + (if exit-after-next (setq exit-after-next 'now)))
  197. + ;; tags
  198. + ((setq e (rassoc c ntable) tg (car e))
  199. + (setq current (org-agenda-query-manip
  200. + current op groups 'tag tg))
  201. + (if exit-after-next (setq exit-after-next 'now))))
  202. + (if (eq exit-after-next 'now) (throw 'exit t))
  203. + (goto-char (point-min))
  204. + (beginning-of-line 1)
  205. + (delete-region (point) (point-at-eol))
  206. + (insert "Query: " current)
  207. + (beginning-of-line 2)
  208. + (delete-region (point) (point-at-eol))
  209. + (org-agenda-query-op-line op)
  210. + (goto-char (point-min)))))
  211. + (if rtn current nil))))
  212. +
  213. +(defun org-agenda-query-op-line (op)
  214. + (insert "Operator: "
  215. + (org-agenda-query-op-entry (equal op "+") "/+" "and")
  216. + (org-agenda-query-op-entry (equal op "|") ";|" "or")
  217. + (org-agenda-query-op-entry (equal op "-") "\\-" "not")
  218. + (org-agenda-query-op-entry (equal op "=") "=" "clear")))
  219. +
  220. +(defun org-agenda-query-op-entry (matchp chars str)
  221. + (if matchp
  222. + (org-add-props (format "[%s %s] " chars (upcase str))
  223. + nil 'face 'org-todo)
  224. + (format "[%s]%s " chars str)))
  225. +
  226. +(defun org-agenda-query-decompose (current)
  227. + (string-match "\\([^/]*\\)/?\\(.*\\)" current))
  228. +
  229. +(defun org-agenda-query-clear (current prefix tag)
  230. + (if (string-match (concat prefix "\\b" (regexp-quote tag) "\\b") current)
  231. + (replace-match "" t t current)
  232. + current))
  233. +
  234. +(defun org-agenda-query-manip (current op groups kind tag)
  235. + "Apply an operator to a query string and a tag.
  236. +CURRENT is the current query string, OP is the operator, GROUPS is a
  237. +list of lists of tags that are mutually exclusive. KIND is 'tag for a
  238. +regular tag, or 'todo for a TODO keyword, and TAG is the tag or
  239. +keyword string."
  240. + ;; If this tag is already in query string, remove it.
  241. + (setq current (org-agenda-query-clear current "[-\\+&|]?" tag))
  242. + (if (equal op "=") current
  243. + ;; When using AND, also remove mutually exclusive tags.
  244. + (if (equal op "+")
  245. + (loop for g in groups do
  246. + (if (member tag g)
  247. + (mapc (lambda (x)
  248. + (setq current
  249. + (org-agenda-query-clear current "\\+" x)))
  250. + g))))
  251. + ;; Decompose current query into q1 (tags) and q2 (TODOs).
  252. + (org-agenda-query-decompose current)
  253. + (let* ((q1 (match-string 1 current))
  254. + (q2 (match-string 2 current)))
  255. + (cond
  256. + ((eq kind 'tag)
  257. + (concat q1 op tag "/" q2))
  258. + ;; It's a TODO; when using AND, drop all other TODOs.
  259. + ((equal op "+")
  260. + (concat q1 "/+" tag))
  261. + (t
  262. + (concat q1 "/" q2 op tag))))))
  263. +
  264. +(defun org-agenda-query-global-todo-keys (&optional files)
  265. + "Return alist of all TODO keywords and their fast keys, in all FILES."
  266. + (let (alist)
  267. + (unless (and files (car files))
  268. + (setq files (org-agenda-files)))
  269. + (save-excursion
  270. + (loop for f in files do
  271. + (set-buffer (find-file-noselect f))
  272. + (loop for k in org-todo-key-alist do
  273. + (setq alist (org-agenda-query-merge-todo-key
  274. + alist k)))))
  275. + alist))
  276. +
  277. +(defun org-agenda-query-merge-todo-key (alist entry)
  278. + (let (e)
  279. + (cond
  280. + ;; if this is not a keyword (:startgroup, etc), ignore it
  281. + ((not (stringp (car entry))))
  282. + ;; if keyword already exists, replace char if it's null
  283. + ((setq e (assoc (car entry) alist))
  284. + (when (null (cdr e)) (setcdr e (cdr entry))))
  285. + ;; if char already exists, prepend keyword but drop char
  286. + ((rassoc (cdr entry) alist)
  287. + (error "TRACE POSITION 2")
  288. + (setq alist (cons (cons (car entry) nil) alist)))
  289. + ;; else, prepend COPY of entry
  290. + (t
  291. + (setq alist (cons (cons (car entry) (cdr entry)) alist)))))
  292. + alist)
  293. +
  294. +(defun org-agenda-query-generic-cmd (op)
  295. + "Activate query manipulation with OP as initial operator."
  296. + (let ((q (org-agenda-query-selection org-agenda-query-string op
  297. + org-tag-alist
  298. + (org-agenda-query-global-todo-keys))))
  299. + (when q
  300. + (setq org-agenda-query-string q)
  301. + (org-agenda-redo))))
  302. +
  303. +(defun org-agenda-query-clear-cmd ()
  304. + "Activate query manipulation, to clear a tag from the string."
  305. + (interactive)
  306. + (org-agenda-query-generic-cmd "="))
  307. +
  308. +(defun org-agenda-query-and-cmd ()
  309. + "Activate query manipulation, initially using the AND (+) operator."
  310. + (interactive)
  311. + (org-agenda-query-generic-cmd "+"))
  312. +
  313. +(defun org-agenda-query-or-cmd ()
  314. + "Activate query manipulation, initially using the OR (|) operator."
  315. + (interactive)
  316. + (org-agenda-query-generic-cmd "|"))
  317. +
  318. +(defun org-agenda-query-not-cmd ()
  319. + "Activate query manipulation, initially using the NOT (-) operator."
  320. + (interactive)
  321. + (org-agenda-query-generic-cmd "-"))
  322. +
  323. ;;; Agenda Finding stuck projects
  324. (defvar org-agenda-skip-regexp nil