* calendar/todos.el: Doubts about todos-ignore-archived-categories.
[bpt/emacs.git] / lisp / calendar / todos.el
CommitLineData
58c7641d 1;;; Todos.el --- facilities for making and maintaining Todo lists
3f031767 2
0e89c3fc 3;; Copyright (C) 1997, 1999, 2001-2012 Free Software Foundation, Inc.
3f031767
SB
4
5;; Author: Oliver Seidel <privat@os10000.net>
58c7641d 6;; Stephen Berman <stephen.berman@gmx.net>
3f031767
SB
7;; Maintainer: Stephen Berman <stephen.berman@gmx.net>
8;; Created: 2 Aug 1997
9;; Keywords: calendar, todo
10
0e89c3fc 11;; This file is [not yet] part of GNU Emacs.
3f031767
SB
12
13;; GNU Emacs is free software: you can redistribute it and/or modify
14;; it under the terms of the GNU General Public License as published by
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
25
3f031767
SB
26;;; Commentary:
27
3f031767
SB
28;;; Code:
29
b28025ed 30(require 'diary-lib)
459c6e93
SB
31;; For remove-if-not and find-if-not in todos-reset-global-current-todos-file
32;; and for remove-duplicates in todos-insertion-commands-args.
0e89c3fc 33(eval-when-compile (require 'cl))
3f031767 34
2c173503 35;; ---------------------------------------------------------------------------
58c7641d 36;;; User options
ee7412e4 37
3f031767 38(defgroup todos nil
58c7641d 39 "Create and maintain categorized lists of todo items."
3f031767 40 :link '(emacs-commentary-link "todos")
0e89c3fc 41 :version "24.2"
3f031767
SB
42 :group 'calendar)
43
0e89c3fc
SB
44(defcustom todos-files-directory (locate-user-emacs-file "todos/")
45 "Directory where user's Todos files are saved."
46 :type 'directory
47 :group 'todos)
48
49(defun todos-files (&optional archives)
50 "Default value of `todos-files-function'.
51This returns the case-insensitive alphabetically sorted list of
52file truenames in `todos-files-directory' with the extension
53\".todo\". With non-nil ARCHIVES return the list of archive file
54truenames (those with the extension \".toda\")."
55 (let ((files (if (file-exists-p todos-files-directory)
56 (mapcar 'file-truename
57 (directory-files todos-files-directory t
58 (if archives "\.toda$" "\.todo$") t)))))
59 (sort files (lambda (s1 s2) (let ((cis1 (upcase s1))
60 (cis2 (upcase s2)))
61 (string< cis1 cis2))))))
62
63(defcustom todos-files-function 'todos-files
64 "Function returning the value of the variable `todos-files'.
65This function should take an optional argument that, if non-nil,
66makes it return the value of the variable `todos-archives'."
67 :type 'function
68 :group 'todos)
69
70(defun todos-short-file-name (file)
71 "Return short form of Todos FILE.
72This lacks the extension and directory components."
73 (file-name-sans-extension (file-name-nondirectory file)))
74
75(defcustom todos-default-todos-file (car (funcall todos-files-function))
76 "Todos file visited by first session invocation of `todos-show'."
77 :type `(radio ,@(mapcar (lambda (f) (list 'const f))
78 (mapcar 'todos-short-file-name
79 (funcall todos-files-function))))
80 :group 'todos)
81
82;; FIXME: is there a better alternative to this?
83(defun todos-reevaluate-default-file-defcustom ()
84 "Reevaluate defcustom of `todos-default-todos-file'.
85Called after adding or deleting a Todos file."
86 (eval (defcustom todos-default-todos-file (car (funcall todos-files-function))
87 "Todos file visited by first session invocation of `todos-show'."
88 :type `(radio ,@(mapcar (lambda (f) (list 'const f))
89 (mapcar 'todos-short-file-name
90 (funcall todos-files-function))))
91 :group 'todos)))
92
93(defcustom todos-show-current-file t
94 "Non-nil to make `todos-show' visit the current Todos file.
95Otherwise, `todos-show' always visits `todos-default-todos-file'."
96 :type 'boolean
97 :initialize 'custom-initialize-default
3af3cd0b 98 :set 'todos-set-show-current-file
0e89c3fc
SB
99 :group 'todos)
100
3af3cd0b 101(defun todos-set-show-current-file (symbol value)
0e89c3fc
SB
102 "The :set function for user option `todos-show-current-file'."
103 (custom-set-default symbol value)
104 (if value
105 (add-hook 'pre-command-hook 'todos-show-current-file nil t)
106 (remove-hook 'pre-command-hook 'todos-show-current-file t)))
107
108(defcustom todos-visit-files-commands (list 'find-file 'dired-find-file)
109 "List of commands to visit files for `todos-after-find-file'.
110Invoking these commands to visit a Todos or Todos Archive file
111calls `todos-show' or `todos-show-archive', so that the file is
112displayed correctly."
113 :type '(repeat function)
114 :group 'todos)
115
116(defcustom todos-initial-file "Todo"
117 "Default file name offered on adding first Todos file."
118 :type 'string
119 :group 'todos)
120
d04d6b95
SB
121(defcustom todos-initial-category "Todo"
122 "Default category name offered on initializing a new Todos file."
123 :type 'string
124 :group 'todos)
125
126(defcustom todos-display-categories-first nil
127 "Non-nil to display category list on first visit to a Todos file."
128 :type 'boolean
129 :group 'todos)
130
131(defcustom todos-prefix ""
b28025ed
SB
132 "String prefixed to todo items for visual distinction."
133 :type 'string
134 :initialize 'custom-initialize-default
135 :set 'todos-reset-prefix
136 :group 'todos)
2c173503 137
3af3cd0b 138(defcustom todos-number-priorities t
58c7641d 139 "Non-nil to prefix items with consecutively increasing integers.
d04d6b95 140These reflect the priorities of the items in each category."
2c173503
SB
141 :type 'boolean
142 :initialize 'custom-initialize-default
143 :set 'todos-reset-prefix
144 :group 'todos)
145
0e89c3fc 146(defun todos-reset-prefix (symbol value)
3af3cd0b 147 "The :set function for `todos-prefix' and `todos-number-priorities'."
0e89c3fc
SB
148 (let ((oldvalue (symbol-value symbol))
149 (files (append todos-files todos-archives)))
150 (custom-set-default symbol value)
151 (when (not (equal value oldvalue))
152 (dolist (f files)
153 (with-current-buffer (find-file-noselect f)
154 (save-window-excursion
155 (todos-show)
156 (save-excursion
157 (widen)
158 (goto-char (point-min))
159 (while (not (eobp))
160 (remove-overlays (point) (point)); 'before-string prefix)
161 (forward-line)))
162 ;; Activate the new setting (save-restriction does not help).
163 (save-excursion (todos-category-select))))))))
164
58c7641d
SB
165;; FIXME: Update when window-width changes. Add todos-reset-separator to
166;; window-configuration-change-hook in todos-mode? But this depends on the
167;; value being window-width instead of a constant length.
0e89c3fc 168(defcustom todos-done-separator (make-string (window-width) ?_)
3af3cd0b
SB
169 "String used to visually separate done from not done items.
170Displayed as an overlay instead of `todos-done-separator' when
171done items are shown."
2c173503
SB
172 :type 'string
173 :initialize 'custom-initialize-default
0e89c3fc 174 :set 'todos-reset-separator
2c173503
SB
175 :group 'todos)
176
3af3cd0b 177;; (defun todos-reset-done-separator (symbol value)
0e89c3fc
SB
178;; "The :set function for `todos-done-separator'
179;; Also added to `window-configuration-change-hook' in Todos mode."
180;; (let ((oldvalue (symbol-value symbol)))
181;; (custom-set-default symbol value)
182;; (when (not (equal value oldvalue))
183;; (make-string (window-width) ?_)
184;; ;; (save-excursion (todos-category-select))
185;; )))
186
2c173503
SB
187(defcustom todos-done-string "DONE "
188 "Identifying string appended to the front of done todos items."
189 :type 'string
58c7641d
SB
190 :initialize 'custom-initialize-default
191 :set 'todos-reset-done-string
192 :group 'todos)
193
0e89c3fc
SB
194(defun todos-reset-done-string (symbol value)
195 "The :set function for user option `todos-done-string'."
196 (let ((oldvalue (symbol-value symbol))
197 (files (append todos-files todos-archives)))
198 (custom-set-default symbol value)
199 ;; Need to reset this to get font-locking right.
200 (setq todos-done-string-start
201 (concat "^\\[" (regexp-quote todos-done-string)))
202 (when (not (equal value oldvalue))
203 (dolist (f files)
204 (with-current-buffer (find-file-noselect f)
205 (let (buffer-read-only)
206 (widen)
207 (goto-char (point-min))
208 (while (not (eobp))
209 (if (re-search-forward
210 (concat "^" (regexp-quote todos-nondiary-start)
211 "\\(" (regexp-quote oldvalue) "\\)")
212 nil t)
213 (replace-match value t t nil 1)
214 (forward-line)))
215 (todos-category-select)))))))
216
58c7641d
SB
217(defcustom todos-comment-string "COMMENT"
218 "String inserted before optional comment appended to done item."
219 :type 'string
220 :initialize 'custom-initialize-default
221 :set 'todos-reset-comment-string
2c173503
SB
222 :group 'todos)
223
0e89c3fc
SB
224(defun todos-reset-comment-string (symbol value)
225 "The :set function for user option `todos-comment-string'."
226 (let ((oldvalue (symbol-value symbol))
227 (files (append todos-files todos-archives)))
228 (custom-set-default symbol value)
229 (when (not (equal value oldvalue))
230 (dolist (f files)
231 (with-current-buffer (find-file-noselect f)
232 (let (buffer-read-only)
233 (save-excursion
234 (widen)
235 (goto-char (point-min))
236 (while (not (eobp))
237 (if (re-search-forward
238 (concat
239 "\\[\\(" (regexp-quote oldvalue) "\\): [^]]*\\]")
240 nil t)
241 (replace-match value t t nil 1)
242 (forward-line)))
243 (todos-category-select))))))))
244
2c173503
SB
245(defcustom todos-show-with-done nil
246 "Non-nil to display done items in all categories."
247 :type 'boolean
248 :group 'todos)
249
58c7641d
SB
250(defun todos-mode-line-control (cat)
251 "Return a mode line control for Todos buffers.
252Argument CAT is the name of the current Todos category.
253This function is the value of the user variable
254`todos-mode-line-function'."
0e89c3fc
SB
255 (let ((file (todos-short-file-name todos-current-todos-file)))
256 (format "%s category %d: %s" file todos-category-number cat)))
58c7641d
SB
257
258(defcustom todos-mode-line-function 'todos-mode-line-control
259 "Function that returns a mode line control for Todos buffers.
0e89c3fc
SB
260The function expects one argument holding the name of the current
261Todos category. The resulting control becomes the local value of
262`mode-line-buffer-identification' in each Todos buffer."
d04d6b95 263 :type 'function
2c173503
SB
264 :group 'todos)
265
0e89c3fc 266(defun todos-special-buffer-name (buffer-type file-list)
520d912e
SB
267 "Rename Todos special buffer using BUFFER-TYPE and FILE-LIST.
268
269The new name is constructed from the string BUFFER-TYPE, which
270refers to one of the top priorities, diary or regexp item
271filters, and the names of the filtered files in FILE-LIST. Used
272in Todos Filter Items mode."
0e89c3fc
SB
273 (let* ((flist (if (listp file-list) file-list (list file-list)))
274 (multi (> (length flist) 1))
275 (fnames (mapconcat (lambda (f) (todos-short-file-name f))
276 flist ", ")))
277 (rename-buffer (format (concat "%s for file" (if multi "s" "")
278 " \"%s\"") buffer-type fnames))))
279
280(defcustom todos-filter-buffer "Todos filtered items"
520d912e 281 "Initial name of buffer in Todos Filter Items mode."
2c173503
SB
282 :type 'string
283 :group 'todos)
284
0e89c3fc 285(defcustom todos-top-priorities-buffer "Todos top priorities"
520d912e 286 "Buffer type string for `todos-special-buffer-name'."
2c173503
SB
287 :type 'string
288 :group 'todos)
289
0e89c3fc 290(defcustom todos-diary-items-buffer "Todos diary items"
520d912e 291 "Buffer type string for `todos-special-buffer-name'."
2c173503 292 :type 'string
3f031767 293 :group 'todos)
2c173503 294
0e89c3fc 295(defcustom todos-regexp-items-buffer "Todos regexp items"
520d912e 296 "Buffer type string for `todos-special-buffer-name'."
3f031767
SB
297 :type 'string
298 :group 'todos)
299
0e89c3fc
SB
300(defcustom todos-priorities-rules nil
301 "List of rules giving how many items `todos-top-priorities' shows.
302This variable should be set interactively by
303`\\[todos-set-top-priorities-in-file]' or
304`\\[todos-set-top-priorities-in-category]'.
305
306Each rule is a list of the form (FILE NUM ALIST), where FILE is a
307member of `todos-files', NUM is a number specifying the default
308number of top priority items for each category in that file, and
309ALIST, when non-nil, consists of conses of a category name in
310FILE and a number specifying the default number of top priority
311items in that category, which overrides NUM."
312 :type 'list
d04d6b95
SB
313 :group 'todos)
314
0e89c3fc
SB
315(defcustom todos-show-priorities 1
316 "Default number of top priorities shown by `todos-top-priorities'."
317 :type 'integer
58c7641d
SB
318 :group 'todos)
319
0e89c3fc
SB
320(defcustom todos-filter-files nil
321 "List of default files for multifile item filtering."
322 :type `(set ,@(mapcar (lambda (f) (list 'const f))
323 (mapcar 'todos-short-file-name
324 (funcall todos-files-function))))
d04d6b95
SB
325 :group 'todos)
326
0e89c3fc
SB
327;; FIXME: is there a better alternative to this?
328(defun todos-reevaluate-filter-files-defcustom ()
329 "Reevaluate defcustom of `todos-filter-files'.
330Called after adding or deleting a Todos file."
331 (eval (defcustom todos-filter-files nil
332 "List of files for multifile item filtering."
333 :type `(set ,@(mapcar (lambda (f) (list 'const f))
334 (mapcar 'todos-short-file-name
335 (funcall todos-files-function))))
336 :group 'todos)))
337
520d912e
SB
338(defcustom todos-filter-done-items nil
339 "Non-nil to include done items when processing regexp filters.
340Done items from corresponding archive files are also included."
341 :type 'boolean
342 :group 'todos)
343
4de20201
SB
344;; FIXME: make this effect only navigation?
345(defcustom todos-ignore-archived-categories t
d04d6b95
SB
346 "Non-nil to ignore categories with only archived items.
347When non-nil such categories are omitted from `todos-categories'
348and hence from commands that use this variable. An exception is
349\\[todos-display-categories], which displays all categories; but
350those with only archived items are shown in `todos-archived-only'
351face and clicking them in Todos Categories mode visits the
352archived categories."
2c173503 353 :type 'boolean
d04d6b95
SB
354 :initialize 'custom-initialize-default
355 :set 'todos-reset-categories
2c173503
SB
356 :group 'todos)
357
4de20201
SB
358;; FIXME: if this is saved and todos.el is loaded before custom-file,
359;; categories mode does not show archived categories
0e89c3fc
SB
360(defun todos-reset-categories (symbol value)
361 "The :set function for `todos-ignore-archived-categories'."
362 (custom-set-default symbol value)
363 (dolist (f (funcall todos-files-function))
364 (with-current-buffer (find-file-noselect f)
365 (if value
366 (setq todos-categories-full todos-categories
367 todos-categories (todos-truncate-categories-list))
368 (setq todos-categories todos-categories-full
369 todos-categories-full nil))
370 (todos-category-select))))
58c7641d
SB
371
372(defcustom todos-use-only-highlighted-region t
373 "Non-nil to enable inserting only highlighted region as new item."
374 :type 'boolean
d04d6b95
SB
375 :group 'todos)
376
377(defcustom todos-include-in-diary nil
378 "Non-nil to allow new Todo items to be included in the diary."
379 :type 'boolean
380 :group 'todos)
381
58c7641d
SB
382(defcustom todos-diary-nonmarking nil
383 "Non-nil to insert new Todo diary items as nonmarking by default.
384This appends `diary-nonmarking-symbol' to the front of an item on
385insertion provided it doesn't begin with `todos-nondiary-marker'."
386 :type 'boolean
387 :group 'todos)
388
d04d6b95
SB
389(defcustom todos-nondiary-marker '("[" "]")
390 "List of strings surrounding item date to block diary inclusion.
391The first string is inserted before the item date and must be a
392non-empty string that does not match a diary date in order to
393have its intended effect. The second string is inserted after
394the diary date."
395 :type '(list string string)
2c173503 396 :group 'todos
d04d6b95
SB
397 :initialize 'custom-initialize-default
398 :set 'todos-reset-nondiary-marker)
2c173503 399
0e89c3fc
SB
400(defun todos-reset-nondiary-marker (symbol value)
401 "The :set function for user option `todos-nondiary-marker'."
402 (let ((oldvalue (symbol-value symbol))
403 (files (append todos-files todos-archives)))
404 (custom-set-default symbol value)
405 ;; Need to reset these to get font-locking right.
406 (setq todos-nondiary-start (nth 0 todos-nondiary-marker)
407 todos-nondiary-end (nth 1 todos-nondiary-marker)
408 todos-date-string-start
409 ;; See comment in defvar of `todos-date-string-start'.
410 (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|"
411 (regexp-quote diary-nonmarking-symbol) "\\)?"))
412 (when (not (equal value oldvalue))
413 (dolist (f files)
414 (with-current-buffer (find-file-noselect f)
415 (let (buffer-read-only)
416 (widen)
417 (goto-char (point-min))
418 (while (not (eobp))
419 (if (re-search-forward
420 (concat "^\\(" todos-done-string-start "[^][]+] \\)?"
421 "\\(?1:" (regexp-quote (car oldvalue))
422 "\\)" todos-date-pattern "\\( "
423 diary-time-regexp "\\)?\\(?2:"
424 (regexp-quote (cadr oldvalue)) "\\)")
425 nil t)
426 (progn
427 (replace-match (nth 0 value) t t nil 1)
428 (replace-match (nth 1 value) t t nil 2))
429 (forward-line)))
430 (todos-category-select)))))))
431
3f031767 432(defcustom todos-print-function 'ps-print-buffer-with-faces
58c7641d 433 "Function called to print buffer content; see `todos-print'."
3f031767
SB
434 :type 'symbol
435 :group 'todos)
2c173503 436
0e89c3fc
SB
437(defcustom todos-completion-ignore-case nil
438 "Non-nil means case of user input in `todos-read-*' is ignored."
439 :type 'boolean
3f031767 440 :group 'todos)
2c173503 441
0e89c3fc
SB
442(defcustom todos-highlight-item nil
443 "Non-nil means highlight items at point."
db2c5d34 444 :type 'boolean
0e89c3fc
SB
445 :initialize 'custom-initialize-default
446 :set 'todos-reset-highlight-item
db2c5d34 447 :group 'todos)
3f031767 448
0e89c3fc
SB
449(defun todos-reset-highlight-item (symbol value)
450 "The :set function for `todos-highlight-item'."
451 (let ((oldvalue (symbol-value symbol))
452 (files (append todos-files todos-archives)))
453 (custom-set-default symbol value)
454 (when (not (equal value oldvalue))
455 (dolist (f files)
456 (let ((buf (get-file-buffer f)))
457 (when buf
458 (with-current-buffer buf
459 (require 'hl-line)
460 (if value
461 (hl-line-mode 1)
462 (hl-line-mode -1)))))))))
463
d04d6b95
SB
464(defcustom todos-always-add-time-string nil
465 "Non-nil adds current time to a new item's date header by default.
466When the Todos insertion commands have a non-nil \"maybe-notime\"
467argument, this reverses the effect of
468`todos-always-add-time-string': if t, these commands omit the
469current time, if nil, they include it."
b28025ed 470 :type 'boolean
3f031767
SB
471 :group 'todos)
472
2c173503 473(defcustom todos-wrap-lines t
0e89c3fc 474 "Non-nil to wrap long lines via `todos-line-wrapping-function'."
2c173503
SB
475 :group 'todos
476 :type 'boolean)
477
478(defcustom todos-line-wrapping-function 'todos-wrap-and-indent
0e89c3fc 479 "Line wrapping function used with non-nil `todos-wrap-lines'."
2c173503
SB
480 :group 'todos
481 :type 'function)
482
0e89c3fc
SB
483(defun todos-wrap-and-indent ()
484 "Use word wrapping on long lines and indent with a wrap prefix.
485The amount of indentation is given by user option
486`todos-indent-to-here'."
487 (set (make-local-variable 'word-wrap) t)
488 (set (make-local-variable 'wrap-prefix) (make-string todos-indent-to-here 32))
489 (unless (member '(continuation) fringe-indicator-alist)
490 (push '(continuation) fringe-indicator-alist)))
491
3af3cd0b
SB
492;; FIXME: :set function (otherwise change takes effect only after killing and
493;; revisiting file)
0e89c3fc
SB
494(defcustom todos-indent-to-here 6
495 "Number of spaces `todos-line-wrapping-function' indents to."
496 :type '(integer :validate
497 (lambda (widget)
498 (unless (> (widget-value widget) 0)
499 (widget-put widget :error
500 "Invalid value: must be a positive integer")
501 widget)))
502 :group 'todos)
503
504(defun todos-indent ()
505 "Indent from point to `todos-indent-to-here'."
506 (indent-to todos-indent-to-here todos-indent-to-here))
507
508(defcustom todos-todo-mode-date-time-regexp
509 (concat "\\(?1:[0-9]\\{4\\}\\)-\\(?2:[0-9]\\{2\\}\\)-"
510 "\\(?3:[0-9]\\{2\\}\\) \\(?4:[0-9]\\{2\\}:[0-9]\\{2\\}\\)")
511 "Regexp matching legacy todo-mode.el item date-time strings.
512In order for `todos-convert-legacy-files' to correctly convert this
513string to the current Todos format, the regexp must contain four
514explicitly numbered groups (see `(elisp) Regexp Backslash'),
515where group 1 matches a string for the year, group 2 a string for
516the month, group 3 a string for the day and group 4 a string for
517the time. The default value converts date-time strings built
518using the default value of `todo-time-string-format' from
519todo-mode.el."
520 :type 'regexp
ee7412e4 521 :group 'todos)
3f031767 522
0e89c3fc
SB
523(defgroup todos-categories nil
524 "Faces for Todos Categories mode."
525 :version "24.2"
526 :group 'todos)
527
528(defcustom todos-categories-category-label "Category"
529 "Category button label in Todos Categories mode."
530 :type 'string
531 :group 'todos-categories)
532
533(defcustom todos-categories-todo-label "Todo"
534 "Todo button label in Todos Categories mode."
535 :type 'string
536 :group 'todos-categories)
537
538(defcustom todos-categories-diary-label "Diary"
539 "Diary button label in Todos Categories mode."
540 :type 'string
541 :group 'todos-categories)
542
543(defcustom todos-categories-done-label "Done"
544 "Done button label in Todos Categories mode."
545 :type 'string
546 :group 'todos-categories)
547
548(defcustom todos-categories-archived-label "Archived"
549 "Archived button label in Todos Categories mode."
550 :type 'string
551 :group 'todos-categories)
552
553(defcustom todos-categories-totals-label "Totals"
554 "String to label total item counts in Todos Categories mode."
555 :type 'string
556 :group 'todos-categories)
557
558(defcustom todos-categories-number-separator " | "
559 "String between number and category in Todos Categories mode.
560This separates the number from the category name in the default
561categories display according to priority."
562 :type 'string
563 :group 'todos-categories)
564
565(defcustom todos-categories-align 'center
566 "Alignment of category names in Todos Categories mode."
567 :type '(radio (const left) (const center) (const right))
568 :group 'todos-categories)
569
ee7412e4 570;; ---------------------------------------------------------------------------
2c173503 571;;; Faces
ee7412e4 572
d04d6b95
SB
573(defgroup todos-faces nil
574 "Faces for the Todos modes."
0e89c3fc 575 :version "24.2"
d04d6b95
SB
576 :group 'todos)
577
db2c5d34 578(defface todos-prefix-string
0e89c3fc
SB
579 ;; '((t :inherit font-lock-constant-face))
580 '((((class grayscale) (background light))
581 (:foreground "LightGray" :weight bold :underline t))
582 (((class grayscale) (background dark))
583 (:foreground "Gray50" :weight bold :underline t))
584 (((class color) (min-colors 88) (background light)) (:foreground "dark cyan"))
585 (((class color) (min-colors 88) (background dark)) (:foreground "Aquamarine"))
586 (((class color) (min-colors 16) (background light)) (:foreground "CadetBlue"))
587 (((class color) (min-colors 16) (background dark)) (:foreground "Aquamarine"))
588 (((class color) (min-colors 8)) (:foreground "magenta"))
589 (t (:weight bold :underline t)))
db2c5d34 590 "Face for Todos prefix string."
d04d6b95 591 :group 'todos-faces)
db2c5d34 592
58c7641d 593(defface todos-mark
0e89c3fc
SB
594 ;; '((t :inherit font-lock-warning-face))
595 '((((class color)
596 (min-colors 88)
597 (background light))
598 (:weight bold :foreground "Red1"))
599 (((class color)
600 (min-colors 88)
601 (background dark))
602 (:weight bold :foreground "Pink"))
603 (((class color)
604 (min-colors 16)
605 (background light))
606 (:weight bold :foreground "Red1"))
607 (((class color)
608 (min-colors 16)
609 (background dark))
610 (:weight bold :foreground "Pink"))
611 (((class color)
612 (min-colors 8))
613 (:foreground "red"))
614 (t
615 (:weight bold :inverse-video t)))
58c7641d
SB
616 "Face for marks on Todos items."
617 :group 'todos-faces)
618
ee7412e4 619(defface todos-button
0e89c3fc
SB
620 ;; '((t :inherit widget-field))
621 '((((type tty))
622 (:foreground "black" :background "yellow3"))
623 (((class grayscale color)
624 (background light))
625 (:background "gray85"))
626 (((class grayscale color)
627 (background dark))
628 (:background "dim gray"))
629 (t
630 (:slant italic)))
ee7412e4 631 "Face for buttons in todos-display-categories."
d04d6b95
SB
632 :group 'todos-faces)
633
634(defface todos-sorted-column
0e89c3fc
SB
635 ;; '((t :inherit fringe))
636 '((((class color)
637 (background light))
638 (:foreground "grey95"))
639 (((class color)
640 (background dark))
641 (:foreground "grey10"))
642 (t
643 (:foreground "gray")))
d04d6b95
SB
644 "Face for buttons in todos-display-categories."
645 :group 'todos-faces)
646
647(defface todos-archived-only
0e89c3fc
SB
648 ;; '((t (:inherit (shadow))))
649 '((((class color)
650 (background light))
651 (:foreground "grey50"))
652 (((class color)
653 (background dark))
654 (:foreground "grey70"))
655 (t
656 (:foreground "gray")))
d04d6b95
SB
657 "Face for archived-only categories in todos-display-categories."
658 :group 'todos-faces)
659
660(defface todos-search
0e89c3fc
SB
661 ;; '((t :inherit match))
662 '((((class color)
663 (min-colors 88)
664 (background light))
665 (:background "yellow1"))
666 (((class color)
667 (min-colors 88)
668 (background dark))
669 (:background "RoyalBlue3"))
670 (((class color)
671 (min-colors 8)
672 (background light))
673 (:foreground "black" :background "yellow"))
674 (((class color)
675 (min-colors 8)
676 (background dark))
677 (:foreground "white" :background "blue"))
678 (((type tty)
679 (class mono))
680 (:inverse-video t))
681 (t
682 (:background "gray")))
d04d6b95
SB
683 "Face for matches found by todos-search."
684 :group 'todos-faces)
ee7412e4 685
0e89c3fc
SB
686(defface todos-diary-expired
687 ;; '((t :inherit font-lock-warning-face))
688 '((((class color)
689 (min-colors 16))
690 (:weight bold :foreground "DarkOrange"))
691 (((class color))
692 (:weight bold :foreground "yellow"))
693 (t
694 (:weight bold)))
695 "Face for expired dates of diary items."
696 :group 'todos-faces)
697(defvar todos-diary-expired-face 'todos-diary-expired)
698
b28025ed 699(defface todos-date
58c7641d 700 '((t :inherit diary))
0e89c3fc 701 "Face for the date string of a Todos item."
d04d6b95 702 :group 'todos-faces)
b28025ed
SB
703(defvar todos-date-face 'todos-date)
704
705(defface todos-time
58c7641d 706 '((t :inherit diary-time))
0e89c3fc 707 "Face for the time string of a Todos item."
d04d6b95 708 :group 'todos-faces)
b28025ed
SB
709(defvar todos-time-face 'todos-time)
710
2c173503 711(defface todos-done
0e89c3fc
SB
712 ;; '((t :inherit font-lock-comment-face))
713 '((((class grayscale)
714 (background light))
715 (:slant italic :weight bold :foreground "DimGray"))
716 (((class grayscale)
717 (background dark))
718 (:slant italic :weight bold :foreground "LightGray"))
719 (((class color)
720 (min-colors 88)
721 (background light))
722 (:foreground "Firebrick"))
723 (((class color)
724 (min-colors 88)
725 (background dark))
726 (:foreground "chocolate1"))
727 (((class color)
728 (min-colors 16)
729 (background light))
730 (:foreground "red"))
731 (((class color)
732 (min-colors 16)
733 (background dark))
734 (:foreground "red1"))
735 (((class color)
736 (min-colors 8)
737 (background light))
738 (:foreground "red"))
739 (((class color)
740 (min-colors 8)
741 (background dark))
742 (:foreground "yellow"))
743 (t
744 (:slant italic :weight bold)))
2c173503 745 "Face for done Todos item header string."
d04d6b95 746 :group 'todos-faces)
2c173503 747(defvar todos-done-face 'todos-done)
b28025ed 748
58c7641d 749(defface todos-comment
0e89c3fc 750 '((t :inherit todos-done))
58c7641d
SB
751 "Face for comments appended to done Todos items."
752 :group 'todos-faces)
753(defvar todos-comment-face 'todos-comment)
754
2c173503 755(defface todos-done-sep
0e89c3fc
SB
756 ;; '((t :inherit font-lock-type-face))
757 '((((class grayscale)
758 (background light))
759 (:weight bold :foreground "Gray90"))
760 (((class grayscale)
761 (background dark))
762 (:weight bold :foreground "DimGray"))
763 (((class color)
764 (min-colors 88)
765 (background light))
766 (:foreground "ForestGreen"))
767 (((class color)
768 (min-colors 88)
769 (background dark))
770 (:foreground "PaleGreen"))
771 (((class color)
772 (min-colors 16)
773 (background light))
774 (:foreground "ForestGreen"))
775 (((class color)
776 (min-colors 16)
777 (background dark))
778 (:foreground "PaleGreen"))
779 (((class color)
780 (min-colors 8))
781 (:foreground "green"))
782 (t
783 (:underline t :weight bold)))
2c173503 784 "Face for separator string bewteen done and not done Todos items."
d04d6b95 785 :group 'todos-faces)
2c173503 786(defvar todos-done-sep-face 'todos-done-sep)
db2c5d34 787
78fe7289
SB
788(defun todos-date-string-matcher (lim)
789 "Search for Todos date string within LIM for font-locking."
790 (re-search-forward
791 (concat todos-date-string-start "\\(?1:" todos-date-pattern "\\)") lim t))
792
793(defun todos-time-string-matcher (lim)
794 "Search for Todos time string within LIM for font-locking."
795 (re-search-forward (concat todos-date-string-start todos-date-pattern
796 " \\(?1:" diary-time-regexp "\\)") lim t))
797
798(defun todos-nondiary-marker-matcher (lim)
799 "Search for Todos nondiary markers within LIM for font-locking."
800 (re-search-forward (concat "^\\(?1:" (regexp-quote todos-nondiary-start) "\\)"
801 todos-date-pattern "\\(?: " diary-time-regexp
802 "\\)?\\(?2:" (regexp-quote todos-nondiary-end) "\\)")
803 lim t))
804
805(defun todos-diary-nonmarking-matcher (lim)
806 "Search for diary nonmarking symbol within LIM for font-locking."
807 (re-search-forward (concat "^\\(?1:" (regexp-quote diary-nonmarking-symbol)
808 "\\)" todos-date-pattern) lim t))
809
810(defun todos-diary-expired-matcher (lim)
811 "Search for expired diary item date within LIM for font-locking."
812 (when (re-search-forward (concat "^\\(?:"
813 (regexp-quote diary-nonmarking-symbol)
814 "\\)?\\(?1:" todos-date-pattern "\\) \\(?2:"
815 diary-time-regexp "\\)?") lim t)
816 (let* ((date (match-string-no-properties 1))
817 (time (match-string-no-properties 2))
818 ;; days-between needs a non-empty time string.
819 (date-time (concat date " " (or time "00:00"))))
820 (or (and (not (string-match ".+day\\|\\*" date))
821 (< (days-between date-time (current-time-string)) 0))
822 (todos-diary-expired-matcher lim)))))
823
824(defun todos-done-string-matcher (lim)
825 "Search for Todos done header within LIM for font-locking."
826 (re-search-forward (concat todos-done-string-start
827 "[^][]+]")
828 lim t))
829
830(defun todos-comment-string-matcher (lim)
831 "Search for Todos done comment within LIM for font-locking."
832 (re-search-forward (concat "\\[\\(?1:" todos-comment-string "\\):")
833 lim t))
834
835;; (defun todos-category-string-matcher (lim)
836;; "Search for Todos category name within LIM for font-locking.
837;; This is for fontifying category names appearing in Todos filter
838;; mode."
839;; (if (eq major-mode 'todos-filter-items-mode)
840;; (re-search-forward
841;; (concat "^\\(?:" todos-date-string-start "\\)?" todos-date-pattern
842;; "\\(?: " diary-time-regexp "\\)?\\(?:"
843;; (regexp-quote todos-nondiary-end) "\\)? \\(?1:\\[.+\\]\\)")
844;; lim t)))
845
846(defun todos-category-string-matcher-1 (lim)
847 "Search for Todos category name within LIM for font-locking.
848This is for fontifying category names appearing in Todos filter
849mode following done items."
850 (if (eq major-mode 'todos-filter-items-mode)
851 (re-search-forward (concat todos-done-string-start todos-date-pattern
852 "\\(?: " diary-time-regexp
853 ;; Use non-greedy operator to prevent
854 ;; capturing possible following non-diary
855 ;; date string.
856 "\\)?] \\(?1:\\[.+?\\]\\)")
857 lim t)))
858
859(defun todos-category-string-matcher-2 (lim)
860 "Search for Todos category name within LIM for font-locking.
861This is for fontifying category names appearing in Todos filter
862mode following todo (not done) items."
863 (if (eq major-mode 'todos-filter-items-mode)
864 (re-search-forward (concat todos-date-string-start todos-date-pattern
865 "\\(?: " diary-time-regexp "\\)?\\(?:"
866 (regexp-quote todos-nondiary-end)
867 "\\)? \\(?1:\\[.+\\]\\)")
868 lim t)))
869
db2c5d34
SB
870(defvar todos-font-lock-keywords
871 (list
0e89c3fc
SB
872 '(todos-nondiary-marker-matcher 1 todos-done-sep-face t)
873 '(todos-nondiary-marker-matcher 2 todos-done-sep-face t)
874 ;; This is the face used by diary-lib.el.
875 '(todos-diary-nonmarking-matcher 1 font-lock-constant-face t)
58c7641d
SB
876 '(todos-date-string-matcher 1 todos-date-face t)
877 '(todos-time-string-matcher 1 todos-time-face t)
878 '(todos-done-string-matcher 0 todos-done-face t)
879 '(todos-comment-string-matcher 1 todos-done-face t)
0e89c3fc
SB
880 ;; '(todos-category-string-matcher 1 todos-done-sep-face t)
881 '(todos-category-string-matcher-1 1 todos-done-sep-face t t)
882 '(todos-category-string-matcher-2 1 todos-done-sep-face t t)
883 '(todos-diary-expired-matcher 1 todos-diary-expired-face t)
884 '(todos-diary-expired-matcher 2 todos-diary-expired-face t t)
885 )
886 "Font-locking for Todos modes.")
db2c5d34 887
3f031767 888;; ---------------------------------------------------------------------------
0e89c3fc 889;;; Todos mode local variables and hook functions
3f031767 890
d04d6b95 891(defvar todos-files (funcall todos-files-function)
58c7641d 892 "List of truenames of user's Todos files.")
d04d6b95
SB
893
894(defvar todos-archives (funcall todos-files-function t)
58c7641d 895 "List of truenames of user's Todos archives.")
f730d273 896
58c7641d
SB
897(defvar todos-current-todos-file nil
898 "Variable holding the name of the currently active Todos file.")
58c7641d 899
0e89c3fc
SB
900(defun todos-show-current-file ()
901 "Visit current instead of default Todos file with `todos-show'.
902This function is added to `pre-command-hook' when user option
903`todos-show-current-file' is set to non-nil."
904 (setq todos-global-current-todos-file todos-current-todos-file))
0e89c3fc
SB
905
906(defun todos-after-find-file ()
907 "Show Todos files correctly when visited from outside of Todos mode."
908 (and (member this-command todos-visit-files-commands)
909 (= (- (point-max) (point-min)) (buffer-size))
910 (member major-mode '(todos-mode todos-archive-mode))
911 (todos-category-select)))
58c7641d 912
2a9e69d6
SB
913;; FIXME: This slows down C-x C-k, can it be optimized? E.g. make
914;; todos-buffer-list as cache
58c7641d 915(defun todos-reset-global-current-todos-file ()
0e89c3fc
SB
916 "Update the value of `todos-global-current-todos-file'.
917This becomes the latest existing Todos file or, if there is none,
918the value of `todos-default-todos-file'.
919This function is added to `kill-buffer-hook' in Todos mode."
459c6e93
SB
920 ;; (let ((buflist (copy-sequence (buffer-list)))
921 ;; (cur todos-global-current-todos-file))
922 ;; (catch 'done
923 ;; (while buflist
924 ;; (let* ((buf (pop buflist))
925 ;; (bufname (buffer-file-name buf)))
926 ;; (when bufname (setq bufname (file-truename bufname)))
927 ;; (when (and (member bufname (funcall todos-files-function))
928 ;; (not (eq buf (current-buffer))))
929 ;; (setq todos-global-current-todos-file bufname)
930 ;; (throw 'done nil)))))
931 ;; (if (equal cur todos-global-current-todos-file)
932 ;; (setq todos-global-current-todos-file todos-default-todos-file))))
933 (let ((todos-buffer-list (nreverse
934 (remove-if-not
935 (lambda (f)
936 (member f (mapcar
937 'file-name-nondirectory
938 (funcall todos-files-function))))
939 (mapcar 'buffer-name (buffer-list)))))
940 latest)
941 ;; (while todos-buffer-list
942 ;; (let ((todos-bufname (pop todos-buffer-list)))
943 ;; (unless (string= todos-bufname (buffer-name))
944 ;; (setq latest todos-bufname
945 ;; todos-buffer-list nil))))
946 (setq latest (find-if-not (lambda (f) (string= f (buffer-name)))
947 todos-buffer-list))
948 (setq todos-global-current-todos-file (or latest todos-default-todos-file))))
58c7641d 949
0e89c3fc
SB
950(defvar todos-categories nil
951 "Alist of categories in the current Todos file.
952The elements are cons cells whose car is a category name and
953whose cdr is a vector of the category's item counts. These are,
3af3cd0b
SB
954in order, the numbers of todo items, of todo items included in
955the Diary, of done items and of archived items.")
0e89c3fc
SB
956
957(defvar todos-categories-full nil
958 "Variable holding non-truncated copy of `todos-categories'.
959Set when `todos-ignore-archived-categories' is set to non-nil, to
960restore full `todos-categories' list when
961`todos-ignore-archived-categories' is reset to nil.")
962
963(defvar todos-categories-with-marks nil
964 "Alist of categories and number of marked items they contain.")
965
58c7641d
SB
966(defvar todos-category-number 1
967 "Variable holding the number of the current Todos category.
0e89c3fc 968Todos categories are numbered starting from 1.")
58c7641d
SB
969
970(defvar todos-first-visit t
971 "Non-nil if first display of this file in the current session.
972See `todos-display-categories-first'.")
973
0e89c3fc
SB
974(defvar todos-show-done-only nil
975 "If non-nil display only done items in current category.
3af3cd0b 976Set by the command `todos-show-done-only' and used by
0e89c3fc 977`todos-category-select'.")
58c7641d 978
0e89c3fc
SB
979;; ---------------------------------------------------------------------------
980;;; Global variables and helper functions
58c7641d 981
0e89c3fc
SB
982(defvar todos-global-current-todos-file nil
983 "Variable holding name of current Todos file.
984Used by functions called from outside of Todos mode to visit the
985current Todos file rather than the default Todos file (i.e. when
986users option `todos-show-current-file' is non-nil).")
987
988(defun todos-reevaluate-defcustoms ()
3af3cd0b 989 "Reevaluate defcustoms that provide choice list of Todos files."
0e89c3fc
SB
990 (custom-set-default 'todos-default-todos-file
991 (symbol-value 'todos-default-todos-file))
992 (todos-reevaluate-default-file-defcustom)
993 (custom-set-default 'todos-filter-files (symbol-value 'todos-filter-files))
994 (todos-reevaluate-filter-files-defcustom))
995
996(defvar todos-edit-buffer "*Todos Edit*"
997 "Name of current buffer in Todos Edit mode.")
998
999(defvar todos-categories-buffer "*Todos Categories*"
1000 "Name of buffer in Todos Categories mode.")
1001
1002(defvar todos-print-buffer "*Todos Print*"
1003 "Name of buffer containing printable Todos text.")
1004
1005(defvar todos-date-pattern
1006 (let ((dayname (diary-name-pattern calendar-day-name-array nil t)))
1007 (concat "\\(?:" dayname "\\|"
1008 (let ((dayname)
1009 ;; FIXME: how to choose between abbreviated and unabbreviated
1010 ;; month name?
1011 (monthname (format "\\(?:%s\\|\\*\\)"
1012 (diary-name-pattern
1013 calendar-month-name-array
1014 calendar-month-abbrev-array t)))
1015 (month "\\(?:[0-9]+\\|\\*\\)")
1016 (day "\\(?:[0-9]+\\|\\*\\)")
1017 (year "-?\\(?:[0-9]+\\|\\*\\)"))
1018 (mapconcat 'eval calendar-date-display-form ""))
1019 "\\)"))
1020 "Regular expression matching a Todos date header.")
58c7641d
SB
1021
1022(defvar todos-nondiary-start (nth 0 todos-nondiary-marker)
1023 "String inserted before item date to block diary inclusion.")
1024
1025(defvar todos-nondiary-end (nth 1 todos-nondiary-marker)
1026 "String inserted after item date matching `todos-nondiary-start'.")
1027
0e89c3fc
SB
1028;; By itself this matches anything, because of the `?'; however, it's only
1029;; used in the context of `todos-date-pattern' (but Emacs Lisp lacks
1030;; lookahead).
1031(defvar todos-date-string-start
1032 (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|"
1033 (regexp-quote diary-nonmarking-symbol) "\\)?")
1034 "Regular expression matching part of item header before the date.")
58c7641d 1035
0e89c3fc
SB
1036(defvar todos-done-string-start
1037 (concat "^\\[" (regexp-quote todos-done-string))
1038 "Regular expression matching start of done item.")
58c7641d 1039
0e89c3fc
SB
1040(defun todos-category-number (cat)
1041 "Return the number of category CAT in this Todos file.
1042The buffer-local variable `todos-category-number' holds this
1043number as its value."
1044 (let ((categories (mapcar 'car todos-categories)))
1045 (setq todos-category-number
1046 ;; Increment by one, so that the highest priority category in Todos
1047 ;; Categories mode is numbered one rather than zero.
1048 (1+ (- (length categories)
1049 (length (member cat categories)))))))
58c7641d 1050
0e89c3fc
SB
1051(defun todos-current-category ()
1052 "Return the name of the current category."
1053 (car (nth (1- todos-category-number) todos-categories)))
58c7641d 1054
0e89c3fc
SB
1055(defconst todos-category-beg "--==-- "
1056 "String marking beginning of category (inserted with its name).")
58c7641d 1057
0e89c3fc
SB
1058(defconst todos-category-done "==--== DONE "
1059 "String marking beginning of category's done items.")
2c173503 1060
0e89c3fc
SB
1061(defun todos-category-select ()
1062 "Display the current category correctly."
1063 (let ((name (todos-current-category))
1064 cat-begin cat-end done-start done-sep-start done-end)
1065 (widen)
1066 (goto-char (point-min))
1067 (re-search-forward
1068 (concat "^" (regexp-quote (concat todos-category-beg name)) "$") nil t)
1069 (setq cat-begin (1+ (line-end-position)))
1070 (setq cat-end (if (re-search-forward
1071 (concat "^" (regexp-quote todos-category-beg)) nil t)
1072 (match-beginning 0)
1073 (point-max)))
1074 (setq mode-line-buffer-identification
1075 (funcall todos-mode-line-function name))
1076 (narrow-to-region cat-begin cat-end)
1077 (todos-prefix-overlays)
1078 (goto-char (point-min))
1079 (if (re-search-forward (concat "\n\\(" (regexp-quote todos-category-done)
1080 "\\)") nil t)
1081 (progn
1082 (setq done-start (match-beginning 0))
1083 (setq done-sep-start (match-beginning 1))
1084 (setq done-end (match-end 0)))
1085 (error "Category %s is missing todos-category-done string" name))
1086 (if todos-show-done-only
1087 (narrow-to-region (1+ done-end) (point-max))
1088 (when (and todos-show-with-done
1089 (re-search-forward todos-done-string-start nil t))
1090 ;; Now we want to see the done items, so reset displayed end to end of
1091 ;; done items.
1092 (setq done-start cat-end)
1093 ;; Make display overlay for done items separator string, unless there
1094 ;; already is one.
1095 (let* ((done-sep todos-done-separator)
1096 (ovs (overlays-at done-sep-start))
1097 ov-sep)
1098 (unless (and ovs (string= (overlay-get (car ovs) 'display) done-sep))
1099 (setq ov-sep (make-overlay done-sep-start done-end))
1100 (overlay-put ov-sep 'display done-sep))))
1101 (narrow-to-region (point-min) done-start)
1102 ;; Loading this from todos-mode, or adding it to the mode hook, causes
520d912e 1103 ;; Emacs to hang in todos-item-start, at (looking-at todos-item-start).
0e89c3fc
SB
1104 (when todos-highlight-item
1105 (require 'hl-line)
1106 (hl-line-mode 1)))))
3f031767 1107
0e89c3fc
SB
1108(defun todos-get-count (type &optional category)
1109 "Return count of TYPE items in CATEGORY.
1110If CATEGORY is nil, default to the current category."
1111 (let* ((cat (or category (todos-current-category)))
2a9e69d6 1112 ;; FIXME: todos-categories-full?
0e89c3fc
SB
1113 (counts (cdr (assoc cat todos-categories)))
1114 (idx (cond ((eq type 'todo) 0)
1115 ((eq type 'diary) 1)
1116 ((eq type 'done) 2)
1117 ((eq type 'archived) 3))))
1118 (aref counts idx)))
ee7412e4 1119
3af3cd0b
SB
1120(defun todos-update-count (type increment &optional category)
1121 "Change count of TYPE items in CATEGORY by integer INCREMENT.
1122With nil or omitted CATEGORY, default to the current category."
0e89c3fc 1123 (let* ((cat (or category (todos-current-category)))
2a9e69d6 1124 ;; FIXME: todos-categories-full?
0e89c3fc
SB
1125 (counts (cdr (assoc cat todos-categories)))
1126 (idx (cond ((eq type 'todo) 0)
1127 ((eq type 'diary) 1)
1128 ((eq type 'done) 2)
1129 ((eq type 'archived) 3))))
1130 (aset counts idx (+ increment (aref counts idx)))))
d04d6b95 1131
459c6e93 1132(defun todos-set-categories () ;FIXME
0e89c3fc
SB
1133 "Set `todos-categories' from the sexp at the top of the file."
1134 ;; New archive files created by `todos-move-category' are empty, which would
1135 ;; make the sexp test fail and raise an error, so in this case we skip it.
1136 (unless (zerop (buffer-size))
1137 (save-excursion
1138 (save-restriction
1139 (widen)
1140 (goto-char (point-min))
1141 ;; todos-truncate-categories-list needs non-nil todos-categories.
1142 (setq todos-categories-full
1143 (if (looking-at "\(\(\"")
1144 (read (buffer-substring-no-properties
1145 (line-beginning-position)
1146 (line-end-position)))
1147 (error "Invalid or missing todos-categories sexp"))
1148 todos-categories todos-categories-full)))
1149 (if (and todos-ignore-archived-categories
1150 (eq major-mode 'todos-mode))
1151 (todos-truncate-categories-list)
1152 todos-categories-full)))
d04d6b95 1153
0e89c3fc
SB
1154(defun todos-update-categories-sexp ()
1155 "Update the `todos-categories' sexp at the top of the file."
1156 (let (buffer-read-only)
1157 (save-excursion
1158 (save-restriction
1159 (widen)
1160 (goto-char (point-min))
1161 (if (looking-at (concat "^" (regexp-quote todos-category-beg)))
459c6e93
SB
1162 (progn (newline) (goto-char (point-min)) ; Make space for sexp.
1163 ;; No categories sexp means the first item was just added
1164 ;; to this file, so have to initialize Todos file and
1165 ;; categories variables in order e.g. to enable categories
1166 ;; display.
1167 (setq todos-default-todos-file (buffer-file-name))
1168 (setq todos-categories (todos-make-categories-list t))
1169 (when todos-ignore-archived-categories
1170 (setq todos-categories-full todos-categories)))
0e89c3fc
SB
1171 ;; With empty buffer (e.g. with new archive in
1172 ;; `todos-move-category') `kill-line' signals end of buffer.
1173 (kill-region (line-beginning-position) (line-end-position)))
1174 ;; todos-categories-full is nil on adding first category.
1175 (prin1 (or todos-categories-full todos-categories)
1176 (current-buffer))))))
d04d6b95 1177
0e89c3fc
SB
1178(defun todos-make-categories-list (&optional force)
1179 "Return an alist of Todos categories and their item counts.
1180With non-nil argument FORCE parse the entire file to build the
1181list; otherwise, get the value by reading the sexp at the top of
1182the file."
1183 (setq todos-categories nil)
1184 (save-excursion
1185 (save-restriction
1186 (widen)
1187 (goto-char (point-min))
1188 (let (counts cat archive)
1189 (when buffer-file-name ; Don't check with `todos-convert-legacy-files'.
1190 ;; FIXME: can todos-archives be too old here?
1191 (unless (member buffer-file-name (funcall todos-files-function t))
1192 (setq archive (concat (file-name-sans-extension
1193 todos-current-todos-file) ".toda"))))
1194 (while (not (eobp))
1195 (cond ((looking-at (concat (regexp-quote todos-category-beg)
1196 "\\(.*\\)\n"))
1197 (setq cat (match-string-no-properties 1))
1198 ;; Counts for each category: [todo diary done archive]
1199 (setq counts (make-vector 4 0))
1200 (setq todos-categories
1201 (append todos-categories (list (cons cat counts))))
1202 ;; todos-archives may be too old here (e.g. during
1203 ;; todos-move-category).
1204 (when (member archive (funcall todos-files-function t))
1205 (let ((archive-count 0))
1206 (with-current-buffer (find-file-noselect archive)
1207 (widen)
1208 (goto-char (point-min))
1209 (when (re-search-forward
1210 (concat (regexp-quote todos-category-beg) cat)
1211 (point-max) t)
1212 (forward-line)
1213 (while (not (or (looking-at
1214 (concat
1215 (regexp-quote todos-category-beg)
1216 "\\(.*\\)\n"))
1217 (eobp)))
1218 (when (looking-at todos-done-string-start)
1219 (setq archive-count (1+ archive-count)))
1220 (forward-line))))
3af3cd0b 1221 (todos-update-count 'archived archive-count cat))))
0e89c3fc 1222 ((looking-at todos-done-string-start)
3af3cd0b 1223 (todos-update-count 'done 1 cat))
0e89c3fc
SB
1224 ((looking-at (concat "^\\("
1225 (regexp-quote diary-nonmarking-symbol)
1226 "\\)?" todos-date-pattern))
3af3cd0b
SB
1227 (todos-update-count 'diary 1 cat)
1228 (todos-update-count 'todo 1 cat))
0e89c3fc 1229 ((looking-at (concat todos-date-string-start todos-date-pattern))
3af3cd0b 1230 (todos-update-count 'todo 1 cat))
0e89c3fc
SB
1231 ;; If first line is todos-categories list, use it and end loop
1232 ;; -- unless FORCEd to scan whole file.
1233 ((bobp)
1234 (unless force
1235 (setq todos-categories (read (buffer-substring-no-properties
1236 (line-beginning-position)
1237 (line-end-position))))
1238 (goto-char (1- (point-max))))))
1239 (forward-line)))))
2a9e69d6 1240 ;; FIXME: todos-categories-full?
0e89c3fc 1241 todos-categories)
3f031767 1242
0e89c3fc
SB
1243(defun todos-truncate-categories-list ()
1244 "Return a truncated alist of Todos categories plus item counts.
1245Categories containing only archived items are omitted. This list
1246is used in Todos mode when `todos-ignore-archived-categories' is
1247non-nil."
1248 (let (cats)
1249 (dolist (catcons todos-categories-full cats)
1250 (let ((cat (car catcons)))
1251 (setq cats
1252 (append cats
1253 (unless (and (zerop (todos-get-count 'todo cat))
1254 (zerop (todos-get-count 'done cat))
1255 (not (zerop (todos-get-count 'archived cat))))
1256 (list catcons))))))))
58c7641d 1257
0e89c3fc
SB
1258(defun todos-check-format ()
1259 "Signal an error if the current Todos file is ill-formatted.
1260Otherwise return t. The error message gives the line number
1261where the invalid formatting was found."
1262 (save-excursion
1263 (save-restriction
1264 (widen)
1265 (goto-char (point-min))
1266 ;; Check for `todos-categories' sexp as the first line
1267 (let ((cats (prin1-to-string (or todos-categories-full todos-categories))))
1268 (unless (looking-at (regexp-quote cats))
1269 (error "Invalid or missing todos-categories sexp")))
1270 (forward-line)
1271 (let ((legit (concat "\\(^" (regexp-quote todos-category-beg) "\\)"
1272 "\\|\\(" todos-date-string-start todos-date-pattern "\\)"
1273 "\\|\\(^[ \t]+[^ \t]*\\)"
1274 "\\|^$"
1275 "\\|\\(^" (regexp-quote todos-category-done) "\\)"
1276 "\\|\\(" todos-done-string-start "\\)")))
1277 (while (not (eobp))
1278 (unless (looking-at legit)
1279 (error "Illegitimate Todos file format at line %d"
1280 (line-number-at-pos (point))))
1281 (forward-line)))))
1282 ;; (message "This Todos file is well-formatted.")
1283 t)
d04d6b95 1284
0e89c3fc 1285(defun todos-repair-categories-sexp ()
520d912e
SB
1286 "Repair corrupt Todos categories sexp.
1287This should only be needed as a consequence of careless manual
1288editing or a bug in todos.el."
0e89c3fc
SB
1289 (interactive)
1290 (let ((todos-categories-full (todos-make-categories-list t)))
1291 (todos-update-categories-sexp)))
ee7412e4 1292
0e89c3fc
SB
1293(defvar todos-item-start (concat "\\(" todos-date-string-start "\\|"
1294 todos-done-string-start "\\)"
1295 todos-date-pattern)
1296 "String identifying start of a Todos item.")
58c7641d 1297
0e89c3fc
SB
1298(defun todos-item-start ()
1299 "Move to start of current Todos item and return its position."
1300 (unless (or
1301 ;; Point is either on last item in this category or on the empty
1302 ;; line between done and not done items.
1303 (looking-at "^$")
1304 ;; There are no done items in this category yet.
1305 (looking-at (regexp-quote todos-category-beg)))
1306 (goto-char (line-beginning-position))
1307 (while (not (looking-at todos-item-start))
1308 (forward-line -1))
1309 (point)))
d04d6b95 1310
0e89c3fc
SB
1311(defun todos-item-end ()
1312 "Move to end of current Todos item and return its position."
1313 ;; Items cannot end with a blank line.
1314 (unless (looking-at "^$")
1315 (let ((done (todos-done-item-p)))
1316 (todos-forward-item)
78fe7289
SB
1317 ;; Adjust if item is last unfinished one before displayed done items.
1318 (when (and (not done) (todos-done-item-p))
1319 (forward-line -1))
1320 (backward-char))
0e89c3fc 1321 (point)))
ee7412e4 1322
0e89c3fc
SB
1323(defun todos-item-string ()
1324 "Return bare text of current item as a string."
1325 (let ((opoint (point))
1326 (start (todos-item-start))
1327 (end (todos-item-end)))
1328 (goto-char opoint)
1329 (and start end (buffer-substring-no-properties start end))))
3f031767 1330
0e89c3fc
SB
1331(defun todos-remove-item ()
1332 "Internal function called in editing, deleting or moving items."
1333 (let* ((beg (todos-item-start))
1334 (end (progn (todos-item-end) (1+ (point))))
1335 (ovs (overlays-in beg beg)))
1336 ;; There can be both prefix/number and mark overlays.
1337 (while ovs (delete-overlay (car ovs)) (pop ovs))
1338 (delete-region beg end)))
ee7412e4 1339
0e89c3fc 1340(defun todos-diary-item-p ()
3af3cd0b 1341 "Return non-nil if item at point has diary entry format."
0e89c3fc
SB
1342 (save-excursion
1343 (todos-item-start)
0e89c3fc 1344 (not (looking-at (regexp-quote todos-nondiary-start)))))
58c7641d 1345
0e89c3fc
SB
1346(defun todos-done-item-p ()
1347 "Return non-nil if item at point is a done item."
1348 (save-excursion
1349 (todos-item-start)
1350 (looking-at todos-done-string-start)))
d04d6b95 1351
0e89c3fc
SB
1352(defvar todos-item-mark (propertize (if (equal todos-prefix "*") "@" "*")
1353 'face 'todos-mark)
1354 "String used to mark items.")
2c173503 1355
0e89c3fc 1356(defun todos-marked-item-p ()
3af3cd0b 1357 "If this item begins with `todos-item-mark', return mark overlay."
0e89c3fc
SB
1358 (let ((ovs (overlays-in (line-beginning-position) (line-beginning-position)))
1359 (mark todos-item-mark)
1360 ov marked)
1361 (catch 'stop
1362 (while ovs
1363 (setq ov (pop ovs))
1364 (and (equal (overlay-get ov 'before-string) mark)
1365 (throw 'stop (setq marked t)))))
1366 (when marked ov)))
3f031767 1367
0e89c3fc
SB
1368(defun todos-insert-with-overlays (item)
1369 "Insert ITEM at point and update prefix/priority number overlays."
1370 (todos-item-start)
1371 (insert item "\n")
1372 (todos-backward-item)
1373 (todos-prefix-overlays))
2c173503 1374
0e89c3fc
SB
1375(defun todos-prefix-overlays ()
1376 "Put before-string overlay in front of this category's items.
1377The overlay's value is the string `todos-prefix' or with non-nil
3af3cd0b
SB
1378`todos-number-priorities' an integer in the sequence from 1 to
1379the number of todo or done items in the category indicating the
0e89c3fc
SB
1380item's priority. Todo and done items are numbered independently
1381of each other."
3af3cd0b 1382 (when (or todos-number-priorities
0e89c3fc
SB
1383 (not (string-match "^[[:space:]]*$" todos-prefix)))
1384 (let ((prefix (propertize (concat todos-prefix " ")
1385 'face 'todos-prefix-string))
1386 (num 0))
1387 (save-excursion
1388 (goto-char (point-min))
1389 (while (not (eobp))
1390 (when (or (todos-date-string-matcher (line-end-position))
1391 (todos-done-string-matcher (line-end-position)))
1392 (goto-char (match-beginning 0))
3af3cd0b 1393 (when todos-number-priorities
0e89c3fc
SB
1394 (setq num (1+ num))
1395 ;; Reset number to 1 for first done item.
1396 (when (and (looking-at todos-done-string-start)
1397 (looking-back (concat "^"
1398 (regexp-quote todos-category-done)
1399 "\n")))
1400 (setq num 1))
1401 (setq prefix (propertize (concat (number-to-string num) " ")
1402 'face 'todos-prefix-string)))
1403 (let ((ovs (overlays-in (point) (point)))
1404 marked ov-pref)
1405 (if ovs
1406 (dolist (ov ovs)
1407 (let ((val (overlay-get ov 'before-string)))
1408 (if (equal val "*")
1409 (setq marked t)
1410 (setq ov-pref val)))))
1411 (unless (equal ov-pref prefix)
1412 ;; Why doesn't this work?
1413 ;; (remove-overlays (point) (point) 'before-string)
1414 (remove-overlays (point) (point))
1415 (overlay-put (make-overlay (point) (point))
1416 'before-string prefix)
1417 (and marked (overlay-put (make-overlay (point) (point))
1418 'before-string todos-item-mark)))))
1419 (forward-line))))))
2c173503 1420
0e89c3fc
SB
1421(defun todos-read-file-name (prompt &optional archive mustmatch)
1422 "Choose and return the name of a Todos file, prompting with PROMPT.
ee7412e4 1423
0e89c3fc
SB
1424Show completions with TAB or SPC; the names are shown in short
1425form but the absolute truename is returned. With non-nil ARCHIVE
1426return the absolute truename of a Todos archive file. With non-nil
1427MUSTMATCH the name of an existing file must be chosen;
1428otherwise, a new file name is allowed."
459c6e93
SB
1429 (let* ((completion-ignore-case todos-completion-ignore-case)
1430 (files (mapcar 'todos-short-file-name
1431 (if archive todos-archives todos-files)))
1432 (file (completing-read prompt files nil mustmatch nil nil
1433 (unless files
1434 ;; Trigger prompt for initial file.
1435 ""))))
1436 (unless (file-exists-p todos-files-directory)
1437 (make-directory todos-files-directory))
0e89c3fc 1438 (unless mustmatch
459c6e93
SB
1439 (setq file (todos-validate-name file 'file)))
1440 (setq file (file-truename (concat todos-files-directory file
1441 (if archive ".toda" ".todo"))))))
d04d6b95 1442
2a9e69d6 1443(defun todos-read-category (prompt &optional mustmatch added)
0e89c3fc
SB
1444 "Choose and return a category name, prompting with PROMPT.
1445Show completions with TAB or SPC. With non-nil MUSTMATCH the
1446name must be that of an existing category; otherwise, a new
2a9e69d6
SB
1447category name is allowed, after checking its validity. Non-nil
1448argument ADDED means the caller is todos-add-category, so don't
1449ask whether to add the category."
0e89c3fc
SB
1450 ;; Allow SPC to insert spaces, for adding new category names.
1451 (let ((map minibuffer-local-completion-map))
1452 (define-key map " " nil)
1453 ;; Make a copy of todos-categories in case history-delete-duplicates is
1454 ;; non-nil, which makes completing-read alter todos-categories.
2a9e69d6 1455 (let* ((categories (copy-sequence todos-categories)) ;FIXME: todos-categories-full?
0e89c3fc
SB
1456 (history (cons 'todos-categories (1+ todos-category-number)))
1457 (completion-ignore-case todos-completion-ignore-case)
1458 (cat (completing-read prompt todos-categories nil
1459 mustmatch nil history
1460 ;; Default for existing categories is the
1461 ;; current category.
1462 (if todos-categories
1463 (todos-current-category)
459c6e93 1464 ;; Trigger prompt for initial category.
2a9e69d6
SB
1465 "")))
1466 new)
0e89c3fc 1467 (unless mustmatch
2a9e69d6
SB
1468 (todos-validate-name cat 'category)
1469 (unless added
0e89c3fc
SB
1470 (if (y-or-n-p (format (concat "There is no category \"%s\" in "
1471 "this file; add it? ") cat))
2a9e69d6
SB
1472 (progn (todos-add-category cat) (setq new t))
1473 (keyboard-quit))));)
1474 ;; Restore the original value of todos-categories unless a new category
1475 ;; was added (since todos-add-category changes todos-categories).
1476 (unless (or new added) (setq todos-categories categories))
0e89c3fc 1477 cat)))
3f031767 1478
2a9e69d6 1479;; FIXME: use completing-read
0e89c3fc
SB
1480(defun todos-validate-name (name type)
1481 "Prompt for new NAME for TYPE until it is valid, then return it.
1482TYPE can be either a file or a category"
2a9e69d6
SB
1483 (let (;(categories todos-categories))
1484 prompt file cat shortname)
0e89c3fc
SB
1485 (while
1486 (and (cond ((string= "" name)
1487 (setq prompt
1488 (cond ((eq type 'file)
1489 ;; FIXME: just todos-files ?
459c6e93 1490 (if todos-files
0e89c3fc
SB
1491 "Enter a non-empty file name: "
1492 ;; Empty string passed by todos-show to
1493 ;; prompt for initial Todos file.
1494 (concat "Initial file name ["
1495 todos-initial-file "]: ")))
1496 ((eq type 'category)
1497 (if todos-categories
1498 "Enter a non-empty category name: "
1499 ;; Empty string passed by todos-show to
1500 ;; prompt for initial category of a new
1501 ;; Todos file.
1502 (concat "Initial category name ["
1503 todos-initial-category "]: "))))))
1504 ((string-match "\\`\\s-+\\'" name)
1505 (setq prompt
1506 "Enter a name that does not contain only white space: "))
1507 ((and (eq type 'file) (member name todos-files))
1508 (setq prompt "Enter a non-existing file name: "))
1509 ((and (eq type 'category) (assoc name todos-categories))
1510 (setq prompt "Enter a non-existing category name: ")))
1511 (setq name (if (or (and (eq type 'file) todos-files)
2a9e69d6
SB
1512 (and (eq type 'category) todos-categories))
1513 (read-from-minibuffer prompt)
0e89c3fc
SB
1514 ;; Offer default initial name.
1515 (read-string prompt nil nil
1516 (cond ((eq type 'file)
1517 todos-initial-file)
1518 ((eq type 'category)
1519 todos-initial-category))))))))
1520 name)
1521
1522;; Adapted from calendar-read-date and calendar-date-string.
1523(defun todos-read-date ()
1524 "Prompt for Gregorian date and return it in the current format.
1525Also accepts `*' as an unspecified month, day, or year."
1526 (let* ((year (calendar-read
1527 ;; FIXME: maybe better like monthname with RET for current month
1528 "Year (>0 or * for any year): "
1529 (lambda (x) (or (eq x '*) (> x 0)))
1530 (number-to-string (calendar-extract-year
1531 (calendar-current-date)))))
1532 (month-array (vconcat calendar-month-name-array (vector "*")))
1533 (abbrevs (vconcat calendar-month-abbrev-array (vector "*")))
1534 (completion-ignore-case todos-completion-ignore-case)
1535 (monthname (completing-read
1536 "Month name (RET for current month, * for any month): "
1537 (mapcar 'list (append month-array nil))
1538 nil t nil nil
1539 (calendar-month-name (calendar-extract-month
1540 (calendar-current-date)) t)))
1541 (month (cdr (assoc-string
1542 monthname (calendar-make-alist month-array nil nil
1543 abbrevs))))
1544 (last (if (= month 13)
1545 31 ; FIXME: what about shorter months?
1546 (let ((yr (if (eq year '*)
1547 1999 ; FIXME: no Feb. 29
1548 year)))
1549 (calendar-last-day-of-month month yr))))
1550 day dayname)
1551 (while (if (numberp day) (or (< day 0) (< last day)) (not (eq day '*)))
1552 (setq day (read-from-minibuffer
1553 (format "Day (1-%d or RET for today or * for any day): " last)
1554 nil nil t nil
1555 (number-to-string
1556 (calendar-extract-day (calendar-current-date))))))
1557 (setq year (if (eq year '*) (symbol-name '*) (number-to-string year)))
1558 (setq day (if (eq day '*) (symbol-name '*) (number-to-string day)))
1559 ;; FIXME: make abbreviation customizable
1560 (setq monthname
1561 (or (and (= month 13) "*")
1562 (calendar-month-name (calendar-extract-month (list month day year))
1563 t)))
1564 (mapconcat 'eval calendar-date-display-form "")))
2c173503 1565
0e89c3fc
SB
1566(defun todos-read-dayname ()
1567 "Choose name of a day of the week with completion and return it."
1568 (let ((completion-ignore-case todos-completion-ignore-case))
1569 (completing-read "Enter a day name: "
1570 (append calendar-day-name-array nil)
1571 nil t)))
1572
1573(defun todos-read-time ()
1574 "Prompt for and return a valid clock time as a string.
58c7641d 1575
0e89c3fc
SB
1576Valid time strings are those matching `diary-time-regexp'.
1577Typing `<return>' at the prompt returns the current time, if the
1578user option `todos-always-add-time-string' is non-nil, otherwise
1579the empty string (i.e., no time string)."
1580 (let (valid answer)
1581 (while (not valid)
1582 (setq answer (read-string "Enter a clock time: " nil nil
1583 (when todos-always-add-time-string
1584 (substring (current-time-string) 11 16))))
1585 (when (or (string= "" answer)
1586 (string-match diary-time-regexp answer))
1587 (setq valid t)))
1588 answer))
58c7641d 1589
0e89c3fc
SB
1590(defun todos-convert-legacy-date-time ()
1591 "Return converted date-time string.
1592Helper function for `todos-convert-legacy-files'."
1593 (let* ((year (match-string 1))
1594 (month (match-string 2))
1595 (monthname (calendar-month-name (string-to-number month) t))
1596 (day (match-string 3))
1597 (time (match-string 4))
1598 dayname)
1599 (replace-match "")
1600 (insert (mapconcat 'eval calendar-date-display-form "")
1601 (when time (concat " " time)))))
58c7641d 1602
0e89c3fc
SB
1603;; ---------------------------------------------------------------------------
1604;;; Item filtering
2c173503 1605
0e89c3fc 1606(defvar todos-multiple-files nil
520d912e 1607 "List of files selected from `todos-multiple-files' widget.")
58c7641d 1608
0e89c3fc
SB
1609(defvar todos-multiple-files-widget nil
1610 "Variable holding widget created by `todos-multiple-files'.")
58c7641d 1611
0e89c3fc
SB
1612(defun todos-multiple-files ()
1613 "Pop to a buffer with a widget for choosing multiple filter files."
1614 (require 'widget)
1615 (eval-when-compile
1616 (require 'wid-edit))
520d912e
SB
1617 (with-current-buffer (get-buffer-create "*Todos Filter Files*")
1618 (pop-to-buffer (current-buffer))
1619 (erase-buffer)
1620 (kill-all-local-variables)
1621 (widget-insert "Select files for generating the top priorities list.\n\n")
1622 (setq todos-multiple-files-widget
1623 (widget-create
1624 `(set ,@(mapcar (lambda (x) (list 'const x))
1625 (mapcar 'todos-short-file-name
1626 (funcall todos-files-function))))))
1627 (widget-insert "\n")
1628 (widget-create 'push-button
1629 :notify (lambda (widget &rest ignore)
1630 (setq todos-multiple-files 'quit)
1631 (quit-window t)
1632 (exit-recursive-edit))
1633 "Cancel")
1634 (widget-insert " ")
1635 (widget-create 'push-button
1636 :notify (lambda (&rest ignore)
1637 (setq todos-multiple-files
1638 (mapcar (lambda (f)
1639 (concat todos-files-directory
1640 f ".todo"))
1641 (widget-value
1642 todos-multiple-files-widget)))
1643 (quit-window t)
1644 (exit-recursive-edit))
1645 "Apply")
1646 (use-local-map widget-keymap)
1647 (widget-setup))
0e89c3fc
SB
1648 (message "Click \"Apply\" after selecting files.")
1649 (recursive-edit))
1650
0e89c3fc
SB
1651(defun todos-filter-items (filter &optional multifile)
1652 "Build and display a list of items from different categories.
1653
1654The items are selected according to the value of FILTER, which
1655can be `top' for top priority items, `diary' for diary items,
1656`regexp' for items matching a regular expresion entered by the
520d912e
SB
1657user, or a cons cell of one of these symbols and a number set by
1658the calling command, which overrides `todos-show-priorities'.
0e89c3fc
SB
1659
1660With non-nil argument MULTIFILE list top priorities of multiple
1661Todos files, by default those in `todos-filter-files'."
58c7641d 1662 (let ((num (if (consp filter) (cdr filter) todos-show-priorities))
0e89c3fc 1663 (buf (get-buffer-create todos-filter-buffer))
d04d6b95 1664 (files (list todos-current-todos-file))
58c7641d 1665 regexp fname bufstr cat beg end done)
0e89c3fc 1666 (when multifile
520d912e
SB
1667 (setq files (or todos-multiple-files ; Passed from todos-*-multifile.
1668 (if (or (consp filter)
1669 (null todos-filter-files))
1670 (progn (todos-multiple-files) todos-multiple-files)
1671 todos-filter-files))
0e89c3fc
SB
1672 todos-multiple-files nil))
1673 (if (eq files 'quit) (keyboard-quit))
1674 (if (null files)
1675 (error "No files have been chosen for filtering")
1676 (with-current-buffer buf
1677 (erase-buffer)
1678 (kill-all-local-variables)
1679 (todos-filter-items-mode))
1680 (when (eq filter 'regexp)
1681 (setq regexp (read-string "Enter a regular expression: ")))
1682 (save-current-buffer
1683 (dolist (f files)
1684 ;; Before inserting file contents into temp buffer, save a modified
1685 ;; buffer visiting it.
1686 (let ((bf (find-buffer-visiting f)))
1687 (when (buffer-modified-p bf)
1688 (with-current-buffer bf (save-buffer))))
1689 (setq fname (todos-short-file-name f))
1690 (with-temp-buffer
520d912e
SB
1691 (when (and todos-filter-done-items (eq filter 'regexp))
1692 ;; If there is a corresponding archive file for the Todos file,
1693 ;; insert it first and add identifiers for todos-jump-to-item.
1694 (let ((arch (concat (file-name-sans-extension f) ".toda")))
1695 (when (file-exists-p arch)
1696 (insert-file-contents arch)
1697 ;; Delete Todos archive file categories sexp.
1698 (delete-region (line-beginning-position)
1699 (1+ (line-end-position)))
1700 (save-excursion
1701 (while (not (eobp))
1702 (when (re-search-forward
1703 (concat (if todos-filter-done-items
1704 (concat "\\(?:" todos-done-string-start
1705 "\\|" todos-date-string-start
1706 "\\)")
1707 todos-date-string-start)
1708 todos-date-pattern "\\(?: "
1709 diary-time-regexp "\\)?"
1710 (if todos-filter-done-items
1711 "\\]"
1712 (regexp-quote todos-nondiary-end)) "?")
1713 nil t)
1714 (insert "(archive) "))
1715 (forward-line))))))
0e89c3fc 1716 (insert-file-contents f)
520d912e
SB
1717 ;; Delete Todos file categories sexp.
1718 (delete-region (line-beginning-position) (1+ (line-end-position)))
0e89c3fc
SB
1719 (let (fnum)
1720 ;; Unless the number of items to show was supplied by prefix
1721 ;; argument of caller, override `todos-show-priorities' with the
1722 ;; file-wide value from `todos-priorities-rules'.
1723 (unless (consp filter)
1724 (setq fnum (nth 1 (assoc f todos-priorities-rules))))
0e89c3fc
SB
1725 (while (re-search-forward
1726 (concat "^" (regexp-quote todos-category-beg) "\\(.+\\)\n")
1727 nil t)
1728 (setq cat (match-string 1))
1729 (let (cnum)
1730 ;; Unless the number of items to show was supplied by prefix
1731 ;; argument of caller, override the file-wide value from
1732 ;; `todos-priorities-rules' if set, else
1733 ;; `todos-show-priorities' with non-nil category-wide value
1734 ;; from `todos-priorities-rules'.
1735 (unless (consp filter)
1736 (let ((cats (nth 2 (assoc f todos-priorities-rules))))
1737 (setq cnum (or (cdr (assoc cat cats))
1738 fnum
1739 ;; FIXME: need this?
1740 todos-show-priorities))))
1741 (delete-region (match-beginning 0) (match-end 0))
520d912e 1742 (setq beg (point)) ; First item in the current category.
0e89c3fc
SB
1743 (setq end (if (re-search-forward
1744 (concat "^" (regexp-quote todos-category-beg))
1745 nil t)
1746 (match-beginning 0)
1747 (point-max)))
1748 (goto-char beg)
1749 (setq done
1750 (if (re-search-forward
1751 (concat "\n" (regexp-quote todos-category-done))
1752 end t)
1753 (match-beginning 0)
1754 end))
520d912e
SB
1755 (unless (and todos-filter-done-items (eq filter 'regexp))
1756 ;; Leave done items.
0e89c3fc
SB
1757 (delete-region done end)
1758 (setq end done))
520d912e 1759 (narrow-to-region beg end) ; Process only current category.
0e89c3fc
SB
1760 (goto-char (point-min))
1761 ;; Apply the filter.
1762 (cond ((eq filter 'diary)
1763 (while (not (eobp))
1764 (if (looking-at (regexp-quote todos-nondiary-start))
1765 (todos-remove-item)
1766 (todos-forward-item))))
1767 ((eq filter 'regexp)
1768 (while (not (eobp))
1769 (if (looking-at todos-item-start)
1770 (if (string-match regexp (todos-item-string))
1771 (todos-forward-item)
1772 (todos-remove-item))
1773 ;; Kill lines that aren't part of a todo or done
1774 ;; item (empty or todos-category-done).
1775 (delete-region (line-beginning-position)
1776 (1+ (line-end-position))))
1777 ;; If last todo item in file matches regexp and
1778 ;; there are no following done items,
1779 ;; todos-category-done string is left dangling,
1780 ;; because todos-forward-item jumps over it.
520d912e
SB
1781 (if (and (eobp)
1782 (looking-back
1783 (concat (regexp-quote todos-done-string)
1784 "\n")))
0e89c3fc
SB
1785 (delete-region (point) (progn
1786 (forward-line -2)
1787 (point))))))
0e89c3fc
SB
1788 (t ; Filter top priority items.
1789 (setq num (or cnum fnum num))
1790 (unless (zerop num)
1791 (todos-forward-item num))))
1792 (setq beg (point))
520d912e
SB
1793 ;; Delete non-top-priority items.
1794 (unless (member filter '(diary regexp))
0e89c3fc
SB
1795 (delete-region beg end))
1796 (goto-char (point-min))
1797 ;; Add file (if using multiple files) and category tags to
1798 ;; item.
1799 (while (not (eobp))
1800 (when (re-search-forward
520d912e
SB
1801 (concat (if todos-filter-done-items
1802 (concat "\\(?:" todos-done-string-start
1803 "\\|" todos-date-string-start
1804 "\\)")
1805 todos-date-string-start)
1806 todos-date-pattern "\\(?: " diary-time-regexp
1807 "\\)?" (if todos-filter-done-items
1808 "\\]"
1809 (regexp-quote todos-nondiary-end))
1810 "?")
0e89c3fc 1811 nil t)
520d912e
SB
1812 (insert " [")
1813 (when (looking-at "(archive) ") (goto-char (match-end 0)))
1814 (insert (if multifile (concat fname ":") "") cat "]"))
0e89c3fc
SB
1815 (forward-line))
1816 (widen)))
1817 (setq bufstr (buffer-string))
1818 (with-current-buffer buf
1819 (let (buffer-read-only)
1820 (insert bufstr)))))))
0e89c3fc
SB
1821 (set-window-buffer (selected-window) (set-buffer buf))
1822 (todos-prefix-overlays)
520d912e 1823 (goto-char (point-min)))))
0e89c3fc
SB
1824
1825(defun todos-set-top-priorities (&optional arg)
1826 "Set number of top priorities shown by `todos-top-priorities'.
1827With non-nil ARG, set the number only for the current Todos
1828category; otherwise, set the number for all categories in the
1829current Todos file.
1830
1831Calling this function via either of the commands
1832`todos-set-top-priorities-in-file' or
1833`todos-set-top-priorities-in-category' is the recommended way to
1834set the user customizable option `todos-priorities-rules'."
1835 (let* ((cat (todos-current-category))
1836 (file todos-current-todos-file)
1837 (rules todos-priorities-rules)
1838 (frule (assoc-string file rules))
1839 (crule (assoc-string cat (nth 2 frule)))
1840 (cur (or (if arg (cdr crule) (nth 1 frule))
1841 todos-show-priorities))
1842 (prompt (concat "Current number of top priorities in this "
1843 (if arg "category" "file") ": %d; "
1844 "enter new number: "))
1845 (new "-1")
1846 nrule)
1847 (while (or (not (string-match "[0-9]+" new)) ; Don't accept "" or "bla".
1848 (< (string-to-number new) 0))
1849 (let ((cur0 cur))
1850 (setq new (read-string (format prompt cur0) nil nil cur0)
1851 prompt "Enter a non-negative number: "
1852 cur0 nil)))
1853 (setq new (string-to-number new))
1854 (setq nrule (if arg
1855 (append (nth 2 (delete crule frule)) (list (cons cat new)))
1856 (append (list file new) (list (nth 2 frule)))))
1857 (setq rules (cons (if arg
1858 (list file cur nrule)
1859 nrule)
1860 (delete frule rules)))
1861 (customize-save-variable 'todos-priorities-rules rules)))
2c173503 1862
d04d6b95 1863
0e89c3fc
SB
1864;; ---------------------------------------------------------------------------
1865;;; Sorting and display routines for Todos Categories mode.
58c7641d 1866
0e89c3fc
SB
1867(defun todos-longest-category-name-length (categories)
1868 "Return the length of the longest name in list CATEGORIES."
1869 (let ((longest 0))
1870 (dolist (c categories longest)
1871 (setq longest (max longest (length c))))))
58c7641d 1872
0e89c3fc
SB
1873(defun todos-padded-string (str)
1874 "Return string STR padded with spaces.
1875The placement of the padding is determined by the value of user
1876option `todos-categories-align'."
1877 (let* ((categories (mapcar 'car todos-categories))
1878 (len (max (todos-longest-category-name-length categories)
1879 (length todos-categories-category-label)))
1880 (strlen (length str))
1881 (strlen-odd (eq (logand strlen 1) 1)) ; oddp from cl.el
1882 (padding (max 0 (/ (- len strlen) 2)))
1883 (padding-left (cond ((eq todos-categories-align 'left) 0)
1884 ((eq todos-categories-align 'center) padding)
1885 ((eq todos-categories-align 'right)
1886 (if strlen-odd (1+ (* padding 2)) (* padding 2)))))
1887 (padding-right (cond ((eq todos-categories-align 'left)
1888 (if strlen-odd (1+ (* padding 2)) (* padding 2)))
1889 ((eq todos-categories-align 'center)
1890 (if strlen-odd (1+ padding) padding))
1891 ((eq todos-categories-align 'right) 0))))
1892 (concat (make-string padding-left 32) str (make-string padding-right 32))))
58c7641d 1893
0e89c3fc
SB
1894(defvar todos-descending-counts nil
1895 "List of keys for category counts sorted in descending order.")
58c7641d 1896
0e89c3fc
SB
1897(defun todos-sort (list &optional key)
1898 "Return a copy of LIST, possibly sorted according to KEY."
1899 (let* ((l (copy-sequence list))
1900 (fn (if (eq key 'alpha)
1901 (lambda (x) (upcase x)) ; Alphabetize case insensitively.
1902 (lambda (x) (todos-get-count key x))))
1903 (descending (member key todos-descending-counts))
1904 (cmp (if (eq key 'alpha)
1905 'string<
1906 (if descending '< '>)))
1907 (pred (lambda (s1 s2) (let ((t1 (funcall fn (car s1)))
1908 (t2 (funcall fn (car s2))))
1909 (funcall cmp t1 t2)))))
1910 (when key
1911 (setq l (sort l pred))
1912 (if descending
1913 (setq todos-descending-counts
1914 (delete key todos-descending-counts))
1915 (push key todos-descending-counts)))
1916 l))
58c7641d 1917
0e89c3fc
SB
1918(defun todos-display-sorted (type)
1919 "Keep point on the TYPE count sorting button just clicked."
1920 (let ((opoint (point)))
1921 (todos-update-categories-display type)
1922 (goto-char opoint)))
d04d6b95 1923
0e89c3fc
SB
1924(defun todos-label-to-key (label)
1925 "Return symbol for sort key associated with LABEL."
1926 (let (key)
1927 (cond ((string= label todos-categories-category-label)
1928 (setq key 'alpha))
1929 ((string= label todos-categories-todo-label)
1930 (setq key 'todo))
1931 ((string= label todos-categories-diary-label)
1932 (setq key 'diary))
1933 ((string= label todos-categories-done-label)
1934 (setq key 'done))
1935 ((string= label todos-categories-archived-label)
1936 (setq key 'archived)))
1937 key))
ee7412e4 1938
0e89c3fc
SB
1939(defun todos-insert-sort-button (label)
1940 "Insert button for displaying categories sorted by item counts.
1941LABEL determines which type of count is sorted."
1942 (setq str (if (string= label todos-categories-category-label)
1943 (todos-padded-string label)
1944 label))
1945 (setq beg (point))
1946 (setq end (+ beg (length str)))
1947 (insert-button str 'face nil
1948 'action
1949 `(lambda (button)
1950 (let ((key (todos-label-to-key ,label)))
1951 (if (and (member key todos-descending-counts)
1952 (eq key 'alpha))
1953 (progn
1954 ;; If display is alphabetical, switch back to
1955 ;; category order.
1956 (todos-display-sorted nil)
1957 (setq todos-descending-counts
1958 (delete key todos-descending-counts)))
1959 (todos-display-sorted key)))))
1960 (setq ovl (make-overlay beg end))
1961 (overlay-put ovl 'face 'todos-button))
ee7412e4 1962
0e89c3fc
SB
1963(defun todos-total-item-counts ()
1964 "Return a list of total item counts for the current file."
1965 (mapcar (lambda (i) (apply '+ (mapcar (lambda (l) (aref l i))
1966 (mapcar 'cdr todos-categories))))
1967 (list 0 1 2 3)))
ee7412e4 1968
459c6e93
SB
1969(defvar todos-category-number nil)
1970
0e89c3fc 1971(defun todos-insert-category-line (cat &optional nonum)
459c6e93 1972 "Insert button with category CAT's name and item counts.
0e89c3fc
SB
1973With non-nil argument NONUM show only these; otherwise, insert a
1974number in front of the button indicating the category's priority.
1975The number and the category name are separated by the string
1976which is the value of the user option
1977`todos-categories-number-separator'."
459c6e93
SB
1978 (let ((archive (member todos-current-todos-file todos-archives))
1979 (num todos-category-number)
0e89c3fc
SB
1980 (str (todos-padded-string cat))
1981 (opoint (point)))
459c6e93 1982 (setq num (1+ num) todos-category-number num)
0e89c3fc
SB
1983 (insert-button
1984 (concat (if nonum
1985 (make-string (+ 4 (length todos-categories-number-separator))
1986 32)
1987 (format " %3d%s" num todos-categories-number-separator))
1988 str
1989 (mapconcat (lambda (elt)
1990 (concat
1991 (make-string (1+ (/ (length (car elt)) 2)) 32) ; label
1992 (format "%3d" (todos-get-count (cdr elt) cat)) ; count
1993 ;; Add an extra space if label length is odd
1994 ;; (using def of oddp from cl.el).
1995 (if (eq (logand (length (car elt)) 1) 1) " ")))
1996 (if archive
1997 (list (cons todos-categories-done-label 'done))
1998 (list (cons todos-categories-todo-label 'todo)
1999 (cons todos-categories-diary-label 'diary)
2000 (cons todos-categories-done-label 'done)
2001 (cons todos-categories-archived-label
2002 'archived)))
2003 ""))
2004 'face (if (and todos-ignore-archived-categories
2005 (zerop (todos-get-count 'todo cat))
2006 (zerop (todos-get-count 'done cat))
2007 (not (zerop (todos-get-count 'archived cat))))
2008 'todos-archived-only
2009 nil)
2010 'action `(lambda (button) (let ((buf (current-buffer)))
2011 (todos-jump-to-category ,cat)
2012 (kill-buffer buf))))
2013 ;; Highlight the sorted count column.
2014 (let* ((beg (+ opoint 6 (length str)))
2015 end ovl)
2016 (cond ((eq nonum 'todo)
2017 (setq beg (+ beg 1 (/ (length todos-categories-todo-label) 2))))
2018 ((eq nonum 'diary)
2019 (setq beg (+ beg 1 (length todos-categories-todo-label)
2020 2 (/ (length todos-categories-diary-label) 2))))
2021 ((eq nonum 'done)
2022 (setq beg (+ beg 1 (length todos-categories-todo-label)
2023 2 (length todos-categories-diary-label)
2024 2 (/ (length todos-categories-done-label) 2))))
2025 ((eq nonum 'archived)
2026 (setq beg (+ beg 1 (length todos-categories-todo-label)
2027 2 (length todos-categories-diary-label)
2028 2 (length todos-categories-done-label)
2029 2 (/ (length todos-categories-archived-label) 2)))))
2030 (unless (= beg (+ opoint 6 (length str)))
2031 (setq end (+ beg 4))
2032 (setq ovl (make-overlay beg end))
2033 (overlay-put ovl 'face 'todos-sorted-column)))
2034 (newline)))
d04d6b95 2035
0e89c3fc
SB
2036(defun todos-display-categories-1 ()
2037 "Prepare buffer for displaying table of categories and item counts."
2038 (unless (eq major-mode 'todos-categories-mode)
2039 (setq todos-global-current-todos-file (or todos-current-todos-file
2040 todos-default-todos-file))
2041 (set-window-buffer (selected-window)
2042 (set-buffer (get-buffer-create todos-categories-buffer)))
2043 (kill-all-local-variables)
2044 (todos-categories-mode)
2045 (let (buffer-read-only)
2046 (erase-buffer)
2047 ;; FIXME: add usage tips?
2048 (insert (format "Category counts for Todos file \"%s\"."
2049 (todos-short-file-name todos-current-todos-file)))
2050 (newline 2)
2051 ;; Make space for the column of category numbers.
2052 (insert (make-string (+ 4 (length todos-categories-number-separator)) 32))
2053 ;; Add the category and item count buttons (if this is the list of
2054 ;; categories in an archive, show only done item counts).
2055 (todos-insert-sort-button todos-categories-category-label)
2056 (if (member todos-current-todos-file todos-archives)
2057 (insert (concat (make-string 6 32)
2058 (format "%s" todos-categories-archived-label)))
2059 (insert (make-string 3 32))
2060 (todos-insert-sort-button todos-categories-todo-label)
2061 (insert (make-string 2 32))
2062 (todos-insert-sort-button todos-categories-diary-label)
2063 (insert (make-string 2 32))
2064 (todos-insert-sort-button todos-categories-done-label)
2065 (insert (make-string 2 32))
2066 (todos-insert-sort-button todos-categories-archived-label))
2067 (newline 2))))
2068
2069(defun todos-update-categories-display (sortkey)
2070 ""
2071 (let* ((cats0 (if (and todos-ignore-archived-categories
459c6e93
SB
2072 ;; FIXME: is this every true?
2073 (not (eq major-mode 'todos-categories-mode)))
2074 todos-categories-full
2075 todos-categories))
2076 (cats (todos-sort cats0 sortkey))
2077 (archive (member todos-current-todos-file todos-archives))
2078 (todos-category-number 0)
2079 ;; Find start of Category button if we just entered Todos Categories
2080 ;; mode.
2081 (pt (if (eq (point) (point-max))
2082 (save-excursion
2083 (forward-line -2)
2084 (goto-char (next-single-char-property-change
2085 (point) 'face nil (line-end-position))))))
2086 (buffer-read-only))
2087 (forward-line 2)
2088 (delete-region (point) (point-max))
2089 ;; Fill in the table with buttonized lines, each showing a category and
2090 ;; its item counts.
2091 (mapc (lambda (cat) (todos-insert-category-line cat sortkey))
2092 (mapcar 'car cats))
2093 (newline)
2094 ;; Add a line showing item count totals.
2095 (insert (make-string (+ 4 (length todos-categories-number-separator)) 32)
2096 (todos-padded-string todos-categories-totals-label)
2097 (mapconcat
2098 (lambda (elt)
2099 (concat
2100 (make-string (1+ (/ (length (car elt)) 2)) 32)
2101 (format "%3d" (nth (cdr elt) (todos-total-item-counts)))
2102 ;; Add an extra space if label length is odd (using
2103 ;; definition of oddp from cl.el).
2104 (if (eq (logand (length (car elt)) 1) 1) " ")))
2105 (if archive
2106 (list (cons todos-categories-done-label 2))
2107 (list (cons todos-categories-todo-label 0)
2108 (cons todos-categories-diary-label 1)
2109 (cons todos-categories-done-label 2)
2110 (cons todos-categories-archived-label 3)))
2111 ""))
2112 ;; Put cursor on Category button initially.
2113 (if pt (goto-char pt))
2114 (setq buffer-read-only t)))
ee7412e4 2115
0e89c3fc
SB
2116;; ---------------------------------------------------------------------------
2117;;; Todos insertion commands, key bindings and keymap
ee7412e4 2118
0e89c3fc
SB
2119;; Can either of these be included in Emacs? The originals are GFDL'd.
2120;; Slightly reformulated from
2121;; http://rosettacode.org/wiki/Power_set#Common_Lisp.
2122(defun powerset-recursive (l)
2123 (cond ((null l)
2124 (list nil))
2125 (t
520d912e
SB
2126 (let ((prev (powerset-recursive (cdr l))))
2127 (append (mapcar (lambda (elt) (cons (car l) elt))
2128 prev)
0e89c3fc
SB
2129 prev)))))
2130;; Elisp implementation of http://rosettacode.org/wiki/Power_set#C
2131(defun powerset-bitwise (l)
2132 (let ((binnum (lsh 1 (length l)))
2133 pset elt)
2134 (dotimes (i binnum)
2135 (let ((bits i)
2136 (ll l))
2137 (while (not (zerop bits))
2138 (let ((arg (pop ll)))
2139 (unless (zerop (logand bits 1))
2140 (setq elt (append elt (list arg))))
2141 (setq bits (lsh bits -1))))
2142 (setq pset (append pset (list elt)))
2143 (setq elt nil)))
2144 pset))
2145
2146;; (defalias 'todos-powerset 'powerset-recursive)
2147(defalias 'todos-powerset 'powerset-bitwise)
ee7412e4 2148
0e89c3fc
SB
2149;; Return list of lists of non-nil atoms produced from ARGLIST. The elements
2150;; of ARGLIST may be atoms or lists.
2151(defun todos-gen-arglists (arglist)
2152 (let (arglists)
2153 (while arglist
2154 (let ((arg (pop arglist)))
2155 (cond ((symbolp arg)
2156 (setq arglists (if arglists
2157 (mapcar (lambda (l) (push arg l)) arglists)
2158 (list (push arg arglists)))))
2159 ((listp arg)
2160 (setq arglists
2161 (mapcar (lambda (a)
2162 (if (= 1 (length arglists))
2163 (apply (lambda (l) (push a l)) arglists)
2164 (mapcar (lambda (l) (push a l)) arglists)))
2165 arg))))))
2166 (setq arglists (mapcar 'reverse (apply 'append (mapc 'car arglists))))))
d04d6b95 2167
0e89c3fc
SB
2168(defvar todos-insertion-commands-args-genlist
2169 '(diary nonmarking (calendar date dayname) time (here region))
2170 "Generator list for argument lists of Todos insertion commands.")
ee7412e4 2171
0e89c3fc
SB
2172(defvar todos-insertion-commands-args
2173 (let ((argslist (todos-gen-arglists todos-insertion-commands-args-genlist))
2174 res new)
2175 (setq res (remove-duplicates
2176 (apply 'append (mapcar 'todos-powerset argslist)) :test 'equal))
2177 (dolist (l res)
2178 (unless (= 5 (length l))
2179 (let ((v (make-vector 5 nil)) elt)
2180 (while l
2181 (setq elt (pop l))
2182 (cond ((eq elt 'diary)
2183 (aset v 0 elt))
2184 ((eq elt 'nonmarking)
2185 (aset v 1 elt))
2186 ((or (eq elt 'calendar)
2187 (eq elt 'date)
2188 (eq elt 'dayname))
2189 (aset v 2 elt))
2190 ((eq elt 'time)
2191 (aset v 3 elt))
2192 ((or (eq elt 'here)
2193 (eq elt 'region))
2194 (aset v 4 elt))))
2195 (setq l (append v nil))))
2196 (setq new (append new (list l))))
2197 new)
2198 "List of all argument lists for Todos insertion commands.")
3f031767 2199
0e89c3fc
SB
2200(defun todos-insertion-command-name (arglist)
2201 "Generate Todos insertion command name from ARGLIST."
2202 (replace-regexp-in-string
2203 "-\\_>" ""
2204 (replace-regexp-in-string
2205 "-+" "-"
2206 (concat "todos-item-insert-"
2207 (mapconcat (lambda (e) (if e (symbol-name e))) arglist "-")))))
d04d6b95 2208
0e89c3fc
SB
2209(defvar todos-insertion-commands-names
2210 (mapcar (lambda (l)
2211 (todos-insertion-command-name l))
2212 todos-insertion-commands-args)
2213 "List of names of Todos insertion commands.")
d04d6b95 2214
0e89c3fc
SB
2215(defmacro todos-define-insertion-command (&rest args)
2216 (let ((name (intern (todos-insertion-command-name args)))
2217 (arg0 (nth 0 args))
2218 (arg1 (nth 1 args))
2219 (arg2 (nth 2 args))
2220 (arg3 (nth 3 args))
2221 (arg4 (nth 4 args)))
2222 `(defun ,name (&optional arg)
3af3cd0b 2223 "Todos item insertion command generated from ARGS."
0e89c3fc
SB
2224 (interactive)
2225 (todos-insert-item arg ',arg0 ',arg1 ',arg2 ',arg3 ',arg4))))
3f031767 2226
0e89c3fc
SB
2227(defvar todos-insertion-commands
2228 (mapcar (lambda (c)
2229 (eval `(todos-define-insertion-command ,@c)))
2230 todos-insertion-commands-args)
2231 "List of Todos insertion commands.")
db2c5d34 2232
0e89c3fc
SB
2233(defvar todos-insertion-commands-arg-key-list
2234 '(("diary" "y" "yy")
2235 ("nonmarking" "k" "kk")
2236 ("calendar" "c" "cc")
2237 ("date" "d" "dd")
2238 ("dayname" "n" "nn")
2239 ("time" "t" "tt")
2240 ("here" "h" "h")
2241 ("region" "r" "r"))
2242 "")
db2c5d34 2243
0e89c3fc
SB
2244(defun todos-insertion-key-bindings (map)
2245 ""
2246 (dolist (c todos-insertion-commands)
2247 (let* ((key "")
2248 (cname (symbol-name c)))
2249 (mapc (lambda (l)
2250 (let ((arg (nth 0 l))
2251 (key1 (nth 1 l))
2252 (key2 (nth 2 l)))
2253 (if (string-match (concat (regexp-quote arg) "\\_>") cname)
2254 (setq key (concat key key2)))
2255 (if (string-match (concat (regexp-quote arg) ".+") cname)
2256 (setq key (concat key key1)))))
2257 todos-insertion-commands-arg-key-list)
2258 (if (string-match (concat (regexp-quote "todos-item-insert") "\\_>") cname)
2259 (setq key (concat key "i")))
2260 (define-key map key c))))
ee7412e4 2261
0e89c3fc
SB
2262(defvar todos-insertion-map
2263 (let ((map (make-keymap)))
2264 (todos-insertion-key-bindings map)
2265 map)
2266 "Keymap for Todos mode insertion commands.")
ee7412e4 2267
0e89c3fc
SB
2268;; ??FIXME: use easy-mmode-define-keymap and easy-mmode-defmap
2269(defvar todos-key-bindings
2270 `(
2271 ;; display
2272 ("Cd" . todos-display-categories) ;FIXME: Cs todos-show-categories?
2273 ;("" . todos-display-categories-alphabetically)
2274 ("H" . todos-highlight-item)
3af3cd0b 2275 ("N" . todos-hide-show-item-numbering)
78fe7289
SB
2276 ("D" . todos-hide-show-date-time)
2277 ("*" . todos-mark-unmark-item)
0e89c3fc
SB
2278 ("C*" . todos-mark-category)
2279 ("Cu" . todos-unmark-category)
2280 ("PP" . todos-print)
2281 ("PF" . todos-print-to-file)
3af3cd0b
SB
2282 ("v" . todos-hide-show-done-items)
2283 ("V" . todos-show-done-only)
0e89c3fc
SB
2284 ("As" . todos-show-archive)
2285 ("Ac" . todos-choose-archive)
2286 ("Y" . todos-diary-items)
2287 ;;("" . todos-update-filter-files)
2288 ("Fe" . todos-edit-multiline)
2289 ("Fh" . todos-highlight-item)
3af3cd0b 2290 ("Fn" . todos-hide-show-item-numbering)
78fe7289 2291 ("Fd" . todos-hide-show-date-time)
0e89c3fc
SB
2292 ("Ftt" . todos-top-priorities)
2293 ("Ftm" . todos-top-priorities-multifile)
2294 ("Fts" . todos-set-top-priorities-in-file)
2295 ("Cts" . todos-set-top-priorities-in-category)
2296 ("Fyy" . todos-diary-items)
2297 ("Fym" . todos-diary-items-multifile)
2298 ("Fxx" . todos-regexp-items)
2299 ("Fxm" . todos-regexp-items-multifile)
0e89c3fc
SB
2300 ;;("" . todos-save-top-priorities)
2301 ;; navigation
2302 ("f" . todos-forward-category)
2303 ("b" . todos-backward-category)
2304 ("j" . todos-jump-to-category)
2305 ("J" . todos-jump-to-category-other-file)
2306 ("n" . todos-forward-item)
2307 ("p" . todos-backward-item)
2308 ("S" . todos-search)
2309 ("X" . todos-clear-matches)
2310 ;; editing
2311 ("Fa" . todos-add-file)
2312 ("Ca" . todos-add-category)
2313 ("Cr" . todos-rename-category)
2314 ("Cg" . todos-merge-category)
2315 ;;("" . todos-merge-categories)
2316 ("Cm" . todos-move-category)
2317 ("Ck" . todos-delete-category)
2318 ("d" . todos-item-done)
2319 ("ee" . todos-edit-item)
2320 ("em" . todos-edit-multiline-item)
2321 ("eh" . todos-edit-item-header)
2322 ("edd" . todos-edit-item-date)
2323 ("edc" . todos-edit-item-date-from-calendar)
2324 ("edt" . todos-edit-item-date-is-today)
2325 ("et" . todos-edit-item-time)
2326 ("eyy" . todos-edit-item-diary-inclusion)
2327 ;; ("" . todos-edit-category-diary-inclusion)
2328 ("eyn" . todos-edit-item-diary-nonmarking)
2329 ;;("" . todos-edit-category-diary-nonmarking)
47011bed 2330 ("ec" . todos-done-item-add-or-edit-comment) ;FIXME: or just "c"?
0e89c3fc
SB
2331 ("i" . ,todos-insertion-map)
2332 ("k" . todos-delete-item)
2333 ("m" . todos-move-item)
2334 ("M" . todos-move-item-to-file)
2335 ;; FIXME: This binding prevents `-' from being used in a numerical prefix
2336 ;; argument without typing C-u
2337 ;; ("-" . todos-raise-item-priority)
2338 ("r" . todos-raise-item-priority)
2339 ;; ("+" . todos-lower-item-priority)
2340 ("l" . todos-lower-item-priority)
2341 ("#" . todos-set-item-priority)
2342 ("u" . todos-item-undo)
2a9e69d6 2343 ("Ad" . todos-archive-done-item) ;FIXME
0e89c3fc
SB
2344 ("AD" . todos-archive-category-done-items) ;FIXME
2345 ("Au" . todos-unarchive-items)
2346 ("AU" . todos-unarchive-category)
2347 ("s" . todos-save)
2348 ("q" . todos-quit)
2349 ([remap newline] . newline-and-indent)
2350 )
2351 "Alist pairing keys defined in Todos modes and their bindings.")
2352
2353(defvar todos-mode-map
2354 (let ((map (make-keymap)))
2355 ;; Don't suppress digit keys, so they can supply prefix arguments.
2356 (suppress-keymap map)
2357 (dolist (ck todos-key-bindings)
2358 (define-key map (car ck) (cdr ck)))
2359 map)
2360 "Todos mode keymap.")
d04d6b95 2361
58c7641d 2362;; FIXME
0e89c3fc
SB
2363(easy-menu-define
2364 todos-menu todos-mode-map "Todos Menu"
2365 '("Todos"
2366 ("Navigation"
2367 ["Next Item" todos-forward-item t]
2368 ["Previous Item" todos-backward-item t]
2369 "---"
2370 ["Next Category" todos-forward-category t]
2371 ["Previous Category" todos-backward-category t]
2372 ["Jump to Category" todos-jump-to-category t]
2373 ["Jump to Category in Other File" todos-jump-to-category-other-file t]
2374 "---"
2375 ["Search Todos File" todos-search t]
2376 ["Clear Highlighting on Search Matches" todos-category-done t])
2377 ("Display"
2378 ["List Current Categories" todos-display-categories t]
2379 ;; ["List Categories Alphabetically" todos-display-categories-alphabetically t]
2380 ["Turn Item Highlighting on/off" todos-highlight-item t]
3af3cd0b 2381 ["Turn Item Numbering on/off" todos-hide-show-item-numbering t]
78fe7289 2382 ["Turn Item Time Stamp on/off" todos-hide-show-date-time t]
3af3cd0b 2383 ["View/Hide Done Items" todos-hide-show-done-items t]
0e89c3fc
SB
2384 "---"
2385 ["View Diary Items" todos-diary-items t]
2386 ["View Top Priority Items" todos-top-priorities t]
2387 ["View Multifile Top Priority Items" todos-top-priorities-multifile t]
2388 "---"
0e89c3fc
SB
2389 ["Print Category" todos-print t])
2390 ("Editing"
2391 ["Insert New Item" todos-insert-item t]
2392 ["Insert Item Here" todos-insert-item-here t]
2393 ("More Insertion Commands")
2394 ["Edit Item" todos-edit-item t]
2395 ["Edit Multiline Item" todos-edit-multiline t]
2396 ["Edit Item Header" todos-edit-item-header t]
2397 ["Edit Item Date" todos-edit-item-date t]
2398 ["Edit Item Time" todos-edit-item-time t]
2399 "---"
2400 ["Lower Item Priority" todos-lower-item-priority t]
2401 ["Raise Item Priority" todos-raise-item-priority t]
2402 ["Set Item Priority" todos-set-item-priority t]
2403 ["Move (Recategorize) Item" todos-move-item t]
2404 ["Delete Item" todos-delete-item t]
2405 ["Undo Done Item" todos-item-undo t]
2406 ["Mark/Unmark Item for Diary" todos-toggle-item-diary-inclusion t]
2407 ["Mark/Unmark Items for Diary" todos-edit-item-diary-inclusion t]
2408 ["Mark & Hide Done Item" todos-item-done t]
2409 ["Archive Done Items" todos-archive-category-done-items t]
2410 "---"
2411 ["Add New Todos File" todos-add-file t]
2412 ["Add New Category" todos-add-category t]
2413 ["Delete Current Category" todos-delete-category t]
2414 ["Rename Current Category" todos-rename-category t]
2415 "---"
2416 ["Save Todos File" todos-save t]
2417 ["Save Top Priorities" todos-save-top-priorities t])
2418 "---"
2419 ["Quit" todos-quit t]
2420 ))
2421
2422(defvar todos-archive-mode-map
2423 (let ((map (make-sparse-keymap)))
2424 (suppress-keymap map t)
2425 ;; navigation commands
2426 (define-key map "f" 'todos-forward-category)
2427 (define-key map "b" 'todos-backward-category)
2428 (define-key map "j" 'todos-jump-to-category)
2429 (define-key map "n" 'todos-forward-item)
2430 (define-key map "p" 'todos-backward-item)
2431 ;; display commands
2432 (define-key map "C" 'todos-display-categories)
2433 (define-key map "H" 'todos-highlight-item)
3af3cd0b 2434 (define-key map "N" 'todos-hide-show-item-numbering)
78fe7289 2435 ;; (define-key map "" 'todos-hide-show-date-time)
0e89c3fc
SB
2436 (define-key map "P" 'todos-print)
2437 (define-key map "q" 'todos-quit)
2438 (define-key map "s" 'todos-save)
2439 (define-key map "S" 'todos-search)
2a9e69d6 2440 (define-key map "t" 'todos-show)
0e89c3fc
SB
2441 (define-key map "u" 'todos-unarchive-item)
2442 (define-key map "U" 'todos-unarchive-category)
2443 map)
2444 "Todos Archive mode keymap.")
2445
2446(defvar todos-edit-mode-map
2447 (let ((map (make-sparse-keymap)))
2448 (define-key map "\C-x\C-q" 'todos-edit-quit)
2449 (define-key map [remap newline] 'newline-and-indent)
2450 map)
2451 "Todos Edit mode keymap.")
2452
2453(defvar todos-categories-mode-map
2454 (let ((map (make-sparse-keymap)))
2455 (suppress-keymap map t)
2456 ;; (define-key map "a" 'todos-display-categories-alphabetically)
2457 (define-key map "c" 'todos-display-categories)
2a9e69d6
SB
2458 (define-key map "l" 'todos-lower-category-priority)
2459 (define-key map "+" 'todos-lower-category-priority)
2460 (define-key map "r" 'todos-raise-category-priority)
2461 (define-key map "-" 'todos-raise-category-priority)
0e89c3fc
SB
2462 (define-key map "n" 'forward-button)
2463 (define-key map "p" 'backward-button)
2464 (define-key map [tab] 'forward-button)
2465 (define-key map [backtab] 'backward-button)
2466 (define-key map "q" 'todos-quit)
2467 ;; (define-key map "A" 'todos-add-category)
2468 ;; (define-key map "D" 'todos-delete-category)
2469 ;; (define-key map "R" 'todos-rename-category)
2470 map)
2471 "Todos Categories mode keymap.")
2472
2473(defvar todos-filter-items-mode-map
2474 (let ((map (make-keymap)))
2475 (suppress-keymap map t)
2476 ;; navigation commands
2477 (define-key map "j" 'todos-jump-to-item)
2478 (define-key map [remap newline] 'todos-jump-to-item)
2479 (define-key map "n" 'todos-forward-item)
2480 (define-key map "p" 'todos-backward-item)
2481 (define-key map "H" 'todos-highlight-item)
3af3cd0b 2482 (define-key map "N" 'todos-hide-show-item-numbering)
78fe7289 2483 (define-key map "D" 'todos-hide-show-date-time)
0e89c3fc
SB
2484 (define-key map "P" 'todos-print)
2485 (define-key map "q" 'todos-quit)
2486 (define-key map "s" 'todos-save)
2487 ;; (define-key map "S" 'todos-save-top-priorities)
2488 ;; editing commands
2489 (define-key map "l" 'todos-lower-item-priority)
2490 (define-key map "r" 'todos-raise-item-priority)
2a9e69d6 2491 (define-key map "#" 'todos-set-item-top-priority)
0e89c3fc
SB
2492 map)
2493 "Todos Top Priorities mode keymap.")
2494
2495;; FIXME: remove when part of Emacs
2496(add-to-list 'auto-mode-alist '("\\.todo\\'" . todos-mode))
2497(add-to-list 'auto-mode-alist '("\\.toda\\'" . todos-archive-mode))
2498
2499(defun todos-modes-set-1 ()
2500 ""
2501 (set (make-local-variable 'font-lock-defaults) '(todos-font-lock-keywords t))
2502 (set (make-local-variable 'indent-line-function) 'todos-indent)
2503 (when todos-wrap-lines (funcall todos-line-wrapping-function)))
2504
2505(defun todos-modes-set-2 ()
2506 ""
2507 (add-to-invisibility-spec 'todos)
2508 (setq buffer-read-only t)
2509 (set (make-local-variable 'hl-line-range-function)
2510 (lambda() (when (todos-item-end)
2511 (cons (todos-item-start) (todos-item-end))))))
2512
2513(defun todos-modes-set-3 ()
2514 (set (make-local-variable 'todos-categories-full) nil)
3af3cd0b 2515 ;; todos-set-categories also sets todos-categories-full.
0e89c3fc
SB
2516 (set (make-local-variable 'todos-categories) (todos-set-categories))
2517 (set (make-local-variable 'todos-category-number) 1)
2518 (set (make-local-variable 'todos-first-visit) t)
2519 (add-hook 'post-command-hook 'todos-after-find-file nil t))
2520
2521(put 'todos-mode 'mode-class 'special)
2522
2523;; Autoloading isn't needed if files are identified by auto-mode-alist
2524;; ;; As calendar reads included Todos file before todos-mode is loaded.
2525;; ;;;###autoload
2526(define-derived-mode todos-mode special-mode "Todos" ()
2527 "Major mode for displaying, navigating and editing Todo lists.
2528
2529\\{todos-mode-map}"
2530 (easy-menu-add todos-menu)
2531 (todos-modes-set-1)
2532 (todos-modes-set-2)
2533 (todos-modes-set-3)
2534 ;; Initialize todos-current-todos-file.
2535 (when (member (file-truename (buffer-file-name))
2536 (funcall todos-files-function))
2537 (set (make-local-variable 'todos-current-todos-file)
2538 (file-truename (buffer-file-name))))
2539 (set (make-local-variable 'todos-first-visit) t)
2540 (set (make-local-variable 'todos-show-done-only) nil)
2a9e69d6 2541 (set (make-local-variable 'todos-categories-with-marks) nil)
0e89c3fc
SB
2542 (when todos-show-current-file
2543 (add-hook 'pre-command-hook 'todos-show-current-file nil t))
2544 ;; FIXME: works more or less, but should be tied to the defcustom
2545 (add-hook 'window-configuration-change-hook
2546 (lambda ()
2547 (setq todos-done-separator (make-string (window-width) ?_)))
2548 nil t)
2549 (add-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file nil t))
2550
2551;; FIXME: need this?
2552(defun todos-unload-hook ()
2553 ""
2554 (remove-hook 'pre-command-hook 'todos-show-current-file t)
2555 (remove-hook 'post-command-hook 'todos-after-find-file t)
2556 (remove-hook 'window-configuration-change-hook
2557 (lambda ()
2558 (setq todos-done-separator
2559 (make-string (window-width) ?_))) t)
2560 (remove-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file t))
2561
2562(put 'todos-archive-mode 'mode-class 'special)
2563
2564(define-derived-mode todos-archive-mode todos-mode "Todos-Arch" ()
2565 "Major mode for archived Todos categories.
2566
2567\\{todos-archive-mode-map}"
2568 (todos-modes-set-1)
2569 (todos-modes-set-2)
2570 (todos-modes-set-3)
2571 (set (make-local-variable 'todos-current-todos-file)
2572 (file-truename (buffer-file-name)))
2573 (set (make-local-variable 'todos-show-done-only) t))
2574
2575(defun todos-mode-external-set ()
d04d6b95 2576 ""
0e89c3fc
SB
2577 (set (make-local-variable 'todos-current-todos-file)
2578 todos-global-current-todos-file)
2579 (let ((cats (with-current-buffer (get-file-buffer todos-current-todos-file)
2580 (if todos-ignore-archived-categories
459c6e93 2581 ;; FIXME: how will this be set?
0e89c3fc
SB
2582 todos-categories-full
2583 (todos-set-categories)))))
2584 (set (make-local-variable 'todos-categories) cats)))
d04d6b95 2585
0e89c3fc
SB
2586(define-derived-mode todos-edit-mode text-mode "Todos-Ed" ()
2587 "Major mode for editing multiline Todo items.
58c7641d 2588
0e89c3fc
SB
2589\\{todos-edit-mode-map}"{
2590 (todos-modes-set-1)
2591 (todos-mode-external-set))
58c7641d 2592
0e89c3fc 2593(put 'todos-categories-mode 'mode-class 'special)
58c7641d 2594
0e89c3fc
SB
2595(define-derived-mode todos-categories-mode special-mode "Todos-Cats" ()
2596 "Major mode for displaying and editing Todos categories.
58c7641d 2597
0e89c3fc
SB
2598\\{todos-categories-mode-map}"
2599 (todos-mode-external-set))
58c7641d 2600
0e89c3fc 2601(put 'todos-filter-mode 'mode-class 'special)
58c7641d 2602
0e89c3fc
SB
2603(define-derived-mode todos-filter-items-mode special-mode "Todos-Fltr" ()
2604 "Mode for displaying and reprioritizing top priority Todos.
58c7641d 2605
0e89c3fc
SB
2606\\{todos-filter-items-mode-map}"
2607 (todos-modes-set-1)
2608 (todos-modes-set-2))
2609
2610;; FIXME: need this?
2611(defun todos-save ()
2612 "Save the current Todos file."
2613 (interactive)
2614 (save-buffer)
2615 ;; (if todos-save-top-priorities-too (todos-save-top-priorities))
2616 )
2617
2618(defun todos-quit ()
2619 "Exit the current Todos-related buffer.
2620Depending on the specific mode, this either kills the buffer or
2621buries it and restores state as needed."
2622 (interactive)
2623 (cond ((eq major-mode 'todos-categories-mode)
2624 (kill-buffer)
2625 (setq todos-descending-counts nil)
2626 (todos-show))
2627 ((eq major-mode 'todos-filter-items-mode)
2628 (kill-buffer)
2629 (todos-show))
2630 ((member major-mode (list 'todos-mode 'todos-archive-mode))
2631 ;; Have to write previously nonexistant archives to file.
2632 (unless (file-exists-p (buffer-file-name)) (todos-save))
2a9e69d6
SB
2633 ;; FIXME: make this customizable?
2634 (todos-save)
0e89c3fc
SB
2635 (bury-buffer))))
2636
2637;; ---------------------------------------------------------------------------
2638;;; Display Commands
2639
2640;;;###autoload
2641(defun todos-show (&optional solicit-file)
2642 "Visit the current Todos file and display one of its categories.
2643
2644With non-nil prefix argument SOLICIT-FILE ask for file to visit.
2645Otherwise, the first invocation of this command in a session
2646visits `todos-default-todos-file' (creating it if it does not yet
2647exist); subsequent invocations from outside of Todos mode revisit
2648this file or, if user option `todos-show-current-file' is
2649non-nil, whichever Todos file was visited last.
2650
2651The category displayed on initial invocation is the first member
2652of `todos-categories' for the current Todos file, on subsequent
2653invocations whichever category was displayed last. If
2654`todos-display-categories-first' is non-nil, then the first
2655invocation of `todos-show' displays a clickable listing of the
2656categories in the current Todos file.
2657
2658In Todos mode just the category's unfinished todo items are shown
2659by default. The done items are hidden, but typing
3af3cd0b 2660`\\[todos-hide-show-done-items]' displays them below the todo
0e89c3fc 2661items. With non-nil user option `todos-show-with-done' both todo
2a9e69d6
SB
2662and done items are always shown on visiting a category.
2663
2664If this command is invoked in Todos Archive mode, it visits the
2665corresponding Todos file, displaying the corresponding category."
3f031767 2666 (interactive "P")
2a9e69d6
SB
2667 (let* ((cat)
2668 (file (cond (solicit-file
459c6e93
SB
2669 (if (funcall todos-files-function)
2670 (todos-read-file-name "Choose a Todos file to visit: "
2671 nil t)
2672 (error "There are no Todos files")))
2673 ((eq major-mode 'todos-archive-mode)
2674 (setq cat (todos-current-category))
2675 (concat (file-name-sans-extension todos-current-todos-file)
2676 ".todo"))
2677 (t
2678 ;; FIXME: If an archive is value of
2679 ;; todos-current-todos-file, todos-show will revisit
2680 ;; rather than the corresponding todo file -- ok or make
2681 ;; it customizable?
2682 (or todos-current-todos-file
2683 (and todos-show-current-file
2684 todos-global-current-todos-file)
2685 todos-default-todos-file
2686 (todos-add-file))))))
0e89c3fc
SB
2687 (if (and todos-first-visit todos-display-categories-first)
2688 (todos-display-categories)
2689 (set-window-buffer (selected-window)
2690 (set-buffer (find-file-noselect file)))
2a9e69d6
SB
2691 ;; If called from archive file, show corresponding category in Todos
2692 ;; file, if it exists.
2693 (when (assoc cat todos-categories)
2694 (setq todos-category-number (todos-category-number cat)))
0e89c3fc 2695 ;; If no Todos file exists, initialize one.
2a9e69d6
SB
2696 (when (zerop (buffer-size))
2697 ;; Call with empty category name to get initial prompt.
2698 (setq todos-category-number (todos-add-category "")))
0e89c3fc
SB
2699 (save-excursion (todos-category-select)))
2700 (setq todos-first-visit nil)))
d04d6b95 2701
0e89c3fc
SB
2702(defun todos-display-categories ()
2703 "Display a table of the current file's categories and item counts.
2704
2705In the initial display the categories are numbered, indicating
2706their current order for navigating by \\[todos-forward-category]
2707and \\[todos-backward-category]. You can persistantly change the
2a9e69d6
SB
2708order of the category at point by typing
2709\\[todos-raise-category-priority] or
2710\\[todos-lower-category-priority].
0e89c3fc
SB
2711
2712The labels above the category names and item counts are buttons,
2713and clicking these changes the display: sorted by category name
2714or by the respective item counts (alternately descending or
2715ascending). In these displays the categories are not numbered
2a9e69d6
SB
2716and \\[todos-raise-category-priority] and
2717\\[todos-lower-category-priority] are
0e89c3fc
SB
2718disabled. (Programmatically, the sorting is triggered by passing
2719a non-nil SORTKEY argument.)
2720
2721In addition, the lines with the category names and item counts
2722are buttonized, and pressing one of these button jumps to the
2723category in Todos mode (or Todos Archive mode, for categories
2724containing only archived items, provided user option
2725`todos-ignore-archived-categories' is non-nil. These categories
2726are shown in `todos-archived-only' face."
2c173503 2727 (interactive)
0e89c3fc
SB
2728 (todos-display-categories-1)
2729 (let (sortkey)
2730 (todos-update-categories-display sortkey)))
2c173503 2731
0e89c3fc
SB
2732;; ;; FIXME: make this toggle with todos-display-categories
2733;; (defun todos-display-categories-alphabetically ()
2734;; ""
2735;; (interactive)
2736;; (todos-display-sorted 'alpha))
3f031767 2737
0e89c3fc
SB
2738;; ;; FIXME: provide key bindings for these or delete them
2739;; (defun todos-display-categories-sorted-by-todo ()
2740;; ""
2741;; (interactive)
2742;; (todos-display-sorted 'todo))
58c7641d 2743
0e89c3fc
SB
2744;; (defun todos-display-categories-sorted-by-diary ()
2745;; ""
2746;; (interactive)
2747;; (todos-display-sorted 'diary))
58c7641d 2748
0e89c3fc
SB
2749;; (defun todos-display-categories-sorted-by-done ()
2750;; ""
2751;; (interactive)
2752;; (todos-display-sorted 'done))
2753
2754;; (defun todos-display-categories-sorted-by-archived ()
2755;; ""
2756;; (interactive)
2757;; (todos-display-sorted 'archived))
2758
3af3cd0b 2759(defun todos-hide-show-item-numbering ()
0e89c3fc 2760 ""
3f031767 2761 (interactive)
3af3cd0b 2762 (todos-reset-prefix 'todos-number-priorities (not todos-number-priorities)))
3f031767 2763
3af3cd0b 2764(defun todos-hide-show-done-items ()
0e89c3fc 2765 "Show hidden or hide visible done items in current category."
2c173503 2766 (interactive)
0e89c3fc
SB
2767 (if (zerop (todos-get-count 'done (todos-current-category)))
2768 (message "There are no done items in this category.")
2769 (save-excursion
2770 (goto-char (point-min))
2771 (let ((todos-show-with-done (not (re-search-forward
2772 todos-done-string-start nil t))))
2773 (todos-category-select)))))
2774
3af3cd0b 2775(defun todos-show-done-only ()
0e89c3fc 2776 "Switch between displaying only done or only todo items."
2c173503 2777 (interactive)
0e89c3fc
SB
2778 (setq todos-show-done-only (not todos-show-done-only))
2779 (todos-category-select))
2c173503 2780
0e89c3fc 2781(defun todos-show-archive (&optional ask)
3af3cd0b
SB
2782 "Visit the archive of the current Todos category, if it exists.
2783If the category has no archived items, prompt to visit the
2784archive anyway. If there is no archive for this file or with
2785non-nil argument ASK, prompt to visit another archive.
2786
0e89c3fc
SB
2787With non-nil argument ASK prompt to choose an archive to visit;
2788see `todos-choose-archive'. The buffer showing the archive is in
2789Todos Archive mode. The first visit in a session displays the
2790first category in the archive, subsequent visits return to the
3af3cd0b 2791last category displayed." ;FIXME
0e89c3fc 2792 (interactive)
3af3cd0b
SB
2793 (let* ((cat (todos-current-category))
2794 (count (todos-get-count 'archived cat))
2795 (archive (concat (file-name-sans-extension todos-current-todos-file)
2796 ".toda"))
47011bed
SB
2797 place)
2798 (setq place (cond (ask 'other-archive)
2799 ((file-exists-p archive) 'this-archive)
2800 (t (when (y-or-n-p (concat "This file has no archive; "
2801 "visit another archive? "))
2802 'other-archive))))
2803 (when (eq place 'other-archive)
2804 (setq archive (todos-read-file-name "Choose a Todos archive: " t t)))
2805 (when (and (eq place 'this-archive) (zerop count))
2806 (setq place (when (y-or-n-p
2807 (concat "This category has no archived items;"
2808 " visit archive anyway? "))
2809 'other-cat)))
2810 (when place
3af3cd0b
SB
2811 (set-window-buffer (selected-window)
2812 (set-buffer (find-file-noselect archive)))
47011bed
SB
2813 (if (member place '(other-archive other-cat))
2814 (setq todos-category-number 1)
2815 (todos-category-number cat))
2816 (todos-category-select))))
58c7641d 2817
0e89c3fc
SB
2818(defun todos-choose-archive ()
2819 "Choose an archive and visit it."
2820 (interactive)
2821 (todos-show-archive t))
58c7641d 2822
0e89c3fc
SB
2823(defun todos-highlight-item ()
2824 "Toggle highlighting the todo item the cursor is on."
2c173503 2825 (interactive)
0e89c3fc
SB
2826 (require 'hl-line)
2827 (if hl-line-mode
2828 (hl-line-mode -1)
2829 (hl-line-mode 1)))
2830
78fe7289 2831(defun todos-hide-show-date-time () ;(&optional all)
0e89c3fc
SB
2832 "Hide or show date-time header of todo items.";; in current category.
2833;; With non-nil prefix argument ALL do this in the whole file."
2834 (interactive "P")
2835 (save-excursion
2836 (save-restriction
2837 (goto-char (point-min))
2838 (let ((ovs (overlays-in (point) (1+ (point))))
2839 ov hidden)
2840 (while ovs
2841 (setq ov (pop ovs))
2842 (if (equal (overlay-get ov 'display) "")
2843 (setq ovs nil hidden t)))
2844 ;; (when all
2845 (widen)
2846 (goto-char (point-min));)
2847 (if hidden
2848 (remove-overlays (point-min) (point-max) 'display "")
2849 (while (not (eobp))
2850 (when (re-search-forward
2851 (concat todos-date-string-start todos-date-pattern
2852 "\\( " diary-time-regexp "\\)?"
2853 (regexp-quote todos-nondiary-end) "? ")
2854 nil t)
2855 (unless (save-match-data (todos-done-item-p))
2856 (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t))
2857 (overlay-put ov 'display "")))
2858 (todos-forward-item)))))))
2859
78fe7289 2860(defun todos-mark-unmark-item (&optional n all)
0e89c3fc
SB
2861 "Mark item at point if unmarked, or unmark it if marked.
2862
2863With a positive numerical prefix argument N, change the
2864markedness of the next N items. With non-nil argument ALL, mark
2865all visible items in the category (depending on visibility, all
2866todo and done items, or just todo or just done items).
2867
2868The mark is the character \"*\" inserted in front of the item's
2869priority number or the `todos-prefix' string; if `todos-prefix'
2870is \"*\", then the mark is \"@\"."
2871 (interactive "p")
2872 (if all (goto-char (point-min)))
2873 (unless (> n 0) (setq n 1))
2874 (let ((i 0))
2875 (while (or (and all (not (eobp)))
2876 (< i n))
2877 (let* ((cat (todos-current-category))
2878 (ov (todos-marked-item-p))
2879 (marked (assoc cat todos-categories-with-marks)))
2880 (if (and ov (not all))
2881 (progn
2882 (delete-overlay ov)
2883 (if (= (cdr marked) 1) ; Deleted last mark in this category.
2884 (setq todos-categories-with-marks
2885 (assq-delete-all cat todos-categories-with-marks))
2886 (setcdr marked (1- (cdr marked)))))
2887 (when (todos-item-start)
2888 (unless (and all (todos-marked-item-p))
2889 (setq ov (make-overlay (point) (point)))
2890 (overlay-put ov 'before-string todos-item-mark)
2891 (if marked
2892 (setcdr marked (1+ (cdr marked)))
2893 (push (cons cat 1) todos-categories-with-marks))))))
2894 (todos-forward-item)
2895 (setq i (1+ i)))))
d04d6b95 2896
0e89c3fc
SB
2897(defun todos-mark-category ()
2898 "Put the \"*\" mark on all items in this category.
2899\(If `todos-prefix' is \"*\", then the mark is \"@\".)"
d04d6b95 2900 (interactive)
78fe7289 2901 (todos-mark-unmark-item 0 t))
d04d6b95 2902
0e89c3fc
SB
2903(defun todos-unmark-category ()
2904 "Remove the \"*\" mark from all items in this category.
2905\(If `todos-prefix' is \"*\", then the mark is \"@\".)"
d04d6b95 2906 (interactive)
0e89c3fc
SB
2907 (remove-overlays (point-min) (point-max) 'before-string todos-item-mark)
2908 (setq todos-categories-with-marks
2909 (delq (assoc (todos-current-category) todos-categories-with-marks)
2910 todos-categories-with-marks)))
2911
2912(defun todos-set-top-priorities-in-file ()
2913 "Set number of top priorities for this file.
2914See `todos-set-top-priorities' for more details."
d04d6b95 2915 (interactive)
0e89c3fc 2916 (todos-set-top-priorities))
d04d6b95 2917
0e89c3fc
SB
2918(defun todos-set-top-priorities-in-category ()
2919 "Set number of top priorities for this category.
2920See `todos-set-top-priorities' for more details."
3f031767 2921 (interactive)
0e89c3fc 2922 (todos-set-top-priorities t))
3f031767 2923
0e89c3fc
SB
2924(defun todos-top-priorities (&optional num)
2925 "List top priorities of each category in `todos-filter-files'.
2926Number of entries for each category is given by NUM, which
2927defaults to `todos-show-priorities'."
2928 (interactive "P")
2929 (let ((arg (if num (cons 'top num) 'top))
2930 (buf todos-top-priorities-buffer)
2931 (file todos-current-todos-file))
2932 (todos-filter-items arg)
2933 (todos-special-buffer-name buf file)))
2934
2935(defun todos-top-priorities-multifile (&optional arg)
2936 "List top priorities of each category in `todos-filter-files'.
2937
2938If the prefix argument ARG is a number, this is the maximum
2939number of top priorities to list in each category. If the prefix
2940argument is `C-u', prompt for which files to filter and use
2941`todos-show-priorities' as the number of top priorities to list
2942in each category. If the prefix argument is `C-uC-u', prompt
2943both for which files to filter and for how many top priorities to
2944list in each category."
2945 (interactive "P")
2946 (let* ((buf todos-top-priorities-buffer)
2947 files
2948 (pref (if (numberp arg)
2949 (cons 'top arg)
2950 (setq files (if (or (consp arg)
2951 (null todos-filter-files))
520d912e
SB
2952 (progn (todos-multiple-files)
2953 todos-multiple-files)
0e89c3fc
SB
2954 todos-filter-files))
2955 (if (equal arg '(16))
2956 (cons 'top (read-number
2957 "Enter number of top priorities to show: "
2958 todos-show-priorities))
2959 'top))))
2960 (todos-filter-items pref t)
2961 (todos-special-buffer-name buf files)))
2962
2963(defun todos-diary-items ()
2964 "Display todo items for diary inclusion in this Todos file."
3f031767 2965 (interactive)
0e89c3fc
SB
2966 (let ((buf todos-diary-items-buffer)
2967 (file todos-current-todos-file))
2968 (todos-filter-items 'diary)
2969 (todos-special-buffer-name buf file)))
58c7641d 2970
0e89c3fc
SB
2971(defun todos-diary-items-multifile (&optional arg)
2972 "Display todo items for diary inclusion in one or more Todos file.
2973The files are those listed in `todos-filter-files'."
2974 (interactive "P")
2975 (let ((buf todos-diary-items-buffer)
2976 (files (if (or arg (null todos-filter-files))
520d912e
SB
2977 (progn (todos-multiple-files)
2978 todos-multiple-files)
0e89c3fc
SB
2979 todos-filter-files)))
2980 (todos-filter-items 'diary t)
2981 (todos-special-buffer-name buf files)))
d04d6b95 2982
0e89c3fc
SB
2983(defun todos-regexp-items ()
2984 "Display todo items matching a user-entered regular expression.
2985The items are those in the current Todos file."
2986 (interactive)
2987 (let ((buf todos-regexp-items-buffer)
2988 (file todos-current-todos-file))
2989 (todos-filter-items 'regexp)
2990 (todos-special-buffer-name buf file)))
db2c5d34 2991
0e89c3fc
SB
2992(defun todos-regexp-items-multifile (&optional arg)
2993 "Display todo items matching a user-entered regular expression.
2994The items are those in the files listed in `todos-filter-files'."
2995 (interactive "P")
2996 (let ((buf todos-regexp-items-buffer)
2997 (files (if (or arg (null todos-filter-files))
520d912e
SB
2998 (progn (todos-multiple-files)
2999 todos-multiple-files)
0e89c3fc
SB
3000 todos-filter-files)))
3001 (todos-filter-items 'regexp t)
3002 (todos-special-buffer-name buf files)))
d04d6b95 3003
0e89c3fc
SB
3004(defun todos-print (&optional to-file)
3005 "Produce a printable version of the current Todos buffer.
3006This converts overlays and soft line wrapping and, depending on
3007the value of `todos-print-function', includes faces. With
3008non-nil argument TO-FILE write the printable version to a file;
3009otherwise, send it to the default printer."
db2c5d34 3010 (interactive)
0e89c3fc
SB
3011 (let ((buf todos-print-buffer)
3012 (header (cond
3013 ((eq major-mode 'todos-mode)
3014 (concat "Todos File: "
3015 (todos-short-file-name todos-current-todos-file)
3016 "\nCategory: " (todos-current-category)))
3017 ((eq major-mode 'todos-filter-items-mode)
3018 "Todos Top Priorities")))
3019 (prefix (propertize (concat todos-prefix " ")
3020 'face 'todos-prefix-string))
3021 (num 0)
3022 (fill-prefix (make-string todos-indent-to-here 32))
3023 (content (buffer-string))
3024 file)
3025 (with-current-buffer (get-buffer-create buf)
3026 (insert content)
3027 (goto-char (point-min))
3028 (while (not (eobp))
3029 (let ((beg (point))
3030 (end (save-excursion (todos-item-end))))
3af3cd0b 3031 (when todos-number-priorities
0e89c3fc
SB
3032 (setq num (1+ num))
3033 (setq prefix (propertize (concat (number-to-string num) " ")
3034 'face 'todos-prefix-string)))
3035 (insert prefix)
3036 (fill-region beg end))
3037 ;; Calling todos-forward-item infloops at todos-item-start due to
3038 ;; non-overlay prefix, so search for item start instead.
3039 (if (re-search-forward todos-item-start nil t)
3040 (beginning-of-line)
3041 (goto-char (point-max))))
3042 (if (re-search-backward (concat "^" (regexp-quote todos-category-done))
3043 nil t)
3044 (replace-match todos-done-separator))
3045 (goto-char (point-min))
3046 (insert header)
3047 (newline 2)
3048 (if to-file
3049 (let ((file (read-file-name "Print to file: ")))
3050 (funcall todos-print-function file))
3051 (funcall todos-print-function)))
3052 (kill-buffer buf)))
2c173503 3053
0e89c3fc
SB
3054(defun todos-print-to-file ()
3055 "Save printable version of this Todos buffer to a file."
d04d6b95 3056 (interactive)
0e89c3fc 3057 (todos-print t))
d04d6b95 3058
0e89c3fc
SB
3059(defun todos-convert-legacy-files ()
3060 "Convert legacy Todo files to the current Todos format.
3061The files `todo-file-do' and `todo-file-done' are converted and
3062saved (the latter as a Todos Archive file) with a new name in
3063`todos-files-directory'. See also the documentation string of
3064`todos-todo-mode-date-time-regexp' for further details."
3065 (interactive)
3066 (if (fboundp 'todo-mode)
3067 (require 'todo-mode)
3068 (error "Void function `todo-mode'"))
3069 ;; Convert `todo-file-do'.
3070 (if (file-exists-p todo-file-do)
3071 (let ((default "todo-do-conv")
3072 file archive-sexp)
3073 (with-temp-buffer
3074 (insert-file-contents todo-file-do)
3075 (let ((end (search-forward ")" (line-end-position) t))
3076 (beg (search-backward "(" (line-beginning-position) t)))
3077 (setq todo-categories
3078 (read (buffer-substring-no-properties beg end))))
3079 (todo-mode)
3080 (delete-region (line-beginning-position) (1+ (line-end-position)))
3081 (while (not (eobp))
3082 (cond
3083 ((looking-at (regexp-quote (concat todo-prefix todo-category-beg)))
3084 (replace-match todos-category-beg))
3085 ((looking-at (regexp-quote todo-category-end))
3086 (replace-match ""))
3087 ((looking-at (regexp-quote (concat todo-prefix " "
3088 todo-category-sep)))
3089 (replace-match todos-category-done))
3090 ((looking-at (concat (regexp-quote todo-prefix) " "
3091 todos-todo-mode-date-time-regexp " "
3092 (regexp-quote todo-initials) ":"))
3093 (todos-convert-legacy-date-time)))
3094 (forward-line))
3095 (setq file (concat todos-files-directory
3096 (read-string
3097 (format "Save file as (default \"%s\"): " default)
3098 nil nil default)
3099 ".todo"))
3100 (write-region (point-min) (point-max) file nil 'nomessage nil t))
3101 (with-temp-buffer
3102 (insert-file-contents file)
3103 (let ((todos-categories (todos-make-categories-list t)))
3104 (todos-update-categories-sexp))
3105 (write-region (point-min) (point-max) file nil 'nomessage))
3106 ;; Convert `todo-file-done'.
3107 (when (file-exists-p todo-file-done)
3108 (with-temp-buffer
3109 (insert-file-contents todo-file-done)
3110 (let ((beg (make-marker))
3111 (end (make-marker))
3112 cat cats comment item)
3113 (while (not (eobp))
3114 (when (looking-at todos-todo-mode-date-time-regexp)
3115 (set-marker beg (point))
3116 (todos-convert-legacy-date-time)
3117 (set-marker end (point))
3118 (goto-char beg)
3119 (insert "[" todos-done-string)
3120 (goto-char end)
3121 (insert "]")
3122 (forward-char)
3123 (when (looking-at todos-todo-mode-date-time-regexp)
3124 (todos-convert-legacy-date-time))
3125 (when (looking-at (concat " " (regexp-quote todo-initials) ":"))
3126 (replace-match "")))
3127 (if (re-search-forward
3128 (concat "^" todos-todo-mode-date-time-regexp) nil t)
3129 (goto-char (match-beginning 0))
3130 (goto-char (point-max)))
3131 (backward-char)
3132 (when (looking-back "\\[\\([^][]+\\)\\]")
3133 (setq cat (match-string 1))
3134 (goto-char (match-beginning 0))
3135 (replace-match ""))
3136 ;; If the item ends with a non-comment parenthesis not
3137 ;; followed by a period, we lose (but we inherit that problem
3138 ;; from todo-mode.el).
3139 (when (looking-back "(\\(.*\\)) ")
3140 (setq comment (match-string 1))
3141 (replace-match "")
3142 (insert "[" todos-comment-string ": " comment "]"))
3143 (set-marker end (point))
3144 (if (member cat cats)
3145 ;; If item is already in its category, leave it there.
3146 (unless (save-excursion
3147 (re-search-backward
3148 (concat "^" (regexp-quote todos-category-beg)
3149 "\\(.*\\)$") nil t)
3150 (string= (match-string 1) cat))
3151 ;; Else move it to its category.
3152 (setq item (buffer-substring-no-properties beg end))
3153 (delete-region beg (1+ end))
3154 (set-marker beg (point))
3155 (re-search-backward
3156 (concat "^" (regexp-quote (concat todos-category-beg cat)))
3157 nil t)
3158 (forward-line)
3159 (if (re-search-forward
3160 (concat "^" (regexp-quote todos-category-beg)
3161 "\\(.*\\)$") nil t)
3162 (progn (goto-char (match-beginning 0))
3163 (newline)
3164 (forward-line -1))
3165 (goto-char (point-max)))
3166 (insert item "\n")
3167 (goto-char beg))
3168 (push cat cats)
3169 (goto-char beg)
3170 (insert todos-category-beg cat "\n\n" todos-category-done "\n"))
3171 (forward-line))
3172 (set-marker beg nil)
3173 (set-marker end nil))
3174 (setq file (concat (file-name-sans-extension file) ".toda"))
3175 (write-region (point-min) (point-max) file nil 'nomessage nil t))
3176 (with-temp-buffer
3177 (insert-file-contents file)
3178 (let ((todos-categories (todos-make-categories-list t)))
3179 (todos-update-categories-sexp))
3180 (write-region (point-min) (point-max) file nil 'nomessage)
3181 (setq archive-sexp (read (buffer-substring-no-properties
3182 (line-beginning-position)
3183 (line-end-position)))))
3184 (setq file (concat (file-name-sans-extension file) ".todo"))
3185 ;; Update categories sexp of converted Todos file again, adding
3186 ;; counts of archived items.
3187 (with-temp-buffer
3188 (insert-file-contents file)
3189 (let ((sexp (read (buffer-substring-no-properties
3190 (line-beginning-position)
3191 (line-end-position)))))
3192 (dolist (cat sexp)
3193 (let ((archive-cat (assoc (car cat) archive-sexp)))
3194 (if archive-cat
3195 (aset (cdr cat) 3 (aref (cdr archive-cat) 2)))))
3196 (delete-region (line-beginning-position) (line-end-position))
3197 (prin1 sexp (current-buffer)))
3198 (write-region (point-min) (point-max) file nil 'nomessage)))
3199 (todos-reevaluate-defcustoms)
3200 (message "Format conversion done."))
3201 (error "No legacy Todo file exists")))
2c173503 3202
0e89c3fc
SB
3203;; ---------------------------------------------------------------------------
3204;;; Navigation Commands
3205
3206(defun todos-forward-category (&optional back)
3207 "Visit the numerically next category in this Todos file.
3208If the current category is the highest numbered, visit the first
3209category. With non-nil argument BACK, visit the numerically
3210previous category (the highest numbered one, if the current
3211category is the first)."
58c7641d 3212 (interactive)
0e89c3fc
SB
3213 (setq todos-category-number
3214 (1+ (mod (- todos-category-number (if back 2 0))
3215 (length todos-categories))))
3216 (todos-category-select)
3217 (goto-char (point-min)))
58c7641d 3218
0e89c3fc
SB
3219(defun todos-backward-category ()
3220 "Visit the numerically previous category in this Todos file.
3221If the current category is the highest numbered, visit the first
3222category."
3223 (interactive)
3224 (todos-forward-category t))
58c7641d 3225
0e89c3fc
SB
3226(defun todos-jump-to-category (&optional cat other-file)
3227 "Jump to a category in this or another Todos file.
3228
3229Programmatically, optional argument CAT provides the category
3230name. When nil (as in interactive calls), prompt for the
3231category, with TAB completion on existing categories. If a
3232non-existing category name is entered, ask whether to add a new
3233category with this name; if affirmed, add it, then jump to that
3234category. With non-nil argument OTHER-FILE, prompt for a Todos
3235file, otherwise jump within the current Todos file."
2c173503 3236 (interactive)
0e89c3fc
SB
3237 (let ((file (or (and other-file
3238 (todos-read-file-name "Choose a Todos file: " nil t))
520d912e
SB
3239 ;; Jump to archived-only Categories from Todos Categories
3240 ;; mode.
0e89c3fc
SB
3241 (and cat
3242 todos-ignore-archived-categories
3243 (zerop (todos-get-count 'todo cat))
3244 (zerop (todos-get-count 'done cat))
3245 (not (zerop (todos-get-count 'archived cat)))
3246 (concat (file-name-sans-extension
3247 todos-current-todos-file) ".toda"))
3248 todos-current-todos-file
520d912e
SB
3249 ;; If invoked from outside of Todos mode before
3250 ;; todos-show...
0e89c3fc 3251 todos-default-todos-file)))
520d912e
SB
3252 (with-current-buffer (find-file-noselect file)
3253 (and other-file (setq todos-current-todos-file file))
3254 (let ((category (or (and (assoc cat todos-categories) cat)
3255 (todos-read-category "Jump to category: "))))
3256 ;; Clean up after selecting category in Todos Categories mode.
3257 (if (string= (buffer-name) todos-categories-buffer)
3258 (kill-buffer))
3259 (if (or cat other-file)
3260 (set-window-buffer (selected-window)
3261 (set-buffer (get-file-buffer file))))
3262 (unless todos-global-current-todos-file
3263 (setq todos-global-current-todos-file todos-current-todos-file))
2a9e69d6
SB
3264 (todos-category-number category) ; (1+ (length t-c)) if new category.
3265 ;; (if (> todos-category-number (length todos-categories))
3266 ;; (setq todos-category-number (todos-add-category category)))
520d912e
SB
3267 (todos-category-select)
3268 (goto-char (point-min))))))
58c7641d 3269
0e89c3fc
SB
3270(defun todos-jump-to-category-other-file ()
3271 "Jump to a category in another Todos file.
3272The category is chosen by prompt, with TAB completion."
3273 (interactive)
3274 (todos-jump-to-category nil t))
58c7641d 3275
0e89c3fc
SB
3276(defun todos-jump-to-item ()
3277 "Jump to the file and category of the filtered item at point."
d04d6b95 3278 (interactive)
0e89c3fc
SB
3279 (let ((str (todos-item-string))
3280 (buf (current-buffer))
520d912e
SB
3281 cat file archive beg)
3282 (string-match (concat (if todos-filter-done-items
3283 (concat "\\(?:" todos-done-string-start "\\|"
3284 todos-date-string-start "\\)")
3285 todos-date-string-start)
3286 todos-date-pattern "\\(?: " diary-time-regexp "\\)?"
3287 (if todos-filter-done-items
3288 "\\]"
3289 (regexp-quote todos-nondiary-end)) "?"
3290 "\\(?4: \\[\\(?3:(archive) \\)?\\(?2:.*:\\)?"
3291 "\\(?1:.*\\)\\]\\).*$") str)
0e89c3fc
SB
3292 (setq cat (match-string 1 str))
3293 (setq file (match-string 2 str))
520d912e
SB
3294 (setq archive (string= (match-string 3 str) "(archive) "))
3295 (setq str (replace-match "" nil nil str 4))
0e89c3fc 3296 (setq file (if file
520d912e
SB
3297 (concat todos-files-directory (substring file 0 -1)
3298 (if archive ".toda" ".todo"))
3299 (if archive
3300 (concat (file-name-sans-extension
3301 todos-global-current-todos-file) ".toda")
3302 todos-global-current-todos-file)))
0e89c3fc
SB
3303 (find-file-noselect file)
3304 (with-current-buffer (get-file-buffer file)
3305 (widen)
3306 (goto-char (point-min))
3307 (re-search-forward
3308 (concat "^" (regexp-quote (concat todos-category-beg cat))) nil t)
3309 (search-forward str)
3310 (setq beg (match-beginning 0)))
3311 (kill-buffer buf)
3312 (set-window-buffer (selected-window) (set-buffer (get-file-buffer file)))
3313 (setq todos-current-todos-file file)
3314 (setq todos-category-number (todos-category-number cat))
520d912e
SB
3315 (let ((todos-show-with-done (if todos-filter-done-items t
3316 todos-show-with-done)))
3317 (todos-category-select))
0e89c3fc
SB
3318 (goto-char beg)))
3319
3320;; FIXME ? disallow prefix arg value < 1 (re-search-* allows these)
3321(defun todos-forward-item (&optional count)
3322 "Move point down to start of item with next lower priority.
3323With numerical prefix COUNT, move point COUNT items downward,"
3324 (interactive "P")
3325 (let* ((not-done (not (or (todos-done-item-p) (looking-at "^$"))))
3326 (start (line-end-position)))
3327 (goto-char start)
3328 (if (re-search-forward todos-item-start nil t (or count 1))
3329 (goto-char (match-beginning 0))
3330 (goto-char (point-max)))
3331 ;; If points advances by one from a todo to a done item, go back to the
3332 ;; space above todos-done-separator, since that is a legitimate place to
3333 ;; insert an item. But skip this space if count > 1, since that should
3334 ;; only stop on an item (FIXME: or not?)
3335 (when (and not-done (todos-done-item-p))
3336 (if (or (not count) (= count 1))
3337 (re-search-backward "^$" start t)))))
58c7641d 3338
0e89c3fc
SB
3339(defun todos-backward-item (&optional count)
3340 "Move point up to start of item with next higher priority.
3341With numerical prefix COUNT, move point COUNT items upward,"
3342 (interactive "P")
3343 (let* ((done (todos-done-item-p)))
3344 ;; FIXME ? this moves to bob if on the first item (but so does previous-line)
3345 (todos-item-start)
3346 (unless (bobp)
3347 (re-search-backward todos-item-start nil t (or count 1)))
78fe7289
SB
3348 ;; Unless this is a regexp filtered items buffer (which can contain
3349 ;; intermixed todo and done items), if points advances by one from a done
3350 ;; to a todo item, go back to the space above todos-done-separator, since
3351 ;; that is a legitimate place to insert an item. But skip this space if
3352 ;; count > 1, since that should only stop on an item (FIXME: or not?)
3353 (when (and done (not (todos-done-item-p)) (or (not count) (= count 1))
3354 (not (equal (buffer-name) todos-regexp-items-buffer)))
3355 (re-search-forward (concat "^" (regexp-quote todos-category-done)) nil t)
3356 (forward-line -1))))
0e89c3fc
SB
3357
3358;; FIXME: (i) Extend search to other Todos files. (ii) Allow navigating among
2a9e69d6
SB
3359;; hits. (But these are available in another form with
3360;; todos-regexp-items-multifile.)
0e89c3fc
SB
3361(defun todos-search ()
3362 "Search for a regular expression in this Todos file.
3363The search runs through the whole file and encompasses all and
3364only todo and done items; it excludes category names. Multiple
3365matches are shown sequentially, highlighted in `todos-search'
3366face."
58c7641d 3367 (interactive)
0e89c3fc
SB
3368 (let ((regex (read-from-minibuffer "Enter a search string (regexp): "))
3369 (opoint (point))
3370 matches match cat in-done ov mlen msg)
3371 (widen)
3372 (goto-char (point-min))
3373 (while (not (eobp))
3374 (setq match (re-search-forward regex nil t))
3375 (goto-char (line-beginning-position))
3376 (unless (or (equal (point) 1)
3377 (looking-at (concat "^" (regexp-quote todos-category-beg))))
3378 (if match (push match matches)))
3379 (forward-line))
3380 (setq matches (reverse matches))
3381 (if matches
3382 (catch 'stop
3383 (while matches
3384 (setq match (pop matches))
3385 (goto-char match)
3386 (todos-item-start)
3387 (when (looking-at todos-done-string-start)
3388 (setq in-done t))
3389 (re-search-backward (concat "^" (regexp-quote todos-category-beg)
3390 "\\(.*\\)\n") nil t)
3391 (setq cat (match-string-no-properties 1))
3392 (todos-category-number cat)
3393 (todos-category-select)
3394 (if in-done
3af3cd0b 3395 (unless todos-show-with-done (todos-hide-show-done-items)))
0e89c3fc
SB
3396 (goto-char match)
3397 (setq ov (make-overlay (- (point) (length regex)) (point)))
3398 (overlay-put ov 'face 'todos-search)
3399 (when matches
3400 (setq mlen (length matches))
3401 (if (y-or-n-p
3402 (if (> mlen 1)
3403 (format "There are %d more matches; go to next match? "
3404 mlen)
3405 "There is one more match; go to it? "))
3406 (widen)
3407 (throw 'stop (setq msg (if (> mlen 1)
3408 (format "There are %d more matches."
3409 mlen)
3410 "There is one more match."))))))
3411 (setq msg "There are no more matches."))
3412 (todos-category-select)
3413 (goto-char opoint)
3414 (message "No match for \"%s\"" regex))
3415 (when msg
3416 (if (y-or-n-p (concat msg "\nUnhighlight matches? "))
3417 (todos-clear-matches)
3418 (message "You can unhighlight the matches later by typing %s"
3419 (key-description (car (where-is-internal
3420 'todos-clear-matches))))))))
d04d6b95 3421
0e89c3fc
SB
3422(defun todos-clear-matches ()
3423 "Remove highlighting on matches found by todos-search."
3424 (interactive)
3425 (remove-overlays 1 (1+ (buffer-size)) 'face 'todos-search))
58c7641d 3426
0e89c3fc
SB
3427;; ---------------------------------------------------------------------------
3428;;; Editing Commands
58c7641d 3429
0e89c3fc
SB
3430(defun todos-add-file ()
3431 "Name and add a new Todos file.
3432Interactively, prompt for a category and display it.
3433Noninteractively, return the name of the new file."
d04d6b95 3434 (interactive)
2a9e69d6 3435 (let ((prompt (concat "Enter name of new Todos file "
0e89c3fc 3436 "(TAB or SPC to see current names): "))
459c6e93
SB
3437 file)
3438 (setq file (todos-read-file-name prompt))
0e89c3fc
SB
3439 (with-current-buffer (get-buffer-create file)
3440 (erase-buffer)
3441 (write-region (point-min) (point-max) file nil 'nomessage nil t)
3442 (kill-buffer file))
3443 (todos-reevaluate-defcustoms)
3444 (if (called-interactively-p)
3445 (progn
2a9e69d6
SB
3446 (set-window-buffer (selected-window)
3447 (set-buffer (find-file-noselect file)))
0e89c3fc
SB
3448 (setq todos-current-todos-file file)
3449 (todos-show))
3450 file)))
3451
3452(defun todos-add-category (&optional cat)
3453 "Add a new category to the current Todos file.
2a9e69d6 3454Called interactively, prompts for category name, then visits the
0e89c3fc 3455category in Todos mode. Non-interactively, argument CAT provides
2a9e69d6 3456the category name and the return value is the category number."
0e89c3fc
SB
3457 (interactive)
3458 (let* ((buffer-read-only)
0e89c3fc
SB
3459 (num (1+ (length todos-categories)))
3460 (counts (make-vector 4 0))) ; [todo diary done archived]
2a9e69d6
SB
3461 (if cat
3462 (setq cat (todos-validate-name cat 'category)) ;FIXME: need this?
3463 (setq cat (todos-read-category "Enter new category name: " nil t)))
3464 (setq todos-categories (append todos-categories (list (cons cat counts))))
3465 (if todos-categories-full
3466 (setq todos-categories-full (append todos-categories-full
3467 (list (cons cat counts)))))
3468 (widen)
3469 (goto-char (point-max))
3470 (save-excursion ; Save point for todos-category-select.
3471 (insert todos-category-beg cat "\n\n" todos-category-done "\n"))
3472 (todos-update-categories-sexp)
3473 ;; If called by command, display the newly added category, else return
3474 ;; the category number to the caller.
3475 (if (called-interactively-p 'any) ; FIXME?
3476 (progn
3477 (setq todos-category-number num)
3478 (todos-category-select))
3479 num)))
0e89c3fc
SB
3480
3481(defun todos-rename-category ()
3482 "Rename current Todos category.
3483If this file has an archive containing this category, rename the
3484category there as well."
3485 (interactive)
3486 (let* ((cat (todos-current-category))
3487 (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat))))
3488 (setq new (todos-validate-name new 'category))
3489 (let* ((ofile todos-current-todos-file)
3490 (archive (concat (file-name-sans-extension ofile) ".toda"))
3491 (buffers (append (list ofile)
3492 (unless (zerop (todos-get-count 'archived cat))
3493 (list archive)))))
3494 (dolist (buf buffers)
3495 (with-current-buffer (find-file-noselect buf)
58c7641d 3496 (let (buffer-read-only)
0e89c3fc
SB
3497 (setq todos-categories (todos-set-categories))
3498 (save-excursion
3499 (save-restriction
3500 (setcar (assoc cat todos-categories) new)
3501 (widen)
3502 (goto-char (point-min))
3503 (todos-update-categories-sexp)
3504 (re-search-forward (concat (regexp-quote todos-category-beg)
3505 "\\(" (regexp-quote cat) "\\)\n")
58c7641d 3506 nil t)
0e89c3fc 3507 (replace-match new t t nil 1)))))))
2a9e69d6 3508 (force-mode-line-update))
0e89c3fc
SB
3509 (save-excursion (todos-category-select)))
3510
3511(defun todos-delete-category (&optional arg)
3512 "Delete current Todos category provided it is empty.
3513With ARG non-nil delete the category unconditionally,
3514i.e. including all existing todo and done items."
3515 (interactive "P")
2a9e69d6
SB
3516 (let* ((file todos-current-todos-file)
3517 (cat (todos-current-category))
0e89c3fc
SB
3518 (todo (todos-get-count 'todo cat))
3519 (done (todos-get-count 'done cat))
3520 (archived (todos-get-count 'archived cat)))
2a9e69d6
SB
3521 (if (and (not arg)
3522 (or (> todo 0) (> done 0)))
3523 (message "%s" (substitute-command-keys
3524 (concat "To delete a non-empty category, "
3525 "type C-u \\[todos-delete-category].")))
3526 (when (cond ((= (length todos-categories) 1)
3527 (y-or-n-p (concat "This is the only category in this file; "
3528 "deleting it will also delete the file.\n"
3529 "Do you want to proceed? ")))
3530 ((> archived 0)
3531 (y-or-n-p (concat "This category has archived items; "
3532 "the archived category will remain\n"
3533 "after deleting the todo category. "
3534 "Do you still want to delete it\n"
3535 "(see 'todos-ignore-archived-categories' "
3536 "for another option)? ")))
3537 (t
3538 (y-or-n-p (concat "Permanently remove category \"" cat
3539 "\"" (and arg " and all its entries")
3540 "? "))))
3541 (widen)
3542 (let ((buffer-read-only)
3543 (beg (re-search-backward
3544 (concat "^" (regexp-quote (concat todos-category-beg cat))
3545 "\n") nil t))
3546 (end (if (re-search-forward
3547 (concat "\n\\(" (regexp-quote todos-category-beg)
3548 ".*\n\\)") nil t)
3549 (match-beginning 1)
3550 (point-max))))
3551 (remove-overlays beg end)
3552 (delete-region beg end)
3553 (if (= (length todos-categories) 1)
3554 ;; If deleted category was the only one, delete the file.
3555 (progn
3556 (todos-reevaluate-defcustoms)
3557 ;; Skip confirming killing the archive buffer if it has been
3558 ;; modified and not saved.
3559 (set-buffer-modified-p nil)
3560 (delete-file file)
3561 (kill-buffer)
3562 (message "Deleted Todos file %s." file))
3563 (setq todos-categories-full (delete (assoc cat todos-categories-full)
3564 todos-categories-full))
3565 (setq todos-categories (if todos-ignore-archived-categories
3566 (delete (assoc cat todos-categories)
3567 todos-categories)
3568 todos-categories-full))
3569 (todos-update-categories-sexp)
3570 (setq todos-category-number
3571 (1+ (mod todos-category-number (length todos-categories))))
3572 (todos-category-select)
3573 (goto-char (point-min))
3574 (message "Deleted category %s." cat)))))))
3f031767 3575
2a9e69d6 3576(defun todos-raise-category-priority (&optional lower)
0e89c3fc
SB
3577 "Raise priority of category point is on in Todos Categories buffer.
3578With non-nil argument LOWER, lower the category's priority."
d04d6b95 3579 (interactive)
459c6e93 3580 (let ((num todos-category-number))
d04d6b95 3581 (save-excursion
0e89c3fc
SB
3582 (forward-line 0)
3583 (skip-chars-forward " ")
3584 (setq num (number-at-point)))
3585 (when (and num (if lower
3586 (< num (length todos-categories))
3587 (> num 1)))
3588 (let* ((col (current-column))
3589 (beg (progn (forward-line (if lower 0 -1)) (point)))
3590 (num1 (progn (skip-chars-forward " ") (1- (number-at-point))))
3591 (num2 (1+ num1))
3592 (end (progn (forward-line 2) (point)))
3593 (catvec (vconcat todos-categories))
3594 (cat1-list (aref catvec num1))
3595 (cat2-list (aref catvec num2))
3596 (cat1 (car cat1-list))
3597 (cat2 (car cat2-list))
3598 buffer-read-only newcats)
3599 (delete-region beg end)
3600 (setq num1 (1+ num1))
3601 (setq num2 (1- num2))
3602 (setq num num2)
3603 (todos-insert-category-line cat2)
3604 (setq num num1)
3605 (todos-insert-category-line cat1)
3606 (aset catvec num2 (cons cat2 (cdr cat2-list)))
3607 (aset catvec num1 (cons cat1 (cdr cat1-list)))
3608 (setq todos-categories (append catvec nil))
3609 (setq newcats todos-categories)
3610 (with-current-buffer (get-file-buffer todos-current-todos-file)
3611 (setq todos-categories newcats)
3612 (todos-update-categories-sexp))
3613 (forward-line (if lower -1 -2))
3614 (forward-char col)))))
d04d6b95 3615
2a9e69d6 3616(defun todos-lower-category-priority ()
0e89c3fc 3617 "Lower priority of category point is on in Todos Categories buffer."
d04d6b95 3618 (interactive)
2a9e69d6
SB
3619 (todos-raise-category-priority t))
3620
3621(defun todos-set-category-priority ()
3622 ""
3623 (interactive)
3624 ;; FIXME
3625 )
2c173503 3626
0e89c3fc
SB
3627(defun todos-move-category ()
3628 "Move current category to a different Todos file.
3629If current category has archived items, also move those to the
3630archive of the file moved to, creating it if it does not exist."
58c7641d 3631 (interactive)
0e89c3fc
SB
3632 (when (or (> (length todos-categories) 1)
3633 (y-or-n-p (concat "This is the only category in this file; "
3634 "moving it will also delete the file.\n"
3635 "Do you want to proceed? ")))
3636 (let* ((ofile todos-current-todos-file)
3637 (cat (todos-current-category))
3638 (nfile (todos-read-file-name "Choose a Todos file: " nil t))
3639 (archive (concat (file-name-sans-extension ofile) ".toda"))
3640 (buffers (append (list ofile)
3641 (unless (zerop (todos-get-count 'archived cat))
3642 (list archive))))
3643 new)
3644 (dolist (buf buffers)
3645 (with-current-buffer (find-file-noselect buf)
3646 (widen)
3647 (goto-char (point-max))
3648 (let* ((beg (re-search-backward
3649 (concat "^"
3650 (regexp-quote (concat todos-category-beg cat)))
3651 nil t))
3652 (end (if (re-search-forward
3653 (concat "^" (regexp-quote todos-category-beg))
3654 nil t 2)
3655 (match-beginning 0)
3656 (point-max)))
3657 (content (buffer-substring-no-properties beg end))
3658 (counts (cdr (assoc cat todos-categories)))
3659 buffer-read-only)
3660 ;; Move the category to the new file. Also update or create
3661 ;; archive file if necessary.
3662 (with-current-buffer
3663 (find-file-noselect
3664 ;; Regenerate todos-archives in case there
3665 ;; is a newly created archive.
3666 (if (member buf (funcall todos-files-function t))
3667 (concat (file-name-sans-extension nfile) ".toda")
3668 nfile))
3669 (let* ((nfile-short (todos-short-file-name nfile))
3670 (prompt (concat
3671 (format "Todos file \"%s\" already has "
3672 nfile-short)
3673 (format "the category \"%s\";\n" cat)
3674 "enter a new category name: "))
3675 buffer-read-only)
3676 (widen)
3677 (goto-char (point-max))
3678 (insert content)
3679 ;; If the file moved to has a category with the same
3680 ;; name, rename the moved category.
3681 (when (assoc cat todos-categories)
3682 (unless (member (file-truename (buffer-file-name))
3683 (funcall todos-files-function t))
3684 (setq new (read-from-minibuffer prompt))
3685 (setq new (todos-validate-name new 'category))))
3686 ;; Replace old with new name in Todos and archive files.
3687 (when new
3688 (goto-char (point-max))
3689 (re-search-backward
3690 (concat "^" (regexp-quote todos-category-beg)
3691 "\\(" (regexp-quote cat) "\\)") nil t)
3692 (replace-match new nil nil nil 1)))
3693 (setq todos-categories
3694 (append todos-categories (list (cons new counts))))
3695 (todos-update-categories-sexp)
3696 ;; If archive was just created, save it to avoid "File <xyz> no
3697 ;; longer exists!" message on invoking
3698 ;; `todos-view-archived-items'. FIXME: maybe better to save
3699 ;; unconditionally?
3700 (unless (file-exists-p (buffer-file-name))
3701 (save-buffer))
3702 (todos-category-number (or new cat))
3703 (todos-category-select))
3704 ;; Delete the category from the old file, and if that was the
3705 ;; last category, delete the file. Also handle archive file
3706 ;; if necessary.
3707 (remove-overlays beg end)
3708 (delete-region beg end)
3709 (goto-char (point-min))
3710 ;; Put point after todos-categories sexp.
3711 (forward-line)
3712 (if (eobp) ; Aside from sexp, file is empty.
3713 (progn
3714 ;; Skip confirming killing the archive buffer.
3715 (set-buffer-modified-p nil)
3716 (delete-file todos-current-todos-file)
3717 (kill-buffer)
3718 (when (member todos-current-todos-file todos-files)
3719 (todos-reevaluate-defcustoms)))
2a9e69d6
SB
3720 (setq todos-categories-full (delete (assoc cat
3721 todos-categories-full)
3722 todos-categories-full))
3723 (setq todos-categories (if todos-ignore-archived-categories
3724 (delete (assoc cat todos-categories)
3725 todos-categories)
3726 todos-categories-full))
0e89c3fc
SB
3727 (todos-update-categories-sexp)
3728 (todos-category-select)))))
3729 (set-window-buffer (selected-window)
3730 (set-buffer (find-file-noselect nfile)))
3731 (todos-category-number (or new cat))
3732 (todos-category-select))))
2c173503 3733
0e89c3fc
SB
3734(defun todos-merge-category ()
3735 "Merge current category into another category in this file.
3736The current category's todo and done items are appended to the
3737chosen category's todo and done items, respectively, which
3738becomes the current category, and the category moved from is
3739deleted."
3740 (interactive)
3741 (let ((buffer-read-only nil)
3742 (cat (todos-current-category))
3743 (goal (todos-read-category "Category to merge to: " t)))
3744 (widen)
3745 ;; FIXME: check if cat has archived items and merge those too
3746 (let* ((cbeg (progn
3747 (re-search-backward
3748 (concat "^" (regexp-quote todos-category-beg)) nil t)
3749 (point)))
3750 (tbeg (progn (forward-line) (point)))
3751 (dbeg (progn
3752 (re-search-forward
3753 (concat "^" (regexp-quote todos-category-done)) nil t)
3754 (forward-line) (point)))
3755 (tend (progn (forward-line -2) (point)))
3756 (cend (progn
3757 (if (re-search-forward
3758 (concat "^" (regexp-quote todos-category-beg)) nil t)
3759 (match-beginning 0)
3760 (point-max))))
3761 (todo (buffer-substring-no-properties tbeg tend))
3762 (done (buffer-substring-no-properties dbeg cend))
3763 here)
2c173503 3764 (goto-char (point-min))
0e89c3fc
SB
3765 (re-search-forward
3766 (concat "^" (regexp-quote (concat todos-category-beg goal))) nil t)
3767 (re-search-forward
3768 (concat "^" (regexp-quote todos-category-done)) nil t)
3769 (forward-line -1)
3770 (setq here (point))
3771 (insert todo)
3772 (goto-char (if (re-search-forward
3773 (concat "^" (regexp-quote todos-category-beg)) nil t)
3774 (match-beginning 0)
3775 (point-max)))
3776 (insert done)
3777 (remove-overlays cbeg cend)
3778 (delete-region cbeg cend)
3af3cd0b
SB
3779 (todos-update-count 'todo (todos-get-count 'todo cat) goal)
3780 (todos-update-count 'done (todos-get-count 'done cat) goal)
2a9e69d6
SB
3781 (setq todos-categories-full (delete (assoc cat todos-categories-full)
3782 todos-categories-full))
3783 (setq todos-categories (if todos-ignore-archived-categories
3784 (delete (assoc cat todos-categories)
3785 todos-categories)
3786 todos-categories-full))
0e89c3fc
SB
3787 (todos-update-categories-sexp)
3788 (todos-category-number goal)
3789 (todos-category-select)
3790 ;; Put point at the start of the merged todo items.
3791 ;; FIXME: what if there are no merged todo items but only done items?
3792 (goto-char here))))
3793
3794;; FIXME
3795(defun todos-merge-categories ()
3796 ""
3797 (interactive)
3798 (let* ((cats (mapcar 'car todos-categories))
3799 (goal (todos-read-category "Category to merge to: " t))
3800 (prompt (format "Merge to %s (type C-g to finish)? " goal))
3801 (source (let ((inhibit-quit t) l)
3802 (while (not (eq last-input-event 7))
3803 (dolist (c cats)
3804 (when (y-or-n-p prompt)
3805 (push c l)
3806 (setq cats (delete c cats))))))))
3807 (widen)
3808 ))
2c173503 3809
0e89c3fc
SB
3810;; FIXME: make insertion options customizable per category?
3811;;;###autoload
3812(defun todos-insert-item (&optional arg diary nonmarking date-type time
3813 region-or-here)
3814 "Add a new Todo item to a category.
3815\(See the note at the end of this document string about key
3816bindings and convenience commands derived from this command.)
f730d273 3817
0e89c3fc
SB
3818With no (or nil) prefix argument ARG, add the item to the current
3819category; with one prefix argument (C-u), prompt for a category
3820from the current Todos file; with two prefix arguments (C-u C-u),
3821first prompt for a Todos file, then a category in that file. If
3822a non-existing category is entered, ask whether to add it to the
3823Todos file; if answered affirmatively, add the category and
3824insert the item there.
d04d6b95 3825
0e89c3fc
SB
3826When argument DIARY is non-nil, this overrides the intent of the
3827user option `todos-include-in-diary' for this item: if
3828`todos-include-in-diary' is nil, include the item in the Fancy
3829Diary display, and if it is non-nil, exclude the item from the
3830Fancy Diary display. When DIARY is nil, `todos-include-in-diary'
3831has its intended effect.
58c7641d 3832
0e89c3fc
SB
3833When the item is included in the Fancy Diary display and the
3834argument NONMARKING is non-nil, this overrides the intent of the
3835user option `todos-diary-nonmarking' for this item: if
3836`todos-diary-nonmarking' is nil, append `diary-nonmarking-symbol'
3837to the item, and if it is non-nil, omit `diary-nonmarking-symbol'.
d04d6b95 3838
0e89c3fc
SB
3839The argument DATE-TYPE determines the content of the item's
3840mandatory date header string and how it is added:
3841- If DATE-TYPE is the symbol `calendar', the Calendar pops up and
3842 when the user puts the cursor on a date and hits RET, that
3843 date, in the format set by `calendar-date-display-form',
3844 becomes the date in the header.
3845- If DATE-TYPE is the symbol `date', the header contains the date
3846 in the format set by `calendar-date-display-form', with year,
3847 month and day individually prompted for (month with tab
3848 completion).
3849- If DATE-TYPE is the symbol `dayname' the header contains a
3850 weekday name instead of a date, prompted for with tab
3851 completion.
3852- If DATE-TYPE has any other value (including nil or none) the
3853 header contains the current date (in the format set by
3854 `calendar-date-display-form').
58c7641d 3855
0e89c3fc
SB
3856With non-nil argument TIME prompt for a time string, which must
3857match `diary-time-regexp'. Typing `<return>' at the prompt
3858returns the current time, if the user option
3859`todos-always-add-time-string' is non-nil, otherwise the empty
3860string (i.e., no time string). If TIME is absent or nil, add or
3861omit the current time string according as
3862`todos-always-add-time-string' is non-nil or nil, respectively.
58c7641d 3863
0e89c3fc
SB
3864The argument REGION-OR-HERE determines the source and location of
3865the new item:
3866- If the REGION-OR-HERE is the symbol `here', prompt for the text
3867 of the new item and insert it directly above the todo item at
3868 point (hence lowering the priority of the remaining items), or
3869 if point is on the empty line below the last todo item, insert
3870 the new item there. An error is signalled if
3871 `todos-insert-item' is invoked with `here' outside of the
3872 current category.
3873- If REGION-OR-HERE is the symbol `region', use the region of the
3874 current buffer as the text of the new item, depending on the
3875 value of user option `todos-use-only-highlighted-region': if
3876 this is non-nil, then use the region only when it is
3877 highlighted; otherwise, use the region regardless of
3878 highlighting. An error is signalled if there is no region in
3879 the current buffer. Prompt for the item's priority in the
3880 category (an integer between 1 and one more than the number of
3881 items in the category), and insert the item accordingly.
3882- If REGION-OR-HERE has any other value (in particular, nil or
3883 none), prompt for the text and the item's priority, and insert
3884 the item accordingly.
58c7641d 3885
0e89c3fc
SB
3886To facilitate using these arguments when inserting a new todo
3887item, convenience commands have been defined for all admissible
78fe7289
SB
3888combinations together with mnenomic key bindings based on on the
3889name of the arguments and their order in the command's argument
3890list: diar_y_ - nonmar_k_ing - _c_alendar or _d_ate or day_n_ame
3891- _t_ime - _r_egion or _h_ere. These key combinations are
3892appended to the basic insertion key (i) and keys that allow a
3893following key must be doubled when used finally. For example,
3894`iyh' will insert a new item with today's date, marked according
3895to the DIARY argument described above, and with priority
3896according to the HERE argument; while `iyy' does the same except
3897the priority is not given by HERE but by prompting."
0e89c3fc
SB
3898;; An alternative interface for customizing key
3899;; binding is also provided with the function
3900;; `todos-insertion-bindings'." ;FIXME
3901 (interactive "P")
3902 (let ((region (eq region-or-here 'region))
3903 (here (eq region-or-here 'here)))
3904 (when region
2a9e69d6
SB
3905 (let (use-empty-active-region)
3906 (unless (and todos-use-only-highlighted-region (use-region-p))
3907 (error "There is no active region"))))
0e89c3fc
SB
3908 (let* ((buf (current-buffer))
3909 (new-item (if region
3910 ;; FIXME: or keep properties?
3911 (buffer-substring-no-properties
3912 (region-beginning) (region-end))
3913 (read-from-minibuffer "Todo item: ")))
3914 (date-string (cond
3915 ((eq date-type 'date)
3916 (todos-read-date))
3917 ((eq date-type 'dayname)
3918 (todos-read-dayname))
3919 ((eq date-type 'calendar)
3920 (setq todos-date-from-calendar t)
3921 (todos-set-date-from-calendar))
3922 (t (calendar-date-string (calendar-current-date) t t))))
3923 (time-string (or (and time (todos-read-time))
3924 (and todos-always-add-time-string
3925 (substring (current-time-string) 11 16)))))
3926 (setq todos-date-from-calendar nil)
3927 (cond ((equal arg '(16)) ; FIXME: cf. set-mark-command
3928 (todos-jump-to-category nil t)
3929 (set-window-buffer
3930 (selected-window)
3931 (set-buffer (get-file-buffer todos-global-current-todos-file))))
3932 ((equal arg '(4)) ; FIXME: just arg?
3933 (todos-jump-to-category)
3934 (set-window-buffer
3935 (selected-window)
3936 (set-buffer (get-file-buffer todos-global-current-todos-file))))
3937 (t
3938 (when (not (derived-mode-p 'todos-mode)) (todos-show))))
3939 (let (buffer-read-only)
3940 (setq new-item
3941 ;; Add date, time and diary marking as required.
3942 (concat (if (not (and diary (not todos-include-in-diary)))
3943 todos-nondiary-start
3944 (when (and nonmarking (not todos-diary-nonmarking))
3945 diary-nonmarking-symbol))
3946 date-string (unless (and time-string
3947 (string= time-string ""))
3948 (concat " " time-string))
3949 (when (not (and diary (not todos-include-in-diary)))
3950 todos-nondiary-end)
3951 " " new-item))
3952 ;; Indent newlines inserted by C-q C-j if nonspace char follows.
3953 (setq new-item (replace-regexp-in-string
3954 "\\(\n\\)[^[:blank:]]"
3955 (concat "\n" (make-string todos-indent-to-here 32))
3956 new-item nil nil 1))
3957 (if here
3958 (cond ((not (eq major-mode 'todos-mode))
3959 (error "Cannot insert a todo item here outside of Todos mode"))
3960 ((not (eq buf (current-buffer)))
3961 (error "Cannot insert an item here after changing buffer"))
3962 ((or (todos-done-item-p)
3963 ;; Point on last blank line.
3964 (save-excursion (forward-line -1) (todos-done-item-p)))
3965 (error "Cannot insert a new item in the done item section"))
3966 (t
3967 (todos-insert-with-overlays new-item)))
3968 (todos-set-item-priority new-item (todos-current-category) t))
3af3cd0b
SB
3969 (todos-update-count 'todo 1)
3970 (if (or diary todos-include-in-diary) (todos-update-count 'diary 1))
0e89c3fc 3971 (todos-update-categories-sexp)))))
d04d6b95 3972
0e89c3fc
SB
3973(defvar todos-date-from-calendar nil
3974 "Helper variable for setting item date from the Emacs Calendar.")
2c173503 3975
0e89c3fc
SB
3976(defun todos-set-date-from-calendar ()
3977 "Return string of date chosen from Calendar."
3978 (when todos-date-from-calendar
3979 (let (calendar-view-diary-initially-flag)
3980 (calendar))
3981 ;; *Calendar* is now current buffer.
3982 (local-set-key (kbd "RET") 'exit-recursive-edit)
3983 (message "Put cursor on a date and type <return> to set it.")
520d912e
SB
3984 ;; FIXME: is there a better way than recursive-edit? Use unwind-protect?
3985 ;; Check recursive-depth?
0e89c3fc
SB
3986 (recursive-edit)
3987 (setq todos-date-from-calendar
3988 (calendar-date-string (calendar-cursor-to-date t) t t))
3989 (calendar-exit)
3990 todos-date-from-calendar))
d04d6b95 3991
0e89c3fc
SB
3992(defun todos-delete-item ()
3993 "Delete at least one item in this category.
ee7412e4 3994
0e89c3fc
SB
3995If there are marked items, delete all of these; otherwise, delete
3996the item at point."
3997 (interactive)
3998 (let* ((cat (todos-current-category))
3999 (marked (assoc cat todos-categories-with-marks))
4000 (item (unless marked (todos-item-string)))
4001 (ov (make-overlay (save-excursion (todos-item-start))
4002 (save-excursion (todos-item-end))))
2a9e69d6 4003 ;; FIXME: make confirmation an option?
0e89c3fc
SB
4004 (answer (if marked
4005 (y-or-n-p "Permanently delete all marked items? ")
4006 (when item
4007 (overlay-put ov 'face 'todos-search)
4008 (y-or-n-p (concat "Permanently delete this item? ")))))
4009 (opoint (point))
4010 buffer-read-only)
4011 (when answer
4012 (and marked (goto-char (point-min)))
4013 (catch 'done
4014 (while (not (eobp))
4015 (if (or (and marked (todos-marked-item-p)) item)
4016 (progn
4017 (if (todos-done-item-p)
3af3cd0b
SB
4018 (todos-update-count 'done -1)
4019 (todos-update-count 'todo -1 cat)
4020 (and (todos-diary-item-p) (todos-update-count 'diary -1)))
0e89c3fc
SB
4021 (delete-overlay ov)
4022 (todos-remove-item)
4023 ;; Don't leave point below last item.
4024 (and item (bolp) (eolp) (< (point-min) (point-max))
4025 (todos-backward-item))
4026 (when item
4027 (throw 'done (setq item nil))))
4028 (todos-forward-item))))
4029 (when marked
4030 (remove-overlays (point-min) (point-max) 'before-string todos-item-mark)
4031 (setq todos-categories-with-marks
4032 (assq-delete-all cat todos-categories-with-marks))
4033 (goto-char opoint))
4034 (todos-update-categories-sexp)
4035 (todos-prefix-overlays))
4036 (if ov (delete-overlay ov))))
4037
4038(defun todos-edit-item ()
4039 "Edit the Todo item at point.
4040If the item consists of only one logical line, edit it in the
4041minibuffer; otherwise, edit it in Todos Edit mode."
4042 (interactive)
4043 (when (todos-item-string)
4044 (let* ((buffer-read-only)
4045 (start (todos-item-start))
4046 (item-beg (progn
4047 (re-search-forward
4048 (concat todos-date-string-start todos-date-pattern
4049 "\\( " diary-time-regexp "\\)?"
4050 (regexp-quote todos-nondiary-end) "?")
4051 (line-end-position) t)
4052 (1+ (- (point) start))))
4053 (item (todos-item-string))
4054 (multiline (> (length (split-string item "\n")) 1))
4055 (opoint (point)))
4056 (if multiline
4057 (todos-edit-multiline t)
4058 (let ((new (read-string "Edit: " (cons item item-beg))))
4059 (while (not (string-match
4060 (concat todos-date-string-start todos-date-pattern) new))
4061 (setq new (read-from-minibuffer
4062 "Item must start with a date: " new)))
4063 ;; Indent newlines inserted by C-q C-j if nonspace char follows.
4064 (setq new (replace-regexp-in-string
4065 "\\(\n\\)[^[:blank:]]"
4066 (concat "\n" (make-string todos-indent-to-here 32)) new
4067 nil nil 1))
4068 ;; If user moved point during editing, make sure it moves back.
4069 (goto-char opoint)
4070 (todos-remove-item)
4071 (todos-insert-with-overlays new)
4072 (move-to-column item-beg))))))
3f031767 4073
0e89c3fc
SB
4074(defun todos-edit-multiline-item ()
4075 "Edit current Todo item in Todos Edit mode.
4076Use of newlines invokes `todos-indent' to insure compliance with
4077the format of Diary entries."
4078 (interactive)
4079 (todos-edit-multiline t))
3f031767 4080
0e89c3fc
SB
4081(defun todos-edit-multiline (&optional item)
4082 ""
4083 (interactive)
4084 ;; FIXME: should there be only one live Todos Edit buffer?
4085 ;; (let ((buffer-name todos-edit-buffer))
4086 (let ((buffer-name (generate-new-buffer-name todos-edit-buffer)))
4087 (set-window-buffer
4088 (selected-window)
4089 (set-buffer (make-indirect-buffer
4090 (file-name-nondirectory todos-current-todos-file)
4091 buffer-name)))
4092 (if item
4093 (narrow-to-region (todos-item-start) (todos-item-end))
4094 (widen))
4095 (todos-edit-mode)
4096 ;; (message (concat "Type %s to check file format validity and "
4097 ;; "return to Todos mode.\n")
4098 ;; (key-description (car (where-is-internal 'todos-edit-quit))))
4099 (message "%s" (substitute-command-keys
4100 (concat "Type \\[todos-edit-quit] to check file format "
4101 "validity and return to Todos mode.\n")))))
3f031767 4102
0e89c3fc
SB
4103(defun todos-edit-quit ()
4104 "Return from Todos Edit mode to Todos mode.
d04d6b95 4105
0e89c3fc
SB
4106If the whole file was in Todos Edit mode, check before returning
4107whether the file is still a valid Todos file and if so, also
4108recalculate the Todos categories sexp, in case changes were made
4109in the number or names of categories."
4110 (interactive)
4111 ;; FIXME: worth doing this only if file was actually changed?
4112 (when (eq (buffer-size) (- (point-max) (point-min)))
4113 (when (todos-check-format)
4114 (todos-make-categories-list t)))
4115 (kill-buffer)
4116 ;; In case next buffer is not the one holding todos-current-todos-file.
4117 (todos-show))
3f031767 4118
0e89c3fc
SB
4119(defun todos-edit-item-header (&optional what)
4120 "Edit date/time header of at least one item.
2c173503 4121
0e89c3fc
SB
4122Interactively, ask whether to edit year, month and day or day of
4123the week, as well as time. If there are marked items, apply the
4124changes to all of these; otherwise, edit just the item at point.
d04d6b95 4125
0e89c3fc
SB
4126Non-interactively, argument WHAT specifies whether to set the
4127date from the Calendar or to today, or whether to edit only the
4128date or day, or only the time."
4129 (interactive)
4130 (let* ((cat (todos-current-category))
4131 (marked (assoc cat todos-categories-with-marks))
4132 (first t) ; Match only first of marked items.
4133 (todos-date-from-calendar t)
4134 ndate ntime nheader)
4135 (save-excursion
4136 (or (and marked (goto-char (point-min))) (todos-item-start))
4137 (catch 'stop
4138 (while (not (eobp))
4139 (and marked
4140 (while (not (todos-marked-item-p))
4141 (todos-forward-item)
4142 (and (eobp) (throw 'stop nil))))
4143 (re-search-forward (concat todos-date-string-start "\\(?1:"
4144 todos-date-pattern
4145 "\\)\\(?2: " diary-time-regexp "\\)?")
4146 (line-end-position) t)
4147 (let* ((odate (match-string-no-properties 1))
4148 (otime (match-string-no-properties 2))
4149 (buffer-read-only))
4150 (cond ((eq what 'today)
4151 (progn
4152 (setq ndate (calendar-date-string
4153 (calendar-current-date) t t))
4154 (replace-match ndate nil nil nil 1)))
4155 ((eq what 'calendar)
4156 (setq ndate (save-match-data (todos-set-date-from-calendar)))
4157 (replace-match ndate nil nil nil 1))
4158 (t
4159 (unless (eq what 'timeonly)
4160 (when first
4161 (setq ndate (if (save-match-data
4162 (string-match "[0-9]+" odate))
4163 (if (y-or-n-p "Change date? ")
4164 (todos-read-date)
4165 (todos-read-dayname))
4166 (if (y-or-n-p "Change day? ")
4167 (todos-read-dayname)
4168 (todos-read-date)))))
4169 (replace-match ndate nil nil nil 1))
4170 (unless (eq what 'dateonly)
4171 (when first
4172 (setq ntime (save-match-data (todos-read-time)))
4173 (when (< 0 (length ntime))
4174 (setq ntime (concat " " ntime))))
4175 (if otime
4176 (replace-match ntime nil nil nil 2)
4177 (goto-char (match-end 1))
4178 (insert ntime)))))
4179 (setq todos-date-from-calendar nil)
4180 (setq first nil))
4181 (if marked
4182 (todos-forward-item)
4183 (goto-char (point-max))))))))
58c7641d 4184
0e89c3fc
SB
4185(defun todos-edit-item-date ()
4186 "Prompt for and apply changes to current item's date."
4187 (interactive)
4188 (todos-edit-item-header 'dateonly))
58c7641d 4189
0e89c3fc
SB
4190(defun todos-edit-item-date-from-calendar ()
4191 "Prompt for changes to current item's date and apply from Calendar."
4192 (interactive)
4193 (todos-edit-item-header 'calendar))
58c7641d 4194
0e89c3fc
SB
4195(defun todos-edit-item-date-is-today ()
4196 "Set item date to today's date."
4197 (interactive)
4198 (todos-edit-item-header 'today))
4199
4200(defun todos-edit-item-time ()
4201 "Prompt For and apply changes to current item's time."
4202 (interactive)
4203 (todos-edit-item-header 'timeonly))
58c7641d 4204
0e89c3fc
SB
4205(defun todos-edit-item-diary-inclusion ()
4206 "Change diary status of one or more todo items in this category.
4207That is, insert `todos-nondiary-marker' if the candidate items
4208lack this marking; otherwise, remove it.
d04d6b95 4209
0e89c3fc
SB
4210If there are marked todo items, change the diary status of all
4211and only these, otherwise change the diary status of the item at
4212point."
4213 (interactive)
4214 (let ((buffer-read-only)
4215 (marked (assoc (todos-current-category)
4216 todos-categories-with-marks)))
4217 (catch 'stop
4218 (save-excursion
4219 (when marked (goto-char (point-min)))
4220 (while (not (eobp))
4221 (if (todos-done-item-p)
4222 (throw 'stop (message "Done items cannot be edited"))
4223 (unless (and marked (not (todos-marked-item-p)))
4224 (let* ((beg (todos-item-start))
4225 (lim (save-excursion (todos-item-end)))
4226 (end (save-excursion
4227 (or (todos-time-string-matcher lim)
4228 (todos-date-string-matcher lim)))))
4229 (if (looking-at (regexp-quote todos-nondiary-start))
4230 (progn
4231 (replace-match "")
4232 (search-forward todos-nondiary-end (1+ end) t)
4233 (replace-match "")
3af3cd0b 4234 (todos-update-count 'diary 1))
0e89c3fc
SB
4235 (when end
4236 (insert todos-nondiary-start)
4237 (goto-char (1+ end))
4238 (insert todos-nondiary-end)
3af3cd0b 4239 (todos-update-count 'diary -1)))))
0e89c3fc
SB
4240 (unless marked (throw 'stop nil))
4241 (todos-forward-item)))))
4242 (todos-update-categories-sexp)))
58c7641d 4243
0e89c3fc
SB
4244(defun todos-edit-category-diary-inclusion (arg)
4245 "Make all items in this category diary items.
4246With prefix ARG, make all items in this category non-diary
4247items."
4248 (interactive "P")
d04d6b95 4249 (save-excursion
0e89c3fc
SB
4250 (goto-char (point-min))
4251 (let ((todo-count (todos-get-count 'todo))
4252 (diary-count (todos-get-count 'diary))
4253 (buffer-read-only))
4254 (catch 'stop
d04d6b95 4255 (while (not (eobp))
0e89c3fc
SB
4256 (if (todos-done-item-p) ; We've gone too far.
4257 (throw 'stop nil)
4258 (let* ((beg (todos-item-start))
4259 (lim (save-excursion (todos-item-end)))
4260 (end (save-excursion
4261 (or (todos-time-string-matcher lim)
4262 (todos-date-string-matcher lim)))))
4263 (if arg
4264 (unless (looking-at (regexp-quote todos-nondiary-start))
4265 (insert todos-nondiary-start)
4266 (goto-char (1+ end))
4267 (insert todos-nondiary-end))
4268 (when (looking-at (regexp-quote todos-nondiary-start))
4269 (replace-match "")
4270 (search-forward todos-nondiary-end (1+ end) t)
4271 (replace-match "")))))
4272 (todos-forward-item))
4273 (unless (if arg (zerop diary-count) (= diary-count todo-count))
3af3cd0b 4274 (todos-update-count 'diary (if arg
0e89c3fc
SB
4275 (- diary-count)
4276 (- todo-count diary-count))))
4277 (todos-update-categories-sexp)))))
d04d6b95 4278
0e89c3fc
SB
4279(defun todos-edit-item-diary-nonmarking ()
4280 "Change non-marking of one or more diary items in this category.
4281That is, insert `diary-nonmarking-symbol' if the candidate items
4282lack this marking; otherwise, remove it.
d04d6b95 4283
0e89c3fc
SB
4284If there are marked todo items, change the non-marking status of
4285all and only these, otherwise change the non-marking status of
4286the item at point."
4287 (interactive)
4288 (let ((buffer-read-only)
4289 (marked (assoc (todos-current-category)
4290 todos-categories-with-marks)))
4291 (catch 'stop
4292 (save-excursion
4293 (when marked (goto-char (point-min)))
4294 (while (not (eobp))
4295 (if (todos-done-item-p)
4296 (throw 'stop (message "Done items cannot be edited"))
4297 (unless (and marked (not (todos-marked-item-p)))
4298 (todos-item-start)
4299 (unless (looking-at (regexp-quote todos-nondiary-start))
4300 (if (looking-at (regexp-quote diary-nonmarking-symbol))
4301 (replace-match "")
4302 (insert diary-nonmarking-symbol))))
4303 (unless marked (throw 'stop nil))
4304 (todos-forward-item)))))))
58c7641d 4305
0e89c3fc
SB
4306(defun todos-edit-category-diary-nonmarking (arg)
4307 "Add `diary-nonmarking-symbol' to all diary items in this category.
4308With prefix ARG, remove `diary-nonmarking-symbol' from all diary
4309items in this category."
4310 (interactive "P")
4311 (save-excursion
4312 (goto-char (point-min))
4313 (let (buffer-read-only)
4314 (catch 'stop
4315 (while (not (eobp))
4316 (if (todos-done-item-p) ; We've gone too far.
4317 (throw 'stop nil)
4318 (unless (looking-at (regexp-quote todos-nondiary-start))
4319 (if arg
4320 (when (looking-at (regexp-quote diary-nonmarking-symbol))
4321 (replace-match ""))
4322 (unless (looking-at (regexp-quote diary-nonmarking-symbol))
4323 (insert diary-nonmarking-symbol))))
4324 (todos-forward-item)))))))
58c7641d 4325
0e89c3fc
SB
4326(defun todos-raise-item-priority (&optional lower)
4327 "Raise priority of current item by moving it up by one item.
4328With non-nil argument LOWER lower item's priority."
4329 (interactive)
2a9e69d6
SB
4330 (unless (or (todos-done-item-p) ; Can't reprioritize done items.
4331 ;; Can't raise or lower todo item when it's the only one.
4332 (< (todos-get-count 'todo) 2)
0e89c3fc 4333 ;; Point is between todo and done items.
2a9e69d6
SB
4334 (looking-at "^$")
4335 ;; Can't lower final todo item.
4336 (and lower
0e89c3fc 4337 (save-excursion
0e89c3fc 4338 (todos-forward-item)
2a9e69d6
SB
4339 (looking-at "^$")))
4340 ;; Can't reprioritize filtered items other than Top Priorities.
4341 (and (eq major-mode 'todos-filter-items-mode)
4342 (not (string-match (regexp-quote todos-top-priorities-buffer)
4343 (buffer-name)))))
4344 (let ((item (todos-item-string))
4345 (marked (todos-marked-item-p))
4346 buffer-read-only)
4347 ;; In Top Priorities buffer, an item's priority can be changed
4348 ;; wrt items in another category, but not wrt items in the same
4349 ;; category.
4350 (when (eq major-mode 'todos-filter-items-mode)
4351 (let* ((regexp (concat todos-date-string-start todos-date-pattern
4352 "\\( " diary-time-regexp "\\)?"
4353 (regexp-quote todos-nondiary-end)
4354 "?\\(?1: \\[\\(.+:\\)?.+\\]\\)"))
4355 (cat1 (save-excursion
4356 (re-search-forward regexp nil t)
4357 (match-string 1)))
4358 (cat2 (save-excursion
4359 (if lower
4360 (todos-forward-item)
4361 (todos-backward-item))
4362 (re-search-forward regexp nil t)
4363 (match-string 1))))
4364 (if (string= cat1 cat2)
4365 (error
4366 (concat "Cannot reprioritize items in the same "
4367 "category in this mode, only in Todos mode")))))
4368 (todos-remove-item)
4369 (if lower (todos-forward-item) (todos-backward-item))
4370 (todos-insert-with-overlays item)
4371 ;; If item was marked, retore the mark.
4372 (and marked (overlay-put (make-overlay (point) (point))
4373 'before-string todos-item-mark)))))
3f031767 4374
0e89c3fc
SB
4375(defun todos-lower-item-priority ()
4376 "Lower priority of current item by moving it down by one item."
4377 (interactive)
4378 (todos-raise-item-priority t))
ee7412e4 4379
0e89c3fc 4380;; FIXME: incorporate todos-(raise|lower)-item-priority ?
0e89c3fc
SB
4381(defun todos-set-item-priority (item cat &optional new)
4382 "Set todo ITEM's priority in category CAT, moving item as needed.
4383Interactively, the item and the category are the current ones,
4384and the priority is a number between 1 and the number of items in
4385the category. Non-interactively with argument NEW, the lowest
4386priority is one more than the number of items in CAT."
4387 (interactive (list (todos-item-string) (todos-current-category)))
4388 (unless (called-interactively-p t)
4389 (todos-category-number cat)
4390 (todos-category-select))
4391 (let* ((todo (todos-get-count 'todo cat))
4392 (maxnum (if new (1+ todo) todo))
4393 (buffer-read-only)
4394 priority candidate prompt)
4395 (unless (zerop todo)
4396 (while (not priority)
4397 (setq candidate
4398 (string-to-number (read-from-minibuffer
4399 (concat prompt
4400 (format "Set item priority (1-%d): "
4401 maxnum)))))
4402 (setq prompt
4403 (when (or (< candidate 1) (> candidate maxnum))
4404 (format "Priority must be an integer between 1 and %d.\n"
4405 maxnum)))
4406 (unless prompt (setq priority candidate)))
4407 ;; Interactively, just relocate the item within its category.
4408 (when (called-interactively-p) (todos-remove-item))
4409 (goto-char (point-min))
4410 (unless (= priority 1) (todos-forward-item (1- priority))))
4411 (todos-insert-with-overlays item)))
ee7412e4 4412
2a9e69d6
SB
4413(defun todos-set-item-top-priority ()
4414 "Set this item's priority in the Top Priorities display.
4415Reprioritizing items that belong to the same category is not
4416allowed; this is reserved for Todos mode."
4417 (interactive)
4418 (when (string-match (regexp-quote todos-top-priorities-buffer) (buffer-name))
4419 (let* ((count 0)
4420 (item (todos-item-string))
4421 (end (todos-item-end))
4422 (beg (todos-item-start))
4423 (regexp (concat todos-date-string-start todos-date-pattern
4424 "\\(?: " diary-time-regexp "\\)?"
4425 (regexp-quote todos-nondiary-end)
4426 "?\\(?1: \\[\\(?:.+:\\)?.+\\]\\)"))
4427 (cat (when (looking-at regexp) (match-string 1)))
4428 buffer-read-only current priority candidate prompt new)
4429 (save-excursion
4430 (goto-char (point-min))
4431 (while (not (eobp))
4432 (setq count (1+ count))
4433 (when (string= item (todos-item-string))
4434 (setq current count))
4435 (todos-forward-item)))
4436 (unless (zerop count)
4437 (while (not priority)
4438 (setq candidate
4439 (string-to-number (read-from-minibuffer
4440 (concat prompt
4441 (format "Set item priority (1-%d): "
4442 count)))))
4443 (setq prompt
4444 (when (or (< candidate 1) (> candidate count))
4445 (format "Priority must be an integer between 1 and %d.\n"
4446 count)))
4447 (unless prompt (setq priority candidate)))
4448 (goto-char (point-min))
4449 (unless (= priority 1) (todos-forward-item (1- priority)))
4450 (setq new (point-marker))
4451 (if (or (and (< priority current)
4452 (todos-item-end)
4453 (save-excursion (search-forward cat beg t)))
4454 (and (> priority current)
4455 (save-excursion (search-backward cat end t))))
4456 (progn
4457 (set-marker new nil)
4458 (goto-char beg)
4459 (error (concat "Cannot reprioritize items in the same category "
4460 "in this mode, only in Todos mode")))
4461 (goto-char beg)
4462 (todos-remove-item)
4463 (goto-char new)
4464 (todos-insert-with-overlays item)
4465 (set-marker new nil))))))
4466
0e89c3fc
SB
4467(defun todos-move-item (&optional file)
4468 "Move at least one todo item to another category.
58c7641d 4469
0e89c3fc
SB
4470If there are marked items, move all of these; otherwise, move
4471the item at point.
58c7641d 4472
0e89c3fc
SB
4473With non-nil argument FILE, first prompt for another Todos file and
4474then a category in that file to move the item or items to.
58c7641d 4475
0e89c3fc
SB
4476If the chosen category is not one of the existing categories,
4477then it is created and the item(s) become(s) the first
4478entry/entries in that category."
4479 (interactive)
4480 (unless (or (todos-done-item-p)
4481 ;; Point is between todo and done items.
4482 (looking-at "^$"))
4483 (let* ((buffer-read-only)
4484 (file1 todos-current-todos-file)
4485 (cat1 (todos-current-category))
4486 (marked (assoc cat1 todos-categories-with-marks))
4487 (num todos-category-number)
4488 (item (todos-item-string))
4489 (diary-item (todos-diary-item-p))
4490 (omark (save-excursion (todos-item-start) (point-marker)))
4491 (file2 (if file
4492 (todos-read-file-name "Choose a Todos file: " nil t)
4493 file1))
4494 (count 0)
4495 (count-diary 0)
4496 cat2 nmark)
4497 (set-buffer (find-file-noselect file2))
4498 (setq cat2 (let* ((pl (if (and marked (> (cdr marked) 1)) "s" ""))
4499 (name (todos-read-category
4500 (concat "Move item" pl " to category: ")))
4501 (prompt (concat "Choose a different category than "
4502 "the current one\n(type `"
4503 (key-description
4504 (car (where-is-internal
4505 'todos-set-item-priority)))
4506 "' to reprioritize item "
4507 "within the same category): ")))
4508 (while (equal name cat1)
4509 (setq name (todos-read-category prompt)))
4510 name))
4511 (set-buffer (get-file-buffer file1))
4512 (if marked
4513 (progn
4514 (setq item nil)
4515 (goto-char (point-min))
4516 (while (not (eobp))
4517 (when (todos-marked-item-p)
4518 (setq item (concat item (todos-item-string) "\n"))
4519 (setq count (1+ count))
4520 (when (todos-diary-item-p)
4521 (setq count-diary (1+ count-diary))))
4522 (todos-forward-item))
4523 ;; Chop off last newline.
4524 (setq item (substring item 0 -1)))
4525 (setq count 1)
4526 (when (todos-diary-item-p) (setq count-diary 1)))
4527 (set-window-buffer (selected-window)
4528 (set-buffer (find-file-noselect file2)))
4529 (unless (assoc cat2 todos-categories) (todos-add-category cat2))
4530 (todos-set-item-priority item cat2 t)
4531 (setq nmark (point-marker))
3af3cd0b
SB
4532 (todos-update-count 'todo count)
4533 (todos-update-count 'diary count-diary)
0e89c3fc
SB
4534 (todos-update-categories-sexp)
4535 (with-current-buffer (get-file-buffer file1)
4536 (save-excursion
4537 (save-restriction
4538 (widen)
4539 (goto-char omark)
4540 (if marked
4541 (let (beg end)
4542 (setq item nil)
4543 (re-search-backward
4544 (concat "^" (regexp-quote todos-category-beg)) nil t)
4545 (forward-line)
4546 (setq beg (point))
4547 (re-search-forward
4548 (concat "^" (regexp-quote todos-category-done)) nil t)
4549 (setq end (match-beginning 0))
4550 (goto-char beg)
4551 (while (< (point) end)
4552 (if (todos-marked-item-p)
4553 (todos-remove-item)
4554 (todos-forward-item))))
4555 (todos-remove-item))))
3af3cd0b
SB
4556 (todos-update-count 'todo (- count) cat1)
4557 (todos-update-count 'diary (- count-diary) cat1)
0e89c3fc
SB
4558 (todos-update-categories-sexp))
4559 (set-window-buffer (selected-window)
4560 (set-buffer (find-file-noselect file2)))
4561 (setq todos-category-number (todos-category-number cat2))
4562 (todos-category-select)
4563 (goto-char nmark))))
58c7641d 4564
0e89c3fc
SB
4565(defun todos-move-item-to-file ()
4566 "Move the current todo item to a category in another Todos file."
58c7641d 4567 (interactive)
0e89c3fc 4568 (todos-move-item t))
58c7641d 4569
0e89c3fc
SB
4570(defun todos-move-item-to-diary ()
4571 "Move one or more items in current category to the diary file.
58c7641d 4572
0e89c3fc
SB
4573If there are marked items, move all of these; otherwise, move
4574the item at point."
4575 (interactive)
4576 ;; FIXME
4577 )
58c7641d 4578
0e89c3fc
SB
4579;; FIXME: make adding date customizable, and make this and time customization
4580;; overridable via double prefix arg ??
4581(defun todos-item-done (&optional arg)
4582 "Tag at least one item in this category as done and hide it.
4583
4584With prefix argument ARG prompt for a comment and append it to
4585the done item; this is only possible if there are no marked
4586items. If there are marked items, tag all of these with
4587`todos-done-string' plus the current date and, if
4588`todos-always-add-time-string' is non-nil, the current time;
4589otherwise, just tag the item at point. Items tagged as done are
4590relocated to the category's (by default hidden) done section."
4591 (interactive "P")
4592 (let* ((cat (todos-current-category))
4593 (marked (assoc cat todos-categories-with-marks)))
4594 (unless (or (todos-done-item-p)
4595 (and (looking-at "^$") (not marked)))
4596 (let* ((date-string (calendar-date-string (calendar-current-date) t t))
4597 (time-string (if todos-always-add-time-string
4598 (concat " " (substring (current-time-string) 11 16))
4599 ""))
4600 (done-prefix (concat "[" todos-done-string date-string time-string
4601 "] "))
4602 (comment (and arg (not marked) (read-string "Enter a comment: ")))
4603 (item-count 0)
4604 (diary-count 0)
4605 item done-item
4606 (buffer-read-only))
4607 (and marked (goto-char (point-min)))
4608 (catch 'done
4609 (while (not (eobp))
4610 (if (or (not marked) (and marked (todos-marked-item-p)))
4611 (progn
4612 (setq item (todos-item-string))
4613 (setq done-item (cond (marked
4614 (concat done-item done-prefix item "\n"))
4615 (comment
4616 (concat done-prefix item " ["
4617 todos-comment-string
4618 ": " comment "]"))
4619 (t
4620 (concat done-prefix item))))
4621 (setq item-count (1+ item-count))
4622 (when (todos-diary-item-p)
4623 (setq diary-count (1+ diary-count)))
4624 (todos-remove-item)
4625 (unless marked (throw 'done nil)))
4626 (todos-forward-item))))
4627 (when marked
4628 ;; Chop off last newline of done item string.
4629 (setq done-item (substring done-item 0 -1))
4630 (remove-overlays (point-min) (point-max) 'before-string todos-item-mark)
4631 (setq todos-categories-with-marks
4632 (assq-delete-all cat todos-categories-with-marks)))
4633 (save-excursion
4634 (widen)
4635 (re-search-forward
4636 (concat "^" (regexp-quote todos-category-done)) nil t)
4637 (forward-char)
4638 (insert done-item "\n"))
3af3cd0b
SB
4639 (todos-update-count 'todo (- item-count))
4640 (todos-update-count 'done item-count)
4641 (todos-update-count 'diary (- diary-count))
0e89c3fc
SB
4642 (todos-update-categories-sexp)
4643 (save-excursion (todos-category-select))))))
4644
47011bed
SB
4645(defun todos-done-item-add-or-edit-comment ()
4646 "Add a comment to this done item or edit an existing comment."
0e89c3fc
SB
4647 (interactive)
4648 (when (todos-done-item-p)
47011bed
SB
4649 (let ((item (todos-item-string))
4650 (end (save-excursion (todos-item-end)))
4651 comment buffer-read-only)
4652 (save-excursion
4653 (todos-item-start)
4654 (if (re-search-forward (concat " \\["
4655 (regexp-quote todos-comment-string)
4656 ": \\([^]]+\\)\\]") end t)
4657 (progn
4658 (setq comment (read-string "Edit comment: "
4659 (cons (match-string 1) 1)))
4660 (replace-match comment nil nil nil 1))
4661 (setq comment (read-string "Enter a comment: "))
4662 (todos-item-end)
4663 (insert " [" todos-comment-string ": " comment "]"))))))
58c7641d 4664
0e89c3fc
SB
4665;; FIXME: implement this or done item editing?
4666(defun todos-uncomment-done-item ()
4667 ""
4668 )
58c7641d 4669
0e89c3fc
SB
4670;; FIXME: delete comment from restored item or just leave it up to user?
4671(defun todos-item-undo ()
4672 "Restore this done item to the todo section of this category."
4673 (interactive)
4674 (when (todos-done-item-p)
4675 (let* ((buffer-read-only)
4676 (done-item (todos-item-string))
4677 (opoint (point))
4678 (orig-mrk (progn (todos-item-start) (point-marker)))
4679 ;; Find the end of the date string added upon making item done.
4680 (start (search-forward "] "))
4681 (item (buffer-substring start (todos-item-end)))
4682 undone)
4683 (todos-remove-item)
4684 ;; If user cancels before setting new priority, then restore everything.
4685 (unwind-protect
4686 (progn
4687 (todos-set-item-priority item (todos-current-category) t)
4688 (setq undone t)
3af3cd0b
SB
4689 (todos-update-count 'todo 1)
4690 (todos-update-count 'done -1)
4691 (and (todos-diary-item-p) (todos-update-count 'diary 1))
0e89c3fc
SB
4692 (todos-update-categories-sexp))
4693 (unless undone
4694 (widen)
4695 (goto-char orig-mrk)
4696 (todos-insert-with-overlays done-item)
4697 (let ((todos-show-with-done t))
4698 (todos-category-select)
4699 (goto-char opoint)))
4700 (set-marker orig-mrk nil)))))
58c7641d 4701
2a9e69d6 4702(defun todos-archive-done-item (&optional all)
0e89c3fc 4703 "Archive at least one done item in this category.
d04d6b95 4704
0e89c3fc
SB
4705If there are marked done items (and no marked todo items),
4706archive all of these; otherwise, with non-nil argument ALL,
4707archive all done items in this category; otherwise, archive the
4708done item at point.
d04d6b95 4709
0e89c3fc
SB
4710If the archive of this file does not exist, it is created. If
4711this category does not exist in the archive, it is created."
4712 (interactive)
2a9e69d6
SB
4713 ;; (when (not (member (buffer-file-name) (funcall todos-files-function t)))
4714 (when (eq major-mode 'todos-mode)
0e89c3fc
SB
4715 (if (and all (zerop (todos-get-count 'done)))
4716 (message "No done items in this category")
4717 (catch 'end
4718 (let* ((cat (todos-current-category))
4719 (tbuf (current-buffer))
4720 (marked (assoc cat todos-categories-with-marks))
4721 (afile (concat (file-name-sans-extension
4722 todos-current-todos-file) ".toda"))
4723 (archive (if (file-exists-p afile)
4724 (find-file-noselect afile t)
4725 (progn
4726 ;; todos-add-category requires an exisiting file...
4727 (with-current-buffer (get-buffer-create afile)
4728 (erase-buffer)
4729 (write-region (point-min) (point-max) afile
4730 nil 'nomessage nil t)))
4731 ;; ...but the file still lacks a categories sexp, so
4732 ;; visiting the file would barf on todos-set-categories,
4733 ;; hence we just return the buffer.
4734 (get-buffer afile)))
4735 (item (and (todos-done-item-p) (concat (todos-item-string) "\n")))
4736 (count 0)
4737 marked-items beg end all-done
4738 buffer-read-only)
4739 (cond
4740 (marked
4741 (save-excursion
4742 (goto-char (point-min))
4743 (while (not (eobp))
4744 (if (todos-marked-item-p)
4745 (if (not (todos-done-item-p))
4746 (throw 'end (message "Only done items can be archived"))
4747 (concat marked-items (todos-item-string) "\n")
4748 (setq count (1+ count)))
4749 (todos-forward-item)))))
4750 (all
4751 (if (y-or-n-p "Archive all done items in this category? ")
4752 (save-excursion
4753 (save-restriction
4754 (goto-char (point-min))
4755 (widen)
4756 (setq beg (progn
4757 (re-search-forward todos-done-string-start nil t)
4758 (match-beginning 0))
4759 end (if (re-search-forward
4760 (concat "^" (regexp-quote todos-category-beg))
4761 nil t)
4762 (match-beginning 0)
4763 (point-max))
4764 all-done (buffer-substring beg end)
4765 count (todos-get-count 'done))))
4766 (throw 'end nil))))
4767 (when (or marked all item)
4768 (with-current-buffer archive
4769 (let ((current todos-global-current-todos-file)
4770 (buffer-read-only))
4771 (widen)
4772 (goto-char (point-min))
4773 (if (progn
4774 (re-search-forward
4775 (concat "^" (regexp-quote (concat todos-category-beg cat)))
4776 nil t)
4777 (re-search-forward (regexp-quote todos-category-done) nil t))
4778 (forward-char)
4779 ;; todos-add-category uses t-c-t-f, so temporarily set it.
4780 (setq todos-current-todos-file afile)
4781 (todos-add-category cat)
4782 (goto-char (point-max)))
4783 (insert (cond (marked marked-items)
4784 (all all-done)
4785 (item)))
3af3cd0b 4786 (todos-update-count 'done (if (or marked all) count 1))
0e89c3fc
SB
4787 (todos-update-categories-sexp)
4788 ;; Save to file now (using write-region in order not to visit
47011bed 4789 ;; afile) so we can visit it later with todos-show-archive.
0e89c3fc
SB
4790 (write-region nil nil afile)
4791 (setq todos-current-todos-file current)))
4792 (with-current-buffer tbuf
4793 (cond ((or marked item)
4794 (and marked (goto-char (point-min)))
4795 (catch 'done
4796 (while (not (eobp))
4797 (if (or (and marked (todos-marked-item-p)) item)
4798 (progn
4799 (todos-remove-item)
3af3cd0b
SB
4800 (todos-update-count 'done -1)
4801 (todos-update-count 'archived 1)
0e89c3fc
SB
4802 ;; Don't leave point below last item.
4803 (and item (bolp) (eolp) (< (point-min) (point-max))
4804 (todos-backward-item))
4805 (when item
4806 (throw 'done (setq item nil))))
4807 (todos-forward-item)))))
4808 (all
4809 (remove-overlays beg end)
4810 (delete-region beg end)
3af3cd0b
SB
4811 (todos-update-count 'done (- count))
4812 (todos-update-count 'archived count)))
0e89c3fc
SB
4813 (when marked
4814 (remove-overlays (point-min) (point-max)
4815 'before-string todos-item-mark)
4816 (setq todos-categories-with-marks
4817 (assq-delete-all cat todos-categories-with-marks))
4818 (goto-char opoint))
4819 (todos-update-categories-sexp)
4820 (todos-prefix-overlays)
4821 ;; FIXME: Heisenbug: item displays mark -- but not when edebugging
4822 (remove-overlays (point-min) (point-max)
4823 'before-string todos-item-mark)))
4824 (display-buffer (find-file-noselect afile) t)
4825 ;; FIXME: how to avoid switch-to-buffer and still get tbuf above
4826 ;; afile? What about pop-to-buffer-same-window in recent trunk?
4827 (switch-to-buffer tbuf))))))
d04d6b95 4828
0e89c3fc
SB
4829(defun todos-archive-category-done-items ()
4830 "Move all done items in this category to its archive."
4831 (interactive)
2a9e69d6 4832 (todos-archive-done-item t))
d04d6b95 4833
0e89c3fc
SB
4834(defun todos-unarchive-items (&optional all)
4835 "Unarchive at least one item in this archive category.
d04d6b95 4836
0e89c3fc
SB
4837If there are marked items, unarchive all of these; otherwise,
4838with non-nil argument ALL, unarchive all items in this category;
4839otherwise, unarchive the item at point.
d04d6b95 4840
0e89c3fc
SB
4841Unarchived items are restored as done items to the corresponding
4842category in the Todos file, inserted at the end of done section.
4843If all items in the archive category were restored, the category
4844is deleted from the archive. If this was the only category in the
4845archive, the archive file is deleted."
4846 (interactive)
4847 (when (member (buffer-file-name) (funcall todos-files-function t))
4848 (catch 'end
4849 (let* ((buffer-read-only nil)
4850 (tbuf (find-file-noselect
4851 (concat (file-name-sans-extension todos-current-todos-file)
4852 ".todo") t))
4853 (cat (todos-current-category))
4854 (marked (assoc cat todos-categories-with-marks))
4855 (item (concat (todos-item-string) "\n"))
4856 (all-items (buffer-substring (point-min) (point-max)))
4857 (all-count (todos-get-count 'done))
4858 marked-items marked-count)
4859 (save-excursion
4860 (goto-char (point-min))
4861 (while (not (eobp))
4862 (when (todos-marked-item-p)
4863 (concat marked-items (todos-item-string) "\n")
4864 (setq marked-count (1+ marked-count)))
4865 (todos-forward-item)))
4866 ;; Restore items to end of category's done section and update counts.
4867 (with-current-buffer tbuf
4868 (let (buffer-read-only)
4869 (widen)
4870 (goto-char (point-min))
4871 (re-search-forward (concat "^" (regexp-quote
4872 (concat todos-category-beg cat)))
4873 nil t)
4874 (if (re-search-forward (concat "^" (regexp-quote todos-category-beg))
4875 nil t)
4876 (goto-char (match-beginning 0))
4877 (goto-char (point-max)))
4878 (cond (marked
4879 (insert marked-items)
3af3cd0b
SB
4880 (todos-update-count 'done marked-count)
4881 (todos-update-count 'archived (- marked-count)))
0e89c3fc
SB
4882 (all
4883 (if (y-or-n-p (concat "Restore this category's items "
4884 "to Todos file as done items "
4885 "and delete this category? "))
4886 (progn (insert all-items)
3af3cd0b
SB
4887 (todos-update-count 'done all-count)
4888 (todos-update-count 'archived (- all-count)))
0e89c3fc
SB
4889 (throw 'end nil)))
4890 (t
4891 (insert item)
3af3cd0b
SB
4892 (todos-update-count 'done 1)
4893 (todos-update-count 'archived -1)))
0e89c3fc
SB
4894 (todos-update-categories-sexp)))
4895 ;; Delete restored items from archive.
4896 (cond ((or marked item)
4897 (and marked (goto-char (point-min)))
4898 (catch 'done
4899 (while (not (eobp))
4900 (if (or (and marked (todos-marked-item-p)) item)
4901 (progn
4902 (todos-remove-item)
3af3cd0b 4903 (todos-update-count 'done -1)
0e89c3fc
SB
4904 ;; Don't leave point below last item.
4905 (and item (bolp) (eolp) (< (point-min) (point-max))
4906 (todos-backward-item))
4907 (when item
4908 (throw 'done (setq item nil))))
4909 (todos-forward-item)))))
4910 (all
4911 (remove-overlays (point-min) (point-max))
4912 (delete-region (point-min) (point-max))
3af3cd0b 4913 (todos-update-count 'done (- all-count))))
0e89c3fc
SB
4914 ;; If that was the last category in the archive, delete the whole file.
4915 (if (= (length todos-categories) 1)
4916 (progn
4917 (delete-file todos-current-todos-file)
4918 ;; Don't bother confirming killing the archive buffer.
4919 (set-buffer-modified-p nil)
4920 (kill-buffer))
4921 ;; Otherwise, if the archive category is now empty, delete it.
4922 (when (eq (point-min) (point-max))
4923 (widen)
4924 (let ((beg (re-search-backward
4925 (concat "^" (regexp-quote todos-category-beg) cat)
4926 nil t))
4927 (end (if (re-search-forward
4928 (concat "^" (regexp-quote todos-category-beg))
4929 nil t 2)
4930 (match-beginning 0)
4931 (point-max))))
4932 (remove-overlays beg end)
4933 (delete-region beg end)
4934 (setq todos-categories (delete (assoc cat todos-categories)
4935 todos-categories))
2a9e69d6
SB
4936 (setq todos-categories (if todos-ignore-archived-categories
4937 (delete (assoc cat todos-categories)
4938 todos-categories)
4939 todos-categories-full))
0e89c3fc
SB
4940 (todos-update-categories-sexp))))
4941 ;; Visit category in Todos file and show restored done items.
4942 (let ((tfile (buffer-file-name tbuf))
4943 (todos-show-with-done t))
4944 (set-window-buffer (selected-window)
4945 (set-buffer (find-file-noselect tfile)))
4946 (todos-category-number cat)
4947 (todos-show)
4948 (message "Items unarchived."))))))
58c7641d 4949
0e89c3fc
SB
4950(defun todos-unarchive-category ()
4951 "Unarchive all items in this category. See `todos-unarchive-items'."
4952 (interactive)
4953 (todos-unarchive-items t))
3f031767
SB
4954
4955(provide 'todos)
4956
3f031767 4957;;; todos.el ends here
58c7641d 4958
520d912e
SB
4959
4960;; ---------------------------------------------------------------------------
4961;;; Addition to calendar.el
4962
4963;; FIXME: autoload when key-binding is defined in calendar.el
4964(defun todos-insert-item-from-calendar ()
4965 ""
4966 (interactive)
4967 ;; FIXME: todos-current-todos-file is nil here, better to solicit Todos
4968 ;; file? todos-global-current-todos-file is nil if no Todos file has been
4969 ;; visited
4970 (pop-to-buffer (file-name-nondirectory todos-global-current-todos-file))
4971 (todos-show)
4972 ;; FIXME: this now calls todos-set-date-from-calendar
4973 (todos-insert-item t 'calendar))
4974
4975;; FIXME: calendar is loaded before todos
4976;; (add-hook 'calendar-load-hook
4977 ;; (lambda ()
4978(define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);))
4979
0e89c3fc 4980;; ---------------------------------------------------------------------------
58c7641d
SB
4981;;; necessitated adaptations to diary-lib.el
4982
4983;; (defun diary-goto-entry (button)
4984;; "Jump to the diary entry for the BUTTON at point."
4985;; (let* ((locator (button-get button 'locator))
4986;; (marker (car locator))
4987;; markbuf file opoint)
4988;; ;; If marker pointing to diary location is valid, use that.
4989;; (if (and marker (setq markbuf (marker-buffer marker)))
4990;; (progn
4991;; (pop-to-buffer markbuf)
4992;; (goto-char (marker-position marker)))
4993;; ;; Marker is invalid (eg buffer has been killed, as is the case with
4994;; ;; included diary files).
4995;; (or (and (setq file (cadr locator))
4996;; (file-exists-p file)
4997;; (find-file-other-window file)
4998;; (progn
4999;; (when (eq major-mode (default-value 'major-mode)) (diary-mode))
5000;; (when (eq major-mode 'todos-mode) (widen))
5001;; (goto-char (point-min))
5002;; (when (re-search-forward (format "%s.*\\(%s\\)"
5003;; (regexp-quote (nth 2 locator))
5004;; (regexp-quote (nth 3 locator)))
5005;; nil t)
5006;; (goto-char (match-beginning 1))
5007;; (when (eq major-mode 'todos-mode)
5008;; (setq opoint (point))
5009;; (re-search-backward (concat "^"
5010;; (regexp-quote todos-category-beg)
5011;; "\\(.*\\)\n")
5012;; nil t)
5013;; (todos-category-number (match-string 1))
5014;; (todos-category-select)
5015;; (goto-char opoint)))))
5016;; (message "Unable to locate this diary entry")))))