aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Berman2010-05-25 01:19:50 +0100
committerStephen Berman2010-05-25 01:19:50 +0100
commit2c173503dcb83660571ab5abe244cc559eb61596 (patch)
tree08802bec7cfcd727be6560ac4fa1d496c99b3256
parentb28025eddc48b2a17a0b1c011ef97eea77917acc (diff)
downloademacs-2c173503dcb83660571ab5abe244cc559eb61596.tar.gz
emacs-2c173503dcb83660571ab5abe244cc559eb61596.zip
* calendar/todos.el: Comment out calendar require, since diary-lib
requires calendar. Rearrange file to group definitions according to their use (types of commands, internal functions, etc.) (todos-file-done, todos-insert-threshold, todos-remove-separator) (todos-date-string, todos-time-string, todos-check-overlay) (todos-show-paren-hack, todos-file-item, todos-more-important-p): Comment out. (todos-current-date, todos-item-end-overlays) (todos-list-categories): Remove. (todos-item-end): Remove (the variable, not the function). (todos-item-overlays): Rename to todos-prefix-overlays and adjust callers. (todos-prefix-overlays): Rename from todos-item-overlays. (todos-done-separator, todos-done-string, todos-show-with-done) (todos-files, todos-archive-file, todos-categories-buffer) (todos-archived-categories-buffer, todos-wrap-lines) (todos-line-wrapping-function): New defcustoms. (todos-done, todos-done-sep): New faces. (todos-done-face, todos-done-sep-face): Corresponding new variables. (todos-search-string, todos-date-nodayname-pattern) (todos-dayname-date-pattern, todos-date-pattern): New variables. (todos-done-string-match, todos-category-string-match) (todos-check-format, todos-wrap-and-indent, todos-reset-separator) (todos-current-category, todos-count-items-in-category) (todos-done-item-p, todos-categories-alist, todos-count-all-items) (todos-longest-category-name-length): New functions. (todos-categories-list): New buffer-specific function replacing todos-list-categories. (todos-toggle-item-numbering, todos-toggle-view-done-items) (todos-search, todos-view-archive, todos-diary-items) (todos-toggle-display-date-time, todos-insert-item-no-time) (todos-insert-item-ask-date, todos-insert-item-for-diary) (todos-insert-item-from-calendar, todos-edit-quit) (todos-change-date, todos-item-done, todos-archive-done-items) (todos-item-undo): New commands. (todos-archive-mode): New mode. (todos-archive-mode-map, todos-edit-mode-map): New keymaps. (todos-category-beg): Change value. (todos-number-prefix): Change default value. (todos-edit-buffer): Change from defvar to defcustom. (todos-font-lock-keywords): Use todos-done-string-match and todos-category-string-match. (todos-backward-item, todos-forward-item): Use todos-done-string and todos-date-pattern. (todos-display-categories): Reimplement using buttons from button.el instead of widgets. (todos-top-priorities): Use with-current-buffer; take done items into account; ensure buffers gets fontified. (todos-add-category): Ensure new category does not begin with empty lines. (todos-jump-to-category): Use todos-category-select instead of todos-show. (todos-rename-category): Prompt for new name in body instead of in interactive spec. (todos-insert-item): Don't insert in done items section of category. Add two optional arguments to control insertion: (i) to insert near point without prompting for priority; (ii) to use defaults for date and time strings, to prompt for these, or to choose date from the Calendar. (todos-insert-item-here): Reimplement using todos-insert-item. (todos-delete-item): Don't move point after deleting last item. (todos-raise-item, todos-lower-item): Take done items into account. (todos-move-item): Don't move done items; update item numbering; restore if user quits before inserting moved item. (todos-print): Prompt for confirmation to print. (todos-reset-prefix): Search backward from end of file instead of forward from top. (todos-jump-to-category-noninteractively): Take Todos archive into account. (todos-category-select): Show or hide done items according to todos-show-with-done; if shown, coordinate separator and prefix overlays. (todos-add-item-non-interactively): Replace binary insertion algorithm with prompting for numerical priority. (todos-insert-with-overlays): Remove use of variable todos-item-end. (todos-item-start): Take done items into account; use todos-date-pattern. (todos-item-end): Reimplement using todos-forward-item. (todos-remove-item): Reimplement using todos-forward-item and todos-backward-item; redo overlay handling. (todos-mode-map): Add some new key bindings and change numerous existing bindings; use "i" as prefix key for item insertion commands. (todos-mode): Use todos-wrap-lines and delegate word-wrap and wrap-prefix settings to todos-wrap-and-indent; add to invisibility spec; set buffer-read-only to t and consequently let-bind this variable in all Todos commands that change buffer content. (todos-edit-mode): Make an indepent mode, not derived from text-mode. (todos-save): Don't save top priorities buffer. (todos-show): Make a no-op if called interactively in narrowed Todos mode, since, also to work around item prefix reduplication bug with show-paren-mode enabled; use todos-categories-list.
-rw-r--r--lisp/ChangeLog95
-rw-r--r--lisp/calendar/todos.el2190
2 files changed, 1573 insertions, 712 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 4110a4ad087..e2d88a96ac4 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,98 @@
12012-09-13 Stephen Berman <stephen.berman@gmx.net>
2
3 * calendar/todos.el: Comment out calendar require, since diary-lib
4 requires calendar. Rearrange file to group definitions according
5 to their use (types of commands, internal functions, etc.)
6 (todos-file-done, todos-insert-threshold, todos-remove-separator)
7 (todos-date-string, todos-time-string, todos-check-overlay)
8 (todos-show-paren-hack, todos-file-item, todos-more-important-p):
9 Comment out.
10 (todos-current-date, todos-item-end-overlays)
11 (todos-list-categories): Remove.
12 (todos-item-end): Remove (the variable, not the function).
13 (todos-item-overlays): Rename to todos-prefix-overlays and adjust
14 callers.
15 (todos-prefix-overlays): Rename from todos-item-overlays.
16 (todos-done-separator, todos-done-string, todos-show-with-done)
17 (todos-files, todos-archive-file, todos-categories-buffer)
18 (todos-archived-categories-buffer, todos-wrap-lines)
19 (todos-line-wrapping-function): New defcustoms.
20 (todos-done, todos-done-sep): New faces.
21 (todos-done-face, todos-done-sep-face): Corresponding new variables.
22 (todos-search-string, todos-date-nodayname-pattern)
23 (todos-dayname-date-pattern, todos-date-pattern): New variables.
24 (todos-done-string-match, todos-category-string-match)
25 (todos-check-format, todos-wrap-and-indent, todos-reset-separator)
26 (todos-current-category, todos-count-items-in-category)
27 (todos-done-item-p, todos-categories-alist, todos-count-all-items)
28 (todos-longest-category-name-length): New functions.
29 (todos-categories-list): New buffer-specific function replacing
30 todos-list-categories.
31 (todos-toggle-item-numbering, todos-toggle-view-done-items)
32 (todos-search, todos-view-archive, todos-diary-items)
33 (todos-toggle-display-date-time, todos-insert-item-no-time)
34 (todos-insert-item-ask-date, todos-insert-item-for-diary)
35 (todos-insert-item-from-calendar, todos-edit-quit)
36 (todos-change-date, todos-item-done, todos-archive-done-items)
37 (todos-item-undo): New commands.
38 (todos-archive-mode): New mode.
39 (todos-archive-mode-map, todos-edit-mode-map): New keymaps.
40 (todos-category-beg): Change value.
41 (todos-number-prefix): Change default value.
42 (todos-edit-buffer): Change from defvar to defcustom.
43 (todos-font-lock-keywords): Use todos-done-string-match and
44 todos-category-string-match.
45 (todos-backward-item, todos-forward-item): Use todos-done-string
46 and todos-date-pattern.
47 (todos-display-categories): Reimplement using buttons from
48 button.el instead of widgets.
49 (todos-top-priorities): Use with-current-buffer; take done items
50 into account; ensure buffers gets fontified.
51 (todos-add-category): Ensure new category does not begin with
52 empty lines.
53 (todos-jump-to-category): Use todos-category-select instead of
54 todos-show.
55 (todos-rename-category): Prompt for new name in body instead of in
56 interactive spec.
57 (todos-insert-item): Don't insert in done items section of
58 category. Add two optional arguments to control insertion: (i) to
59 insert near point without prompting for priority; (ii) to use
60 defaults for date and time strings, to prompt for these, or to
61 choose date from the Calendar.
62 (todos-insert-item-here): Reimplement using todos-insert-item.
63 (todos-delete-item): Don't move point after deleting last item.
64 (todos-raise-item, todos-lower-item): Take done items into account.
65 (todos-move-item): Don't move done items; update item numbering;
66 restore if user quits before inserting moved item.
67 (todos-print): Prompt for confirmation to print.
68 (todos-reset-prefix): Search backward from end of file instead of
69 forward from top.
70 (todos-jump-to-category-noninteractively): Take Todos archive into
71 account.
72 (todos-category-select): Show or hide done items according to
73 todos-show-with-done; if shown, coordinate separator and prefix
74 overlays.
75 (todos-add-item-non-interactively): Replace binary insertion
76 algorithm with prompting for numerical priority.
77 (todos-insert-with-overlays): Remove use of variable todos-item-end.
78 (todos-item-start): Take done items into account; use
79 todos-date-pattern.
80 (todos-item-end): Reimplement using todos-forward-item.
81 (todos-remove-item): Reimplement using todos-forward-item and
82 todos-backward-item; redo overlay handling.
83 (todos-mode-map): Add some new key bindings and change numerous
84 existing bindings; use "i" as prefix key for item insertion
85 commands.
86 (todos-mode): Use todos-wrap-lines and delegate word-wrap and
87 wrap-prefix settings to todos-wrap-and-indent; add to invisibility
88 spec; set buffer-read-only to t and consequently let-bind this
89 variable in all Todos commands that change buffer content.
90 (todos-edit-mode): Make an indepent mode, not derived from text-mode.
91 (todos-save): Don't save top priorities buffer.
92 (todos-show): Make a no-op if called interactively in narrowed
93 Todos mode, since, also to work around item prefix reduplication
94 bug with show-paren-mode enabled; use todos-categories-list.
95
12012-09-12 Stephen Berman <stephen.berman@gmx.net> 962012-09-12 Stephen Berman <stephen.berman@gmx.net>
2 97
3 * calendar/todos.el: Comment out time-stamp require; require 98 * calendar/todos.el: Comment out time-stamp require; require
diff --git a/lisp/calendar/todos.el b/lisp/calendar/todos.el
index 5e1a3cda104..c4788044520 100644
--- a/lisp/calendar/todos.el
+++ b/lisp/calendar/todos.el
@@ -168,14 +168,6 @@
168;; todo list is already established is not as simple as changing 168;; todo list is already established is not as simple as changing
169;; the variable - the todo files have to be changed by hand. 169;; the variable - the todo files have to be changed by hand.
170;; 170;;
171;; FIXME: eliminate variable todos-prefix, use overlays:
172;; (defun todos-prefix ()
173;; "Display a Todo prefix string as an overlay."
174;; (let (ov)
175;; (setq ov (make-overlay (line-beginning-position) (line-end-position)))
176;; (overlay-put ov 'before-string
177;; (propertize todos-prefix 'face 'todos-prefix-string))))
178;;
179;; Variable todos-file-do 171;; Variable todos-file-do
180;; 172;;
181;; This variable is fairly self-explanatory. You have to store 173;; This variable is fairly self-explanatory. You have to store
@@ -265,18 +257,19 @@
265;;; Code: 257;;; Code:
266 258
267;; (require 'time-stamp) 259;; (require 'time-stamp)
268(require 'calendar) 260;; (require 'calendar) ; required by diary-lib
269(require 'diary-lib) 261(require 'diary-lib)
270 262
271;; User-configurable variables: 263;; ---------------------------------------------------------------------------
272 264
265;;; Customizable options
273(defgroup todos nil 266(defgroup todos nil
274 "Maintain a list of todo items." 267 "Maintain lists of todo items."
275 :link '(emacs-commentary-link "todos") 268 :link '(emacs-commentary-link "todos")
276 :version "21.1" 269 :version "21.1"
277 :group 'calendar) 270 :group 'calendar)
278 271
279(defcustom todos-prefix "§" ; "*/*" 272(defcustom todos-prefix "§" ; "*/*" FIXME ascii default
280 "String prefixed to todo items for visual distinction." 273 "String prefixed to todo items for visual distinction."
281 :type 'string 274 :type 'string
282 :initialize 'custom-initialize-default 275 :initialize 'custom-initialize-default
@@ -297,40 +290,97 @@
297;; the diary file somewhat." 290;; the diary file somewhat."
298;; :type 'string 291;; :type 'string
299;; :group 'todos) 292;; :group 'todos)
300(defcustom todos-file-do (convert-standard-filename "~/.emacs.d/.todos-do") 293
294(defcustom todos-number-prefix t
295 "Non-nil to show item prefixes as consecutively increasing integers."
296 :type 'boolean
297 :initialize 'custom-initialize-default
298 :set 'todos-reset-prefix
299 :group 'todos)
300
301(defcustom todos-done-separator (make-string (window-width) ?-)
302 "String used to visual separate done from not done items.
303Displayed in a before-string overlay by `todos-toggle-view-done-items'."
304 :type 'string
305 :initialize 'custom-initialize-default
306 :set 'todos-reset-separator
307 :group 'todos)
308
309(defcustom todos-done-string "DONE "
310 "Identifying string appended to the front of done todos items."
311 :type 'string
312 ;; :initialize 'custom-initialize-default
313 ;; :set
314 :group 'todos)
315
316(defcustom todos-show-with-done nil
317 "Non-nil to display done items in all categories."
318 :type 'boolean
319 :group 'todos)
320
321;; FIXME: use user-emacs-directory here and below
322(defcustom todos-file-do (convert-standard-filename "~/.emacs.d/.todos-do")
301 "TODO mode list file." 323 "TODO mode list file."
302 :type 'file 324 :type 'file
303 :group 'todos) 325 :group 'todos)
304(defcustom todos-file-done (convert-standard-filename "~/.emacs.d/.todos-done") 326
305 "TODO mode archive file." 327(defcustom todos-files '((convert-standard-filename "~/.emacs.d/.todos"))
328 "List of Todos files."
329 :type 'list
330 :group 'todos)
331
332(defcustom todos-archive-file (convert-standard-filename "~/.emacs.d/.todos-archive")
333 "File of finished Todos categories."
306 :type 'file 334 :type 'file
307 :group 'todos) 335 :group 'todos)
308(defcustom todos-mode-hook nil 336
337;; (defcustom todos-file-done (convert-standard-filename "~/.emacs.d/.todos-done")
338;; "TODO mode archive file."
339;; :type 'file
340;; :group 'todos)
341
342(defcustom todos-mode-hook nil
309 "TODO mode hooks." 343 "TODO mode hooks."
310 :type 'hook 344 :type 'hook
311 :group 'todos) 345 :group 'todos)
346
312(defcustom todos-edit-mode-hook nil 347(defcustom todos-edit-mode-hook nil
313 "TODO Edit mode hooks." 348 "TODO Edit mode hooks."
314 :type 'hook 349 :type 'hook
315 :group 'todos) 350 :group 'todos)
316(defcustom todos-insert-threshold 0 351
317 "TODO mode insertion accuracy. 352;; (defcustom todos-insert-threshold 0
318 353;; "TODO mode insertion accuracy.
319If you have 8 items in your TODO list, then you may get asked 4 354
320questions by the binary insertion algorithm. However, you may not 355;; If you have 8 items in your TODO list, then you may get asked 4
321really have a need for such accurate priorities amongst your TODO 356;; questions by the binary insertion algorithm. However, you may not
322items. If you now think about the binary insertion halving the size 357;; really have a need for such accurate priorities amongst your TODO
323of the window each time, then the threshold is the window size at 358;; items. If you now think about the binary insertion halving the size
324which it will stop. If you set the threshold to zero, the upper and 359;; of the window each time, then the threshold is the window size at
325lower bound will coincide at the end of the loop and you will insert 360;; which it will stop. If you set the threshold to zero, the upper and
326your item just before that point. If you set the threshold to, 361;; lower bound will coincide at the end of the loop and you will insert
327e.g. 8, it will stop as soon as the window size drops below that 362;; your item just before that point. If you set the threshold to,
328amount and will insert the item in the approximate center of that 363;; e.g. 8, it will stop as soon as the window size drops below that
329window." 364;; amount and will insert the item in the approximate center of that
330 :type 'integer 365;; window."
366;; :type 'integer
367;; :group 'todos)
368
369(defcustom todos-categories-buffer "*TODOS Categories*"
370 "Name of buffer displayed by `todos-display-categories'"
371 :type 'string
372 :group 'todos)
373
374(defcustom todos-archived-categories-buffer "*TODOS Archived Categories*"
375 "Name of buffer displayed by `todos-display-categories'"
376 :type 'string
331 :group 'todos) 377 :group 'todos)
332(defvar todos-edit-buffer " *TODO Edit*" 378
333 "TODO Edit buffer name.") 379(defcustom todos-edit-buffer " *TODO Edit*"
380 "TODO Edit buffer name."
381 :type 'string
382 :group 'todos)
383
334(defcustom todos-file-top (convert-standard-filename "~/.todos-top") 384(defcustom todos-file-top (convert-standard-filename "~/.todos-top")
335 "TODO mode top priorities file. 385 "TODO mode top priorities file.
336 386
@@ -339,102 +389,83 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
339 :type 'string 389 :type 'string
340 :group 'todos) 390 :group 'todos)
341 391
392(defcustom todos-include-in-diary nil
393 "Non-nil to allow new Todo items to be included in the diary."
394 :type 'boolean
395 :group 'todos)
396
397(defcustom todos-exclusion-start "["
398 "String prepended to item date to block diary inclusion."
399 :type 'string
400 :group 'todos
401 ;; :initialize 'custom-initialize-default
402 ;; :set ; change in whole Todos file
403 )
404
405(defcustom todos-exclusion-end "]"
406 "String appended to item date to match todos-exclusion-start."
407 :type 'string
408 :group 'todos
409 ;; :initialize 'custom-initialize-default
410 ;; :set ; change in whole Todos file
411 )
412
342(defcustom todos-print-function 'ps-print-buffer-with-faces 413(defcustom todos-print-function 'ps-print-buffer-with-faces
343 "Function to print the current buffer." 414 "Function to print the current buffer."
344 :type 'symbol 415 :type 'symbol
345 :group 'todos) 416 :group 'todos)
417
346(defcustom todos-show-priorities 1 418(defcustom todos-show-priorities 1
347 "Default number of priorities to show by \\[todos-top-priorities]. 419 "Default number of priorities to show by \\[todos-top-priorities].
3480 means show all entries." 4200 means show all entries."
349 :type 'integer 421 :type 'integer
350 :group 'todos) 422 :group 'todos)
423
351(defcustom todos-print-priorities 0 424(defcustom todos-print-priorities 0
352 "Default number of priorities to print by \\[todos-print]. 425 "Default number of priorities to print by \\[todos-print].
3530 means print all entries." 4260 means print all entries."
354 :type 'integer 427 :type 'integer
355 :group 'todos) 428 :group 'todos)
356(defcustom todos-remove-separator t 429;; (defcustom todos-remove-separator t
357 "Non-nil to remove category separators in\ 430;; "Non-nil to remove category separators in\
358\\[todos-top-priorities] and \\[todos-print]." 431;; \\[todos-top-priorities] and \\[todos-print]."
359 :type 'boolean 432;; :type 'boolean
360 :group 'todos) 433;; :group 'todos)
434
361(defcustom todos-save-top-priorities-too t 435(defcustom todos-save-top-priorities-too t
362 "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'." 436 "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'."
363 :type 'boolean 437 :type 'boolean
364 :group 'todos) 438 :group 'todos)
439
365(defcustom todos-completion-ignore-case t ;; FIXME: nil for release 440(defcustom todos-completion-ignore-case t ;; FIXME: nil for release
366 "Non-nil means don't consider case significant in todos-completing-read." 441 "Non-nil means don't consider case significant in todos-completing-read."
367 :type 'boolean 442 :type 'boolean
368 :group 'todos) 443 :group 'todos)
369 444
370;; ;; Thanks for the ISO time stamp format go to Karl Eichwalder <ke@suse.de>
371;; ;; My format string for the appt.el package is "%3b %2d, %y, %02I:%02M%p".
372;; ;;
373;; ;; FIXME: use calendar format instead
374;; (defcustom todos-time-string-format
375;; "%:y-%02m-%02d %02H:%02M"
376;; "TODO mode time string format for done entries.
377;; For details see the variable `time-stamp-format'."
378;; :type 'string
379;; :group 'todos)
380
381;; (defcustom todos-entry-prefix-function 'todos-entry-timestamp-initials
382;; "Function producing text to insert at start of todo entry."
383;; :type 'symbol
384;; :group 'todos)
385;; (defcustom todos-initials (or (getenv "INITIALS") (user-login-name))
386;; "Initials of todo item author."
387;; :type 'string
388;; :group 'todos)
389
390;; (defun todos-entry-timestamp-initials ()
391;; "Prepend timestamp and your initials to the head of a TODO entry."
392;; (let ((time-stamp-format todos-time-string-format))
393;; (concat (time-stamp-string) " " todos-initials ": ")))
394
395;; (defcustom todos-date (calendar-date-string (calendar-current-date) t t)
396;; "Date string inserted in front of a todo item."
397;; :type 'string
398;; :group 'todos)
399
400;; (defcustom todos-time (substring (current-time-string) 11 16)
401;; "Time string inserted in front of a todo item."
402;; :type 'string
403;; :group 'todos)
404
405(defun todos-current-date (&optional time)
406 "Return current date as a string for insertion in front of a todo item.
407With non-nil TIME append the current time."
408 (concat (calendar-date-string (calendar-current-date) t t)
409 (when time
410 (concat " " (substring (current-time-string) 11 16)))))
411
412(defcustom todos-add-time-string t 445(defcustom todos-add-time-string t
413 "Add current time to date string inserted in front of new items." 446 "Add current time to date string inserted in front of new items."
414 :type 'boolean 447 :type 'boolean
415 :group 'todos) 448 :group 'todos)
416 449
417;; "Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec") 450(defcustom todos-wrap-lines t
418;; (regexp-opt (list "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30" "31"))) 451 "" ;FIXME
419(defun todos-date-string () 452 :group 'todos
420 "Return a regexp matching a diary date string." 453 :type 'boolean)
421 (let ((month (regexp-opt (list "Jan" "Feb" "Mar" "Apr" "May" "Jun"
422 "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")))
423 (day "[0-3]?[0-9]")
424 (year "[0-9]\\{4\\}"))
425 (concat month " " day ", " year)))
426
427(defun todos-time-string ()
428 "Return a regexp matching a diary time string."
429 "[0-9]?[0-9][:.][0-9]\\{2\\}")
430 454
455(defcustom todos-line-wrapping-function 'todos-wrap-and-indent
456 "" ;FIXME
457 :group 'todos
458 :type 'function)
459
460;; ---------------------------------------------------------------------------
461
462;;; Faces
431(defface todos-prefix-string 463(defface todos-prefix-string
432 '((t 464 '((t
433 :inherit font-lock-constant-face 465 :inherit font-lock-constant-face
434 )) 466 ))
435 "Face for Todos prefix string." 467 "Face for Todos prefix string."
436 :group 'todos) 468 :group 'todos)
437;; (defvar todos-prefix-face 'todos-prefix-string)
438 469
439(defface todos-date 470(defface todos-date
440 '((t 471 '((t
@@ -452,83 +483,33 @@ With non-nil TIME append the current time."
452 :group 'todos) 483 :group 'todos)
453(defvar todos-time-face 'todos-time) 484(defvar todos-time-face 'todos-time)
454 485
455(defun todos-date-string-match (lim) 486(defface todos-done
456 "Find Todos date strings for font-locking." 487 '((t
457 (let ((lim (point-max))) 488 :inherit font-lock-comment-face
458 (re-search-forward (concat "^\\[?\\(" (todos-date-string) "\\)") lim t))) 489 ))
490 "Face for done Todos item header string."
491 :group 'todos)
492(defvar todos-done-face 'todos-done)
459 493
460(defun todos-time-string-match (lim) 494(defface todos-done-sep
461 "Find Todos time strings for font-locking." 495 '((t
462 (let ((lim (point-max))) 496 :inherit font-lock-type-face
463 (re-search-forward (concat "^\\[?" (todos-date-string) 497 ))
464 " \\(" (todos-time-string) "\\)") lim t))) 498 "Face for separator string bewteen done and not done Todos items."
499 :group 'todos)
500(defvar todos-done-sep-face 'todos-done-sep)
465 501
466(defvar todos-font-lock-keywords 502(defvar todos-font-lock-keywords
467 (list 503 (list
468 '(todos-date-string-match 1 todos-date-face t) 504 '(todos-date-string-match 1 todos-date-face t)
469 '(todos-time-string-match 1 todos-time-face t)) 505 '(todos-time-string-match 1 todos-time-face t)
506 '(todos-done-string-match 0 todos-done-face t)
507 '(todos-category-string-match 0 todos-done-sep-face t))
470 "Font-locking for Todos mode.") 508 "Font-locking for Todos mode.")
471 509
472(defcustom todos-include-in-diary nil
473 "Non-nil to allow new Todo items to be included in the diary."
474 :type 'boolean
475 :group 'todos)
476
477(defcustom todos-exclusion-start "["
478 "String prepended to item date to block diary inclusion."
479 :type 'string
480 :group 'todos
481 ;; :initialize 'custom-initialize-default
482 ;; :set ; change in whole Todos file
483 )
484
485(defcustom todos-exclusion-end "]"
486 "String appended to item date to match todos-exclusion-start."
487 :type 'string
488 :group 'todos
489 ;; :initialize 'custom-initialize-default
490 ;; :set ; change in whole Todos file
491 )
492
493(defun todos-toggle-item-diary-inclusion ()
494 "" ;FIXME add docstring
495 (interactive)
496 (save-excursion
497 (let ((beg (goto-char (todos-item-start)))
498 (end (save-excursion
499 (or (todos-time-string-match (todos-item-end))
500 (todos-date-string-match (todos-item-end))))))
501 (if (looking-at "\\[") ; FIXME use todos-exclusion-start
502 (progn
503 (replace-match "")
504 (search-forward "]" (1+ end) t) ; FIXME use todos-exclusion-end
505 (replace-match ""))
506 (when end
507 (insert "[") ; FIXME use todos-exclusion-start
508 (goto-char (1+ end))
509 (insert "]")))))) ; FIXME use todos-exclusion-end
510
511(defun todos-toggle-diary-inclusion (arg)
512 "" ;FIXME add docstring
513 (interactive "p")
514 (save-excursion
515 (save-restriction
516 (when (eq arg 2) (widen))
517 (when (or (eq arg 1) (eq arg 2))
518 (goto-char (point-min))
519 (when (eq arg 2)
520 (re-search-forward (concat "^" (regexp-quote todos-category-beg))
521 (point-max) t)
522 (forward-line)
523 (when (looking-at (regexp-quote todos-category-end)) (forward-line)))
524 (while (not (eobp))
525 (todos-toggle-item-diary-inclusion)
526 (todos-forward-item))))))
527
528;; --------------------------------------------------------------------------- 510;; ---------------------------------------------------------------------------
529 511
530;; Set up some helpful context ... 512;;; Internal variables
531
532(defvar todos-categories nil 513(defvar todos-categories nil
533 "TODO categories.") 514 "TODO categories.")
534 515
@@ -541,172 +522,89 @@ With non-nil TIME append the current time."
541(defvar todos-mode-map 522(defvar todos-mode-map
542 (let ((map (make-keymap))) 523 (let ((map (make-keymap)))
543 (suppress-keymap map t) 524 (suppress-keymap map t)
525 ;; navigation commands
544 (define-key map "+" 'todos-forward-category) 526 (define-key map "+" 'todos-forward-category)
545 (define-key map "-" 'todos-backward-category) 527 (define-key map "-" 'todos-backward-category)
546 (define-key map "A" 'todos-add-category) 528 (define-key map "j" 'todos-jump-to-category)
529 (define-key map "n" 'todos-forward-item)
530 (define-key map "p" 'todos-backward-item)
531 (define-key map "S" 'todos-search)
532 ;; display commands
547 (define-key map "C" 'todos-display-categories) 533 (define-key map "C" 'todos-display-categories)
548 (define-key map "d" 'todos-file-item) ;done/delete 534 (define-key map "h" 'todos-highlight-item)
535 (define-key map "N" 'todos-toggle-item-numbering)
536 ;; (define-key map "" 'todos-toggle-display-date-time)
537 (define-key map "P" 'todos-print)
538 (define-key map "q" 'todos-quit)
539 (define-key map "s" 'todos-save)
540 (define-key map "v" 'todos-toggle-view-done-items)
541 (define-key map "Y" 'todos-diary-items)
542 ;; (define-key map "S" 'todos-save-top-priorities)
543 (define-key map "t" 'todos-top-priorities)
544 ;; editing commands
545 (define-key map "A" 'todos-add-category)
546 (define-key map "d" 'todos-item-done)
547 ;; (define-key map "" 'todos-archive-done-items)
549 (define-key map "D" 'todos-delete-category) 548 (define-key map "D" 'todos-delete-category)
550 (define-key map "e" 'todos-edit-item) 549 (define-key map "e" 'todos-edit-item)
551 (define-key map "E" 'todos-edit-multiline) 550 (define-key map "E" 'todos-edit-multiline)
552 (define-key map "f" 'todos-file-item) 551 ;; (define-key map "" 'todos-change-date)
553 (define-key map "i" 'todos-insert-item) 552 ;; (define-key map "f" 'todos-file-item)
554 (define-key map "I" 'todos-insert-item-here) 553 (define-key map "ii" 'todos-insert-item)
555 (define-key map "j" 'todos-jump-to-category) 554 (define-key map "ih" 'todos-insert-item-here)
555 (define-key map "ia" 'todos-insert-item-ask-date)
556 (define-key map "id" 'todos-insert-item-for-diary)
557 ;; (define-key map "in" 'todos-insert-item-no-time)
556 (define-key map "k" 'todos-delete-item) 558 (define-key map "k" 'todos-delete-item)
557 (define-key map "l" 'todos-lower-item) 559 (define-key map "l" 'todos-lower-item)
558 (define-key map "m" 'todos-move-item) 560 (define-key map "m" 'todos-move-item)
561 (define-key map "r" 'todos-raise-item)
562 (define-key map "R" 'todos-rename-category)
563 (define-key map "u" 'todos-item-undo)
564 (define-key map "y" 'todos-toggle-item-diary-inclusion)
565 ;; (define-key map "" 'todos-toggle-diary-inclusion)
566 map)
567 "Todos mode keymap.")
568
569(defvar todos-archive-mode-map
570 (let ((map (make-sparse-keymap)))
571 (suppress-keymap map t)
572 ;; navigation commands
573 (define-key map "+" 'todos-forward-category)
574 (define-key map "-" 'todos-backward-category)
575 (define-key map "j" 'todos-jump-to-category)
559 (define-key map "n" 'todos-forward-item) 576 (define-key map "n" 'todos-forward-item)
560 (define-key map "p" 'todos-backward-item) 577 (define-key map "p" 'todos-backward-item)
578 ;; display commands
579 (define-key map "C" 'todos-display-categories)
580 (define-key map "h" 'todos-highlight-item)
581 (define-key map "N" 'todos-toggle-item-numbering)
582 ;; (define-key map "" 'todos-toggle-display-date-time)
561 (define-key map "P" 'todos-print) 583 (define-key map "P" 'todos-print)
562 (define-key map "q" 'todos-quit) 584 (define-key map "q" 'todos-quit)
563 (define-key map "r" 'todos-raise-item)
564 (define-key map "R" 'todos-rename-category)
565 (define-key map "s" 'todos-save) 585 (define-key map "s" 'todos-save)
566 (define-key map "S" 'todos-save-top-priorities) 586 (define-key map "S" 'todos-search)
567 (define-key map "t" 'todos-top-priorities) 587 map)
588 "Todos Archive mode keymap.")
589
590(defvar todos-edit-mode-map
591 (let ((map (make-keymap)))
592 (define-key map "\C-c\C-q" 'todos-edit-quit)
568 map) 593 map)
569 "TODO mode keymap.") 594 "Todos Edit mode keymap.")
570 595
571(defvar todos-category-number 0 "TODO category number.") 596(defvar todos-category-number 0 "TODO category number.")
572 597
573(defvar todos-tmp-buffer-name " *todo tmp*") 598(defvar todos-tmp-buffer-name " *todo tmp*")
574 599
575;; ;; FIXME: should the following four be defconsts? 600(defvar todos-category-beg "--==-- "
576;; (defvar todos-category-sep (make-string 75 ?-)
577;; "Category separator.")
578
579(defvar todos-category-beg "--- " ;" --- "
580 "Category start separator to be prepended onto category name.") 601 "Category start separator to be prepended onto category name.")
581 602
582;; (defvar todos-category-end "--- End"
583;; "Separator after a category.")
584
585(defvar todos-item-end " :::"
586 "String marking the end of a todo item.
587In Todos mode it is made invisible with an overlay.")
588
589;; --------------------------------------------------------------------------- 603;; ---------------------------------------------------------------------------
590 604
591(defcustom todos-number-prefix nil 605;;; Commands
592 "Non-nil to show item prefixes as consecutively increasing integers."
593 :type 'boolean
594 :initialize 'custom-initialize-default
595 :set 'todos-reset-prefix
596 :group 'todos)
597
598(defun todos-reset-prefix (symbol value)
599 "Set SYMBOL's value to VALUE, and ." ; FIXME
600 (let ((oldvalue (symbol-value symbol)))
601 (custom-set-default symbol value)
602 (when (not (equal value oldvalue))
603 (save-window-excursion
604 (todos-show)
605 (save-excursion
606 (widen)
607 (goto-char (point-min))
608 (while (not (eobp))
609 (re-search-forward
610 (concat "^" (regexp-quote todos-category-beg)) (point-max) t)
611 (forward-line)
612 (or (eobp)
613 (while (not (looking-at (regexp-quote todos-category-end)))
614 (remove-overlays (1- (point)) (1+ (point)))
615 (forward-line)))))
616 ;; activate the prefix setting (save-restriction does not help)
617 (todos-show)))))
618
619;; FIXME: rename and/or rewrite
620(defun todos-update-numbered-prefix ()
621 "Update consecutive item numbering in the current category."
622 (save-excursion
623 (goto-char (point-min))
624 (while (not (eobp))
625 (remove-overlays (1- (point)) (1+ (point)))
626 (todos-forward-item))
627 (todos-show)))
628
629(defvar todos-item-start-overlays nil "")
630
631(defvar todos-item-end-overlays nil "")
632 606
633(defun todos-check-overlay (prop) 607;;; Navigation
634 "" ;FIXME add docstring
635 (let ((ovlist (overlays-in (1- (point)) (1+ (point)))))
636 ;; (let ((ovlist (overlays-in (point) (point))))
637 (when ovlist (overlay-get (car ovlist) prop))))
638
639(defun todos-item-overlays ()
640 "" ;FIXME add docstring
641 (let ((prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string))
642 (num 1)
643 ;; (paren show-paren-mode)
644 ov-pref ov-end)
645 ;; turn off show-paren-mode to avoid overlay reduplication problem
646 ;; (if paren (show-paren-mode 0))
647 (save-excursion
648 (goto-char (point-min))
649 (while (not (eobp))
650 (if todos-number-prefix
651 (setq prefix (propertize (concat (number-to-string num) " ")
652 'face 'todos-prefix-string)))
653 (unless (todos-check-overlay 'before-string)
654 (or (and (setq ov-pref (pop todos-item-start-overlays))
655 (move-overlay ov-pref (point) (point)))
656 (and (setq ov-pref (make-overlay (point) (point)))
657 (overlay-put ov-pref 'before-string prefix))))
658 (re-search-forward (concat "\\(" (regexp-quote todos-item-end) "\\)\n"))
659 (backward-char)
660 (unless (todos-check-overlay 'invisible)
661 (or (and (setq ov-end (pop
662 todos-item-end-overlays))
663 (move-overlay ov-end (match-beginning 1) (match-end 1)))
664 (and (setq ov-end (make-overlay (match-beginning 1) (match-end 1)))
665 (overlay-put ov-end 'invisible t))))
666 (forward-line)
667 (if todos-number-prefix (setq num (1+ num))))
668 ;; (if paren (show-paren-mode 1))
669 ;; (todos-show-paren-hack)
670 )))
671
672(defun todos-show-paren-hack ()
673 "Purge overlay duplication due to show-paren-mode."
674 (save-excursion
675 (when show-paren-mode
676 (goto-char (point-min))
677 (while (not (eobp))
678 ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point))))
679 (let ((ovlist (overlays-in (point) (point)))
680 ov)
681 (while (> (length ovlist) 1)
682 (setq ov (pop ovlist))
683 (delete-overlay ov)))
684 (forward-line))
685 (if (and (bolp) (eolp))
686 ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point)))))
687 (let ((ovlist (overlays-in (point) (point))))
688 (remove-overlays (1- (point)) (1+ (point))))))))
689
690(defun todos-category-select ()
691 "Make TODO mode display the current category correctly."
692 (let ((name (nth todos-category-number todos-categories)))
693 (setq mode-line-buffer-identification
694;; (concat "Category: " name))
695 (concat "Category: " (format "%18s" name)))
696 (widen)
697 (goto-char (point-min))
698 (search-forward-regexp
699 (concat "\n" ;"^" (regexp-quote todos-category-sep) "\n"
700 (regexp-quote (concat todos-category-beg name))
701 "$"))
702 (let ((begin (1+ (line-end-position))))
703 ;; (search-forward-regexp (concat "^" todos-category-end))
704 (re-search-forward (concat "^" todos-category-beg) (point-max) t)
705 (narrow-to-region begin (line-beginning-position))
706 (goto-char (point-min))))
707 (todos-item-overlays)
708 ;; (todos-show-paren-hack)
709 )
710 608
711(defun todos-forward-category () 609(defun todos-forward-category ()
712 "Go forward to TODO list of next category." 610 "Go forward to TODO list of next category."
@@ -722,65 +620,398 @@ In Todos mode it is made invisible with an overlay.")
722 (mod (1- todos-category-number) (length todos-categories))) 620 (mod (1- todos-category-number) (length todos-categories)))
723 (todos-category-select)) 621 (todos-category-select))
724 622
623;; FIXME: Document that a non-existing name creates that category, and add
624;; y-or-n-p confirmation -- or eliminate this possibility?
625(defun todos-jump-to-category ()
626 "Jump to a category. Default is previous category."
627 (interactive)
628 (let ((category (todos-completing-read)))
629 (if (string= "" category)
630 (setq category (nth todos-category-number todos-categories)))
631 (setq todos-category-number
632 (if (member category todos-categories)
633 (- (length todos-categories)
634 (length (member category todos-categories)))
635 (todos-add-category category)))
636 ;; (todos-show)))
637 (todos-category-select)))
638
639;; FIXME ? todos-{backward,forward}-item skip over empty line between done and
640;; not done items (but todos-forward-item gets there when done items are not
641;; displayed)
725(defun todos-backward-item (&optional count) 642(defun todos-backward-item (&optional count)
726 "Select previous entry of TODO list." 643 "Select previous entry of TODO list."
727 (interactive "P") 644 (interactive "P")
728 (re-search-backward (concat (regexp-quote todos-item-end) "\n") nil t count) 645 ;; FIXME ? this moves to bob if on the first item (but so does previous-line)
729 (goto-char (todos-item-start))) 646 (todos-item-start)
647 (unless (bobp)
648 (re-search-backward (concat "^\\(\\[\\(" (regexp-quote todos-done-string)
649 "\\)?\\)?\\(" todos-date-pattern "\\)")
650 nil t (or count 1))))
730 651
731(defun todos-forward-item (&optional count) 652(defun todos-forward-item (&optional count)
732 "Select COUNT-th next entry of TODO list." 653 "Select COUNT-th next entry of TODO list."
733 (interactive "P") 654 (interactive "P")
734 (when (todos-check-overlay 'invisible) (goto-char (todos-item-start))) 655 (goto-char (line-end-position))
735 (re-search-forward (concat (regexp-quote todos-item-end) "\n") nil t count)) 656 (if (re-search-forward (concat "^\\(\\[\\(" (regexp-quote todos-done-string)
657 "\\)?\\)?\\(" todos-date-pattern "\\)")
658 nil t (or count 1))
659 (goto-char (match-beginning 0))
660 (goto-char (point-max))))
661
662;; (defun todos-forward-item (&optional count)
663;; "Select COUNT-th next entry of TODO list."
664;; (interactive "P")
665;; (let ((opoint (point))
666;; (done (save-excursion
667;; (if (re-search-forward (concat "\n\n\\\(\\["
668;; (regexp-quote todos-done-string)
669;; "\\)") nil t)
670;; (match-beginning 1)))))
671;; ;; FIXME: can this be simplified?
672;; (if (looking-at (concat "^\\(\\[\\(" (regexp-quote todos-done-string) "\\)?\\)?"
673;; todos-date-pattern)) ; on item header
674;; (re-search-forward (concat "^\\(\\[\\(" (regexp-quote todos-done-string)
675;; "\\)?\\)?\\(" todos-date-pattern "\\)")
676;; nil t (if count (1+ count) 2))
677;; (re-search-forward (concat "^\\(\\[\\(" (regexp-quote todos-done-string)
678;; "\\)?\\)?\\(" todos-date-pattern "\\)")
679;; nil t (or count 1)))
680;; (cond ((save-excursion
681;; (goto-char opoint)
682;; (looking-at "^$")) ; between done and not done items
683;; (forward-line 0))
684;; ((and done (> (point) done))
685;; (forward-line -1)) ; FIXME: count ?
686;; ((eq (point) opoint) ; on last item
687;; (goto-char (point-max)))
688;; (t
689;; (goto-char (match-beginning 0))))))
690
691(defvar todos-search-string nil
692 "" ;FIXME
693 )
694(defun todos-search ()
695 "" ;FIXME
696 (interactive)
697 (let ((regex (read-from-minibuffer "Enter a search string (regexp): "))
698 (start (point))
699 found cat in-done)
700 (widen)
701 (goto-char (point-min))
702 (while (and (setq found (re-search-forward regex nil t))
703 (save-excursion
704 (goto-char (line-beginning-position))
705 (looking-at (concat "^" (regexp-quote todos-category-beg)))))
706 (forward-line))
707 (if found
708 (progn
709 (setq found (match-beginning 0))
710 (todos-item-start)
711 (when (looking-at (concat "^\\[" (regexp-quote todos-done-string)))
712 (setq in-done t))
713 (re-search-backward (concat "^" (regexp-quote todos-category-beg)
714 "\\(.*\\)\n") nil t)
715 (setq cat (match-string-no-properties 1))
716 (setq todos-category-number
717 (- (length todos-categories) (length (member cat todos-categories))))
718 (todos-category-select)
719 (when in-done (unless todos-show-with-done (todos-toggle-view-done-items)))
720 (goto-char found))
721 (todos-category-select)
722 (goto-char start)
723 (message "No match for \"%s\"" regex))))
724
725;;; Display
736 726
737(defun todos-save () 727(defun todos-display-categories ()
738 "Save the TODO list." 728 "Display an alphabetical list of clickable Todos category names.
729Click or type RET on a category name to go to it."
739 (interactive) 730 (interactive)
740 (save-excursion 731 (let ((categories (copy-sequence todos-categories))
741 (save-restriction 732 (cat-alist (todos-categories-alist))
742 (save-buffer))) 733 (len (todos-longest-category-name-length))
743 (if todos-save-top-priorities-too (todos-save-top-priorities))) 734 beg)
735 ;; alphabetize the list case insensitively
736 (setq categories (sort categories (lambda (s1 s2) (let ((cis1 (upcase s1))
737 (cis2 (upcase s2)))
738 (string< cis1 cis2)))))
739 (with-current-buffer (get-buffer-create todos-categories-buffer)
740 (switch-to-buffer (current-buffer))
741 (erase-buffer)
742 (kill-all-local-variables)
743 (insert "Press a button to display the corresponding category.\n\n")
744 (setq beg (point))
745 (mapc (lambda (cat)
746 (let* ((catlen (length cat))
747 (catlen-odd (eq (logand catlen 1) 1)) ; oddp from cl.el
748 (padding (/ (- len catlen) 2)))
749 (insert-button (concat (make-string padding 32) cat
750 (make-string (if catlen-odd
751 (1+ padding)
752 padding)
753 32))
754 'face 'tool-bar
755 'action
756 `(lambda (button)
757 (todos-jump-to-category-noninteractively ,cat)))
758 (insert (make-string 8 32)
759 "(not done: "
760 (number-to-string (car (cadr (assoc cat cat-alist))))
761 ", done: "
762 (number-to-string (cdr (cadr (assoc cat cat-alist))))
763 ")")
764 (newline)))
765 categories))))
766 ;; (require 'widget)
767 ;; (eval-when-compile
768 ;; (require 'wid-edit))
769 ;; (with-current-buffer (get-buffer-create todos-categories-buffer)
770 ;; (switch-to-buffer (current-buffer))
771 ;; (erase-buffer)
772 ;; (kill-all-local-variables)
773 ;; (widget-insert "Press a button to display the corresponding category.\n\n")
774 ;; (setq beg (point))
775 ;; (mapc (lambda (cat)
776 ;; (widget-create 'push-button
777 ;; :notify (lambda (widget &rest ignore)
778 ;; (todos-jump-to-category-noninteractively
779 ;; (widget-get widget :value)))
780
781 ;; cat)
782 ;; (widget-insert " (not done: "
783 ;; (number-to-string (car (cadr (assoc cat cat-alist))))
784 ;; ", done: "
785 ;; (number-to-string (cdr (cadr (assoc cat cat-alist))))
786 ;; ")\n"))
787 ;; categories)
788 ;; (use-local-map widget-keymap)
789 ;; (widget-setup))))
790
791(defun todos-toggle-item-numbering ()
792 "" ;FIXME
793 (interactive)
794 (todos-reset-prefix 'todos-number-prefix (not todos-number-prefix)))
744 795
745(defun todos-quit () 796(defun todos-toggle-view-done-items ()
746 "Done with TODO list for now." 797 "" ; FIXME
747 (interactive) 798 (interactive)
748 (widen) 799 (let ((beg (point-min))
749 (todos-save) 800 (done-sep (if (string-match "^[[:space:]]*$" todos-done-separator)
750 (message "") 801 todos-done-separator
751 (bury-buffer)) 802 (propertize (concat todos-done-separator "\n")
803 'face 'todos-done-sep)))
804 (todos-show-with-done nil)
805 (done (point-max))
806 end ov)
807 (save-excursion
808 (goto-char beg)
809 (if (re-search-forward (concat "\n\\[" (regexp-quote todos-done-string))
810 nil t)
811 ;; hide done items
812 (progn (setq end (match-beginning 0))
813 (narrow-to-region beg end))
814 (widen)
815 (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t)
816 (setq end (or (match-beginning 0) (point-max)))
817 (goto-char beg)
818 (if (re-search-forward
819 (concat (if (eq beg done) "" "\n") ; no newline if no unfinished items
820 "\n\\(\\[" (regexp-quote todos-done-string) "\\)")
821 end t)
822 ;; show done items
823 (let ((prefix (propertize
824 (concat (if todos-number-prefix "1" todos-prefix) " ")
825 'face 'todos-prefix-string))
826 ov-done ov-pref)
827 (setq done (match-beginning 1))
828 (narrow-to-region beg end)
829 (todos-prefix-overlays)
830 ;; add non-empty separator overlay in front of prefix overlay on
831 ;; first done item
832 (unless (string= done-sep todos-done-separator)
833 (goto-char done)
834 (remove-overlays done done)
835 ;; must make separator overlay after making prefix overlay to
836 ;; get the order separator before prefix
837 (setq ov-pref (make-overlay done done)
838 ov-done (make-overlay done done))
839 (overlay-put ov-pref 'before-string prefix)
840 (overlay-put ov-done 'before-string done-sep)))
841 ;; (when (setq ov (car (overlays-in done done)))
842 ;; (when (equal (overlay-get ov 'before-string) done-sep)
843 ;; (push ov todos-done-overlays)
844 ;; (delete-overlay ov)))
845 (todos-category-select)
846 (error "No done items in this category"))))))
847
848(defun todos-view-archive (&optional cat)
849 ""
850 (interactive)
851 (if (file-exists-p todos-archive-file)
852 (progn
853 (find-file todos-archive-file)
854 (if cat
855 (if (member cat (todos-categories-list (current-buffer)))
856 (todos-jump-to-category-noninteractively cat)
857 (error "No archived items from this category"))
858 (todos-category-select)))
859 (error "There is currently no Todos archive")))
860
861;; FIXME: very slow
862(defun todos-diary-items ()
863 "Display all todo items marked for diary inclusion."
864 (interactive)
865 (let ((bufname "*Todo diary entries*")
866 opoint)
867 (save-restriction
868 (save-current-buffer
869 (widen)
870 (copy-to-buffer bufname (point-min) (point-max))))
871 (with-current-buffer bufname
872 ;; (todos-mode)
873 (goto-char (point-min))
874 (while (not (eobp))
875 (setq opoint (point))
876 (cond ((looking-at "\\[")
877 (progn
878 (todos-forward-item)
879 (if (string-match
880 (concat "^" (regexp-quote todos-category-beg) ".*$")
881 (buffer-substring opoint (point)))
882 (kill-region opoint (+ opoint (match-beginning 0)))
883 (kill-region opoint (point)))))
884 ((looking-at "^$")
885 (kill-line))
886 (t
887 (todos-forward-item))))
888 (goto-char (point-min))
889 (while (not (eobp))
890 (setq opoint (point))
891 (if (looking-at (regexp-quote todos-category-beg))
892 (when (progn (forward-line)
893 (or (looking-at (regexp-quote todos-category-beg))
894 ;; category has done but no unfinished items
895 (and (looking-at "^$") (forward-line))
896 (eobp)))
897 (kill-region opoint (point)))
898 (forward-line)))
899 (make-local-variable 'font-lock-defaults)
900 (setq font-lock-defaults '(todos-font-lock-keywords t))
901 (font-lock-fontify-buffer)
902 (setq buffer-read-only t))
903 (display-buffer bufname)))
752 904
753(defun todos-edit-item () 905(defun todos-highlight-item ()
754 "Edit current TODO list entry." 906 "Highlight the todo item the cursor is on."
755 (interactive) 907 (interactive)
756 (let ((item (todos-item-string))) 908 (if hl-line-mode ; todos-highlight-item
757 (if (todos-string-multiline-p item) 909 (hl-line-mode 0)
758 (todos-edit-multiline) 910 (hl-line-mode 1)))
759 (let ((new (read-from-minibuffer "Edit: " item)))
760 (todos-remove-item)
761 (insert new todos-item-end "\n")
762 (todos-backward-item)
763 (if todos-number-prefix
764 (todos-update-numbered-prefix)
765 (todos-item-overlays))))))
766 911
767;; FIXME to work with overlays 912;; FIXME: make this a customizable option for whole Todos file
768(defun todos-edit-multiline () 913(defun todos-toggle-display-date-time ()
769 "Set up a buffer for editing a multiline TODO list entry." 914 "" ; FIXME
770 (interactive) 915 (interactive)
771 (let ((buffer-name (generate-new-buffer-name todos-edit-buffer))) 916 (save-excursion
772 (switch-to-buffer 917 (goto-char (point-min))
773 (make-indirect-buffer 918 (let ((ovs (overlays-in (point) (line-end-position)))
774 (file-name-nondirectory todos-file-do) buffer-name)) 919 ov hidden)
775 (message "To exit, simply kill this buffer and return to list.") 920 (while ovs
776 (todos-edit-mode) 921 (setq ov (car ovs))
777 (narrow-to-region (todos-item-start) (todos-item-end)))) 922 (if (equal (overlay-get ov 'display) "")
923 (setq ovs nil
924 hidden t)
925 (setq ovs (cdr ovs))))
926 (if hidden (remove-overlays (point-min) (point-max) 'display "")
927 (while (not (eobp))
928 (re-search-forward (concat "^\\[?" todos-date-pattern
929 " \\(" diary-time-regexp "\\)?\\]? ")
930 ; FIXME: this space in header? ^
931 nil t)
932 (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t))
933 (overlay-put ov 'display "")
934 (forward-line)))
935 (todos-update-numbered-prefix))))
936
937;;;###autoload
938(defun todos-top-priorities (&optional nof-priorities category-pr-page show-done)
939 "List top priorities for each category.
940
941Number of entries for each category is given by NOF-PRIORITIES which
942defaults to \'todos-show-priorities\'.
943
944If CATEGORY-PR-PAGE is non-nil, a page separator \'^L\' is inserted
945between each category.
946
947With non-nil SHOW-DONE, include done items in the listing."
948
949 (interactive "P")
950 (or nof-priorities (setq nof-priorities todos-show-priorities))
951 (if (listp nof-priorities) ;universal argument
952 (setq nof-priorities (car nof-priorities)))
953 (let ((todos-print-buffer-name todos-tmp-buffer-name)
954 (todos-category-break (if category-pr-page " " ""))
955 beg end done)
956 (save-excursion
957 (todos-show))
958 (save-restriction
959 (save-current-buffer
960 (widen)
961 (if (buffer-live-p (get-buffer todos-print-buffer-name))
962 (kill-buffer todos-print-buffer-name))
963 (copy-to-buffer todos-print-buffer-name (point-min) (point-max))))
964 (with-current-buffer todos-print-buffer-name
965 (goto-char (point-min))
966 (while (re-search-forward ;Find category start
967 (concat "^" (regexp-quote todos-category-beg))
968 nil t)
969 (setq beg (+ (line-end-position) 1)) ;Start of first entry.
970 (setq end (if (re-search-forward todos-category-beg nil t)
971 (match-beginning 0)
972 (point-max)))
973 (goto-char beg)
974 (setq done
975 (if (re-search-forward
976 (concat
977 (if (looking-at "^$") "" "\n") ; no unfinished items
978 "\n\\(\\[" (regexp-quote todos-done-string) "\\)")
979 end t)
980 (match-beginning 1)
981 end))
982 (unless show-done
983 (delete-region done end)
984 (setq end done))
985 (narrow-to-region beg end) ;In case we have too few entries.
986 (goto-char (point-min))
987 (if (zerop nof-priorities) ;Traverse entries.
988 (goto-char end) ;All entries
989 (todos-forward-item nof-priorities))
990 (setq beg (point))
991 (delete-region beg end)
992 (widen))
993 (and (looking-at " ") (replace-match "")) ;Remove trailing form-feed.
994 (goto-char (point-min)) ;Due to display buffer
995 (make-local-variable 'font-lock-defaults)
996 (setq font-lock-defaults '(todos-font-lock-keywords t))
997 (font-lock-fontify-buffer)
998 (setq buffer-read-only t))
999 ;; Could have used switch-to-buffer as it has a norecord argument,
1000 ;; which is nice when we are called from e.g. todos-print.
1001 ;; Else we could have used pop-to-buffer.
1002 ;; (display-buffer todos-print-buffer-name)
1003 (display-buffer todos-print-buffer-name)
1004 (message "Type C-x 1 to remove %s window. M-C-v to scroll the help."
1005 todos-print-buffer-name)))
1006
1007;;; Editing
778 1008
779;;;###autoload 1009;;;###autoload
780(defun todos-add-category (&optional cat) 1010(defun todos-add-category (&optional cat)
781 "Add new category CAT to the TODO list." 1011 "Add new category CAT to the TODO list."
782 (interactive) 1012 (interactive)
783 (let ((buf (find-file-noselect todos-file-do t)) 1013 (let ((buffer-read-only)
1014 (buf (find-file-noselect todos-file-do t))
784 (prompt "Category: ")) 1015 (prompt "Category: "))
785 (unless (zerop (buffer-size buf)) 1016 (unless (zerop (buffer-size buf))
786 (and (null todos-categories) 1017 (and (null todos-categories)
@@ -801,54 +1032,25 @@ In Todos mode it is made invisible with an overlay.")
801 (setq todos-categories (cons cat todos-categories)) 1032 (setq todos-categories (cons cat todos-categories))
802 (widen) 1033 (widen)
803 (goto-char (point-min)) 1034 (goto-char (point-min))
804 ;; (insert (format "%s\n%s%s\n%s\n" todos-category-sep todos-category-beg cat 1035 ;; make sure file does not begin with empty lines (shouldn't, but may be
805 ;; todos-category-end)) 1036 ;; added by mistake), otherwise new categories will contain them, so
1037 ;; won't be really empty
1038 (while (looking-at "^$") (kill-line))
806 (insert todos-category-beg cat "\n") 1039 (insert todos-category-beg cat "\n")
807 (if (interactive-p) 1040 (if (interactive-p)
808 ;; properly display the newly added category 1041 ;; properly display the newly added category
809 (progn (setq todos-category-number 0) (todos-show)) 1042 (progn (setq todos-category-number 0) (todos-show))
810 0)))) 1043 0))))
811 1044
812;;;###autoload 1045;; FIXME: use function for category name choice here and in todos-add-category
813(defun todos-add-item-non-interactively (new-item category) 1046(defun todos-rename-category ()
814 "Insert NEW-ITEM in TODO list as a new entry in CATEGORY."
815 ;; FIXME: really need this? (and in save-excursion?)
816 (save-excursion
817 (todos-show))
818 ;; (save-excursion
819 (if (string= "" category)
820 (setq category (nth todos-category-number todos-categories)))
821 (let ((cat-exists (member category todos-categories)))
822 (setq todos-category-number
823 (if cat-exists
824 (- (length todos-categories) (length cat-exists))
825 (todos-add-category category))))
826 ;; FIXME: really need this? (yes for todos-move-item, to show moved to category)
827 (todos-show)
828 (setq todos-previous-line 0)
829 (let ((top 1)
830 (bottom (1+ (count-lines (point-min) (point-max)))))
831 (while (> (- bottom top) todos-insert-threshold)
832 (let* ((current (/ (+ top bottom) 2))
833 (answer (if (< current bottom)
834 (todos-more-important-p current) nil)))
835 (if answer
836 (setq bottom current)
837 (setq top (1+ current)))))
838 (setq top (/ (+ top bottom) 2))
839 ;; goto-line doesn't have the desired behavior in a narrowed buffer.
840 (goto-char (point-min))
841 (forward-line (1- top)))
842 (todos-insert-with-overlays new-item)
843 ;; (todos-show-paren-hack)
844 );)
845
846(defun todos-rename-category (new)
847 "Rename current Todos category." 1047 "Rename current Todos category."
848 (interactive "sCategory: ") 1048 (interactive)
849 (let ((cat (nth todos-category-number todos-categories)) 1049 (let* ((buffer-read-only)
850 (vec (vconcat todos-categories)) 1050 (cat (nth todos-category-number todos-categories))
851 prompt) 1051 (vec (vconcat todos-categories))
1052 (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat)))
1053 prompt)
852 (while (and (cond ((string= "" new) 1054 (while (and (cond ((string= "" new)
853 (setq prompt "Enter a non-empty category name: ")) 1055 (setq prompt "Enter a non-empty category name: "))
854 ((string-match "\\`\\s-+\\'" new) 1056 ((string-match "\\`\\s-+\\'" new)
@@ -860,12 +1062,8 @@ In Todos mode it is made invisible with an overlay.")
860 (setq todos-categories (append vec nil)) 1062 (setq todos-categories (append vec nil))
861 (save-excursion 1063 (save-excursion
862 (widen) 1064 (widen)
863 (re-search-backward (concat ;(regexp-quote todos-category-sep) "\n" 1065 (re-search-backward (concat (regexp-quote todos-category-beg) "\\("
864 (regexp-quote todos-category-beg) "\\(" 1066 (regexp-quote cat) "\\)\n") nil t)
865 (regexp-quote cat) "\\)\n") (point-min) t)
866 ;; (goto-char (match-end 0))
867 ;; (when (looking-at (regexp-quote cat))
868 ;; (replace-match new t))
869 (replace-match new t t nil 1) 1067 (replace-match new t t nil 1)
870 (goto-char (point-min)) 1068 (goto-char (point-min))
871 (setq mode-line-buffer-identification 1069 (setq mode-line-buffer-identification
@@ -879,161 +1077,242 @@ With ARG non-nil delete the category unconditionally,
879i.e. including all existing entries." 1077i.e. including all existing entries."
880 (interactive "P") 1078 (interactive "P")
881 (if (and (null arg) 1079 (if (and (null arg)
1080 ;; FIXME: what about done items?
882 (not (eq (point-max) (point-min)))) 1081 (not (eq (point-max) (point-min))))
883 (message "This category is not empty, so it cannot be deleted") 1082 (message "To delete a non-empty category, call the command with a prefix argument.")
884 (let ((cat (nth todos-category-number todos-categories)) beg end) 1083 (let ((cat (nth todos-category-number todos-categories)) beg end)
885 (when (y-or-n-p (concat "Permanently remove category \"" cat 1084 (when (y-or-n-p (concat "Permanently remove category \"" cat
886 "\"" (and arg " and all its entries") "? ")) 1085 "\"" (and arg " and all its entries") "? "))
887 (widen) 1086 (let ((buffer-read-only))
888 (setq beg (re-search-backward 1087 (widen)
889 (concat "^" ;(regexp-quote todos-category-sep) "\n" 1088 (setq beg (re-search-backward (concat "^" (regexp-quote todos-category-beg)
890 (regexp-quote todos-category-beg) cat "\n") 1089 cat "\n") nil t)
891 (point-min) nil) 1090 end (progn
892 end (progn 1091 (re-search-forward (concat "\n\\("
893 (re-search-forward 1092 (regexp-quote todos-category-beg)
894 ;; (concat "^" (regexp-quote todos-category-end) "\n"))) 1093 ".*\n\\)") nil t)
895 (concat "\n" (regexp-quote todos-category-beg) ".*\n") 1094 (match-beginning 1)))
896 (point-max) t) 1095 (remove-overlays beg end)
897 (match-beginning 0))) 1096 (kill-region beg end)
898 (remove-overlays beg end) 1097 (setq todos-categories (delete cat todos-categories))
899 (kill-region beg end) 1098 (todos-category-select)
900 (setq todos-categories (delete cat todos-categories)) 1099 (message "Deleted category %s" cat))))))
901 (todos-category-select)
902 (message "Deleted category %s" cat)))))
903 1100
904(defcustom todos-categories-buffer "*TODOS Categories*" 1101;;;###autoload
905 "Name of buffer displayed by `todos-display-categories'" 1102(defun todos-insert-item (&optional arg here date-time) ; FIXME revise docstring
906 :type 'string 1103 "Insert new TODO list item.
907 :group 'todos)
908 1104
909(defun todos-display-categories () 1105With prefix argument ARG solicit the category, otherwise use the
910 "Display an alphabetical list of clickable Todos category names. 1106current category.
911Click or type RET on a category name to go to it."
912 (interactive)
913 ;; (setq todos-window-configuration (current-window-configuration))
914 (let ((categories (copy-sequence todos-categories))
915 beg)
916 ;; alphabetize the list case insensitively
917 (setq categories (sort categories (lambda (s1 s2) (let ((cis1 (upcase s1))
918 (cis2 (upcase s2)))
919 (string< cis1 cis2)))))
920 (require 'widget)
921 (eval-when-compile
922 (require 'wid-edit))
923 (with-current-buffer (get-buffer-create todos-categories-buffer)
924 (switch-to-buffer (current-buffer))
925 (erase-buffer)
926 (kill-all-local-variables)
927 (widget-insert "Press a button to display the corresponding category.\n\n")
928 (setq beg (point))
929 (mapc (lambda (cat)
930 (widget-create 'push-button
931 :notify (lambda (widget &rest ignore)
932 (todos-jump-to-category-noninteractively
933 (widget-get widget :value)))
934
935 cat)
936 (widget-insert "\n"))
937 categories)
938 (use-local-map widget-keymap)
939 (widget-setup))))
940 1107
941(defun todos-jump-to-category-noninteractively (cat) 1108With non-nil argument HERE insert the new item directly above the
942 (let ((name todos-categories-buffer)) 1109item at point. If point is on an empty line, insert the new item
943 (if (string= (buffer-name) name) 1110there.
944 (kill-buffer name)))
945 ;; (set-window-configuration todos-window-configuration)
946 (switch-to-buffer (file-name-nondirectory todos-file-do))
947 (widen)
948 (goto-char (point-min))
949 (setq todos-category-number (- (length todos-categories)
950 (length (member cat todos-categories))))
951 (todos-category-select))
952 1111
953;;;###autoload 1112If the value of TIME is `omit', insert the item without a time
954(defun todos-insert-item (arg) 1113string; with the value `ask', solicit a time string; with any
955 "Insert new TODO list entry. 1114other value, add or omit the current time in accordance with
956With a prefix argument solicit the category, otherwise use the current 1115`todos-add-time-string'."
957category."
958 (interactive "P") 1116 (interactive "P")
959 ;; (save-excursion 1117 (unless (or (todos-done-item-p)
1118 (save-excursion (forward-line -1) (todos-done-item-p)))
960 (if (not (derived-mode-p 'todos-mode)) (todos-show)) 1119 (if (not (derived-mode-p 'todos-mode)) (todos-show))
961 (let* ((new-item (concat (unless todos-include-in-diary "[") 1120 (let* ((buffer-read-only)
962 (todos-current-date todos-add-time-string) 1121 (date-string (cond ;; ((eq date-time 'omit) "")
1122 ((eq date-time 'ask)
1123 (read-from-minibuffer "Enter a date: "))
1124 ((eq date-time 'to-date)
1125 (with-current-buffer "*Calendar*"
1126 (calendar-date-string (calendar-cursor-to-date t) t t)))
1127 (t (calendar-date-string (calendar-current-date) t t))))
1128 (time-string (if todos-add-time-string
1129 (cond ((eq date-time 'omit) "")
1130 ((eq date-time 'ask)
1131 (read-from-minibuffer "Enter a clock time: "))
1132 (t (substring (current-time-string) 11 16)))
1133 ""))
1134 (new-item (concat (unless todos-include-in-diary "[")
1135 date-string (unless (string= time-string "")
1136 (concat " " time-string))
963 (unless todos-include-in-diary "]") " " 1137 (unless todos-include-in-diary "]") " "
964 (read-from-minibuffer "New TODO entry: "))) 1138 (read-from-minibuffer "New TODO entry: ")))
965 (current-category (nth todos-category-number todos-categories)) 1139 (current-category (nth todos-category-number todos-categories))
966 (category (if arg (todos-completing-read) current-category))) 1140 (category (if arg (todos-completing-read) current-category)))
967 (todos-add-item-non-interactively new-item category)));) 1141 (if here
1142 (todos-insert-with-overlays new-item)
1143 (todos-add-item-non-interactively new-item category)))))
968 1144
969(defun todos-insert-item-here () 1145(defun todos-insert-item-here (&optional date-time)
970 "Insert a new TODO list entry directly above the entry at point. 1146 "" ;FIXME add docstring
971If point is on an empty line, insert the entry there."
972 (interactive) 1147 (interactive)
973 (if (not (derived-mode-p 'todos-mode)) (todos-show)) 1148 (todos-insert-item nil t date-time))
974 (let ((new (concat (unless todos-include-in-diary "[")
975 (todos-current-date todos-add-time-string)
976 (unless todos-include-in-diary "]") " "
977 (read-from-minibuffer "New TODO entry: "))))
978 (todos-insert-with-overlays new)))
979 1149
980(defun todos-insert-with-overlays (item) 1150(defun todos-insert-item-no-time (&optional here)
981 "" ;FIXME add docstring 1151 "" ;FIXME add docstring
982 (let (ov-start ov-end p1 p2) 1152 (interactive)
983 (unless (and (bolp) (eolp)) (goto-char (todos-item-start))) 1153 (todos-insert-item nil here 'omit))
984 (insert item todos-item-end "\n") 1154
985 (todos-backward-item) 1155(defun todos-insert-item-ask-date (&optional here)
986 (if todos-number-prefix 1156 "" ;FIXME add docstring
987 (todos-update-numbered-prefix) 1157 (interactive)
988 (todos-item-overlays)))) 1158 (todos-insert-item nil here 'ask))
989 1159
990(defun todos-more-important-p (line) 1160(defun todos-insert-item-for-diary (&optional arg here date-time)
991 "Ask whether entry is more important than the one at LINE." 1161 "" ;FIXME
992 (unless (equal todos-previous-line line) 1162 (interactive "P")
993 (setq todos-previous-line line) 1163 (let ((todos-include-in-diary t))
994 (goto-char (point-min)) 1164 (todos-insert-item arg here date-time)))
995 (forward-line (1- todos-previous-line)) 1165
996 (let ((item (todos-item-string-start))) 1166;; FIXME: autoload when key-binding is defined in calendar.el
997 (setq todos-previous-answer 1167(defun todos-insert-item-from-calendar ()
998 (y-or-n-p (concat "More important than '" item "'? "))))) 1168 "" ;FIXME
999 todos-previous-answer) 1169 (interactive)
1170 (pop-to-buffer (file-name-nondirectory todos-file-do))
1171 (todos-show)
1172 (todos-insert-item t nil 'to-date))
1173
1174;; FIXME: calendar is loaded before todos
1175;; (add-hook 'calendar-load-hook
1176 ;; (lambda ()
1177 (define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);))
1000 1178
1001(defun todos-delete-item () 1179(defun todos-delete-item ()
1002 "Delete current TODO list entry." 1180 "Delete current TODO list entry."
1003 (interactive) 1181 (interactive)
1004 (if (> (count-lines (point-min) (point-max)) 0) 1182 (if (> (count-lines (point-min) (point-max)) 0)
1005 (let* ((todos-entry (todos-item-string-start)) 1183 (let* ((buffer-read-only)
1184 (todos-entry (todos-item-string-start))
1006 (todos-answer (y-or-n-p (concat "Permanently remove '" 1185 (todos-answer (y-or-n-p (concat "Permanently remove '"
1007 todos-entry "'? ")))) 1186 todos-entry "'? "))))
1008 (when todos-answer 1187 (when todos-answer
1009 (todos-remove-item) 1188 (todos-remove-item)
1010 (when (and (bolp) (eolp)) (todos-backward-item)) 1189 (when (and (bolp) (eolp)
1190 ;; not if last item was deleted
1191 (< (point-min) (point-max)))
1192 (todos-backward-item))
1193 ;; FIXME: is todos-prefix-overlays part of if-sexp, and is it needed
1194 ;; at all?
1011 (if todos-number-prefix 1195 (if todos-number-prefix
1012 (todos-update-numbered-prefix) 1196 (todos-update-numbered-prefix)
1013 (todos-item-overlays)))) 1197 (todos-prefix-overlays))))
1014 (error "No TODO list entry to delete"))) 1198 (error "No TODO list entry to delete")))
1015 1199
1200(defun todos-edit-item ()
1201 "Edit current TODO list entry."
1202 (interactive)
1203 (let ((buffer-read-only)
1204 (item (todos-item-string))
1205 (opoint (point)))
1206 (if (todos-string-multiline-p item)
1207 (todos-edit-multiline)
1208 (let ((new (read-from-minibuffer "Edit: " item)))
1209 (while (not (string-match (concat "^\\[?" todos-date-pattern) new))
1210 (setq new (read-from-minibuffer "Item must start with a date: " new)))
1211 ;; If user moved point during editing, make sure it moves back.
1212 (goto-char opoint)
1213 (todos-remove-item)
1214 (todos-insert-with-overlays new)))))
1215
1216;; FIXME: run todos-check-format on exiting buffer (or check for date string
1217;; and indentation)
1218(defun todos-edit-multiline ()
1219 "Set up a buffer for editing a multiline TODO list entry."
1220 (interactive)
1221 (let ((buffer-name (generate-new-buffer-name todos-edit-buffer)))
1222 (switch-to-buffer
1223 (make-indirect-buffer
1224 (file-name-nondirectory todos-file-do) buffer-name))
1225 (message "To exit, simply kill this buffer and return to list.")
1226 (todos-edit-mode)
1227 (narrow-to-region (todos-item-start) (todos-item-end))))
1228
1229(defun todos-edit-quit ()
1230 "" ;FIXME
1231 (interactive)
1232 (save-excursion (todos-category-select)))
1233
1234;; FIXME
1235(defun todos-change-date (&optional event)
1236 "" ;FIXME
1237 (interactive)
1238 (let (dmarker
1239 calendar-view-diary-initially-flag
1240 new-date)
1241 (save-excursion
1242 (todos-item-start)
1243 (setq dmarker (point-marker)))
1244 (calendar)
1245 (message "Put the cursor on the desired date in the Calendar and press `q'")
1246 (setq new-date
1247 (calendar-date-string (calendar-cursor-to-date t) t t))
1248 ;; (pop-to-buffer (file-name-nondirectory todos-file-do))
1249 ;; (todos-show)
1250 (when (eq last-command 'calendar-exit)
1251 (goto-char (marker-position dmarker))
1252 (re-search-forward (concat "^\\[?\\(" todos-date-pattern "\\)\\]?")
1253 (line-end-position) t)
1254 (replace-match new-date nil nil nil 1))))
1255
1016(defun todos-raise-item () 1256(defun todos-raise-item ()
1017 "Raise priority of current entry." 1257 "Raise priority of current entry."
1018 (interactive) 1258 (interactive)
1019 (if (and (not (and (bolp) (eolp))) 1259 (unless (or (todos-done-item-p)
1020 (> (count-lines (point-min) (point)) 0)) 1260 (looking-at "^$")) ; between done and not done items
1021 (let ((item (todos-item-string))) 1261 (let (buffer-read-only)
1022 (todos-remove-item) 1262 (if (> (count-lines (point-min) (point)) 0)
1023 (todos-backward-item) 1263 (let ((item (todos-item-string)))
1024 (todos-insert-with-overlays item)) 1264 (todos-remove-item)
1025 (error "No TODO list entry to raise"))) 1265 (todos-backward-item)
1266 (todos-insert-with-overlays item))
1267 (error "No TODO list entry to raise")))))
1026 1268
1027(defun todos-lower-item () 1269(defun todos-lower-item ()
1028 "Lower priority of current entry." 1270 "Lower priority of current entry."
1029 (interactive) 1271 (interactive)
1030 (if (> (count-lines (point) (point-max)) 1) 1272 (unless (or (todos-done-item-p)
1031 ;; Assume there is a final newline 1273 (looking-at "^$")) ; between done and not done items
1032 (let ((item (todos-item-string))) 1274 (let ((buffer-read-only)
1033 (todos-remove-item) 1275 (done (save-excursion
1034 (todos-forward-item) 1276 (if (re-search-forward (concat "\n\n\\\["
1035 (todos-insert-with-overlays item)) 1277 (regexp-quote todos-done-string))
1036 (error "No TODO list entry to lower"))) 1278 nil t)
1279 (match-beginning 0)
1280 (point-max)))))
1281 (if (> (count-lines (point) done) 1)
1282 ;; Assume there is a final newline
1283 (let ((item (todos-item-string))
1284 opoint)
1285 (todos-remove-item)
1286 (todos-forward-item)
1287 (todos-insert-with-overlays item))
1288 (error "No TODO list entry to lower")))))
1289
1290;; FIXME: moves last not done item when point on empty line below it
1291;; (defun todos-move-item ()
1292;; "Move the current todo item to another, interactively named, category.
1293
1294;; If the named category is not one of the current todo categories, then
1295;; it is created and the item becomes the first entry in that category."
1296;; (interactive)
1297;; (unless (or (todos-done-item-p)
1298;; (looking-at "^$")) ; between done and not done items
1299;; (let ((item (todos-item-string))
1300;; (category (todos-completing-read))
1301;; orig moved)
1302;; (setq (save-excursion (todos-item-start)))
1303;; (todos-remove-item)
1304;; ;; numbered prefix isn't cached (see todos-remove-item) so have to update
1305;; (if todos-number-prefix (todos-update-numbered-prefix))
1306;; (setq chgr (prepare-change-group))
1307;; ;; FIXME
1308;; (unwind-protect
1309;; (progn
1310;; (activate-change-group chgr)
1311;; (todos-add-item-non-interactively item category)
1312;; (setq moved t))
1313;; (if moved
1314;; (accept-change-group chgr)
1315;; (cancel-change-group chgr))))))
1037 1316
1038(defun todos-move-item () 1317(defun todos-move-item ()
1039 "Move the current todo item to another, interactively named, category. 1318 "Move the current todo item to another, interactively named, category.
@@ -1041,111 +1320,175 @@ If point is on an empty line, insert the entry there."
1041If the named category is not one of the current todo categories, then 1320If the named category is not one of the current todo categories, then
1042it is created and the item becomes the first entry in that category." 1321it is created and the item becomes the first entry in that category."
1043 (interactive) 1322 (interactive)
1044 (let ((item (todos-item-string)) 1323 (unless (or (todos-done-item-p)
1045 (inhibit-quit t) 1324 (looking-at "^$")) ; between done and not done items
1046 (category (todos-completing-read))) 1325 (let ((buffer-read-only)
1047 (todos-remove-item) 1326 (oldnum todos-category-number)
1048 (todos-add-item-non-interactively item category))) 1327 (oldcat (nth todos-category-number todos-categories))
1049 1328 (item (todos-item-string))
1050(defun todos-file-item (&optional comment) 1329 (newcat (todos-completing-read))
1051 "File the current TODO list entry away, annotated with an optional COMMENT." 1330 (opoint (point))
1052 (interactive "sComment: ") 1331 (orig-mrk (save-excursion (todos-item-start) (point-marker)))
1053 (or (> (count-lines (point-min) (point-max)) 0) 1332 moved)
1054 (error "No TODO list entry to file away")) 1333 (todos-remove-item)
1055 (let ((time-stamp-format todos-time-string-format)) 1334 ;; numbered prefix isn't cached (see todos-remove-item) so have to update
1056 (when (and comment (> (length comment) 0)) 1335 (if todos-number-prefix (todos-update-numbered-prefix))
1057 (goto-char (todos-item-end)) 1336 (unwind-protect
1058 (insert 1337 (progn
1059 (if (save-excursion (beginning-of-line) 1338 (todos-add-item-non-interactively item newcat)
1060 (looking-at (regexp-quote todos-prefix))) 1339 (setq moved t))
1061 " " 1340 (unless moved
1062 "\n\t") 1341 (widen)
1063 "(" comment ")")) 1342 (goto-char orig-mrk)
1064 (goto-char (todos-item-end)) 1343 (todos-insert-with-overlays item)
1065 (insert " [" (nth todos-category-number todos-categories) "]") 1344 (setq todos-category-number oldnum)
1066 (goto-char (todos-item-start)) 1345 (todos-category-select)
1067 (let ((temp-point (point))) 1346 ;; FIXME: does this work?
1068 (if (looking-at (regexp-quote todos-prefix)) 1347 (goto-char opoint))
1069 (replace-match (time-stamp-string)) 1348 (set-marker orig-mrk nil)))))
1070 ;; Standard prefix -> timestamp 1349
1071 ;; Else prefix non-standard item start with timestamp 1350;; (defun todos-file-item (&optional comment)
1072 (insert (time-stamp-string))) 1351;; "File the current TODO list entry away, annotated with an optional COMMENT."
1073 (append-to-file temp-point (1+ (todos-item-end)) todos-file-done) 1352;; (interactive "sComment: ")
1074 (delete-region temp-point (1+ (todos-item-end)))) 1353;; (or (> (count-lines (point-min) (point-max)) 0)
1075 (todos-backward-item) 1354;; (error "No TODO list entry to file away"))
1076 (message ""))) 1355;; (let ((time-stamp-format todos-time-string-format))
1077 1356;; (when (and comment (> (length comment) 0))
1078(defun todos-highlight-item () 1357;; (goto-char (todos-item-end))
1079 "Highlight the todo item the cursor is on." 1358;; (insert
1359;; (if (save-excursion (beginning-of-line)
1360;; (looking-at (regexp-quote todos-prefix)))
1361;; " "
1362;; "\n\t")
1363;; "(" comment ")"))
1364;; (goto-char (todos-item-end))
1365;; (insert " [" (nth todos-category-number todos-categories) "]")
1366;; (goto-char (todos-item-start))
1367;; (let ((temp-point (point)))
1368;; (if (looking-at (regexp-quote todos-prefix))
1369;; (replace-match (time-stamp-string))
1370;; ;; Standard prefix -> timestamp
1371;; ;; Else prefix non-standard item start with timestamp
1372;; (insert (time-stamp-string)))
1373;; (append-to-file temp-point (1+ (todos-item-end)) todos-file-done)
1374;; (delete-region temp-point (1+ (todos-item-end))))
1375;; (todos-backward-item)
1376;; (message ""))
1377
1378(defun todos-item-done ()
1379 "Mark current item as done and move it to category's done section."
1080 (interactive) 1380 (interactive)
1081 (if hl-line-mode ; todos-highlight-item 1381 (unless (or (todos-done-item-p)
1082 (hl-line-mode 0) 1382 (looking-at "^$"))
1083 (hl-line-mode 1))) 1383 (let* ((buffer-read-only)
1084;; --------------------------------------------------------------------------- 1384 (item (todos-item-string))
1085 1385 (date-string (calendar-date-string (calendar-current-date) t t))
1086;; Utility functions: 1386 (time-string (if todos-add-time-string
1087 1387 (concat " " (substring (current-time-string) 11 16))
1088 1388 ""))
1089;;;###autoload 1389 (done-item (concat "[" todos-done-string date-string time-string "] " item))
1090(defun todos-top-priorities (&optional nof-priorities category-pr-page) 1390 (items-end (point-max))
1091 "List top priorities for each category. 1391 next-cat)
1092 1392 (todos-remove-item)
1093Number of entries for each category is given by NOF-PRIORITIES which 1393 (save-excursion
1094defaults to \'todos-show-priorities\'. 1394 (widen)
1095 1395 (setq next-cat
1096If CATEGORY-PR-PAGE is non-nil, a page separator \'^L\' is inserted 1396 (save-excursion
1097between each category." 1397 (if (re-search-forward (concat "^" (regexp-quote todos-category-beg))
1398 nil t)
1399 (match-beginning 0)
1400 (point-max))))
1401 ;; insert next done item at the top of the done items list
1402 (if (re-search-forward (concat "^\\[" (regexp-quote todos-done-string))
1403 next-cat t)
1404 (goto-char (match-beginning 0))
1405 ;; need empty line between done and not done items in order not to have
1406 ;; hanging todos-prefix when done items are hidden
1407 (goto-char next-cat)
1408 (newline))
1409 (todos-insert-with-overlays done-item)))
1410 (todos-show)))
1098 1411
1099 (interactive "P") 1412(defun todos-archive-done-items ()
1100 (or nof-priorities (setq nof-priorities todos-show-priorities)) 1413 "Archive the done items in the current category."
1101 (if (listp nof-priorities) ;universal argument 1414 (interactive)
1102 (setq nof-priorities (car nof-priorities))) 1415 (let ((archive (find-file-noselect todos-archive-file t))
1103 (let ((todos-print-buffer-name todos-tmp-buffer-name) 1416 (cat (nth todos-category-number todos-categories))
1104 ;;(todos-print-category-number 0) 1417 beg end)
1105 (todos-category-break (if category-pr-page " " "")) 1418 (save-excursion
1106 (cat-end
1107 (concat
1108 (if todos-remove-separator
1109 (concat todos-category-end "\n"
1110 (regexp-quote todos-prefix) " " todos-category-sep "\n")
1111 (concat todos-category-end "\n"))))
1112 beg end)
1113 (save-excursion ; FIXME: need this?
1114 (todos-show)
1115 (save-restriction 1419 (save-restriction
1116 (save-current-buffer 1420 (widen)
1117 (widen) 1421 (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t)
1118 (copy-to-buffer todos-print-buffer-name (point-min) (point-max)) 1422 (setq end (or (match-beginning 0) (point-max)))
1119 (set-buffer todos-print-buffer-name) 1423 (re-search-backward (concat "^" (regexp-quote todos-category-beg)
1120 (goto-char (point-min)) 1424 (regexp-quote cat))
1121 ;; (when (re-search-forward (regexp-quote todos-header) nil t) 1425 nil t)
1122 ;; (beginning-of-line 1) 1426 (if (not (re-search-forward (concat "\\[" (regexp-quote todos-done-string))
1123 ;; (delete-region (point) (line-end-position))) 1427 nil t))
1124 (while (re-search-forward ;Find category start 1428 (error "No done items in this category")
1125 (regexp-quote (concat todos-prefix todos-category-beg)) 1429 (setq beg (match-beginning 0))
1126 nil t) 1430 (setq done (buffer-substring beg end))
1127 (setq beg (+ (line-end-position) 1)) ;Start of first entry. 1431 (with-current-buffer archive
1128 (re-search-forward cat-end nil t)
1129 (setq end (match-beginning 0))
1130 (replace-match todos-category-break)
1131 (narrow-to-region beg end) ;In case we have too few entries.
1132 (goto-char (point-min)) 1432 (goto-char (point-min))
1133 (if (zerop nof-priorities) ;Traverse entries. 1433 (if (re-search-forward (regexp-quote (concat "^" todos-category-beg cat))
1134 (goto-char end) ;All entries 1434 nil t)
1135 (todos-forward-item nof-priorities)) 1435 (forward-char)
1136 (setq beg (point)) 1436 (insert todos-category-beg cat "\n"))
1137 (delete-region beg end) 1437 (insert done))
1138 (widen)) 1438 (delete-region beg end)
1139 (and (looking-at " ") (replace-match "")) ;Remove trailing form-feed. 1439 (remove-overlays beg end)
1140 (goto-char (point-min)) ;Due to display buffer 1440 (kill-line -1)))))
1141 ;; FIXME: after todos-edit-multiline widening remains 1441 (message "Done items archived."))
1142 ))) 1442
1143 ;; Could have used switch-to-buffer as it has a norecord argument, 1443;; FIXME: undone item leaves item number overlay behind
1144 ;; which is nice when we are called from e.g. todos-print. 1444(defun todos-item-undo ()
1145 ;; Else we could have used pop-to-buffer. 1445 "" ;FIXME
1146 (display-buffer todos-print-buffer-name) 1446 (interactive)
1147 (message "Type C-x 1 to remove %s window. M-C-v to scroll the help." 1447 (when (todos-done-item-p)
1148 todos-print-buffer-name))) 1448 (let* ((buffer-read-only)
1449 (cat (nth todos-category-number todos-categories))
1450 (start (progn
1451 (todos-item-start)
1452 (search-forward "] "))) ; end of done date string
1453 (item (buffer-substring start (todos-item-end))))
1454 (todos-remove-item)
1455 (todos-add-item-non-interactively item cat))))
1456
1457(defun todos-toggle-item-diary-inclusion ()
1458 "" ;FIXME add docstring
1459 (interactive)
1460 (save-excursion
1461 (let* ((buffer-read-only)
1462 (beg (todos-item-start))
1463 (lim (save-excursion (todos-item-end)))
1464 (end (save-excursion
1465 (or (todos-time-string-match lim)
1466 (todos-date-string-match lim)))))
1467 (if (looking-at "\\[") ; FIXME use todos-exclusion-start
1468 (progn
1469 (replace-match "")
1470 (search-forward "]" (1+ end) t) ; FIXME use todos-exclusion-end
1471 (replace-match ""))
1472 (when end
1473 (insert "[") ; FIXME use todos-exclusion-start
1474 (goto-char (1+ end))
1475 (insert "]")))))) ; FIXME use todos-exclusion-end
1476
1477(defun todos-toggle-diary-inclusion (arg)
1478 "" ;FIXME add docstring
1479 (interactive "p")
1480 (save-excursion
1481 (save-restriction
1482 (when (eq arg 2) (widen)) ;FIXME: don't toggle done items
1483 (when (or (eq arg 1) (eq arg 2))
1484 (goto-char (point-min))
1485 (when (eq arg 2)
1486 (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t)
1487 (forward-line)
1488 (when (looking-at (regexp-quote todos-category-end)) (forward-line)))
1489 (while (not (eobp))
1490 (todos-toggle-item-diary-inclusion)
1491 (todos-forward-item))))))
1149 1492
1150(defun todos-save-top-priorities (&optional nof-priorities) 1493(defun todos-save-top-priorities (&optional nof-priorities)
1151 "Save top priorities for each category in `todos-file-top'. 1494 "Save top priorities for each category in `todos-file-top'.
@@ -1169,47 +1512,359 @@ between each category.
1169 1512
1170Number of entries for each category is given by `todos-print-priorities'." 1513Number of entries for each category is given by `todos-print-priorities'."
1171 (interactive "P") 1514 (interactive "P")
1172 (save-window-excursion 1515 (when (yes-or-no-p "Print Todos list? ")
1173 (save-excursion 1516 (save-window-excursion
1174 (save-restriction
1175 (todos-top-priorities todos-print-priorities
1176 category-pr-page)
1177 (set-buffer todos-tmp-buffer-name)
1178 (and (funcall todos-print-function)
1179 (kill-this-buffer))
1180 (message "Todo printing done.")))))
1181
1182(defun todos-list-categories ()
1183 "Return a list of the Todo mode categories."
1184 (let ((todos-buf (find-file-noselect todos-file-do))
1185 categories)
1186 (with-current-buffer todos-buf
1187 (save-excursion 1517 (save-excursion
1188 (save-restriction 1518 (save-restriction
1519 (todos-top-priorities todos-print-priorities
1520 category-pr-page)
1521 (set-buffer todos-tmp-buffer-name)
1522 (and (funcall todos-print-function)
1523 (kill-this-buffer))
1524 (message "Todo printing done."))))))
1525
1526;; ---------------------------------------------------------------------------
1527
1528;;; Internal functions
1529
1530;; "Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec")
1531;; (regexp-opt (list "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30" "31")))
1532
1533;; FIXME: use diary-date-forms instead?
1534;; (defun todos-date-string ()
1535;; "Return a regexp matching a diary date string."
1536;; (let ((month (regexp-opt (list "Jan" "Feb" "Mar" "Apr" "May" "Jun"
1537;; "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")))
1538;; (day "[0-3]?[0-9]")
1539;; (year "[0-9]\\{4\\}"))
1540;; (concat month " " day ", " year)))
1541
1542;; FIXME: use diary-time-regexp
1543;; (defun todos-time-string ()
1544;; "Return a regexp matching a diary time string."
1545;; "[0-9]?[0-9][:.][0-9]\\{2\\}")
1546
1547(defvar todos-date-nodayname-pattern
1548 (let ((dayname)
1549 (monthname (format "\\(%s\\|\\*\\)"
1550 (diary-name-pattern calendar-month-name-array
1551 calendar-month-abbrev-array t)))
1552 (month "\\([0-9]+\\|\\*\\)")
1553 (day "\\([0-9]+\\|\\*\\)")
1554 (year "-?\\([0-9]+\\|\\*\\)"))
1555 (mapconcat 'eval calendar-date-display-form ""))
1556 "Regular expression matching a Todos date header without day name.")
1557
1558;; (defvar todos-dayname-pattern
1559;; (diary-name-pattern calendar-day-name-array nil t)
1560;; "Regular expression matching a day name in a Todos date header.")
1561
1562(defvar todos-dayname-date-pattern
1563 (let ((dayname (diary-name-pattern calendar-day-name-array nil t)))
1564 (concat dayname "\\(?:, " todos-date-nodayname-pattern "\\)?"))
1565 "Regular expression matching a Todos date header with day name.")
1566
1567(defvar todos-date-pattern
1568 (concat "\\(?:" todos-date-nodayname-pattern "\\)\\|"
1569 "\\(?:" todos-date-dayname-pattern "\\)")
1570 "Regular expression matching a Todos date header.")
1571
1572(defun todos-date-string-match (lim)
1573 "Find Todos date strings for font-locking."
1574 (re-search-forward (concat "^\\[?\\(" todos-date-pattern "\\)") lim t))
1575
1576(defun todos-time-string-match (lim)
1577 "Find Todos time strings for font-locking."
1578 (re-search-forward (concat "^\\[?\\(?:" todos-date-pattern "\\)"
1579 " \\(?1:" diary-time-regexp "\\)") lim t))
1580
1581(defun todos-done-string-match (lim)
1582 "Find Todos done headers for font-locking."
1583 (re-search-forward (concat "^\\[" (regexp-quote todos-done-string) "[^][]+]")
1584 lim t))
1585
1586(defun todos-category-string-match (lim)
1587 "Find Todos category headers for font-locking."
1588 (re-search-forward (concat "^" (regexp-quote todos-category-beg) ".*$")
1589 lim t))
1590
1591(defun todos-check-format ()
1592 "Signal an error if the current Todos file is ill-formatted."
1593 (save-excursion
1594 (save-restriction
1595 (widen)
1596 (goto-char (point-min))
1597 (let ((legit (concat "^\\(" (regexp-quote todos-category-beg) "\\)"
1598 "\\|\\(\\[?" todos-date-pattern "\\)"
1599 "\\|\\([ \t]+[^ \t]*\\)"
1600 "\\|$")))
1601 (while (not (eobp))
1602 (unless (looking-at legit)
1603 (error "Illegitimate Todos file format at line %d"
1604 (line-number-at-pos (point))))
1605 (forward-line)))))
1606 (message "This Todos file is well-formatted."))
1607
1608(defun todos-wrap-and-indent ()
1609 "" ;FIXME
1610 (make-local-variable 'word-wrap)
1611 (setq word-wrap t)
1612 (make-local-variable 'wrap-prefix)
1613 (setq wrap-prefix (make-string (+ 5 (length todos-prefix)) 32))
1614 (unless (member '(continuation) fringe-indicator-alist)
1615 (push '(continuation) fringe-indicator-alist)))
1616
1617(defun todos-reset-prefix (symbol value)
1618 "Set SYMBOL's value to VALUE, and ." ; FIXME
1619 (let ((oldvalue (symbol-value symbol)))
1620 (custom-set-default symbol value)
1621 (when (not (equal value oldvalue))
1622 (save-window-excursion
1623 (todos-show)
1624 (save-excursion
1189 (widen) 1625 (widen)
1190 (goto-char (point-max)) 1626 (goto-char (point-min))
1191 (while (re-search-backward 1627 (while (not (eobp))
1192 ;; (concat "^" (regexp-quote (concat todos-prefix todos-category-beg)) 1628 (remove-overlays (point) (point)); 'before-string prefix)
1193 (concat "^" ;(regexp-quote todos-category-sep) "\n" 1629 (forward-line)))
1194 (regexp-quote todos-category-beg) 1630 ;; activate the prefix setting (save-restriction does not help)
1195 "\\(.*\\)\n") 1631 (todos-show)))))
1196 (point-min) t)
1197 (push (match-string-no-properties 1) categories)))))
1198 categories))
1199 1632
1200(defun todos-jump-to-category () 1633;; FIXME: rename and/or rewrite
1201 "Jump to a category. Default is previous category." 1634(defun todos-update-numbered-prefix ()
1202 (interactive) 1635 "Update consecutive item numbering in the current category."
1203 (let ((category (todos-completing-read))) 1636 (save-excursion
1204 (if (string= "" category) 1637 (goto-char (point-min))
1205 (setq category (nth todos-category-number todos-categories))) 1638 (while (not (eobp))
1206 (setq todos-category-number 1639 (remove-overlays (point) (point) 'before-string)
1207 (if (member category todos-categories) 1640 (todos-forward-item))
1208 (- (length todos-categories)
1209 (length (member category todos-categories)))
1210 (todos-add-category category)))
1211 (todos-show))) 1641 (todos-show)))
1212 1642
1643(defvar todos-item-start-overlays nil "")
1644
1645;; (defvar todos-done-overlays nil "")
1646
1647;; (defun todos-check-overlay (prop)
1648;; "" ;FIXME add docstring
1649;; ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point)))))
1650;; (let ((ovlist (overlays-in (point) (point))))
1651;; (when ovlist (overlay-get (car ovlist) prop))))
1652
1653(defun todos-prefix-overlays ()
1654 "" ;FIXME add docstring
1655 (when (or todos-number-prefix
1656 (not (string-match "^[[:space:]]*$" todos-prefix)))
1657 (let ((prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string))
1658 (num 0)
1659 lim ov-pref)
1660 (save-excursion
1661 (goto-char (point-min))
1662 (while (or (todos-date-string-match lim)
1663 (todos-done-string-match lim))
1664 (goto-char (match-beginning 0))
1665 (when todos-number-prefix
1666 (setq num (1+ num))
1667 ;; reset number for done items
1668 (if (or (looking-at (concat "\n\\[" (regexp-quote todos-done-string)))
1669 ;; if last not done item is multiline, then
1670 ;; todos-done-string-match skips empty line, so have
1671 ;; to look back
1672 (and (looking-at (concat "^\\[" (regexp-quote todos-done-string)))
1673 (looking-back "\n\n")))
1674 (setq num 1))
1675 (setq prefix (propertize (concat (number-to-string num) " ")
1676 'face 'todos-prefix-string)))
1677 (or (and (setq ov-pref (car (overlays-in (point) (point))))
1678 (equal (overlay-get ov-pref 'before-string) prefix))
1679 (and (setq ov-pref (pop todos-item-start-overlays))
1680 (move-overlay ov-pref (point) (point)))
1681 (and (setq ov-pref (make-overlay (point) (point)))
1682 (overlay-put ov-pref 'before-string prefix)))
1683 (forward-line))))))
1684
1685;; (defun todos-show-paren-hack ()
1686;; "Purge overlay duplication due to show-paren-mode."
1687;; (save-excursion
1688;; (when show-paren-mode
1689;; (goto-char (point-min))
1690;; (while (not (eobp))
1691;; ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point))))
1692;; (let ((ovlist (overlays-in (point) (point)))
1693;; ov)
1694;; (while (> (length ovlist) 1)
1695;; (setq ov (pop ovlist))
1696;; (delete-overlay ov)))
1697;; (forward-line))
1698;; (if (and (bolp) (eolp))
1699;; ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point)))))
1700;; (let ((ovlist (overlays-in (point) (point))))
1701;; (remove-overlays (1- (point)) (1+ (point))))))))
1702
1703(defun todos-reset-separator (symbol value)
1704 "Set SYMBOL's value to VALUE, and ." ; FIXME
1705 (let ((oldvalue (symbol-value symbol)))
1706 (custom-set-default symbol value)
1707 ;; (setq todos-done-overlays nil)
1708 (when (not (equal value oldvalue))
1709 (save-window-excursion
1710 (todos-show)
1711 (save-excursion
1712 (goto-char (point-min))
1713 (when (re-search-forward (concat "^\\[" (regexp-quote todos-done-string)) nil t)
1714 (remove-overlays (point) (point))))
1715 ;; activate the prefix setting (save-restriction does not help)
1716 (todos-show)))))
1717
1718;; FIXME: use this; should be defsubst?
1719(defun todos-current-category ()
1720 "Return the name of the current category."
1721 (nth todos-category-number todos-categories))
1722
1723(defun todos-category-select ()
1724 "Make TODO mode display the current category correctly."
1725 (let ((name (nth todos-category-number todos-categories)))
1726 (setq mode-line-buffer-identification (concat "Category: " name))
1727 (widen)
1728 (goto-char (point-min))
1729 (search-forward-regexp
1730 (concat "^" (regexp-quote (concat todos-category-beg name))
1731 "$"))
1732 (let ((begin (1+ (line-end-position)))
1733 (end (or (and (re-search-forward (concat "^" todos-category-beg) nil t)
1734 (match-beginning 0))
1735 (point-max))))
1736 (narrow-to-region begin end)
1737 (goto-char (point-min))))
1738 (todos-prefix-overlays)
1739 (let ((beg (point-min))
1740 (done-sep (if (string-match "^[[:space:]]*$" todos-done-separator)
1741 todos-done-separator
1742 (propertize (concat todos-done-separator "\n")
1743 'face 'todos-done-sep)))
1744 done ov)
1745 (when (re-search-forward (concat "\n\\(\\[" (regexp-quote todos-done-string)
1746 "\\)") nil t)
1747 (setq done (match-beginning 1)
1748 end (match-beginning 0))
1749 (if todos-show-with-done
1750 ;; with an empty separator just display the done items
1751 (if (string= done-sep todos-done-separator)
1752 (narrow-to-region (point-min) (point-max))
1753 ;; else display the separator in an overlay in front of the prefix
1754 ;; overlay on first done item
1755 (let ((prefix (propertize
1756 (concat (if todos-number-prefix "1" todos-prefix) " ")
1757 'face 'todos-prefix-string)))
1758 (goto-char done)
1759 (remove-overlays done done)
1760 ;; must make separator overlay after making prefix overlay to get
1761 ;; the order separator before prefix
1762 (setq ov-pref (make-overlay done done)
1763 ov-done (make-overlay done done))
1764 (overlay-put ov-pref 'before-string prefix)
1765 (overlay-put ov-done 'before-string done-sep)))
1766 ;; hide done items
1767 (narrow-to-region (point-min) end))))
1768 (goto-char (point-min)))
1769
1770;; FIXME: using numbering for priority instead of importance?
1771;;;###autoload
1772(defun todos-add-item-non-interactively (new-item category)
1773 "Insert NEW-ITEM in TODO list as a new entry in CATEGORY."
1774 ;; FIXME: really need this? (and in save-excursion?)
1775 (save-excursion
1776 (todos-show))
1777 (if (string= "" category)
1778 (setq category (nth todos-category-number todos-categories)))
1779 (let ((cat-exists (member category todos-categories)))
1780 (setq todos-category-number
1781 (if cat-exists
1782 (- (length todos-categories) (length cat-exists))
1783 (todos-add-category category))))
1784 ;; FIXME: really need this? (yes for todos-move-item, to show moved to category)
1785 (todos-show) ; now at point-min
1786 ;; (setq todos-previous-line 0)
1787 ;; (let* ((top 1)
1788 ;; (end (save-excursion
1789 ;; (goto-char (point-min))
1790 ;; (if (re-search-forward (concat "\n\n\\\(\\["
1791 ;; (regexp-quote todos-done-string)
1792 ;; "\\)") nil t)
1793 ;; (match-beginning 1)
1794 ;; (point-max))))
1795 ;; (bottom (count-lines (point-min) end)))
1796 ;; (while (> (- bottom top) todos-insert-threshold)
1797 ;; (let* ((current (/ (+ top bottom) 2))
1798 ;; (answer (if (< current bottom)
1799 ;; (todos-more-important-p current) nil)))
1800 ;; (if answer
1801 ;; (setq bottom current)
1802 ;; (setq top (1+ current)))))
1803 ;; (setq top (/ (+ top bottom) 2))
1804 ;; (goto-char (point-min))
1805 ;; (forward-line (1- top)))
1806 (unless (or (eq (point-min) (point-max)) ; no unfinished items
1807 (when (re-search-forward (concat "^\\["
1808 (regexp-quote todos-done-string))
1809 nil t)
1810 (forward-line -1)
1811 (bobp))) ; there are done items but no unfinished items
1812 (let* ((num-items (1+ (car (todos-count-items-in-category))))
1813 (priority (string-to-number (read-from-minibuffer
1814 (format "Set item priority (1-%d): "
1815 num-items))))
1816 prompt)
1817 (while (cond ((not (integerp priority))
1818 (setq prompt "Priority must be an integer.\n"))
1819 ((< priority 1)
1820 (setq prompt "Priority cannot be higher than 1.\n"))
1821 ((> priority num-items)
1822 (setq prompt (format "Priority cannot be lower than %d.\n"
1823 num-items))))
1824 (setq priority
1825 (string-to-number (read-from-minibuffer
1826 (concat prompt
1827 (format "Set item priority (1-%d): "
1828 num-items))))))
1829 (goto-char (point-min))
1830 (todos-forward-item (1- priority))))
1831 (todos-insert-with-overlays new-item))
1832
1833(defun todos-jump-to-category-noninteractively (cat)
1834 (let ((bufname (buffer-name)))
1835 (cond ((string= bufname todos-categories-buffer)
1836 (switch-to-buffer (file-name-nondirectory todos-file-do)))
1837 ((string= bufname todos-archived-categories-buffer)
1838 ;; Is pop-to-buffer better for this case?
1839 (switch-to-buffer (file-name-nondirectory todos-archive-file))))
1840 (kill-buffer bufname))
1841 (widen)
1842 (goto-char (point-min))
1843 (setq todos-category-number (- (length todos-categories)
1844 (length (member cat todos-categories))))
1845 (todos-category-select))
1846
1847(defun todos-insert-with-overlays (item)
1848 "" ;FIXME add docstring
1849 ;; FIXME: breaks without narrowing, e.g. todos-item-done
1850 ;; (unless (and (bolp) (eolp)) (goto-char (todos-item-start)))
1851 (insert item "\n")
1852 (todos-backward-item)
1853 (if todos-number-prefix
1854 (todos-update-numbered-prefix)
1855 (todos-prefix-overlays)))
1856
1857;; (defun todos-more-important-p (line)
1858;; "Ask whether entry is more important than the one at LINE."
1859;; (unless (equal todos-previous-line line)
1860;; (setq todos-previous-line line)
1861;; (goto-char (point-min))
1862;; (forward-line (1- todos-previous-line))
1863;; (let ((item (todos-item-string-start)))
1864;; (setq todos-previous-answer
1865;; (y-or-n-p (concat "More important than '" item "'? ")))))
1866;; todos-previous-answer)
1867
1213(defun todos-line-string () 1868(defun todos-line-string ()
1214 "Return current line in buffer as a string." 1869 "Return current line in buffer as a string."
1215 (buffer-substring (line-beginning-position) (line-end-position))) 1870 (buffer-substring (line-beginning-position) (line-end-position)))
@@ -1223,51 +1878,112 @@ Number of entries for each category is given by `todos-print-priorities'."
1223 item)) 1878 item))
1224 1879
1225(defun todos-item-start () 1880(defun todos-item-start ()
1226 "Return point at start of current TODO list item." 1881 "Move to start of current TODO list item and return its position."
1227 (save-excursion 1882 (unless (or (looking-at "^$") ; last item or between done and not done
1228 (if (re-search-backward (concat (regexp-quote todos-item-end) "\n") nil t) 1883 (looking-at (regexp-quote todos-category-beg))) ; for todos-count-items
1229 (forward-line) 1884 (goto-char (line-beginning-position))
1230 (goto-char (point-min))) 1885 (while (not (looking-at (concat "^\\(\\[\\(" (regexp-quote todos-done-string)
1231 ;; for widened buffer in todos-toggle-diary-inclusion 1886 "\\)?\\)?" todos-date-pattern)))
1232 ;; (while (looking-at 1887 (forward-line -1)))
1233 ;; (concat "^" (regexp-opt (list todos-category-sep todos-category-beg 1888 (point))
1234 ;; todos-category-end))))
1235 ;; (forward-line))
1236 (point)))
1237 1889
1238(defun todos-item-end () 1890(defun todos-item-end ()
1239 "Return point at end of current TODO list item." 1891 "Move to end of current TODO list item and return its position."
1240 (if (todos-check-overlay 'invisible) 1892 (unless (looking-at "^$") ; last item or between done and not done
1241 (search-backward todos-item-end) 1893 (todos-forward-item)
1242 (when (not (and (bolp) (eobp))) 1894 (backward-char))
1243 (save-excursion 1895 (point))
1244 (re-search-forward (concat "\\(" (regexp-quote todos-item-end) "\\)\n"))
1245 (match-beginning 1)))))
1246 1896
1247(defun todos-remove-item () 1897(defun todos-remove-item ()
1248 "Delete the current entry from the TODO list." 1898 "Delete the current entry from the TODO list."
1249 (let ((beg (todos-item-start)) 1899 (let* ((end (progn (todos-forward-item) (point)))
1250 (end (save-excursion 1900 (beg (progn (todos-backward-item) (point)))
1251 (unless (todos-check-overlay 'invisible) (goto-char (todos-item-end))) 1901 (ov-start (car (overlays-in beg beg))))
1252 (line-end-position))) 1902 (when ov-start
1253 ov-start ov-end) 1903 ;; don't cache numbers, since they can be popped out of order in
1254 (goto-char (todos-item-start)) 1904 ;; todos-prefix-overlays
1255 ;; (setq ov-start (car (overlays-in (1- (point)) (1+ (point))))) 1905 (unless todos-number-prefix
1256 (setq ov-start (car (overlays-in (point) (point)))) 1906 (push ov-start todos-item-start-overlays))
1257 (push ov-start todos-item-start-overlays) 1907 (delete-overlay ov-start))
1258 (delete-overlay ov-start) 1908 (delete-region beg end)))
1259 (goto-char (todos-item-end))
1260 ;; (setq ov-end (car (overlays-in (1- (point)) (1+ (point)))))
1261 ;; FIXME
1262 (setq ov-end (car (overlays-in (point) (point))))
1263 (push ov-end todos-item-end-overlays)
1264 (delete-overlay ov-end)
1265 (delete-region (todos-item-start) (1+ end))))
1266 1909
1267(defun todos-item-string () 1910(defun todos-item-string ()
1268 "Return current TODO list entry as a string." 1911 "Return current TODO list entry as a string."
1269 (buffer-substring (todos-item-start) (todos-item-end))) 1912 (buffer-substring (todos-item-start) (todos-item-end)))
1270 1913
1914(defun todos-done-item-p ()
1915 "" ;FIXME
1916 (save-excursion
1917 (todos-item-start)
1918 (looking-at (concat "^\\[" (regexp-quote todos-done-string)))))
1919
1920(defun todos-count-items-in-category ()
1921 "Return number of not done and done items in current category."
1922 (save-excursion
1923 (let ((not-done 0)
1924 (done 0)
1925 (beg (point-min))
1926 end)
1927 (save-restriction
1928 (widen)
1929 (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t)
1930 (setq end (or (match-beginning 0) (point-max)))
1931 (goto-char beg)
1932 (while (> end (point))
1933 (if (todos-done-item-p)
1934 (setq done (1+ done))
1935 (setq not-done (1+ not-done)))
1936 (todos-forward-item)
1937 (when (and (not (> (point) end))
1938 (looking-at "^$")
1939 (not (eobp)))
1940 ;; point is between done and not done items
1941 (setq not-done (1- not-done))))
1942 (cons not-done done)))))
1943
1944;; FIXME: rename, since *-alist is by convention a variable name
1945(defun todos-categories-alist ()
1946 "Return alist of categories and some of their properties.
1947The properties are at least the numbers of the unfinished and
1948done items in the category."
1949 (let (todos-categories-alist)
1950 (save-excursion
1951 (save-restriction
1952 (widen)
1953 (goto-char (point-min))
1954 (let ((not-done 0)
1955 (done 0)
1956 category beg end)
1957 (while (not (eobp))
1958 (cond ((looking-at (concat (regexp-quote todos-category-beg)
1959 "\\(.*\\)\n"))
1960 (setq not-done 0 done 0)
1961 (push (list (match-string-no-properties 1) (cons not-done done))
1962 todos-categories-alist))
1963 ((looking-at (concat "^\\[" (regexp-quote todos-done-string)))
1964 (setq done (1+ done))
1965 (setcdr (cadr (car todos-categories-alist)) done))
1966 ((looking-at (concat "^\\[?" todos-date-pattern))
1967 (setq not-done (1+ not-done))
1968 (setcar (cadr (car todos-categories-alist)) not-done)))
1969 (forward-line)))))
1970 todos-categories-alist))
1971
1972(defun todos-count-all-items ()
1973 ""
1974 (let ((unfinished 0)
1975 (done 0))
1976 (dolist (l (todos-categories-alist))
1977 (setq unfinished (+ unfinished (car (cadr l)))
1978 done (+ done (cdr (cadr l)))))
1979 (cons unfinished done)))
1980
1981(defun todos-longest-category-name-length ()
1982 ""
1983 (let ((longest 0))
1984 (dolist (c (todos-categories-alist) longest)
1985 (setq longest (max longest (length (car c)))))))
1986
1271(defun todos-string-count-lines (string) 1987(defun todos-string-count-lines (string)
1272 "Return the number of lines STRING spans." 1988 "Return the number of lines STRING spans."
1273 (length (split-string string "\n"))) 1989 (length (split-string string "\n")))
@@ -1295,7 +2011,21 @@ Number of entries for each category is given by `todos-print-priorities'."
1295 (setq todos-categories categories) 2011 (setq todos-categories categories)
1296 category))) 2012 category)))
1297 2013
2014(defun todos-categories-list (buf)
2015 "Return a list of the Todo mode categories in buffer BUF."
2016 (let (categories)
2017 (with-current-buffer buf
2018 (save-excursion
2019 (save-restriction
2020 (widen)
2021 (goto-char (point-max))
2022 (while (re-search-backward (concat "^" (regexp-quote todos-category-beg)
2023 "\\(.*\\)\n") nil t)
2024 (push (match-string-no-properties 1) categories)))))
2025 categories))
2026
1298;; --------------------------------------------------------------------------- 2027;; ---------------------------------------------------------------------------
2028;;; Mode setup
1299 2029
1300(easy-menu-define todos-menu todos-mode-map "Todo Menu" 2030(easy-menu-define todos-menu todos-mode-map "Todo Menu"
1301 '("Todo" 2031 '("Todo"
@@ -1335,59 +2065,95 @@ Number of entries for each category is given by `todos-print-priorities'."
1335 (setq mode-name "TODOS") 2065 (setq mode-name "TODOS")
1336 (use-local-map todos-mode-map) 2066 (use-local-map todos-mode-map)
1337 (easy-menu-add todos-menu) 2067 (easy-menu-add todos-menu)
2068 (when todos-wrap-lines (funcall todos-line-wrapping-function))
1338 (make-local-variable 'font-lock-defaults) 2069 (make-local-variable 'font-lock-defaults)
1339 (setq font-lock-defaults '(todos-font-lock-keywords t)) 2070 (setq font-lock-defaults '(todos-font-lock-keywords t))
1340 (make-local-variable 'word-wrap)
1341 (setq word-wrap t)
1342 (make-local-variable 'wrap-prefix)
1343 (setq wrap-prefix (make-string (+ 5 (length todos-prefix)) 32))
1344 (unless (member '(continuation) fringe-indicator-alist)
1345 (push '(continuation) fringe-indicator-alist))
1346 (make-local-variable 'hl-line-range-function) 2071 (make-local-variable 'hl-line-range-function)
1347 (setq hl-line-range-function 2072 (setq hl-line-range-function
1348 (lambda() (when (todos-item-end) 2073 (lambda() (when (todos-item-end)
1349 (cons (todos-item-start) (todos-item-end))))) 2074 (cons (todos-item-start) (todos-item-end)))))
1350 ;; (add-hook 'post-command-hook 'todos-show-paren-hack nil t) 2075 ;; (add-hook 'post-command-hook 'todos-show-paren-hack nil t)
2076 (add-to-invisibility-spec 'todos)
2077 ;; FIXME: use this and let-bind in editing commands?
2078 (setq buffer-read-only t)
1351 (run-mode-hooks 'todos-mode-hook)) 2079 (run-mode-hooks 'todos-mode-hook))
1352 2080
1353;; (defvar date) 2081(defun todos-archive-mode ()
1354;; (defvar entry) 2082 "Major mode for archived Todos categories.
1355 2083
1356;; ;; t-c should be used from diary code, which requires calendar. 2084\\{todos-mode-map}"
1357;; (declare-function calendar-current-date "calendar" nil) 2085 (interactive)
1358 2086 (kill-all-local-variables)
1359;; ;; Read about this function in the setup instructions above! 2087 (setq major-mode 'todos-archive-mode)
1360;; ;;;###autoload 2088 (setq mode-name "TODOS Arch")
1361;; (defun todos-cp () 2089 (use-local-map todos-archive-mode-map)
1362;; "Make a diary entry appear only in the current date's diary." 2090 ;; (easy-menu-add todos-menu)
1363;; (if (equal (calendar-current-date) date) 2091 (when todos-wrap-lines (funcall todos-line-wrapping-function))
1364;; entry)) 2092 (make-local-variable 'font-lock-defaults)
2093 (setq font-lock-defaults '(todos-font-lock-keywords t))
2094 (make-local-variable 'hl-line-range-function)
2095 (setq hl-line-range-function
2096 (lambda() (when (todos-item-end)
2097 (cons (todos-item-start) (todos-item-end)))))
2098 ;; (add-hook 'post-command-hook 'todos-show-paren-hack nil t)
2099 (add-to-invisibility-spec 'todos)
2100 (run-mode-hooks 'todos-mode-hook))
1365 2101
1366(define-derived-mode todos-edit-mode text-mode "TODO Edit" 2102(defun todos-edit-mode ()
1367 "Major mode for editing items in the TODO list. 2103 "Major mode for editing items in the TODO list.
1368 2104
1369\\{todos-edit-mode-map}") 2105\\{todos-edit-mode-map}"
2106 (interactive)
2107 (setq major-mode 'todos-edit-mode)
2108 (setq mode-name "TODOS Edit")
2109 (use-local-map todos-edit-mode-map)
2110 (make-local-variable 'font-lock-defaults)
2111 (setq font-lock-defaults '(todos-font-lock-keywords t))
2112 (when todos-wrap-lines (funcall todos-line-wrapping-function)))
2113
2114(defun todos-save ()
2115 "Save the TODO list."
2116 (interactive)
2117 (save-excursion
2118 (save-restriction
2119 (save-buffer)))
2120 ;; (if todos-save-top-priorities-too (todos-save-top-priorities)))
2121 )
2122
2123(defun todos-quit ()
2124 "Done with TODO list for now."
2125 (interactive)
2126 (widen)
2127 (todos-save)
2128 (message "")
2129 (bury-buffer))
1370 2130
1371;;;###autoload 2131;;;###autoload
1372(defun todos-show () 2132(defun todos-show ()
1373 "Show TODO list." 2133 "Show TODO list."
1374 (interactive) 2134 (interactive)
1375 ;; Call todos-initial-setup only if there is neither a Todo file nor 2135 ;; Make this a no-op if called interactively in narrowed Todos mode, since
1376 ;; a corresponding unsaved buffer. 2136 ;; it is in that case redundant, but in particular to work around the bug of
1377 (if (or (file-exists-p todos-file-do) 2137 ;; item prefix reduplication with show-paren-mode enabled.
1378 (let* ((buf (get-buffer (file-name-nondirectory todos-file-do))) 2138 (unless (and (called-interactively-p)
1379 (bufname (buffer-file-name buf))) 2139 (eq major-mode 'todos-mode)
1380 (equal (expand-file-name todos-file-do) bufname))) 2140 (< (- ( point-max) (point-min)) (buffer-size)))
1381 (find-file todos-file-do) 2141 ;; Call todos-initial-setup only if there is neither a Todo file nor
1382 (todos-initial-setup)) 2142 ;; a corresponding unsaved buffer.
1383 (unless (eq major-mode 'todos-mode) (todos-mode)) 2143 (if (or (file-exists-p todos-file-do)
1384 (unless todos-categories 2144 (let* ((buf (get-buffer (file-name-nondirectory todos-file-do)))
1385 (setq todos-categories (todos-list-categories))) 2145 (bufname (buffer-file-name buf)))
1386 ;; (beginning-of-line) 2146 (equal (expand-file-name todos-file-do) bufname)))
1387 (save-excursion 2147 (find-file todos-file-do)
1388 (todos-category-select) 2148 (todos-initial-setup))
1389 ;; (todos-show-paren-hack) 2149 (unless (eq major-mode 'todos-mode) (todos-mode))
1390 )) 2150 (unless todos-categories
2151 (setq todos-categories (todos-categories-list (buffer-name))))
2152 ;; (beginning-of-line)
2153 (save-excursion
2154 (todos-category-select)
2155 ;; (todos-show-paren-hack)
2156 )))
1391 2157
1392(defun todos-initial-setup () 2158(defun todos-initial-setup ()
1393 "Set up things to work properly in TODO mode." 2159 "Set up things to work properly in TODO mode."