aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Berman2011-12-02 14:27:28 +0100
committerStephen Berman2011-12-02 14:27:28 +0100
commit58c7641d1b069be3ead47dbe4a44c8360ef8d1f2 (patch)
treebd64ad740779b725893bbb0ff1cc0a4592c8eb1c
parentd04d6b955b4caaa9817ec053eddb59e923a68cf8 (diff)
downloademacs-58c7641d1b069be3ead47dbe4a44c8360ef8d1f2.tar.gz
emacs-58c7641d1b069be3ead47dbe4a44c8360ef8d1f2.zip
* calendar/todos.el: Remove old commentary from todo-mode.el; add
and revise further doc strings and comments; require cl.el at compile time for remove-duplicates; use function powerset from http://rosettacode.org/wiki/Power_set#Common_Lisp (GFDL); further code rearrangement. Add adapted version of diary-goto-entry as comment. (todos-file-top, todos-archived-categories-buffer) (todos-save-top-priorities-too, todos-toggle-item-diary-inclusion) (todos-save-top-priorities, todos-reset-separator) (todos-switch-todos-file, todos-item-string-start, todos-counts) (todos-string-count-lines, todos-string-multiline-p) (todos-display-categories-alphabetically): Remove. (todos-insert-item-ask-date, todos-insert-item-ask-date-time) (todos-insert-item-ask-date-time-for-diary) (todos-insert-item-ask-date-time-for-diary-here) (todos-insert-item-ask-date-time-here) (todos-insert-item-ask-date-maybe-notime) (todos-insert-item-ask-date-maybe-notime-for-diary) (todos-insert-item-ask-date-maybe-notime-for-diary-here) (todos-insert-item-ask-date-maybe-notime-here) (todos-insert-item-ask-date-for-diary) (todos-insert-item-ask-date-for-diary-here) (todos-insert-item-ask-date-here, todos-insert-item-ask-dayname) (todos-insert-item-ask-dayname-time) (todos-insert-item-ask-dayname-time-for-diary) (todos-insert-item-ask-dayname-time-for-diary-here) (todos-insert-item-ask-dayname-time-here) (todos-insert-item-ask-dayname-maybe-notime) (todos-insert-item-ask-dayname-maybe-notime-for-diary) (todos-insert-item-ask-dayname-maybe-notime-for-diary-here) (todos-insert-item-ask-dayname-maybe-notime-here) (todos-insert-item-ask-dayname-for-diary) (todos-insert-item-ask-dayname-for-diary-here) (todos-insert-item-ask-dayname-here, todos-insert-item-ask-time) (todos-insert-item-ask-time-for-diary) (todos-insert-item-ask-time-for-diary-here) (todos-insert-item-ask-time-here) (todos-insert-item-maybe-notime) (todos-insert-item-maybe-notime-for-diary) (todos-insert-item-maybe-notime-for-diary-here) (todos-insert-item-maybe-notime-here) (todos-insert-item-for-diary, todos-insert-item-for-diary-here) (todos-insert-item-here): Remove; all of these are now generated on loading (some with the same name, most with other names.) (todos-item-counts, todos-display-categories-alphabetically) (todos-display-categories-sorted-by-todo) (todos-display-categories-sorted-by-diary) (todos-display-categories-sorted-by-done) (todos-display-categories-sorted-by-archived): Comment out. (todos-comment-string, todos-mode-line-function) (todos-filter-function, todos-priorities-rules) (todos-visit-files-commands, todos-categories-totals-label) (todos-use-only-highlighted-region, todos-diary-nonmarking): New defcustoms. (todos-mark, todos-comment): New faces. (todos-comment-face): Corresponding new variable. (todos-categories-full, todos-global-current-todos-file) (todos-first-visit, todos-insertion-commands-args-genlist) (todos-insertion-commands-args, todos-insertion-commands-names) (todos-insertion-commands, todos-insertion-commands-arg-key-list) (todos-top-priorities-widgets, todos-date-from-calendar) (todos-item-mark, todos-categories-with-marks): New variables. (todos-mode-line-control, todos-reset-global-current-todos-file) (todos-gen-arglists, todos-insertion-command-name) (todos-insertion-key-bindings, todos-unload-hook) (todos-filter-items, todos-set-date-from-calendar) (todos-comment-string-matcher, todos-after-find-file) (todos-reset-nondiary-marker, todos-reset-done-string) (todos-reset-comment-string, todos-show-current-file) (todos-item-marked-p, todos-total-item-counts): New functions. (todos-define-insertion-command): New macro. (todos-toggle-mark-item, todos-mark-category) (todos-unmark-category, todos-set-top-priorities) (todos-merged-diary-items, todos-regexp-items) (todos-merged-regexp-items, todos-custom-items) (todos-merged-custom-items, todos-comment-done-item) (todos-archive-category-done-items, todos-unarchive-items) (todos-print-to-file): New commands. (todos-done-separator): Change :set function. (todos-done-string): Uncomment :initialize and :set functions. (todos-files): Use file-truename. (todos-show-current-file): Rename from todos-auto-switch-todos-file and change :set function accordingly. (todos-font-lock-keywords): Use todos-comment-string-matcher; change names of other matcher functions to new *-matcher. (todos-category-number): Change initial value. (todos-insertion-map): Use todos-insertion-key-bindings to generate key definitions. (todos-mode-map): Don't suppress digit keys, so they can supply prefix arguments; add new and change some existing bindings. (todos-archive-mode-map): Change a key binding. (todos-categories-mode-map): Comment out a key binding. (todos-filter-items-mode-map): Rename from todos-top-priorities-mode-map. (todos-mode): Make todos-current-todos-file, todos-categories-full, todos-categories, todos-first-visit, todos-category-number, todos-show-done-only, todos-categories-with-marks local variables and set them; add todos-show-current-file to pre-command-hook, todos-after-find-file to post-command-hook and todos-reset-global-current-todos-file to kill-buffer-hook. (todos-archive-mode): Make todos-current-todos-file, todos-categories and todos-category-number local variables and set them; add todos-after-find-file to post-command-hook. (todos-raw-mode): New derived major mode. (todos-categories-mode): Don't set font-lock-defaults and buffer-read-only; make todos-current-todos-file and todos-categories local variables and set them. (todos-filter-items-mode): Rename from todos-top-priorities-mode-map. (todos-quit): Don't reset todos-categories on quitting todos-categories-mode; handle quitting todos-filter-items-mode. (todos-show): Simplify; when visiting an archive file switch to corresponding Todos file; use todos-first-visit. (todos-view-archived-items): Simplify; call todos-category-number. (todos-show-archive): Rename from todos-switch-to-archive and adjust callers; simplify. (todos-toggle-display-date-time): Add optional argument to toggle display in entire file. (todos-top-priorities): Use todos-filter-items, which now contains the previous core of this command. (todos-merged-top-priorities, todos-diary-items): Use todos-filter-items. (todos-forward-category): Add optional argument to go to the previous category. (todos-backward-category): Use todos-forward-category. (todos-jump-to-category): Refine implementation. (todos-forward-item, todos-backward-item): Fix movement from todo to done item and vice versa. (todos-add-file): Remove argument and simplify. (todos-rename-category): Use todos-current-todos-file and todos-mode-line-function; set todos-categories with todos-set-categories. (todos-delete-category): Ask what to do if category has archived items. (todos-raise-category): Ensure modified todos-categories is added to file's categories sexp. (todos-move-category): Improve implementation, especially handling of archived categories. (todos-merge-category): Tweak; set item counts. (todos-insert-item): Improve handling of various argument values; add new argument values to control marking of diary items and to use region for item body. (todos-insert-item-from-calendar): Use todos-global-current-todos-file. (todos-delete-item, todos-edit-item-header): Handle marked items. (todos-edit-item): Incorporate functionality of removed todos-string-multiline-p. (todos-edit-multiline): Use set-window-buffer instead of switch-to-buffer. (todos-edit-quit): Don't save on quitting; use todos-show instead of todos-category-select. (todos-raise-item-priority): Add argument to lower priority; improve handling of top priority items in todos-filter-items-mode; restore marks. (todos-lower-item-priority): Use todos-raise-item-priority. (todos-set-item-priority): Increment maximum number if item is new. (todos-move-item): Handle marked items; delay changing category moved from till after movement to avoid restoring if user cancels before insertion. (todos-item-done): Add optional argument to insert comment; fix item counts and update sexp. (todos-item-undo): Fix item counts and update. (todos-archive-done-item-or-items): Rename from todos-archive-done-items; add optional argument to archive all items in category; handle marked items. (todos-unarchive-category): Use todos-unarchive-items. (todos-toggle-diary-inclusion): Incorporate functionality of removed todos-toggle-item-diary-inclusion; handle marked items. (todos-print): Add optional argument to print to file. (todos-done-string-start): Don't use todos-nondiary-start. (todos-date-string-matcher, todos-time-string-matcher) (todos-done-string-matcher, todos-category-string-matcher): Rename from *-match and adjust callers. (todos-wrap-and-indent): Use set instead of setq for local variables. (todos-prefix-overlays): Improve overlay handling. (todos-reset-categories): Fix and complete implementation. (todos-toggle-show-current-file): Rename from todos-toggle-switch-todos-file-noninteractively. (todos-category-select): Use todos-mode-line-function. (todos-item-start): Comment out code used by removed function. (todos-remove-item): Handle presence of both prefix/number and mark overlays. (todos-get-count): Simplify. (todos-set-count): Change argument list and adjust callers; simplify. (todos-set-categories): Handle new archive files; use todos-categories-full and todos-ignore-archived-categories. (todos-truncate-categories-list): Use todos-categories-full. (todos-update-categories-sexp): Use kill-region instead of kill-line; use todos-categories-full. (todos-read-file-name): Add argument to require existing file and adjust callers; use file-truename. (todos-read-category): Remove argument to require existing category and delegate it to completing-read in function body. (todos-validate-category-name): Make empty string prompt only for initial category name. (todos-read-date): Use = instead of eq for testing if month = 13, and if it is, set monthname to *. (todos-display-categories): Use todos-global-current-todos-file; use set-window-buffer instead of switch-to-buffer; add a line showing item count totals. (todos-padded-string): Use the longest of category name or label. (todos-descending-counts): Rename from todos-descending-counts-store and adjust users. (todos-insert-category-line): Adjust format; use mapconcat; kill buffer after jumping to category.
-rw-r--r--lisp/ChangeLog216
-rw-r--r--lisp/calendar/todos.el4569
2 files changed, 2804 insertions, 1981 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index db18c225cde..0ee809b5d2f 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,208 @@
12012-09-19 Stephen Berman <stephen.berman@gmx.net>
2
3 * calendar/todos.el: Remove old commentary from todo-mode.el; add
4 and revise further doc strings and comments; require cl.el at
5 compile time for remove-duplicates; use function powerset from
6 http://rosettacode.org/wiki/Power_set#Common_Lisp (GFDL); further
7 code rearrangement. Add adapted version of diary-goto-entry as comment.
8 (todos-file-top, todos-archived-categories-buffer)
9 (todos-save-top-priorities-too, todos-toggle-item-diary-inclusion)
10 (todos-save-top-priorities, todos-reset-separator)
11 (todos-switch-todos-file, todos-item-string-start, todos-counts)
12 (todos-string-count-lines, todos-string-multiline-p)
13 (todos-display-categories-alphabetically): Remove.
14 (todos-insert-item-ask-date, todos-insert-item-ask-date-time)
15 (todos-insert-item-ask-date-time-for-diary)
16 (todos-insert-item-ask-date-time-for-diary-here)
17 (todos-insert-item-ask-date-time-here)
18 (todos-insert-item-ask-date-maybe-notime)
19 (todos-insert-item-ask-date-maybe-notime-for-diary)
20 (todos-insert-item-ask-date-maybe-notime-for-diary-here)
21 (todos-insert-item-ask-date-maybe-notime-here)
22 (todos-insert-item-ask-date-for-diary)
23 (todos-insert-item-ask-date-for-diary-here)
24 (todos-insert-item-ask-date-here, todos-insert-item-ask-dayname)
25 (todos-insert-item-ask-dayname-time)
26 (todos-insert-item-ask-dayname-time-for-diary)
27 (todos-insert-item-ask-dayname-time-for-diary-here)
28 (todos-insert-item-ask-dayname-time-here)
29 (todos-insert-item-ask-dayname-maybe-notime)
30 (todos-insert-item-ask-dayname-maybe-notime-for-diary)
31 (todos-insert-item-ask-dayname-maybe-notime-for-diary-here)
32 (todos-insert-item-ask-dayname-maybe-notime-here)
33 (todos-insert-item-ask-dayname-for-diary)
34 (todos-insert-item-ask-dayname-for-diary-here)
35 (todos-insert-item-ask-dayname-here, todos-insert-item-ask-time)
36 (todos-insert-item-ask-time-for-diary)
37 (todos-insert-item-ask-time-for-diary-here)
38 (todos-insert-item-ask-time-here)
39 (todos-insert-item-maybe-notime)
40 (todos-insert-item-maybe-notime-for-diary)
41 (todos-insert-item-maybe-notime-for-diary-here)
42 (todos-insert-item-maybe-notime-here)
43 (todos-insert-item-for-diary, todos-insert-item-for-diary-here)
44 (todos-insert-item-here): Remove; all of these are now generated
45 on loading (some with the same name, most with other names.)
46 (todos-item-counts, todos-display-categories-alphabetically)
47 (todos-display-categories-sorted-by-todo)
48 (todos-display-categories-sorted-by-diary)
49 (todos-display-categories-sorted-by-done)
50 (todos-display-categories-sorted-by-archived): Comment out.
51 (todos-comment-string, todos-mode-line-function)
52 (todos-filter-function, todos-priorities-rules)
53 (todos-visit-files-commands, todos-categories-totals-label)
54 (todos-use-only-highlighted-region, todos-diary-nonmarking):
55 New defcustoms.
56 (todos-mark, todos-comment): New faces.
57 (todos-comment-face): Corresponding new variable.
58 (todos-categories-full, todos-global-current-todos-file)
59 (todos-first-visit, todos-insertion-commands-args-genlist)
60 (todos-insertion-commands-args, todos-insertion-commands-names)
61 (todos-insertion-commands, todos-insertion-commands-arg-key-list)
62 (todos-top-priorities-widgets, todos-date-from-calendar)
63 (todos-item-mark, todos-categories-with-marks): New variables.
64 (todos-mode-line-control, todos-reset-global-current-todos-file)
65 (todos-gen-arglists, todos-insertion-command-name)
66 (todos-insertion-key-bindings, todos-unload-hook)
67 (todos-filter-items, todos-set-date-from-calendar)
68 (todos-comment-string-matcher, todos-after-find-file)
69 (todos-reset-nondiary-marker, todos-reset-done-string)
70 (todos-reset-comment-string, todos-show-current-file)
71 (todos-item-marked-p, todos-total-item-counts): New functions.
72 (todos-define-insertion-command): New macro.
73 (todos-toggle-mark-item, todos-mark-category)
74 (todos-unmark-category, todos-set-top-priorities)
75 (todos-merged-diary-items, todos-regexp-items)
76 (todos-merged-regexp-items, todos-custom-items)
77 (todos-merged-custom-items, todos-comment-done-item)
78 (todos-archive-category-done-items, todos-unarchive-items)
79 (todos-print-to-file): New commands.
80 (todos-done-separator): Change :set function.
81 (todos-done-string): Uncomment :initialize and :set functions.
82 (todos-files): Use file-truename.
83 (todos-show-current-file): Rename from
84 todos-auto-switch-todos-file and change :set function accordingly.
85 (todos-font-lock-keywords): Use todos-comment-string-matcher;
86 change names of other matcher functions to new *-matcher.
87 (todos-category-number): Change initial value.
88 (todos-insertion-map): Use todos-insertion-key-bindings to
89 generate key definitions.
90 (todos-mode-map): Don't suppress digit keys, so they can supply
91 prefix arguments; add new and change some existing bindings.
92 (todos-archive-mode-map): Change a key binding.
93 (todos-categories-mode-map): Comment out a key binding.
94 (todos-filter-items-mode-map): Rename from
95 todos-top-priorities-mode-map.
96 (todos-mode): Make todos-current-todos-file,
97 todos-categories-full, todos-categories, todos-first-visit,
98 todos-category-number, todos-show-done-only,
99 todos-categories-with-marks local variables and set them; add
100 todos-show-current-file to pre-command-hook, todos-after-find-file
101 to post-command-hook and todos-reset-global-current-todos-file to
102 kill-buffer-hook.
103 (todos-archive-mode): Make todos-current-todos-file,
104 todos-categories and todos-category-number local variables and set
105 them; add todos-after-find-file to post-command-hook.
106 (todos-raw-mode): New derived major mode.
107 (todos-categories-mode): Don't set font-lock-defaults and
108 buffer-read-only; make todos-current-todos-file and
109 todos-categories local variables and set them.
110 (todos-filter-items-mode): Rename from todos-top-priorities-mode-map.
111 (todos-quit): Don't reset todos-categories on quitting
112 todos-categories-mode; handle quitting todos-filter-items-mode.
113 (todos-show): Simplify; when visiting an archive file switch to
114 corresponding Todos file; use todos-first-visit.
115 (todos-view-archived-items): Simplify; call todos-category-number.
116 (todos-show-archive): Rename from todos-switch-to-archive and
117 adjust callers; simplify.
118 (todos-toggle-display-date-time): Add optional argument to toggle
119 display in entire file.
120 (todos-top-priorities): Use todos-filter-items, which now contains
121 the previous core of this command.
122 (todos-merged-top-priorities, todos-diary-items):
123 Use todos-filter-items.
124 (todos-forward-category): Add optional argument to go to the
125 previous category.
126 (todos-backward-category): Use todos-forward-category.
127 (todos-jump-to-category): Refine implementation.
128 (todos-forward-item, todos-backward-item): Fix movement from todo
129 to done item and vice versa.
130 (todos-add-file): Remove argument and simplify.
131 (todos-rename-category): Use todos-current-todos-file and
132 todos-mode-line-function; set todos-categories with
133 todos-set-categories.
134 (todos-delete-category): Ask what to do if category has archived items.
135 (todos-raise-category): Ensure modified todos-categories is added
136 to file's categories sexp.
137 (todos-move-category): Improve implementation, especially handling
138 of archived categories.
139 (todos-merge-category): Tweak; set item counts.
140 (todos-insert-item): Improve handling of various argument values;
141 add new argument values to control marking of diary items and to
142 use region for item body.
143 (todos-insert-item-from-calendar): Use todos-global-current-todos-file.
144 (todos-delete-item, todos-edit-item-header): Handle marked items.
145 (todos-edit-item): Incorporate functionality of removed
146 todos-string-multiline-p.
147 (todos-edit-multiline): Use set-window-buffer instead of
148 switch-to-buffer.
149 (todos-edit-quit): Don't save on quitting; use todos-show instead
150 of todos-category-select.
151 (todos-raise-item-priority): Add argument to lower priority;
152 improve handling of top priority items in todos-filter-items-mode;
153 restore marks.
154 (todos-lower-item-priority): Use todos-raise-item-priority.
155 (todos-set-item-priority): Increment maximum number if item is new.
156 (todos-move-item): Handle marked items; delay changing category
157 moved from till after movement to avoid restoring if user cancels
158 before insertion.
159 (todos-item-done): Add optional argument to insert comment; fix
160 item counts and update sexp.
161 (todos-item-undo): Fix item counts and update.
162 (todos-archive-done-item-or-items): Rename from
163 todos-archive-done-items; add optional argument to archive all
164 items in category; handle marked items.
165 (todos-unarchive-category): Use todos-unarchive-items.
166 (todos-toggle-diary-inclusion): Incorporate functionality of
167 removed todos-toggle-item-diary-inclusion; handle marked items.
168 (todos-print): Add optional argument to print to file.
169 (todos-done-string-start): Don't use todos-nondiary-start.
170 (todos-date-string-matcher, todos-time-string-matcher)
171 (todos-done-string-matcher, todos-category-string-matcher): Rename
172 from *-match and adjust callers.
173 (todos-wrap-and-indent): Use set instead of setq for local variables.
174 (todos-prefix-overlays): Improve overlay handling.
175 (todos-reset-categories): Fix and complete implementation.
176 (todos-toggle-show-current-file): Rename from
177 todos-toggle-switch-todos-file-noninteractively.
178 (todos-category-select): Use todos-mode-line-function.
179 (todos-item-start): Comment out code used by removed function.
180 (todos-remove-item): Handle presence of both prefix/number and
181 mark overlays.
182 (todos-get-count): Simplify.
183 (todos-set-count): Change argument list and adjust callers; simplify.
184 (todos-set-categories): Handle new archive files; use
185 todos-categories-full and todos-ignore-archived-categories.
186 (todos-truncate-categories-list): Use todos-categories-full.
187 (todos-update-categories-sexp): Use kill-region instead of
188 kill-line; use todos-categories-full.
189 (todos-read-file-name): Add argument to require existing file and
190 adjust callers; use file-truename.
191 (todos-read-category): Remove argument to require existing
192 category and delegate it to completing-read in function body.
193 (todos-validate-category-name): Make empty string prompt only for
194 initial category name.
195 (todos-read-date): Use = instead of eq for testing if month = 13,
196 and if it is, set monthname to *.
197 (todos-display-categories): Use todos-global-current-todos-file;
198 use set-window-buffer instead of switch-to-buffer; add a line
199 showing item count totals.
200 (todos-padded-string): Use the longest of category name or label.
201 (todos-descending-counts): Rename from
202 todos-descending-counts-store and adjust users.
203 (todos-insert-category-line): Adjust format; use mapconcat; kill
204 buffer after jumping to category.
205
12012-09-18 Stephen Berman <stephen.berman@gmx.net> 2062012-09-18 Stephen Berman <stephen.berman@gmx.net>
2 207
3 * calendar/todos.el Add and revise various doc strings, remove 208 * calendar/todos.el Add and revise various doc strings, remove
@@ -87,7 +292,7 @@
87 Todos files; change display to include category (and file) name as 292 Todos files; change display to include category (and file) name as
88 part of item header; use todos-top-priorities-mode. 293 part of item header; use todos-top-priorities-mode.
89 (todos-diary-items): Reimplement using only todos-top-priorities. 294 (todos-diary-items): Reimplement using only todos-top-priorities.
90 (todos-forward-category, todos-backward-category): Accommodate to 295 (todos-forward-category, todos-backward-category): Adjust to
91 1-based numbering of categories; move point to top of category. 296 1-based numbering of categories; move point to top of category.
92 (todos-jump-to-category): Rewrite, adding optional arguments to 297 (todos-jump-to-category): Rewrite, adding optional arguments to
93 provide a category in non-interactive uses and to prompt for which 298 provide a category in non-interactive uses and to prompt for which
@@ -104,8 +309,8 @@
104 (todos-delete-category): Use todos-get-count and 309 (todos-delete-category): Use todos-get-count and
105 todos-update-categories-sexp, let-bind variable that were 310 todos-update-categories-sexp, let-bind variable that were
106 mistakenly global; use delete-region instead of kill-region; 311 mistakenly global; use delete-region instead of kill-region;
107 accommodate to 1-based numbering of categories; move point to top 312 adjust to 1-based numbering of categories; move point to top of
108 of category. 313 category.
109 (todos-raise-category): Handle item count vectors; use 314 (todos-raise-category): Handle item count vectors; use
110 todos-insert-category-line and todos-update-categories-sexp. 315 todos-insert-category-line and todos-update-categories-sexp.
111 (todos-insert-item): Use nil time-string argument to omit time 316 (todos-insert-item): Use nil time-string argument to omit time
@@ -169,7 +374,7 @@
169 (todos-item-done): Handle diary items; simplify handling of 374 (todos-item-done): Handle diary items; simplify handling of
170 insertion in done items section. 375 insertion in done items section.
171 (todos-item-undo): Handle diary items. 376 (todos-item-undo): Handle diary items.
172 (todos-archive-done-items): Accommodate to new handling of archive 377 (todos-archive-done-items): Adjust to new handling of archive
173 files (in parallel with Todos files); handle diary items; use 378 files (in parallel with Todos files); handle diary items; use
174 todos-done-string-start. 379 todos-done-string-start.
175 (todos-toggle-item-diary-inclusion): Use todos-nondiary-start, 380 (todos-toggle-item-diary-inclusion): Use todos-nondiary-start,
@@ -192,8 +397,7 @@
192 (todos-reset-separator): Handle archive files. 397 (todos-reset-separator): Handle archive files.
193 (todos-category-number): Make category number one more than its 398 (todos-category-number): Make category number one more than its
194 list index. 399 list index.
195 (todos-current-category): Accommodate to 1-based numbering of 400 (todos-current-category): Adjust to 1-based numbering of categories.
196 categories.
197 (todos-category-select): Simplify handling of done items and done 401 (todos-category-select): Simplify handling of done items and done
198 separator string overlay. 402 separator string overlay.
199 (todos-item-start): Use todos-date-string-start and 403 (todos-item-start): Use todos-date-string-start and
diff --git a/lisp/calendar/todos.el b/lisp/calendar/todos.el
index 5d9c9561669..34a1c10df70 100644
--- a/lisp/calendar/todos.el
+++ b/lisp/calendar/todos.el
@@ -1,9 +1,9 @@
1;;; Todos.el --- major mode for displaying and editing Todo lists 1;;; Todos.el --- facilities for making and maintaining Todo lists
2 2
3;; Copyright (C) 1997, 1999, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 3;; Copyright (C) 1997, 1999, 2001-2011 Free Software Foundation, Inc.
4;; 2008, 2009 Free Software Foundation, Inc.
5 4
6;; Author: Oliver Seidel <privat@os10000.net> 5;; Author: Oliver Seidel <privat@os10000.net>
6;; Stephen Berman <stephen.berman@gmx.net>
7;; Maintainer: Stephen Berman <stephen.berman@gmx.net> 7;; Maintainer: Stephen Berman <stephen.berman@gmx.net>
8;; Created: 2 Aug 1997 8;; Created: 2 Aug 1997
9;; Keywords: calendar, todo 9;; Keywords: calendar, todo
@@ -23,253 +23,42 @@
23;; You should have received a copy of the GNU General Public License 23;; You should have received a copy of the GNU General Public License
24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
25 25
26;; ---------------------------------------------------------------------------
27
28;;; Commentary: 26;;; Commentary:
29 27
30;; Mode Description 28;; UI
31;; 29;; - display
32;; TODO is a major mode for EMACS which offers functionality to 30;; - show todos in cat
33;; treat most lines in one buffer as a list of items one has to 31;; - show done in cat
34;; do. There are facilities to add new items, which are 32;; - show catlist
35;; categorised, to edit or even delete items from the buffer. 33;; - show top priorities in all cats
36;; The buffer contents are currently compatible with the diary, 34;; - show archived
37;; so that the list of todos-items will show up in the FANCY diary 35;; - navigation
38;; mode. 36;; -
39;; 37;; - editing
40;; Notice: Besides the major mode, this file also exports the
41;; function `todos-show' which will change to the one specific
42;; TODO file that has been specified in the todos-file-do
43;; variable. If this file does not conform to the TODO mode
44;; conventions, the todos-show function will add the appropriate
45;; header and footer. I don't anticipate this to cause much
46;; grief, but be warned, in case you attempt to read a plain text
47;; file.
48;;
49;; Preface, Quickstart Installation
50;;
51;; To get this to work, make Emacs execute the line
52;;
53;; (autoload 'todos "todos"
54;; "Major mode for editing TODO lists." t)
55;; (autoload 'todos-show "todos"
56;; "Show TODO items." t)
57;; (autoload 'todos-insert-item "todos"
58;; "Add TODO item." t)
59;;
60;; You may now enter new items by typing "M-x todos-insert-item",
61;; or enter your TODO list file by typing "M-x todos-show".
62;;
63;; The TODO list file has a special format and some auxiliary
64;; information, which will be added by the todos-show function if
65;; it attempts to visit an un-initialised file. Hence it is
66;; recommended to use the todos-show function for the first time,
67;; in order to initialise the file, but it is not necessary
68;; afterwards.
69;;
70;; As these commands are quite long to type, I would recommend
71;; the addition of two bindings to your to your global keymap. I
72;; personally have the following in my initialisation file:
73;;
74;; (global-set-key "\C-ct" 'todos-show) ; switch to TODO buffer
75;; (global-set-key "\C-ci" 'todos-insert-item) ; insert new item
76;;
77;; Note, however, that this recommendation has prompted some
78;; criticism, since the keys C-c LETTER are reserved for user
79;; functions. I believe my recommendation is acceptable, since
80;; the Emacs Lisp Manual *Tips* section also details that the
81;; mode itself should not bind any functions to those keys. The
82;; express aim of the above two bindings is to work outside the
83;; mode, which doesn't need the show function and offers a
84;; different binding for the insert function. They serve as
85;; shortcuts and are not even needed (since the TODO mode will be
86;; entered by visiting the TODO file, and later by switching to
87;; its buffer).
88;;
89;; If you are an advanced user of this package, please consult
90;; the whole source code for autoloads, because there are several
91;; extensions that are not explicitly listed in the above quick
92;; installation.
93;;
94;; Pre-Requisites
95;;
96;; This package will require the following packages to be
97;; available on the load-path:
98;;
99;; time-stamp
100;; easymenu
101;;
102;; Operation
103;;
104;; You will have the following facilities available:
105;;
106;; M-x todos-show will enter the todo list screen, here type
107;;
108;; + to go to next category
109;; - to go to previous category
110;; d to file the current entry, including a
111;; comment and timestamp
112;; e to edit the current entry
113;; E to edit a multi-line entry
114;; f to file the current entry, including a
115;; comment and timestamp
116;; i to insert a new entry, with prefix, omit category
117;; I to insert a new entry at current cursor position
118;; j jump to category
119;; k to kill the current entry
120;; l to lower the current entry's priority
121;; n for the next entry
122;; p for the previous entry
123;; P print
124;; q to save the list and exit the buffer
125;; r to raise the current entry's priority
126;; s to save the list
127;; S to save the list of top priorities
128;; t show top priority items for each category
129;;
130;; When you add a new entry, you are asked for the text and then
131;; for the category. I for example have categories for things
132;; that I want to do in the office (like mail my mum), that I
133;; want to do in town (like buy cornflakes) and things I want to
134;; do at home (move my suitcases). The categories can be
135;; selected with the cursor keys and if you type in the name of a
136;; category which didn't exist before, an empty category of the
137;; desired name will be added and filled with the new entry.
138;;
139;; Configuration
140;;
141;; Variable todos-prefix
142;;
143;; I would like to recommend that you use the prefix "*/*" (by
144;; leaving the variable 'todos-prefix' untouched) so that the
145;; diary displays each entry every day.
146;;
147;; To understand what I mean, please read the documentation that
148;; goes with the calendar since that will tell you how you can
149;; set up the fancy diary display and use the #include command to
150;; include your todo list file as part of your diary.
151;;
152;; If you have the diary package set up to usually display more
153;; than one day's entries at once, consider using
154;;
155;; "&%%(equal (calendar-current-date) date)"
156;;
157;; as the value of `todos-prefix'. Please note that this may slow
158;; down the processing of your diary file some.
159;;
160;; Carsten Dominik <dominik@strw.LeidenUniv.nl> suggested that
161;;
162;; "&%%(todos-cp)"
163;;
164;; might be nicer and to that effect a function has been declared
165;; further down in the code. You may wish to auto-load this.
166;;
167;; Carsten also writes that that *changing* the prefix after the
168;; todo list is already established is not as simple as changing
169;; the variable - the todo files have to be changed by hand.
170;;
171;; Variable todos-file-do
172;;
173;; This variable is fairly self-explanatory. You have to store
174;; your TODO list somewhere. This variable tells the package
175;; where to go and find this file.
176;;
177;; Variable todos-file-done
178;;
179;; Even when you're done, you may wish to retain the entries.
180;; Given that they're timestamped and you are offered to add a
181;; comment, this can make a useful diary of past events. It will
182;; even blend in with the EMACS diary package. So anyway, this
183;; variable holds the name of the file for the filed todos-items.
184;;
185;; Variable todos-file-top
186;;
187;; File storing the top priorities of your TODO list when
188;; todos-save-top-priorities is non-nil. Nice to include in your
189;; diary instead of the complete TODO list.
190;;
191;; Variable todos-mode-hook
192;;
193;; Just like other modes, too, this mode offers to call your
194;; functions before it goes about its business. This variable
195;; will be inspected for any functions you may wish to have
196;; called once the other TODO mode preparations have been
197;; completed.
198;;
199;; Variable todos-insert-threshold
200;;
201;; Another nifty feature is the insertion accuracy. If you have
202;; 8 items in your TODO list, then you may get asked 4 questions
203;; by the binary insertion algorithm. However, you may not
204;; really have a need for such accurate priorities amongst your
205;; TODO items. If you now think about the binary insertion
206;; halving the size of the window each time, then the threshold
207;; is the window size at which it will stop. If you set the
208;; threshold to zero, the upper and lower bound will coincide at
209;; the end of the loop and you will insert your item just before
210;; that point. If you set the threshold to, e.g. 8, it will stop
211;; as soon as the window size drops below that amount and will
212;; insert the item in the approximate center of that window. I
213;; got the idea for this feature after reading a very helpful
214;; e-mail reply from Trey Jackson <trey@cs.berkeley.edu> who
215;; corrected some of my awful coding and pointed me towards some
216;; good reading. Thanks Trey!
217;;
218;; Things to do
219;;
220;; These originally were my ideas, but now also include all the
221;; suggestions that I included before forgetting them:
222;;
223;; o Fancy fonts for todo/top-priority buffer
224;; o Remove todos-prefix option in todos-top-priorities
225;; o Rename category
226;; o Move entry from one category to another one
227;; o Entries which both have the generic */* prefix and a
228;; "deadline" entry which are understood by diary, indicating
229;; an event (unless marked by &)
230;; o The optional COUNT variable of todos-forward-item should be
231;; applied to the other functions performing similar tasks
232;; o Modularization could be done for repeated elements of
233;; the code, like the completing-read lines of code.
234;; o license / version function
235;; o export to diary file
236;; o todos-report-bug
237;; o GNATS support
238;; o elide multiline (as in bbdb, or, to a lesser degree, in
239;; outline mode)
240;; o rewrite complete package to store data as Lisp objects
241;; and have display modes for display, for diary export,
242;; etc. (Richard Stallman pointed out this is a bad idea)
243;; o so base todos.el on generic-mode.el instead
244;;
245;; History and Gossip
246;;
247;; Many thanks to all the ones who have contributed to the
248;; evolution of this package! I hope I have listed all of you
249;; somewhere in the documentation or at least in the RCS history!
250;;
251;; Enjoy this package and express your gratitude by sending nice
252;; things to my parents' address!
253;; 38;;
254;; Oliver Seidel 39;; Internals
255;; (Lessingstr. 8, 65760 Eschborn, Federal Republic of Germany) 40;; - cat props: name, number, todos, done, archived
41;; - item props: priority, date-time, status?
42;; - file format
43;; - cat begin
44;; - todo items 0...n
45;; - empty line
46;; - done-separator
47;; - done item 0...n
256 48
257;;; Code: 49;;; Code:
258 50
259;; (require 'time-stamp)
260;; (require 'calendar) ; required by diary-lib
261(require 'diary-lib) 51(require 'diary-lib)
262 52
263;; --------------------------------------------------------------------------- 53;; ---------------------------------------------------------------------------
264;;; Customizable options 54;;; User options
265 55
266(defgroup todos nil 56(defgroup todos nil
267 "Maintain categorized lists of todo items." 57 "Create and maintain categorized lists of todo items."
268 :link '(emacs-commentary-link "todos") 58 :link '(emacs-commentary-link "todos")
269 :version "24.1" 59 :version "24.1"
270 :group 'calendar) 60 :group 'calendar)
271 61
272;; FIXME: need this?
273(defcustom todos-initial-category "Todo" 62(defcustom todos-initial-category "Todo"
274 "Default category name offered on initializing a new Todos file." 63 "Default category name offered on initializing a new Todos file."
275 :type 'string 64 :type 'string
@@ -288,28 +77,36 @@
288 :group 'todos) 77 :group 'todos)
289 78
290(defcustom todos-number-prefix t 79(defcustom todos-number-prefix t
291 "Non-nil to show item prefixes as consecutively increasing integers. 80 "Non-nil to prefix items with consecutively increasing integers.
292These reflect the priorities of the items in each category." 81These reflect the priorities of the items in each category."
293 :type 'boolean 82 :type 'boolean
294 :initialize 'custom-initialize-default 83 :initialize 'custom-initialize-default
295 :set 'todos-reset-prefix 84 :set 'todos-reset-prefix
296 :group 'todos) 85 :group 'todos)
297 86
298;; FIXME: Update when window-width changes (add todos-reset-separator to 87;; FIXME: Update when window-width changes. Add todos-reset-separator to
299;; window-configuration-change-hook in todos-mode?) 88;; window-configuration-change-hook in todos-mode? But this depends on the
89;; value being window-width instead of a constant length.
300(defcustom todos-done-separator (make-string (window-width) ?-) 90(defcustom todos-done-separator (make-string (window-width) ?-)
301 "String used to visual separate done from not done items. 91 "String used to visual separate done from not done items.
302Displayed in a before-string overlay by `todos-toggle-view-done-items'." 92Displayed in a before-string overlay by `todos-toggle-view-done-items'."
303 :type 'string 93 :type 'string
304 :initialize 'custom-initialize-default 94 :initialize 'custom-initialize-default
305 :set 'todos-reset-separator 95 :set 'todos-reset-prefix
306 :group 'todos) 96 :group 'todos)
307 97
308(defcustom todos-done-string "DONE " 98(defcustom todos-done-string "DONE "
309 "Identifying string appended to the front of done todos items." 99 "Identifying string appended to the front of done todos items."
310 :type 'string 100 :type 'string
311 ;; :initialize 'custom-initialize-default 101 :initialize 'custom-initialize-default
312 ;; :set 'todos-reset-done-string 102 :set 'todos-reset-done-string
103 :group 'todos)
104
105(defcustom todos-comment-string "COMMENT"
106 "String inserted before optional comment appended to done item."
107 :type 'string
108 :initialize 'custom-initialize-default
109 :set 'todos-reset-comment-string
313 :group 'todos) 110 :group 'todos)
314 111
315(defcustom todos-show-with-done nil 112(defcustom todos-show-with-done nil
@@ -317,6 +114,24 @@ Displayed in a before-string overlay by `todos-toggle-view-done-items'."
317 :type 'boolean 114 :type 'boolean
318 :group 'todos) 115 :group 'todos)
319 116
117(defun todos-mode-line-control (cat)
118 "Return a mode line control for Todos buffers.
119Argument CAT is the name of the current Todos category.
120This function is the value of the user variable
121`todos-mode-line-function'."
122 (let ((file (file-name-sans-extension
123 (file-name-nondirectory todos-current-todos-file))))
124 (format "%s category %d: %s" file todos-category-number cat)))
125
126(defcustom todos-mode-line-function 'todos-mode-line-control
127 "Function that returns a mode line control for Todos buffers.
128The function is expected to take one argument that holds the name
129of the current Todos category. The resulting control becomes the
130local value of `mode-line-buffer-identification' in each Todos
131buffer."
132 :type 'function
133 :group 'todos)
134
320(defcustom todos-files-directory (locate-user-emacs-file "todos/") 135(defcustom todos-files-directory (locate-user-emacs-file "todos/")
321 "Directory where user's Todos files are saved." 136 "Directory where user's Todos files are saved."
322 :type 'directory 137 :type 'directory
@@ -325,21 +140,43 @@ Displayed in a before-string overlay by `todos-toggle-view-done-items'."
325(defun todos-files (&optional archives) 140(defun todos-files (&optional archives)
326 "Default value of `todos-files-function'. 141 "Default value of `todos-files-function'.
327This returns the case-insensitive alphabetically sorted list of 142This returns the case-insensitive alphabetically sorted list of
328files in `todos-files-directory' with the extension \".todo\". 143file truenames in `todos-files-directory' with the extension
329With non-nil ARCHIVES return the list of archive files." 144\".todo\". With non-nil ARCHIVES return the list of archive file
330 (sort (directory-files todos-files-directory t 145truenames (those with the extension \".toda\")."
331 (if archives "\.toda$" "\.todo$") t) 146 (let ((files (mapcar 'file-truename
332 (lambda (s1 s2) (let ((cis1 (upcase s1)) 147 (directory-files todos-files-directory t
333 (cis2 (upcase s2))) 148 (if archives "\.toda$" "\.todo$") t))))
334 (string< cis1 cis2))))) 149 (sort files (lambda (s1 s2) (let ((cis1 (upcase s1))
150 (cis2 (upcase s2)))
151 (string< cis1 cis2))))))
335 152
336(defcustom todos-files-function 'todos-files 153(defcustom todos-files-function 'todos-files
337 "Function returning the value of the variable `todos-files'. 154 "Function returning the value of the variable `todos-files'.
338If this function is called with an optional non-nil argument, 155This function should take an optional argument that, if non-nil,
339then it returns the value of the variable `todos-archives'." 156makes it return the value of the variable `todos-archives'."
157 :type 'function
158 :group 'todos)
159
160(defcustom todos-filter-function nil
161 ""
340 :type 'function 162 :type 'function
341 :group 'todos) 163 :group 'todos)
342 164
165(defcustom todos-priorities-rules (list)
166 "List of rules for choosing top priorities of each Todos file.
167The rules should be set interactively by invoking
168`todos-set-top-priorities'.
169
170Each rule is a list whose first element is a member of
171`todos-files', whose second element is a number specifying the
172default number of top priority items for the categories in that
173file, and whose third element is an alist whose elements are
174conses of a category name in that file and the number of top
175priority items in that category that `todos-top-priorities' shows
176by default, which overrides the number for the file."
177 :type 'list
178 :group 'todos)
179
343(defcustom todos-merged-files nil 180(defcustom todos-merged-files nil
344 "List of files for `todos-merged-top-priorities'." 181 "List of files for `todos-merged-top-priorities'."
345 :type `(set ,@(mapcar (lambda (x) (list 'const x)) 182 :type `(set ,@(mapcar (lambda (x) (list 'const x))
@@ -347,17 +184,19 @@ then it returns the value of the variable `todos-archives'."
347 :group 'todos) 184 :group 'todos)
348 185
349(defcustom todos-prompt-merged-files nil 186(defcustom todos-prompt-merged-files nil
350 "Non-nil to prompt for merging files for `todos-top-priorities'." 187 "Non-nil to prompt for merging files for `todos-filter-items'."
351 :type 'boolean 188 :type 'boolean
352 :group 'todos) 189 :group 'todos)
353 190
354(defcustom todos-auto-switch-todos-file nil ;FIXME: t by default? 191(defcustom todos-show-current-file t
355 "Non-nil to make a Todos file current upon changing to it." 192 "Non-nil to make `todos-show' visit the current Todos file.
193Otherwise, `todos-show' always visits `todos-default-todos-file'."
356 :type 'boolean 194 :type 'boolean
357 :initialize 'custom-initialize-default 195 :initialize 'custom-initialize-default
358 :set 'todos-toggle-switch-todos-file-noninteractively 196 :set 'todos-toggle-show-current-file
359 :group 'todos) 197 :group 'todos)
360 198
199;; FIXME: omit second sentence from doc string?
361(defcustom todos-default-todos-file (car (funcall todos-files-function)) 200(defcustom todos-default-todos-file (car (funcall todos-files-function))
362 "Todos file visited by first session invocation of `todos-show'. 201 "Todos file visited by first session invocation of `todos-show'.
363Normally this should be set by invoking `todos-change-default-file' 202Normally this should be set by invoking `todos-change-default-file'
@@ -366,10 +205,12 @@ either directly or as a side effect of `todos-add-file'."
366 (funcall todos-files-function))) 205 (funcall todos-files-function)))
367 :group 'todos) 206 :group 'todos)
368 207
369;; FIXME: make a defvar instead of a defcustom, and one for each member of todos-file 208(defcustom todos-visit-files-commands (list 'find-file 'dired-find-file)
370(defcustom todos-file-top "~/todos.todt" ;FIXME 209 "List of commands to visit files for `todos-after-find-file'.
371 "TODO mode top priorities file." 210Invoking these commands to visit a Todos or Todos Archive file
372 :type 'file 211calls `todos-show' or `todos-show-archive', so that the file is
212displayed correctly."
213 :type '(repeat function)
373 :group 'todos) 214 :group 'todos)
374 215
375(defcustom todos-categories-buffer "*Todos Categories*" 216(defcustom todos-categories-buffer "*Todos Categories*"
@@ -402,19 +243,23 @@ either directly or as a side effect of `todos-add-file'."
402 :type 'string 243 :type 'string
403 :group 'todos) 244 :group 'todos)
404 245
246(defcustom todos-categories-totals-label "Totals"
247 "String to label total item counts in `todos-categories-buffer'."
248 :type 'string
249 :group 'todos)
250
405(defcustom todos-categories-number-separator " | " 251(defcustom todos-categories-number-separator " | "
406 "String between number and category in `todos-categories-mode'. 252 "String between number and category in `todos-categories-buffer'.
407This separates the number from the category name in the default 253This separates the number from the category name in the default
408categories display according to priority." 254categories display according to priority."
409 :type 'string 255 :type 'string
410 :group 'todos) 256 :group 'todos)
411 257
412(defcustom todos-categories-align 'center 258(defcustom todos-categories-align 'center
413 "" 259 "Alignment of category names in `todos-categories-buffer'."
414 :type '(radio (const left) (const center) (const right)) 260 :type '(radio (const left) (const center) (const right))
415 :group 'todos) 261 :group 'todos)
416 262
417;; FIXME: set for each Todos file?
418(defcustom todos-ignore-archived-categories nil 263(defcustom todos-ignore-archived-categories nil
419 "Non-nil to ignore categories with only archived items. 264 "Non-nil to ignore categories with only archived items.
420When non-nil such categories are omitted from `todos-categories' 265When non-nil such categories are omitted from `todos-categories'
@@ -428,14 +273,25 @@ archived categories."
428 :set 'todos-reset-categories 273 :set 'todos-reset-categories
429 :group 'todos) 274 :group 'todos)
430 275
431(defcustom todos-archived-categories-buffer "*Todos Archived Categories*" 276;; FIXME
432 "Name of buffer displayed by `todos-display-categories'." 277(defcustom todos-edit-buffer "*Todos Edit*"
278 "Name of current buffer in Todos Edit mode."
433 :type 'string 279 :type 'string
434 :group 'todos) 280 :group 'todos)
435 281
436(defcustom todos-edit-buffer "*Todos Edit*" 282;; (defcustom todos-edit-buffer "*Todos Top Priorities*"
437 "TODO Edit buffer name." 283;; "TODO Edit buffer name."
438 :type 'string 284;; :type 'string
285;; :group 'todos)
286
287;; (defcustom todos-edit-buffer "*Todos Diary Entries*"
288;; "TODO Edit buffer name."
289;; :type 'string
290;; :group 'todos)
291
292(defcustom todos-use-only-highlighted-region t
293 "Non-nil to enable inserting only highlighted region as new item."
294 :type 'boolean
439 :group 'todos) 295 :group 'todos)
440 296
441(defcustom todos-include-in-diary nil 297(defcustom todos-include-in-diary nil
@@ -443,6 +299,13 @@ archived categories."
443 :type 'boolean 299 :type 'boolean
444 :group 'todos) 300 :group 'todos)
445 301
302(defcustom todos-diary-nonmarking nil
303 "Non-nil to insert new Todo diary items as nonmarking by default.
304This appends `diary-nonmarking-symbol' to the front of an item on
305insertion provided it doesn't begin with `todos-nondiary-marker'."
306 :type 'boolean
307 :group 'todos)
308
446(defcustom todos-nondiary-marker '("[" "]") 309(defcustom todos-nondiary-marker '("[" "]")
447 "List of strings surrounding item date to block diary inclusion. 310 "List of strings surrounding item date to block diary inclusion.
448The first string is inserted before the item date and must be a 311The first string is inserted before the item date and must be a
@@ -455,28 +318,24 @@ the diary date."
455 :set 'todos-reset-nondiary-marker) 318 :set 'todos-reset-nondiary-marker)
456 319
457(defcustom todos-print-function 'ps-print-buffer-with-faces 320(defcustom todos-print-function 'ps-print-buffer-with-faces
458 "Function to print the current buffer." 321 "Function called to print buffer content; see `todos-print'."
459 :type 'symbol 322 :type 'symbol
460 :group 'todos) 323 :group 'todos)
461 324
325;; FIXME: rename, change meaning of zero, refer to todos-priorities-rules
462(defcustom todos-show-priorities 1 326(defcustom todos-show-priorities 1
463 "Default number of priorities to show by \\[todos-top-priorities]. 327 "Default number of priorities to show by `todos-top-priorities'.
4640 means show all entries." 3280 means show all entries."
465 :type 'integer 329 :type 'integer
466 :group 'todos) 330 :group 'todos)
467 331
468(defcustom todos-print-priorities 0 332(defcustom todos-print-priorities 0
469 "Default number of priorities to print by \\[todos-print]. 333 "Default number of priorities to print by `todos-print'.
4700 means print all entries." 3340 means print all entries."
471 :type 'integer 335 :type 'integer
472 :group 'todos) 336 :group 'todos)
473 337
474(defcustom todos-save-top-priorities-too t 338(defcustom todos-completion-ignore-case t ;; FIXME: nil for release?
475 "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'."
476 :type 'boolean
477 :group 'todos)
478
479(defcustom todos-completion-ignore-case t ;; FIXME: nil for release
480 "Non-nil means don't consider case significant in `todos-read-category'." 339 "Non-nil means don't consider case significant in `todos-read-category'."
481 :type 'boolean 340 :type 'boolean
482 :group 'todos) 341 :group 'todos)
@@ -491,17 +350,17 @@ current time, if nil, they include it."
491 :group 'todos) 350 :group 'todos)
492 351
493(defcustom todos-wrap-lines t 352(defcustom todos-wrap-lines t
494 "" 353 "Non-nil to wrap long lines by `todos-line-wrapping-function'." ;FIXME
495 :group 'todos 354 :group 'todos
496 :type 'boolean) 355 :type 'boolean)
497 356
498(defcustom todos-line-wrapping-function 'todos-wrap-and-indent 357(defcustom todos-line-wrapping-function 'todos-wrap-and-indent
499 "" 358 "Function called when `todos-wrap-lines' is non-nil." ;FIXME
500 :group 'todos 359 :group 'todos
501 :type 'function) 360 :type 'function)
502 361
503(defcustom todos-indent-to-here 6 362(defcustom todos-indent-to-here 6
504 "" 363 "Number of spaces `todos-line-wrapping-function' indents to."
505 :type 'integer 364 :type 'integer
506 :group 'todos) 365 :group 'todos)
507 366
@@ -514,167 +373,347 @@ current time, if nil, they include it."
514 :group 'todos) 373 :group 'todos)
515 374
516(defface todos-prefix-string 375(defface todos-prefix-string
517 '((t 376 '((t :inherit font-lock-constant-face))
518 :inherit font-lock-constant-face
519 ))
520 "Face for Todos prefix string." 377 "Face for Todos prefix string."
521 :group 'todos-faces) 378 :group 'todos-faces)
522 379
380(defface todos-mark
381 '((t :inherit font-lock-warning-face))
382 "Face for marks on Todos items."
383 :group 'todos-faces)
384
523(defface todos-button 385(defface todos-button
524 '((t 386 '((t :inherit widget-field))
525 :inherit widget-field
526 ))
527 "Face for buttons in todos-display-categories." 387 "Face for buttons in todos-display-categories."
528 :group 'todos-faces) 388 :group 'todos-faces)
529 389
530(defface todos-sorted-column 390(defface todos-sorted-column
531 '((t 391 '((t :inherit fringe))
532 :inherit fringe
533 ))
534 "Face for buttons in todos-display-categories." 392 "Face for buttons in todos-display-categories."
535 :group 'todos-faces) 393 :group 'todos-faces)
536 394
537(defface todos-archived-only 395(defface todos-archived-only
538 '((t 396 '((t (:inherit (shadow))))
539 (:inherit (shadow))
540 ))
541 "Face for archived-only categories in todos-display-categories." 397 "Face for archived-only categories in todos-display-categories."
542 :group 'todos-faces) 398 :group 'todos-faces)
543 399
544(defface todos-search 400(defface todos-search
545 '((t 401 '((t :inherit match))
546 :inherit match
547 ))
548 "Face for matches found by todos-search." 402 "Face for matches found by todos-search."
549 :group 'todos-faces) 403 :group 'todos-faces)
550 404
551(defface todos-date 405(defface todos-date
552 '((t 406 '((t :inherit diary))
553 :inherit diary
554 ))
555 "Face for Todos prefix string." 407 "Face for Todos prefix string."
556 :group 'todos-faces) 408 :group 'todos-faces)
557(defvar todos-date-face 'todos-date) 409(defvar todos-date-face 'todos-date)
558 410
559(defface todos-time 411(defface todos-time
560 '((t 412 '((t :inherit diary-time))
561 :inherit diary-time
562 ))
563 "Face for Todos prefix string." 413 "Face for Todos prefix string."
564 :group 'todos-faces) 414 :group 'todos-faces)
565(defvar todos-time-face 'todos-time) 415(defvar todos-time-face 'todos-time)
566 416
567(defface todos-done 417(defface todos-done
568 '((t 418 '((t :inherit font-lock-comment-face))
569 :inherit font-lock-comment-face
570 ))
571 "Face for done Todos item header string." 419 "Face for done Todos item header string."
572 :group 'todos-faces) 420 :group 'todos-faces)
573(defvar todos-done-face 'todos-done) 421(defvar todos-done-face 'todos-done)
574 422
423(defface todos-comment
424 '((t :inherit font-lock-comment-face))
425 "Face for comments appended to done Todos items."
426 :group 'todos-faces)
427(defvar todos-comment-face 'todos-comment)
428
575(defface todos-done-sep 429(defface todos-done-sep
576 '((t 430 '((t :inherit font-lock-type-face))
577 :inherit font-lock-type-face
578 ))
579 "Face for separator string bewteen done and not done Todos items." 431 "Face for separator string bewteen done and not done Todos items."
580 :group 'todos-faces) 432 :group 'todos-faces)
581(defvar todos-done-sep-face 'todos-done-sep) 433(defvar todos-done-sep-face 'todos-done-sep)
582 434
583(defvar todos-font-lock-keywords 435(defvar todos-font-lock-keywords
584 (list 436 (list
585 '(todos-date-string-match 1 todos-date-face t) 437 '(todos-date-string-matcher 1 todos-date-face t)
586 '(todos-time-string-match 1 todos-time-face t) 438 '(todos-time-string-matcher 1 todos-time-face t)
587 '(todos-done-string-match 0 todos-done-face t) 439 '(todos-done-string-matcher 0 todos-done-face t)
588 '(todos-category-string-match 1 todos-done-sep-face t)) 440 '(todos-comment-string-matcher 1 todos-done-face t)
441 '(todos-category-string-matcher 1 todos-done-sep-face t))
589 "Font-locking for Todos mode.") 442 "Font-locking for Todos mode.")
590 443
591;; --------------------------------------------------------------------------- 444;; ---------------------------------------------------------------------------
592;;; Modes setup 445;;; Modes setup
593 446
594(defvar todos-files (funcall todos-files-function) 447(defvar todos-files (funcall todos-files-function)
595 "List of user's Todos files.") 448 "List of truenames of user's Todos files.")
596 449
597(defvar todos-archives (funcall todos-files-function t) 450(defvar todos-archives (funcall todos-files-function t)
598 "List of user's Todos archives.") 451 "List of truenames of user's Todos archives.")
599 452
600(defvar todos-categories nil 453(defvar todos-categories nil
601 "List of categories in the current Todos file. 454 "Alist of categories in the current Todos file.
602The elements are lists whose car is a category name and whose cdr 455The elements are cons cells whose car is a category name and
603is the category's property list.") 456whose cdr is a vector of the category's item counts. These are,
457in order, the numbers of todo items, todo items included in the
458Diary, done items and archived items.")
459
460(defvar todos-categories-full nil
461 "Variable holding non-truncated copy of `todos-categories'.
462Set when `todos-ignore-archived-categories' is set to non-nil, to
463restore full `todos-categories' list when
464`todos-ignore-archived-categories' is reset to nil.")
465
466(defvar todos-current-todos-file nil
467 "Variable holding the name of the currently active Todos file.")
468;; Automatically set by `todos-switch-todos-file'.")
469
470;; FIXME: Add function to kill-buffer-hook that sets this to the latest
471;; existing Todos file or else todos-default-todos-file on killing the buffer
472;; of a Todos file
473(defvar todos-global-current-todos-file nil
474 "Variable holding name of current Todos file.
475Used by functions called from outside of Todos mode to visit the
476current Todos file rather than the default Todos file (i.e. when
477users option `todos-show-current-file' is non-nil).")
478
479(defun todos-reset-global-current-todos-file ()
480 ""
481 (let ((buflist (copy-sequence (buffer-list)))
482 (cur todos-global-current-todos-file))
483 (catch 'done
484 (while buflist
485 (let* ((buf (pop buflist))
486 (bufname (buffer-file-name buf)))
487 (when bufname (setq bufname (file-truename bufname)))
488 (when (and (member bufname todos-files)
489 (not (eq buf (current-buffer))))
490 (setq todos-global-current-todos-file bufname)
491 (throw 'done nil)))))
492 (if (equal cur todos-global-current-todos-file)
493 (setq todos-global-current-todos-file todos-default-todos-file))))
494
495(defvar todos-category-number 1
496 "Variable holding the number of the current Todos category.
497This number is one more than the index of the category in
498`todos-categories'.")
499
500(defvar todos-first-visit t
501 "Non-nil if first display of this file in the current session.
502See `todos-display-categories-first'.")
503
504;; FIXME: rename?
505(defvar todos-tmp-buffer-name " *todo tmp*")
506
507(defvar todos-category-beg "--==-- "
508 "String marking beginning of category (inserted with its name).")
509
510(defvar todos-category-done "==--== DONE "
511 "String marking beginning of category's done items.")
512
513(defvar todos-nondiary-start (nth 0 todos-nondiary-marker)
514 "String inserted before item date to block diary inclusion.")
515
516(defvar todos-nondiary-end (nth 1 todos-nondiary-marker)
517 "String inserted after item date matching `todos-nondiary-start'.")
518
519(defvar todos-show-done-only nil
520 "If non-nil display only done items in current category.
521Set by `todos-toggle-show-done-only' and used by
522`todos-category-select'.")
523
524;;; Todos insertion commands, key bindings and keymap
525
526;; http://rosettacode.org/wiki/Power_set#Common_Lisp (GFDL)
527(defun powerset (l)
528 (if (null l)
529 (list nil)
530 (let ((prev (powerset (cdr l))))
531 (append (mapcar #'(lambda (elt) (cons (car l) elt)) prev)
532 prev))))
533
534;; Return list of lists of non-nil atoms produced from ARGLIST. The elements
535;; of ARGLIST may be atoms or lists.
536(defun todos-gen-arglists (arglist)
537 (let (arglists)
538 (while arglist
539 (let ((arg (pop arglist)))
540 (cond ((symbolp arg)
541 (setq arglists (if arglists
542 (mapcar (lambda (l) (push arg l)) arglists)
543 (list (push arg arglists)))))
544 ((listp arg)
545 (setq arglists
546 (mapcar (lambda (a)
547 (if (= 1 (length arglists))
548 (apply (lambda (l) (push a l)) arglists)
549 (mapcar (lambda (l) (push a l)) arglists)))
550 arg))))))
551 (setq arglists (mapcar 'reverse (apply 'append (mapc 'car arglists))))))
552
553(defvar todos-insertion-commands-args-genlist
554 '(diary nonmarking (calendar date dayname) time (here region))
555 "Generator list for argument lists of Todos insertion commands.")
556
557(eval-when-compile (require 'cl)) ; remove-duplicates
558
559(defvar todos-insertion-commands-args
560 (let ((argslist (todos-gen-arglists todos-insertion-commands-args-genlist))
561 res new)
562 (setq res (remove-duplicates
563 (apply 'append (mapcar 'powerset argslist)) :test 'equal))
564 (dolist (l res)
565 (unless (= 5 (length l))
566 (let ((v (make-vector 5 nil)) elt)
567 (while l
568 (setq elt (pop l))
569 (cond ((eq elt 'diary)
570 (aset v 0 elt))
571 ((eq elt 'nonmarking)
572 (aset v 1 elt))
573 ((or (eq elt 'calendar)
574 (eq elt 'date)
575 (eq elt 'dayname))
576 (aset v 2 elt))
577 ((eq elt 'time)
578 (aset v 3 elt))
579 ((or (eq elt 'here)
580 (eq elt 'region))
581 (aset v 4 elt))))
582 (setq l (append v nil))))
583 (setq new (append new (list l))))
584 new)
585 "List of all argument lists for Todos insertion commands.")
586
587(defun todos-insertion-command-name (arglist)
588 "Generate Todos insertion command name from ARGLIST."
589 (replace-regexp-in-string
590 "-\\_>" ""
591 (replace-regexp-in-string
592 "-+" "-"
593 (concat "todos-item-insert-"
594 (mapconcat (lambda (e) (if e (symbol-name e))) arglist "-")))))
595
596(defvar todos-insertion-commands-names
597 (mapcar (lambda (l)
598 (todos-insertion-command-name l))
599 todos-insertion-commands-args)
600 "List of names of Todos insertion commands.")
601
602(defmacro todos-define-insertion-command (&rest args)
603 (let ((name (intern (todos-insertion-command-name args)))
604 (arg0 (nth 0 args))
605 (arg1 (nth 1 args))
606 (arg2 (nth 2 args))
607 (arg3 (nth 3 args))
608 (arg4 (nth 4 args)))
609 `(defun ,name (&optional arg)
610 "Todos item insertion command."
611 (interactive)
612 (todos-insert-item arg ',arg0 ',arg1 ',arg2 ',arg3 ',arg4))))
613
614(defvar todos-insertion-commands
615 (mapcar (lambda (c)
616 (eval `(todos-define-insertion-command ,@c)))
617 todos-insertion-commands-args)
618 "List of Todos insertion commands.")
619
620(defvar todos-insertion-commands-arg-key-list
621 '(("diary" "y" "yy")
622 ("nonmarking" "k" "kk")
623 ("calendar" "c" "cc")
624 ("date" "d" "dd")
625 ("dayname" "n" "nn")
626 ("time" "t" "tt")
627 ("here" "h" "h")
628 ("region" "r" "r"))
629 "")
630
631(defun todos-insertion-key-bindings (map)
632 ""
633 (dolist (c todos-insertion-commands)
634 (let* ((key "")
635 (cname (symbol-name c)))
636 ;; (if (string-match "diary\\_>" cname) (setq key (concat key "yy")))
637 ;; (if (string-match "diary.+" cname) (setq key (concat key "y")))
638 ;; (if (string-match "nonmarking\\_>" cname) (setq key (concat key "kk")))
639 ;; (if (string-match "nonmarking.+" cname) (setq key (concat key "k")))
640 ;; (if (string-match "calendar\\_>" cname) (setq key (concat key "cc")))
641 ;; (if (string-match "calendar.+" cname) (setq key (concat key "c")))
642 ;; (if (string-match "date\\_>" cname) (setq key (concat key "dd")))
643 ;; (if (string-match "date.+" cname) (setq key (concat key "d")))
644 ;; (if (string-match "dayname\\_>" cname) (setq key (concat key "nn")))
645 ;; (if (string-match "dayname.+" cname) (setq key (concat key "n")))
646 ;; (if (string-match "time\\_>" cname) (setq key (concat key "tt")))
647 ;; (if (string-match "time.+" cname) (setq key (concat key "t")))
648 ;; (if (string-match "here" cname) (setq key (concat key "h")))
649 ;; (if (string-match "region" cname) (setq key (concat key "r")))
650 (mapc (lambda (l)
651 (let ((arg (nth 0 l))
652 (key1 (nth 1 l))
653 (key2 (nth 2 l)))
654 (if (string-match (concat (regexp-quote arg) "\\_>") cname)
655 (setq key (concat key key2)))
656 (if (string-match (concat (regexp-quote arg) ".+") cname)
657 (setq key (concat key key1)))))
658 todos-insertion-commands-arg-key-list)
659 (if (string-match (concat (regexp-quote "todos-item-insert") "\\_>") cname)
660 (setq key (concat key "i")))
661 (define-key map key c))))
604 662
605(defvar todos-insertion-map 663(defvar todos-insertion-map
606 (let ((map (make-keymap))) 664 (let ((map (make-keymap)))
607 (define-key map "i" 'todos-insert-item) 665 (todos-insertion-key-bindings map)
608 (define-key map "h" 'todos-insert-item-here)
609 (define-key map "dd" 'todos-insert-item-ask-date)
610 (define-key map "dtt" 'todos-insert-item-ask-date-time)
611 (define-key map "dtyy" 'todos-insert-item-ask-date-time-for-diary)
612 (define-key map "dtyh" 'todos-insert-item-ask-date-time-for-diary-here)
613 (define-key map "dth" 'todos-insert-item-ask-date-time-here)
614 (define-key map "dmm" 'todos-insert-item-ask-date-maybe-notime)
615 (define-key map "dmyy" 'todos-insert-item-ask-date-maybe-notime-for-diary)
616 (define-key map "dmyh" 'todos-insert-item-ask-date-maybe-notime-for-diary-here)
617 (define-key map "dmh" 'todos-insert-item-ask-date-maybe-notime-here)
618 (define-key map "dyy" 'todos-insert-item-ask-date-for-diary)
619 (define-key map "dyh" 'todos-insert-item-ask-date-for-diary-here)
620 (define-key map "dh" 'todos-insert-item-ask-date-here)
621 (define-key map "nn" 'todos-insert-item-ask-dayname)
622 (define-key map "ntt" 'todos-insert-item-ask-dayname-time)
623 (define-key map "ntyy" 'todos-insert-item-ask-dayname-time-for-diary)
624 (define-key map "ntyh" 'todos-insert-item-ask-dayname-time-for-diary-here)
625 (define-key map "nth" 'todos-insert-item-ask-dayname-time-here)
626 (define-key map "nmm" 'todos-insert-item-ask-dayname-maybe-notime)
627 (define-key map "nmyy" 'todos-insert-item-ask-dayname-maybe-notime-for-diary)
628 (define-key map "nmyh" 'todos-insert-item-ask-dayname-maybe-notime-for-diary-here)
629 (define-key map "nmh" 'todos-insert-item-ask-dayname-maybe-notime-here)
630 (define-key map "nyy" 'todos-insert-item-ask-dayname-for-diary)
631 (define-key map "nyh" 'todos-insert-item-ask-dayname-for-diary-here)
632 (define-key map "nh" 'todos-insert-item-ask-dayname-here)
633 (define-key map "tt" 'todos-insert-item-ask-time)
634 (define-key map "tyy" 'todos-insert-item-ask-time-for-diary)
635 (define-key map "tyh" 'todos-insert-item-ask-time-for-diary-here)
636 (define-key map "th" 'todos-insert-item-ask-time-here)
637 (define-key map "mm" 'todos-insert-item-maybe-notime)
638 (define-key map "myy" 'todos-insert-item-maybe-notime-for-diary)
639 (define-key map "myh" 'todos-insert-item-maybe-notime-for-diary-here)
640 (define-key map "mh" 'todos-insert-item-maybe-notime-here)
641 (define-key map "yy" 'todos-insert-item-for-diary)
642 (define-key map "yh" 'todos-insert-item-for-diary-here)
643 map) 666 map)
644 "Keymap for Todos mode insertion commands.") 667 "Keymap for Todos mode insertion commands.")
645 668
646(defvar todos-mode-map 669(defvar todos-mode-map
647 (let ((map (make-keymap))) 670 (let ((map (make-keymap)))
648 (suppress-keymap map t) 671 ;; Don't suppress digit keys, so they can supply prefix arguments.
649 ;; navigation commands 672 (suppress-keymap map)
650 (define-key map "f" 'todos-forward-category)
651 (define-key map "b" 'todos-backward-category)
652 (define-key map "j" 'todos-jump-to-category)
653 (define-key map "J" 'todos-jump-to-category-other-file)
654 (define-key map "n" 'todos-forward-item)
655 (define-key map "p" 'todos-backward-item)
656 (define-key map "S" 'todos-search)
657 (define-key map "X" 'todos-clear-matches)
658 ;; display commands 673 ;; display commands
659 (define-key map "Cd" 'todos-display-categories) ;FIXME: Cs todos-show-categories? 674 (define-key map "Cd" 'todos-display-categories) ;FIXME: Cs todos-show-categories?
660 ;; (define-key map "" 'todos-display-categories-alphabetically) 675 ;; (define-key map "" 'todos-display-categories-alphabetically)
661 (define-key map "H" 'todos-highlight-item) 676 (define-key map "H" 'todos-highlight-item)
662 (define-key map "N" 'todos-toggle-item-numbering) 677 (define-key map "N" 'todos-toggle-item-numbering)
663 ;; (define-key map "" 'todos-toggle-display-date-time) 678 (define-key map "D" 'todos-toggle-display-date-time)
679 (define-key map "*" 'todos-toggle-mark-item)
680 (define-key map "C*" 'todos-mark-category)
681 (define-key map "Cu" 'todos-unmark-category)
664 (define-key map "P" 'todos-print) 682 (define-key map "P" 'todos-print)
683 ;; (define-key map "" 'todos-print-to-file)
665 (define-key map "v" 'todos-toggle-view-done-items) 684 (define-key map "v" 'todos-toggle-view-done-items)
666 (define-key map "V" 'todos-toggle-show-done-only) 685 (define-key map "V" 'todos-toggle-show-done-only)
667 (define-key map "Av" 'todos-view-archived-items) 686 (define-key map "Av" 'todos-view-archived-items)
668 (define-key map "As" 'todos-switch-to-archive) 687 (define-key map "As" 'todos-show-archive)
669 (define-key map "Ac" 'todos-choose-archive) 688 (define-key map "Ac" 'todos-choose-archive)
670 (define-key map "Y" 'todos-diary-items) 689 (define-key map "Y" 'todos-diary-items)
671 (define-key map "t" 'todos-top-priorities) 690 ;; (define-key map "" 'todos-update-merged-files)
672 (define-key map "T" 'todos-merged-top-priorities) 691 ;; (define-key map "" 'todos-set-top-priorities)
692 (define-key map "Ftt" 'todos-top-priorities)
693 (define-key map "Ftm" 'todos-merged-top-priorities)
694 (define-key map "Fdd" 'todos-diary-items)
695 (define-key map "Fdm" 'todos-merged-diary-items)
696 (define-key map "Frr" 'todos-regexp-items)
697 (define-key map "Frm" 'todos-merged-regexp-items)
698 (define-key map "Fcc" 'todos-custom-items)
699 (define-key map "Fcm" 'todos-merged-custom-items)
673 ;; (define-key map "" 'todos-save-top-priorities) 700 ;; (define-key map "" 'todos-save-top-priorities)
701 ;; navigation commands
702 (define-key map "f" 'todos-forward-category)
703 (define-key map "b" 'todos-backward-category)
704 (define-key map "j" 'todos-jump-to-category)
705 (define-key map "J" 'todos-jump-to-category-other-file)
706 (define-key map "n" 'todos-forward-item)
707 (define-key map "p" 'todos-backward-item)
708 (define-key map "S" 'todos-search)
709 (define-key map "X" 'todos-clear-matches)
674 ;; editing commands 710 ;; editing commands
675 (define-key map "Fa" 'todos-add-file) 711 (define-key map "Fa" 'todos-add-file)
712 ;; (define-key map "" 'todos-change-default-file)
676 (define-key map "Ca" 'todos-add-category) 713 (define-key map "Ca" 'todos-add-category)
677 (define-key map "Cr" 'todos-rename-category) 714 (define-key map "Cr" 'todos-rename-category)
715 (define-key map "Cg" 'todos-merge-category)
716 ;; (define-key map "" 'todos-merge-categories)
678 (define-key map "Cm" 'todos-move-category) 717 (define-key map "Cm" 'todos-move-category)
679 (define-key map "Ck" 'todos-delete-category) 718 (define-key map "Ck" 'todos-delete-category)
680 (define-key map "d" 'todos-item-done) 719 (define-key map "d" 'todos-item-done)
@@ -682,24 +721,95 @@ is the category's property list.")
682 (define-key map "em" 'todos-edit-multiline) 721 (define-key map "em" 'todos-edit-multiline)
683 (define-key map "eh" 'todos-edit-item-header) 722 (define-key map "eh" 'todos-edit-item-header)
684 (define-key map "ed" 'todos-edit-item-date) 723 (define-key map "ed" 'todos-edit-item-date)
724 (define-key map "ey" 'todos-edit-item-date-is-today)
685 (define-key map "et" 'todos-edit-item-time) 725 (define-key map "et" 'todos-edit-item-time)
726 (define-key map "ec" 'todos-comment-done-item) ;FIXME: or just "c"?
686 (define-key map "i" todos-insertion-map) 727 (define-key map "i" todos-insertion-map)
687 (define-key map "k" 'todos-delete-item) 728 (define-key map "k" 'todos-delete-item)
688 (define-key map "m" 'todos-move-item) 729 (define-key map "m" 'todos-move-item)
689 (define-key map "M" 'todos-move-item-to-file) 730 (define-key map "M" 'todos-move-item-to-file)
731 ;; FIXME: This prevents `-' from being used in a numerical prefix argument
732 ;; without typing C-u
690 (define-key map "-" 'todos-raise-item-priority) 733 (define-key map "-" 'todos-raise-item-priority)
734 (define-key map "r" 'todos-raise-item-priority)
691 (define-key map "+" 'todos-lower-item-priority) 735 (define-key map "+" 'todos-lower-item-priority)
736 (define-key map "l" 'todos-lower-item-priority)
692 (define-key map "#" 'todos-set-item-priority) 737 (define-key map "#" 'todos-set-item-priority)
693 (define-key map "u" 'todos-item-undo) 738 (define-key map "u" 'todos-item-undo)
694 (define-key map "Ad" 'todos-archive-done-items) 739 (define-key map "Ad" 'todos-archive-done-item-or-items) ;FIXME
695 (define-key map "y" 'todos-toggle-item-diary-inclusion) 740 (define-key map "AD" 'todos-archive-category-done-items) ;FIXME
741 ;; (define-key map "" 'todos-unarchive-items)
742 ;; (define-key map "" 'todos-unarchive-category)
743 (define-key map "y" 'todos-toggle-diary-inclusion)
696 ;; (define-key map "" 'todos-toggle-diary-inclusion) 744 ;; (define-key map "" 'todos-toggle-diary-inclusion)
745 ;; (define-key map "" 'todos-toggle-item-diary-nonmarking)
746 ;; (define-key map "" 'todos-toggle-diary-nonmarking)
697 (define-key map "s" 'todos-save) 747 (define-key map "s" 'todos-save)
698 (define-key map "q" 'todos-quit) 748 (define-key map "q" 'todos-quit)
699 (define-key map [remap newline] 'newline-and-indent) 749 (define-key map [remap newline] 'newline-and-indent)
700 map) 750 map)
701 "Todos mode keymap.") 751 "Todos mode keymap.")
702 752
753(easy-menu-define
754 todos-menu todos-mode-map "Todos Menu"
755 '("Todos"
756 ("Navigation"
757 ["Next Item" todos-forward-item t]
758 ["Previous Item" todos-backward-item t]
759 "---"
760 ["Next Category" todos-forward-category t]
761 ["Previous Category" todos-backward-category t]
762 ["Jump to Category" todos-jump-to-category t]
763 ["Jump to Category in Other File" todos-jump-to-category-other-file t]
764 "---"
765 ["Search Todos File" todos-search t]
766 ["Clear Highlighting on Search Matches" todos-category-done t])
767 ("Display"
768 ["List Current Categories" todos-display-categories t]
769 ;; ["List Categories Alphabetically" todos-display-categories-alphabetically t]
770 ["Turn Item Highlighting on/off" todos-highlight-item t]
771 ["Turn Item Numbering on/off" todos-toggle-item-numbering t]
772 ["Turn Item Time Stamp on/off" todos-toggle-display-date-time t]
773 ["View/Hide Done Items" todos-toggle-view-done-items t]
774 "---"
775 ["View Diary Items" todos-diary-items t]
776 ["View Top Priority Items" todos-top-priorities t]
777 ["View Merged Top Priority Items" todos-merged-top-priorities t]
778 "---"
779 ["View Archive" todos-view-archive t]
780 ["Print Category" todos-print t]) ;FIXME
781 ("Editing"
782 ["Insert New Item" todos-insert-item t]
783 ["Insert Item Here" todos-insert-item-here t]
784 ("More Insertion Commands")
785 ["Edit Item" todos-edit-item t]
786 ["Edit Multiline Item" todos-edit-multiline t]
787 ["Edit Item Header" todos-edit-item-header t]
788 ["Edit Item Date" todos-edit-item-date t]
789 ["Edit Item Time" todos-edit-item-time t]
790 "---"
791 ["Lower Item Priority" todos-lower-item-priority t]
792 ["Raise Item Priority" todos-raise-item-priority t]
793 ["Set Item Priority" todos-set-item-priority t]
794 ["Move (Recategorize) Item" todos-move-item t]
795 ["Delete Item" todos-delete-item t]
796 ["Undo Done Item" todos-item-undo t]
797 ["Mark/Unmark Item for Diary" todos-toggle-item-diary-inclusion t]
798 ["Mark/Unmark Items for Diary" todos-toggle-diary-inclusion t]
799 ["Mark & Hide Done Item" todos-item-done t]
800 ["Archive Done Items" todos-archive-category-done-items t] ;FIXME
801 "---"
802 ["Add New Todos File" todos-add-file t]
803 ["Add New Category" todos-add-category t]
804 ["Delete Current Category" todos-delete-category t]
805 ["Rename Current Category" todos-rename-category t]
806 "---"
807 ["Save Todos File" todos-save t]
808 ["Save Top Priorities" todos-save-top-priorities t])
809 "---"
810 ["Quit" todos-quit t]
811 ))
812
703(defvar todos-archive-mode-map 813(defvar todos-archive-mode-map
704 (let ((map (make-sparse-keymap))) 814 (let ((map (make-sparse-keymap)))
705 (suppress-keymap map t) 815 (suppress-keymap map t)
@@ -719,7 +829,8 @@ is the category's property list.")
719 (define-key map "s" 'todos-save) 829 (define-key map "s" 'todos-save)
720 (define-key map "S" 'todos-search) 830 (define-key map "S" 'todos-search)
721 (define-key map "t" 'todos-show) ;FIXME: should show same category 831 (define-key map "t" 'todos-show) ;FIXME: should show same category
722 (define-key map "u" 'todos-unarchive-category) 832 ;; (define-key map "u" 'todos-unarchive-item)
833 (define-key map "U" 'todos-unarchive-category)
723 map) 834 map)
724 "Todos Archive mode keymap.") 835 "Todos Archive mode keymap.")
725 836
@@ -733,7 +844,7 @@ is the category's property list.")
733(defvar todos-categories-mode-map 844(defvar todos-categories-mode-map
734 (let ((map (make-sparse-keymap))) 845 (let ((map (make-sparse-keymap)))
735 (suppress-keymap map t) 846 (suppress-keymap map t)
736 (define-key map "a" 'todos-display-categories-alphabetically) 847 ;; (define-key map "a" 'todos-display-categories-alphabetically)
737 (define-key map "c" 'todos-display-categories) 848 (define-key map "c" 'todos-display-categories)
738 (define-key map "+" 'todos-lower-category) 849 (define-key map "+" 'todos-lower-category)
739 (define-key map "-" 'todos-raise-category) 850 (define-key map "-" 'todos-raise-category)
@@ -748,7 +859,7 @@ is the category's property list.")
748 map) 859 map)
749 "Todos Categories mode keymap.") 860 "Todos Categories mode keymap.")
750 861
751(defvar todos-top-priorities-mode-map 862(defvar todos-filter-items-mode-map
752 (let ((map (make-keymap))) 863 (let ((map (make-keymap)))
753 (suppress-keymap map t) 864 (suppress-keymap map t)
754 ;; navigation commands 865 ;; navigation commands
@@ -776,92 +887,6 @@ is the category's property list.")
776 map) 887 map)
777 "Todos Top Priorities mode keymap.") 888 "Todos Top Priorities mode keymap.")
778 889
779(defvar todos-current-todos-file nil
780 "Variable holding the name of the currently active Todos file.
781Automatically set by `todos-switch-todos-file'.")
782
783(defvar todos-category-number 0
784 "Number.")
785
786(defvar todos-tmp-buffer-name " *todo tmp*")
787
788(defvar todos-category-beg "--==-- "
789 "String marking beginning of category (inserted with its name).")
790
791(defvar todos-category-done "==--== DONE "
792 "String marking beginning of category's done items.")
793
794(defvar todos-nondiary-start (nth 0 todos-nondiary-marker)
795 "String inserted before item date to block diary inclusion.")
796
797(defvar todos-nondiary-end (nth 1 todos-nondiary-marker)
798 "String inserted after item date matching todos-nondiary-start.")
799
800(defvar todos-show-done-only nil
801 "If non-nil display only done items in current category.
802Set by `todos-toggle-show-done-only' and used by
803`todos-category-select'.")
804
805(easy-menu-define
806 todos-menu todos-mode-map "Todos Menu"
807 '("Todos"
808 ("Navigation"
809 ["Next Item" todos-forward-item t]
810 ["Previous Item" todos-backward-item t]
811 "---"
812 ["Next Category" todos-forward-category t]
813 ["Previous Category" todos-backward-category t]
814 ["Jump to Category" todos-jump-to-category t]
815 ["Jump to Category in Other File" todos-jump-to-category-other-file t]
816 "---"
817 ["Search Todos File" todos-search t]
818 ["Clear Highlighting on Search Matches" todos-category-done t])
819 ("Display"
820 ["List Current Categories" todos-display-categories t]
821 ["List Categories Alphabetically" todos-display-categories-alphabetically t]
822 ["Turn Item Highlighting on/off" todos-highlight-item t]
823 ["Turn Item Numbering on/off" todos-toggle-item-numbering t]
824 ["Turn Item Time Stamp on/off" todos-toggle-display-date-time t]
825 ["View/Hide Done Items" todos-toggle-view-done-items t]
826 "---"
827 ["View Diary Items" todos-diary-items t]
828 ["View Top Priority Items" todos-top-priorities t]
829 ["View Merged Top Priority Items" todos-merged-top-priorities t]
830 "---"
831 ["View Archive" todos-view-archive t]
832 ["Print Category" todos-print-category t])
833 ("Editing"
834 ["Insert New Item" todos-insert-item t]
835 ["Insert Item Here" todos-insert-item-here t]
836 ("More Insertion Commands")
837 ["Edit Item" todos-edit-item t]
838 ["Edit Multiline Item" todos-edit-multiline t]
839 ["Edit Item Header" todos-edit-item-header t]
840 ["Edit Item Date" todos-edit-item-date t]
841 ["Edit Item Time" todos-edit-item-time t]
842 "---"
843 ["Lower Item Priority" todos-lower-item-priority t]
844 ["Raise Item Priority" todos-raise-item-priority t]
845 ["Set Item Priority" todos-set-item-priority t]
846 ["Move (Recategorize) Item" todos-move-item t]
847 ["Delete Item" todos-delete-item t]
848 ["Undo Done Item" todos-item-undo t]
849 ["Mark/Unmark Item for Diary" todos-toggle-item-diary-inclusion t]
850 ["Mark/Unmark Items for Diary" todos-toggle-diary-inclusion t]
851 ["Mark & Hide Done Item" todos-item-done t]
852 ["Archive Done Items" todos-archive-done-items t]
853 "---"
854 ["Add New Todos File" todos-add-file t]
855 ["Add New Category" todos-add-category t]
856 ["Delete Current Category" todos-delete-category t]
857 ["Rename Current Category" todos-rename-category t]
858 "---"
859 ["Save Todos File" todos-save t]
860 ["Save Top Priorities" todos-save-top-priorities t])
861 "---"
862 ["Quit" todos-quit t]
863 ))
864
865;; FIXME: remove when part of Emacs 890;; FIXME: remove when part of Emacs
866(add-to-list 'auto-mode-alist '("\\.todo\\'" . todos-mode)) 891(add-to-list 'auto-mode-alist '("\\.todo\\'" . todos-mode))
867(add-to-list 'auto-mode-alist '("\\.toda\\'" . todos-archive-mode)) 892(add-to-list 'auto-mode-alist '("\\.toda\\'" . todos-archive-mode))
@@ -871,7 +896,7 @@ Set by `todos-toggle-show-done-only' and used by
871 (set (make-local-variable 'font-lock-defaults) '(todos-font-lock-keywords t)) 896 (set (make-local-variable 'font-lock-defaults) '(todos-font-lock-keywords t))
872 (set (make-local-variable 'indent-line-function) 'todos-indent) 897 (set (make-local-variable 'indent-line-function) 'todos-indent)
873 (when todos-wrap-lines (funcall todos-line-wrapping-function)) 898 (when todos-wrap-lines (funcall todos-line-wrapping-function))
874) 899)
875 900
876(defun todos-modes-set-2 () 901(defun todos-modes-set-2 ()
877 "" 902 ""
@@ -880,21 +905,40 @@ Set by `todos-toggle-show-done-only' and used by
880 (set (make-local-variable 'hl-line-range-function) 905 (set (make-local-variable 'hl-line-range-function)
881 (lambda() (when (todos-item-end) 906 (lambda() (when (todos-item-end)
882 (cons (todos-item-start) (todos-item-end))))) 907 (cons (todos-item-start) (todos-item-end)))))
883) 908)
884 909
910;; Autoloading isn't needed if files are identified by auto-mode-alist
885;; ;; As calendar reads included Todos file before todos-mode is loaded. 911;; ;; As calendar reads included Todos file before todos-mode is loaded.
886;; ;;;###autoload 912;; ;;;###autoload
887(define-derived-mode todos-mode nil "Todos" () 913(define-derived-mode todos-mode nil "Todos" () ;FIXME: derive from special-mode?
888 "Major mode for displaying, navigating and editing Todo lists. 914 "Major mode for displaying, navigating and editing Todo lists.
889 915
890\\{todos-mode-map}" 916\\{todos-mode-map}"
891 (easy-menu-add todos-menu) 917 (easy-menu-add todos-menu)
892 (todos-modes-set-1) 918 (todos-modes-set-1)
893 (todos-modes-set-2) 919 (todos-modes-set-2)
920 (when (member (file-truename (buffer-file-name))
921 (funcall todos-files-function))
922 (set (make-local-variable 'todos-current-todos-file)
923 (file-truename (buffer-file-name))))
924 (set (make-local-variable 'todos-categories-full) nil)
925 ;; todos-set-categories sets todos-categories-full.
926 (set (make-local-variable 'todos-categories) (todos-set-categories))
927 (set (make-local-variable 'todos-first-visit) t)
928 (set (make-local-variable 'todos-category-number) 1) ;0)
894 (set (make-local-variable 'todos-show-done-only) nil) 929 (set (make-local-variable 'todos-show-done-only) nil)
895 (when todos-auto-switch-todos-file 930 (set (make-local-variable 'todos-categories-with-marks) nil)
896 (add-hook 'post-command-hook 931 (when todos-show-current-file
897 'todos-switch-todos-file nil t))) 932 (add-hook 'pre-command-hook 'todos-show-current-file nil t))
933 (add-hook 'post-command-hook 'todos-after-find-file nil t)
934 (add-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file nil t))
935
936;; FIXME:
937(defun todos-unload-hook ()
938 ""
939 (remove-hook 'pre-command-hook 'todos-show-current-file t)
940 (remove-hook 'post-command-hook 'todos-after-find-file t)
941 (remove-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file t))
898 942
899(define-derived-mode todos-archive-mode nil "Todos-Arch" () 943(define-derived-mode todos-archive-mode nil "Todos-Arch" ()
900 "Major mode for archived Todos categories. 944 "Major mode for archived Todos categories.
@@ -903,9 +947,21 @@ Set by `todos-toggle-show-done-only' and used by
903 (todos-modes-set-1) 947 (todos-modes-set-1)
904 (todos-modes-set-2) 948 (todos-modes-set-2)
905 (set (make-local-variable 'todos-show-done-only) t) 949 (set (make-local-variable 'todos-show-done-only) t)
906 (when todos-auto-switch-todos-file 950 (set (make-local-variable 'todos-current-todos-file)
907 (add-hook 'post-command-hook 951 (file-truename (buffer-file-name)))
908 'todos-switch-todos-file nil t))) 952 (set (make-local-variable 'todos-categories) (todos-set-categories))
953 (set (make-local-variable 'todos-category-number) 1) ; 0)
954 (add-hook 'post-command-hook 'todos-after-find-file nil t))
955
956;; FIXME: return to Todos or Archive mode
957(define-derived-mode todos-raw-mode nil "Todos Raw" ()
958 "Emergency repair mode for Todos files."
959 (when (member major-mode '(todos-mode todos-archive-mode))
960 (setq buffer-read-only nil)
961 (set (make-local-variable 'font-lock-defaults) '(todos-font-lock-keywords t))
962 (widen)
963 ;; FIXME: doesn't DTRT here
964 (todos-prefix-overlays)))
909 965
910(define-derived-mode todos-edit-mode nil "Todos-Ed" () 966(define-derived-mode todos-edit-mode nil "Todos-Ed" ()
911 "Major mode for editing multiline Todo items. 967 "Major mode for editing multiline Todo items.
@@ -917,19 +973,24 @@ Set by `todos-toggle-show-done-only' and used by
917 "Major mode for displaying and editing Todos categories. 973 "Major mode for displaying and editing Todos categories.
918 974
919\\{todos-categories-mode-map}" 975\\{todos-categories-mode-map}"
920 (make-local-variable 'font-lock-defaults) 976 (set (make-local-variable 'todos-current-todos-file)
921 (setq font-lock-defaults '(todos-font-lock-keywords t)) 977 todos-global-current-todos-file)
922 (setq buffer-read-only t)) 978 (let ((cats (with-current-buffer (get-file-buffer todos-current-todos-file)
923 979 (if todos-ignore-archived-categories
924(define-derived-mode todos-top-priorities-mode nil "Todos-Top" () 980 todos-categories-full
981 (todos-set-categories)))))
982 (set (make-local-variable 'todos-categories) cats)))
983
984(define-derived-mode todos-filter-items-mode nil "Todos-Top" ()
925 "Mode for displaying and reprioritizing top priority Todos. 985 "Mode for displaying and reprioritizing top priority Todos.
926 986
927\\{todos-top-priorites-mode-map}" 987\\{todos-filter-items-mode-map}"
928 (todos-modes-set-1) 988 (todos-modes-set-1)
929 (todos-modes-set-2)) 989 (todos-modes-set-2))
930 990
991;; FIXME: need this?
931(defun todos-save () 992(defun todos-save ()
932 "Save the TODO list." 993 "Save the current Todos file."
933 (interactive) 994 (interactive)
934 ;; (todos-update-categories-sexp) 995 ;; (todos-update-categories-sexp)
935 (save-buffer) 996 (save-buffer)
@@ -937,12 +998,16 @@ Set by `todos-toggle-show-done-only' and used by
937 ) 998 )
938 999
939(defun todos-quit () 1000(defun todos-quit ()
940 "Done with TODO list for now." 1001 "Exit the current Todos-related buffer.
1002Depending on the specific mode, this either kills and the buffer
1003or buries it."
941 (interactive) 1004 (interactive)
942 (cond ((eq major-mode 'todos-categories-mode) 1005 (cond ((eq major-mode 'todos-categories-mode)
943 (kill-buffer) 1006 (kill-buffer)
944 (setq todos-descending-counts-store nil) 1007 (setq todos-descending-counts nil)
945 (setq todos-categories nil) 1008 (todos-show))
1009 ((eq major-mode 'todos-filter-items-mode)
1010 (kill-buffer)
946 (todos-show)) 1011 (todos-show))
947 ((member major-mode (list 'todos-mode 'todos-archive-mode)) 1012 ((member major-mode (list 'todos-mode 'todos-archive-mode))
948 (todos-save) 1013 (todos-save)
@@ -957,130 +1022,45 @@ Set by `todos-toggle-show-done-only' and used by
957(defun todos-show (&optional solicit-file) 1022(defun todos-show (&optional solicit-file)
958 "Visit the current Todos file and display one of its categories. 1023 "Visit the current Todos file and display one of its categories.
959 1024
960With non-nil prefix argument SOLICIT-FILE ask for file to visit, 1025With non-nil prefix argument SOLICIT-FILE ask for file to visit.
961otherwise the first invocation of this command in a session 1026Otherwise, the first invocation of this command in a session
962visits `todos-default-todos-file' (creating it if it does not yet 1027visits `todos-default-todos-file' (creating it if it does not yet
963exist). Subsequent invocations from outside of Todos mode 1028exist); subsequent invocations from outside of Todos mode revisit
964revisit this file or whichever Todos file has been made 1029this file or, if user option `todos-show-current-file' is
965current (e.g. by calling `todos-switch-todos-file'). 1030non-nil, whichever Todos file was visited last.
966 1031
967The category displayed is initially the first member of 1032The category displayed on initial invocation is the first member
968`todos-categories' for the current Todos file, subsequently 1033of `todos-categories' for the current Todos file, on subsequent
969whichever category is current. If 1034invocations whichever category was displayed last. If
970`todos-display-categories-first' is non-nil, then the first 1035`todos-display-categories-first' is non-nil, then the first
971invocation of `todos-show' displays a clickable listing of the 1036invocation of `todos-show' displays a clickable listing of the
972categories in the current Todos file." 1037categories in the current Todos file."
973 (interactive "P") 1038 (interactive "P")
974 ;; ;; Make this a no-op if called interactively in narrowed Todos mode, since 1039 (let ((file (cond (solicit-file
975 ;; ;; it is redundant in that case, but in particular to work around the bug of 1040 (if (funcall todos-files-function)
976 ;; ;; item prefix reduplication with show-paren-mode enabled. 1041 (todos-read-file-name "Select a Todos file to visit: "
977 ;; (unless (and (called-interactively-p) 1042 nil t)
978 ;; (or (eq major-mode 'todos-mode) (eq major-mode 'todos-archive-mode)) 1043 (error "There are no Todos files")))
979 ;; (< (- ( point-max) (point-min)) (buffer-size))) 1044 ((eq major-mode 'todos-archive-mode)
980 (when (and (called-interactively-p) 1045 ;; FIXME: should it visit same category?
981 (or solicit-file 1046 (concat (file-name-sans-extension todos-current-todos-file)
982 (member todos-current-todos-file todos-archives))) 1047 ".todo"))
983 (setq todos-current-todos-file nil 1048 (t
984 todos-categories nil 1049 (or todos-current-todos-file
985 todos-category-number 0)) 1050 (and todos-show-current-file
986 (let ((first-visit (or (not todos-current-todos-file) ;first call 1051 todos-global-current-todos-file)
987 ;; after switching to a not yet visited Todos file 1052 todos-default-todos-file
988 (not (buffer-live-p 1053 (todos-add-file))))))
989 (get-file-buffer todos-current-todos-file)))))) 1054 (if (and todos-first-visit todos-display-categories-first)
990 (if solicit-file 1055 (todos-display-categories)
991 (setq todos-current-todos-file 1056 (set-window-buffer (selected-window)
992 (todos-read-file-name "Select a Todos file to visit: ")) 1057 (set-buffer (find-file-noselect file)))
993 (or todos-current-todos-file 1058 ;; If no Todos file exists, initialize one.
994 (setq todos-current-todos-file (or todos-default-todos-file 1059 (if (zerop (buffer-size))
995 (todos-add-file))))) 1060 ;; Call with empty category name to get initial prompt.
996 (if (and first-visit todos-display-categories-first) 1061 (setq todos-category-number (todos-add-category "")))
997 (todos-display-categories) 1062 (save-excursion (todos-category-select)))
998 (find-file todos-current-todos-file) 1063 (setq todos-first-visit nil)))
999 ;; (or (eq major-mode 'todos-mode) (todos-mode))
1000 ;; initialize new Todos file
1001 (if (zerop (buffer-size))
1002 (setq todos-category-number (todos-add-category))
1003 ;; FIXME: let user choose category?
1004 (if (zerop todos-category-number) (setq todos-category-number 1)))
1005 (or todos-categories
1006 (setq todos-categories (if todos-ignore-archived-categories
1007 (todos-truncate-categories-list)
1008 (todos-make-categories-list))))
1009 (save-excursion (todos-category-select)))));)
1010
1011;; FIXME: make core of this internal?
1012(defun todos-display-categories (&optional sortkey)
1013 "Display the category names of the current Todos file.
1014The numbers indicate the current order of the categories.
1015
1016With non-nil SORTKEY display a non-numbered alphabetical list.
1017The lists are in Todos Categories mode.
1018
1019The category names are buttonized, and pressing a button displays
1020the category in Todos mode."
1021 (interactive)
1022 (let* ((cats0 (if (and todos-ignore-archived-categories
1023 (not (eq major-mode 'todos-categories-mode)))
1024 (todos-make-categories-list t)
1025 todos-categories))
1026 (cats (todos-sort cats0 sortkey))
1027 ;; used by todos-insert-category-line
1028 (num 0))
1029 (with-current-buffer (get-buffer-create todos-categories-buffer)
1030 (switch-to-buffer (current-buffer))
1031 (let (buffer-read-only)
1032 (erase-buffer)
1033 (kill-all-local-variables)
1034 (insert (format "Category counts for Todos file \"%s\"."
1035 (file-name-sans-extension
1036 (file-name-nondirectory todos-current-todos-file))))
1037 (newline 2)
1038 ;; FIXME: abstract format from here and todos-insert-category-line
1039 (insert (make-string (+ 3 (length todos-categories-number-separator)) 32))
1040 (save-excursion
1041 (todos-insert-sort-button todos-categories-category-label)
1042 (if (member todos-current-todos-file todos-archives)
1043 (insert (concat (make-string 6 32)
1044 (format "%s" todos-categories-archived-label)))
1045 (insert (make-string 3 32))
1046 (todos-insert-sort-button todos-categories-todo-label)
1047 (insert (make-string 2 32))
1048 (todos-insert-sort-button todos-categories-diary-label)
1049 (insert (make-string 2 32))
1050 (todos-insert-sort-button todos-categories-done-label)
1051 (insert (make-string 2 32))
1052 (todos-insert-sort-button todos-categories-archived-label))
1053 (newline 2)
1054 (mapc (lambda (cat) (todos-insert-category-line cat sortkey))
1055 (mapcar 'car cats))))
1056 (todos-categories-mode))))
1057
1058;; FIXME: make this toggle with todos-display-categories
1059(defun todos-display-categories-alphabetically ()
1060 ""
1061 (interactive)
1062 (todos-display-sorted 'alpha))
1063
1064;; FIXME: provide key bindings for these or delete them
1065(defun todos-display-categories-sorted-by-todo ()
1066 ""
1067 (interactive)
1068 (todos-display-sorted 'todo))
1069
1070(defun todos-display-categories-sorted-by-diary ()
1071 ""
1072 (interactive)
1073 (todos-display-sorted 'diary))
1074
1075(defun todos-display-categories-sorted-by-done ()
1076 ""
1077 (interactive)
1078 (todos-display-sorted 'done))
1079
1080(defun todos-display-categories-sorted-by-archived ()
1081 ""
1082 (interactive)
1083 (todos-display-sorted 'archived))
1084 1064
1085(defun todos-toggle-item-numbering () 1065(defun todos-toggle-item-numbering ()
1086 "" 1066 ""
@@ -1088,7 +1068,7 @@ the category in Todos mode."
1088 (todos-reset-prefix 'todos-number-prefix (not todos-number-prefix))) 1068 (todos-reset-prefix 'todos-number-prefix (not todos-number-prefix)))
1089 1069
1090(defun todos-toggle-view-done-items () 1070(defun todos-toggle-view-done-items ()
1091 "" 1071 "Show hidden or hide visible done items in current category."
1092 (interactive) 1072 (interactive)
1093 (save-excursion 1073 (save-excursion
1094 (goto-char (point-min)) 1074 (goto-char (point-min))
@@ -1101,8 +1081,9 @@ the category in Todos mode."
1101 (when (zerop (todos-get-count 'done cat)) 1081 (when (zerop (todos-get-count 'done cat))
1102 (message "There are no done items in this category."))))) 1082 (message "There are no done items in this category.")))))
1103 1083
1084;; FIXME: should there be `todos-toggle-view-todo-items'?
1104(defun todos-toggle-show-done-only () 1085(defun todos-toggle-show-done-only ()
1105 "" 1086 "Make category display done or back to todo items." ;FIXME
1106 (interactive) 1087 (interactive)
1107 (setq todos-show-done-only (not todos-show-done-only)) 1088 (setq todos-show-done-only (not todos-show-done-only))
1108 (todos-category-select)) 1089 (todos-category-select))
@@ -1116,45 +1097,33 @@ The buffer showing these items is in Todos Archive mode."
1116 (message "There are no archived items from this category.") 1097 (message "There are no archived items from this category.")
1117 (let* ((tfile-base (file-name-sans-extension todos-current-todos-file)) 1098 (let* ((tfile-base (file-name-sans-extension todos-current-todos-file))
1118 (afile (concat tfile-base ".toda"))) 1099 (afile (concat tfile-base ".toda")))
1119 (find-file afile) 1100 (set-window-buffer (selected-window) (set-buffer
1120 (todos-archive-mode) 1101 (find-file-noselect afile)))
1121 (unless (string= todos-current-todos-file afile) 1102 (todos-category-number cat)
1122 (setq todos-current-todos-file afile)
1123 (setq todos-categories nil))
1124 (unless todos-categories
1125 (setq todos-categories (todos-make-categories-list)))
1126 (setq todos-category-number
1127 (- (length todos-categories)
1128 (length (member cat todos-categories)))) ;FIXME
1129 (todos-jump-to-category cat))))) 1103 (todos-jump-to-category cat)))))
1130 1104
1131(defun todos-switch-to-archive (&optional ask) 1105(defun todos-show-archive (&optional ask)
1132 "Visit the archive of the current Todos file, if it exists. 1106 "Visit the archive of the current Todos file, if it exists.
1133The buffer showing the archive is in Todos Archive mode. The 1107With non-nil argument ASK prompt to choose an archive to visit;
1134first visit in a session displays the first category in the 1108see `todos-choose-archive'. The buffer showing the archive is in
1135archive, subsequent visits return to the last category 1109Todos Archive mode. The first visit in a session displays the
1136displayed." 1110first category in the archive, subsequent visits return to the
1111last category displayed."
1137 (interactive) 1112 (interactive)
1138 (let* ((tfile-base (file-name-sans-extension todos-current-todos-file)) 1113 (let* ((tfile-base (file-name-sans-extension todos-current-todos-file))
1139 (afile (if ask 1114 (afile (if ask
1140 (todos-read-file-name "Choose a Todos archive: " t) 1115 (todos-read-file-name "Choose a Todos archive: " t t)
1141 (concat tfile-base ".toda")))) 1116 (concat tfile-base ".toda"))))
1142 (if (not (file-exists-p afile)) 1117 (if (not (file-exists-p afile))
1143 (message "There is currently no Todos archive for this file.") 1118 (message "There is currently no Todos archive for this file.")
1144 (find-file afile) 1119 (set-window-buffer (selected-window) (set-buffer
1145 (todos-archive-mode) 1120 (find-file-noselect afile)))
1146 (unless (string= todos-current-todos-file afile)
1147 (setq todos-current-todos-file afile)
1148 (setq todos-categories nil))
1149 (unless todos-categories
1150 (setq todos-categories (todos-make-categories-list))
1151 (setq todos-category-number 1))
1152 (todos-category-select)))) 1121 (todos-category-select))))
1153 1122
1154(defun todos-choose-archive () 1123(defun todos-choose-archive ()
1155 "Choose an archive and visit it." 1124 "Choose an archive and visit it."
1156 (interactive) 1125 (interactive)
1157 (todos-switch-to-archive t)) 1126 (todos-show-archive t))
1158 1127
1159(defun todos-highlight-item () 1128(defun todos-highlight-item ()
1160 "Highlight the todo item the cursor is on." 1129 "Highlight the todo item the cursor is on."
@@ -1163,34 +1132,89 @@ displayed."
1163 (hl-line-mode 0) 1132 (hl-line-mode 0)
1164 (hl-line-mode 1))) 1133 (hl-line-mode 1)))
1165 1134
1166;; FIXME: make this a customizable option for whole Todos file 1135(defun todos-toggle-display-date-time (&optional all)
1167(defun todos-toggle-display-date-time () 1136 "Hide or show date/time of todo items in current category.
1168 "" 1137With non-nil prefix argument ALL do this in the whole file."
1169 (interactive) 1138 (interactive "P")
1170 (save-excursion 1139 (save-excursion
1171 (goto-char (point-min)) 1140 (save-restriction
1172 (let ((ovs (overlays-in (point) (line-end-position))) 1141 (goto-char (point-min))
1173 ov hidden) 1142 (let ((ovs (overlays-in (point) (1+ (point))))
1174 (while ovs 1143 ov hidden)
1175 (setq ov (car ovs)) 1144 (while ovs
1176 (if (equal (overlay-get ov 'display) "") 1145 (setq ov (pop ovs))
1177 (setq ovs nil 1146 (if (equal (overlay-get ov 'display) "")
1178 hidden t) 1147 (setq ovs nil hidden t)))
1179 (setq ovs (cdr ovs)))) 1148 (when all (widen) (goto-char (point-min)))
1180 (if hidden (remove-overlays (point-min) (point-max) 'display "") 1149 (if hidden
1181 (while (not (eobp)) 1150 (remove-overlays (point-min) (point-max) 'display "")
1182 (re-search-forward (concat todos-date-string-start todos-date-pattern 1151 (while (not (eobp))
1183 "\\( " diary-time-regexp "\\)?\\]? ") 1152 (when (re-search-forward
1184 ; FIXME: this space in header? ^ 1153 (concat todos-date-string-start todos-date-pattern
1185 nil t) 1154 "\\( " diary-time-regexp "\\)?"
1186 ;; FIXME: wrong match data if search fails 1155 (regexp-quote todos-nondiary-end) "? ")
1187 (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t)) 1156 nil t)
1188 (overlay-put ov 'display "") 1157 (unless (save-match-data (todos-done-item-p))
1189 (forward-line)))))) 1158 (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t))
1159 (overlay-put ov 'display "")))
1160 (todos-forward-item)))))))
1161
1162(defun todos-toggle-mark-item (&optional n all)
1163 "Mark item at point if unmarked, or unmark it if marked.
1164
1165With a positive numerical prefix argument N, change the
1166markedness of the next N items. With non-nil argument ALL, mark
1167all visible items in the category (depending on visibility, all
1168todo and done items, or just todo or just done items).
1169
1170The mark is the character \"*\" inserted in front of the item's
1171priority number or the `todos-prefix' string; if `todos-prefix'
1172is \"*\", then the mark is \"@\"."
1173 (interactive "p")
1174 (if all (goto-char (point-min)))
1175 (unless (> n 0) (setq n 1))
1176 (let ((i 0))
1177 (while (or (and all (not (eobp)))
1178 (< i n))
1179 (let* ((cat (todos-current-category))
1180 (ov (todos-item-marked-p))
1181 (marked (assoc cat todos-categories-with-marks)))
1182 (if (and ov (not all))
1183 (progn
1184 (delete-overlay ov)
1185 (if (= (cdr marked) 1) ; Deleted last mark in this category.
1186 (setq todos-categories-with-marks
1187 (assq-delete-all cat todos-categories-with-marks))
1188 (setcdr marked (1- (cdr marked)))))
1189 (when (todos-item-start)
1190 (unless (and all (todos-item-marked-p))
1191 (setq ov (make-overlay (point) (point)))
1192 (overlay-put ov 'before-string todos-item-mark)
1193 (if marked
1194 (setcdr marked (1+ (cdr marked)))
1195 (push (cons cat 1) todos-categories-with-marks))))))
1196 (todos-forward-item)
1197 (setq i (1+ i)))))
1190 1198
1191(defun todos-update-merged-files () 1199(defun todos-mark-category ()
1192 "" 1200 "Put the \"*\" mark on all items in this category.
1201\(If `todos-prefix' is \"*\", then the mark is \"@\".)"
1193 (interactive) 1202 (interactive)
1203 (todos-toggle-mark-item 0 t))
1204
1205(defun todos-unmark-category ()
1206 "Remove the \"*\" mark from all items in this category.
1207\(If `todos-prefix' is \"*\", then the mark is \"@\".)"
1208 (interactive)
1209 (remove-overlays (point-min) (point-max) 'before-string todos-item-mark)
1210 (setq todos-categories-with-marks
1211 (delq (assoc (todos-current-category) todos-categories-with-marks)
1212 todos-categories-with-marks)))
1213
1214(defun todos-update-merged-files ()
1215 "Interactively add files to or remove from `todos-merged-files'.
1216You can also customize `todos-merged-files' directly."
1217 (interactive) ;FIXME
1194 (let ((files (funcall todos-files-function))) 1218 (let ((files (funcall todos-files-function)))
1195 (dolist (f files) 1219 (dolist (f files)
1196 (if (member f todos-merged-files) 1220 (if (member f todos-merged-files)
@@ -1205,46 +1229,144 @@ displayed."
1205 (append todos-merged-files (list f))))))) 1229 (append todos-merged-files (list f)))))))
1206 (customize-save-variable 'todos-merged-files todos-merged-files)) 1230 (customize-save-variable 'todos-merged-files todos-merged-files))
1207 1231
1208(defun todos-top-priorities (&optional num merge) ;FIXME: rename b/c of diary items 1232(defvar todos-top-priorities-widgets nil
1209 "List top priorities for each category. 1233 "Widget placeholder used by `todos-set-top-priorities'.
1234This variable temporarily holds user changed values which are
1235saved to `todos-priorities-rules'.")
1210 1236
1211Number of entries for each category is given by NUM which 1237(defun todos-set-top-priorities ()
1212defaults to \'todos-show-priorities\'. With non-nil argument 1238 ""
1239 (interactive)
1240 (let ((buf (get-buffer-create "*Todos Top Priorities*"))
1241 (files (funcall todos-files-function))
1242 file frules cats fwidget cwidgets rules)
1243 (with-current-buffer buf
1244 (let ((inhibit-read-only t))
1245 (erase-buffer))
1246 (remove-overlays)
1247 (kill-all-local-variables)
1248 (setq todos-top-priorities-widgets nil)
1249 (dolist (f files)
1250 (with-temp-buffer
1251 (insert-file-contents f)
1252 (setq file (file-name-sans-extension (file-name-nondirectory f))
1253 frules (assoc file todos-priorities-rules)
1254 cats (mapcar 'car (todos-set-categories))))
1255 (setq fwidget
1256 (widget-create 'editable-field
1257 :size 2
1258 :value (or (and frules (cadr frules))
1259 "")
1260 :tag file
1261 :format " %v : %t\n"))
1262 (dolist (c cats)
1263 (let ((tp-num (cdr (assoc c cats)))
1264 cwidget)
1265 (widget-insert " ")
1266 (setq cwidget (widget-create 'editable-field
1267 :size 2
1268 :value (or tp-num "")
1269 :tag c
1270 :format " %v : %t\n"))
1271 (push cwidget cwidgets)))
1272 (push (cons fwidget cwidgets) todos-top-priorities-widgets))
1273 (widget-insert "\n\n")
1274 (widget-create 'push-button
1275 :notify (lambda (widget &rest ignore)
1276 (kill-buffer))
1277 "Cancel")
1278 (widget-insert " ")
1279 (widget-create 'push-button
1280 :notify (lambda (&rest ignore)
1281 (let ((widgets todos-top-priorities-widgets)
1282 (rules todos-priorities-rules)
1283 tp-cats)
1284 (setq rules nil)
1285 (dolist (w widgets)
1286 (let* ((fwid (car w))
1287 (cwids (cdr w))
1288 (fname (widget-get fwid :tag))
1289 (fval (widget-value fwid)))
1290 (dolist (c cwids)
1291 (let ((cat (widget-get c :tag))
1292 (cval (widget-value c)))
1293 (push (cons cat cval) tp-cats)))
1294 (push (list fname fval tp-cats) rules)))
1295 (setq todos-priorities-rules rules)
1296 (customize-save-variable 'todos-priorities-rules
1297 todos-priorities-rules)))
1298 "Apply")
1299 (use-local-map widget-keymap)
1300 (widget-setup))
1301 (set-window-buffer (selected-window) (set-buffer buf))))
1302
1303(defun todos-filter-items (&optional filter merge)
1304 "Display a filtered list of items from different categories.
1305
1306The special items are either the first NUM items (the top priority items) or the items marked as diary entries in each category of the current Todos file.
1307
1308Number of entries for each category is given by NUM, which
1309defaults to `todos-show-priorities'. With non-nil argument
1213MERGE list top priorities of all Todos files in 1310MERGE list top priorities of all Todos files in
1214`todos-merged-files'. If `todos-prompt-merged-files' is non-nil, 1311`todos-merged-files'. If `todos-prompt-merged-files' is non-nil,
1215prompt to update the list of merged files." 1312prompt to update the list of merged files."
1216 (interactive "p") 1313 (let ((num (if (consp filter) (cdr filter) todos-show-priorities))
1217 (or num (setq num todos-show-priorities)) 1314 (buf (get-buffer-create todos-tmp-buffer-name))
1218 (let ((todos-print-buffer-name todos-tmp-buffer-name)
1219 (files (list todos-current-todos-file)) 1315 (files (list todos-current-todos-file))
1220 file bufstr cat beg end done) 1316 regexp fname bufstr cat beg end done)
1221 (when merge 1317 (when merge
1222 (if (or todos-prompt-merged-files (null todos-merged-files)) 1318 ;; FIXME: same or different treatment for top priorities and other
1223 (todos-update-merged-files)) 1319 ;; filters? And what about todos-prompt-merged-files?
1224 (setq files todos-merged-files)) 1320 (setq files (if (member filter '(diary regexp custom))
1225 (if (buffer-live-p (get-buffer todos-print-buffer-name)) 1321 (or (and todos-prompt-merged-files
1226 (kill-buffer todos-print-buffer-name)) 1322 (todos-update-merged-files))
1323 todos-merged-files
1324 (todos-update-merged-files))
1325 ;; Set merged files for top priorities.
1326 (or (mapcar (lambda (f)
1327 (let ((file (car f))
1328 (val (nth 1 f)))
1329 (and val (not (zerop val))
1330 (push file files))))
1331 todos-priorities-rules)
1332 (if (y-or-n-p "Choose files for merging top priorities? ")
1333 (progn (todos-set-top-priorities) (error ""))
1334 (error "No files are set for merging top priorities"))))))
1335 (with-current-buffer buf
1336 (erase-buffer)
1337 (kill-all-local-variables)
1338 (todos-filter-items-mode))
1339 (when (eq filter 'regexp)
1340 (setq regexp (read-string "Enter a regular expression: ")))
1227 (save-current-buffer 1341 (save-current-buffer
1228 (dolist (f files) 1342 (dolist (f files)
1229 (find-file f) 1343 (setq fname (file-name-sans-extension (file-name-nondirectory f)))
1230 (todos-switch-todos-file)
1231 (setq file (file-name-sans-extension
1232 (file-name-nondirectory todos-current-todos-file)))
1233 (with-current-buffer (get-file-buffer f)
1234 (save-restriction
1235 (widen)
1236 (setq bufstr (buffer-string))))
1237 (with-temp-buffer 1344 (with-temp-buffer
1238 (insert bufstr) 1345 (insert-file-contents f)
1239 (goto-char (point-min)) 1346 (goto-char (point-min))
1347 ;; Unless the number of items to show was supplied by prefix
1348 ;; argument of caller, override `todos-show-priorities' with the
1349 ;; nonzero file-wide value from `todos-priorities-rules'.
1350 (unless (consp filter)
1351 (let ((tp-val (nth 1 (assoc fname todos-priorities-rules))))
1352 (unless (zerop (length tp-val))
1353 (setq num (string-to-number tp-val)))))
1240 (unless (looking-at (concat "^" (regexp-quote todos-category-beg))) 1354 (unless (looking-at (concat "^" (regexp-quote todos-category-beg)))
1241 (kill-line 1)) 1355 (kill-line 1))
1242 (while (re-search-forward 1356 (while (re-search-forward
1243 (concat "^" (regexp-quote todos-category-beg) "\\(.+\\)\n") 1357 (concat "^" (regexp-quote todos-category-beg) "\\(.+\\)\n")
1244 nil t) 1358 nil t)
1245 (setq cat (match-string 1)) 1359 (setq cat (match-string 1))
1360 ;; Unless the number of items to show was supplied by prefix
1361 ;; argument of caller, override `todos-show-priorities' with the
1362 ;; nonzero category-wide value from `todos-priorities-rules'.
1363 (unless (consp filter)
1364 (let* ((cats (nth 2 (assoc fname todos-priorities-rules)))
1365 (tp-val (cdr (assoc cat cats))))
1366 (unless (zerop (length tp-val))
1367 (setq num (string-to-number tp-val)))))
1246 (delete-region (match-beginning 0) (match-end 0)) 1368 (delete-region (match-beginning 0) (match-end 0))
1247 (setq beg (point)) ;Start of first entry. 1369 (setq beg (point)) ; Start of first entry.
1248 (setq end (if (re-search-forward 1370 (setq end (if (re-search-forward
1249 (concat "^" (regexp-quote todos-category-beg)) nil t) 1371 (concat "^" (regexp-quote todos-category-beg)) nil t)
1250 (match-beginning 0) 1372 (match-beginning 0)
@@ -1257,140 +1379,212 @@ prompt to update the list of merged files."
1257 end)) 1379 end))
1258 (delete-region done end) 1380 (delete-region done end)
1259 (setq end done) 1381 (setq end done)
1260 (narrow-to-region beg end) ;In case we have too few entries. 1382 (narrow-to-region beg end) ; Process current category.
1261 (goto-char (point-min)) 1383 (goto-char (point-min))
1262 (cond ((< num 0) ; get only diary items 1384 ;; Apply the filter.
1385 (cond ((eq filter 'diary)
1263 (while (not (eobp)) 1386 (while (not (eobp))
1264 (if (looking-at (regexp-quote todos-nondiary-start)) 1387 (if (looking-at (regexp-quote todos-nondiary-start))
1265 (todos-remove-item) 1388 (todos-remove-item)
1266 (todos-forward-item)))) 1389 (todos-forward-item))))
1267 ((zerop num) ; keep all items 1390 ((eq filter 'regexp)
1268 (goto-char end)) 1391 (while (not (eobp))
1269 (t 1392 (if (string-match regexp (todos-item-string))
1393 (todos-forward-item)
1394 (todos-remove-item))))
1395 ((eq filter 'custom)
1396 (if todos-filter-function
1397 (funcall todos-filter-function)
1398 (error "No custom filter function has been defined")))
1399 (t ; Filter top priority items.
1270 (todos-forward-item num))) 1400 (todos-forward-item num)))
1271 (setq beg (point)) 1401 (setq beg (point))
1272 (if (>= num 0) (delete-region beg end)) 1402 (unless (member filter '(diary regexp custom))
1273 (goto-char (point-min)) 1403 (delete-region beg end))
1274 (while (not (eobp)) 1404 (goto-char (point-min))
1275 (when (re-search-forward (concat todos-date-string-start 1405 ;; Add file (if using merged files) and category tags to item.
1276 todos-date-pattern 1406 (while (not (eobp))
1277 "\\( " diary-time-regexp "\\)?\\]?") 1407 (when (re-search-forward
1278 nil t) 1408 (concat todos-date-string-start todos-date-pattern
1279 (insert (concat " [" (if merge (concat file ":")) cat "]"))) 1409 "\\( " diary-time-regexp "\\)?"
1280 (forward-line)) 1410 (regexp-quote todos-nondiary-end) "?")
1281 (widen)) 1411 nil t)
1282 (append-to-buffer todos-print-buffer-name (point-min) (point-max))))) 1412 (insert (concat " [" (if merge (concat fname ":")) cat "]")))
1283 (with-current-buffer todos-print-buffer-name 1413 (forward-line))
1284 (todos-prefix-overlays) 1414 (widen))
1285 (todos-top-priorities-mode) 1415 (setq bufstr (buffer-string))
1286 (goto-char (point-min)) ;Due to display buffer 1416 (with-current-buffer buf
1287 ;; (make-local-variable 'font-lock-defaults) 1417 (let (buffer-read-only)
1288 ;; (setq font-lock-defaults '(todos-font-lock-keywords t)) 1418 (insert bufstr))))))
1289 (font-lock-fontify-buffer)) 1419 (set-window-buffer (selected-window) (set-buffer buf))
1290 ;; (setq buffer-read-only t)) 1420 (todos-prefix-overlays)
1291 ;; Could have used switch-to-buffer as it has a norecord argument, 1421 (goto-char (point-min))
1292 ;; which is nice when we are called from e.g. todos-print. 1422 ;; FIXME: this is necessary -- why?
1293 ;; Else we could have used pop-to-buffer. 1423 (font-lock-fontify-buffer)))
1294 (display-buffer todos-print-buffer-name) 1424
1295 (message "Type C-x 1 to remove %s window. M-C-v to scroll the help." 1425(defun todos-top-priorities (&optional num)
1296 todos-print-buffer-name))) 1426 "List top priorities of each category in `todos-merged-files'.
1427Number of entries for each category is given by NUM, which
1428defaults to `todos-show-priorities'."
1429 (interactive "p")
1430 (let ((arg (if num (cons 'top num) 'top)))
1431 (todos-filter-items arg)))
1297 1432
1298(defun todos-merged-top-priorities (&optional num) 1433(defun todos-merged-top-priorities (&optional num)
1299 "" 1434 "List top priorities of each category in `todos-merged-files'.
1435Number of entries for each category is given by NUM, which
1436defaults to `todos-show-priorities'."
1300 (interactive "p") 1437 (interactive "p")
1301 (todos-top-priorities num t)) 1438 (let ((arg (if num (cons 'top num) 'top)))
1439 (todos-filter-items arg t)))
1302 1440
1303(defun todos-diary-items (&optional merge) 1441(defun todos-diary-items ()
1304 "Display todo items marked for diary inclusion. 1442 "Display todo items for diary inclusion in this Todos file."
1305The items are those in the current Todos file, or with prefix 1443 (interactive)
1306argument MERGE those in all Todos files in `todos-merged-files'." 1444 (todos-filter-items 'diary))
1307 (interactive "P") 1445
1308 (todos-top-priorities -1 merge)) 1446(defun todos-merged-diary-items ()
1447 "Display todo items for diary inclusion in one or more Todos file.
1448The files are those listed in `todos-merged-files'."
1449 (interactive)
1450 (todos-filter-items 'diary t))
1451
1452(defun todos-regexp-items ()
1453 "Display todo items matching a user-entered regular expression.
1454The items are those in the current Todos file."
1455 (interactive)
1456 (todos-filter-items 'regexp))
1457
1458(defun todos-merged-regexp-items ()
1459 "Display todo items matching a user-entered regular expression.
1460The items are those in the files listed in `todos-merged-files'."
1461 (interactive)
1462 (todos-filter-items 'regexp t))
1463
1464(defun todos-custom-items ()
1465 "Display todo items filtered by `todos-filter-function'.
1466The items are those in the current Todos file."
1467 (interactive)
1468 (todos-filter-items 'custom))
1469
1470(defun todos-merged-custom-items ()
1471 "Display todo items filtered by `todos-filter-function'.
1472The items are those in the files listed in `todos-merged-files'."
1473 (interactive)
1474 (todos-filter-items 'custom t))
1309 1475
1310;;; Navigation 1476;;; Navigation
1311 1477
1312(defun todos-forward-category () 1478(defun todos-forward-category (&optional back)
1313 "Go forward to TODO list of next category." 1479 "Visit the numerically next category in this Todos file.
1480With non-nil argument BACK, visit the numerically previous
1481category."
1314 (interactive) 1482 (interactive)
1315 (setq todos-category-number 1483 (setq todos-category-number
1316 (1+ (mod todos-category-number (length todos-categories)))) 1484 (1+ (mod (- todos-category-number (if back 2 0))
1485 (length todos-categories))))
1317 (todos-category-select) 1486 (todos-category-select)
1318 (goto-char (point-min))) 1487 (goto-char (point-min)))
1319 1488
1320(defun todos-backward-category () 1489(defun todos-backward-category ()
1321 "Go back to TODO list of previous category." 1490 "Visit the numerically previous category in this Todos file."
1322 (interactive) 1491 (interactive)
1323 (setq todos-category-number 1492 (todos-forward-category t))
1324 (1+ (mod (- todos-category-number 2) (length todos-categories))))
1325 (todos-category-select)
1326 (goto-char (point-min)))
1327 1493
1328;; FIXME: Document that a non-existing name creates that category, and add 1494;; FIXME: autoload?
1329;; y-or-n-p confirmation -- or eliminate this possibility?
1330(defun todos-jump-to-category (&optional cat other-file) 1495(defun todos-jump-to-category (&optional cat other-file)
1331 "Jump to a category in a Todos file. 1496 "Jump to a category in this or another Todos file.
1332When called interactively, prompt for the category. 1497Optional argument CAT provides the category name. Otherwise,
1333Non-interactively, the argument CAT provides the category. With 1498prompt for the category, with TAB completion on existing
1334non-nil argument OTHER-FILE, prompt for a Todos file, otherwise 1499categories. If a non-existing category name is entered, ask
1335stay with the current Todos file. See also 1500whether to add a new category with this name, if affirmed, do so,
1336`todos-jump-to-category-other-file'." 1501then jump to that category. With non-nil argument OTHER-FILE,
1502prompt for a Todos file, otherwise jump within the current Todos
1503file."
1337 (interactive) 1504 (interactive)
1338 (when (or (and other-file 1505 (let ((file (or (and other-file
1339 (setq todos-current-todos-file 1506 (todos-read-file-name "Choose a Todos file: " nil t))
1340 (todos-read-file-name "Choose a Todos file: "))) 1507 ;; Jump to archived-only Categories from Todos Categories mode.
1341 (and cat 1508 (and cat
1342 todos-ignore-archived-categories 1509 todos-ignore-archived-categories
1343 (zerop (todos-get-count 'todo cat)) 1510 (zerop (todos-get-count 'todo cat))
1344 (zerop (todos-get-count 'done cat)) 1511 (zerop (todos-get-count 'done cat))
1345 (not (zerop (todos-get-count 'archived cat))) 1512 (not (zerop (todos-get-count 'archived cat)))
1346 (setq todos-current-todos-file 1513 (concat (file-name-sans-extension
1347 (concat (file-name-sans-extension todos-current-todos-file) 1514 todos-current-todos-file) ".toda"))
1348 ".toda")))) 1515 todos-current-todos-file
1349 (with-current-buffer (find-file-noselect todos-current-todos-file) 1516 ;; If invoked from outside of Todos mode before todos-show...
1350 ;; (or (eq major-mode 'todos-mode) (todos-mode)) 1517 todos-default-todos-file)))
1351 (setq todos-categories (todos-make-categories-list)))) 1518 (with-current-buffer (find-file-noselect file)
1352 (let ((category (or (and (assoc cat todos-categories) cat) 1519 (and other-file (setq todos-current-todos-file file))
1353 (todos-read-category "Jump to category: ")))) 1520 (let ((category (or (and (assoc cat todos-categories) cat)
1354 (if (string= "" category) 1521 (todos-read-category "Jump to category: "))))
1355 (setq category (todos-current-category))) 1522 ;; ;; FIXME: why is this needed?
1356 (if (string= (buffer-name) todos-categories-buffer) 1523 ;; (if (string= "" category)
1357 (kill-buffer)) 1524 ;; (setq category (todos-current-category)))
1358 (if (or cat other-file) 1525 ;; Clean up after selecting category in Todos Categories mode.
1359 (switch-to-buffer (get-file-buffer todos-current-todos-file))) 1526 (if (string= (buffer-name) todos-categories-buffer)
1360 (setq todos-category-number 1527 (kill-buffer))
1361 (or (todos-category-number category) 1528 (if (or cat other-file)
1362 (todos-add-category category))) 1529 (set-window-buffer (selected-window)
1363 (todos-category-select) 1530 (set-buffer (get-file-buffer file))))
1364 (goto-char (point-min)))) 1531 (unless todos-global-current-todos-file
1532 (setq todos-global-current-todos-file todos-current-todos-file))
1533 (todos-category-number category)
1534 (if (> todos-category-number (length todos-categories))
1535 (setq todos-category-number (todos-add-category category)))
1536 (todos-category-select)
1537 (goto-char (point-min))))))
1365 1538
1366(defun todos-jump-to-category-other-file () 1539(defun todos-jump-to-category-other-file ()
1367 "" 1540 "Jump to a category in another Todos file.
1541The category is chosen by prompt, with TAB completion."
1368 (interactive) 1542 (interactive)
1369 (todos-jump-to-category nil t)) 1543 (todos-jump-to-category nil t))
1370 1544
1371;; FIXME ? todos-{backward,forward}-item skip over empty line between done and 1545;; FIXME ? disallow prefix arg value < 1 (re-search-* allows these)
1372;; not done items (but todos-forward-item gets there when done items are not 1546(defun todos-forward-item (&optional count)
1373;; displayed). Also disallow prefix arg value < 1 (re-search-* allows these) 1547 "Move point down to start of item with next lower priority.
1374(defun todos-backward-item (&optional count) 1548With numerical prefix COUNT, move point COUNT items downward,"
1375 "Select COUNT-th previous entry of TODO list."
1376 (interactive "P") 1549 (interactive "P")
1377 ;; FIXME ? this moves to bob if on the first item (but so does previous-line) 1550 (let* ((not-done (not (or (todos-done-item-p) (looking-at "^$"))))
1378 (todos-item-start) 1551 (start (line-end-position)))
1379 (unless (bobp) 1552 (goto-char start)
1380 (re-search-backward todos-item-start nil t (or count 1)))) 1553 (if (re-search-forward todos-item-start nil t (or count 1))
1554 (goto-char (match-beginning 0))
1555 (goto-char (point-max)))
1556 ;; If points advances by one from a todo to a done item, go back to the
1557 ;; space above todos-done-separator, since that is a legitimate place to
1558 ;; insert an item. But skip this space if count > 1, since that should
1559 ;; only stop on an item (FIXME: or not?)
1560 (when (and not-done (todos-done-item-p))
1561 (if (or (not count) (= count 1))
1562 (re-search-backward "^$" start t)))))
1381 1563
1382(defun todos-forward-item (&optional count) 1564(defun todos-backward-item (&optional count)
1383 "Select COUNT-th next entry of TODO list." 1565 "Move point up to start of item with next higher priority.
1566With numerical prefix COUNT, move point COUNT items upward,"
1384 (interactive "P") 1567 (interactive "P")
1385 (goto-char (line-end-position)) 1568 (let* ((done (todos-done-item-p)))
1386 (if (re-search-forward todos-item-start nil t (or count 1)) 1569 ;; FIXME ? this moves to bob if on the first item (but so does previous-line)
1387 (goto-char (match-beginning 0)) 1570 (todos-item-start)
1388 (goto-char (point-max)))) 1571 (unless (bobp)
1572 (re-search-backward todos-item-start nil t (or count 1)))
1573 ;; If points advances by one from a done to a todo item, go back to the
1574 ;; space above todos-done-separator, since that is a legitimate place to
1575 ;; insert an item. But skip this space if count > 1, since that should
1576 ;; only stop on an item (FIXME: or not?)
1577 (when (and done (not (todos-done-item-p))
1578 (or (not count) (= count 1)))
1579 (re-search-forward (concat "^" (regexp-quote todos-category-done)) nil t)
1580 (forward-line -1))))
1389 1581
1390(defun todos-search () 1582(defun todos-search ()
1391 "Perform a search for a regular expression, with repetition. 1583 "Search for a regular expression in this Todos file.
1392The search encompasses all todo and done items within the current Todos file; it excludes category names. Matches are highlighted 1584The search runs through the whole file and encompasses all and
1393" 1585only todo and done items; it excludes category names. Multiple
1586matches are shown sequentially, highlighted in `todos-search'
1587face."
1394 (interactive) 1588 (interactive)
1395 (let ((regex (read-from-minibuffer "Enter a search string (regexp): ")) 1589 (let ((regex (read-from-minibuffer "Enter a search string (regexp): "))
1396 (opoint (point)) 1590 (opoint (point))
@@ -1418,7 +1612,8 @@ The search encompasses all todo and done items within the current Todos file; it
1418 (setq cat (match-string-no-properties 1)) 1612 (setq cat (match-string-no-properties 1))
1419 (todos-category-number cat) 1613 (todos-category-number cat)
1420 (todos-category-select) 1614 (todos-category-select)
1421 (if in-done (unless todos-show-with-done (todos-toggle-view-done-items))) 1615 (if in-done
1616 (unless todos-show-with-done (todos-toggle-view-done-items)))
1422 (goto-char match) 1617 (goto-char match)
1423 (setq ov (make-overlay (- (point) (length regex)) (point))) 1618 (setq ov (make-overlay (- (point) (length regex)) (point)))
1424 (overlay-put ov 'face 'todos-search) 1619 (overlay-put ov 'face 'todos-search)
@@ -1426,11 +1621,13 @@ The search encompasses all todo and done items within the current Todos file; it
1426 (setq mlen (length matches)) 1621 (setq mlen (length matches))
1427 (if (y-or-n-p 1622 (if (y-or-n-p
1428 (if (> mlen 1) 1623 (if (> mlen 1)
1429 (format "There are %d more matches; go to next match? " mlen) 1624 (format "There are %d more matches; go to next match? "
1625 mlen)
1430 "There is one more match; go to it? ")) 1626 "There is one more match; go to it? "))
1431 (widen) 1627 (widen)
1432 (throw 'stop (setq msg (if (> mlen 1) 1628 (throw 'stop (setq msg (if (> mlen 1)
1433 (format "There are %d more matches." mlen) 1629 (format "There are %d more matches."
1630 mlen)
1434 "There is one more match.")))))) 1631 "There is one more match."))))))
1435 (setq msg "There are no more matches.")) 1632 (setq msg "There are no more matches."))
1436 (todos-category-select) 1633 (todos-category-select)
@@ -1444,19 +1641,21 @@ The search encompasses all todo and done items within the current Todos file; it
1444 'todos-clear-matches)))))))) 1641 'todos-clear-matches))))))))
1445 1642
1446(defun todos-clear-matches () 1643(defun todos-clear-matches ()
1447 "Removing highlighting on matches found by todos-search." 1644 "Remove highlighting on matches found by todos-search."
1448 (interactive) 1645 (interactive)
1449 (remove-overlays 1 (1+ (buffer-size)) 'face 'todos-search)) 1646 (remove-overlays 1 (1+ (buffer-size)) 'face 'todos-search))
1450 1647
1451;;; Editing 1648;;; Editing
1452 1649
1453(defun todos-add-file (&optional arg) 1650(defun todos-add-file ()
1454 "" 1651 "Name and add a new Todos file.
1455 (interactive "p") 1652Interactively, prompt for a category and display it.
1653Noninteractively, return the name of the new file."
1654 (interactive)
1456 (let ((default-file (if todos-default-todos-file 1655 (let ((default-file (if todos-default-todos-file
1457 (file-name-sans-extension 1656 (file-name-sans-extension
1458 (file-name-nondirectory todos-default-todos-file)))) 1657 (file-name-nondirectory todos-default-todos-file))))
1459 file prompt) 1658 file prompt shortname)
1460 (while 1659 (while
1461 (and 1660 (and
1462 (cond 1661 (cond
@@ -1468,36 +1667,49 @@ The search encompasses all todo and done items within the current Todos file; it
1468 ((string-match "\\`\\s-+\\'" file) 1667 ((string-match "\\`\\s-+\\'" file)
1469 (setq prompt "Enter a name that is not only white space: "))) 1668 (setq prompt "Enter a name that is not only white space: ")))
1470 (setq file (todos-read-file-name prompt)))) 1669 (setq file (todos-read-file-name prompt))))
1471 (if (or (not default-file) 1670 (setq shortname (file-name-sans-extension (file-name-nondirectory file)))
1472 (yes-or-no-p (concat "Make %s new default Todos file " 1671 (with-current-buffer (get-buffer-create file)
1473 "[current default is \"%s\"]? ")
1474 file default-file))
1475 (todos-change-default-file file)
1476 (message "\"%s\" remains the default Todos file." default-file))
1477 (with-current-buffer (get-buffer-create todos-default-todos-file)
1478 (erase-buffer) 1672 (erase-buffer)
1479 (write-region (point-min) (point-max) todos-default-todos-file 1673 (write-region (point-min) (point-max) file nil 'nomessage nil t)
1480 nil 'nomessage nil t)) 1674 (kill-buffer file))
1481 (if arg (todos-show) file))) 1675 ;; FIXME: todos-change-default-file yields a Custom mismatch
1676 ;; (if (or (not default-file)
1677 ;; (yes-or-no-p (concat (format "Make \"%s\" new default Todos file "
1678 ;; shortname)
1679 ;; (format "[current default is \"%s\"]? "
1680 ;; default-file))))
1681 ;; (todos-change-default-file file)
1682 ;; (message "\"%s\" remains the default Todos file." default-file))
1683 (if (called-interactively-p)
1684 (progn
1685 (setq todos-current-todos-file file)
1686 (todos-show))
1687 file)))
1482 1688
1483;; FIXME: omit this and just use defcustom? 1689;; FIXME: omit this and just use defcustom? Says "changed outside of Custom
1690;; (mismatch)"
1484(defun todos-change-default-file (&optional file) 1691(defun todos-change-default-file (&optional file)
1485 "" 1692 ""
1486 (interactive) 1693 (interactive)
1487 (let ((new-default (or file 1694 (let ((new-default (or file
1488 (todos-read-file-name "Choose new default Todos file: ")))) 1695 (todos-read-file-name "Choose new default Todos file: "
1696 nil t))))
1489 (customize-save-variable 'todos-default-todos-file new-default) 1697 (customize-save-variable 'todos-default-todos-file new-default)
1490 (message "\"%s\" is new default Todos file." 1698 (message "\"%s\" is new default Todos file."
1491 (file-name-sans-extension (file-name-nondirectory new-default))))) 1699 (file-name-sans-extension (file-name-nondirectory new-default)))))
1492 1700
1493(defun todos-add-category (&optional cat) 1701(defun todos-add-category (&optional cat)
1494 "Add new category CAT to the TODO list." 1702 "Add a new category to the current Todos file.
1703Called interactively, prompt for category name, then visit the
1704category in Todos mode. Non-interactively, argument CAT provides
1705the category name, which is also the return value."
1495 (interactive) 1706 (interactive)
1496 (let* ((buffer-read-only) 1707 (let* ((buffer-read-only)
1708 ;; FIXME: check against todos-archive-done-item-or-items with empty file
1497 (buf (find-file-noselect todos-current-todos-file t)) 1709 (buf (find-file-noselect todos-current-todos-file t))
1710 ;; (buf (get-file-buffer todos-current-todos-file))
1498 (num (1+ (length todos-categories))) 1711 (num (1+ (length todos-categories)))
1499 (counts (make-vector 4 0))) ; [todo diary done archived] 1712 (counts (make-vector 4 0))) ; [todo diary done archived]
1500 ;; (counts (list 'todo 0 'diary 0 'done 0 'archived 0)))
1501 (unless (zerop (buffer-size buf)) 1713 (unless (zerop (buffer-size buf))
1502 (and (null todos-categories) 1714 (and (null todos-categories)
1503 (error "Error in %s: File is non-empty but contains no category" 1715 (error "Error in %s: File is non-empty but contains no category"
@@ -1508,23 +1720,26 @@ The search encompasses all todo and done items within the current Todos file; it
1508 (setq todos-categories (append todos-categories (list (cons cat counts)))) 1720 (setq todos-categories (append todos-categories (list (cons cat counts))))
1509 (widen) 1721 (widen)
1510 (goto-char (point-max)) 1722 (goto-char (point-max))
1511 (save-excursion ; for subsequent todos-category-select 1723 (save-excursion ; Save point for todos-category-select.
1512 (insert todos-category-beg cat "\n\n" todos-category-done "\n")) 1724 (insert todos-category-beg cat "\n\n" todos-category-done "\n"))
1513 (todos-update-categories-sexp) 1725 (todos-update-categories-sexp)
1514 (if (called-interactively-p 'any) ; FIXME 1726 ;; If called by command, display the newly added category, else return
1515 ;; properly display the newly added category 1727 ;; the category number to the caller.
1728 (if (called-interactively-p 'any) ; FIXME?
1516 (progn 1729 (progn
1517 (setq todos-category-number num) 1730 (setq todos-category-number num)
1518 (todos-category-select)) 1731 (todos-category-select))
1519 num)))) 1732 num))))
1520 1733
1521(defun todos-rename-category () 1734(defun todos-rename-category ()
1522 "Rename current Todos category." 1735 "Rename current Todos category.
1736If this file has an archive containing this category, rename the
1737category there as well."
1523 (interactive) 1738 (interactive)
1524 (let* ((cat (todos-current-category)) 1739 (let* ((cat (todos-current-category))
1525 (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat)))) 1740 (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat))))
1526 (setq new (todos-validate-category-name new)) 1741 (setq new (todos-validate-category-name new))
1527 (let* ((ofile (buffer-file-name)) 1742 (let* ((ofile todos-current-todos-file)
1528 (archive (concat (file-name-sans-extension ofile) ".toda")) 1743 (archive (concat (file-name-sans-extension ofile) ".toda"))
1529 (buffers (append (list ofile) 1744 (buffers (append (list ofile)
1530 (unless (zerop (todos-get-count 'archived cat)) 1745 (unless (zerop (todos-get-count 'archived cat))
@@ -1532,57 +1747,63 @@ The search encompasses all todo and done items within the current Todos file; it
1532 (dolist (buf buffers) 1747 (dolist (buf buffers)
1533 (with-current-buffer (find-file-noselect buf) 1748 (with-current-buffer (find-file-noselect buf)
1534 (let (buffer-read-only) 1749 (let (buffer-read-only)
1535 ;; (setq todos-categories (if (string= buf archive) 1750 (setq todos-categories (todos-set-categories))
1536 ;; (todos-make-categories-list t)
1537 ;; todos-categories))
1538 (todos-set-categories)
1539 (save-excursion 1751 (save-excursion
1540 (save-restriction 1752 (save-restriction
1541 (setcar (assoc cat todos-categories) new) 1753 (setcar (assoc cat todos-categories) new)
1542 (widen) 1754 (widen)
1543 (goto-char (point-min)) 1755 (goto-char (point-min))
1544 (todos-update-categories-sexp) 1756 (todos-update-categories-sexp)
1545 (re-search-forward (concat (regexp-quote todos-category-beg) "\\(" 1757 (re-search-forward (concat (regexp-quote todos-category-beg)
1546 (regexp-quote cat) "\\)\n") nil t) 1758 "\\(" (regexp-quote cat) "\\)\n")
1759 nil t)
1547 (replace-match new t t nil 1))))))) 1760 (replace-match new t t nil 1)))))))
1548 (setq mode-line-buffer-identification 1761 (setq mode-line-buffer-identification
1549 (format "Category %d: %s" todos-category-number new))) 1762 (funcall todos-mode-line-function new)))
1550 (save-excursion (todos-category-select))) 1763 (save-excursion (todos-category-select)))
1551 1764
1552;; FIXME: what if cat has archived items?
1553(defun todos-delete-category (&optional arg) 1765(defun todos-delete-category (&optional arg)
1554 "Delete current Todos category provided it is empty. 1766 "Delete current Todos category provided it is empty.
1555With ARG non-nil delete the category unconditionally, 1767With ARG non-nil delete the category unconditionally,
1556i.e. including all existing entries." 1768i.e. including all existing todo and done items."
1557 (interactive "P") 1769 (interactive "P")
1558 (let* ((cat (todos-current-category)) 1770 (let* ((cat (todos-current-category))
1559 (todo (todos-get-count 'todo cat)) 1771 (todo (todos-get-count 'todo cat))
1560 (done (todos-get-count 'done cat))) 1772 (done (todos-get-count 'done cat))
1773 (archived (todos-get-count 'archived cat)))
1561 (if (and (not arg) 1774 (if (and (not arg)
1562 (or (> todo 0) (> done 0))) 1775 (or (> todo 0) (> done 0)))
1563 (message "To delete a non-empty category, type C-u D.") 1776 (message "To delete a non-empty category, type C-u D.")
1564 (when (y-or-n-p (concat "Permanently remove category \"" cat 1777 (when (yes-or-no-p (concat "Permanently remove category \"" cat
1565 "\"" (and arg " and all its entries") "? ")) 1778 "\"" (and arg " and all its entries") "? "))
1566 (widen) 1779 ;; FIXME ? optionally delete archived category as well?
1567 (let ((buffer-read-only) 1780 (when (and archived
1568 (beg (re-search-backward 1781 (y-or-n-p (concat "This category has archived items; "
1569 (concat "^" (regexp-quote (concat todos-category-beg cat)) 1782 "the archived category will remain\n"
1570 "\n") nil t)) 1783 "after deleting the todo category. "
1571 (end (if (re-search-forward 1784 "Do you still want to delete it\n"
1572 (concat "\n\\(" (regexp-quote todos-category-beg) 1785 "(see 'todos-ignore-archived-categories' "
1573 ".*\n\\)") nil t) 1786 "for another option)? ")))
1574 (match-beginning 1) 1787 (widen)
1575 (point-max)))) 1788 (let ((buffer-read-only)
1576 (remove-overlays beg end) 1789 (beg (re-search-backward
1577 (delete-region beg end) 1790 (concat "^" (regexp-quote (concat todos-category-beg cat))
1578 (setq todos-categories (delete (assoc cat todos-categories) 1791 "\n") nil t))
1579 todos-categories)) 1792 (end (if (re-search-forward
1580 (todos-update-categories-sexp) 1793 (concat "\n\\(" (regexp-quote todos-category-beg)
1581 (setq todos-category-number 1794 ".*\n\\)") nil t)
1582 (1+ (mod todos-category-number (length todos-categories)))) 1795 (match-beginning 1)
1583 (todos-category-select) 1796 (point-max))))
1584 (goto-char (point-min)) 1797 (remove-overlays beg end)
1585 (message "Deleted category %s" cat)))))) 1798 (delete-region beg end)
1799 (setq todos-categories (delete (assoc cat todos-categories)
1800 todos-categories))
1801 (todos-update-categories-sexp)
1802 (setq todos-category-number
1803 (1+ (mod todos-category-number (length todos-categories))))
1804 (todos-category-select)
1805 (goto-char (point-min))
1806 (message "Deleted category %s" cat)))))))
1586 1807
1587(defun todos-raise-category (&optional lower) 1808(defun todos-raise-category (&optional lower)
1588 "Raise priority of category point is on in Categories buffer. 1809 "Raise priority of category point is on in Categories buffer.
@@ -1606,7 +1827,7 @@ With non-nil argument LOWER, lower the category's priority."
1606 (cat2-list (aref catvec num2)) 1827 (cat2-list (aref catvec num2))
1607 (cat1 (car cat1-list)) 1828 (cat1 (car cat1-list))
1608 (cat2 (car cat2-list)) 1829 (cat2 (car cat2-list))
1609 (buffer-read-only)) 1830 buffer-read-only newcats)
1610 (delete-region beg end) 1831 (delete-region beg end)
1611 (setq num1 (1+ num1)) 1832 (setq num1 (1+ num1))
1612 (setq num2 (1- num2)) 1833 (setq num2 (1- num2))
@@ -1617,7 +1838,9 @@ With non-nil argument LOWER, lower the category's priority."
1617 (aset catvec num2 (cons cat2 (cdr cat2-list))) 1838 (aset catvec num2 (cons cat2 (cdr cat2-list)))
1618 (aset catvec num1 (cons cat1 (cdr cat1-list))) 1839 (aset catvec num1 (cons cat1 (cdr cat1-list)))
1619 (setq todos-categories (append catvec nil)) 1840 (setq todos-categories (append catvec nil))
1841 (setq newcats todos-categories)
1620 (with-current-buffer (get-file-buffer todos-current-todos-file) 1842 (with-current-buffer (get-file-buffer todos-current-todos-file)
1843 (setq todos-categories newcats)
1621 (todos-update-categories-sexp)) 1844 (todos-update-categories-sexp))
1622 (forward-line (if lower -1 -2)) 1845 (forward-line (if lower -1 -2))
1623 (forward-char col))))) 1846 (forward-char col)))))
@@ -1627,95 +1850,118 @@ With non-nil argument LOWER, lower the category's priority."
1627 (interactive) 1850 (interactive)
1628 (todos-raise-category t)) 1851 (todos-raise-category t))
1629 1852
1630;; FIXME: use save-restriction?
1631(defun todos-move-category () 1853(defun todos-move-category ()
1632 "Move current category to a different Todos file. 1854 "Move current category to a different Todos file.
1633If current category has archived items, also move those to the 1855If current category has archived items, also move those to the
1634archive of the file moved to, creating it if it does not exist." 1856archive of the file moved to, creating it if it does not exist."
1635 (interactive) 1857 (interactive)
1636 ;; FIXME: warn if only category in file? If so, delete file after moving category
1637 (when (or (> (length todos-categories) 1) 1858 (when (or (> (length todos-categories) 1)
1638 (y-or-n-p (concat "This is the only category in this file; " 1859 (y-or-n-p (concat "This is the only category in this file; "
1639 "moving it will delete the file.\n" 1860 "moving it will also delete the file.\n"
1640 "Do you want to proceed? "))) 1861 "Do you want to proceed? ")))
1641 (let* ((ofile (buffer-file-name)) 1862 (let* ((ofile todos-current-todos-file)
1642 (cat (todos-current-category)) 1863 (cat (todos-current-category))
1643 ;; FIXME: check if cat exists in nfile, and if so rename it 1864 (nfile (todos-read-file-name "Choose a Todos file: " nil t))
1644 (nfile (todos-read-file-name "Choose a Todos file: "))
1645 (archive (concat (file-name-sans-extension ofile) ".toda")) 1865 (archive (concat (file-name-sans-extension ofile) ".toda"))
1646 (buffers (append (list ofile) 1866 (buffers (append (list ofile)
1647 (unless (zerop (todos-get-count 'archived cat)) 1867 (unless (zerop (todos-get-count 'archived cat))
1648 (list archive))))) 1868 (list archive))))
1869 new)
1649 (dolist (buf buffers) 1870 (dolist (buf buffers)
1650 (with-current-buffer (find-file-noselect buf) 1871 (with-current-buffer (find-file-noselect buf)
1651 (save-excursion 1872 (widen)
1652 (save-restriction 1873 (goto-char (point-max))
1653 (widen) 1874 (let* ((beg (re-search-backward
1654 (goto-char (point-max)) 1875 (concat "^"
1655 (let ((buffer-read-only nil) 1876 (regexp-quote (concat todos-category-beg cat)))
1656 (beg (re-search-backward 1877 nil t))
1657 (concat "^" 1878 (end (if (re-search-forward
1658 (regexp-quote (concat todos-category-beg cat))) 1879 (concat "^" (regexp-quote todos-category-beg))
1659 nil t)) 1880 nil t 2)
1660 (end (if (re-search-forward 1881 (match-beginning 0)
1661 (concat "^" (regexp-quote todos-category-beg)) 1882 (point-max)))
1662 nil t 2) 1883 (content (buffer-substring-no-properties beg end))
1663 (match-beginning 0) 1884 (counts (cdr (assoc cat todos-categories)))
1664 (point-max))) 1885 buffer-read-only)
1665 (content (buffer-substring-no-properties beg end))) 1886 ;; Move the category to the new file. Also update or create
1666 (with-current-buffer 1887 ;; archive file if necessary.
1667 (find-file-noselect 1888 (with-current-buffer
1668 ;; regenerate todos-archives in case there 1889 (find-file-noselect
1669 ;; is a newly created archive 1890 ;; Regenerate todos-archives in case there
1670 (if (member buf (funcall todos-files-function t)) 1891 ;; is a newly created archive.
1671 (concat (file-name-sans-extension nfile) ".toda") 1892 (if (member buf (funcall todos-files-function t))
1672 nfile)) 1893 (concat (file-name-sans-extension nfile) ".toda")
1673 (let (buffer-read-only) 1894 nfile))
1674 (save-excursion 1895 (let* ((nfile-short (file-name-sans-extension
1675 (save-restriction 1896 (file-name-nondirectory nfile)))
1676 (widen) 1897 (prompt (concat
1677 (goto-char (point-max)) 1898 (format "Todos file \"%s\" already has "
1678 (insert content) 1899 nfile-short)
1679 (goto-char (point-min)) 1900 (format "the category \"%s\";\n" cat)
1680 (if (zerop (buffer-size)) 1901 "enter a new category name: "))
1681 (progn 1902 buffer-read-only)
1682 (set-buffer-modified-p nil) ; no questions 1903 (widen)
1683 (delete-file (buffer-file-name)) 1904 (goto-char (point-max))
1684 (kill-buffer)) 1905 (insert content)
1685 (unless (looking-at 1906 ;; If the file moved to has a category with the same
1686 (concat "^" (regexp-quote todos-category-beg))) 1907 ;; name, rename the moved category.
1687 (kill-whole-line)) 1908 (when (assoc cat todos-categories)
1688 (save-buffer))))) 1909 (unless (member (file-truename (buffer-file-name))
1689 (remove-overlays beg end) 1910 (funcall todos-files-function t))
1690 (delete-region beg end) 1911 (setq new (read-from-minibuffer prompt))
1691 (goto-char (point-min)) 1912 (setq new (todos-validate-category-name new))))
1692 (if (zerop (buffer-size)) 1913 ;; Replace old with new name in Todos and archive files.
1693 (progn 1914 (when new
1694 (set-buffer-modified-p nil) 1915 (goto-char (point-max))
1695 (delete-file (buffer-file-name)) 1916 (re-search-backward
1696 (kill-buffer)) 1917 (concat "^" (regexp-quote todos-category-beg)
1697 (unless (looking-at 1918 "\\(" (regexp-quote cat) "\\)") nil t)
1698 (concat "^" (regexp-quote todos-category-beg))) 1919 (replace-match new nil nil nil 1)))
1699 (kill-whole-line)) 1920 (setq todos-categories
1700 (save-buffer)))))))) 1921 (append todos-categories (list (cons new counts))))
1701 ;; (todos-switch-todos-file nfile)))) 1922 (todos-update-categories-sexp)
1702 (find-file nfile) 1923 ;; If archive was just created, save it to avoid "File <xyz> no
1703 (setq todos-current-todos-file nfile 1924 ;; longer exists!" message on invoking
1704 todos-categories (todos-make-categories-list t) 1925 ;; `todos-view-archived-items'. FIXME: maybe better to save
1705 todos-category-number (todos-category-number cat)) 1926 ;; unconditionally?
1927 (unless (file-exists-p (buffer-file-name))
1928 (save-buffer))
1929 (todos-category-number (or new cat))
1930 (todos-category-select))
1931 ;; Delete the category from the old file, and if that was the
1932 ;; last category, delete the file. Also handle archive file
1933 ;; if necessary.
1934 (remove-overlays beg end)
1935 (delete-region beg end)
1936 (goto-char (point-min))
1937 ;; Put point after todos-categories sexp.
1938 (forward-line)
1939 (if (eobp) ; Aside from sexp, file is empty.
1940 (progn
1941 ;; Skip confirming killing the archive buffer.
1942 (set-buffer-modified-p nil)
1943 (delete-file todos-current-todos-file)
1944 (kill-buffer))
1945 (setq todos-categories (delete (assoc cat todos-categories)
1946 todos-categories))
1947 (todos-update-categories-sexp)
1948 (todos-category-select)))))
1949 (set-window-buffer (selected-window)
1950 (set-buffer (find-file-noselect nfile)))
1951 (todos-category-number (or new cat))
1706 (todos-category-select)))) 1952 (todos-category-select))))
1707 1953
1708(defun todos-merge-category () 1954(defun todos-merge-category ()
1709 "Merge this category's items to another category in this file. 1955 "Merge this category with chosen category in this file. The
1710The todo and done items are appended to the todo and done items, 1956current category's todo and done items are appended to the chosen
1711respectively, of the category merged to, which becomes the 1957category's todo and done items, respectively, which becomes the
1712current category, and the category merged from is deleted." 1958current category, and the category moved from is deleted."
1713 (interactive) 1959 (interactive)
1714 (let ((buffer-read-only nil) 1960 (let ((buffer-read-only nil)
1715 (cat (todos-current-category)) 1961 (cat (todos-current-category))
1716 (goal (todos-read-category "Category to merge to: "))) 1962 (goal (todos-read-category "Category to merge to: " t)))
1717 (widen) 1963 (widen)
1718 ;; FIXME: what if cat has archived items? 1964 ;; FIXME: check if cat has archived items and merge those too
1719 (let* ((cbeg (progn 1965 (let* ((cbeg (progn
1720 (re-search-backward 1966 (re-search-backward
1721 (concat "^" (regexp-quote todos-category-beg)) nil t) 1967 (concat "^" (regexp-quote todos-category-beg)) nil t)
@@ -1724,8 +1970,8 @@ current category, and the category merged from is deleted."
1724 (dbeg (progn 1970 (dbeg (progn
1725 (re-search-forward 1971 (re-search-forward
1726 (concat "^" (regexp-quote todos-category-done)) nil t) 1972 (concat "^" (regexp-quote todos-category-done)) nil t)
1727 (match-beginning 0))) 1973 (forward-line) (point)))
1728 (tend (forward-line -1)) 1974 (tend (progn (forward-line -2) (point)))
1729 (cend (progn 1975 (cend (progn
1730 (if (re-search-forward 1976 (if (re-search-forward
1731 (concat "^" (regexp-quote todos-category-beg)) nil t) 1977 (concat "^" (regexp-quote todos-category-beg)) nil t)
@@ -1736,7 +1982,7 @@ current category, and the category merged from is deleted."
1736 here) 1982 here)
1737 (goto-char (point-min)) 1983 (goto-char (point-min))
1738 (re-search-forward 1984 (re-search-forward
1739 (concat "^" (regexp-quote todos-category-beg goal)) nil t) 1985 (concat "^" (regexp-quote (concat todos-category-beg goal))) nil t)
1740 (re-search-forward 1986 (re-search-forward
1741 (concat "^" (regexp-quote todos-category-done)) nil t) 1987 (concat "^" (regexp-quote todos-category-done)) nil t)
1742 (forward-line -1) 1988 (forward-line -1)
@@ -1749,20 +1995,23 @@ current category, and the category merged from is deleted."
1749 (insert done) 1995 (insert done)
1750 (remove-overlays cbeg cend) 1996 (remove-overlays cbeg cend)
1751 (delete-region cbeg cend) 1997 (delete-region cbeg cend)
1998 (todos-set-count 'todo (todos-get-count 'todo cat) goal)
1999 (todos-set-count 'done (todos-get-count 'done cat) goal)
1752 (setq todos-categories (delete (assoc cat todos-categories) 2000 (setq todos-categories (delete (assoc cat todos-categories)
1753 todos-categories)) 2001 todos-categories))
1754 (todos-update-categories-sexp) 2002 (todos-update-categories-sexp)
1755 (setq todos-category-number (todos-category-number goal)) 2003 (todos-category-number goal)
1756 (todos-category-select) 2004 (todos-category-select)
1757 ;; Put point at the start of the merged todo items 2005 ;; Put point at the start of the merged todo items.
1758 ;; FIXME: what if there are no merged todo items but only done items? 2006 ;; FIXME: what if there are no merged todo items but only done items?
1759 (goto-char here)))) 2007 (goto-char here))))
1760 2008
2009;; FIXME
1761(defun todos-merge-categories () 2010(defun todos-merge-categories ()
1762 "" 2011 ""
1763 (interactive) 2012 (interactive)
1764 (let* ((cats (mapcar 'car todos-categories)) 2013 (let* ((cats (mapcar 'car todos-categories))
1765 (goal (todos-read-category "Category to merge to: ")) 2014 (goal (todos-read-category "Category to merge to: " t))
1766 (prompt (format "Merge to %s (type C-g to finish)? " goal)) 2015 (prompt (format "Merge to %s (type C-g to finish)? " goal))
1767 (source (let ((inhibit-quit t) l) 2016 (source (let ((inhibit-quit t) l)
1768 (while (not (eq last-input-event 7)) 2017 (while (not (eq last-input-event 7))
@@ -1773,319 +2022,178 @@ current category, and the category merged from is deleted."
1773 (widen) 2022 (widen)
1774 )) 2023 ))
1775 2024
2025;; FIXME: make insertion options customizable per category
1776;;;###autoload 2026;;;###autoload
1777(defun todos-insert-item (&optional arg date-type time diary here) 2027;; (defun todos-insert-item (&optional arg use-point date-type time
1778 "Insert new TODO list item. 2028;; diary nonmarking)
1779 2029(defun todos-insert-item (&optional arg diary nonmarking date-type time
1780With prefix argument ARG solicit the category, otherwise use the 2030 region-or-here)
1781current category. 2031 "Add a new Todo item to a category.
1782 2032See the note at the end of this document string about key
1783Argument DATE-TYPE sets the form of the item's mandatory date 2033bindings and convenience commands derived from this command.
1784string. With the value `date' this is the full date (whose 2034
1785format is set by `calendar-date-display-form', with year, month 2035With no (or nil) prefix argument ARG, add the item to the current
1786and day individually solicited (month with tab completion). With 2036category; with one prefix argument (C-u), prompt for a category
1787the value `dayname' a weekday name is used, solicited with tab 2037from the current Todos file; with two prefix arguments (C-u C-u),
1788completion. With the value `calendar' the full date string is 2038first prompt for a Todos file, then a category in that file. If
1789used and set by selecting from the Calendar. With any other 2039a non-existing category is entered, ask whether to add it to the
1790value (including none) the full current date is used. 2040Todos file; if answered affirmatively, add the category and
1791 2041insert the item there.
1792Argument TIME determines the occurrence and value of the time 2042
1793string. With the value `omit' insert the item without a time 2043When argument DIARY is non-nil, this overrides the intent of the
1794string. With the value `ask' solicit a time string; this may be 2044user option `todos-include-in-diary' for this item: if
1795empty or else must match `date-time-regexp'. With any other 2045`todos-include-in-diary' is nil, include the item in the Fancy
1796value add or omit the current time in accordance with 2046Diary display, and if it is non-nil, exclude the item from the
1797`todos-always-add-time-string'. 2047Fancy Diary display. When DIARY is nil, `todos-include-in-diary'
1798 2048has its intended effect.
1799With non-nil argument DIARY mark item for inclusion in user's diary. If `todos-include-in-diary' is non-nil 2049
1800 2050When the item is included in the Fancy Diary display and the
1801With non-nil argument HERE insert the new item directly above the 2051argument NONMARKING is non-nil, this overrides the intent of the
1802item at point. If point is on an empty line, insert the new item 2052user option `todos-diary-nonmarking' for this item: if
1803there." 2053`todos-diary-nonmarking' is nil, append `diary-nonmarking-symbol'
2054to the item, and if it is non-nil, omit `diary-nonmarking-symbol'.
2055
2056The argument DATE-TYPE determines the content of the item's
2057mandatory date header string and how it is added:
2058- If DATE-TYPE is the symbol `calendar', the Calendar pops up and
2059 when the user puts the cursor on a date and hits RET, that
2060 date, in the format set by `calendar-date-display-form',
2061 becomes the date in the header.
2062- If DATE-TYPE is the symbol `date', the header contains the date
2063 in the format set by `calendar-date-display-form', with year,
2064 month and day individually prompted for (month with tab
2065 completion).
2066- If DATE-TYPE is the symbol `dayname' the header contains a
2067 weekday name instead of a date, prompted for with tab
2068 completion.
2069- If DATE-TYPE has any other value (including nil or none) the
2070 header contains the current date (in the format set by
2071 `calendar-date-display-form').
2072
2073With non-nil argument TIME prompt for a time string; this must
2074either be empty or else match `diary-time-regexp'. If TIME is
2075nil, add or omit the current time according to value of the user
2076option `todos-always-add-time-string'.
2077
2078The argument REGION-OR-HERE determines the source and location of
2079the new item:
2080- If the REGION-OR-HERE is the symbol `here', prompt for the text
2081 of the new item and insert it directly above the todo item at
2082 point, or if point is on the empty line below the last todo
2083 item, insert the new item there. An error is signalled if
2084 `todos-insert-item' is invoked with `here' outside of the
2085 current category.
2086- If REGION-OR-HERE is the symbol `region', use the region of the
2087 current buffer as the text of the new item, depending on the
2088 value of user option `todos-use-only-highlighted-region': if
2089 this is non-nil, then use the region only when it is
2090 highlighted; otherwise, use the region regardless of
2091 highlighting. An error is signalled if there is no region in
2092 the current buffer. Prompt for the item's priority in the
2093 category (an integer between 1 and one more than the number of
2094 items in the category), and insert the item accordingly.
2095- If REGION-OR-HERE has any other value (in particular, nil or
2096 none), prompt for the text and the item's priority, and insert
2097 the item accordingly.
2098
2099To facilitate using these arguments when inserting a new todo
2100item, convenience commands have been defined for all admissible
2101combinations (96 in all!) together with mnenomic key bindings
2102based on on the name of the arguments and their order: _h_ere or
2103_r_egion - _c_alendar or _d_ate or day_n_ame - _t_ime - diar_y_ -
2104nonmar_k_ing. An alternative interface for customizing key
2105binding is also provided with the function
2106`todos-insertion-bindings'." ;FIXME
1804 (interactive "P") 2107 (interactive "P")
1805 (unless (or (todos-done-item-p) 2108 (let ((region (eq region-or-here 'region))
1806 (save-excursion (forward-line -1) (todos-done-item-p))) 2109 (here (eq region-or-here 'here)))
1807 ;; FIXME: deletable if command not autoloaded 2110 (when region
1808 (when (not (derived-mode-p 'todos-mode)) (todos-show)) 2111 ;; FIXME: better to use use-region-p or region-active-p?
1809 (let* ((buffer-read-only) 2112 (unless (and (if todos-use-only-highlighted-region
2113 transient-mark-mode
2114 t)
2115 mark-active)
2116 (error "The mark is not set now, so there is no region")))
2117 (let* ((buf (current-buffer))
2118 (new-item (if region
2119 ;; FIXME: or keep properties?
2120 (buffer-substring-no-properties
2121 (region-beginning) (region-end))
2122 (read-from-minibuffer "Todo item: ")))
1810 (date-string (cond 2123 (date-string (cond
1811 ((eq date-type 'ask-date) 2124 ((eq date-type 'date)
1812 (todos-read-date)) 2125 (todos-read-date))
1813 ((eq date-type 'ask-dayname) 2126 ((eq date-type 'dayname)
1814 (todos-read-dayname)) 2127 (todos-read-dayname))
1815 ((eq date-type 'calendar) 2128 ((eq date-type 'calendar)
1816 ;; FIXME: should only be executed from Calendar 2129 (setq todos-date-from-calendar t)
2130 (let (calendar-view-diary-initially-flag)
2131 (calendar))
1817 (with-current-buffer "*Calendar*" 2132 (with-current-buffer "*Calendar*"
1818 (calendar-date-string (calendar-cursor-to-date t) t t))) 2133 (todos-set-date-from-calendar))
2134 todos-date-from-calendar)
1819 (t (calendar-date-string (calendar-current-date) t t)))) 2135 (t (calendar-date-string (calendar-current-date) t t))))
1820 (time-string (cond ((eq time 'ask-time) 2136 ;; FIXME: should TIME override `todos-always-add-time-string'? But
1821 (todos-read-time)) 2137 ;; then add another option to use current time or prompt for time
1822 (todos-always-add-time-string 2138 ;; string?
1823 (substring (current-time-string) 11 16)) 2139 (time-string (or (and time (todos-read-time))
1824 (t nil))) 2140 (and todos-always-add-time-string
1825 (new-item (concat (unless (or diary todos-include-in-diary) 2141 (substring (current-time-string) 11 16)))))
1826 todos-nondiary-start) 2142 (setq todos-date-from-calendar nil)
1827 date-string (when time-string (concat " " time-string)) 2143 (cond ((equal arg '(16)) ; FIXME: cf. set-mark-command
1828 (unless (or diary todos-include-in-diary) 2144 (todos-jump-to-category nil t)
1829 todos-nondiary-end) 2145 (set-window-buffer
1830 " " 2146 (selected-window)
1831 (read-from-minibuffer "New TODO entry: "))) 2147 (set-buffer (get-file-buffer todos-global-current-todos-file))))
1832 (cat (if arg (todos-read-category "Insert item in category: ") 2148 ((equal arg '(4)) ; FIXME: just arg?
1833 (todos-current-category)))) 2149 (todos-jump-to-category)
1834 ;; indent newlines inserted by C-q C-j if nonspace char follows 2150 (set-window-buffer
1835 (setq new-item (replace-regexp-in-string 2151 (selected-window)
1836 "\\(\n\\)[^[:blank:]]" 2152 (set-buffer (get-file-buffer todos-global-current-todos-file))))
1837 (concat "\n" (make-string todos-indent-to-here 32)) new-item 2153 (t
1838 nil nil 1)) 2154 (when (not (derived-mode-p 'todos-mode)) (todos-show))))
1839 (unless (assoc cat todos-categories) (todos-add-category cat)) 2155 (let (buffer-read-only)
1840 ;; (unless here (todos-set-item-priority new-item cat)) 2156 (setq new-item
1841 ;; (todos-insert-with-overlays new-item) 2157 ;; Add date, time and diary marking as required.
1842 (if here 2158 (concat (if (not (and diary (not todos-include-in-diary)))
1843 (todos-insert-with-overlays new-item) 2159 todos-nondiary-start
1844 (todos-set-item-priority new-item cat)) 2160 (when (and nonmarking (not todos-diary-nonmarking))
1845 (todos-item-counts cat 'insert) 2161 diary-nonmarking-symbol))
1846 (if (or diary todos-include-in-diary) (todos-item-counts cat 'diary)) 2162 date-string (when time-string
1847 (todos-update-categories-sexp)))) 2163 (concat " " time-string))
1848 2164 (when (not (and diary (not todos-include-in-diary)))
1849;; FIXME: make insertion options customizable per category 2165 todos-nondiary-end)
1850 2166 " " new-item))
1851;; current date ~ current day ~ ask date ~ ask day 2167 ;; Indent newlines inserted by C-q C-j if nonspace char follows.
1852;; current time ~ ask time ~ maybe no time 2168 (setq new-item (replace-regexp-in-string
1853;; for diary ~ not for diary 2169 "\\(\n\\)[^[:blank:]]"
1854;; here ~ ask priority 2170 (concat "\n" (make-string todos-indent-to-here 32))
1855 2171 new-item nil nil 1))
1856;; date-type: date name (calendar) - (maybe-no)time - diary - here 2172 (if here
1857 2173 (cond ((not (eq major-mode 'todos-mode))
1858;; ii todos-insert-item + current-date/dayname + current/no-time 2174 (error "Cannot insert a todo item here outside of Todos mode"))
1859;; ih todos-insert-item-here 2175 ((not (eq buf (current-buffer)))
1860;; idd todos-insert-item-ask-date 2176 (error "Cannot insert an item here after changing buffer"))
1861;; idtt todos-insert-item-ask-date-time 2177 ((or (todos-done-item-p)
1862;; idtyy todos-insert-item-ask-date-time-for-diary 2178 ;; Point on last blank line.
1863;; idtyh todos-insert-item-ask-date-time-for-diary-here 2179 (save-excursion (forward-line -1) (todos-done-item-p)))
1864;; idth todos-insert-item-ask-date-time-here 2180 (error "Cannot insert a new item in the done item section"))
1865;; idmm todos-insert-item-ask-date-maybe-notime 2181 (t
1866;; idmyy todos-insert-item-ask-date-maybe-notime-for-diary 2182 (todos-insert-with-overlays new-item)))
1867;; idmyh todos-insert-item-ask-date-maybe-notime-for-diary-here 2183 (todos-set-item-priority new-item (todos-current-category) t))
1868;; idmh todos-insert-item-ask-date-maybe-notime-here 2184 (todos-set-count 'todo 1)
1869;; idyy todos-insert-item-ask-date-for-diary 2185 (if (or diary todos-include-in-diary) (todos-set-count 'diary 1))
1870;; idyh todos-insert-item-ask-date-for-diary-here 2186 (todos-update-categories-sexp)))))
1871;; idh todos-insert-item-ask-date-here
1872;; inn todos-insert-item-ask-dayname
1873;; intt todos-insert-item-ask-dayname-time
1874;; intyy todos-insert-item-ask-dayname-time-for-diary
1875;; intyh todos-insert-item-ask-dayname-time-for-diary-here
1876;; inth todos-insert-item-ask-dayname-time-here
1877;; inmm todos-insert-item-ask-dayname-maybe-notime
1878;; inmyy todos-insert-item-ask-dayname-maybe-notime-for-diary
1879;; inmyh todos-insert-item-ask-dayname-maybe-notime-for-diary-here
1880;; inmh todos-insert-item-ask-dayname-maybe-notime-here
1881;; inyy todos-insert-item-ask-dayname-for-diary
1882;; inyh todos-insert-item-ask-dayname-for-diary-here
1883;; inh todos-insert-item-ask-dayname-here
1884;; itt todos-insert-item-ask-time
1885;; ityy todos-insert-item-ask-time-for-diary
1886;; ityh todos-insert-item-ask-time-for-diary-here
1887;; ith todos-insert-item-ask-time-here
1888;; im todos-insert-item-maybe-notime
1889;; imyy todos-insert-item-maybe-notime-for-diary
1890;; imyh todos-insert-item-maybe-notime-for-diary-here
1891;; imh todos-insert-item-maybe-notime-here
1892;; iyy todos-insert-item-for-diary
1893;; iyh todos-insert-item-for-diary-here
1894
1895(defun todos-insert-item-ask-date (&optional arg)
1896 ""
1897 (interactive "P")
1898 (todos-insert-item arg 'ask-date))
1899
1900(defun todos-insert-item-ask-date-time (&optional arg)
1901 ""
1902 (interactive "P")
1903 (todos-insert-item arg 'ask-date 'ask-time))
1904
1905(defun todos-insert-item-ask-date-time-for-diary (&optional arg)
1906 ""
1907 (interactive "P")
1908 (todos-insert-item arg 'ask-date 'ask-time t))
1909
1910(defun todos-insert-item-ask-date-time-for-diary-here ()
1911 ""
1912 (interactive)
1913 (todos-insert-item nil 'ask-date 'ask-time t t))
1914
1915(defun todos-insert-item-ask-date-time-here ()
1916 ""
1917 (interactive)
1918 (todos-insert-item nil 'ask-date 'ask-time nil t))
1919
1920(defun todos-insert-item-ask-date-maybe-notime (&optional arg)
1921 ""
1922 (interactive "P")
1923 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
1924 (todos-insert-item arg 'ask-date)))
1925
1926(defun todos-insert-item-ask-date-maybe-notime-for-diary (&optional arg)
1927 ""
1928 (interactive "P")
1929 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
1930 (todos-insert-item arg 'ask-date nil t)))
1931
1932(defun todos-insert-item-ask-date-maybe-notime-for-diary-here ()
1933 ""
1934 (interactive)
1935 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
1936 (todos-insert-item nil 'ask-date nil t t)))
1937
1938(defun todos-insert-item-ask-date-maybe-notime-here ()
1939 ""
1940 (interactive)
1941 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
1942 (todos-insert-item nil 'ask-date nil nil nil t)))
1943
1944(defun todos-insert-item-ask-date-for-diary (&optional arg)
1945 ""
1946 (interactive "P")
1947 (todos-insert-item arg 'ask-date nil t))
1948
1949(defun todos-insert-item-ask-date-for-diary-here ()
1950 ""
1951 (interactive)
1952 (todos-insert-item nil 'ask-date nil t t))
1953
1954(defun todos-insert-item-ask-date-here ()
1955 ""
1956 (interactive)
1957 (todos-insert-item nil 'ask-date nil nil t))
1958
1959(defun todos-insert-item-ask-dayname (&optional arg)
1960 ""
1961 (interactive "P")
1962 (todos-insert-item arg 'ask-dayname))
1963
1964(defun todos-insert-item-ask-dayname-time (&optional arg)
1965 ""
1966 (interactive "P")
1967 (todos-insert-item arg 'ask-dayname 'ask-time))
1968
1969(defun todos-insert-item-ask-dayname-time-for-diary (&optional arg)
1970 ""
1971 (interactive "P")
1972 (todos-insert-item arg 'ask-dayname 'ask-time t))
1973
1974(defun todos-insert-item-ask-dayname-time-for-diary-here ()
1975 ""
1976 (interactive)
1977 (todos-insert-item nil 'ask-dayname 'ask-time t t))
1978
1979(defun todos-insert-item-ask-dayname-time-here ()
1980 ""
1981 (interactive)
1982 (todos-insert-item nil 'ask-dayname 'ask-time nil t))
1983
1984(defun todos-insert-item-ask-dayname-maybe-notime (&optional arg)
1985 ""
1986 (interactive "P")
1987 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
1988 (todos-insert-item arg 'ask-dayname)))
1989
1990(defun todos-insert-item-ask-dayname-maybe-notime-for-diary (&optional arg)
1991 ""
1992 (interactive "P")
1993 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
1994 (todos-insert-item arg 'ask-dayname nil t)))
1995
1996(defun todos-insert-item-ask-dayname-maybe-notime-for-diary-here ()
1997 ""
1998 (interactive)
1999 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
2000 (todos-insert-item nil 'ask-dayname nil t t)))
2001
2002(defun todos-insert-item-ask-dayname-maybe-notime-here ()
2003 ""
2004 (interactive)
2005 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
2006 (todos-insert-item nil 'ask-dayname nil nil t)))
2007
2008(defun todos-insert-item-ask-dayname-for-diary (&optional arg)
2009 ""
2010 (interactive "P")
2011 (todos-insert-item arg 'ask-dayname nil t))
2012
2013(defun todos-insert-item-ask-dayname-for-diary-here ()
2014 ""
2015 (interactive)
2016 (todos-insert-item nil 'ask-dayname nil t t))
2017
2018(defun todos-insert-item-ask-dayname-here ()
2019 ""
2020 (interactive)
2021 (todos-insert-item nil 'ask-dayname nil nil t))
2022
2023(defun todos-insert-item-ask-time (&optional arg)
2024 ""
2025 (interactive "P")
2026 (todos-insert-item arg nil 'ask-time))
2027
2028(defun todos-insert-item-ask-time-for-diary (&optional arg)
2029 ""
2030 (interactive "P")
2031 (todos-insert-item arg nil 'ask-time t))
2032
2033(defun todos-insert-item-ask-time-for-diary-here ()
2034 ""
2035 (interactive)
2036 (todos-insert-item nil nil 'ask-time t t))
2037
2038(defun todos-insert-item-ask-time-here ()
2039 ""
2040 (interactive)
2041 (todos-insert-item nil nil 'ask-time nil t))
2042
2043(defun todos-insert-item-maybe-notime (&optional arg)
2044 ""
2045 (interactive "P")
2046 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
2047 (todos-insert-item arg)))
2048
2049(defun todos-insert-item-maybe-notime-for-diary (&optional arg)
2050 ""
2051 (interactive "P")
2052 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
2053 (todos-insert-item arg nil nil t)))
2054
2055(defun todos-insert-item-maybe-notime-for-diary-here ()
2056 ""
2057 (interactive)
2058 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
2059 (todos-insert-item nil nil nil t t)))
2060
2061(defun todos-insert-item-maybe-notime-here ()
2062 ""
2063 (interactive)
2064 (let ((todos-always-add-time-string (not todos-always-add-time-string)))
2065 (todos-insert-item nil nil nil nil t)))
2066
2067(defun todos-insert-item-for-diary (&optional arg)
2068 ""
2069 (interactive "P")
2070 (todos-insert-item nil nil nil t))
2071
2072(defun todos-insert-item-for-diary-here ()
2073 ""
2074 (interactive)
2075 (todos-insert-item nil nil nil t t))
2076
2077(defun todos-insert-item-here ()
2078 "Insert new Todo item directly above the item at point.
2079If point is on an empty line, insert the new item there."
2080 (interactive)
2081 (todos-insert-item nil nil nil nil t))
2082 2187
2083;; FIXME: autoload when key-binding is defined in calendar.el 2188;; FIXME: autoload when key-binding is defined in calendar.el
2084(defun todos-insert-item-from-calendar () 2189(defun todos-insert-item-from-calendar ()
2085 "" 2190 ""
2086 (interactive) 2191 (interactive)
2087 (pop-to-buffer (file-name-nondirectory todos-current-todos-file)) 2192 ;; FIXME: todos-current-todos-file is nil here, better to solicit Todos file?
2193 ;; FIXME: t-g-c-t-f is nil if no Todos file has been visited
2194 (pop-to-buffer (file-name-nondirectory todos-global-current-todos-file))
2088 (todos-show) 2195 (todos-show)
2196 ;; FIXME: this now calls todos-set-date-from-calendar
2089 (todos-insert-item t 'calendar)) 2197 (todos-insert-item t 'calendar))
2090 2198
2091;; FIXME: calendar is loaded before todos 2199;; FIXME: calendar is loaded before todos
@@ -2093,29 +2201,67 @@ If point is on an empty line, insert the new item there."
2093 ;; (lambda () 2201 ;; (lambda ()
2094 (define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);)) 2202 (define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);))
2095 2203
2204(defvar todos-date-from-calendar nil)
2205(defun todos-set-date-from-calendar ()
2206 ""
2207 (when todos-date-from-calendar
2208 (local-set-key (kbd "RET") 'exit-recursive-edit)
2209 (message "Put cursor on a date and type <return> to set it.")
2210 ;; FIXME: is there a better way than recursive-edit?
2211 ;; FIXME: use unwind-protect? Check recursive-depth?
2212 (recursive-edit)
2213 (setq todos-date-from-calendar
2214 (calendar-date-string (calendar-cursor-to-date t) t t))
2215 (calendar-exit)))
2216
2096(defun todos-delete-item () 2217(defun todos-delete-item ()
2097 "Delete current TODO list entry." 2218 "Delete at least one item in this category.
2219
2220If there are marked items, delete all of these; otherwise, delete
2221the item at point."
2098 (interactive) 2222 (interactive)
2099 (if (> (count-lines (point-min) (point-max)) 0) 2223 (let* ((cat (todos-current-category))
2100 (let* ((buffer-read-only) 2224 (marked (assoc cat todos-categories-with-marks))
2101 (item (todos-item-string-start)) 2225 (item (unless marked (todos-item-string)))
2102 (diary-item (todos-diary-item-p)) 2226 (ov (make-overlay (save-excursion (todos-item-start))
2103 (cat (todos-current-category)) 2227 (save-excursion (todos-item-end))))
2104 (answer (y-or-n-p (concat "Permanently remove '" item "'? ")))) 2228 ;; FIXME: make confirmation an option
2105 (when answer 2229 (answer (if marked
2106 (todos-remove-item) 2230 (y-or-n-p "Permanently delete all marked items? ")
2107 (when (and (bolp) (eolp) 2231 (when item
2108 ;; not if last item was deleted 2232 (overlay-put ov 'face 'todos-search)
2109 (< (point-min) (point-max))) 2233 (y-or-n-p (concat "Permanently delete this item? ")))))
2110 (todos-backward-item)) 2234 (opoint (point))
2111 (todos-item-counts cat 'delete) 2235 buffer-read-only)
2112 (and diary-item (todos-item-counts cat 'nondiary)) 2236 (when answer
2113 (todos-update-categories-sexp) 2237 (and marked (goto-char (point-min)))
2114 (todos-prefix-overlays))) 2238 (catch 'done
2115 (message "No TODO list entry to delete"))) ;FIXME: better message 2239 (while (not (eobp))
2240 (if (or (and marked (todos-item-marked-p)) item)
2241 (progn
2242 (if (todos-done-item-p)
2243 (todos-set-count 'done -1)
2244 (todos-set-count 'todo -1 cat)
2245 (and (todos-diary-item-p) (todos-set-count 'diary -1)))
2246 (delete-overlay ov)
2247 (todos-remove-item)
2248 ;; Don't leave point below last item.
2249 (and item (bolp) (eolp) (< (point-min) (point-max))
2250 (todos-backward-item))
2251 (when item
2252 (throw 'done (setq item nil))))
2253 (todos-forward-item))))
2254 (when marked
2255 (remove-overlays (point-min) (point-max) 'before-string todos-item-mark)
2256 (setq todos-categories-with-marks
2257 (assq-delete-all cat todos-categories-with-marks))
2258 (goto-char opoint))
2259 (todos-update-categories-sexp)
2260 (todos-prefix-overlays))
2261 (if ov (delete-overlay ov))))
2116 2262
2117(defun todos-edit-item () 2263(defun todos-edit-item ()
2118 "Edit current TODO list entry." 2264 "Edit current Todo item in the minibuffer."
2119 (interactive) 2265 (interactive)
2120 (when (todos-item-string) 2266 (when (todos-item-string)
2121 (let* ((buffer-read-only) 2267 (let* ((buffer-read-only)
@@ -2128,14 +2274,16 @@ If point is on an empty line, insert the new item there."
2128 (line-end-position) t) 2274 (line-end-position) t)
2129 (1+ (- (point) start)))) 2275 (1+ (- (point) start))))
2130 (item (todos-item-string)) 2276 (item (todos-item-string))
2277 (multiline (> (length (split-string item "\n")) 1))
2131 (opoint (point))) 2278 (opoint (point)))
2132 (if (todos-string-multiline-p item) 2279 (if multiline
2133 (todos-edit-multiline) 2280 (todos-edit-multiline)
2134 (let ((new (read-string "Edit: " (cons item item-beg)))) 2281 (let ((new (read-string "Edit: " (cons item item-beg))))
2135 (while (not (string-match (concat todos-date-string-start 2282 (while (not (string-match
2136 todos-date-pattern) new)) 2283 (concat todos-date-string-start todos-date-pattern) new))
2137 (setq new (read-from-minibuffer "Item must start with a date: " new))) 2284 (setq new (read-from-minibuffer
2138 ;; indent newlines inserted by C-q C-j if nonspace char follows 2285 "Item must start with a date: " new)))
2286 ;; Indent newlines inserted by C-q C-j if nonspace char follows.
2139 (setq new (replace-regexp-in-string 2287 (setq new (replace-regexp-in-string
2140 "\\(\n\\)[^[:blank:]]" 2288 "\\(\n\\)[^[:blank:]]"
2141 (concat "\n" (make-string todos-indent-to-here 32)) new 2289 (concat "\n" (make-string todos-indent-to-here 32)) new
@@ -2149,152 +2297,162 @@ If point is on an empty line, insert the new item there."
2149;; FIXME: run todos-check-format on exiting buffer (or check for date string 2297;; FIXME: run todos-check-format on exiting buffer (or check for date string
2150;; and indentation) 2298;; and indentation)
2151(defun todos-edit-multiline () 2299(defun todos-edit-multiline ()
2152 "Set up a buffer for editing a multiline TODO list entry." 2300 "Edit current Todo item in Todos Edit mode.
2301Use of newlines invokes `todos-indent' to insure compliance with
2302the format of Diary entries."
2153 (interactive) 2303 (interactive)
2154 (let ((buffer-name (generate-new-buffer-name todos-edit-buffer))) 2304 (let ((buffer-name (generate-new-buffer-name todos-edit-buffer)))
2155 (switch-to-buffer 2305 (set-window-buffer
2156 (make-indirect-buffer 2306 (selected-window)
2157 (file-name-nondirectory todos-current-todos-file) buffer-name)) 2307 (set-buffer (make-indirect-buffer
2308 (file-name-nondirectory todos-current-todos-file)
2309 buffer-name)))
2158 (narrow-to-region (todos-item-start) (todos-item-end)) 2310 (narrow-to-region (todos-item-start) (todos-item-end))
2159 (todos-edit-mode) 2311 (todos-edit-mode)
2160 (message "Type %s to return to Todos mode." 2312 (message "Type %s to return to Todos mode."
2161 (key-description (car (where-is-internal 'todos-edit-quit)))))) 2313 (key-description (car (where-is-internal 'todos-edit-quit))))))
2162 2314
2163(defun todos-edit-quit () 2315(defun todos-edit-quit ()
2164 "" 2316 "Return from Todos Edit mode to Todos mode."
2165 (interactive) 2317 (interactive)
2166 (todos-save)
2167 ;; (unlock-buffer)
2168 (kill-buffer) 2318 (kill-buffer)
2169 (save-excursion (todos-category-select))) 2319 (todos-show))
2170 2320
2171(defun todos-edit-item-header (&optional part) 2321(defun todos-edit-item-header (&optional what)
2172 "" 2322 "Edit date/time header of at least one item.
2323
2324Interactively, ask whether to edit year, month and day or day of
2325the week, as well as time. If there are marked items, apply the
2326changes to all of these; otherwise, edit just the item at point.
2327
2328Non-interactively, argument WHAT specifies whether to edit only
2329the date or only the time, or to set the date to today."
2173 (interactive) 2330 (interactive)
2174 (todos-item-start) 2331 (let* ((cat (todos-current-category))
2175 (re-search-forward (concat todos-date-string-start "\\(?1:" todos-date-pattern 2332 (marked (assoc cat todos-categories-with-marks))
2176 "\\)\\(?2: " diary-time-regexp "\\)?") 2333 (first t)
2177 (line-end-position) t)
2178 (let* ((odate (match-string-no-properties 1))
2179 (otime (match-string-no-properties 2))
2180 (buffer-read-only)
2181 ndate ntime nheader) 2334 ndate ntime nheader)
2182 (unless (eq part 'timeonly) 2335 (save-excursion
2183 (setq ndate (if (save-match-data (string-match "[0-9]+" odate)) 2336 (or (and marked (goto-char (point-min))) (todos-item-start))
2184 (if (y-or-n-p "Change date? ") 2337 (catch 'stop
2185 (todos-read-date) 2338 (while (not (eobp))
2186 (todos-read-dayname)) 2339 (and marked
2187 (if (y-or-n-p "Change day? ") 2340 (while (not (todos-item-marked-p))
2188 (todos-read-dayname) 2341 (todos-forward-item)
2189 (todos-read-date)))) 2342 (and (eobp) (throw 'stop nil))))
2190 (replace-match ndate nil nil nil 1)) 2343 (re-search-forward (concat todos-date-string-start "\\(?1:"
2191 (unless (eq part 'dateonly) 2344 todos-date-pattern
2192 (setq ntime (save-match-data (todos-read-time))) 2345 "\\)\\(?2: " diary-time-regexp "\\)?")
2193 (when (< 0 (length ntime)) (setq ntime (concat " " ntime))) 2346 (line-end-position) t)
2194 (if otime 2347 (let* ((odate (match-string-no-properties 1))
2195 (replace-match ntime nil nil nil 2) 2348 (otime (match-string-no-properties 2))
2196 (goto-char (match-end 1)) 2349 (buffer-read-only))
2197 (insert ntime))))) 2350 (if (eq what 'today)
2351 (progn
2352 (setq ndate (calendar-date-string (calendar-current-date) t t))
2353 (replace-match ndate nil nil nil 1))
2354 (unless (eq what 'timeonly)
2355 (when first
2356 (setq ndate (if (save-match-data (string-match "[0-9]+" odate))
2357 (if (y-or-n-p "Change date? ")
2358 (todos-read-date)
2359 (todos-read-dayname))
2360 (if (y-or-n-p "Change day? ")
2361 (todos-read-dayname)
2362 (todos-read-date)))))
2363 (replace-match ndate nil nil nil 1))
2364 (unless (eq what 'dateonly)
2365 (when first
2366 (setq ntime (save-match-data (todos-read-time)))
2367 (when (< 0 (length ntime)) (setq ntime (concat " " ntime))))
2368 (if otime
2369 (replace-match ntime nil nil nil 2)
2370 (goto-char (match-end 1))
2371 (insert ntime))))
2372 (setq first nil))
2373 (if marked
2374 (todos-forward-item)
2375 (goto-char (point-max))))))))
2198 2376
2199(defun todos-edit-item-date () 2377(defun todos-edit-item-date ()
2200 "" 2378 "Prompt For and apply changes to current item's date."
2201 (interactive) 2379 (interactive)
2202 (todos-edit-item-header 'dateonly)) 2380 (todos-edit-item-header 'dateonly))
2203 2381
2204(defun todos-edit-item-date-is-today () 2382(defun todos-edit-item-date-is-today ()
2205 "" 2383 "Set item date to today's date."
2206 (interactive) 2384 (interactive)
2207 (todos-edit-item-header 'today)) 2385 (todos-edit-item-header 'today))
2208 2386
2209(defun todos-edit-item-time () 2387(defun todos-edit-item-time ()
2210 "" 2388 "Prompt For and apply changes to current item's time."
2211 (interactive) 2389 (interactive)
2212 (todos-edit-item-header 'timeonly)) 2390 (todos-edit-item-header 'timeonly))
2213 2391
2214;; (progn 2392(defun todos-raise-item-priority (&optional lower)
2215;; (re-search-forward "\\(?1:foo\\)\\(ba\\)\\(?2:z\\)?" nil t) 2393 "Raise priority of current item by moving it up by one item.
2216;; (goto-char (point-max)) 2394With non-nil argument LOWER lower item's priority."
2217;; (concat (match-string-no-properties 1) ", " (match-string-no-properties 2)))
2218
2219;; foobaz
2220
2221
2222(defun todos-raise-item-priority ()
2223 "Raise priority of current entry."
2224 (interactive) 2395 (interactive)
2225 (unless (or (todos-done-item-p) 2396 (unless (or (todos-done-item-p)
2226 (looking-at "^$")) ; between done and not done items 2397 (looking-at "^$")) ; We're between todo and done items.
2227 (let (buffer-read-only) 2398 (let (buffer-read-only)
2228 (if (> (count-lines (point-min) (point)) 0) 2399 (if (or (and lower
2229 (let ((item (todos-item-string))) 2400 (save-excursion
2230 (when (eq major-mode 'todos-top-priorities-mode) 2401 ;; Can't lower final todo item.
2231 (let ((cat1 (save-excursion 2402 (todos-forward-item)
2232 (re-search-forward 2403 (and (looking-at todos-item-start)
2233 (concat todos-date-string-start todos-date-pattern 2404 (not (todos-done-item-p)))))
2234 "\\( " diary-time-regexp 2405 ;; Can't raise or lower only todo item.
2235 "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)") 2406 (> (count-lines (point-min) (point)) 0))
2236 nil t) 2407 (let ((item (todos-item-string))
2408 (marked (todos-item-marked-p)))
2409 ;; In Todos Top Priorities mode, an item's priority can be changed
2410 ;; wrt items in another category, but not wrt items in the same
2411 ;; category.
2412 (when (eq major-mode 'todos-filter-items-mode)
2413 (let* ((regexp (concat todos-date-string-start todos-date-pattern
2414 "\\( " diary-time-regexp "\\)?"
2415 (regexp-quote todos-nondiary-end)
2416 "?\\(?1: \\[\\(.+:\\)?.+\\]\\)"))
2417 (cat1 (save-excursion
2418 (re-search-forward regexp nil t)
2237 (match-string 1))) 2419 (match-string 1)))
2238 (cat2 (save-excursion 2420 (cat2 (save-excursion
2239 (todos-backward-item) 2421 (if lower
2240 (re-search-forward 2422 (todos-forward-item)
2241 (concat todos-date-string-start todos-date-pattern 2423 (todos-backward-item))
2242 "\\( " diary-time-regexp 2424 (re-search-forward regexp nil t)
2243 "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)") 2425 (match-string 1))))
2244 nil t)
2245 (match-string 1))))
2246 (if (string= cat1 cat2) 2426 (if (string= cat1 cat2)
2247 (error "Cannot change item's priority in its category; do this in Todos mode")))) 2427 ;; FIXME: better message
2428 (error (concat "Cannot change item's priority in its "
2429 "category; do this in Todos mode")))))
2248 (todos-remove-item) 2430 (todos-remove-item)
2249 (todos-backward-item) 2431 (if lower (todos-forward-item) (todos-backward-item))
2250 (todos-insert-with-overlays item)) 2432 (todos-insert-with-overlays item)
2251 (message "No TODO list entry to raise"))))) ;FIXME: better message 2433 ;; If item was marked, retore the mark.
2434 (and marked (overlay-put (make-overlay (point) (point))
2435 'before-string todos-item-mark)))
2436 (message ""))))) ;FIXME: no message ?
2252 2437
2253(defun todos-lower-item-priority () 2438(defun todos-lower-item-priority ()
2254 "Lower priority of current entry." 2439 "Lower priority of current item by moving it down by one item."
2255 (interactive) 2440 (interactive)
2256 (unless (or (todos-done-item-p) 2441 (todos-raise-item-priority t))
2257 (looking-at "^$")) ; between done and not done items 2442
2258 (let (buffer-read-only) 2443;; FIXME: incorporate todos-(raise|lower)-item-priority ?
2259 (if (save-excursion 2444(defun todos-set-item-priority (item cat &optional new)
2260 ;; can only lower non-final unfinished item 2445 "Set todo ITEM's priority in category CAT, moving item as needed.
2261 (todos-forward-item) 2446Interactively, the item and the category are the current ones,
2262 (and (looking-at todos-item-start) 2447and the priority is a number between 1 and the number of items in
2263 (not (todos-done-item-p)))) 2448the category. Non-interactively with argument NEW, the lowest
2264 ;; Assume there is a final newline 2449priority is one more than the number of items in CAT."
2265 (let ((item (todos-item-string)))
2266 (when (eq major-mode 'todos-top-priorities-mode)
2267 (let ((cat1 (save-excursion
2268 (re-search-forward
2269 (concat todos-date-string-start todos-date-pattern
2270 "\\( " diary-time-regexp
2271 "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)")
2272 nil t)
2273 (match-string 1)))
2274 (cat2 (save-excursion
2275 (todos-forward-item)
2276 (re-search-forward
2277 (concat todos-date-string-start todos-date-pattern
2278 "\\( " diary-time-regexp
2279 "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)")
2280 nil t)
2281 (match-string 1))))
2282 (if (string= cat1 cat2)
2283 (error "Cannot change item's priority in its category; do this in Todos mode"))))
2284 (todos-remove-item)
2285 (todos-forward-item)
2286 (when (todos-done-item-p) (forward-line -1))
2287 (todos-insert-with-overlays item))
2288 (message "No TODO list entry to lower"))))) ;FIXME: better message
2289
2290(defun todos-set-item-priority (item cat)
2291 "Set priority of todo ITEM in category CAT and move item to suit."
2292 (interactive (list (todos-item-string) (todos-current-category))) 2450 (interactive (list (todos-item-string) (todos-current-category)))
2293 (unless (called-interactively-p t) 2451 (unless (called-interactively-p t)
2294 (todos-category-number cat) 2452 (todos-category-number cat)
2295 (todos-category-select)) 2453 (todos-category-select))
2296 (let* ((todo (todos-get-count 'todo cat)) 2454 (let* ((todo (todos-get-count 'todo cat))
2297 (maxnum (1+ todo)) 2455 (maxnum (if new (1+ todo) todo))
2298 (buffer-read-only) 2456 (buffer-read-only)
2299 priority candidate prompt) 2457 priority candidate prompt)
2300 (unless (zerop todo) 2458 (unless (zerop todo)
@@ -2306,139 +2464,189 @@ If point is on an empty line, insert the new item there."
2306 maxnum))))) 2464 maxnum)))))
2307 (setq prompt 2465 (setq prompt
2308 (when (or (< candidate 1) (> candidate maxnum)) 2466 (when (or (< candidate 1) (> candidate maxnum))
2309 (format "Priority must be an integer between 1 and %d.\n" maxnum))) 2467 (format "Priority must be an integer between 1 and %d.\n"
2468 maxnum)))
2310 (unless prompt (setq priority candidate))) 2469 (unless prompt (setq priority candidate)))
2311 ;; interactively, just relocate the item within its category 2470 ;; Interactively, just relocate the item within its category.
2312 (when (called-interactively-p) (todos-remove-item)) 2471 (when (called-interactively-p) (todos-remove-item))
2313 (goto-char (point-min)) 2472 (goto-char (point-min))
2314 (unless (= priority 1) (todos-forward-item (1- priority)))) 2473 (unless (= priority 1) (todos-forward-item (1- priority))))
2315 (todos-insert-with-overlays item))) 2474 (todos-insert-with-overlays item)))
2316 2475
2317;; (defun todos-set-item-top-priority () 2476;; FIXME: apply to marked items?
2318;; "Set priority of item at point in the top priorities listing."
2319;; (interactive)
2320;; (let* ((item (todos-item-string))
2321;; (cat (save-excursion
2322;; (re-search-forward
2323;; (concat todos-date-string-start todos-date-pattern
2324;; "\\( " diary-time-regexp
2325;; "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)")
2326;; nil t)
2327;; (match-string 1)))
2328;; (opoint (point))
2329;; (count 1)
2330;; (old-priority (save-excursion
2331;; (goto-char (point-min))
2332;; (while (< (point) opoint)
2333;; (todos-forward-item)
2334;; (setq count (1+ count))))))
2335;; )
2336
2337(defun todos-move-item (&optional file) 2477(defun todos-move-item (&optional file)
2338 "Move the current todo item to another, interactively named, category. 2478 "Move at least one todo item to another category.
2339 2479
2340If the named category is not one of the current todo categories, 2480If there are marked items, move all of these; otherwise, move
2341then it is created and the item becomes the first entry in that 2481the item at point.
2342category.
2343 2482
2344With optional non-nil argument FILE, first ask for another Todos 2483With non-nil argument FILE, first prompt for another Todos file and
2345file and then solicit a category within that file to move the 2484then a category in that file to move the item or items to.
2346item to." 2485
2486If the chosen category is not one of the existing categories,
2487then it is created and the item(s) become(s) the first
2488entry/entries in that category."
2347 (interactive) 2489 (interactive)
2348 (unless (or (todos-done-item-p) 2490 (unless (or (todos-done-item-p)
2349 (looking-at "^$")) ; between done and not done items 2491 (looking-at "^$")) ; We're between todo and done items.
2350 (let ((buffer-read-only) 2492 (let* ((buffer-read-only)
2351 (modified (buffer-modified-p)) 2493 (file1 todos-current-todos-file)
2352 (oldfile todos-current-todos-file) 2494 (cat1 (todos-current-category))
2353 (oldnum todos-category-number) 2495 (marked (assoc cat1 todos-categories-with-marks))
2354 (oldcat (todos-current-category)) 2496 (num todos-category-number)
2355 (item (todos-item-string)) 2497 (item (todos-item-string))
2356 (diary-item (todos-diary-item-p)) 2498 (diary-item (todos-diary-item-p))
2357 (newfile (if file (todos-read-file-name "Choose a Todos file: "))) 2499 (omark (save-excursion (todos-item-start) (point-marker)))
2358 (opoint (point)) 2500 (file2 (if file
2359 (orig-mrk (progn (todos-item-start) (point-marker))) 2501 (todos-read-file-name "Choose a Todos file: " nil t)
2360 newcat moved) 2502 file1))
2361 (unwind-protect 2503 (count 0)
2504 (count-diary 0)
2505 cat2 nmark)
2506 (set-buffer (find-file-noselect file2))
2507 (setq cat2 (let* ((pl (if (and marked (> (cdr marked) 1)) "s" ""))
2508 (name (todos-read-category
2509 (concat "Move item" pl " to category: ")))
2510 (prompt (concat "Choose a different category than "
2511 "the current one\n(type `"
2512 (key-description
2513 (car (where-is-internal
2514 'todos-set-item-priority)))
2515 "' to reprioritize item "
2516 "within the same category): ")))
2517 (while (equal name cat1)
2518 (setq name (todos-read-category prompt)))
2519 name))
2520 (set-buffer (get-file-buffer file1))
2521 (if marked
2362 (progn 2522 (progn
2363 (todos-remove-item) 2523 (setq item nil)
2364 (todos-item-counts oldcat 'delete) 2524 (goto-char (point-min))
2365 (and diary-item (todos-item-counts oldcat 'nondiary)) 2525 (while (not (eobp))
2366 (when newfile 2526 (when (todos-item-marked-p)
2367 (find-file-existing newfile) 2527 (setq item (concat item (todos-item-string) "\n"))
2368 (setq todos-current-todos-file newfile 2528 (setq count (1+ count))
2369 todos-categories (todos-make-categories-list))) 2529 (when (todos-diary-item-p)
2370 (setq newcat (todos-read-category "Move item to category: ")) 2530 (setq count-diary (1+ count-diary))))
2371 (unless (assoc newcat todos-categories) (todos-add-category newcat)) 2531 (todos-forward-item))
2372 (todos-set-item-priority item newcat) 2532 ;; Chop off last newline.
2373 (setq moved t) 2533 (setq item (substring item 0 -1)))
2374 (todos-item-counts newcat 'insert) 2534 (setq count 1)
2375 (and diary-item (todos-item-counts newcat 'diary))) 2535 (when (todos-diary-item-p) (setq count-diary 1)))
2376 (unless moved 2536 (set-window-buffer (selected-window)
2377 (if newfile 2537 (set-buffer (find-file-noselect file2)))
2378 (find-file-existing oldfile) 2538 (unless (assoc cat2 todos-categories) (todos-add-category cat2))
2379 (setq todos-current-todos-file oldfile 2539 (todos-set-item-priority item cat2 t)
2380 todos-categories (todos-make-categories-list))) 2540 (setq nmark (point-marker))
2381 (widen) 2541 (todos-set-count 'todo count)
2382 (goto-char orig-mrk) 2542 (todos-set-count 'diary count-diary)
2383 (todos-insert-with-overlays item) 2543 (todos-update-categories-sexp)
2384 (setq todos-category-number oldnum) 2544 (with-current-buffer (get-file-buffer file1)
2385 (todos-item-counts oldcat 'insert) 2545 (save-excursion
2386 (and diary-item (todos-item-counts oldcat 'diary)) 2546 (save-restriction
2387 (todos-category-select) 2547 (widen)
2388 (set-buffer-modified-p modified) 2548 (goto-char omark)
2389 (goto-char opoint)) 2549 (if marked
2390 (set-marker orig-mrk nil))))) 2550 (let (beg end)
2551 (setq item nil)
2552 (re-search-backward
2553 (concat "^" (regexp-quote todos-category-beg)) nil t)
2554 (forward-line)
2555 (setq beg (point))
2556 (re-search-forward
2557 (concat "^" (regexp-quote todos-category-done)) nil t)
2558 (setq end (match-beginning 0))
2559 (goto-char beg)
2560 (while (< (point) end)
2561 (if (todos-item-marked-p)
2562 (todos-remove-item)
2563 (todos-forward-item))))
2564 (todos-remove-item))))
2565 (todos-set-count 'todo (- count) cat1)
2566 (todos-set-count 'diary (- count-diary) cat1)
2567 (todos-update-categories-sexp))
2568 (set-window-buffer (selected-window)
2569 (set-buffer (find-file-noselect file2)))
2570 (setq todos-category-number (todos-category-number cat2))
2571 (todos-category-select)
2572 (goto-char nmark))))
2391 2573
2392(defun todos-move-item-to-file () 2574(defun todos-move-item-to-file ()
2393 "" 2575 "Move the current todo item to a category in another Todos file."
2394 (interactive) 2576 (interactive)
2395 (todos-move-item t)) 2577 (todos-move-item t))
2396 2578
2397(defun todos-item-done () 2579;; FIXME: apply to marked items?
2398 "Mark current item as done and move it to category's done section." 2580(defun todos-item-done (&optional arg)
2399 (interactive) 2581 "Tag this item as done and move it to category's done section.
2582With prefix argument ARG prompt for a comment and append it to the
2583done item."
2584 (interactive "P")
2400 (unless (or (todos-done-item-p) 2585 (unless (or (todos-done-item-p)
2401 (looking-at "^$")) 2586 (looking-at "^$"))
2402 (let* ((buffer-read-only) 2587 (let* ((buffer-read-only)
2403 (cat (todos-current-category))
2404 (item (todos-item-string)) 2588 (item (todos-item-string))
2405 (diary-item (todos-diary-item-p)) 2589 (diary-item (todos-diary-item-p))
2406 (date-string (calendar-date-string (calendar-current-date) t t)) 2590 (date-string (calendar-date-string (calendar-current-date) t t))
2407 (time-string (if todos-always-add-time-string ;FIXME: delete condition 2591 (time-string (if todos-always-add-time-string ;FIXME: delete condition
2408 (concat " " (substring (current-time-string) 11 16)) 2592 (concat " " (substring (current-time-string) 11 16))
2409 "")) 2593 ""))
2410 ;; FIXME: todos-nondiary-* 2594 ;; FIXME: todos-nondiary-* ?
2411 (done-item (concat "[" todos-done-string date-string time-string "] " 2595 (done-item (concat "[" todos-done-string date-string time-string "] "
2412 item))) 2596 item))
2597 (comment (and arg (read-string "Enter a comment: "))))
2413 (todos-remove-item) 2598 (todos-remove-item)
2599 (unless (zerop (length comment))
2600 (setq done-item (concat done-item " [" todos-comment-string ": "
2601 comment "]")))
2414 (save-excursion 2602 (save-excursion
2415 (widen) 2603 (widen)
2416 (re-search-forward (concat "^" (regexp-quote todos-category-done)) nil t) 2604 (re-search-forward (concat "^" (regexp-quote todos-category-done)) nil t)
2417 (forward-char) 2605 (forward-char)
2418 (todos-insert-with-overlays done-item)) 2606 (todos-insert-with-overlays done-item))
2419 (todos-item-counts cat 'done) 2607 (todos-set-count 'todo -1)
2420 (and diary-item (todos-item-counts cat 'nondiary)) 2608 (todos-set-count 'done 1)
2609 (and diary-item (todos-set-count 'diary -1))
2610 (todos-update-categories-sexp)
2421 (save-excursion (todos-category-select))))) 2611 (save-excursion (todos-category-select)))))
2422 2612
2423(defun todos-item-undo () 2613(defun todos-comment-done-item ()
2614 "Add a comment to this done item."
2615 (interactive)
2616 (when (todos-done-item-p)
2617 (let ((comment (read-string "Enter a comment: "))
2618 buffer-read-only)
2619 (todos-item-end)
2620 (insert " [" todos-comment-string ": " comment "]"))))
2621
2622;; FIXME: implement this or done item editing?
2623(defun todos-uncomment-done-item ()
2424 "" 2624 ""
2625 )
2626
2627;; FIXME: delete comment from restored item or just leave it up to user?
2628(defun todos-item-undo ()
2629 "Restore this done item to the todo section of this category."
2425 (interactive) 2630 (interactive)
2426 (when (todos-done-item-p) 2631 (when (todos-done-item-p)
2427 (let* ((buffer-read-only) 2632 (let* ((buffer-read-only)
2428 (cat (todos-current-category))
2429 (done-item (todos-item-string)) 2633 (done-item (todos-item-string))
2430 (opoint (point)) 2634 (opoint (point))
2431 (orig-mrk (progn (todos-item-start) (point-marker))) 2635 (orig-mrk (progn (todos-item-start) (point-marker)))
2432 (start (search-forward "] ")) ; end of done date string 2636 ;; Find the end of the date string added upon making item done.
2637 (start (search-forward "] "))
2433 (item (buffer-substring start (todos-item-end))) 2638 (item (buffer-substring start (todos-item-end)))
2434 undone) 2639 undone)
2435 (todos-remove-item) 2640 (todos-remove-item)
2641 ;; If user cancels before setting new priority, then restore everything.
2436 (unwind-protect 2642 (unwind-protect
2437 (progn 2643 (progn
2438 (todos-set-item-priority item cat) 2644 (todos-set-item-priority item (todos-current-category) t)
2439 (setq undone t) 2645 (setq undone t)
2440 (todos-item-counts cat 'undo) 2646 (todos-set-count 'todo 1)
2441 (and (todos-diary-item-p) (todos-item-counts cat 'diary))) 2647 (todos-set-count 'done -1)
2648 (and (todos-diary-item-p) (todos-set-count 'diary 1))
2649 (todos-update-categories-sexp))
2442 (unless undone 2650 (unless undone
2443 (widen) 2651 (widen)
2444 (goto-char orig-mrk) 2652 (goto-char orig-mrk)
@@ -2448,142 +2656,301 @@ item to."
2448 (goto-char opoint))) 2656 (goto-char opoint)))
2449 (set-marker orig-mrk nil))))) 2657 (set-marker orig-mrk nil)))))
2450 2658
2451(defun todos-archive-done-items () 2659(defun todos-archive-done-item-or-items (&optional all)
2452 "Archive the done items in the current category." 2660 "Archive at least one done item in this category.
2661
2662If there are marked done items (and no marked todo items),
2663archive all of these; otherwise, with non-nil argument ALL,
2664archive all done items in this category; otherwise, archive the
2665done item at point.
2666
2667If the archive of this file does not exist, it is created. If
2668this category does not exist in the archive, it is created."
2453 (interactive) 2669 (interactive)
2454 (let ((cat (todos-current-category))) 2670 (when (not (member (buffer-file-name) (funcall todos-files-function t)))
2455 (if (zerop (todos-get-count 'done cat)) 2671 (if (and all (zerop (todos-get-count 'done cat)))
2456 (message "No done items in this category") 2672 (message "No done items in this category")
2457 (when (y-or-n-p "Move all done items in this category to the archive? ") 2673 (catch 'end
2458 (let* ((afile (concat (file-name-sans-extension (buffer-file-name)) ".toda")) 2674 (let* ((cat (todos-current-category))
2459 (archive (find-file-noselect afile t)) 2675 (tbuf (current-buffer))
2460 beg end 2676 (marked (assoc cat todos-categories-with-marks))
2461 (buffer-read-only nil)) 2677 (afile (concat (file-name-sans-extension
2462 (save-excursion 2678 todos-current-todos-file) ".toda"))
2463 (save-restriction 2679 (archive (if (file-exists-p afile)
2680 (find-file-noselect afile t)
2681 (progn
2682 ;; todos-add-category requires an exisiting file...
2683 (with-current-buffer (get-buffer-create afile)
2684 (erase-buffer)
2685 (write-region (point-min) (point-max) afile
2686 nil 'nomessage nil t)))
2687 ;; ...but the file still lacks a categories sexp, so
2688 ;; visiting the file would barf on todos-set-categories,
2689 ;; hence we just return the buffer.
2690 (get-buffer afile)))
2691 (item (and (todos-done-item-p) (concat (todos-item-string) "\n")))
2692 (count 0)
2693 marked-items beg end all-done
2694 buffer-read-only)
2695 (cond
2696 (marked
2697 (save-excursion
2464 (goto-char (point-min)) 2698 (goto-char (point-min))
2465 (widen) 2699 (while (not (eobp))
2466 (setq beg (progn 2700 (if (todos-item-marked-p)
2467 (re-search-forward todos-done-string-start nil t) 2701 (if (not (todos-done-item-p))
2468 (match-beginning 0))) 2702 (throw 'end (message "Only done items can be archived"))
2469 (setq end (if (re-search-forward 2703 (concat marked-items (todos-item-string) "\n")
2470 (concat "^" (regexp-quote todos-category-beg)) nil t) 2704 (setq count (1+ count)))
2471 (match-beginning 0) 2705 (todos-forward-item)))))
2472 (point-max))) 2706 (all
2473 (setq done (buffer-substring beg end)) 2707 (if (y-or-n-p "Archive all done items in this category? ")
2474 (with-current-buffer archive 2708 (save-excursion
2475 (let (buffer-read-only) 2709 (save-restriction
2476 (widen) 2710 (goto-char (point-min))
2477 (goto-char (point-min)) 2711 (widen)
2478 (if (progn 2712 (setq beg (progn
2479 (re-search-forward 2713 (re-search-forward todos-done-string-start nil t)
2480 (concat "^" (regexp-quote (concat todos-category-beg cat))) 2714 (match-beginning 0))
2481 nil t) 2715 end (if (re-search-forward
2482 (re-search-forward (regexp-quote todos-category-done) nil t)) 2716 (concat "^" (regexp-quote todos-category-beg))
2483 (forward-char) 2717 nil t)
2484 (insert todos-category-beg cat "\n\n" todos-category-done "\n")) 2718 (match-beginning 0)
2485 (insert done) 2719 (point-max))
2486 (save-buffer))) 2720 all-done (buffer-substring beg end)
2487 (remove-overlays beg end) 2721 count (todos-get-count 'done))))
2488 (delete-region beg end) 2722 (throw 'end nil))))
2489 (todos-item-counts cat 'archive))))) 2723 (when (or marked all item)
2490 (message "Done items archived.")))) 2724 (with-current-buffer archive
2725 (let ((current todos-global-current-todos-file)
2726 (buffer-read-only))
2727 (widen)
2728 (goto-char (point-min))
2729 (if (progn
2730 (re-search-forward
2731 (concat "^" (regexp-quote (concat todos-category-beg cat)))
2732 nil t)
2733 (re-search-forward (regexp-quote todos-category-done) nil t))
2734 (forward-char)
2735 ;; todos-add-category uses t-c-t-f, so temporarily set it.
2736 (setq todos-current-todos-file afile)
2737 (todos-add-category cat)
2738 (goto-char (point-max)))
2739 (insert (cond (marked marked-items)
2740 (all all-done)
2741 (item)))
2742 (todos-set-count 'done (if (or marked all) count 1))
2743 (todos-update-categories-sexp)
2744 ;; Save to file now (using write-region in order not to visit
2745 ;; afile) so we can visit it later with todos-view-archived-items
2746 ;; or todos-show-archive.
2747 (write-region nil nil afile)
2748 (setq todos-current-todos-file current)))
2749 (with-current-buffer tbuf
2750 (cond ((or marked item)
2751 (and marked (goto-char (point-min)))
2752 (catch 'done
2753 (while (not (eobp))
2754 (if (or (and marked (todos-item-marked-p)) item)
2755 (progn
2756 (todos-remove-item)
2757 (todos-set-count 'done -1)
2758 (todos-set-count 'archived 1)
2759 ;; Don't leave point below last item.
2760 (and item (bolp) (eolp) (< (point-min) (point-max))
2761 (todos-backward-item))
2762 (when item
2763 (throw 'done (setq item nil))))
2764 (todos-forward-item)))))
2765 (all
2766 (remove-overlays beg end)
2767 (delete-region beg end)
2768 (todos-set-count 'done (- count))
2769 (todos-set-count 'archived count)))
2770 (when marked
2771 (remove-overlays (point-min) (point-max)
2772 'before-string todos-item-mark)
2773 (setq todos-categories-with-marks
2774 (assq-delete-all cat todos-categories-with-marks))
2775 (goto-char opoint))
2776 (todos-update-categories-sexp)
2777 (todos-prefix-overlays)
2778 ;; FIXME: Heisenbug: item displays mark -- but not when edebugging
2779 (remove-overlays (point-min) (point-max)
2780 'before-string todos-item-mark)))
2781 (display-buffer (find-file-noselect afile) t)
2782 ;; FIXME: how to avoid switch-to-buffer and still get tbuf above
2783 ;; afile? What about pop-to-buffer-same-window in recent trunk?
2784 (switch-to-buffer tbuf))))))
2785
2786(defun todos-archive-category-done-items ()
2787 "Move all done items in this category to its archive."
2788 (interactive)
2789 (todos-archive-done-item-or-items t))
2491 2790
2492(defun todos-unarchive-category () 2791(defun todos-unarchive-items (&optional all)
2493 "Restore this archived category to done items in Todos file." 2792 "Unarchive at least one item in this archive category.
2793
2794If there are marked items, unarchive all of these; otherwise,
2795with non-nil argument ALL, unarchive all items in this category;
2796otherwise, unarchive the item at point.
2797
2798Unarchived items are restored as done items to the corresponding
2799category in the Todos file, inserted at the end of done section.
2800If all items in the archive category were restored, the category
2801is deleted from the archive. If this was the only category in the
2802archive, the archive file is deleted."
2494 (interactive) 2803 (interactive)
2495 (when (y-or-n-p "Restore all items in this category to Todos file as done items? ") 2804 (when (member (buffer-file-name) (funcall todos-files-function t))
2496 (let ((buffer-read-only nil) 2805 (catch 'end
2497 (tbuf (find-file-noselect 2806 (let* ((buffer-read-only nil)
2498 (concat (file-name-sans-extension (buffer-file-name)) ".todo") 2807 (tbuf (find-file-noselect
2499 t)) 2808 (concat (file-name-sans-extension todos-current-todos-file)
2500 (cat (todos-current-category)) 2809 ".todo") t))
2501 (items (buffer-substring (point-min) (point-max)))) 2810 (cat (todos-current-category))
2502 (with-current-buffer tbuf 2811 (marked (assoc cat todos-categories-with-marks))
2503 (let (buffer-read-only) 2812 (item (concat (todos-item-string) "\n"))
2504 (widen) 2813 (all-items (buffer-substring (point-min) (point-max)))
2814 (all-count (todos-get-count 'done))
2815 marked-items marked-count)
2816 (save-excursion
2505 (goto-char (point-min)) 2817 (goto-char (point-min))
2506 (re-search-forward (concat "^" (regexp-quote 2818 (while (not (eobp))
2507 (concat todos-category-beg cat))) 2819 (when (todos-item-marked-p)
2508 nil t) 2820 (concat marked-items (todos-item-string) "\n")
2509 (if (re-search-forward (concat "^" (regexp-quote todos-category-beg)) 2821 (setq marked-count (1+ marked-count)))
2510 nil t) 2822 (todos-forward-item)))
2511 (goto-char (match-beginning 0)) 2823 ;; Restore items to end of category's done section and update counts.
2512 (goto-char (point-max))) 2824 (with-current-buffer tbuf
2513 (insert items))) 2825 (let (buffer-read-only)
2514 (widen) 2826 (widen)
2515 (let ((beg (re-search-backward (concat "^" 2827 (goto-char (point-min))
2516 (regexp-quote todos-category-beg) 2828 (re-search-forward (concat "^" (regexp-quote
2517 cat) nil t)) 2829 (concat todos-category-beg cat)))
2518 (end (if (re-search-forward 2830 nil t)
2519 (concat "^" (regexp-quote todos-category-beg)) nil t 2) 2831 (if (re-search-forward (concat "^" (regexp-quote todos-category-beg))
2520 (match-beginning 0) 2832 nil t)
2521 (point-max)))) 2833 (goto-char (match-beginning 0))
2522 (remove-overlays beg end) 2834 (goto-char (point-max)))
2523 (delete-region beg end)) 2835 (cond (marked
2524 (goto-char (point-min)) 2836 (insert marked-items)
2525 (if (re-search-forward 2837 (todos-set-count 'done marked-count)
2526 (concat "^" (regexp-quote todos-category-beg)) nil t) 2838 (todos-set-count 'archived (- marked-count)))
2527 (progn 2839 (all
2528 ;; delete category from archive 2840 (if (y-or-n-p (concat "Restore this category's items "
2529 (setq todos-categories (delete (assoc cat todos-categories) 2841 "to Todos file as done items "
2530 todos-categories)) 2842 "and delete this category? "))
2531 (todos-update-categories-sexp)) 2843 (progn (insert all-items)
2532 ;; no more categories in archive, so delete it 2844 (todos-set-count 'done all-count)
2533 (set-buffer-modified-p nil) ; no questions 2845 (todos-set-count 'archived (- all-count)))
2534 (delete-file (buffer-file-name)) 2846 (throw 'end nil)))
2535 (kill-buffer)) 2847 (t
2536 (let ((tfile (buffer-file-name tbuf)) 2848 (insert item)
2537 (todos-show-with-done t)) 2849 (todos-set-count 'done 1)
2538 (find-file tfile) 2850 (todos-set-count 'archived -1)))
2539 (setq todos-current-todos-file tfile 2851 (todos-update-categories-sexp)))
2540 ;; also updates item counts 2852 ;; Delete restored items from archive.
2541 todos-categories (todos-make-categories-list t) 2853 (cond ((or marked item)
2542 todos-category-number (todos-category-number cat)) 2854 (and marked (goto-char (point-min)))
2543 (todos-show) 2855 (catch 'done
2544 (message "Items unarchived."))))) 2856 (while (not (eobp))
2545 2857 (if (or (and marked (todos-item-marked-p)) item)
2546(defun todos-toggle-item-diary-inclusion () 2858 (progn
2547 "" 2859 (todos-remove-item)
2860 (todos-set-count 'done -1)
2861 ;; Don't leave point below last item.
2862 (and item (bolp) (eolp) (< (point-min) (point-max))
2863 (todos-backward-item))
2864 (when item
2865 (throw 'done (setq item nil))))
2866 (todos-forward-item)))))
2867 (all
2868 (remove-overlays (point-min) (point-max))
2869 (delete-region (point-min) (point-max))
2870 (todos-set-count 'done (- all-count))))
2871 ;; If that was the last category in the archive, delete the whole file.
2872 (if (= (length todos-categories) 1)
2873 (progn
2874 (delete-file todos-current-todos-file)
2875 ;; Don't bother confirming killing the archive buffer.
2876 (set-buffer-modified-p nil)
2877 (kill-buffer))
2878 ;; Otherwise, if the archive category is now empty, delete it.
2879 (when (eq (point-min) (point-max))
2880 (widen)
2881 (let ((beg (re-search-backward
2882 (concat "^" (regexp-quote todos-category-beg) cat)
2883 nil t))
2884 (end (if (re-search-forward
2885 (concat "^" (regexp-quote todos-category-beg))
2886 nil t 2)
2887 (match-beginning 0)
2888 (point-max))))
2889 (remove-overlays beg end)
2890 (delete-region beg end)
2891 (setq todos-categories (delete (assoc cat todos-categories)
2892 todos-categories))
2893 (todos-update-categories-sexp))))
2894 ;; Visit category in Todos file and show restored done items.
2895 (let ((tfile (buffer-file-name tbuf))
2896 (todos-show-with-done t))
2897 (set-window-buffer (selected-window)
2898 (set-buffer (find-file-noselect tfile)))
2899 (todos-category-number cat)
2900 (todos-show)
2901 (message "Items unarchived."))))))
2902
2903(defun todos-unarchive-category ()
2904 "Unarchive all items in this category. See `todos-unarchive-items'."
2548 (interactive) 2905 (interactive)
2549 (save-excursion 2906 (todos-unarchive-items t))
2550 (let* ((buffer-read-only) 2907
2551 (beg (todos-item-start)) 2908(defun todos-toggle-diary-inclusion (&optional all)
2552 (lim (save-excursion (todos-item-end))) 2909 "Toggle diary status of one or more todo items in this category.
2553 (end (save-excursion 2910
2554 (or (todos-time-string-match lim) 2911If a candidate item is marked with `todos-nondiary-marker',
2555 (todos-date-string-match lim)))) 2912remove this marker; otherwise, insert it.
2556 (cat (todos-current-category))) 2913
2557 (if (looking-at (regexp-quote todos-nondiary-start)) 2914With non-nil argument ALL toggle the diary status of all todo
2558 (progn 2915items in this category; otherwise, if there are marked todo
2559 (replace-match "") 2916items, toggle the diary status of all and only these, otherwise
2560 (search-forward todos-nondiary-end (1+ end) t) 2917toggle the diary status of the item at point. "
2561 (replace-match "") 2918 (interactive)
2562 (todos-item-counts cat 'nondiary)) 2919 (let ((marked (assoc (todos-current-category)
2563 (when end 2920 todos-categories-with-marks)))
2564 (insert todos-nondiary-start) 2921 (catch 'stop
2565 (goto-char (1+ end)) 2922 (save-excursion
2566 (insert todos-nondiary-end) 2923 (save-restriction
2567 (todos-item-counts cat 'diary)))))) 2924 (when (or marked all) (goto-char (point-min)))
2568 2925 (while (not (eobp))
2569(defun todos-toggle-diary-inclusion (arg) 2926 (if (todos-done-item-p)
2570 "" 2927 (throw 'stop (message "Done items cannot be changed"))
2571 (interactive "p") 2928 (unless (and marked (not (todos-item-marked-p)))
2572 (save-excursion 2929 (save-excursion
2573 (save-restriction 2930 (let* ((buffer-read-only)
2574 (when (eq arg 2) (widen)) ;FIXME: don't toggle done items 2931 (beg (todos-item-start))
2575 (when (or (eq arg 1) (eq arg 2)) 2932 (lim (save-excursion (todos-item-end)))
2576 (goto-char (point-min)) 2933 (end (save-excursion
2577 (when (eq arg 2) 2934 (or (todos-time-string-matcher lim)
2578 (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t) 2935 (todos-date-string-matcher lim)))))
2579 (forward-line) 2936 (if (looking-at (regexp-quote todos-nondiary-start))
2580 (when (looking-at (regexp-quote todos-category-done)) (forward-line))) 2937 (progn
2581 (while (not (eobp)) 2938 (replace-match "")
2582 (todos-toggle-item-diary-inclusion) 2939 (search-forward todos-nondiary-end (1+ end) t)
2583 (todos-forward-item)))))) 2940 (replace-match "")
2941 (todos-set-count 'diary 1))
2942 (when end
2943 (insert todos-nondiary-start)
2944 (goto-char (1+ end))
2945 (insert todos-nondiary-end)
2946 (todos-set-count 'diary -1))))))
2947 (unless (or marked all) (throw 'stop nil))
2948 (todos-forward-item))))))
2949 (todos-update-categories-sexp)))
2584 2950
2585(defun todos-toggle-item-diary-nonmarking () 2951(defun todos-toggle-item-diary-nonmarking ()
2586 "" 2952 "Mark or unmark this todos diary item for calendar display.
2953See `diary-nonmarking-symbol'."
2587 (interactive) 2954 (interactive)
2588 (let ((buffer-read-only)) 2955 (let ((buffer-read-only))
2589 (save-excursion 2956 (save-excursion
@@ -2594,7 +2961,8 @@ item to."
2594 (insert diary-nonmarking-symbol)))))) 2961 (insert diary-nonmarking-symbol))))))
2595 2962
2596(defun todos-toggle-diary-nonmarking () 2963(defun todos-toggle-diary-nonmarking ()
2597 "" 2964 "Mark or unmark this category's todos diary items for calendar.
2965See `diary-nonmarking-symbol'."
2598 (interactive) 2966 (interactive)
2599 (save-excursion 2967 (save-excursion
2600 (goto-char (point-min)) 2968 (goto-char (point-min))
@@ -2602,52 +2970,28 @@ item to."
2602 (todos-toggle-item-diary-nonmarking) 2970 (todos-toggle-item-diary-nonmarking)
2603 (todos-forward-item)))) 2971 (todos-forward-item))))
2604 2972
2605;; FIXME: save to a file named according to the current todos file 2973(defun todos-print (&optional to-file)
2606(defun todos-save-top-priorities (&optional nof-priorities) 2974 "Produce a printable version of the current Todos buffer.
2607 "Save top priorities for each category in `todos-file-top'. 2975This includes overlays, indentation, and, depending on the value
2608 2976of `todos-print-function', faces. With non-nil argument TO-FILE
2609Number of entries for each category is given by NOF-PRIORITIES which 2977write the printable version to a file; otherwise, send it to the
2610defaults to `todos-show-priorities'." 2978default printer."
2611 (interactive "P")
2612 (save-window-excursion
2613 (save-excursion
2614 (save-restriction
2615 (todos-top-priorities nof-priorities)
2616 (set-buffer todos-tmp-buffer-name)
2617 (write-file todos-file-top)
2618 (kill-this-buffer)))))
2619
2620;; ;;;###autoload
2621;; (defun todos-print (&optional category-pr-page)
2622;; "Print todo summary using `todos-print-function'.
2623;; If CATEGORY-PR-PAGE is non-nil, a page separator `^L' is inserted
2624;; between each category.
2625
2626;; Number of entries for each category is given by `todos-print-priorities'."
2627;; (interactive "P")
2628;; (when (yes-or-no-p "Print Todos list? ")
2629;; (save-window-excursion
2630;; (save-excursion
2631;; (save-restriction
2632;; (todos-top-priorities todos-print-priorities
2633;; category-pr-page)
2634;; (set-buffer todos-tmp-buffer-name)
2635;; (and (funcall todos-print-function)
2636;; (kill-this-buffer))
2637;; (message "Todo printing done."))))))
2638
2639(defun todos-print ()
2640 ""
2641 (interactive) 2979 (interactive)
2642 (let ((buf (cond ((eq major-mode 'todos-mode) 2980 (let ((buf todos-tmp-buffer-name) ;FIXME
2643 (concat "Category: " (todos-current-category) " (" 2981 (header (cond
2644 (file-name-nondirectory todos-current-todos-file) ") ")) 2982 ((eq major-mode 'todos-mode)
2645 ((eq major-mode 'todos-top-priorities-mode) 2983 (concat "Todos File: "
2646 "Todos Top Priorities"))) 2984 (file-name-sans-extension
2647 (prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string)) 2985 (file-name-nondirectory todos-current-todos-file))
2986 "\nCategory: " (todos-current-category)))
2987 ((eq major-mode 'todos-filter-items-mode)
2988 "Todos Top Priorities")))
2989 (prefix (propertize (concat todos-prefix " ")
2990 'face 'todos-prefix-string))
2648 (num 0) 2991 (num 0)
2649 (fill-prefix (make-string todos-indent-to-here 32)) 2992 (fill-prefix (make-string todos-indent-to-here 32))
2650 (content (buffer-string))) 2993 (content (buffer-string))
2994 file)
2651 (with-current-buffer (get-buffer-create buf) 2995 (with-current-buffer (get-buffer-create buf)
2652 (insert content) 2996 (insert content)
2653 (goto-char (point-min)) 2997 (goto-char (point-min))
@@ -2660,15 +3004,28 @@ defaults to `todos-show-priorities'."
2660 'face 'todos-prefix-string))) 3004 'face 'todos-prefix-string)))
2661 (insert prefix) 3005 (insert prefix)
2662 (fill-region beg end)) 3006 (fill-region beg end))
2663 (todos-forward-item)) 3007 ;; Calling todos-forward-item infloops at todos-item-start due to
2664 ;; FIXME: ask user to choose between sending to printer: 3008 ;; non-overlay prefix, so search for item start instead.
2665 ;; (ps-print-buffer-with-faces) 3009 (if (re-search-forward todos-item-start nil t)
2666 ;; and printing to a file: 3010 (beginning-of-line)
2667 (ps-spool-buffer-with-faces) 3011 (goto-char (point-max))))
2668 ;; (write-file ) 3012 (if (re-search-backward (concat "^" (regexp-quote todos-category-done))
2669 ) 3013 nil t)
3014 (replace-match todos-done-separator))
3015 (goto-char (point-min))
3016 (insert header)
3017 (newline 2)
3018 (if to-file
3019 (let ((file (read-file-name "Print to file: ")))
3020 (funcall todos-print-function file))
3021 (funcall todos-print-function)))
2670 (kill-buffer buf))) 3022 (kill-buffer buf)))
2671 3023
3024(defun todos-print-to-file ()
3025 "Save printable version of this Todos buffer to a file."
3026 (interactive)
3027 (todos-print t))
3028
2672;; --------------------------------------------------------------------------- 3029;; ---------------------------------------------------------------------------
2673 3030
2674;;; Internals 3031;;; Internals
@@ -2678,9 +3035,9 @@ defaults to `todos-show-priorities'."
2678 (concat "\\(?:" dayname "\\|" 3035 (concat "\\(?:" dayname "\\|"
2679 (let ((dayname) 3036 (let ((dayname)
2680 (monthname (format "\\(?:%s\\|\\*\\)" 3037 (monthname (format "\\(?:%s\\|\\*\\)"
2681 (diary-name-pattern calendar-month-name-array 3038 (diary-name-pattern
2682 calendar-month-abbrev-array 3039 calendar-month-name-array
2683 t))) 3040 calendar-month-abbrev-array t)))
2684 (month "\\(?:[0-9]+\\|\\*\\)") 3041 (month "\\(?:[0-9]+\\|\\*\\)")
2685 (day "\\(?:[0-9]+\\|\\*\\)") 3042 (day "\\(?:[0-9]+\\|\\*\\)")
2686 (year "-?\\(?:[0-9]+\\|\\*\\)")) 3043 (year "-?\\(?:[0-9]+\\|\\*\\)"))
@@ -2689,34 +3046,39 @@ defaults to `todos-show-priorities'."
2689 "Regular expression matching a Todos date header.") 3046 "Regular expression matching a Todos date header.")
2690 3047
2691(defvar todos-date-string-start 3048(defvar todos-date-string-start
3049 ;; FIXME: with ? matches anything
2692 (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|" 3050 (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|"
2693 (regexp-quote diary-nonmarking-symbol) "\\)?") ;FIXME: matches anything 3051 (regexp-quote diary-nonmarking-symbol) "\\)?")
2694 "Regular expression matching part of item header before the date.") 3052 "Regular expression matching part of item header before the date.")
2695 3053
2696(defvar todos-done-string-start 3054(defvar todos-done-string-start
2697 (concat "^" (regexp-quote todos-nondiary-start) (regexp-quote todos-done-string)) 3055 (concat "^\\[" (regexp-quote todos-done-string))
2698 "Regular expression matching start of done item.") 3056 "Regular expression matching start of done item.")
2699 3057
2700;; FIXME: rename these *-matcher 3058(defun todos-date-string-matcher (lim)
2701(defun todos-date-string-match (lim)
2702 "Search for Todos date strings within LIM for font-locking." 3059 "Search for Todos date strings within LIM for font-locking."
2703 (re-search-forward (concat todos-date-string-start "\\(?1:" 3060 (re-search-forward
2704 todos-date-pattern "\\)") lim t)) 3061 (concat todos-date-string-start "\\(?1:" todos-date-pattern "\\)") lim t))
2705 3062
2706(defun todos-time-string-match (lim) 3063(defun todos-time-string-matcher (lim)
2707 "Search for Todos time strings within LIM for font-locking." 3064 "Search for Todos time strings within LIM for font-locking."
2708 (re-search-forward (concat todos-date-string-start todos-date-pattern 3065 (re-search-forward (concat todos-date-string-start todos-date-pattern
2709 " \\(?1:" diary-time-regexp "\\)") lim t)) 3066 " \\(?1:" diary-time-regexp "\\)") lim t))
2710 3067
2711(defun todos-done-string-match (lim) 3068(defun todos-done-string-matcher (lim)
2712 "Search for Todos done headers within LIM for font-locking." 3069 "Search for Todos done headers within LIM for font-locking."
2713 (re-search-forward (concat todos-done-string-start 3070 (re-search-forward (concat todos-done-string-start
2714 "[^][]+]") 3071 "[^][]+]")
2715 lim t)) 3072 lim t))
2716 3073
2717(defun todos-category-string-match (lim) 3074(defun todos-comment-string-matcher (lim)
3075 "Search for Todos done comment within LIM for font-locking."
3076 (re-search-forward (concat "\\[\\(?1:" todos-comment-string "\\):")
3077 lim t))
3078
3079(defun todos-category-string-matcher (lim)
2718 "Search for Todos category headers within LIM for font-locking." 3080 "Search for Todos category headers within LIM for font-locking."
2719 (if (eq major-mode 'todos-top-priorities-mode) 3081 (if (eq major-mode 'todos-filter-items-mode)
2720 (re-search-forward 3082 (re-search-forward
2721 ;; (concat "^\\(?1:" (regexp-quote todos-category-beg) ".*\\)$") 3083 ;; (concat "^\\(?1:" (regexp-quote todos-category-beg) ".*\\)$")
2722 (concat "\\(?:^\\[?" todos-date-pattern "\\(?: " diary-time-regexp 3084 (concat "\\(?:^\\[?" todos-date-pattern "\\(?: " diary-time-regexp
@@ -2739,62 +3101,77 @@ defaults to `todos-show-priorities'."
2739 (forward-line))))) 3101 (forward-line)))))
2740 (message "This Todos file is well-formatted.")) 3102 (message "This Todos file is well-formatted."))
2741 3103
3104(defun todos-after-find-file ()
3105 "Show Todos files correctly when visited from outside of Todos mode."
3106 (and (member this-command todos-visit-files-commands)
3107 (= (- (point-max) (point-min)) (buffer-size))
3108 (member major-mode '(todos-mode todos-archive-mode))
3109 (todos-category-select)))
3110
2742(defun todos-wrap-and-indent () 3111(defun todos-wrap-and-indent ()
2743 "" 3112 "Use word wrapping on long lines and indent with a wrap prefix.
2744 (make-local-variable 'word-wrap) 3113The amount of indentation is given by user option
2745 (setq word-wrap t) 3114`todos-indent-to-here'."
2746 (make-local-variable 'wrap-prefix) 3115 (set (make-local-variable 'word-wrap) t)
2747 (setq wrap-prefix (make-string todos-indent-to-here 32)) 3116 (set (make-local-variable 'wrap-prefix) (make-string todos-indent-to-here 32))
2748 (unless (member '(continuation) fringe-indicator-alist) 3117 (unless (member '(continuation) fringe-indicator-alist)
2749 (push '(continuation) fringe-indicator-alist))) 3118 (push '(continuation) fringe-indicator-alist)))
2750 3119
2751(defun todos-indent () 3120(defun todos-indent ()
2752 "" 3121 "Indent from point to `todos-indent-to-here'."
2753 (indent-to todos-indent-to-here todos-indent-to-here)) 3122 (indent-to todos-indent-to-here todos-indent-to-here))
2754 3123
2755(defun todos-prefix-overlays () 3124(defun todos-prefix-overlays ()
2756 "" 3125 "Put before-string overlay in front of this category's items.
3126The overlay's value is the string `todos-prefix' or with non-nil
3127`todos-number-prefix' an integer in the sequence from 1 to the
3128number of todo or done items in the category indicating the
3129item's priority. Todo and done items are numbered independently
3130of each other."
2757 (when (or todos-number-prefix 3131 (when (or todos-number-prefix
2758 (not (string-match "^[[:space:]]*$" todos-prefix))) 3132 (not (string-match "^[[:space:]]*$" todos-prefix)))
2759 (let ((prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string)) 3133 (let ((prefix (propertize (concat todos-prefix " ")
3134 'face 'todos-prefix-string))
2760 (num 0)) 3135 (num 0))
2761 (save-excursion 3136 (save-excursion
2762 (goto-char (point-min)) 3137 (goto-char (point-min))
2763 (while (not (eobp)) 3138 (while (not (eobp))
2764 (when (or (todos-date-string-match (line-end-position)) 3139 (when (or (todos-date-string-matcher (line-end-position))
2765 (todos-done-string-match (line-end-position))) 3140 (todos-done-string-matcher (line-end-position)))
2766 (goto-char (match-beginning 0)) 3141 (goto-char (match-beginning 0))
2767 (when todos-number-prefix 3142 (when todos-number-prefix
2768 (setq num (1+ num)) 3143 (setq num (1+ num))
2769 ;; reset number for done items 3144 ;; Reset number for done items.
2770 (when 3145 (when
2771 ;; FIXME: really need this? 3146 ;; FIXME: really need this?
2772 ;; if last not done item is multiline, then 3147 ;; If last not done item is multiline, then
2773 ;; todos-done-string-match skips empty line, so have 3148 ;; todos-done-string-matcher skips empty line, so have
2774 ;; to look back. 3149 ;; to look back.
2775 (and (looking-at ;; (concat "^\\[" (regexp-quote todos-done-string)) 3150 (and (looking-at todos-done-string-start)
2776 todos-done-string-start) 3151 (looking-back (concat "^"
2777 (looking-back (concat "^" (regexp-quote todos-category-done) 3152 (regexp-quote todos-category-done)
2778 "\n"))) 3153 "\n")))
2779 (setq num 1)) 3154 (setq num 1))
2780 (setq prefix (propertize (concat (number-to-string num) " ") 3155 (setq prefix (propertize (concat (number-to-string num) " ")
2781 'face 'todos-prefix-string))) 3156 'face 'todos-prefix-string)))
2782 (let* ((ovs (overlays-in (point) (point))) 3157 (let ((ovs (overlays-in (point) (point)))
2783 (ov-pref (car ovs)) 3158 marked ov-pref)
2784 (val (when ov-pref (overlay-get ov-pref 'before-string)))) 3159 (if ovs
2785 ;; FIXME: is this possible? 3160 (dolist (ov ovs)
2786 (when (and (> (length ovs) 1) 3161 (let ((val (overlay-get ov 'before-string)))
2787 (not (equal val prefix))) 3162 (if (equal val "*")
2788 (setq ov-pref (cadr ovs))) 3163 (setq marked t)
2789 (when (not (equal val prefix)) 3164 (setq ov-pref val)))))
2790 ;; (when ov-pref (delete-overlay ov-pref)) ; why doesn't this work ??? 3165 (unless (equal ov-pref prefix)
2791 (remove-overlays (point) (point)); 'before-string val) ; or this ??? 3166 (remove-overlays (point) (point)) ; 'before-string) doesn't work
2792 (setq ov-pref (make-overlay (point) (point))) 3167 (overlay-put (make-overlay (point) (point))
2793 (overlay-put ov-pref 'before-string prefix)))) 3168 'before-string prefix)
2794 (forward-line)))))) 3169 (and marked (overlay-put (make-overlay (point) (point))
3170 'before-string todos-item-mark)))))
3171 (forward-line))))))
2795 3172
2796(defun todos-reset-prefix (symbol value) 3173(defun todos-reset-prefix (symbol value)
2797 "Set SYMBOL's value to VALUE, and ." ; FIXME 3174 "The :set function for `todos-prefix' and `todos-number-prefix'."
2798 (let ((oldvalue (symbol-value symbol)) 3175 (let ((oldvalue (symbol-value symbol))
2799 (files (append todos-files todos-archives))) 3176 (files (append todos-files todos-archives)))
2800 (custom-set-default symbol value) 3177 (custom-set-default symbol value)
@@ -2809,106 +3186,118 @@ defaults to `todos-show-priorities'."
2809 (while (not (eobp)) 3186 (while (not (eobp))
2810 (remove-overlays (point) (point)); 'before-string prefix) 3187 (remove-overlays (point) (point)); 'before-string prefix)
2811 (forward-line))) 3188 (forward-line)))
2812 ;; activate the new setting (save-restriction does not help) 3189 ;; Activate the new setting (save-restriction does not help).
2813 (save-excursion (todos-category-select)))))))) 3190 (save-excursion (todos-category-select))))))))
2814 3191
2815(defun todos-reset-separator (symbol value) 3192(defun todos-reset-nondiary-marker (symbol value)
2816 "Set SYMBOL's value to VALUE, and ." ; FIXME 3193 "The :set function for user option `todos-nondiary-marker'."
2817 (let ((oldvalue (symbol-value symbol)) 3194 (let ((oldvalue (symbol-value symbol))
2818 (files (append todos-files todos-archives))) 3195 (files (append todos-files todos-archives)))
2819 (custom-set-default symbol value) 3196 (custom-set-default symbol value)
3197 ;; Need to reset these to get font-locking right.
3198 (setq todos-nondiary-start (nth 0 todos-nondiary-marker)
3199 todos-nondiary-end (nth 1 todos-nondiary-marker)
3200 todos-date-string-start
3201 ;; FIXME: with ? matches anything
3202 (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|"
3203 (regexp-quote diary-nonmarking-symbol) "\\)?"))
2820 (when (not (equal value oldvalue)) 3204 (when (not (equal value oldvalue))
2821 (dolist (f files) 3205 (dolist (f files)
2822 (with-current-buffer (find-file-noselect f) 3206 (with-current-buffer (find-file-noselect f)
2823 (save-window-excursion 3207 (let (buffer-read-only)
2824 (todos-show) 3208 (widen)
2825 (save-excursion 3209 (goto-char (point-min))
2826 (goto-char (point-min)) 3210 (while (not (eobp))
2827 (when (re-search-forward 3211 (if (re-search-forward
2828 ;; (concat "^\\[" (regexp-quote todos-done-string)) 3212 (concat "^\\(" todos-done-string-start "[^][]+] \\)?"
2829 todos-done-string-start nil t) 3213 "\\(?1:" (regexp-quote (car oldvalue))
2830 (remove-overlays (point) (point)))) 3214 "\\)" todos-date-pattern "\\( "
2831 ;; activate the new setting (save-restriction does not help) 3215 diary-time-regexp "\\)?\\(?2:"
2832 ;; FIXME: need to wrap in save-excursion ? 3216 (regexp-quote (cadr oldvalue)) "\\)")
3217 nil t)
3218 (progn
3219 (replace-match (nth 0 value) t t nil 1)
3220 (replace-match (nth 1 value) t t nil 2))
3221 (forward-line)))
2833 (todos-category-select))))))) 3222 (todos-category-select)))))))
2834 3223
2835(defun todos-reset-done-string (symbol value) 3224(defun todos-reset-done-string (symbol value)
2836 "Set SYMBOL's value to VALUE, and ." ; FIXME 3225 "The :set function for user option `todos-done-string'."
2837 ;; (let ((oldvalue (symbol-value symbol))) 3226 (let ((oldvalue (symbol-value symbol))
2838 ;; (custom-set-default symbol value) 3227 (files (append todos-files todos-archives)))
2839 ;; (when (not (equal value oldvalue)) 3228 (custom-set-default symbol value)
2840 ;; (save-window-excursion 3229 ;; Need to reset this to get font-locking right.
2841 ;; (todos-show) 3230 (setq todos-done-string-start
2842 ;; (save-excursion 3231 (concat "^\\[" (regexp-quote todos-done-string)))
2843 ;; (goto-char (point-min)) 3232 (when (not (equal value oldvalue))
2844 ;; (when (re-search-forward ;; (concat "^\\[" (regexp-quote todos-done-string)) 3233 (dolist (f files)
2845 ;; todos-done-string-start nil t) 3234 (with-current-buffer (find-file-noselect f)
2846 ;; (remove-overlays (point) (point)))) 3235 (let (buffer-read-only)
2847 ;; ;; activate the new setting (save-restriction does not help) 3236 (widen)
2848 ;; ;; FIXME: need to wrap in save-excursion ? 3237 (goto-char (point-min))
2849 ;; (todos-category-select)))) 3238 (while (not (eobp))
2850 ) 3239 (if (re-search-forward
3240 (concat "^" (regexp-quote todos-nondiary-start)
3241 "\\(" (regexp-quote oldvalue) "\\)")
3242 nil t)
3243 (replace-match value t t nil 1)
3244 (forward-line)))
3245 (todos-category-select)))))))
3246
3247(defun todos-reset-comment-string (symbol value)
3248 "The :set function for user option `todos-comment-string'."
3249 (let ((oldvalue (symbol-value symbol))
3250 (files (append todos-files todos-archives)))
3251 (custom-set-default symbol value)
3252 (when (not (equal value oldvalue))
3253 (dolist (f files)
3254 (with-current-buffer (find-file-noselect f)
3255 (let (buffer-read-only)
3256 (save-excursion
3257 (widen)
3258 (goto-char (point-min))
3259 (while (not (eobp))
3260 (if (re-search-forward
3261 (concat
3262 "\\[\\(" (regexp-quote oldvalue) "\\): [^]]*\\]")
3263 nil t)
3264 (replace-match value t t nil 1)
3265 (forward-line)))
3266 (todos-category-select))))))))
2851 3267
2852(defun todos-reset-categories (symbol value) 3268(defun todos-reset-categories (symbol value)
2853 "Set SYMBOL's value to VALUE, and ." ; FIXME 3269 "The :set function for `todos-ignore-archived-categories'."
2854 (custom-set-default symbol value) 3270 (custom-set-default symbol value)
2855 (save-window-excursion 3271 (dolist (f (funcall todos-files-function))
2856 (todos-show) 3272 (with-current-buffer (find-file-noselect f)
2857 (setq todos-categories 3273 (if value
2858 (if value 3274 (setq todos-categories-full todos-categories
2859 (todos-truncate-categories-list) 3275 todos-categories (todos-truncate-categories-list))
2860 ;; FIXME: with-current-buffer Todos 3276 (setq todos-categories todos-categories-full
2861 ;; file and update 3277 todos-categories-full nil))
2862 ;; todos-categories-sexp 3278 (todos-category-select))))
2863 (todos-make-categories-list t))))) 3279
2864 ;; (save-excursion 3280(defun todos-toggle-show-current-file (symbol value)
2865 ;; ;; activate the new setting (save-restriction does not help) 3281 "The :set function for user option `todos-show-current-file'."
2866 ;; ;; FIXME: need to wrap in save-excursion ?
2867 ;; (todos-category-select)))))
2868
2869(defun todos-toggle-switch-todos-file-noninteractively (symbol value)
2870 ""
2871 (custom-set-default symbol value) 3282 (custom-set-default symbol value)
2872 (if value 3283 (if value
2873 (add-hook 'post-command-hook 3284 (add-hook 'pre-command-hook 'todos-show-current-file nil t)
2874 'todos-switch-todos-file nil t) 3285 (remove-hook 'pre-command-hook 'todos-show-current-file t)))
2875 (remove-hook 'post-command-hook 3286
2876 'todos-switch-todos-file t))) 3287(defun todos-show-current-file ()
2877 3288 "Visit current instead of default Todos file with `todos-show'.
2878(defun todos-switch-todos-file (&optional file) ;FIXME: need FILE? 3289This function is added to `pre-command-hook' when user option
2879 "Make another Todos file the current Todos file. 3290`todos-show-current-file' is set to non-nil."
2880Called by post-command-hook if `todos-auto-switch-todos-file' is 3291 (setq todos-global-current-todos-file todos-current-todos-file))
2881non-nil (and also in `todos-top-priorities'), it makes the 3292 ;; (and (eq major-mode 'todos-mode)
2882current buffer the current Todos file if it is visiting a Todos 3293 ;; (setq todos-global-current-todos-file (buffer-file-name))))
2883file." 3294
2884 (let ((file (or file (buffer-file-name))) 3295;; FIXME: rename to todos-set-category-number ?
2885 (files (if todos-show-done-only ;FIXME: should only hold for
2886 (funcall todos-files-function t) ; todos-archives
2887 (funcall todos-files-function)))
2888 cat)
2889 (when (and (member file files)
2890 (not (equal todos-current-todos-file file)))
2891 ;; (let ((catbuf (get-buffer todos-categories-buffer)))
2892 ;; (if catbuf (not (eq (other-buffer) catbuf)))))
2893 (if todos-ignore-archived-categories
2894 (progn
2895 (setq todos-categories nil)
2896 (setq todos-categories (todos-truncate-categories-list)))
2897 (setq todos-categories (todos-make-categories-list)))
2898 ;; if file is already in a buffer, redisplay the previous current category
2899 (when (< (- (point-max) (point-min)) (buffer-size))
2900 (widen)
2901 (when (re-search-backward (concat "^" (regexp-quote todos-category-beg)
2902 "\\(.+\\)\n") nil t)
2903 (setq cat (match-string-no-properties 1))
2904 (setq todos-category-number (todos-category-number cat))))
2905 (setq todos-current-todos-file file)
2906 ;; (or todos-category-number (setq todos-category-number 1))
2907 ;; (if (zerop todos-category-number) (setq todos-category-number 1))
2908 (todos-show))))
2909
2910(defun todos-category-number (cat) 3296(defun todos-category-number (cat)
2911 "Set todos-category-number to index of CAT in todos-categories." 3297 "Set and return buffer-local value of `todos-category-number'.
3298This value is one more than the index of category CAT, starting
3299with one instead of zero, so that the highest priority
3300category (see `todos-display-categories') has the number one."
2912 (let ((categories (mapcar 'car todos-categories))) 3301 (let ((categories (mapcar 'car todos-categories)))
2913 (setq todos-category-number 3302 (setq todos-category-number
2914 (1+ (- (length categories) 3303 (1+ (- (length categories)
@@ -2918,14 +3307,14 @@ file."
2918 "Return the name of the current category." 3307 "Return the name of the current category."
2919 (car (nth (1- todos-category-number) todos-categories))) 3308 (car (nth (1- todos-category-number) todos-categories)))
2920 3309
2921;; FIXME: wrap in save-excursion (or else have to use todos-show in
2922;; e.g. todos-{forward, backward}-category)
2923(defun todos-category-select () 3310(defun todos-category-select ()
2924 "Display the current category correctly. 3311 "Display the current category correctly.
2925 3312
2926With non-nil `todos-show-with-done' display the category's done 3313With non-nil user option `todos-show-done-only' display only the
2927\(but not archived) items below the unfinished todo items; else 3314category's done (but not archived) items; else (the default)
2928display just the todo items." 3315display just the todo items, or with non-nil user option
3316`todos-show-with-done' also display the category's done items
3317below the todo items."
2929 (let ((name (todos-current-category)) 3318 (let ((name (todos-current-category))
2930 cat-begin cat-end done-start done-sep-start done-end) 3319 cat-begin cat-end done-start done-sep-start done-end)
2931 (widen) 3320 (widen)
@@ -2938,7 +3327,7 @@ display just the todo items."
2938 (match-beginning 0) 3327 (match-beginning 0)
2939 (point-max))) 3328 (point-max)))
2940 (setq mode-line-buffer-identification 3329 (setq mode-line-buffer-identification
2941 (concat (format "Category %d: %s" todos-category-number name))) 3330 (funcall todos-mode-line-function name))
2942 (narrow-to-region cat-begin cat-end) 3331 (narrow-to-region cat-begin cat-end)
2943 (todos-prefix-overlays) 3332 (todos-prefix-overlays)
2944 (goto-char (point-min)) 3333 (goto-char (point-min))
@@ -2951,12 +3340,13 @@ display just the todo items."
2951 (error "Category %s is missing todos-category-done string" name)) 3340 (error "Category %s is missing todos-category-done string" name))
2952 (if todos-show-done-only 3341 (if todos-show-done-only
2953 (narrow-to-region (1+ done-end) (point-max)) 3342 (narrow-to-region (1+ done-end) (point-max))
2954 ;; display or hide done items as per todos-show-with-done 3343 ;; Display or hide done items as per todos-show-with-done.
2955 ;; FIXME: use todos-done-string-start ? 3344 ;; FIXME: use todos-done-string-start ?
2956 (when (re-search-forward (concat "\n\\(\\[" (regexp-quote todos-done-string) 3345 (when (re-search-forward (concat "\n\\(\\["
3346 (regexp-quote todos-done-string)
2957 "\\)") nil t) 3347 "\\)") nil t)
2958 (let (done-sep prefix ov-pref ov-done) 3348 (let (done-sep prefix ov-pref ov-done)
2959 ;; FIXME: delete overlay when not viewing done items 3349 ;; FIXME: delete overlay when not viewing done items?
2960 (when todos-show-with-done 3350 (when todos-show-with-done
2961 (setq done-sep todos-done-separator) 3351 (setq done-sep todos-done-separator)
2962 (setq done-start cat-end) 3352 (setq done-start cat-end)
@@ -2965,20 +3355,12 @@ display just the todo items."
2965 (narrow-to-region (point-min) done-start)))) 3355 (narrow-to-region (point-min) done-start))))
2966 3356
2967(defun todos-insert-with-overlays (item) 3357(defun todos-insert-with-overlays (item)
2968 "" 3358 "Insert ITEM and update prefix/priority number overlays."
2969 (todos-item-start) 3359 (todos-item-start)
2970 (insert item "\n") 3360 (insert item "\n")
2971 (todos-backward-item) 3361 (todos-backward-item)
2972 (todos-prefix-overlays)) 3362 (todos-prefix-overlays))
2973 3363
2974(defun todos-item-string-start ()
2975 "Return the start of this TODO list entry as a string."
2976 ;; Suitable for putting in the minibuffer when asking the user
2977 (let ((item (todos-item-string)))
2978 (if (> (length item) 60)
2979 (setq item (concat (substring item 0 56) "...")))
2980 item))
2981
2982(defvar todos-item-start ;; (concat "^\\(\\[\\(" (regexp-quote todos-done-string) 3364(defvar todos-item-start ;; (concat "^\\(\\[\\(" (regexp-quote todos-done-string)
2983 ;; "\\)?\\)?" todos-date-pattern) 3365 ;; "\\)?\\)?" todos-date-pattern)
2984 (concat "\\(" todos-date-string-start "\\|" todos-done-string-start 3366 (concat "\\(" todos-date-string-start "\\|" todos-done-string-start
@@ -2986,36 +3368,39 @@ display just the todo items."
2986 "String identifying start of a Todos item.") 3368 "String identifying start of a Todos item.")
2987 3369
2988(defun todos-item-start () 3370(defun todos-item-start ()
2989 "Move to start of current TODO list item and return its position." 3371 "Move to start of current Todos item and return its position."
2990 (unless (or (looking-at "^$") ; last item or between done and not done 3372 (unless (looking-at "^$")
2991 (looking-at (regexp-quote todos-category-beg))) ; for todos-count-items 3373 ;; (or (looking-at "^$") ; last item or between done and not done
3374 ;; ;; FIXME: need this? (was needed by abandoned todos-count-items)
3375 ;; (looking-at (regexp-quote todos-category-beg)))
2992 (goto-char (line-beginning-position)) 3376 (goto-char (line-beginning-position))
2993 (while (not (looking-at todos-item-start)) 3377 (while (not (looking-at todos-item-start))
2994 (forward-line -1)) 3378 (forward-line -1))
2995 (point))) 3379 (point)))
2996 3380
2997(defun todos-item-end () 3381(defun todos-item-end ()
2998 "Move to end of current TODO list item and return its position." 3382 "Move to end of current Todos item and return its position."
2999 (unless (looking-at "^$") ; FIXME: 3383 ;; Items cannot end with a blank line.
3384 (unless (looking-at "^$")
3000 (let ((done (todos-done-item-p))) 3385 (let ((done (todos-done-item-p)))
3001 (todos-forward-item) 3386 (todos-forward-item)
3002 ;; adjust if item is last unfinished one before displayed done items 3387 ;; Adjust if item is last unfinished one before displayed done items.
3003 (when (and (not done) (todos-done-item-p)) 3388 (when (and (not done) (todos-done-item-p))
3004 (forward-line -1)) 3389 (forward-line -1))
3005 (backward-char)) 3390 (backward-char))
3006 (point))) 3391 (point)))
3007 3392
3008(defun todos-remove-item () 3393(defun todos-remove-item ()
3009 "Delete the current entry from the TODO list." 3394 "Internal function called in editing, deleting or moving items."
3010 (let* ((beg (todos-item-start)) 3395 (let* ((beg (todos-item-start))
3011 (end (progn (todos-item-end) (1+ (point)))) 3396 (end (progn (todos-item-end) (1+ (point))))
3012 (ov-start (car (overlays-in beg beg)))) 3397 (ovs (overlays-in beg beg)))
3013 (when ov-start 3398 ;; There can be both prefix/number and mark overlays.
3014 (delete-overlay ov-start)) 3399 (while ovs (delete-overlay (car ovs)) (pop ovs))
3015 (delete-region beg end))) 3400 (delete-region beg end)))
3016 3401
3017(defun todos-item-string () 3402(defun todos-item-string ()
3018 "Return current TODO list entry as a string." 3403 "Return bare text of current item as a string."
3019 (let ((opoint (point)) 3404 (let ((opoint (point))
3020 (start (todos-item-start)) 3405 (start (todos-item-start))
3021 (end (todos-item-end))) 3406 (end (todos-item-end)))
@@ -3023,71 +3408,132 @@ display just the todo items."
3023 (and start end (buffer-substring-no-properties start end)))) 3408 (and start end (buffer-substring-no-properties start end))))
3024 3409
3025(defun todos-diary-item-p () 3410(defun todos-diary-item-p ()
3026 "" 3411 "Return non-nil if item at point is marked for diary inclusion."
3027 (save-excursion 3412 (save-excursion
3028 (todos-item-start) 3413 (todos-item-start)
3029 (looking-at todos-date-pattern))) 3414 (looking-at todos-date-pattern)))
3030 3415
3031(defun todos-done-item-p () 3416(defun todos-done-item-p ()
3032 "" 3417 "Return non-nil if item at point is a done item."
3033 (save-excursion 3418 (save-excursion
3034 (todos-item-start) 3419 (todos-item-start)
3035 (looking-at todos-done-string-start))) 3420 (looking-at todos-done-string-start)))
3036 3421
3037;; FIXME: should be defsubst? 3422(defvar todos-item-mark (propertize (if (equal todos-prefix "*") "@" "*")
3038(defun todos-counts (cat) 3423 'face 'todos-mark)
3039 "Plist/Vector of item type counts in category CAT. 3424 "String used to mark items.")
3040The counted types are all todo items, todo items for diary
3041inclusion, done items and archived items."
3042 (cdr (assoc cat todos-categories)))
3043
3044(defun todos-get-count (type cat)
3045 "Return count of TYPE items in category CAT."
3046 (let (idx)
3047 (cond ((eq type 'todo)
3048 (setq idx 0))
3049 ((eq type 'diary)
3050 (setq idx 1))
3051 ((eq type 'done)
3052 (setq idx 2))
3053 ((eq type 'archived)
3054 (setq idx 3)))
3055 (aref (todos-counts cat) idx)
3056 ;; (plist-get (todos-counts cat) type)
3057 ))
3058 3425
3059(defun todos-set-count (type counts increment) 3426(defun todos-item-marked-p ()
3060 "Increment count of item TYPE in vector COUNTS by INCREMENT." 3427 "If this item is marked, return mark overlay."
3061 (let (idx) 3428 (let ((ovs (overlays-in (line-beginning-position) (line-beginning-position)))
3062 (cond ((eq type 'todo) 3429 (mark todos-item-mark)
3063 (setq idx 0)) 3430 ov marked)
3064 ((eq type 'diary) 3431 (catch 'stop
3065 (setq idx 1)) 3432 (while ovs
3066 ((eq type 'done) 3433 (setq ov (pop ovs))
3067 (setq idx 2)) 3434 (and (equal (overlay-get ov 'before-string) mark)
3068 ((eq type 'archived) 3435 (throw 'stop (setq marked t)))))
3069 (setq idx 3))) 3436 (when marked ov)))
3070 (aset counts idx (+ increment (aref counts idx))) 3437
3071 ;; (plist-put counts type (1+ (plist-get counts type))) 3438(defvar todos-categories-with-marks nil
3072 )) 3439 "Alist of categories and number of marked items they contain.")
3440
3441(defun todos-get-count (type &optional category)
3442 "Return count of TYPE items in CATEGORY.
3443If CATEGORY is nil, default to the current category."
3444 (let* ((cat (or category (todos-current-category)))
3445 (counts (cdr (assoc cat todos-categories)))
3446 (idx (cond ((eq type 'todo) 0)
3447 ((eq type 'diary) 1)
3448 ((eq type 'done) 2)
3449 ((eq type 'archived) 3))))
3450 (aref counts idx)))
3451
3452(defun todos-set-count (type increment &optional category)
3453 "Increment count of TYPE items in CATEGORY by INCREMENT.
3454If CATEGORY is nil, default to the current category."
3455 (let* ((cat (or category (todos-current-category)))
3456 (counts (cdr (assoc cat todos-categories)))
3457 (idx (cond ((eq type 'todo) 0)
3458 ((eq type 'diary) 1)
3459 ((eq type 'done) 2)
3460 ((eq type 'archived) 3))))
3461 (aset counts idx (+ increment (aref counts idx)))))
3462
3463;; (defun todos-item-counts (operation &optional cat1 cat2)
3464;; "Update item counts in category CAT1 changed by OPERATION.
3465;; If CAT1 is nil, update counts from the current category. With
3466;; non-nil CAT2 include specified counts from that category in the
3467;; calculation for CAT1.
3468;; After updating the item counts, update the `todos-categories' sexp."
3469;; (let* ((cat (or cat1 (todos-current-category))))
3470;; (cond ((eq type 'insert)
3471;; (todos-set-count 'todo 1 cat))
3472;; ((eq type 'diary)
3473;; (todos-set-count 'diary 1 cat))
3474;; ((eq type 'nondiary)
3475;; (todos-set-count 'diary -1 cat))
3476;; ((eq type 'delete)
3477;; ;; FIXME: ok if last done item was deleted?
3478;; (if (save-excursion
3479;; (re-search-backward (concat "^" (regexp-quote
3480;; todos-category-done)) nil t))
3481;; (todos-set-count 'done -1 cat)
3482;; (todos-set-count 'todo -1 cat)))
3483;; ((eq type 'done)
3484;; (unless (member (buffer-file-name) (funcall todos-files-function t))
3485;; (todos-set-count 'todo -1 cat))
3486;; (todos-set-count 'done 1 cat))
3487;; ((eq type 'undo)
3488;; (todos-set-count 'todo 1 cat)
3489;; (todos-set-count 'done -1 cat))
3490;; ((eq type 'archive1)
3491;; (todos-set-count 'archived 1 cat)
3492;; (todos-set-count 'done -1 cat))
3493;; ((eq type 'archive)
3494;; (if (member (buffer-file-name) (funcall todos-files-function t))
3495;; ;; In Archive file augment done count with cat's previous
3496;; ;; done count,
3497;; (todos-set-count 'done (todos-get-count 'done cat) cat)
3498;; ;; In Todos file augment archive count with cat's previous
3499;; ;; done count, and make the latter zero.
3500;; (todos-set-count 'archived (todos-get-count 'done cat) cat)
3501;; (todos-set-count 'done (- (todos-get-count 'done cat)) cat)))
3502;; ((eq type 'merge)
3503;; ;; Augment todo and done counts of cat by those of cat2.
3504;; (todos-set-count 'todo (todos-get-count 'todo cat2) cat)
3505;; (todos-set-count 'done (todos-get-count 'done cat2) cat)))
3506;; (todos-update-categories-sexp)))
3073 3507
3074(defun todos-set-categories () 3508(defun todos-set-categories ()
3075 "Set todos-categories from the sexp at the top of the file." 3509 "Set `todos-categories' from the sexp at the top of the file."
3076 (save-excursion 3510 ;; New archive files created by `todos-move-category' are empty, which would
3077 (save-restriction 3511 ;; make the sexp test fail and raise an error, so in this case we skip it.
3078 (widen) 3512 (unless (zerop (buffer-size))
3079 (goto-char (point-min)) 3513 (save-excursion
3080 (if (looking-at "\(\(\"") 3514 (save-restriction
3081 (setq todos-categories (read (buffer-substring-no-properties 3515 (widen)
3082 (line-beginning-position) 3516 (goto-char (point-min))
3083 (line-end-position)))) 3517 ;; todos-truncate-categories-list needs non-nil todos-categories.
3084 (error "Invalid or missing todos-categories sexp"))))) 3518 (setq todos-categories-full
3085 3519 (if (looking-at "\(\(\"")
3520 (read (buffer-substring-no-properties
3521 (line-beginning-position)
3522 (line-end-position)))
3523 (error "Invalid or missing todos-categories sexp"))
3524 todos-categories todos-categories-full)))
3525 (if (and todos-ignore-archived-categories
3526 (eq major-mode 'todos-mode))
3527 (todos-truncate-categories-list)
3528 todos-categories-full)))
3529
3530;; FIXME: currently unused -- make this a command to rebuild a corrupted
3531;; todos-cats sexp ?
3086(defun todos-make-categories-list (&optional force) 3532(defun todos-make-categories-list (&optional force)
3087 "Return a list of Todos categories and their item counts. 3533 "Return an alist of Todos categories and their item counts.
3088The items counts are contained in a vector specifying the numbers 3534With non-nil argument FORCE parse the entire file to build the
3089of todo items, done items and archived items in the category, in 3535list; otherwise, get the value by reading the sexp at the top of
3090that order." 3536the file."
3091 (setq todos-categories nil) 3537 (setq todos-categories nil)
3092 (save-excursion 3538 (save-excursion
3093 (save-restriction 3539 (save-restriction
@@ -3102,13 +3548,12 @@ that order."
3102 (cond ((looking-at (concat (regexp-quote todos-category-beg) 3548 (cond ((looking-at (concat (regexp-quote todos-category-beg)
3103 "\\(.*\\)\n")) 3549 "\\(.*\\)\n"))
3104 (setq cat (match-string-no-properties 1)) 3550 (setq cat (match-string-no-properties 1))
3105 ;; counts for each category: [todo diary done archive] 3551 ;; Counts for each category: [todo diary done archive]
3106 (setq counts (make-vector 4 0)) 3552 (setq counts (make-vector 4 0))
3107 ;; (setq counts (list 'todo 0 'diary 0 'done 0 'archived 0))
3108 (setq todos-categories 3553 (setq todos-categories
3109 (append todos-categories (list (cons cat counts)))) 3554 (append todos-categories (list (cons cat counts))))
3110 ;; todos-archives may be too old here (e.g. during 3555 ;; todos-archives may be too old here (e.g. during
3111 ;; todos-move-category) 3556 ;; todos-move-category).
3112 (when (member archive (funcall todos-files-function t)) 3557 (when (member archive (funcall todos-files-function t))
3113 (with-current-buffer (find-file-noselect archive) 3558 (with-current-buffer (find-file-noselect archive)
3114 (widen) 3559 (widen)
@@ -3118,22 +3563,24 @@ that order."
3118 (point-max) t) 3563 (point-max) t)
3119 (forward-line) 3564 (forward-line)
3120 (while (not (or (looking-at 3565 (while (not (or (looking-at
3121 (concat (regexp-quote todos-category-beg) 3566 (concat
3122 "\\(.*\\)\n")) 3567 (regexp-quote todos-category-beg)
3568 "\\(.*\\)\n"))
3123 (eobp))) 3569 (eobp)))
3124 (when (looking-at todos-done-string-start) 3570 (when (looking-at todos-done-string-start)
3125 (todos-set-count 'archived counts 1)) 3571 (todos-set-count 'archived 1 cat))
3126 (forward-line)))))) 3572 (forward-line))))))
3127 ((looking-at todos-done-string-start) 3573 ((looking-at todos-done-string-start)
3128 (todos-set-count 'done counts 1)) 3574 (todos-set-count 'done 1 cat))
3129 ((looking-at (concat "^\\(" (regexp-quote diary-nonmarking-symbol) 3575 ((looking-at (concat "^\\("
3576 (regexp-quote diary-nonmarking-symbol)
3130 "\\)?" todos-date-pattern)) 3577 "\\)?" todos-date-pattern))
3131 (todos-set-count 'diary counts 1) 3578 (todos-set-count 'diary 1 cat)
3132 (todos-set-count 'todo counts 1)) 3579 (todos-set-count 'todo 1 cat))
3133 ((looking-at (concat todos-date-string-start todos-date-pattern)) 3580 ((looking-at (concat todos-date-string-start todos-date-pattern))
3134 (todos-set-count 'todo counts 1)) 3581 (todos-set-count 'todo 1 cat))
3135 ;; if first line is todos-categories list, use it and end loop 3582 ;; If first line is todos-categories list, use it and end loop
3136 ;; unless forced by non-nil parameter `force' to scan whole file 3583 ;; unless forced by non-nil parameter `force' to scan whole file.
3137 ((bobp) 3584 ((bobp)
3138 (unless force 3585 (unless force
3139 (setq todos-categories (read (buffer-substring-no-properties 3586 (setq todos-categories (read (buffer-substring-no-properties
@@ -3143,26 +3590,23 @@ that order."
3143 (forward-line))))) 3590 (forward-line)))))
3144 todos-categories) 3591 todos-categories)
3145 3592
3146;; FIXME: don't let truncated list get written by todos-update-categories-sexp
3147(defun todos-truncate-categories-list () 3593(defun todos-truncate-categories-list ()
3148 "Return a truncated list of Todos categories plus item counts. 3594 "Return a truncated alist of Todos categories plus item counts.
3149Categories containing only archived items are omitted. This list 3595Categories containing only archived items are omitted. This list
3150is used in Todos mode when `todos-ignore-archived-categories' is 3596is used in Todos mode when `todos-ignore-archived-categories' is
3151non-nil." 3597non-nil."
3152 (let (cats) 3598 (let (cats)
3153 (unless todos-categories 3599 (dolist (catcons todos-categories-full cats)
3154 (setq todos-categories (todos-make-categories-list)))
3155 (dolist (catcons todos-categories cats)
3156 (let ((cat (car catcons))) 3600 (let ((cat (car catcons)))
3157 (setq cats 3601 (setq cats
3158 (append cats 3602 (append cats
3159 (unless (and (zerop (todos-get-count 'todo cat)) 3603 (unless (and (zerop (todos-get-count 'todo cat))
3160 (zerop (todos-get-count 'done cat)) 3604 (zerop (todos-get-count 'done cat))
3161 (not (zerop (todos-get-count 'archived cat)))) 3605 (not (zerop (todos-get-count 'archived cat))))
3162 (list catcons)))))))) 3606 (list catcons))))))))
3163 3607
3164(defun todos-update-categories-sexp () 3608(defun todos-update-categories-sexp ()
3165 "" 3609 "Update the `todos-categories' sexp at the top of the file."
3166 (let (buffer-read-only) 3610 (let (buffer-read-only)
3167 (save-excursion 3611 (save-excursion
3168 (save-restriction 3612 (save-restriction
@@ -3170,53 +3614,20 @@ non-nil."
3170 (goto-char (point-min)) 3614 (goto-char (point-min))
3171 (if (looking-at (concat "^" (regexp-quote todos-category-beg))) 3615 (if (looking-at (concat "^" (regexp-quote todos-category-beg)))
3172 (progn (newline) (goto-char (point-min))) 3616 (progn (newline) (goto-char (point-min)))
3173 (kill-line)) 3617 ;; With empty buffer (e.g. with new archive in
3174 (prin1 todos-categories (current-buffer)))))) 3618 ;; `todos-move-category') `kill-line' signals end of buffer.
3175 3619 (kill-region (line-beginning-position) (line-end-position)))
3176;; FIXME: should done diary items count as diary? 3620 ;; FIXME
3177(defun todos-item-counts (cat &optional type) 3621 ;; (prin1 todos-categories (current-buffer))))))
3178 "" 3622 (prin1 todos-categories-full (current-buffer))))))
3179 (let ((counts (todos-counts cat))) 3623
3180 (cond ((eq type 'insert) 3624(defun todos-read-file-name (prompt &optional archive mustmatch)
3181 (todos-set-count 'todo counts 1)) 3625 "Choose and return the name of a Todos file, prompting with PROMPT.
3182 ((eq type 'diary) 3626Show completions with TAB or SPC; the names are shown in short
3183 (todos-set-count 'diary counts 1)) 3627form but the absolute truename is returned. With non-nil ARCHIVE
3184 ((eq type 'nondiary) 3628return the absolute truename of a Todos archive file. With non-nil
3185 (todos-set-count 'diary counts -1)) 3629MUSTMATCH the name of an existing file must be chosen;
3186 ((eq type 'delete) 3630otherwise, a new file name is allowed." ;FIXME: is this possible?
3187 ;; FIXME: ok if last done item was deleted?
3188 (if (save-excursion
3189 (re-search-backward (concat "^" (regexp-quote
3190 todos-category-done)) nil t))
3191 (todos-set-count 'done counts -1)
3192 (todos-set-count 'todo counts -1)))
3193 ((eq type 'done)
3194 (todos-set-count 'todo counts -1)
3195 (todos-set-count 'done counts 1))
3196 ((eq type 'undo)
3197 (todos-set-count 'todo counts 1)
3198 (todos-set-count 'done counts -1))
3199 ((eq type 'archive)
3200 (todos-set-count 'archived counts (todos-get-count 'done cat)) ;arch+done
3201 (todos-set-count 'done counts (- (todos-get-count 'done cat))))) ; 0
3202 (todos-update-categories-sexp)))
3203
3204(defun todos-longest-category-name-length (categories)
3205 ""
3206 (let ((longest 0))
3207 (dolist (c categories longest)
3208 (setq longest (max longest (length c))))))
3209
3210(defun todos-string-count-lines (string)
3211 "Return the number of lines STRING spans."
3212 (length (split-string string "\n")))
3213
3214(defun todos-string-multiline-p (string)
3215 "Return non-nil if STRING spans several lines."
3216 (> (todos-string-count-lines string) 1))
3217
3218(defun todos-read-file-name (prompt &optional archive)
3219 ""
3220 (unless (file-exists-p todos-files-directory) 3631 (unless (file-exists-p todos-files-directory)
3221 (make-directory todos-files-directory)) 3632 (make-directory todos-files-directory))
3222 (let* ((completion-ignore-case t) 3633 (let* ((completion-ignore-case t)
@@ -3224,27 +3635,37 @@ non-nil."
3224 (directory-files todos-files-directory nil 3635 (directory-files todos-files-directory nil
3225 (if archive "\.toda$" "\.todo$")))) 3636 (if archive "\.toda$" "\.todo$"))))
3226 (file (concat todos-files-directory 3637 (file (concat todos-files-directory
3227 (completing-read prompt files nil t) 3638 (completing-read prompt files nil mustmatch)
3228 (if archive ".toda" ".todo")))) 3639 (if archive ".toda" ".todo"))))
3229 (expand-file-name file))) 3640 (file-truename file)))
3230 3641
3231(defun todos-read-category (prompt) 3642(defun todos-read-category (prompt &optional mustmatch)
3232 "Return a category name from the current Todos file, with completion. 3643 "Choose and return a category name, prompting with PROMPT.
3233Prompt with PROMPT." 3644Show completions with TAB or SPC. With non-nil MUSTMATCH the
3234 ;; allow SPC to insert spaces, for adding new category names with 3645name must be that of an existing category; otherwise, a new
3235 ;; todos-move-item 3646category name is allowed, after checking its validity."
3647 ;; Allow SPC to insert spaces, for adding new category names.
3236 (let ((map minibuffer-local-completion-map)) 3648 (let ((map minibuffer-local-completion-map))
3237 (define-key map " " nil) 3649 (define-key map " " nil)
3238 ;; make a copy of todos-categories in case history-delete-duplicates is 3650 ;; Make a copy of todos-categories in case history-delete-duplicates is
3239 ;; non-nil, which makes completing-read alter todos-categories 3651 ;; non-nil, which makes completing-read alter todos-categories.
3240 (let* ((categories (copy-sequence todos-categories)) 3652 (let* ((categories (copy-sequence todos-categories))
3241 (history (cons 'todos-categories (1+ todos-category-number))) 3653 (history (cons 'todos-categories (1+ todos-category-number)))
3242 ;; (default (todos-current-category)) ;FIXME: why this default?
3243 (completion-ignore-case todos-completion-ignore-case) 3654 (completion-ignore-case todos-completion-ignore-case)
3244 (category (completing-read prompt 3655 (category (completing-read prompt todos-categories nil
3245 ;; (concat "Category [" default "]: ") 3656 mustmatch nil history
3246 todos-categories nil nil nil history))); default))) 3657 (if todos-categories
3247 ;; restore the original value of todos-categories 3658 (todos-current-category)
3659 ;; Trigger prompt for initial category
3660 ""))))
3661 ;; FIXME: let "" return todos-current-category
3662 (unless mustmatch
3663 (when (and (not (assoc category categories))
3664 (y-or-n-p (format (concat "There is no category \"%s\" in "
3665 "this file; add it? ") category)))
3666 (todos-validate-category-name category)
3667 (todos-add-category category)))
3668 ;; Restore the original value of todos-categories.
3248 (setq todos-categories categories) 3669 (setq todos-categories categories)
3249 category))) 3670 category)))
3250 3671
@@ -3253,31 +3674,76 @@ Prompt with PROMPT."
3253 (let (prompt) 3674 (let (prompt)
3254 (while 3675 (while
3255 (and (cond ((string= "" cat) 3676 (and (cond ((string= "" cat)
3256 (if todos-categories 3677 ;; (if todos-categories
3257 (setq prompt "Enter a non-empty category name: ") 3678 ;; (setq prompt "Enter a non-empty category name: ")
3258 ;; prompt for initial category of a new Todos file 3679 ;; Prompt for initial category of a new Todos file.
3259 (setq prompt (concat "Initial category name [" 3680 (setq prompt (concat "Initial category name ["
3260 todos-initial-category "]: ")))) 3681 todos-initial-category "]: ")));)
3261 ((string-match "\\`\\s-+\\'" cat) 3682 ((string-match "\\`\\s-+\\'" cat)
3262 (setq prompt 3683 (setq prompt
3263 "Enter a category name that is not only white space: ")) 3684 "Enter a category name that is not only white space: "))
3685 ;; FIXME: add completion
3264 ((assoc cat todos-categories) 3686 ((assoc cat todos-categories)
3265 (setq prompt "Enter a non-existing category name: "))) 3687 (setq prompt "Enter a non-existing category name: ")))
3266 (setq cat (if todos-categories 3688 (setq cat (if todos-categories
3267 (read-from-minibuffer prompt) 3689 (read-from-minibuffer prompt)
3268 ;; offer default initial category name 3690 ;; Offer default initial category name.
3269 ;; FIXME: if input is just whitespace, raises "End of
3270 ;; file during parsing" error
3271 (prin1-to-string 3691 (prin1-to-string
3272 (read-from-minibuffer prompt nil nil t nil 3692 (read-from-minibuffer prompt nil nil t nil
3273 (list todos-initial-category)))))))) 3693 (list todos-initial-category))))))))
3274 cat) 3694 cat)
3275 3695
3276;; adapted from calendar-read-date and calendar-date-string 3696;; (defun todos-read-category (prompt)
3697;; "Prompt with PROMPT for an existing category name and return it.
3698;; Show completions with TAB or SPC."
3699;; ;; Make a copy of todos-categories in case history-delete-duplicates is
3700;; ;; non-nil, which makes completing-read alter todos-categories.
3701;; (let* ((categories (copy-sequence todos-categories))
3702;; (history (cons 'todos-categories (1+ todos-category-number)))
3703;; (completion-ignore-case todos-completion-ignore-case)
3704;; (category (completing-read prompt todos-categories nil
3705;; mustmatch nil history)))
3706;; (setq category (completing-read prompt todos-categories nil t))
3707;; ;; Restore the original value of todos-categories.
3708;; (setq todos-categories categories)
3709;; category))
3710
3711;; (defun todos-new-category-name (prompt)
3712;; "Prompt with PROMPT for a new category name and return it."
3713;; (let ((map minibuffer-local-completion-map)
3714;; prompt-n)
3715;; ;; Allow SPC to insert spaces, for adding new category names.
3716;; (define-key map " " nil)
3717;; (while
3718;; ;; Validate entered category name.
3719;; (and (cond ((string= "" cat)
3720;; (setq prompt-n
3721;; (if todos-categories
3722;; "Enter a non-empty category name: "
3723;; ;; Prompt for initial category of a new Todos file.
3724;; (concat "Initial category name ["
3725;; todos-initial-category "]: "))))
3726;; ((string-match "\\`\\s-+\\'" cat)
3727;; (setq prompt-n
3728;; "Enter a category name that is not only white space: "))
3729;; ((assoc cat todos-categories)
3730;; (setq prompt-n "Enter a non-existing category name: ")))
3731;; (setq cat (if todos-categories
3732;; (read-from-minibuffer prompt)
3733;; ;; Offer default initial category name.
3734;; (prin1-to-string
3735;; (read-from-minibuffer
3736;; (or prompt prompt-n) nil nil t nil
3737;; (list todos-initial-category))))))
3738;; (setq prompt nil)))
3739;; cat)
3740
3741;; ;; Adapted from calendar-read-date and calendar-date-string.
3277(defun todos-read-date () 3742(defun todos-read-date ()
3278 "Prompt for Gregorian date and return it in the current format. 3743 "Prompt for Gregorian date and return it in the current format.
3279Also accepts `*' as an unspecified month, day, or year." 3744Also accepts `*' as an unspecified month, day, or year."
3280 (let* ((year (calendar-read 3745 (let* ((year (calendar-read
3746 ;; FIXME: maybe better like monthname with RET for current month
3281 "Year (>0 or * for any year): " 3747 "Year (>0 or * for any year): "
3282 (lambda (x) (or (eq x '*) (> x 0))) 3748 (lambda (x) (or (eq x '*) (> x 0)))
3283 (number-to-string (calendar-extract-year 3749 (number-to-string (calendar-extract-year
@@ -3292,8 +3758,9 @@ Also accepts `*' as an unspecified month, day, or year."
3292 (calendar-month-name (calendar-extract-month 3758 (calendar-month-name (calendar-extract-month
3293 (calendar-current-date)) t))) 3759 (calendar-current-date)) t)))
3294 (month (cdr (assoc-string 3760 (month (cdr (assoc-string
3295 monthname (calendar-make-alist month-array nil nil abbrevs)))) 3761 monthname (calendar-make-alist month-array nil nil
3296 (last (if (eq month 13) 3762 abbrevs))))
3763 (last (if (= month 13)
3297 31 ; FIXME: what about shorter months? 3764 31 ; FIXME: what about shorter months?
3298 (let ((yr (if (eq year '*) 3765 (let ((yr (if (eq year '*)
3299 1999 ; FIXME: no Feb. 29 3766 1999 ; FIXME: no Feb. 29
@@ -3310,18 +3777,21 @@ Also accepts `*' as an unspecified month, day, or year."
3310 (setq day (if (eq day '*) (symbol-name '*) (number-to-string day))) 3777 (setq day (if (eq day '*) (symbol-name '*) (number-to-string day)))
3311 ;; FIXME: make abbreviation customizable 3778 ;; FIXME: make abbreviation customizable
3312 (setq monthname 3779 (setq monthname
3313 (calendar-month-name (calendar-extract-month (list month day year)) t)) 3780 (or (and (= month 13) "*")
3781 (calendar-month-name (calendar-extract-month (list month day year))
3782 t)))
3314 (mapconcat 'eval calendar-date-display-form ""))) 3783 (mapconcat 'eval calendar-date-display-form "")))
3315 3784
3316(defun todos-read-dayname () 3785(defun todos-read-dayname ()
3317 "" 3786 "Choose name of a day of the week with completion and return it."
3318 (let ((completion-ignore-case t)) 3787 (let ((completion-ignore-case t))
3319 (completing-read "Enter a day name: " 3788 (completing-read "Enter a day name: "
3320 (append calendar-day-name-array nil) 3789 (append calendar-day-name-array nil)
3321 nil t))) 3790 nil t)))
3322 3791
3323(defun todos-read-time () 3792(defun todos-read-time ()
3324 "" 3793 "Prompt for and return a valid clock time as a string.
3794Valid time strings are those matching `diary-time-regexp'."
3325 (let (valid answer) 3795 (let (valid answer)
3326 (while (not valid) 3796 (while (not valid)
3327 (setq answer (read-from-minibuffer 3797 (setq answer (read-from-minibuffer
@@ -3331,10 +3801,137 @@ Also accepts `*' as an unspecified month, day, or year."
3331 (setq valid t))) 3801 (setq valid t)))
3332 answer)) 3802 answer))
3333 3803
3804;;; Sorting and display routines for todos-categories-mode.
3805
3806(defun todos-display-categories (&optional sortkey)
3807 "Display a table of the current file's categories and item counts.
3808
3809In the initial display the categories are numbered, indicating
3810their current order for navigating by \\[todos-forward-category]
3811and \\[todos-backward-category]. You can persistantly change the
3812order of the category at point by typing \\[todos-raise-category]
3813or \\[todos-lower-category].
3814
3815The labels above the category names and item counts are buttons,
3816and clicking these changes the display: sorted by category name
3817or by the respective item counts (alternately descending or
3818ascending). In these displays the categories are not numbered
3819and \\[todos-raise-category] and \\[todos-lower-category] are
3820disabled. (Programmatically, the sorting is triggered by passing
3821a non-nil SORTKEY argument.)
3822
3823In addition, the lines with the category names and item counts
3824are buttonized, and pressing one of these button jumps to the
3825category in Todos mode (or Todos Archive mode, for categories
3826containing only archived items, provided user option
3827`todos-ignore-archived-categories' is non-nil. These categories
3828are shown in `todos-archived-only' face."
3829 (interactive)
3830 (unless (eq major-mode 'todos-categories-mode)
3831 (setq todos-global-current-todos-file (or todos-current-todos-file
3832 todos-default-todos-file)))
3833 (let* ((cats0 (if (and todos-ignore-archived-categories
3834 (not (eq major-mode 'todos-categories-mode)))
3835 todos-categories-full
3836 todos-categories))
3837 (cats (todos-sort cats0 sortkey))
3838 (archive (member todos-current-todos-file todos-archives))
3839 ;; `num' is used by todos-insert-category-line.
3840 (num 0))
3841 (set-window-buffer (selected-window)
3842 (set-buffer (get-buffer-create todos-categories-buffer)))
3843 (let (buffer-read-only)
3844 (erase-buffer)
3845 (kill-all-local-variables)
3846 (todos-categories-mode)
3847 ;; FIXME: add usage tips?
3848 (insert (format "Category counts for Todos file \"%s\"."
3849 (file-name-sans-extension
3850 (file-name-nondirectory todos-current-todos-file))))
3851 (newline 2)
3852 ;; Make space for the column of category numbers.
3853 (insert (make-string (+ 4 (length todos-categories-number-separator)) 32))
3854 ;; Add the category and item count buttons (if this is the list of
3855 ;; categories in an archive, show only done item counts).
3856 (save-excursion
3857 (todos-insert-sort-button todos-categories-category-label)
3858 (if (member todos-current-todos-file todos-archives)
3859 (insert (concat (make-string 6 32)
3860 (format "%s" todos-categories-archived-label)))
3861 (insert (make-string 3 32))
3862 (todos-insert-sort-button todos-categories-todo-label)
3863 (insert (make-string 2 32))
3864 (todos-insert-sort-button todos-categories-diary-label)
3865 (insert (make-string 2 32))
3866 (todos-insert-sort-button todos-categories-done-label)
3867 (insert (make-string 2 32))
3868 (todos-insert-sort-button todos-categories-archived-label))
3869 (newline 2)
3870 ;; Fill in the table with buttonized lines, each showing a category and
3871 ;; its item counts.
3872 (mapc (lambda (cat) (todos-insert-category-line cat sortkey))
3873 (mapcar 'car cats))
3874 (newline)
3875 ;; Add a line showing item count totals.
3876 (insert (make-string (+ 4 (length todos-categories-number-separator)) 32)
3877 (todos-padded-string todos-categories-totals-label)
3878 (mapconcat
3879 (lambda (elt)
3880 (concat
3881 (make-string (1+ (/ (length (car elt)) 2)) 32)
3882 (format "%3d" (nth (cdr elt) (todos-total-item-counts)))
3883 ;; Add an extra space if label length is odd (using
3884 ;; definition of oddp from cl.el).
3885 (if (eq (logand (length (car elt)) 1) 1) " ")))
3886 (if archive
3887 (list (cons todos-categories-done-label 2))
3888 (list (cons todos-categories-todo-label 0)
3889 (cons todos-categories-diary-label 1)
3890 (cons todos-categories-done-label 2)
3891 (cons todos-categories-archived-label 3)))
3892 ""))))
3893 (setq buffer-read-only t)))
3894
3895;; ;; FIXME: make this toggle with todos-display-categories
3896;; (defun todos-display-categories-alphabetically ()
3897;; ""
3898;; (interactive)
3899;; (todos-display-sorted 'alpha))
3900
3901;; ;; FIXME: provide key bindings for these or delete them
3902;; (defun todos-display-categories-sorted-by-todo ()
3903;; ""
3904;; (interactive)
3905;; (todos-display-sorted 'todo))
3906
3907;; (defun todos-display-categories-sorted-by-diary ()
3908;; ""
3909;; (interactive)
3910;; (todos-display-sorted 'diary))
3911
3912;; (defun todos-display-categories-sorted-by-done ()
3913;; ""
3914;; (interactive)
3915;; (todos-display-sorted 'done))
3916
3917;; (defun todos-display-categories-sorted-by-archived ()
3918;; ""
3919;; (interactive)
3920;; (todos-display-sorted 'archived))
3921
3922(defun todos-longest-category-name-length (categories)
3923 "Return the length of the longest name in list CATEGORIES."
3924 (let ((longest 0))
3925 (dolist (c categories longest)
3926 (setq longest (max longest (length c))))))
3927
3334(defun todos-padded-string (str) 3928(defun todos-padded-string (str)
3335 "" 3929 "Return string STR padded with spaces.
3930The placement of the padding is determined by the value of user
3931option `todos-categories-align'."
3336 (let* ((categories (mapcar 'car todos-categories)) 3932 (let* ((categories (mapcar 'car todos-categories))
3337 (len (todos-longest-category-name-length categories)) 3933 (len (max (todos-longest-category-name-length categories)
3934 (length todos-categories-category-label)))
3338 (strlen (length str)) 3935 (strlen (length str))
3339 (strlen-odd (eq (logand strlen 1) 1)) ; oddp from cl.el 3936 (strlen-odd (eq (logand strlen 1) 1)) ; oddp from cl.el
3340 (padding (max 0 (/ (- len strlen) 2))) 3937 (padding (max 0 (/ (- len strlen) 2)))
@@ -3349,17 +3946,16 @@ Also accepts `*' as an unspecified month, day, or year."
3349 ((eq todos-categories-align 'right) 0)))) 3946 ((eq todos-categories-align 'right) 0))))
3350 (concat (make-string padding-left 32) str (make-string padding-right 32)))) 3947 (concat (make-string padding-left 32) str (make-string padding-right 32))))
3351 3948
3352(defvar todos-descending-counts-store nil 3949(defvar todos-descending-counts nil
3353 "Alist of current sorted category counts, keyed by sort key.") 3950 "List of keys for category counts sorted in descending order.")
3354 3951
3355;; FIXME: rename to todos-insert-category-info ?
3356(defun todos-sort (list &optional key) 3952(defun todos-sort (list &optional key)
3357 "Return a copy of LIST, possibly sorted according to KEY." ;FIXME 3953 "Return a copy of LIST, possibly sorted according to KEY."
3358 (let* ((l (copy-sequence list)) 3954 (let* ((l (copy-sequence list))
3359 (fn (if (eq key 'alpha) 3955 (fn (if (eq key 'alpha)
3360 (lambda (x) (upcase x)) ;alphabetize case insensitively 3956 (lambda (x) (upcase x)) ; Alphabetize case insensitively.
3361 (lambda (x) (todos-get-count key x)))) 3957 (lambda (x) (todos-get-count key x))))
3362 (descending (member key todos-descending-counts-store)) 3958 (descending (member key todos-descending-counts))
3363 (cmp (if (eq key 'alpha) 3959 (cmp (if (eq key 'alpha)
3364 'string< 3960 'string<
3365 (if descending '< '>))) 3961 (if descending '< '>)))
@@ -3369,13 +3965,13 @@ Also accepts `*' as an unspecified month, day, or year."
3369 (when key 3965 (when key
3370 (setq l (sort l pred)) 3966 (setq l (sort l pred))
3371 (if descending 3967 (if descending
3372 (setq todos-descending-counts-store 3968 (setq todos-descending-counts
3373 (delete key todos-descending-counts-store)) 3969 (delete key todos-descending-counts))
3374 (push key todos-descending-counts-store))) 3970 (push key todos-descending-counts)))
3375 l)) 3971 l))
3376 3972
3377(defun todos-display-sorted (type) 3973(defun todos-display-sorted (type)
3378 "Keep point on the count sorting button just clicked." 3974 "Keep point on the TYPE count sorting button just clicked."
3379 (let ((opoint (point))) 3975 (let ((opoint (point)))
3380 (todos-display-categories type) 3976 (todos-display-categories type)
3381 (goto-char opoint))) 3977 (goto-char opoint)))
@@ -3396,7 +3992,8 @@ Also accepts `*' as an unspecified month, day, or year."
3396 key)) 3992 key))
3397 3993
3398(defun todos-insert-sort-button (label) 3994(defun todos-insert-sort-button (label)
3399 "" 3995 "Insert button for displaying categories sorted by item counts.
3996LABEL determines which type of count is sorted."
3400 (setq str (if (string= label todos-categories-category-label) 3997 (setq str (if (string= label todos-categories-category-label)
3401 (todos-padded-string label) 3998 (todos-padded-string label)
3402 label)) 3999 label))
@@ -3406,102 +4003,124 @@ Also accepts `*' as an unspecified month, day, or year."
3406 'action 4003 'action
3407 `(lambda (button) 4004 `(lambda (button)
3408 (let ((key (todos-label-to-key ,label))) 4005 (let ((key (todos-label-to-key ,label)))
3409 (if (and (member key todos-descending-counts-store) 4006 (if (and (member key todos-descending-counts)
3410 (eq key 'alpha)) 4007 (eq key 'alpha))
3411 (progn 4008 (progn
3412 (todos-display-categories) 4009 (todos-display-categories)
3413 (setq todos-descending-counts-store 4010 (setq todos-descending-counts
3414 (delete key todos-descending-counts-store))) 4011 (delete key todos-descending-counts)))
3415 (todos-display-sorted key))))) 4012 (todos-display-sorted key)))))
3416 (setq ovl (make-overlay beg end)) 4013 (setq ovl (make-overlay beg end))
3417 (overlay-put ovl 'face 'todos-button)) 4014 (overlay-put ovl 'face 'todos-button))
3418 4015
4016(defun todos-total-item-counts ()
4017 "Return a list of total item counts for the current file."
4018 (mapcar (lambda (i) (apply '+ (mapcar (lambda (l) (aref l i))
4019 (mapcar 'cdr todos-categories))))
4020 (list 0 1 2 3)))
4021
3419(defun todos-insert-category-line (cat &optional nonum) 4022(defun todos-insert-category-line (cat &optional nonum)
3420 "" 4023 "Insert button displaying category CAT's name and item counts.
3421 (let ((archive (member todos-current-todos-file todos-archives)) 4024With non-nil argument NONUM show only these; otherwise, insert a
4025number in front of the button indicating the category's priority.
4026The number and the category name are separated by the string
4027which is the value of the user option
4028`todos-categories-number-separator'."
4029 (let* ((archive (member todos-current-todos-file todos-archives))
3422 (str (todos-padded-string cat)) 4030 (str (todos-padded-string cat))
3423 (opoint (point))) 4031 (opoint (point)))
3424 ;; beg end ovl) 4032 ;; num is declared in caller.
3425 ;; num is declared in caller
3426 (setq num (1+ num)) 4033 (setq num (1+ num))
3427 ;; (if nonum
3428 ;; (insert (make-string 4 32))
3429 ;; (insert " " (format "%2d" num) " | "))
3430 ;; (setq beg (point))
3431 ;; (setq end (+ beg (length str)))
3432 (insert-button 4034 (insert-button
3433 ;; FIXME: use mapconcat?
3434 (concat (if nonum 4035 (concat (if nonum
3435 (make-string (+ 3 (length todos-categories-number-separator)) 32) 4036 (make-string (+ 4 (length todos-categories-number-separator))
3436 (format " %2d%s" num todos-categories-number-separator)) 4037 32)
4038 (format " %3d%s" num todos-categories-number-separator))
3437 str 4039 str
3438 (make-string (+ 2 (/ (length todos-categories-todo-label) 2)) 32) 4040 (mapconcat (lambda (elt)
3439 (unless archive 4041 (concat
3440 (concat 4042 (make-string (1+ (/ (length (car elt)) 2)) 32) ; label
3441 (format "%2d" (todos-get-count 'todo cat)) 4043 (format "%3d" (todos-get-count (cdr elt) cat)) ; count
3442 (make-string (+ 2 (/ (length todos-categories-diary-label) 2)) 32))) 4044 ;; Add an extra space if label length is odd
3443 (unless archive 4045 ;; (using def of oddp from cl.el).
3444 (concat 4046 (if (eq (logand (length (car elt)) 1) 1) " ")))
3445 (format "%2d" (todos-get-count 'diary cat)) 4047 (if archive
3446 (make-string (+ 3 (/ (length todos-categories-done-label) 2)) 32))) 4048 (list (cons todos-categories-done-label 'done))
3447 (format "%2d" (todos-get-count 'done cat)) 4049 (list (cons todos-categories-todo-label 'todo)
3448 (unless archive 4050 (cons todos-categories-diary-label 'diary)
3449 (concat 4051 (cons todos-categories-done-label 'done)
3450 (make-string (+ 2 (/ (length todos-categories-archived-label) 2)) 32) 4052 (cons todos-categories-archived-label
3451 (format "%2d" (todos-get-count 'archived cat)) 4053 'archived)))
3452 (make-string 2 32)))) 4054 ""))
3453 'face (if (and todos-ignore-archived-categories 4055 'face (if (and todos-ignore-archived-categories
3454 (zerop (todos-get-count 'todo cat)) 4056 (zerop (todos-get-count 'todo cat))
3455 (zerop (todos-get-count 'done cat)) 4057 (zerop (todos-get-count 'done cat))
3456 (not (zerop (todos-get-count 'archived cat)))) 4058 (not (zerop (todos-get-count 'archived cat))))
3457 'todos-archived-only 4059 'todos-archived-only
3458 nil) 4060 nil)
3459 'action `(lambda (button) (todos-jump-to-category ,cat))) 4061 'action `(lambda (button) (let ((buf (current-buffer)))
3460 ;; (setq ovl (make-overlay beg end)) 4062 (todos-jump-to-category ,cat)
3461 ;; (overlay-put ovl 'face 'todos-button) 4063 (kill-buffer buf))))
3462 (let* ((beg1 (+ opoint 6 (length str))) 4064 ;; Highlight the sorted count column.
3463 end1 ovl1) 4065 (let* ((beg (+ opoint 6 (length str)))
4066 end ovl)
3464 (cond ((eq nonum 'todo) 4067 (cond ((eq nonum 'todo)
3465 (setq beg1 (+ beg1 1 (/ (length todos-categories-todo-label) 2)))) 4068 (setq beg (+ beg 1 (/ (length todos-categories-todo-label) 2))))
3466 ((eq nonum 'diary) 4069 ((eq nonum 'diary)
3467 (setq beg1 (+ beg1 1 (length todos-categories-todo-label) 4070 (setq beg (+ beg 1 (length todos-categories-todo-label)
3468 2 (/ (length todos-categories-diary-label) 2)))) 4071 2 (/ (length todos-categories-diary-label) 2))))
3469 ((eq nonum 'done) 4072 ((eq nonum 'done)
3470 (setq beg1 (+ beg1 1 (length todos-categories-todo-label) 4073 (setq beg (+ beg 1 (length todos-categories-todo-label)
3471 2 (length todos-categories-diary-label) 4074 2 (length todos-categories-diary-label)
3472 2 (/ (length todos-categories-done-label) 2)))) 4075 2 (/ (length todos-categories-done-label) 2))))
3473 ((eq nonum 'archived) 4076 ((eq nonum 'archived)
3474 (setq beg1 (+ beg1 1 (length todos-categories-todo-label) 4077 (setq beg (+ beg 1 (length todos-categories-todo-label)
3475 2 (length todos-categories-diary-label) 4078 2 (length todos-categories-diary-label)
3476 2 (length todos-categories-done-label) 4079 2 (length todos-categories-done-label)
3477 2 (/ (length todos-categories-archived-label) 2))))) 4080 2 (/ (length todos-categories-archived-label) 2)))))
3478 (unless (= beg1 (+ opoint 6 (length str))) 4081 (unless (= beg (+ opoint 6 (length str)))
3479 (setq end1 (+ beg1 4)) 4082 (setq end (+ beg 4))
3480 (setq ovl1 (make-overlay beg1 end1)) 4083 (setq ovl (make-overlay beg end))
3481 (overlay-put ovl1 'face 'todos-sorted-column))) 4084 (overlay-put ovl 'face 'todos-sorted-column)))
3482 (insert (concat "\n")))) 4085 (newline)))
3483 4086
3484(provide 'todos) 4087(provide 'todos)
3485 4088
3486;;; UI
3487;; - display
3488;; - show todos in cat
3489;; - show done in cat
3490;; - show catlist
3491;; - show top priorities in all cats
3492;; - show archived
3493;; - navigation
3494;; -
3495;; - editing
3496;;
3497;;; Internals
3498;; - cat props: name, number, todos, done, archived
3499;; - item props: priority, date-time, status?
3500;; - file format
3501;; - cat begin
3502;; - todo items 0...n
3503;; - empty line
3504;; - done-separator
3505;; - done item 0...n
3506
3507;;; todos.el ends here 4089;;; todos.el ends here
4090
4091;;; necessitated adaptations to diary-lib.el
4092
4093;; (defun diary-goto-entry (button)
4094;; "Jump to the diary entry for the BUTTON at point."
4095;; (let* ((locator (button-get button 'locator))
4096;; (marker (car locator))
4097;; markbuf file opoint)
4098;; ;; If marker pointing to diary location is valid, use that.
4099;; (if (and marker (setq markbuf (marker-buffer marker)))
4100;; (progn
4101;; (pop-to-buffer markbuf)
4102;; (goto-char (marker-position marker)))
4103;; ;; Marker is invalid (eg buffer has been killed, as is the case with
4104;; ;; included diary files).
4105;; (or (and (setq file (cadr locator))
4106;; (file-exists-p file)
4107;; (find-file-other-window file)
4108;; (progn
4109;; (when (eq major-mode (default-value 'major-mode)) (diary-mode))
4110;; (when (eq major-mode 'todos-mode) (widen))
4111;; (goto-char (point-min))
4112;; (when (re-search-forward (format "%s.*\\(%s\\)"
4113;; (regexp-quote (nth 2 locator))
4114;; (regexp-quote (nth 3 locator)))
4115;; nil t)
4116;; (goto-char (match-beginning 1))
4117;; (when (eq major-mode 'todos-mode)
4118;; (setq opoint (point))
4119;; (re-search-backward (concat "^"
4120;; (regexp-quote todos-category-beg)
4121;; "\\(.*\\)\n")
4122;; nil t)
4123;; (todos-category-number (match-string 1))
4124;; (todos-category-select)
4125;; (goto-char opoint)))))
4126;; (message "Unable to locate this diary entry")))))