aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Berman2010-06-18 23:52:10 +0100
committerStephen Berman2010-06-18 23:52:10 +0100
commitee7412e467393ef608f1b1f868e368ab923ae406 (patch)
treef9fe5f7b3a5ea08051c4010741eba9142c935ac0
parent2c173503dcb83660571ab5abe244cc559eb61596 (diff)
downloademacs-ee7412e467393ef608f1b1f868e368ab923ae406.tar.gz
emacs-ee7412e467393ef608f1b1f868e368ab923ae406.zip
* calendar/todos.el: Numerous spelling and comment fixes, doc
string fixes to conform with checkdoc, further rearrangement of definitions, etc. (todos-previous-line, todos-previous-answer) (todos-insert-item-ask-date, todos-change-date) (todos-date-nodayname-pattern, todos-dayname-date-pattern) (todos-count-items-in-category, todos-count-all-items): Remove. (todos-update-numbered-prefix, todos-item-start-overlays) (todos-add-item-non-interactively): Comment out, also in uses. (todos-done-separator): Change default value. (todos-always-add-time-string): Rename from todos-add-time-string and adjust uses. (todos-read-category): Rename from todos-completing-read and adjust callers; use todos-current-category. (todos-make-categories-alist): Rename from function todos-categories-alist. (todos-categories-alist): New variable. (todos-indent-to-here): New defcustom. (todos-button): New face. (todos-display-categories-alphabetically, todos-raise-category) (todos-lower-category, todos-insert-item-for-diary-ask-date-time) (todos-insert-item-here-ask-date-time) (todos-insert-item-ask-date-time) (todos-insert-item-ask-dayname-time): New commands. (todos-edit-item-header): New command replacing todos-change-date. (todos-category-number, todos-indent, todos-item-counts) (todos-check-category-name, todos-read-date, todos-read-dayname) (todos-read-time, todos-padded-string) (todos-insert-category-name): New functions. (todos-set-item-priority): New function replacing todos-add-item-non-interactively. (todos-mode-map): Remap newline to newline-and-indent. (todos-edit-mode-map): Make sparse keymap; remap newline to newline-and-indent. (todos-categories-mode-map): New keymap. (todos-mode, todos-edit-mode): Make indent-line-function local variable and set to todos-indent. (todos-categories-mode): New major mode. (todos-display-categories): List categories initially in their numerical order; add optional argument to switch to alphabetical listing. (todos-toggle-view-done-items): Simplify implementation. (todos-toggle-display-date-time): Fix regexp search string. (todos-backward-item, todos-forward-item): Use variable todos-item-start. (todos-add-category): Use todos-check-category-name and todos-categories-alist. (todos-rename-category): Use todos-current-category, todos-check-category-name and todos-categories-alist. (todos-delete-category): Use todos-check-category-name and todos-categories-alist and take done items into account. (todos-insert-item): Use separate arguments to handle insertion of date/dayname and time strings, add new argument to mark item for diary inclusion, use new todos-read-* functions, todos-set-item-priority and todos-item-counts. (todos-insert-item-here, todos-insert-item-for-diary) (todos-insert-item-from-calendar): Adapt to new version of todos-insert-item. (todos-delete-item, todos-item-done): Use todos-item-counts. (todos-edit-item): Indent newlines inserted by C-q C-j if nonspace char follows. (todos-lower-item): Ensure only not-done items can be lowered. (todos-move-item): Use todos-current-category, todos-read-category and todos-item-counts. (todos-archive-done-items): Use todos-current-category and todos-item-counts; fix regexp search string. (todos-item-undo): Use todos-current-category, todos-set-item-priority, todos-insert-with-overlays and todos-item-counts; restore if user quits before inserting undone item. (todos-date-pattern): Rewrite without using todos-date-nodayname-pattern and todos-dayname-date-pattern. (todos-date-string-match): Do not make todos-date-pattern an unnumbered group. (todos-time-string-match): Make todos-date-pattern a shy group. (todos-wrap-and-indent): Use todos-indent-to-here. (todos-reset-prefix): Revert to using todos-show instead of todos-category-select. (todos-prefix-overlays): Fix numbering of done items and updating of prefix. (todos-category-select): Use todos-current-category; fix display of separator string; don't move point to top of category. (todos-jump-to-category-noninteractively): Use todos-category-number. (todos-insert-with-overlays): Use todos-item-start unconditionally. (todos-item-start): New variable. (todos-item-start): Use it to define this function. (todos-item-end): Adjust if item is last unfinished one before displayed done items. (todos-remove-item): Use todos-item-start and todos-item-end instead of todos-forward-item and todos-backward-item. (todos-longest-category-name-length): Add argument for list of categories.
-rw-r--r--lisp/ChangeLog100
-rw-r--r--lisp/calendar/todos.el1761
2 files changed, 984 insertions, 877 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index e2d88a96ac4..0bcb61c3712 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,5 +1,100 @@
12012-09-13 Stephen Berman <stephen.berman@gmx.net> 12012-09-13 Stephen Berman <stephen.berman@gmx.net>
2 2
3 * calendar/todos.el: Numerous spelling and comment fixes, doc
4 string fixes to conform with checkdoc, further rearrangement of
5 definitions, etc.
6 (todos-previous-line, todos-previous-answer)
7 (todos-insert-item-ask-date, todos-change-date)
8 (todos-date-nodayname-pattern, todos-dayname-date-pattern)
9 (todos-count-items-in-category, todos-count-all-items): Remove.
10 (todos-update-numbered-prefix, todos-item-start-overlays)
11 (todos-add-item-non-interactively): Comment out, also in uses.
12 (todos-done-separator): Change default value.
13 (todos-always-add-time-string): Rename from todos-add-time-string
14 and adjust uses.
15 (todos-read-category): Rename from todos-completing-read and
16 adjust callers; use todos-current-category.
17 (todos-make-categories-alist): Rename from function
18 todos-categories-alist.
19 (todos-categories-alist): New variable.
20 (todos-indent-to-here): New defcustom.
21 (todos-button): New face.
22 (todos-display-categories-alphabetically, todos-raise-category)
23 (todos-lower-category, todos-insert-item-for-diary-ask-date-time)
24 (todos-insert-item-here-ask-date-time)
25 (todos-insert-item-ask-date-time)
26 (todos-insert-item-ask-dayname-time): New commands.
27 (todos-edit-item-header): New command replacing todos-change-date.
28 (todos-category-number, todos-indent, todos-item-counts)
29 (todos-check-category-name, todos-read-date, todos-read-dayname)
30 (todos-read-time, todos-padded-string)
31 (todos-insert-category-name): New functions.
32 (todos-set-item-priority): New function replacing
33 todos-add-item-non-interactively.
34 (todos-mode-map): Remap newline to newline-and-indent.
35 (todos-edit-mode-map): Make sparse keymap; remap newline to
36 newline-and-indent.
37 (todos-categories-mode-map): New keymap.
38 (todos-mode, todos-edit-mode): Make indent-line-function local
39 variable and set to todos-indent.
40 (todos-categories-mode): New major mode.
41 (todos-display-categories): List categories initially in their
42 numerical order; add optional argument to switch to alphabetical
43 listing.
44 (todos-toggle-view-done-items): Simplify implementation.
45 (todos-toggle-display-date-time): Fix regexp search string.
46 (todos-backward-item, todos-forward-item): Use variable
47 todos-item-start.
48 (todos-add-category): Use todos-check-category-name and
49 todos-categories-alist.
50 (todos-rename-category): Use todos-current-category,
51 todos-check-category-name and todos-categories-alist.
52 (todos-delete-category): Use todos-check-category-name and
53 todos-categories-alist and take done items into account.
54 (todos-insert-item): Use separate arguments to handle insertion of
55 date/dayname and time strings, add new argument to mark item for
56 diary inclusion, use new todos-read-* functions,
57 todos-set-item-priority and todos-item-counts.
58 (todos-insert-item-here, todos-insert-item-for-diary)
59 (todos-insert-item-from-calendar): Adapt to new version of
60 todos-insert-item.
61 (todos-delete-item, todos-item-done): Use todos-item-counts.
62 (todos-edit-item): Indent newlines inserted by C-q C-j if nonspace
63 char follows.
64 (todos-lower-item): Ensure only not-done items can be lowered.
65 (todos-move-item): Use todos-current-category, todos-read-category
66 and todos-item-counts.
67 (todos-archive-done-items): Use todos-current-category and
68 todos-item-counts; fix regexp search string.
69 (todos-item-undo): Use todos-current-category,
70 todos-set-item-priority, todos-insert-with-overlays and
71 todos-item-counts; restore if user quits before inserting undone
72 item.
73 (todos-date-pattern): Rewrite without using
74 todos-date-nodayname-pattern and todos-dayname-date-pattern.
75 (todos-date-string-match): Do not make todos-date-pattern an
76 unnumbered group.
77 (todos-time-string-match): Make todos-date-pattern a shy group.
78 (todos-wrap-and-indent): Use todos-indent-to-here.
79 (todos-reset-prefix): Revert to using todos-show instead of
80 todos-category-select.
81 (todos-prefix-overlays): Fix numbering of done items and updating
82 of prefix.
83 (todos-category-select): Use todos-current-category; fix display
84 of separator string; don't move point to top of category.
85 (todos-jump-to-category-noninteractively): Use todos-category-number.
86 (todos-insert-with-overlays): Use todos-item-start unconditionally.
87 (todos-item-start): New variable.
88 (todos-item-start): Use it to define this function.
89 (todos-item-end): Adjust if item is last unfinished one before
90 displayed done items.
91 (todos-remove-item): Use todos-item-start and todos-item-end
92 instead of todos-forward-item and todos-backward-item.
93 (todos-longest-category-name-length): Add argument for list of
94 categories.
95
962012-09-13 Stephen Berman <stephen.berman@gmx.net>
97
3 * calendar/todos.el: Comment out calendar require, since diary-lib 98 * calendar/todos.el: Comment out calendar require, since diary-lib
4 requires calendar. Rearrange file to group definitions according 99 requires calendar. Rearrange file to group definitions according
5 to their use (types of commands, internal functions, etc.) 100 to their use (types of commands, internal functions, etc.)
@@ -10,9 +105,8 @@
10 (todos-current-date, todos-item-end-overlays) 105 (todos-current-date, todos-item-end-overlays)
11 (todos-list-categories): Remove. 106 (todos-list-categories): Remove.
12 (todos-item-end): Remove (the variable, not the function). 107 (todos-item-end): Remove (the variable, not the function).
13 (todos-item-overlays): Rename to todos-prefix-overlays and adjust 108 (todos-prefix-overlays): Rename from todos-item-overlays and adjust
14 callers. 109 callers.
15 (todos-prefix-overlays): Rename from todos-item-overlays.
16 (todos-done-separator, todos-done-string, todos-show-with-done) 110 (todos-done-separator, todos-done-string, todos-show-with-done)
17 (todos-files, todos-archive-file, todos-categories-buffer) 111 (todos-files, todos-archive-file, todos-categories-buffer)
18 (todos-archived-categories-buffer, todos-wrap-lines) 112 (todos-archived-categories-buffer, todos-wrap-lines)
@@ -35,7 +129,7 @@
35 (todos-insert-item-from-calendar, todos-edit-quit) 129 (todos-insert-item-from-calendar, todos-edit-quit)
36 (todos-change-date, todos-item-done, todos-archive-done-items) 130 (todos-change-date, todos-item-done, todos-archive-done-items)
37 (todos-item-undo): New commands. 131 (todos-item-undo): New commands.
38 (todos-archive-mode): New mode. 132 (todos-archive-mode): New major mode.
39 (todos-archive-mode-map, todos-edit-mode-map): New keymaps. 133 (todos-archive-mode-map, todos-edit-mode-map): New keymaps.
40 (todos-category-beg): Change value. 134 (todos-category-beg): Change value.
41 (todos-number-prefix): Change default value. 135 (todos-number-prefix): Change default value.
diff --git a/lisp/calendar/todos.el b/lisp/calendar/todos.el
index c4788044520..7ec54e0f2e9 100644
--- a/lisp/calendar/todos.el
+++ b/lisp/calendar/todos.el
@@ -1,4 +1,4 @@
1;;; todos.el --- major mode for editing TODO list files 1;;; Todos.el --- major mode for displaying and editing Todo lists
2 2
3;; Copyright (C) 1997, 1999, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 3;; Copyright (C) 1997, 1999, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
4;; 2008, 2009 Free Software Foundation, Inc. 4;; 2008, 2009 Free Software Foundation, Inc.
@@ -48,7 +48,7 @@
48;; 48;;
49;; Preface, Quickstart Installation 49;; Preface, Quickstart Installation
50;; 50;;
51;; To get this to work, make emacs execute the line 51;; To get this to work, make Emacs execute the line
52;; 52;;
53;; (autoload 'todos "todos" 53;; (autoload 'todos "todos"
54;; "Major mode for editing TODO lists." t) 54;; "Major mode for editing TODO lists." t)
@@ -237,7 +237,7 @@
237;; o GNATS support 237;; o GNATS support
238;; o elide multiline (as in bbdb, or, to a lesser degree, in 238;; o elide multiline (as in bbdb, or, to a lesser degree, in
239;; outline mode) 239;; outline mode)
240;; o rewrite complete package to store data as lisp objects 240;; o rewrite complete package to store data as Lisp objects
241;; and have display modes for display, for diary export, 241;; and have display modes for display, for diary export,
242;; etc. (Richard Stallman pointed out this is a bad idea) 242;; etc. (Richard Stallman pointed out this is a bad idea)
243;; o so base todos.el on generic-mode.el instead 243;; o so base todos.el on generic-mode.el instead
@@ -261,8 +261,8 @@
261(require 'diary-lib) 261(require 'diary-lib)
262 262
263;; --------------------------------------------------------------------------- 263;; ---------------------------------------------------------------------------
264
265;;; Customizable options 264;;; Customizable options
265
266(defgroup todos nil 266(defgroup todos nil
267 "Maintain lists of todo items." 267 "Maintain lists of todo items."
268 :link '(emacs-commentary-link "todos") 268 :link '(emacs-commentary-link "todos")
@@ -275,21 +275,6 @@
275 :initialize 'custom-initialize-default 275 :initialize 'custom-initialize-default
276 :set 'todos-reset-prefix 276 :set 'todos-reset-prefix
277 :group 'todos) 277 :group 'todos)
278
279;; "TODO mode prefix for entries.
280
281;; This is useful in conjunction with `calendar' and `diary' if you use
282
283;; #include \"~/.todos-do\"
284
285;; in your diary file to include your todo list file as part of your
286;; diary. With the default value \"*/*\" the diary displays each entry
287;; every day and it may also be marked on every day of the calendar.
288;; Using \"&%%(equal (calendar-current-date) date)\" instead will only
289;; show and mark todo entries for today, but may slow down processing of
290;; the diary file somewhat."
291;; :type 'string
292;; :group 'todos)
293 278
294(defcustom todos-number-prefix t 279(defcustom todos-number-prefix t
295 "Non-nil to show item prefixes as consecutively increasing integers." 280 "Non-nil to show item prefixes as consecutively increasing integers."
@@ -298,7 +283,8 @@
298 :set 'todos-reset-prefix 283 :set 'todos-reset-prefix
299 :group 'todos) 284 :group 'todos)
300 285
301(defcustom todos-done-separator (make-string (window-width) ?-) 286;; FIXME: length (window-width) causes problems. Also, bad when window-width changes
287(defcustom todos-done-separator (make-string (1- (window-width)) ?-)
302 "String used to visual separate done from not done items. 288 "String used to visual separate done from not done items.
303Displayed in a before-string overlay by `todos-toggle-view-done-items'." 289Displayed in a before-string overlay by `todos-toggle-view-done-items'."
304 :type 'string 290 :type 'string
@@ -334,11 +320,6 @@ Displayed in a before-string overlay by `todos-toggle-view-done-items'."
334 :type 'file 320 :type 'file
335 :group 'todos) 321 :group 'todos)
336 322
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 323(defcustom todos-mode-hook nil
343 "TODO mode hooks." 324 "TODO mode hooks."
344 :type 'hook 325 :type 'hook
@@ -349,30 +330,13 @@ Displayed in a before-string overlay by `todos-toggle-view-done-items'."
349 :type 'hook 330 :type 'hook
350 :group 'todos) 331 :group 'todos)
351 332
352;; (defcustom todos-insert-threshold 0
353;; "TODO mode insertion accuracy.
354
355;; If you have 8 items in your TODO list, then you may get asked 4
356;; questions by the binary insertion algorithm. However, you may not
357;; really have a need for such accurate priorities amongst your TODO
358;; items. If you now think about the binary insertion halving the size
359;; of the window each time, then the threshold is the window size at
360;; which it will stop. If you set the threshold to zero, the upper and
361;; lower bound will coincide at the end of the loop and you will insert
362;; your item just before that point. If you set the threshold to,
363;; e.g. 8, it will stop as soon as the window size drops below that
364;; amount and will insert the item in the approximate center of that
365;; window."
366;; :type 'integer
367;; :group 'todos)
368
369(defcustom todos-categories-buffer "*TODOS Categories*" 333(defcustom todos-categories-buffer "*TODOS Categories*"
370 "Name of buffer displayed by `todos-display-categories'" 334 "Name of buffer displayed by `todos-display-categories'."
371 :type 'string 335 :type 'string
372 :group 'todos) 336 :group 'todos)
373 337
374(defcustom todos-archived-categories-buffer "*TODOS Archived Categories*" 338(defcustom todos-archived-categories-buffer "*TODOS Archived Categories*"
375 "Name of buffer displayed by `todos-display-categories'" 339 "Name of buffer displayed by `todos-display-categories'."
376 :type 'string 340 :type 'string
377 :group 'todos) 341 :group 'todos)
378 342
@@ -403,7 +367,7 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
403 ) 367 )
404 368
405(defcustom todos-exclusion-end "]" 369(defcustom todos-exclusion-end "]"
406 "String appended to item date to match todos-exclusion-start." 370 "String appended to item date to match `todos-exclusion-start'."
407 :type 'string 371 :type 'string
408 :group 'todos 372 :group 'todos
409 ;; :initialize 'custom-initialize-default 373 ;; :initialize 'custom-initialize-default
@@ -426,11 +390,6 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
4260 means print all entries." 3900 means print all entries."
427 :type 'integer 391 :type 'integer
428 :group 'todos) 392 :group 'todos)
429;; (defcustom todos-remove-separator t
430;; "Non-nil to remove category separators in\
431;; \\[todos-top-priorities] and \\[todos-print]."
432;; :type 'boolean
433;; :group 'todos)
434 393
435(defcustom todos-save-top-priorities-too t 394(defcustom todos-save-top-priorities-too t
436 "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'." 395 "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'."
@@ -438,28 +397,33 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
438 :group 'todos) 397 :group 'todos)
439 398
440(defcustom todos-completion-ignore-case t ;; FIXME: nil for release 399(defcustom todos-completion-ignore-case t ;; FIXME: nil for release
441 "Non-nil means don't consider case significant in todos-completing-read." 400 "Non-nil means don't consider case significant in `todos-read-category'."
442 :type 'boolean 401 :type 'boolean
443 :group 'todos) 402 :group 'todos)
444 403
445(defcustom todos-add-time-string t 404(defcustom todos-always-add-time-string t
446 "Add current time to date string inserted in front of new items." 405 "Add current time to date string inserted in front of new items."
447 :type 'boolean 406 :type 'boolean
448 :group 'todos) 407 :group 'todos)
449 408
450(defcustom todos-wrap-lines t 409(defcustom todos-wrap-lines t
451 "" ;FIXME 410 ""
452 :group 'todos 411 :group 'todos
453 :type 'boolean) 412 :type 'boolean)
454 413
455(defcustom todos-line-wrapping-function 'todos-wrap-and-indent 414(defcustom todos-line-wrapping-function 'todos-wrap-and-indent
456 "" ;FIXME 415 ""
457 :group 'todos 416 :group 'todos
458 :type 'function) 417 :type 'function)
459 418
460;; --------------------------------------------------------------------------- 419(defcustom todos-indent-to-here 6
420 ""
421 :type 'integer
422 :group 'todos)
461 423
424;; ---------------------------------------------------------------------------
462;;; Faces 425;;; Faces
426
463(defface todos-prefix-string 427(defface todos-prefix-string
464 '((t 428 '((t
465 :inherit font-lock-constant-face 429 :inherit font-lock-constant-face
@@ -467,6 +431,13 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
467 "Face for Todos prefix string." 431 "Face for Todos prefix string."
468 :group 'todos) 432 :group 'todos)
469 433
434(defface todos-button
435 '((t
436 :inherit tool-bar
437 ))
438 "Face for buttons in todos-display-categories."
439 :group 'todos)
440
470(defface todos-date 441(defface todos-date
471 '((t 442 '((t
472 :inherit diary 443 :inherit diary
@@ -508,17 +479,11 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
508 "Font-locking for Todos mode.") 479 "Font-locking for Todos mode.")
509 480
510;; --------------------------------------------------------------------------- 481;; ---------------------------------------------------------------------------
482;;; Mode setup
511 483
512;;; Internal variables
513(defvar todos-categories nil 484(defvar todos-categories nil
514 "TODO categories.") 485 "TODO categories.")
515 486
516(defvar todos-previous-line 0
517 "Previous line asked about.")
518
519(defvar todos-previous-answer 0
520 "Previous answer got.")
521
522(defvar todos-mode-map 487(defvar todos-mode-map
523 (let ((map (make-keymap))) 488 (let ((map (make-keymap)))
524 (suppress-keymap map t) 489 (suppress-keymap map t)
@@ -549,10 +514,10 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
549 (define-key map "e" 'todos-edit-item) 514 (define-key map "e" 'todos-edit-item)
550 (define-key map "E" 'todos-edit-multiline) 515 (define-key map "E" 'todos-edit-multiline)
551 ;; (define-key map "" 'todos-change-date) 516 ;; (define-key map "" 'todos-change-date)
552 ;; (define-key map "f" 'todos-file-item) 517 ;; (define-key map "f" 'todos-file-item)
553 (define-key map "ii" 'todos-insert-item) 518 (define-key map "ii" 'todos-insert-item)
554 (define-key map "ih" 'todos-insert-item-here) 519 (define-key map "ih" 'todos-insert-item-here)
555 (define-key map "ia" 'todos-insert-item-ask-date) 520 (define-key map "ia" 'todos-insert-item-ask-date-time)
556 (define-key map "id" 'todos-insert-item-for-diary) 521 (define-key map "id" 'todos-insert-item-for-diary)
557 ;; (define-key map "in" 'todos-insert-item-no-time) 522 ;; (define-key map "in" 'todos-insert-item-no-time)
558 (define-key map "k" 'todos-delete-item) 523 (define-key map "k" 'todos-delete-item)
@@ -563,6 +528,7 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
563 (define-key map "u" 'todos-item-undo) 528 (define-key map "u" 'todos-item-undo)
564 (define-key map "y" 'todos-toggle-item-diary-inclusion) 529 (define-key map "y" 'todos-toggle-item-diary-inclusion)
565 ;; (define-key map "" 'todos-toggle-diary-inclusion) 530 ;; (define-key map "" 'todos-toggle-diary-inclusion)
531 (define-key map [remap newline] 'newline-and-indent)
566 map) 532 map)
567 "Todos mode keymap.") 533 "Todos mode keymap.")
568 534
@@ -588,11 +554,26 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
588 "Todos Archive mode keymap.") 554 "Todos Archive mode keymap.")
589 555
590(defvar todos-edit-mode-map 556(defvar todos-edit-mode-map
591 (let ((map (make-keymap))) 557 (let ((map (make-sparse-keymap)))
592 (define-key map "\C-c\C-q" 'todos-edit-quit) 558 (define-key map "\C-c\C-q" 'todos-edit-quit)
559 (define-key map [remap newline] 'newline-and-indent)
593 map) 560 map)
594 "Todos Edit mode keymap.") 561 "Todos Edit mode keymap.")
595 562
563(defvar todos-categories-mode-map
564 (let ((map (make-sparse-keymap)))
565 (suppress-keymap map t)
566 (define-key map "a" 'todos-display-categories-alphabetically)
567 (define-key map "c" 'todos-display-categories)
568 (define-key map "l" 'todos-lower-category)
569 (define-key map "r" 'todos-raise-category)
570 (define-key map "q" 'bury-buffer) ;FIXME ?
571 ;; (define-key map "A" 'todos-add-category)
572 ;; (define-key map "D" 'todos-delete-category)
573 ;; (define-key map "R" 'todos-rename-category)
574 map)
575 "Todos Categories mode keymap.")
576
596(defvar todos-category-number 0 "TODO category number.") 577(defvar todos-category-number 0 "TODO category number.")
597 578
598(defvar todos-tmp-buffer-name " *todo tmp*") 579(defvar todos-tmp-buffer-name " *todo tmp*")
@@ -600,250 +581,206 @@ Automatically generated when `todos-save-top-priorities' is non-nil."
600(defvar todos-category-beg "--==-- " 581(defvar todos-category-beg "--==-- "
601 "Category start separator to be prepended onto category name.") 582 "Category start separator to be prepended onto category name.")
602 583
603;; --------------------------------------------------------------------------- 584(easy-menu-define todos-menu todos-mode-map "Todo Menu"
585 '("Todo"
586 ["Next category" todos-forward-category t]
587 ["Previous category" todos-backward-category t]
588 ["Jump to category" todos-jump-to-category t]
589 ["Show top priority items" todos-top-priorities t]
590 ["Print categories" todos-print t]
591 "---"
592 ["Edit item" todos-edit-item t]
593 ["File item" todos-file-item t]
594 ["Insert new item" todos-insert-item t]
595 ["Insert item here" todos-insert-item-here t]
596 ["Kill item" todos-delete-item t]
597 "---"
598 ["Lower item priority" todos-lower-item t]
599 ["Raise item priority" todos-raise-item t]
600 "---"
601 ["Next item" todos-forward-item t]
602 ["Previous item" todos-backward-item t]
603 "---"
604 ["Save" todos-save t]
605 ["Save Top Priorities" todos-save-top-priorities t]
606 "---"
607 ["Quit" todos-quit t]
608 ))
604 609
605;;; Commands 610;; As calendar reads .todos-do before todos-mode is loaded.
611;;;###autoload
612(defun todos-mode ()
613 "Major mode for displaying, navigating and editing Todo lists.
606 614
607;;; Navigation 615\\{todos-mode-map}"
616 (interactive)
617 (kill-all-local-variables)
618 (setq major-mode 'todos-mode)
619 (setq mode-name "TODOS")
620 (use-local-map todos-mode-map)
621 (easy-menu-add todos-menu)
622 (when todos-wrap-lines (funcall todos-line-wrapping-function))
623 (make-local-variable 'indent-line-function)
624 (setq indent-line-function 'todos-indent)
625 (make-local-variable 'font-lock-defaults)
626 (setq font-lock-defaults '(todos-font-lock-keywords t))
627 (make-local-variable 'hl-line-range-function)
628 (setq hl-line-range-function
629 (lambda() (when (todos-item-end)
630 (cons (todos-item-start) (todos-item-end)))))
631 ;; (add-hook 'post-command-hook 'todos-show-paren-hack nil t)
632 (add-to-invisibility-spec 'todos)
633 (setq buffer-read-only t)
634 (run-mode-hooks 'todos-mode-hook))
608 635
609(defun todos-forward-category () 636(defun todos-archive-mode ()
610 "Go forward to TODO list of next category." 637 "Major mode for archived Todos categories.
638
639\\{todos-archive-mode-map}"
611 (interactive) 640 (interactive)
612 (setq todos-category-number 641 (kill-all-local-variables)
613 (mod (1+ todos-category-number) (length todos-categories))) 642 (setq major-mode 'todos-archive-mode)
614 (todos-category-select)) 643 (setq mode-name "TODOS Archive")
644 (use-local-map todos-archive-mode-map)
645 ;; (easy-menu-add todos-menu)
646 (when todos-wrap-lines (funcall todos-line-wrapping-function))
647 (make-local-variable 'font-lock-defaults)
648 (setq font-lock-defaults '(todos-font-lock-keywords t))
649 (make-local-variable 'hl-line-range-function)
650 (setq hl-line-range-function
651 (lambda() (when (todos-item-end)
652 (cons (todos-item-start) (todos-item-end)))))
653 ;; (add-hook 'post-command-hook 'todos-show-paren-hack nil t)
654 (add-to-invisibility-spec 'todos)
655 (run-mode-hooks 'todos-mode-hook))
615 656
616(defun todos-backward-category () 657(defun todos-edit-mode ()
617 "Go back to TODO list of previous category." 658 "Major mode for editing multiline Todo items.
659
660\\{todos-edit-mode-map}"
618 (interactive) 661 (interactive)
619 (setq todos-category-number 662 (setq major-mode 'todos-edit-mode)
620 (mod (1- todos-category-number) (length todos-categories))) 663 (setq mode-name "TODOS Edit")
621 (todos-category-select)) 664 (use-local-map todos-edit-mode-map)
665 (make-local-variable 'font-lock-defaults)
666 (setq font-lock-defaults '(todos-font-lock-keywords t))
667 (make-local-variable 'indent-line-function)
668 (setq indent-line-function 'todos-indent)
669 (when todos-wrap-lines (funcall todos-line-wrapping-function)))
622 670
623;; FIXME: Document that a non-existing name creates that category, and add 671(defun todos-categories-mode ()
624;; y-or-n-p confirmation -- or eliminate this possibility? 672 "Major mode for displaying and editing Todos categories.
625(defun todos-jump-to-category () 673
626 "Jump to a category. Default is previous category." 674\\{todos-categories-mode-map}"
627 (interactive) 675 (interactive)
628 (let ((category (todos-completing-read))) 676 (setq major-mode 'todos-categories-mode)
629 (if (string= "" category) 677 (setq mode-name "TODOS Categories")
630 (setq category (nth todos-category-number todos-categories))) 678 (use-local-map todos-categories-mode-map)
631 (setq todos-category-number 679 (make-local-variable 'font-lock-defaults)
632 (if (member category todos-categories) 680 (setq font-lock-defaults '(todos-font-lock-keywords t))
633 (- (length todos-categories) 681 (setq buffer-read-only t)
634 (length (member category todos-categories))) 682)
635 (todos-add-category category)))
636 ;; (todos-show)))
637 (todos-category-select)))
638 683
639;; FIXME ? todos-{backward,forward}-item skip over empty line between done and 684(defun todos-save ()
640;; not done items (but todos-forward-item gets there when done items are not 685 "Save the TODO list."
641;; displayed) 686 (interactive)
642(defun todos-backward-item (&optional count) 687 (save-excursion
643 "Select previous entry of TODO list." 688 (save-restriction
644 (interactive "P") 689 (save-buffer)))
645 ;; FIXME ? this moves to bob if on the first item (but so does previous-line) 690 ;; (if todos-save-top-priorities-too (todos-save-top-priorities)))
646 (todos-item-start) 691 )
647 (unless (bobp)
648 (re-search-backward (concat "^\\(\\[\\(" (regexp-quote todos-done-string)
649 "\\)?\\)?\\(" todos-date-pattern "\\)")
650 nil t (or count 1))))
651 692
652(defun todos-forward-item (&optional count) 693(defun todos-quit ()
653 "Select COUNT-th next entry of TODO list." 694 "Done with TODO list for now."
654 (interactive "P") 695 (interactive)
655 (goto-char (line-end-position)) 696 (widen)
656 (if (re-search-forward (concat "^\\(\\[\\(" (regexp-quote todos-done-string) 697 (todos-save)
657 "\\)?\\)?\\(" todos-date-pattern "\\)") 698 (message "")
658 nil t (or count 1)) 699 (bury-buffer))
659 (goto-char (match-beginning 0))
660 (goto-char (point-max))))
661 700
662;; (defun todos-forward-item (&optional count) 701;; ---------------------------------------------------------------------------
663;; "Select COUNT-th next entry of TODO list." 702;;; Commands
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 703
691(defvar todos-search-string nil 704;;; Display
692 "" ;FIXME 705
693 ) 706;;;###autoload
694(defun todos-search () 707(defun todos-show ()
695 "" ;FIXME 708 "Show TODO list."
696 (interactive) 709 (interactive)
697 (let ((regex (read-from-minibuffer "Enter a search string (regexp): ")) 710 ;; Make this a no-op if called interactively in narrowed Todos mode, since
698 (start (point)) 711 ;; it is in that case redundant, but in particular to work around the bug of
699 found cat in-done) 712 ;; item prefix reduplication with show-paren-mode enabled.
700 (widen) 713 (unless (and (called-interactively-p)
701 (goto-char (point-min)) 714 (eq major-mode 'todos-mode)
702 (while (and (setq found (re-search-forward regex nil t)) 715 (< (- ( point-max) (point-min)) (buffer-size)))
703 (save-excursion 716 ;; Call todos-initial-setup only if there is neither a Todo file nor
704 (goto-char (line-beginning-position)) 717 ;; a corresponding unsaved buffer.
705 (looking-at (concat "^" (regexp-quote todos-category-beg))))) 718 (if (or (file-exists-p todos-file-do)
706 (forward-line)) 719 (let* ((buf (get-buffer (file-name-nondirectory todos-file-do)))
707 (if found 720 (bufname (buffer-file-name buf)))
708 (progn 721 (equal (expand-file-name todos-file-do) bufname)))
709 (setq found (match-beginning 0)) 722 (find-file todos-file-do)
710 (todos-item-start) 723 (todos-initial-setup))
711 (when (looking-at (concat "^\\[" (regexp-quote todos-done-string))) 724 (unless (eq major-mode 'todos-mode) (todos-mode))
712 (setq in-done t)) 725 (unless todos-categories-alist
713 (re-search-backward (concat "^" (regexp-quote todos-category-beg) 726 (setq todos-categories-alist (todos-make-categories-alist)))
714 "\\(.*\\)\n") nil t) 727 (unless todos-categories
715 (setq cat (match-string-no-properties 1)) 728 (setq todos-categories (mapcar 'car todos-categories-alist)))
716 (setq todos-category-number 729 (save-excursion
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) 730 (todos-category-select)
722 (goto-char start) 731 ;; (todos-show-paren-hack)
723 (message "No match for \"%s\"" regex)))) 732 )))
724 733
725;;; Display 734(defun todos-display-categories (&optional alpha)
735 "Display a numbered list of the Todos category names.
736The numbers give the order of the categories.
726 737
727(defun todos-display-categories () 738With non-nil ALPHA display a non-numbered alphabetical list.
728 "Display an alphabetical list of clickable Todos category names. 739The lists are in Todos Categories mode.
729Click or type RET on a category name to go to it." 740
741The category names are buttonized, and pressing a button displays
742the category in Todos mode."
730 (interactive) 743 (interactive)
731 (let ((categories (copy-sequence todos-categories)) 744 (let ((categories (copy-sequence todos-categories))
732 (cat-alist (todos-categories-alist)) 745 (num 0))
733 (len (todos-longest-category-name-length)) 746 (when alpha ;alphabetize the list case insensitively
734 beg) 747 (setq categories (sort categories (lambda (s1 s2) (let ((cis1 (upcase s1))
735 ;; alphabetize the list case insensitively 748 (cis2 (upcase s2)))
736 (setq categories (sort categories (lambda (s1 s2) (let ((cis1 (upcase s1)) 749 (string< cis1 cis2))))))
737 (cis2 (upcase s2)))
738 (string< cis1 cis2)))))
739 (with-current-buffer (get-buffer-create todos-categories-buffer) 750 (with-current-buffer (get-buffer-create todos-categories-buffer)
740 (switch-to-buffer (current-buffer)) 751 (switch-to-buffer (current-buffer))
741 (erase-buffer) 752 (let (buffer-read-only)
742 (kill-all-local-variables) 753 (erase-buffer)
743 (insert "Press a button to display the corresponding category.\n\n") 754 (kill-all-local-variables)
744 (setq beg (point)) 755 (insert "Press a button to display the corresponding category.\n\n")
745 (mapc (lambda (cat) 756 ;; FIXME: abstract format from here and todos-insert-category-name
746 (let* ((catlen (length cat)) 757 (insert (make-string 4 32) (todos-padded-string "Category")
747 (catlen-odd (eq (logand catlen 1) 1)) ; oddp from cl.el 758 (make-string 7 32) "Todos Done\n\n")
748 (padding (/ (- len catlen) 2))) 759 (save-excursion
749 (insert-button (concat (make-string padding 32) cat 760 (mapc '(lambda (cat) (todos-insert-category-name cat alpha)) categories)))
750 (make-string (if catlen-odd 761 (todos-categories-mode))))
751 (1+ padding) 762
752 padding) 763(defun todos-display-categories-alphabetically ()
753 32)) 764 ""
754 'face 'tool-bar 765 (interactive)
755 'action 766 (todos-display-categories t))
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 767
791(defun todos-toggle-item-numbering () 768(defun todos-toggle-item-numbering ()
792 "" ;FIXME 769 ""
793 (interactive) 770 (interactive)
794 (todos-reset-prefix 'todos-number-prefix (not todos-number-prefix))) 771 (todos-reset-prefix 'todos-number-prefix (not todos-number-prefix)))
795 772
796(defun todos-toggle-view-done-items () 773(defun todos-toggle-view-done-items ()
797 "" ; FIXME 774 ""
798 (interactive) 775 (interactive)
799 (let ((beg (point-min)) 776 (save-excursion
800 (done-sep (if (string-match "^[[:space:]]*$" todos-done-separator) 777 (goto-char (point-min))
801 todos-done-separator 778 (let ((todos-show-with-done
802 (propertize (concat todos-done-separator "\n") 779 (if (re-search-forward (concat "\n\\(\\[" (regexp-quote todos-done-string)
803 'face 'todos-done-sep))) 780 "\\)") nil t)
804 (todos-show-with-done nil) 781 nil
805 (done (point-max)) 782 t)))
806 end ov) 783 (todos-category-select))))
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 784
848(defun todos-view-archive (&optional cat) 785(defun todos-view-archive (&optional cat)
849 "" 786 ""
@@ -858,7 +795,7 @@ Click or type RET on a category name to go to it."
858 (todos-category-select))) 795 (todos-category-select)))
859 (error "There is currently no Todos archive"))) 796 (error "There is currently no Todos archive")))
860 797
861;; FIXME: very slow 798;; FIXME: slow
862(defun todos-diary-items () 799(defun todos-diary-items ()
863 "Display all todo items marked for diary inclusion." 800 "Display all todo items marked for diary inclusion."
864 (interactive) 801 (interactive)
@@ -911,7 +848,7 @@ Click or type RET on a category name to go to it."
911 848
912;; FIXME: make this a customizable option for whole Todos file 849;; FIXME: make this a customizable option for whole Todos file
913(defun todos-toggle-display-date-time () 850(defun todos-toggle-display-date-time ()
914 "" ; FIXME 851 ""
915 (interactive) 852 (interactive)
916 (save-excursion 853 (save-excursion
917 (goto-char (point-min)) 854 (goto-char (point-min))
@@ -926,13 +863,15 @@ Click or type RET on a category name to go to it."
926 (if hidden (remove-overlays (point-min) (point-max) 'display "") 863 (if hidden (remove-overlays (point-min) (point-max) 'display "")
927 (while (not (eobp)) 864 (while (not (eobp))
928 (re-search-forward (concat "^\\[?" todos-date-pattern 865 (re-search-forward (concat "^\\[?" todos-date-pattern
929 " \\(" diary-time-regexp "\\)?\\]? ") 866 "\\( " diary-time-regexp "\\)?\\]? ")
930 ; FIXME: this space in header? ^ 867 ; FIXME: this space in header? ^
931 nil t) 868 nil t)
932 (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t)) 869 (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t))
933 (overlay-put ov 'display "") 870 (overlay-put ov 'display "")
934 (forward-line))) 871 (forward-line)))
935 (todos-update-numbered-prefix)))) 872 ;; FIXME: need this?
873 ;; (todos-update-numbered-prefix)
874 )))
936 875
937;;;###autoload 876;;;###autoload
938(defun todos-top-priorities (&optional nof-priorities category-pr-page show-done) 877(defun todos-top-priorities (&optional nof-priorities category-pr-page show-done)
@@ -1004,6 +943,91 @@ With non-nil SHOW-DONE, include done items in the listing."
1004 (message "Type C-x 1 to remove %s window. M-C-v to scroll the help." 943 (message "Type C-x 1 to remove %s window. M-C-v to scroll the help."
1005 todos-print-buffer-name))) 944 todos-print-buffer-name)))
1006 945
946;;; Navigation
947
948(defun todos-forward-category ()
949 "Go forward to TODO list of next category."
950 (interactive)
951 (setq todos-category-number
952 (mod (1+ todos-category-number) (length todos-categories)))
953 (todos-category-select))
954
955(defun todos-backward-category ()
956 "Go back to TODO list of previous category."
957 (interactive)
958 (setq todos-category-number
959 (mod (1- todos-category-number) (length todos-categories)))
960 (todos-category-select))
961
962;; FIXME: Document that a non-existing name creates that category, and add
963;; y-or-n-p confirmation -- or eliminate this possibility?
964(defun todos-jump-to-category ()
965 "Jump to a category. Default is previous category."
966 (interactive)
967 (let ((category (todos-read-category)))
968 (if (string= "" category)
969 (setq category (todos-current-category)))
970 (setq todos-category-number
971 (if (member category todos-categories)
972 (- (length todos-categories)
973 (length (member category todos-categories)))
974 (todos-add-category category)))
975 ;; (todos-show)))
976 (todos-category-select)))
977
978;; FIXME ? todos-{backward,forward}-item skip over empty line between done and
979;; not done items (but todos-forward-item gets there when done items are not
980;; displayed). Also disallow prefix arg value < 1 (re-search-* allows these)
981(defun todos-backward-item (&optional count)
982 "Select COUNT-th previous entry of TODO list."
983 (interactive "P")
984 ;; FIXME ? this moves to bob if on the first item (but so does previous-line)
985 (todos-item-start)
986 (unless (bobp)
987 (re-search-backward todos-item-start nil t (or count 1))))
988
989(defun todos-forward-item (&optional count)
990 "Select COUNT-th next entry of TODO list."
991 (interactive "P")
992 (goto-char (line-end-position))
993 (if (re-search-forward todos-item-start nil t (or count 1))
994 (goto-char (match-beginning 0))
995 (goto-char (point-max))))
996
997;; FIXME: continue search with same regexp
998(defvar todos-search-string nil
999 ""
1000 )
1001(defun todos-search ()
1002 ""
1003 (interactive)
1004 (let ((regex (read-from-minibuffer "Enter a search string (regexp): "))
1005 (start (point))
1006 found cat in-done)
1007 (widen)
1008 (goto-char (point-min))
1009 (while (and (setq found (re-search-forward regex nil t))
1010 (save-excursion
1011 (goto-char (line-beginning-position))
1012 (looking-at (concat "^" (regexp-quote todos-category-beg)))))
1013 (forward-line))
1014 (if found
1015 (progn
1016 (setq found (match-beginning 0))
1017 (todos-item-start)
1018 (when (looking-at (concat "^\\[" (regexp-quote todos-done-string)))
1019 (setq in-done t))
1020 (re-search-backward (concat "^" (regexp-quote todos-category-beg)
1021 "\\(.*\\)\n") nil t)
1022 (setq cat (match-string-no-properties 1))
1023 (todos-category-number cat)
1024 (todos-category-select)
1025 (when in-done (unless todos-show-with-done (todos-toggle-view-done-items)))
1026 (goto-char found))
1027 (todos-category-select)
1028 (goto-char start)
1029 (message "No match for \"%s\"" regex))))
1030
1007;;; Editing 1031;;; Editing
1008 1032
1009;;;###autoload 1033;;;###autoload
@@ -1011,25 +1035,18 @@ With non-nil SHOW-DONE, include done items in the listing."
1011 "Add new category CAT to the TODO list." 1035 "Add new category CAT to the TODO list."
1012 (interactive) 1036 (interactive)
1013 (let ((buffer-read-only) 1037 (let ((buffer-read-only)
1014 (buf (find-file-noselect todos-file-do t)) 1038 (buf (find-file-noselect todos-file-do t)))
1015 (prompt "Category: "))
1016 (unless (zerop (buffer-size buf)) 1039 (unless (zerop (buffer-size buf))
1017 (and (null todos-categories) 1040 (and (null todos-categories)
1018 (error "Error in %s: File is non-empty but contains no category" 1041 (error "Error in %s: File is non-empty but contains no category"
1019 todos-file-do))) 1042 todos-file-do)))
1020 (unless cat (setq cat (read-from-minibuffer prompt))) 1043 (unless cat (setq cat (read-from-minibuffer "Category: ")))
1021 (with-current-buffer buf 1044 (with-current-buffer buf
1022 ;; reject names that could induce bugs and confusion 1045 (setq cat (todos-check-category-name cat))
1023 (while (and (cond ((string= "" cat)
1024 (setq prompt "Enter a non-empty category name: "))
1025 ((string-match "\\`\\s-+\\'" cat)
1026 (setq prompt "Enter a category name that is not only white space: "))
1027 ((member cat todos-categories)
1028 (setq prompt "Enter a non-existing category name: ")))
1029 (setq cat (read-from-minibuffer prompt))))
1030 ;; initialize a newly created Todo buffer for Todo mode 1046 ;; initialize a newly created Todo buffer for Todo mode
1031 (unless (file-exists-p todos-file-do) (todos-mode)) 1047 (unless (file-exists-p todos-file-do) (todos-mode))
1032 (setq todos-categories (cons cat todos-categories)) 1048 (push cat todos-categories)
1049 (push (list cat (cons 0 0)) todos-categories-alist)
1033 (widen) 1050 (widen)
1034 (goto-char (point-min)) 1051 (goto-char (point-min))
1035 ;; make sure file does not begin with empty lines (shouldn't, but may be 1052 ;; make sure file does not begin with empty lines (shouldn't, but may be
@@ -1042,33 +1059,24 @@ With non-nil SHOW-DONE, include done items in the listing."
1042 (progn (setq todos-category-number 0) (todos-show)) 1059 (progn (setq todos-category-number 0) (todos-show))
1043 0)))) 1060 0))))
1044 1061
1045;; FIXME: use function for category name choice here and in todos-add-category
1046(defun todos-rename-category () 1062(defun todos-rename-category ()
1047 "Rename current Todos category." 1063 "Rename current Todos category."
1048 (interactive) 1064 (interactive)
1049 (let* ((buffer-read-only) 1065 (let* ((buffer-read-only)
1050 (cat (nth todos-category-number todos-categories)) 1066 (cat (todos-current-category))
1051 (vec (vconcat todos-categories)) 1067 (vec (vconcat todos-categories))
1052 (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat))) 1068 (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat))))
1053 prompt) 1069 (setq new (todos-check-category-name new))
1054 (while (and (cond ((string= "" new) 1070 (aset vec todos-category-number new)
1055 (setq prompt "Enter a non-empty category name: "))
1056 ((string-match "\\`\\s-+\\'" new)
1057 (setq prompt "Enter a category name that is not only white space: "))
1058 ((member new todos-categories)
1059 (setq prompt "Enter a non-existing category name: ")))
1060 (setq new (read-from-minibuffer prompt))))
1061 (aset vec todos-category-number new)
1062 (setq todos-categories (append vec nil)) 1071 (setq todos-categories (append vec nil))
1072 (setcar (assoc cat todos-categories-alist) new)
1063 (save-excursion 1073 (save-excursion
1064 (widen) 1074 (widen)
1065 (re-search-backward (concat (regexp-quote todos-category-beg) "\\(" 1075 (re-search-backward (concat (regexp-quote todos-category-beg) "\\("
1066 (regexp-quote cat) "\\)\n") nil t) 1076 (regexp-quote cat) "\\)\n") nil t)
1067 (replace-match new t t nil 1) 1077 (replace-match new t t nil 1)
1068 (goto-char (point-min)) 1078 (goto-char (point-min))
1069 (setq mode-line-buffer-identification 1079 (setq mode-line-buffer-identification (concat "Category: " new))))
1070 (concat "Category: " new))))
1071;; (concat "Category: " (format "%18s" new)))))
1072 (todos-category-select)) 1080 (todos-category-select))
1073 1081
1074(defun todos-delete-category (&optional arg) 1082(defun todos-delete-category (&optional arg)
@@ -1076,11 +1084,13 @@ With non-nil SHOW-DONE, include done items in the listing."
1076With ARG non-nil delete the category unconditionally, 1084With ARG non-nil delete the category unconditionally,
1077i.e. including all existing entries." 1085i.e. including all existing entries."
1078 (interactive "P") 1086 (interactive "P")
1079 (if (and (null arg) 1087 (let* ((cat (todos-current-category))
1080 ;; FIXME: what about done items? 1088 (not-done (car (todos-item-counts cat)))
1081 (not (eq (point-max) (point-min)))) 1089 (done (cdr (todos-item-counts cat)))
1082 (message "To delete a non-empty category, call the command with a prefix argument.") 1090 beg end)
1083 (let ((cat (nth todos-category-number todos-categories)) beg end) 1091 (if (and (null arg)
1092 (or (> not-done 0) (> done 0)))
1093 (message "To delete a non-empty category, type C-u D.")
1084 (when (y-or-n-p (concat "Permanently remove category \"" cat 1094 (when (y-or-n-p (concat "Permanently remove category \"" cat
1085 "\"" (and arg " and all its entries") "? ")) 1095 "\"" (and arg " and all its entries") "? "))
1086 (let ((buffer-read-only)) 1096 (let ((buffer-read-only))
@@ -1095,81 +1105,192 @@ i.e. including all existing entries."
1095 (remove-overlays beg end) 1105 (remove-overlays beg end)
1096 (kill-region beg end) 1106 (kill-region beg end)
1097 (setq todos-categories (delete cat todos-categories)) 1107 (setq todos-categories (delete cat todos-categories))
1108 (setq todos-categories-alist
1109 (delete (assoc cat todos-categories-alist) todos-categories-alist))
1098 (todos-category-select) 1110 (todos-category-select)
1099 (message "Deleted category %s" cat)))))) 1111 (message "Deleted category %s" cat))))))
1100 1112
1113(defun todos-raise-category (&optional lower)
1114 "Raise priority of category point is on in Categories buffer.
1115With non-nil argument LOWER, lower the category's priority."
1116 (interactive)
1117 (let (num)
1118 (save-excursion
1119 (forward-line 0)
1120 (skip-chars-forward " ")
1121 (setq num (number-at-point)))
1122 (when (and num (if lower
1123 (< num (length todos-categories))
1124 (> num 1)))
1125 (let* ((col (current-column))
1126 (beg (progn (forward-line (if lower 0 -1)) (point)))
1127 (num1 (progn (skip-chars-forward " ") (1- (number-at-point))))
1128 (num2 (1+ num1))
1129 (end (progn (forward-line 2) (point)))
1130 (catvec (vconcat todos-categories))
1131 (cat1 (aref catvec num1))
1132 (cat2 (aref catvec num2))
1133 (buffer-read-only))
1134 (delete-region beg end)
1135 (setq num1 (1+ num1)
1136 num2 (1- num2))
1137 (setq num num2)
1138 (todos-insert-category-name cat2)
1139 (setq num num1)
1140 (todos-insert-category-name cat1)
1141 (aset catvec num2 cat2)
1142 (aset catvec num1 cat1)
1143 (setq todos-categories (append catvec nil))
1144 (forward-line (if lower -1 -2))
1145 (forward-char col)))))
1146
1147(defun todos-lower-category ()
1148 "Lower priority of category point is on in Categories buffer."
1149 (interactive)
1150 (todos-raise-category t))
1151
1101;;;###autoload 1152;;;###autoload
1102(defun todos-insert-item (&optional arg here date-time) ; FIXME revise docstring 1153(defun todos-insert-item (&optional arg date-type time diary here)
1103 "Insert new TODO list item. 1154 "Insert new TODO list item.
1104 1155
1105With prefix argument ARG solicit the category, otherwise use the 1156With prefix argument ARG solicit the category, otherwise use the
1106current category. 1157current category.
1107 1158
1159Argument DATE-TYPE sets the form of the item's mandatory date
1160string. With the value `date' this is the full date (whose
1161format is set by `calendar-date-display-form', with year, month
1162and day individually solicited (month with tab completion). With
1163the value `dayname' a weekday name is used, solicited with tab
1164completion. With the value `calendar' the full date string is
1165used and set by selecting from the Calendar. With any other
1166value (including none) the full current date is used.
1167
1168Argument TIME determines the occurrence and value of the time
1169string. With the value `omit' insert the item without a time
1170string. With the value `ask' solicit a time string; this may be
1171empty or else must match `date-time-regexp'. With any other
1172value add or omit the current time in accordance with
1173`todos-always-add-time-string'.
1174
1175With non-nil argument DIARY mark item for inclusion in user's diary. If `todos-include-in-diary' is non-nil
1176
1108With non-nil argument HERE insert the new item directly above the 1177With non-nil argument HERE insert the new item directly above the
1109item at point. If point is on an empty line, insert the new item 1178item at point. If point is on an empty line, insert the new item
1110there. 1179there."
1111
1112If the value of TIME is `omit', insert the item without a time
1113string; with the value `ask', solicit a time string; with any
1114other value, add or omit the current time in accordance with
1115`todos-add-time-string'."
1116 (interactive "P") 1180 (interactive "P")
1117 (unless (or (todos-done-item-p) 1181 (unless (or (todos-done-item-p)
1118 (save-excursion (forward-line -1) (todos-done-item-p))) 1182 (save-excursion (forward-line -1) (todos-done-item-p)))
1119 (if (not (derived-mode-p 'todos-mode)) (todos-show)) 1183 (if (not (derived-mode-p 'todos-mode)) (todos-show))
1120 (let* ((buffer-read-only) 1184 (let* ((buffer-read-only)
1121 (date-string (cond ;; ((eq date-time 'omit) "") 1185 (date-string (cond
1122 ((eq date-time 'ask) 1186 ((eq date-type 'ask-date)
1123 (read-from-minibuffer "Enter a date: ")) 1187 (todos-read-date))
1124 ((eq date-time 'to-date) 1188 ((eq date-type 'ask-dayname)
1189 (todos-read-dayname))
1190 ((eq date-type 'calendar)
1191 ;; FIXME: should only be executed from Calendar
1125 (with-current-buffer "*Calendar*" 1192 (with-current-buffer "*Calendar*"
1126 (calendar-date-string (calendar-cursor-to-date t) t t))) 1193 (calendar-date-string (calendar-cursor-to-date t) t t)))
1127 (t (calendar-date-string (calendar-current-date) t t)))) 1194 (t (calendar-date-string (calendar-current-date) t t))))
1128 (time-string (if todos-add-time-string 1195 (time-string (cond ((eq time 'omit) nil)
1129 (cond ((eq date-time 'omit) "") 1196 ((eq time 'ask-time)
1130 ((eq date-time 'ask) 1197 (todos-read-time))
1131 (read-from-minibuffer "Enter a clock time: ")) 1198 (todos-always-add-time-string
1132 (t (substring (current-time-string) 11 16))) 1199 (substring (current-time-string) 11 16))))
1133 "")) 1200 (new-item (concat (unless (or diary todos-include-in-diary) "[") ;FIXME
1134 (new-item (concat (unless todos-include-in-diary "[") 1201 date-string (when time-string (concat " " time-string))
1135 date-string (unless (string= time-string "") 1202 ;; FIXME
1136 (concat " " time-string)) 1203 (unless (or diary todos-include-in-diary) "]") " "
1137 (unless todos-include-in-diary "]") " "
1138 (read-from-minibuffer "New TODO entry: "))) 1204 (read-from-minibuffer "New TODO entry: ")))
1139 (current-category (nth todos-category-number todos-categories)) 1205 (cat (if arg (todos-read-category) (todos-current-category))))
1140 (category (if arg (todos-completing-read) current-category))) 1206 ;; indent newlines inserted by C-q C-j if nonspace char follows
1141 (if here 1207 (setq new-item (replace-regexp-in-string
1142 (todos-insert-with-overlays new-item) 1208 "\\(\n\\)[^[:blank:]]"
1143 (todos-add-item-non-interactively new-item category))))) 1209 (concat "\n" (make-string todos-indent-to-here 32)) new-item
1144 1210 nil nil 1))
1145(defun todos-insert-item-here (&optional date-time) 1211 ;; (if here
1146 "" ;FIXME add docstring 1212 ;; (todos-insert-with-overlays new-item)
1213 ;; (todos-add-item-non-interactively new-item cat))
1214 (unless here (todos-set-item-priority new-item cat))
1215 (todos-insert-with-overlays new-item)
1216 (todos-item-counts cat 'insert))))
1217
1218;; FIXME: make insertion options customizable per category
1219;; date-type: d n (c) - time - diary - here
1220;; idd inn itt iyy ih
1221;; idtt idyy idh intt inyy inh ityy iyh
1222;; idtyy idyh intyy inyh ityh
1223;; idtyh intyh
1224;; idth inth
1225
1226;; todos-insert-item
1227;; todos-insert-item-ask-date
1228;; todos-insert-item-ask-date-time
1229;; todos-insert-item-ask-dayname
1230;; todos-insert-item-ask-dayname-time
1231;; todos-insert-item-ask-time
1232;; todos-insert-item-for-diary
1233;; todos-insert-item-for-diary-ask-date
1234;; todos-insert-item-for-diary-ask-date-time
1235;; todos-insert-item-for-diary-ask-dayname
1236;; todos-insert-item-for-diary-ask-dayname-time
1237;; todos-insert-item-for-diary-ask-time
1238;; todos-insert-item-here
1239;; todos-insert-item-here-ask-date
1240;; todos-insert-item-here-ask-date-time
1241;; todos-insert-item-here-ask-dayname
1242;; todos-insert-item-here-ask-dayname-time
1243;; todos-insert-item-here-ask-time
1244;; todos-insert-item-here-ask-time-diary
1245;; todos-insert-item-here-for-diary
1246;; todos-insert-item-here-for-diary-ask-date-time
1247;; todos-insert-item-here-for-diary-ask-time
1248;; todos-insert-item-here-for-diary-ask-dayname-time
1249
1250(defun todos-insert-item-here ()
1251 ""
1147 (interactive) 1252 (interactive)
1148 (todos-insert-item nil t date-time)) 1253 (todos-insert-item nil nil nil t))
1149 1254
1150(defun todos-insert-item-no-time (&optional here) 1255(defun todos-insert-item-here-ask-date-time ()
1151 "" ;FIXME add docstring 1256 ""
1152 (interactive) 1257 (interactive)
1153 (todos-insert-item nil here 'omit)) 1258 (todos-insert-item nil 'ask-date 'ask-time t))
1259
1260;; (defun todos-insert-item-no-time ()
1261;; ""
1262;; (interactive)
1263;; (todos-insert-item nil nil 'omit t))
1264
1265(defun todos-insert-item-ask-date-time (&optional arg)
1266 ""
1267 (interactive "P")
1268 (todos-insert-item arg 'ask-date 'ask-time))
1154 1269
1155(defun todos-insert-item-ask-date (&optional here) 1270(defun todos-insert-item-ask-dayname-time (&optional arg)
1156 "" ;FIXME add docstring 1271 ""
1157 (interactive) 1272 (interactive)
1158 (todos-insert-item nil here 'ask)) 1273 (todos-insert-item arg 'ask-dayname 'ask-time))
1274
1275(defun todos-insert-item-for-diary (&optional arg)
1276 ""
1277 (interactive "P")
1278 (let ((todos-include-in-diary t))
1279 (todos-insert-item arg)))
1159 1280
1160(defun todos-insert-item-for-diary (&optional arg here date-time) 1281(defun todos-insert-item-for-diary-ask-date-time (&optional arg)
1161 "" ;FIXME 1282 ""
1162 (interactive "P") 1283 (interactive "P")
1163 (let ((todos-include-in-diary t)) 1284 (let ((todos-include-in-diary t))
1164 (todos-insert-item arg here date-time))) 1285 (todos-insert-item arg 'ask-dayname 'ask-time)))
1165 1286
1166;; FIXME: autoload when key-binding is defined in calendar.el 1287;; FIXME: autoload when key-binding is defined in calendar.el
1167(defun todos-insert-item-from-calendar () 1288(defun todos-insert-item-from-calendar ()
1168 "" ;FIXME 1289 ""
1169 (interactive) 1290 (interactive)
1170 (pop-to-buffer (file-name-nondirectory todos-file-do)) 1291 (pop-to-buffer (file-name-nondirectory todos-file-do))
1171 (todos-show) 1292 (todos-show)
1172 (todos-insert-item t nil 'to-date)) 1293 (todos-insert-item t 'calendar))
1173 1294
1174;; FIXME: calendar is loaded before todos 1295;; FIXME: calendar is loaded before todos
1175;; (add-hook 'calendar-load-hook 1296;; (add-hook 'calendar-load-hook
@@ -1190,11 +1311,12 @@ other value, add or omit the current time in accordance with
1190 ;; not if last item was deleted 1311 ;; not if last item was deleted
1191 (< (point-min) (point-max))) 1312 (< (point-min) (point-max)))
1192 (todos-backward-item)) 1313 (todos-backward-item))
1314 (todos-item-counts (todos-current-category) 'delete)
1193 ;; FIXME: is todos-prefix-overlays part of if-sexp, and is it needed 1315 ;; FIXME: is todos-prefix-overlays part of if-sexp, and is it needed
1194 ;; at all? 1316 ;; at all?
1195 (if todos-number-prefix 1317 ;; (if todos-number-prefix
1196 (todos-update-numbered-prefix) 1318 ;; (todos-update-numbered-prefix)
1197 (todos-prefix-overlays)))) 1319 (todos-prefix-overlays)));)
1198 (error "No TODO list entry to delete"))) 1320 (error "No TODO list entry to delete")))
1199 1321
1200(defun todos-edit-item () 1322(defun todos-edit-item ()
@@ -1208,6 +1330,11 @@ other value, add or omit the current time in accordance with
1208 (let ((new (read-from-minibuffer "Edit: " item))) 1330 (let ((new (read-from-minibuffer "Edit: " item)))
1209 (while (not (string-match (concat "^\\[?" todos-date-pattern) new)) 1331 (while (not (string-match (concat "^\\[?" todos-date-pattern) new))
1210 (setq new (read-from-minibuffer "Item must start with a date: " new))) 1332 (setq new (read-from-minibuffer "Item must start with a date: " new)))
1333 ;; indent newlines inserted by C-q C-j if nonspace char follows
1334 (setq new (replace-regexp-in-string
1335 "\\(\n\\)[^[:blank:]]"
1336 (concat "\n" (make-string todos-indent-to-here 32)) new
1337 nil nil 1))
1211 ;; If user moved point during editing, make sure it moves back. 1338 ;; If user moved point during editing, make sure it moves back.
1212 (goto-char opoint) 1339 (goto-char opoint)
1213 (todos-remove-item) 1340 (todos-remove-item)
@@ -1227,31 +1354,22 @@ other value, add or omit the current time in accordance with
1227 (narrow-to-region (todos-item-start) (todos-item-end)))) 1354 (narrow-to-region (todos-item-start) (todos-item-end))))
1228 1355
1229(defun todos-edit-quit () 1356(defun todos-edit-quit ()
1230 "" ;FIXME 1357 ""
1231 (interactive) 1358 (interactive)
1232 (save-excursion (todos-category-select))) 1359 (save-excursion (todos-category-select)))
1233 1360
1234;; FIXME 1361;; FIXME: complete
1235(defun todos-change-date (&optional event) 1362(defun todos-edit-item-header ()
1236 "" ;FIXME 1363 ""
1237 (interactive) 1364 (interactive)
1238 (let (dmarker 1365 (todos-item-start)
1239 calendar-view-diary-initially-flag 1366 (re-search-forward (concat "^\\[?\\(?1:" todos-date-pattern
1240 new-date) 1367 "\\) \\(?2:" diary-time-regexp "\\)")
1241 (save-excursion 1368 (line-end-position) t)
1242 (todos-item-start) 1369 ;; ask date or dayname
1243 (setq dmarker (point-marker))) 1370 (replace-match new-date nil nil nil 1)
1244 (calendar) 1371 ;; ask time
1245 (message "Put the cursor on the desired date in the Calendar and press `q'") 1372 (replace-match new-date nil nil nil 2))
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 1373
1256(defun todos-raise-item () 1374(defun todos-raise-item ()
1257 "Raise priority of current entry." 1375 "Raise priority of current entry."
@@ -1271,48 +1389,28 @@ other value, add or omit the current time in accordance with
1271 (interactive) 1389 (interactive)
1272 (unless (or (todos-done-item-p) 1390 (unless (or (todos-done-item-p)
1273 (looking-at "^$")) ; between done and not done items 1391 (looking-at "^$")) ; between done and not done items
1274 (let ((buffer-read-only) 1392 (let* ((buffer-read-only)
1275 (done (save-excursion 1393 ;; (end (save-excursion (todos-forward-item) (point)))
1276 (if (re-search-forward (concat "\n\n\\\[" 1394 ;; (done (save-excursion
1277 (regexp-quote todos-done-string)) 1395 ;; (if (re-search-forward (concat "\n\n\\\["
1278 nil t) 1396 ;; (regexp-quote todos-done-string))
1279 (match-beginning 0) 1397 ;; nil t)
1280 (point-max))))) 1398 ;; (match-beginning 0)
1281 (if (> (count-lines (point) done) 1) 1399 ;; (point-max))))
1400 )
1401 ;; (if (> (count-lines (point) done) 1)
1402 (if (save-excursion
1403 ;; can only lower non-final unfinished item
1404 (todos-forward-item)
1405 (and (looking-at todos-item-start)
1406 (not (todos-done-item-p))))
1282 ;; Assume there is a final newline 1407 ;; Assume there is a final newline
1283 (let ((item (todos-item-string)) 1408 (let ((item (todos-item-string)))
1284 opoint)
1285 (todos-remove-item) 1409 (todos-remove-item)
1286 (todos-forward-item) 1410 (todos-forward-item)
1411 (when (todos-done-item-p) (forward-line -1))
1287 (todos-insert-with-overlays item)) 1412 (todos-insert-with-overlays item))
1288 (error "No TODO list entry to lower"))))) 1413 (error "No TODO list entry to lower"))))) ;FIXME: better message
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))))))
1316 1414
1317(defun todos-move-item () 1415(defun todos-move-item ()
1318 "Move the current todo item to another, interactively named, category. 1416 "Move the current todo item to another, interactively named, category.
@@ -1324,57 +1422,35 @@ it is created and the item becomes the first entry in that category."
1324 (looking-at "^$")) ; between done and not done items 1422 (looking-at "^$")) ; between done and not done items
1325 (let ((buffer-read-only) 1423 (let ((buffer-read-only)
1326 (oldnum todos-category-number) 1424 (oldnum todos-category-number)
1327 (oldcat (nth todos-category-number todos-categories)) 1425 (oldcat (todos-current-category))
1328 (item (todos-item-string)) 1426 (item (todos-item-string))
1329 (newcat (todos-completing-read)) 1427 (newcat (todos-read-category))
1330 (opoint (point)) 1428 (opoint (point))
1331 (orig-mrk (save-excursion (todos-item-start) (point-marker))) 1429 (orig-mrk (progn (todos-item-start) (point-marker)))
1332 moved) 1430 moved)
1333 (todos-remove-item) 1431 (todos-remove-item)
1334 ;; numbered prefix isn't cached (see todos-remove-item) so have to update 1432 ;; numbered prefix isn't cached (see todos-remove-item) so have to update
1335 (if todos-number-prefix (todos-update-numbered-prefix)) 1433 ;; (if todos-number-prefix (todos-update-numbered-prefix))
1336 (unwind-protect 1434 (unwind-protect
1337 (progn 1435 (progn
1338 (todos-add-item-non-interactively item newcat) 1436 ;; (todos-add-item-non-interactively item newcat)
1339 (setq moved t)) 1437 (todos-set-item-priority item newcat)
1438 (todos-insert-with-overlays item)
1439 (setq moved t)
1440 (todos-item-counts oldcat 'delete)
1441 (todos-item-counts newcat 'insert))
1340 (unless moved 1442 (unless moved
1341 (widen) 1443 (widen)
1342 (goto-char orig-mrk) 1444 (goto-char orig-mrk)
1343 (todos-insert-with-overlays item) 1445 (todos-insert-with-overlays item)
1344 (setq todos-category-number oldnum) 1446 (setq todos-category-number oldnum)
1447 ;; (todos-item-counts oldcat 'move-failed)
1448 ;; (todos-item-counts newcat 'move-failed)
1345 (todos-category-select) 1449 (todos-category-select)
1346 ;; FIXME: does this work? 1450 ;; FIXME: does this work?
1347 (goto-char opoint)) 1451 (goto-char opoint))
1348 (set-marker orig-mrk nil))))) 1452 (set-marker orig-mrk nil)))))
1349 1453
1350;; (defun todos-file-item (&optional comment)
1351;; "File the current TODO list entry away, annotated with an optional COMMENT."
1352;; (interactive "sComment: ")
1353;; (or (> (count-lines (point-min) (point-max)) 0)
1354;; (error "No TODO list entry to file away"))
1355;; (let ((time-stamp-format todos-time-string-format))
1356;; (when (and comment (> (length comment) 0))
1357;; (goto-char (todos-item-end))
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 () 1454(defun todos-item-done ()
1379 "Mark current item as done and move it to category's done section." 1455 "Mark current item as done and move it to category's done section."
1380 (interactive) 1456 (interactive)
@@ -1383,7 +1459,7 @@ it is created and the item becomes the first entry in that category."
1383 (let* ((buffer-read-only) 1459 (let* ((buffer-read-only)
1384 (item (todos-item-string)) 1460 (item (todos-item-string))
1385 (date-string (calendar-date-string (calendar-current-date) t t)) 1461 (date-string (calendar-date-string (calendar-current-date) t t))
1386 (time-string (if todos-add-time-string 1462 (time-string (if todos-always-add-time-string ;FIXME: delete condition
1387 (concat " " (substring (current-time-string) 11 16)) 1463 (concat " " (substring (current-time-string) 11 16))
1388 "")) 1464 ""))
1389 (done-item (concat "[" todos-done-string date-string time-string "] " item)) 1465 (done-item (concat "[" todos-done-string date-string time-string "] " item))
@@ -1407,13 +1483,14 @@ it is created and the item becomes the first entry in that category."
1407 (goto-char next-cat) 1483 (goto-char next-cat)
1408 (newline)) 1484 (newline))
1409 (todos-insert-with-overlays done-item))) 1485 (todos-insert-with-overlays done-item)))
1486 (todos-item-counts (todos-current-category) 'done)
1410 (todos-show))) 1487 (todos-show)))
1411 1488
1412(defun todos-archive-done-items () 1489(defun todos-archive-done-items ()
1413 "Archive the done items in the current category." 1490 "Archive the done items in the current category."
1414 (interactive) 1491 (interactive)
1415 (let ((archive (find-file-noselect todos-archive-file t)) 1492 (let ((archive (find-file-noselect todos-archive-file t))
1416 (cat (nth todos-category-number todos-categories)) 1493 (cat (todos-current-category))
1417 beg end) 1494 beg end)
1418 (save-excursion 1495 (save-excursion
1419 (save-restriction 1496 (save-restriction
@@ -1423,11 +1500,12 @@ it is created and the item becomes the first entry in that category."
1423 (re-search-backward (concat "^" (regexp-quote todos-category-beg) 1500 (re-search-backward (concat "^" (regexp-quote todos-category-beg)
1424 (regexp-quote cat)) 1501 (regexp-quote cat))
1425 nil t) 1502 nil t)
1426 (if (not (re-search-forward (concat "\\[" (regexp-quote todos-done-string)) 1503 (if (not (re-search-forward (concat "^\\[" (regexp-quote todos-done-string))
1427 nil t)) 1504 nil t))
1428 (error "No done items in this category") 1505 (error "No done items in this category")
1429 (setq beg (match-beginning 0)) 1506 (setq beg (match-beginning 0))
1430 (setq done (buffer-substring beg end)) 1507 (setq done (buffer-substring beg end))
1508 ;; FIXME: update archive alist
1431 (with-current-buffer archive 1509 (with-current-buffer archive
1432 (goto-char (point-min)) 1510 (goto-char (point-min))
1433 (if (re-search-forward (regexp-quote (concat "^" todos-category-beg cat)) 1511 (if (re-search-forward (regexp-quote (concat "^" todos-category-beg cat))
@@ -1437,25 +1515,43 @@ it is created and the item becomes the first entry in that category."
1437 (insert done)) 1515 (insert done))
1438 (delete-region beg end) 1516 (delete-region beg end)
1439 (remove-overlays beg end) 1517 (remove-overlays beg end)
1440 (kill-line -1))))) 1518 (kill-line -1)
1519 (todos-item-counts cat 'archive)))))
1441 (message "Done items archived.")) 1520 (message "Done items archived."))
1442 1521
1443;; FIXME: undone item leaves item number overlay behind
1444(defun todos-item-undo () 1522(defun todos-item-undo ()
1445 "" ;FIXME 1523 ""
1446 (interactive) 1524 (interactive)
1447 (when (todos-done-item-p) 1525 (when (todos-done-item-p)
1448 (let* ((buffer-read-only) 1526 (let* ((buffer-read-only)
1449 (cat (nth todos-category-number todos-categories)) 1527 (cat (todos-current-category))
1450 (start (progn 1528 (done-item (todos-item-string))
1451 (todos-item-start) 1529 (opoint (point))
1452 (search-forward "] "))) ; end of done date string 1530 (orig-mrk (progn (todos-item-start) (point-marker)))
1453 (item (buffer-substring start (todos-item-end)))) 1531 (start (search-forward "] ")) ; end of done date string
1532 (item (buffer-substring start (todos-item-end)))
1533 undone)
1454 (todos-remove-item) 1534 (todos-remove-item)
1455 (todos-add-item-non-interactively item cat)))) 1535 ;; (if todos-number-prefix (todos-update-numbered-prefix))
1536 (unwind-protect
1537 (progn
1538 ;; (todos-add-item-non-interactively item cat)
1539 (todos-set-item-priority item cat)
1540 (todos-insert-with-overlays item)
1541 (setq undone t)
1542 (todos-item-counts cat 'undo))
1543 (unless undone
1544 (widen)
1545 (goto-char orig-mrk)
1546 (todos-insert-with-overlays done-item)
1547 ;; (todos-item-counts cat 'done)
1548 (let ((todos-show-with-done t))
1549 (todos-category-select)
1550 (goto-char opoint)))
1551 (set-marker orig-mrk nil)))))
1456 1552
1457(defun todos-toggle-item-diary-inclusion () 1553(defun todos-toggle-item-diary-inclusion ()
1458 "" ;FIXME add docstring 1554 ""
1459 (interactive) 1555 (interactive)
1460 (save-excursion 1556 (save-excursion
1461 (let* ((buffer-read-only) 1557 (let* ((buffer-read-only)
@@ -1475,7 +1571,7 @@ it is created and the item becomes the first entry in that category."
1475 (insert "]")))))) ; FIXME use todos-exclusion-end 1571 (insert "]")))))) ; FIXME use todos-exclusion-end
1476 1572
1477(defun todos-toggle-diary-inclusion (arg) 1573(defun todos-toggle-diary-inclusion (arg)
1478 "" ;FIXME add docstring 1574 ""
1479 (interactive "p") 1575 (interactive "p")
1480 (save-excursion 1576 (save-excursion
1481 (save-restriction 1577 (save-restriction
@@ -1527,64 +1623,37 @@ Number of entries for each category is given by `todos-print-priorities'."
1527 1623
1528;;; Internal functions 1624;;; Internal functions
1529 1625
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 1626(defvar todos-date-pattern
1568 (concat "\\(?:" todos-date-nodayname-pattern "\\)\\|" 1627 (let ((dayname (diary-name-pattern calendar-day-name-array nil t)))
1569 "\\(?:" todos-date-dayname-pattern "\\)") 1628 (concat "\\(" dayname "\\|"
1629 (let ((dayname)
1630 (monthname (format "\\(%s\\|\\*\\)"
1631 (diary-name-pattern calendar-month-name-array
1632 calendar-month-abbrev-array
1633 t)))
1634 (month "\\([0-9]+\\|\\*\\)")
1635 (day "\\([0-9]+\\|\\*\\)")
1636 (year "-?\\([0-9]+\\|\\*\\)"))
1637 (mapconcat 'eval calendar-date-display-form ""))
1638 "\\)"))
1570 "Regular expression matching a Todos date header.") 1639 "Regular expression matching a Todos date header.")
1571 1640
1572(defun todos-date-string-match (lim) 1641(defun todos-date-string-match (lim)
1573 "Find Todos date strings for font-locking." 1642 "Find Todos date strings within LIM for font-locking."
1574 (re-search-forward (concat "^\\[?\\(" todos-date-pattern "\\)") lim t)) 1643 (re-search-forward (concat "^\\[?" todos-date-pattern) lim t))
1575 1644
1576(defun todos-time-string-match (lim) 1645(defun todos-time-string-match (lim)
1577 "Find Todos time strings for font-locking." 1646 "Find Todos time strings within LIM for font-locking."
1578 (re-search-forward (concat "^\\[?\\(?:" todos-date-pattern "\\)" 1647 (re-search-forward (concat "^\\[?" todos-date-pattern
1579 " \\(?1:" diary-time-regexp "\\)") lim t)) 1648 " \\(?1:" diary-time-regexp "\\)") lim t))
1580 1649
1581(defun todos-done-string-match (lim) 1650(defun todos-done-string-match (lim)
1582 "Find Todos done headers for font-locking." 1651 "Find Todos done headers within LIM for font-locking."
1583 (re-search-forward (concat "^\\[" (regexp-quote todos-done-string) "[^][]+]") 1652 (re-search-forward (concat "^\\[" (regexp-quote todos-done-string) "[^][]+]")
1584 lim t)) 1653 lim t))
1585 1654
1586(defun todos-category-string-match (lim) 1655(defun todos-category-string-match (lim)
1587 "Find Todos category headers for font-locking." 1656 "Find Todos category headers within LIM for font-locking."
1588 (re-search-forward (concat "^" (regexp-quote todos-category-beg) ".*$") 1657 (re-search-forward (concat "^" (regexp-quote todos-category-beg) ".*$")
1589 lim t)) 1658 lim t))
1590 1659
@@ -1606,14 +1675,19 @@ Number of entries for each category is given by `todos-print-priorities'."
1606 (message "This Todos file is well-formatted.")) 1675 (message "This Todos file is well-formatted."))
1607 1676
1608(defun todos-wrap-and-indent () 1677(defun todos-wrap-and-indent ()
1609 "" ;FIXME 1678 ""
1610 (make-local-variable 'word-wrap) 1679 (make-local-variable 'word-wrap)
1611 (setq word-wrap t) 1680 (setq word-wrap t)
1612 (make-local-variable 'wrap-prefix) 1681 (make-local-variable 'wrap-prefix)
1613 (setq wrap-prefix (make-string (+ 5 (length todos-prefix)) 32)) 1682 ;; (setq wrap-prefix (make-string (+ 5 (length todos-prefix)) 32))
1683 (setq wrap-prefix (make-string todos-indent-to-here 32))
1614 (unless (member '(continuation) fringe-indicator-alist) 1684 (unless (member '(continuation) fringe-indicator-alist)
1615 (push '(continuation) fringe-indicator-alist))) 1685 (push '(continuation) fringe-indicator-alist)))
1616 1686
1687(defun todos-indent ()
1688 ""
1689 (indent-to todos-indent-to-here todos-indent-to-here))
1690
1617(defun todos-reset-prefix (symbol value) 1691(defun todos-reset-prefix (symbol value)
1618 "Set SYMBOL's value to VALUE, and ." ; FIXME 1692 "Set SYMBOL's value to VALUE, and ." ; FIXME
1619 (let ((oldvalue (symbol-value symbol))) 1693 (let ((oldvalue (symbol-value symbol)))
@@ -1628,77 +1702,91 @@ Number of entries for each category is given by `todos-print-priorities'."
1628 (remove-overlays (point) (point)); 'before-string prefix) 1702 (remove-overlays (point) (point)); 'before-string prefix)
1629 (forward-line))) 1703 (forward-line)))
1630 ;; activate the prefix setting (save-restriction does not help) 1704 ;; activate the prefix setting (save-restriction does not help)
1631 (todos-show))))) 1705 ;; (todos-show)
1632 1706 (todos-category-select)
1633;; FIXME: rename and/or rewrite 1707 ))))
1634(defun todos-update-numbered-prefix () 1708
1635 "Update consecutive item numbering in the current category." 1709;; FIXME: ??? with todos-lower-item leaves overlay of lower item if this is
1636 (save-excursion 1710;; the third or greater item number -- but not in edebug
1637 (goto-char (point-min)) 1711;; (defun todos-update-numbered-prefix ()
1638 (while (not (eobp)) 1712;; "Update consecutive item numbering in the current category."
1639 (remove-overlays (point) (point) 'before-string) 1713;; (save-excursion
1640 (todos-forward-item)) 1714;; (goto-char (point-min))
1641 (todos-show))) 1715;; (while (not (eobp))
1716;; (let ((ov (car (overlays-in (point) (point))))
1717;; val)
1718;; (when ov
1719;; (setq val (overlay-get ov 'before-string))
1720;; (remove-overlays (point) (point) 'before-string val)))
1721;; (todos-forward-item))
1722;; (todos-show)))
1723
1724;; (defun todos-update-numbered-prefix ()
1725;; "Update consecutive item numbering in the current category."
1726;; (save-excursion
1727;; (goto-char (point-min))
1728;; (while (not (eobp))
1729;; (remove-overlays (point) (point))
1730;; (todos-forward-item))
1731;; ;; FIXME: is todos-prefix-overlays enough?
1732;; (todos-show)))
1642 1733
1643(defvar todos-item-start-overlays nil "") 1734;; (defvar todos-item-start-overlays nil "")
1644 1735
1645;; (defvar todos-done-overlays nil "") 1736;; (defvar todos-done-overlays nil "")
1646 1737
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 () 1738(defun todos-prefix-overlays ()
1654 "" ;FIXME add docstring 1739 ""
1655 (when (or todos-number-prefix 1740 (when (or todos-number-prefix
1656 (not (string-match "^[[:space:]]*$" todos-prefix))) 1741 (not (string-match "^[[:space:]]*$" todos-prefix)))
1657 (let ((prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string)) 1742 (let ((prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string))
1658 (num 0) 1743 (num 0))
1659 lim ov-pref)
1660 (save-excursion 1744 (save-excursion
1661 (goto-char (point-min)) 1745 (goto-char (point-min))
1662 (while (or (todos-date-string-match lim) 1746 (while (not (eobp))
1663 (todos-done-string-match lim)) 1747 (when (or (todos-date-string-match (line-end-position))
1664 (goto-char (match-beginning 0)) 1748 (todos-done-string-match (line-end-position)))
1665 (when todos-number-prefix 1749 (goto-char (match-beginning 0))
1666 (setq num (1+ num)) 1750 (when todos-number-prefix
1667 ;; reset number for done items 1751 (setq num (1+ num))
1668 (if (or (looking-at (concat "\n\\[" (regexp-quote todos-done-string))) 1752 ;; reset number for done items
1669 ;; if last not done item is multiline, then 1753 (when ;; (or
1670 ;; todos-done-string-match skips empty line, so have 1754 ;; ;; FIXME: really need this?
1671 ;; to look back 1755 ;; (looking-at (concat "\n\\[" (regexp-quote todos-done-string)))
1672 (and (looking-at (concat "^\\[" (regexp-quote todos-done-string))) 1756 ;; if last not done item is multiline, then
1673 (looking-back "\n\n"))) 1757 ;; todos-done-string-match skips empty line, so have
1674 (setq num 1)) 1758 ;; to look back.
1675 (setq prefix (propertize (concat (number-to-string num) " ") 1759 (and (looking-at (concat "^\\[" (regexp-quote todos-done-string)))
1676 'face 'todos-prefix-string))) 1760 (looking-back "\n\n"));)
1677 (or (and (setq ov-pref (car (overlays-in (point) (point)))) 1761 (setq num 1))
1678 (equal (overlay-get ov-pref 'before-string) prefix)) 1762 (setq prefix (propertize (concat (number-to-string num) " ")
1679 (and (setq ov-pref (pop todos-item-start-overlays)) 1763 'face 'todos-prefix-string)))
1680 (move-overlay ov-pref (point) (point))) 1764 ;; (let ((ovs (overlays-in (point) (point))))
1681 (and (setq ov-pref (make-overlay (point) (point))) 1765 ;; (or (and (setq ov-pref (car ovs))
1682 (overlay-put ov-pref 'before-string prefix))) 1766 ;; ;; when done-separator overlay is in front of prefix overlay
1683 (forward-line)))))) 1767 ;; (if (and (> (length ovs) 1)
1684 1768 ;; (not (equal (overlay-get ov-pref 'before-string)
1685;; (defun todos-show-paren-hack () 1769 ;; prefix)))
1686;; "Purge overlay duplication due to show-paren-mode." 1770 ;; (setq ov-pref (cadr ovs))
1687;; (save-excursion 1771 ;; t)
1688;; (when show-paren-mode 1772 ;; (equal (overlay-get ov-pref 'before-string) prefix))
1689;; (goto-char (point-min)) 1773 ;; ;; non-numerical prefix
1690;; (while (not (eobp)) 1774 ;; (and (setq ov-pref (pop todos-item-start-overlays))
1691;; ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point)))) 1775 ;; (move-overlay ov-pref (point) (point)))
1692;; (let ((ovlist (overlays-in (point) (point))) 1776 ;; (and (setq ov-pref (make-overlay (point) (point)))
1693;; ov) 1777 ;; (overlay-put ov-pref 'before-string prefix))))
1694;; (while (> (length ovlist) 1) 1778 (let* ((ovs (overlays-in (point) (point)))
1695;; (setq ov (pop ovlist)) 1779 (ov-pref (car ovs))
1696;; (delete-overlay ov))) 1780 (val (when ov-pref (overlay-get ov-pref 'before-string))))
1697;; (forward-line)) 1781 (when (and (> (length ovs) 1)
1698;; (if (and (bolp) (eolp)) 1782 (not (equal val prefix)))
1699;; ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point))))) 1783 (setq ov-pref (cadr ovs)))
1700;; (let ((ovlist (overlays-in (point) (point)))) 1784 (when (not (equal val prefix))
1701;; (remove-overlays (1- (point)) (1+ (point)))))))) 1785 ;; (delete-overlay ov-pref)
1786 (remove-overlays (point) (point)); 'before-string val)
1787 (setq ov-pref (make-overlay (point) (point)))
1788 (overlay-put ov-pref 'before-string prefix))))
1789 (forward-line))))))
1702 1790
1703(defun todos-reset-separator (symbol value) 1791(defun todos-reset-separator (symbol value)
1704 "Set SYMBOL's value to VALUE, and ." ; FIXME 1792 "Set SYMBOL's value to VALUE, and ." ; FIXME
@@ -1710,19 +1798,24 @@ Number of entries for each category is given by `todos-print-priorities'."
1710 (todos-show) 1798 (todos-show)
1711 (save-excursion 1799 (save-excursion
1712 (goto-char (point-min)) 1800 (goto-char (point-min))
1713 (when (re-search-forward (concat "^\\[" (regexp-quote todos-done-string)) nil t) 1801 (when (re-search-forward (concat "^\\[" (regexp-quote todos-done-string))
1802 nil t)
1714 (remove-overlays (point) (point)))) 1803 (remove-overlays (point) (point))))
1715 ;; activate the prefix setting (save-restriction does not help) 1804 ;; activate the prefix setting (save-restriction does not help)
1716 (todos-show))))) 1805 (todos-show)))))
1717 1806
1718;; FIXME: use this; should be defsubst? 1807;; FIXME: should be defsubst?
1808(defun todos-category-number (cat)
1809 "Set todos-category-number to index of CAT in todos-categories."
1810 (setq todos-category-number (- (length todos-categories)
1811 (length (member cat todos-categories)))))
1719(defun todos-current-category () 1812(defun todos-current-category ()
1720 "Return the name of the current category." 1813 "Return the name of the current category."
1721 (nth todos-category-number todos-categories)) 1814 (nth todos-category-number todos-categories))
1722 1815
1723(defun todos-category-select () 1816(defun todos-category-select ()
1724 "Make TODO mode display the current category correctly." 1817 "Make TODO mode display the current category correctly."
1725 (let ((name (nth todos-category-number todos-categories))) 1818 (let ((name (todos-current-category)))
1726 (setq mode-line-buffer-identification (concat "Category: " name)) 1819 (setq mode-line-buffer-identification (concat "Category: " name))
1727 (widen) 1820 (widen)
1728 (goto-char (point-min)) 1821 (goto-char (point-min))
@@ -1736,138 +1829,104 @@ Number of entries for each category is given by `todos-print-priorities'."
1736 (narrow-to-region begin end) 1829 (narrow-to-region begin end)
1737 (goto-char (point-min)))) 1830 (goto-char (point-min))))
1738 (todos-prefix-overlays) 1831 (todos-prefix-overlays)
1739 (let ((beg (point-min)) 1832 ;; display or hide done items as per todos-show-with-done
1740 (done-sep (if (string-match "^[[:space:]]*$" todos-done-separator) 1833 (save-excursion
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) 1834 (when (re-search-forward (concat "\n\\(\\[" (regexp-quote todos-done-string)
1746 "\\)") nil t) 1835 "\\)") nil t)
1747 (setq done (match-beginning 1) 1836 (let (done end done-sep prefix ov-pref ov-done)
1748 end (match-beginning 0)) 1837 (setq done (match-beginning 1)
1749 (if todos-show-with-done 1838 end (match-beginning 0))
1750 ;; with an empty separator just display the done items 1839 (if todos-show-with-done
1751 (if (string= done-sep todos-done-separator) 1840 (progn
1752 (narrow-to-region (point-min) (point-max)) 1841 (setq done-sep todos-done-separator)
1753 ;; else display the separator in an overlay in front of the prefix 1842 (unless (string-match "^[[:space:]]*$" todos-done-separator)
1754 ;; overlay on first done item 1843 (setq done-sep (propertize (concat todos-done-separator "\n")
1755 (let ((prefix (propertize 1844 'face 'todos-done-sep))
1756 (concat (if todos-number-prefix "1" todos-prefix) " ") 1845 (setq prefix (propertize
1757 'face 'todos-prefix-string))) 1846 (concat (if todos-number-prefix "1" todos-prefix) " ")
1758 (goto-char done) 1847 'face 'todos-prefix-string))
1759 (remove-overlays done done) 1848 ;; FIXME? Just deleting done-sep overlay results in bad
1760 ;; must make separator overlay after making prefix overlay to get 1849 ;; display (except when stepping though in edebug)
1761 ;; the order separator before prefix 1850 (remove-overlays done done)
1762 (setq ov-pref (make-overlay done done) 1851 ;; must make separator overlay after making prefix overlay to get
1763 ov-done (make-overlay done done)) 1852 ;; the order separator before prefix
1764 (overlay-put ov-pref 'before-string prefix) 1853 (setq ov-pref (make-overlay done done)
1765 (overlay-put ov-done 'before-string done-sep))) 1854 ov-done (make-overlay done done))
1766 ;; hide done items 1855 (overlay-put ov-pref 'before-string prefix)
1767 (narrow-to-region (point-min) end)))) 1856 (overlay-put ov-done 'before-string done-sep)))
1768 (goto-char (point-min))) 1857 (narrow-to-region (point-min) end))))))
1769 1858
1770;; FIXME: using numbering for priority instead of importance? 1859;; FIXME: why autoload?
1771;;;###autoload 1860;;;###autoload
1772(defun todos-add-item-non-interactively (new-item category) 1861;; (defun todos-add-item-non-interactively (item category)
1773 "Insert NEW-ITEM in TODO list as a new entry in CATEGORY." 1862;; "Insert item ITEM into category CATEGORY and set its priority."
1774 ;; FIXME: really need this? (and in save-excursion?) 1863;; (todos-category-number category)
1775 (save-excursion 1864;; (todos-show) ; now at point-min
1776 (todos-show)) 1865;; (unless (or (eq (point-min) (point-max)) ; no unfinished items
1777 (if (string= "" category) 1866;; (when (re-search-forward (concat "^\\["
1778 (setq category (nth todos-category-number todos-categories))) 1867;; (regexp-quote todos-done-string))
1779 (let ((cat-exists (member category todos-categories))) 1868;; nil t)
1780 (setq todos-category-number 1869;; (forward-line -1)
1781 (if cat-exists 1870;; (bobp))) ; there are done items but no unfinished items
1782 (- (length todos-categories) (length cat-exists)) 1871;; (let* ((maxnum (1+ (car (todos-item-counts category))))
1783 (todos-add-category category)))) 1872;; priority candidate prompt)
1784 ;; FIXME: really need this? (yes for todos-move-item, to show moved to category) 1873;; (while (null priority)
1785 (todos-show) ; now at point-min 1874;; (setq candidate
1786 ;; (setq todos-previous-line 0) 1875;; (string-to-number (read-from-minibuffer
1787 ;; (let* ((top 1) 1876;; (concat prompt
1788 ;; (end (save-excursion 1877;; (format "Set item priority (1-%d): "
1789 ;; (goto-char (point-min)) 1878;; maxnum)))))
1790 ;; (if (re-search-forward (concat "\n\n\\\(\\[" 1879;; (setq prompt
1791 ;; (regexp-quote todos-done-string) 1880;; (when (or (< candidate 1) (> candidate maxnum))
1792 ;; "\\)") nil t) 1881;; (format "Priority must be an integer between 1 and %d.\n" maxnum)))
1793 ;; (match-beginning 1) 1882;; (unless prompt (setq priority candidate)))
1794 ;; (point-max)))) 1883;; (goto-char (point-min))
1795 ;; (bottom (count-lines (point-min) end))) 1884;; (unless (= priority 1) (todos-forward-item (1- priority)))))
1796 ;; (while (> (- bottom top) todos-insert-threshold) 1885;; (todos-insert-with-overlays item))
1797 ;; (let* ((current (/ (+ top bottom) 2)) 1886
1798 ;; (answer (if (< current bottom) 1887(defun todos-set-item-priority (item cat)
1799 ;; (todos-more-important-p current) nil))) 1888 "Set the priority of unfinished item ITEM in category CAT."
1800 ;; (if answer 1889 (todos-category-number cat)
1801 ;; (setq bottom current) 1890 (todos-category-select)
1802 ;; (setq top (1+ current))))) 1891 (let* ((not-done (car (todos-item-counts cat)))
1803 ;; (setq top (/ (+ top bottom) 2)) 1892 (maxnum (1+ not-done))
1804 ;; (goto-char (point-min)) 1893 priority candidate prompt)
1805 ;; (forward-line (1- top))) 1894 (unless (zerop not-done)
1806 (unless (or (eq (point-min) (point-max)) ; no unfinished items 1895 (while (null priority)
1807 (when (re-search-forward (concat "^\\[" 1896 (setq candidate
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 1897 (string-to-number (read-from-minibuffer
1826 (concat prompt 1898 (concat prompt
1827 (format "Set item priority (1-%d): " 1899 (format "Set item priority (1-%d): "
1828 num-items)))))) 1900 maxnum)))))
1901 (setq prompt
1902 (when (or (< candidate 1) (> candidate maxnum))
1903 (format "Priority must be an integer between 1 and %d.\n" maxnum)))
1904 (unless prompt (setq priority candidate)))
1829 (goto-char (point-min)) 1905 (goto-char (point-min))
1830 (todos-forward-item (1- priority)))) 1906 (unless (= priority 1) (todos-forward-item (1- priority))))))
1831 (todos-insert-with-overlays new-item))
1832 1907
1833(defun todos-jump-to-category-noninteractively (cat) 1908(defun todos-jump-to-category-noninteractively (cat)
1909 ""
1834 (let ((bufname (buffer-name))) 1910 (let ((bufname (buffer-name)))
1835 (cond ((string= bufname todos-categories-buffer) 1911 (cond ((string= bufname todos-categories-buffer)
1836 (switch-to-buffer (file-name-nondirectory todos-file-do))) 1912 (switch-to-buffer (file-name-nondirectory todos-file-do)))
1837 ((string= bufname todos-archived-categories-buffer) 1913 ((string= bufname todos-archived-categories-buffer)
1838 ;; Is pop-to-buffer better for this case? 1914 ;; FIXME: is pop-to-buffer better for this case?
1839 (switch-to-buffer (file-name-nondirectory todos-archive-file)))) 1915 (switch-to-buffer (file-name-nondirectory todos-archive-file))))
1840 (kill-buffer bufname)) 1916 (kill-buffer bufname))
1841 (widen) 1917 (widen)
1842 (goto-char (point-min)) 1918 (goto-char (point-min))
1843 (setq todos-category-number (- (length todos-categories) 1919 (todos-category-number cat)
1844 (length (member cat todos-categories))))
1845 (todos-category-select)) 1920 (todos-category-select))
1846 1921
1847(defun todos-insert-with-overlays (item) 1922(defun todos-insert-with-overlays (item)
1848 "" ;FIXME add docstring 1923 ""
1849 ;; FIXME: breaks without narrowing, e.g. todos-item-done 1924 (todos-item-start)
1850 ;; (unless (and (bolp) (eolp)) (goto-char (todos-item-start)))
1851 (insert item "\n") 1925 (insert item "\n")
1852 (todos-backward-item) 1926 (todos-backward-item)
1853 (if todos-number-prefix 1927 ;; (if todos-number-prefix
1854 (todos-update-numbered-prefix) 1928 ;; (todos-update-numbered-prefix)
1855 (todos-prefix-overlays))) 1929 (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
1868(defun todos-line-string ()
1869 "Return current line in buffer as a string."
1870 (buffer-substring (line-beginning-position) (line-end-position)))
1871 1930
1872(defun todos-item-string-start () 1931(defun todos-item-string-start ()
1873 "Return the start of this TODO list entry as a string." 1932 "Return the start of this TODO list entry as a string."
@@ -1877,33 +1936,40 @@ Number of entries for each category is given by `todos-print-priorities'."
1877 (setq item (concat (substring item 0 56) "..."))) 1936 (setq item (concat (substring item 0 56) "...")))
1878 item)) 1937 item))
1879 1938
1939(defvar todos-item-start (concat "^\\(\\[\\(" (regexp-quote todos-done-string)
1940 "\\)?\\)?" todos-date-pattern)
1941 "String identifying start of a Todos item.")
1942
1880(defun todos-item-start () 1943(defun todos-item-start ()
1881 "Move to start of current TODO list item and return its position." 1944 "Move to start of current TODO list item and return its position."
1882 (unless (or (looking-at "^$") ; last item or between done and not done 1945 (unless (or (looking-at "^$") ; last item or between done and not done
1883 (looking-at (regexp-quote todos-category-beg))) ; for todos-count-items 1946 (looking-at (regexp-quote todos-category-beg))) ; for todos-count-items
1884 (goto-char (line-beginning-position)) 1947 (goto-char (line-beginning-position))
1885 (while (not (looking-at (concat "^\\(\\[\\(" (regexp-quote todos-done-string) 1948 (while (not (looking-at todos-item-start))
1886 "\\)?\\)?" todos-date-pattern)))
1887 (forward-line -1))) 1949 (forward-line -1)))
1888 (point)) 1950 (point))
1889 1951
1890(defun todos-item-end () 1952(defun todos-item-end ()
1891 "Move to end of current TODO list item and return its position." 1953 "Move to end of current TODO list item and return its position."
1892 (unless (looking-at "^$") ; last item or between done and not done 1954 (unless (looking-at "^$") ; FIXME:
1893 (todos-forward-item) 1955 (let ((done (todos-done-item-p)))
1894 (backward-char)) 1956 (todos-forward-item)
1957 ;; adjust if item is last unfinished one before displayed done items
1958 (when (and (not done) (todos-done-item-p))
1959 (forward-line -1))
1960 (backward-char)))
1895 (point)) 1961 (point))
1896 1962
1897(defun todos-remove-item () 1963(defun todos-remove-item ()
1898 "Delete the current entry from the TODO list." 1964 "Delete the current entry from the TODO list."
1899 (let* ((end (progn (todos-forward-item) (point))) 1965 (let* ((beg (todos-item-start))
1900 (beg (progn (todos-backward-item) (point))) 1966 (end (progn (todos-item-end) (1+ (point))))
1901 (ov-start (car (overlays-in beg beg)))) 1967 (ov-start (car (overlays-in beg beg))))
1902 (when ov-start 1968 (when ov-start
1903 ;; don't cache numbers, since they can be popped out of order in 1969 ;; ;; don't cache numbers, since they can be popped out of order in
1904 ;; todos-prefix-overlays 1970 ;; ;; todos-prefix-overlays
1905 (unless todos-number-prefix 1971 ;; (unless todos-number-prefix
1906 (push ov-start todos-item-start-overlays)) 1972 ;; (push ov-start todos-item-start-overlays))
1907 (delete-overlay ov-start)) 1973 (delete-overlay ov-start))
1908 (delete-region beg end))) 1974 (delete-region beg end)))
1909 1975
@@ -1912,38 +1978,16 @@ Number of entries for each category is given by `todos-print-priorities'."
1912 (buffer-substring (todos-item-start) (todos-item-end))) 1978 (buffer-substring (todos-item-start) (todos-item-end)))
1913 1979
1914(defun todos-done-item-p () 1980(defun todos-done-item-p ()
1915 "" ;FIXME 1981 ""
1916 (save-excursion 1982 (save-excursion
1917 (todos-item-start) 1983 (todos-item-start)
1918 (looking-at (concat "^\\[" (regexp-quote todos-done-string))))) 1984 (looking-at (concat "^\\[" (regexp-quote todos-done-string)))))
1919 1985
1920(defun todos-count-items-in-category () 1986
1921 "Return number of not done and done items in current category." 1987(defvar todos-categories-alist nil
1922 (save-excursion 1988 "Variable for storing the result of todos-make-categories-alist.")
1923 (let ((not-done 0) 1989(defun todos-make-categories-alist ()
1924 (done 0) 1990 "Return an alist of categories and some of their properties.
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 1991The properties are at least the numbers of the unfinished and
1948done items in the category." 1992done items in the category."
1949 (let (todos-categories-alist) 1993 (let (todos-categories-alist)
@@ -1969,19 +2013,34 @@ done items in the category."
1969 (forward-line))))) 2013 (forward-line)))))
1970 todos-categories-alist)) 2014 todos-categories-alist))
1971 2015
1972(defun todos-count-all-items () 2016(defun todos-item-counts (cat &optional how)
1973 "" 2017 ""
1974 (let ((unfinished 0) 2018 (let* ((counts (cadr (assoc cat todos-categories-alist)))
1975 (done 0)) 2019 (not-done (car counts))
1976 (dolist (l (todos-categories-alist)) 2020 (done (cdr counts)))
1977 (setq unfinished (+ unfinished (car (cadr l))) 2021 (cond ((eq how 'insert)
1978 done (+ done (cdr (cadr l))))) 2022 (setcar counts (1+ not-done)))
1979 (cons unfinished done))) 2023 ((eq how 'delete)
1980 2024 (if (todos-done-item-p) ;FIXME: fails if last done item was deleted
1981(defun todos-longest-category-name-length () 2025 (setcdr counts (1- done))
2026 (setcar counts (1- not-done))))
2027 ;; ((eq how 'move-failed)
2028 ;; (setcar counts not-done))
2029 ((eq how 'done)
2030 (setcar counts (1- not-done))
2031 (setcdr counts (1+ done)))
2032 ((eq how 'undo)
2033 (setcar counts (1+ not-done))
2034 (setcdr counts (1- done)))
2035 ((eq how 'archive)
2036 (setcdr counts 0))
2037 (t
2038 (cons not-done done)))))
2039
2040(defun todos-longest-category-name-length (categories)
1982 "" 2041 ""
1983 (let ((longest 0)) 2042 (let ((longest 0))
1984 (dolist (c (todos-categories-alist) longest) 2043 (dolist (c categories longest)
1985 (setq longest (max longest (length (car c))))))) 2044 (setq longest (max longest (length (car c)))))))
1986 2045
1987(defun todos-string-count-lines (string) 2046(defun todos-string-count-lines (string)
@@ -1992,8 +2051,8 @@ done items in the category."
1992 "Return non-nil if STRING spans several lines." 2051 "Return non-nil if STRING spans several lines."
1993 (> (todos-string-count-lines string) 1)) 2052 (> (todos-string-count-lines string) 1))
1994 2053
1995(defun todos-completing-read () 2054(defun todos-read-category ()
1996 "Return a category name, with completion, for use in Todo mode." 2055 "Return an existing category name, with tab completion."
1997 ;; allow SPC to insert spaces, for adding new category names with 2056 ;; allow SPC to insert spaces, for adding new category names with
1998 ;; todos-move-item 2057 ;; todos-move-item
1999 (let ((map minibuffer-local-completion-map)) 2058 (let ((map minibuffer-local-completion-map))
@@ -2002,7 +2061,7 @@ done items in the category."
2002 ;; non-nil, which makes completing-read alter todos-categories 2061 ;; non-nil, which makes completing-read alter todos-categories
2003 (let* ((categories (copy-sequence todos-categories)) 2062 (let* ((categories (copy-sequence todos-categories))
2004 (history (cons 'todos-categories (1+ todos-category-number))) 2063 (history (cons 'todos-categories (1+ todos-category-number)))
2005 (default (nth todos-category-number todos-categories)) 2064 (default (todos-current-category)) ;FIXME: why this default?
2006 (completion-ignore-case todos-completion-ignore-case) 2065 (completion-ignore-case todos-completion-ignore-case)
2007 (category (completing-read 2066 (category (completing-read
2008 (concat "Category [" default "]: ") 2067 (concat "Category [" default "]: ")
@@ -2011,6 +2070,62 @@ done items in the category."
2011 (setq todos-categories categories) 2070 (setq todos-categories categories)
2012 category))) 2071 category)))
2013 2072
2073(defun todos-check-category-name (cat)
2074 "Reject names for category CAT that could yield bugs or confusion."
2075 (let (prompt)
2076 (while (and (cond ((string= "" cat)
2077 (setq prompt "Enter a non-empty category name: "))
2078 ((string-match "\\`\\s-+\\'" cat)
2079 (setq prompt "Enter a category name that is not only white space: "))
2080 ((member cat todos-categories)
2081 (setq prompt "Enter a non-existing category name: ")))
2082 (setq cat (read-from-minibuffer prompt)))))
2083 cat)
2084
2085;; adapted from calendar-read-date
2086(defun todos-read-date ()
2087 "Prompt for Gregorian date and return it in the current format."
2088 (let* ((year (calendar-read
2089 "Year (>0): "
2090 (lambda (x) (> x 0))
2091 (number-to-string (calendar-extract-year
2092 (calendar-current-date)))))
2093 (month-array calendar-month-name-array)
2094 (completion-ignore-case t)
2095 (month (cdr (assoc-string
2096 (completing-read
2097 "Month name (RET for current month): "
2098 (mapcar 'list (append month-array nil))
2099 nil t nil nil
2100 (calendar-month-name (calendar-extract-month
2101 (calendar-current-date))))
2102 (calendar-make-alist month-array 1) t)))
2103 (last (calendar-last-day-of-month month year))
2104 day)
2105 (while (or (not (numberp day)) (< day 0) (< last day))
2106 (setq day (read-from-minibuffer
2107 (format "Day (1-%d): " last) nil nil t nil
2108 (number-to-string (calendar-extract-day (calendar-current-date))))))
2109 (calendar-date-string (list month day year) t t)))
2110
2111(defun todos-read-dayname ()
2112 ""
2113 (let ((completion-ignore-case t))
2114 (completing-read "Enter a day name: "
2115 (append calendar-day-name-array nil)
2116 nil t)))
2117
2118(defun todos-read-time ()
2119 ""
2120 (let (valid answer)
2121 (while (not valid)
2122 (setq answer (read-from-minibuffer
2123 "Enter a clock time: "))
2124 (when (or (string= "" answer)
2125 (string-match diary-time-regexp answer))
2126 (setq valid t)))
2127 answer))
2128
2014(defun todos-categories-list (buf) 2129(defun todos-categories-list (buf)
2015 "Return a list of the Todo mode categories in buffer BUF." 2130 "Return a list of the Todo mode categories in buffer BUF."
2016 (let (categories) 2131 (let (categories)
@@ -2024,136 +2139,35 @@ done items in the category."
2024 (push (match-string-no-properties 1) categories))))) 2139 (push (match-string-no-properties 1) categories)))))
2025 categories)) 2140 categories))
2026 2141
2027;; --------------------------------------------------------------------------- 2142(defun todos-padded-string (str)
2028;;; Mode setup 2143 ""
2029 2144 (let* ((len (todos-longest-category-name-length todos-categories-alist))
2030(easy-menu-define todos-menu todos-mode-map "Todo Menu" 2145 (strlen (length str))
2031 '("Todo" 2146 (strlen-odd (eq (logand strlen 1) 1)) ; oddp from cl.el
2032 ["Next category" todos-forward-category t] 2147 (padding (/ (- len strlen) 2)))
2033 ["Previous category" todos-backward-category t] 2148 (concat (make-string padding 32) str
2034 ["Jump to category" todos-jump-to-category t] 2149 (make-string (if strlen-odd (1+ padding) padding) 32))))
2035 ["Show top priority items" todos-top-priorities t] 2150
2036 ["Print categories" todos-print t] 2151(defun todos-insert-category-name (cat &optional nonum)
2037 "---" 2152 ""
2038 ["Edit item" todos-edit-item t] 2153 (let* ((buf (get-buffer (file-name-nondirectory todos-file-do)))
2039 ["File item" todos-file-item t] 2154 (cat-alist todos-categories-alist)
2040 ["Insert new item" todos-insert-item t] 2155 (counts (todos-item-counts cat)))
2041 ["Insert item here" todos-insert-item-here t] 2156 ;; num is declared in caller
2042 ["Kill item" todos-delete-item t] 2157 (setq num (1+ num))
2043 "---" 2158 (if nonum
2044 ["Lower item priority" todos-lower-item t] 2159 (insert (make-string 4 32))
2045 ["Raise item priority" todos-raise-item t] 2160 (insert " " (format "%2d" num) " "))
2046 "---" 2161 (insert-button (todos-padded-string cat)
2047 ["Next item" todos-forward-item t] 2162 'face 'todos-button
2048 ["Previous item" todos-backward-item t] 2163 'action
2049 "---" 2164 `(lambda (button)
2050 ["Save" todos-save t] 2165 (todos-jump-to-category-noninteractively ,cat)))
2051 ["Save Top Priorities" todos-save-top-priorities t] 2166 (insert (make-string 8 32)
2052 "---" 2167 (format "%2d" (car counts))
2053 ["Quit" todos-quit t] 2168 (make-string 5 32)
2054 )) 2169 (format "%2d" (cdr counts)))
2055 2170 (newline)))
2056;; As calendar reads .todos-do before todos-mode is loaded.
2057;;;###autoload
2058(defun todos-mode ()
2059 "Major mode for editing TODO lists.
2060
2061\\{todos-mode-map}"
2062 (interactive)
2063 (kill-all-local-variables)
2064 (setq major-mode 'todos-mode)
2065 (setq mode-name "TODOS")
2066 (use-local-map todos-mode-map)
2067 (easy-menu-add todos-menu)
2068 (when todos-wrap-lines (funcall todos-line-wrapping-function))
2069 (make-local-variable 'font-lock-defaults)
2070 (setq font-lock-defaults '(todos-font-lock-keywords t))
2071 (make-local-variable 'hl-line-range-function)
2072 (setq hl-line-range-function
2073 (lambda() (when (todos-item-end)
2074 (cons (todos-item-start) (todos-item-end)))))
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)
2079 (run-mode-hooks 'todos-mode-hook))
2080
2081(defun todos-archive-mode ()
2082 "Major mode for archived Todos categories.
2083
2084\\{todos-mode-map}"
2085 (interactive)
2086 (kill-all-local-variables)
2087 (setq major-mode 'todos-archive-mode)
2088 (setq mode-name "TODOS Arch")
2089 (use-local-map todos-archive-mode-map)
2090 ;; (easy-menu-add todos-menu)
2091 (when todos-wrap-lines (funcall todos-line-wrapping-function))
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))
2101
2102(defun todos-edit-mode ()
2103 "Major mode for editing items in the TODO list.
2104
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))
2130
2131;;;###autoload
2132(defun todos-show ()
2133 "Show TODO list."
2134 (interactive)
2135 ;; Make this a no-op if called interactively in narrowed Todos mode, since
2136 ;; it is in that case redundant, but in particular to work around the bug of
2137 ;; item prefix reduplication with show-paren-mode enabled.
2138 (unless (and (called-interactively-p)
2139 (eq major-mode 'todos-mode)
2140 (< (- ( point-max) (point-min)) (buffer-size)))
2141 ;; Call todos-initial-setup only if there is neither a Todo file nor
2142 ;; a corresponding unsaved buffer.
2143 (if (or (file-exists-p todos-file-do)
2144 (let* ((buf (get-buffer (file-name-nondirectory todos-file-do)))
2145 (bufname (buffer-file-name buf)))
2146 (equal (expand-file-name todos-file-do) bufname)))
2147 (find-file todos-file-do)
2148 (todos-initial-setup))
2149 (unless (eq major-mode 'todos-mode) (todos-mode))
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 )))
2157 2171
2158(defun todos-initial-setup () 2172(defun todos-initial-setup ()
2159 "Set up things to work properly in TODO mode." 2173 "Set up things to work properly in TODO mode."
@@ -2164,5 +2178,4 @@ done items in the category."
2164 2178
2165(provide 'todos) 2179(provide 'todos)
2166 2180
2167;; arch-tag: 6fd91be5-776e-4464-a109-da4ea0e4e497
2168;;; todos.el ends here 2181;;; todos.el ends here