diff options
| author | Stephen Berman | 2011-12-02 14:27:28 +0100 |
|---|---|---|
| committer | Stephen Berman | 2011-12-02 14:27:28 +0100 |
| commit | 58c7641d1b069be3ead47dbe4a44c8360ef8d1f2 (patch) | |
| tree | bd64ad740779b725893bbb0ff1cc0a4592c8eb1c | |
| parent | d04d6b955b4caaa9817ec053eddb59e923a68cf8 (diff) | |
| download | emacs-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/ChangeLog | 216 | ||||
| -rw-r--r-- | lisp/calendar/todos.el | 4569 |
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 @@ | |||
| 1 | 2012-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 | |||
| 1 | 2012-09-18 Stephen Berman <stephen.berman@gmx.net> | 206 | 2012-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. |
| 292 | These reflect the priorities of the items in each category." | 81 | These 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. |
| 302 | Displayed in a before-string overlay by `todos-toggle-view-done-items'." | 92 | Displayed 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. | ||
| 119 | Argument CAT is the name of the current Todos category. | ||
| 120 | This 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. | ||
| 128 | The function is expected to take one argument that holds the name | ||
| 129 | of the current Todos category. The resulting control becomes the | ||
| 130 | local value of `mode-line-buffer-identification' in each Todos | ||
| 131 | buffer." | ||
| 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'. |
| 327 | This returns the case-insensitive alphabetically sorted list of | 142 | This returns the case-insensitive alphabetically sorted list of |
| 328 | files in `todos-files-directory' with the extension \".todo\". | 143 | file truenames in `todos-files-directory' with the extension |
| 329 | With 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 | 145 | truenames (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'. |
| 338 | If this function is called with an optional non-nil argument, | 155 | This function should take an optional argument that, if non-nil, |
| 339 | then it returns the value of the variable `todos-archives'." | 156 | makes 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. | ||
| 167 | The rules should be set interactively by invoking | ||
| 168 | `todos-set-top-priorities'. | ||
| 169 | |||
| 170 | Each rule is a list whose first element is a member of | ||
| 171 | `todos-files', whose second element is a number specifying the | ||
| 172 | default number of top priority items for the categories in that | ||
| 173 | file, and whose third element is an alist whose elements are | ||
| 174 | conses of a category name in that file and the number of top | ||
| 175 | priority items in that category that `todos-top-priorities' shows | ||
| 176 | by 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. |
| 193 | Otherwise, `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'. |
| 363 | Normally this should be set by invoking `todos-change-default-file' | 202 | Normally 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." | 210 | Invoking these commands to visit a Todos or Todos Archive file |
| 372 | :type 'file | 211 | calls `todos-show' or `todos-show-archive', so that the file is |
| 212 | displayed 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'. |
| 407 | This separates the number from the category name in the default | 253 | This separates the number from the category name in the default |
| 408 | categories display according to priority." | 254 | categories 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. |
| 420 | When non-nil such categories are omitted from `todos-categories' | 265 | When 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. | ||
| 304 | This appends `diary-nonmarking-symbol' to the front of an item on | ||
| 305 | insertion 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. |
| 448 | The first string is inserted before the item date and must be a | 311 | The 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'. |
| 464 | 0 means show all entries." | 328 | 0 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'. |
| 470 | 0 means print all entries." | 334 | 0 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. |
| 602 | The elements are lists whose car is a category name and whose cdr | 455 | The elements are cons cells whose car is a category name and |
| 603 | is the category's property list.") | 456 | whose cdr is a vector of the category's item counts. These are, |
| 457 | in order, the numbers of todo items, todo items included in the | ||
| 458 | Diary, done items and archived items.") | ||
| 459 | |||
| 460 | (defvar todos-categories-full nil | ||
| 461 | "Variable holding non-truncated copy of `todos-categories'. | ||
| 462 | Set when `todos-ignore-archived-categories' is set to non-nil, to | ||
| 463 | restore 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. | ||
| 475 | Used by functions called from outside of Todos mode to visit the | ||
| 476 | current Todos file rather than the default Todos file (i.e. when | ||
| 477 | users 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. | ||
| 497 | This 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. | ||
| 502 | See `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. | ||
| 521 | Set 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. | ||
| 781 | Automatically 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. | ||
| 802 | Set 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. |
| 1002 | Depending on the specific mode, this either kills and the buffer | ||
| 1003 | or 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 | ||
| 960 | With non-nil prefix argument SOLICIT-FILE ask for file to visit, | 1025 | With non-nil prefix argument SOLICIT-FILE ask for file to visit. |
| 961 | otherwise the first invocation of this command in a session | 1026 | Otherwise, the first invocation of this command in a session |
| 962 | visits `todos-default-todos-file' (creating it if it does not yet | 1027 | visits `todos-default-todos-file' (creating it if it does not yet |
| 963 | exist). Subsequent invocations from outside of Todos mode | 1028 | exist); subsequent invocations from outside of Todos mode revisit |
| 964 | revisit this file or whichever Todos file has been made | 1029 | this file or, if user option `todos-show-current-file' is |
| 965 | current (e.g. by calling `todos-switch-todos-file'). | 1030 | non-nil, whichever Todos file was visited last. |
| 966 | 1031 | ||
| 967 | The category displayed is initially the first member of | 1032 | The category displayed on initial invocation is the first member |
| 968 | `todos-categories' for the current Todos file, subsequently | 1033 | of `todos-categories' for the current Todos file, on subsequent |
| 969 | whichever category is current. If | 1034 | invocations 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 |
| 971 | invocation of `todos-show' displays a clickable listing of the | 1036 | invocation of `todos-show' displays a clickable listing of the |
| 972 | categories in the current Todos file." | 1037 | categories 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. | ||
| 1014 | The numbers indicate the current order of the categories. | ||
| 1015 | |||
| 1016 | With non-nil SORTKEY display a non-numbered alphabetical list. | ||
| 1017 | The lists are in Todos Categories mode. | ||
| 1018 | |||
| 1019 | The category names are buttonized, and pressing a button displays | ||
| 1020 | the 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. |
| 1133 | The buffer showing the archive is in Todos Archive mode. The | 1107 | With non-nil argument ASK prompt to choose an archive to visit; |
| 1134 | first visit in a session displays the first category in the | 1108 | see `todos-choose-archive'. The buffer showing the archive is in |
| 1135 | archive, subsequent visits return to the last category | 1109 | Todos Archive mode. The first visit in a session displays the |
| 1136 | displayed." | 1110 | first category in the archive, subsequent visits return to the |
| 1111 | last 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 | "" | 1137 | With 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 | |||
| 1165 | With a positive numerical prefix argument N, change the | ||
| 1166 | markedness of the next N items. With non-nil argument ALL, mark | ||
| 1167 | all visible items in the category (depending on visibility, all | ||
| 1168 | todo and done items, or just todo or just done items). | ||
| 1169 | |||
| 1170 | The mark is the character \"*\" inserted in front of the item's | ||
| 1171 | priority number or the `todos-prefix' string; if `todos-prefix' | ||
| 1172 | is \"*\", 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'. | ||
| 1216 | You 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'. |
| 1234 | This variable temporarily holds user changed values which are | ||
| 1235 | saved to `todos-priorities-rules'.") | ||
| 1210 | 1236 | ||
| 1211 | Number of entries for each category is given by NUM which | 1237 | (defun todos-set-top-priorities () |
| 1212 | defaults 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 | |||
| 1306 | The 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 | |||
| 1308 | Number of entries for each category is given by NUM, which | ||
| 1309 | defaults to `todos-show-priorities'. With non-nil argument | ||
| 1213 | MERGE list top priorities of all Todos files in | 1310 | MERGE 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, |
| 1215 | prompt to update the list of merged files." | 1312 | prompt 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'. |
| 1427 | Number of entries for each category is given by NUM, which | ||
| 1428 | defaults 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'. |
| 1435 | Number of entries for each category is given by NUM, which | ||
| 1436 | defaults 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." |
| 1305 | The items are those in the current Todos file, or with prefix | 1443 | (interactive) |
| 1306 | argument 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. | ||
| 1448 | The 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. | ||
| 1454 | The 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. | ||
| 1460 | The 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'. | ||
| 1466 | The 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'. | ||
| 1472 | The 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. |
| 1480 | With non-nil argument BACK, visit the numerically previous | ||
| 1481 | category." | ||
| 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. |
| 1332 | When called interactively, prompt for the category. | 1497 | Optional argument CAT provides the category name. Otherwise, |
| 1333 | Non-interactively, the argument CAT provides the category. With | 1498 | prompt for the category, with TAB completion on existing |
| 1334 | non-nil argument OTHER-FILE, prompt for a Todos file, otherwise | 1499 | categories. If a non-existing category name is entered, ask |
| 1335 | stay with the current Todos file. See also | 1500 | whether to add a new category with this name, if affirmed, do so, |
| 1336 | `todos-jump-to-category-other-file'." | 1501 | then jump to that category. With non-nil argument OTHER-FILE, |
| 1502 | prompt for a Todos file, otherwise jump within the current Todos | ||
| 1503 | file." | ||
| 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. |
| 1541 | The 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) | 1548 | With 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. |
| 1566 | With 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. |
| 1392 | The search encompasses all todo and done items within the current Todos file; it excludes category names. Matches are highlighted | 1584 | The search runs through the whole file and encompasses all and |
| 1393 | " | 1585 | only todo and done items; it excludes category names. Multiple |
| 1586 | matches are shown sequentially, highlighted in `todos-search' | ||
| 1587 | face." | ||
| 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") | 1652 | Interactively, prompt for a category and display it. |
| 1653 | Noninteractively, 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. |
| 1703 | Called interactively, prompt for category name, then visit the | ||
| 1704 | category in Todos mode. Non-interactively, argument CAT provides | ||
| 1705 | the 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. |
| 1736 | If this file has an archive containing this category, rename the | ||
| 1737 | category 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. |
| 1555 | With ARG non-nil delete the category unconditionally, | 1767 | With ARG non-nil delete the category unconditionally, |
| 1556 | i.e. including all existing entries." | 1768 | i.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. |
| 1633 | If current category has archived items, also move those to the | 1855 | If current category has archived items, also move those to the |
| 1634 | archive of the file moved to, creating it if it does not exist." | 1856 | archive 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 |
| 1710 | The todo and done items are appended to the todo and done items, | 1956 | current category's todo and done items are appended to the chosen |
| 1711 | respectively, of the category merged to, which becomes the | 1957 | category's todo and done items, respectively, which becomes the |
| 1712 | current category, and the category merged from is deleted." | 1958 | current 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 | |
| 1780 | With prefix argument ARG solicit the category, otherwise use the | 2030 | region-or-here) |
| 1781 | current category. | 2031 | "Add a new Todo item to a category. |
| 1782 | 2032 | See the note at the end of this document string about key | |
| 1783 | Argument DATE-TYPE sets the form of the item's mandatory date | 2033 | bindings and convenience commands derived from this command. |
| 1784 | string. With the value `date' this is the full date (whose | 2034 | |
| 1785 | format is set by `calendar-date-display-form', with year, month | 2035 | With no (or nil) prefix argument ARG, add the item to the current |
| 1786 | and day individually solicited (month with tab completion). With | 2036 | category; with one prefix argument (C-u), prompt for a category |
| 1787 | the value `dayname' a weekday name is used, solicited with tab | 2037 | from the current Todos file; with two prefix arguments (C-u C-u), |
| 1788 | completion. With the value `calendar' the full date string is | 2038 | first prompt for a Todos file, then a category in that file. If |
| 1789 | used and set by selecting from the Calendar. With any other | 2039 | a non-existing category is entered, ask whether to add it to the |
| 1790 | value (including none) the full current date is used. | 2040 | Todos file; if answered affirmatively, add the category and |
| 1791 | 2041 | insert the item there. | |
| 1792 | Argument TIME determines the occurrence and value of the time | 2042 | |
| 1793 | string. With the value `omit' insert the item without a time | 2043 | When argument DIARY is non-nil, this overrides the intent of the |
| 1794 | string. With the value `ask' solicit a time string; this may be | 2044 | user option `todos-include-in-diary' for this item: if |
| 1795 | empty or else must match `date-time-regexp'. With any other | 2045 | `todos-include-in-diary' is nil, include the item in the Fancy |
| 1796 | value add or omit the current time in accordance with | 2046 | Diary display, and if it is non-nil, exclude the item from the |
| 1797 | `todos-always-add-time-string'. | 2047 | Fancy Diary display. When DIARY is nil, `todos-include-in-diary' |
| 1798 | 2048 | has its intended effect. | |
| 1799 | With non-nil argument DIARY mark item for inclusion in user's diary. If `todos-include-in-diary' is non-nil | 2049 | |
| 1800 | 2050 | When the item is included in the Fancy Diary display and the | |
| 1801 | With non-nil argument HERE insert the new item directly above the | 2051 | argument NONMARKING is non-nil, this overrides the intent of the |
| 1802 | item at point. If point is on an empty line, insert the new item | 2052 | user option `todos-diary-nonmarking' for this item: if |
| 1803 | there." | 2053 | `todos-diary-nonmarking' is nil, append `diary-nonmarking-symbol' |
| 2054 | to the item, and if it is non-nil, omit `diary-nonmarking-symbol'. | ||
| 2055 | |||
| 2056 | The argument DATE-TYPE determines the content of the item's | ||
| 2057 | mandatory 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 | |||
| 2073 | With non-nil argument TIME prompt for a time string; this must | ||
| 2074 | either be empty or else match `diary-time-regexp'. If TIME is | ||
| 2075 | nil, add or omit the current time according to value of the user | ||
| 2076 | option `todos-always-add-time-string'. | ||
| 2077 | |||
| 2078 | The argument REGION-OR-HERE determines the source and location of | ||
| 2079 | the 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 | |||
| 2099 | To facilitate using these arguments when inserting a new todo | ||
| 2100 | item, convenience commands have been defined for all admissible | ||
| 2101 | combinations (96 in all!) together with mnenomic key bindings | ||
| 2102 | based 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_ - | ||
| 2104 | nonmar_k_ing. An alternative interface for customizing key | ||
| 2105 | binding 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. | ||
| 2079 | If 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 | |||
| 2220 | If there are marked items, delete all of these; otherwise, delete | ||
| 2221 | the 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. |
| 2301 | Use of newlines invokes `todos-indent' to insure compliance with | ||
| 2302 | the 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 | |||
| 2324 | Interactively, ask whether to edit year, month and day or day of | ||
| 2325 | the week, as well as time. If there are marked items, apply the | ||
| 2326 | changes to all of these; otherwise, edit just the item at point. | ||
| 2327 | |||
| 2328 | Non-interactively, argument WHAT specifies whether to edit only | ||
| 2329 | the 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)) | 2394 | With 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) | 2446 | Interactively, the item and the category are the current ones, |
| 2262 | (and (looking-at todos-item-start) | 2447 | and the priority is a number between 1 and the number of items in |
| 2263 | (not (todos-done-item-p)))) | 2448 | the category. Non-interactively with argument NEW, the lowest |
| 2264 | ;; Assume there is a final newline | 2449 | priority 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 | ||
| 2340 | If the named category is not one of the current todo categories, | 2480 | If there are marked items, move all of these; otherwise, move |
| 2341 | then it is created and the item becomes the first entry in that | 2481 | the item at point. |
| 2342 | category. | ||
| 2343 | 2482 | ||
| 2344 | With optional non-nil argument FILE, first ask for another Todos | 2483 | With non-nil argument FILE, first prompt for another Todos file and |
| 2345 | file and then solicit a category within that file to move the | 2484 | then a category in that file to move the item or items to. |
| 2346 | item to." | 2485 | |
| 2486 | If the chosen category is not one of the existing categories, | ||
| 2487 | then it is created and the item(s) become(s) the first | ||
| 2488 | entry/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. |
| 2582 | With prefix argument ARG prompt for a comment and append it to the | ||
| 2583 | done 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 | |||
| 2662 | If there are marked done items (and no marked todo items), | ||
| 2663 | archive all of these; otherwise, with non-nil argument ALL, | ||
| 2664 | archive all done items in this category; otherwise, archive the | ||
| 2665 | done item at point. | ||
| 2666 | |||
| 2667 | If the archive of this file does not exist, it is created. If | ||
| 2668 | this 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 | |||
| 2794 | If there are marked items, unarchive all of these; otherwise, | ||
| 2795 | with non-nil argument ALL, unarchive all items in this category; | ||
| 2796 | otherwise, unarchive the item at point. | ||
| 2797 | |||
| 2798 | Unarchived items are restored as done items to the corresponding | ||
| 2799 | category in the Todos file, inserted at the end of done section. | ||
| 2800 | If all items in the archive category were restored, the category | ||
| 2801 | is deleted from the archive. If this was the only category in the | ||
| 2802 | archive, 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) | 2911 | If a candidate item is marked with `todos-nondiary-marker', |
| 2555 | (todos-date-string-match lim)))) | 2912 | remove this marker; otherwise, insert it. |
| 2556 | (cat (todos-current-category))) | 2913 | |
| 2557 | (if (looking-at (regexp-quote todos-nondiary-start)) | 2914 | With non-nil argument ALL toggle the diary status of all todo |
| 2558 | (progn | 2915 | items in this category; otherwise, if there are marked todo |
| 2559 | (replace-match "") | 2916 | items, toggle the diary status of all and only these, otherwise |
| 2560 | (search-forward todos-nondiary-end (1+ end) t) | 2917 | toggle 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. |
| 2953 | See `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. |
| 2965 | See `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'. | 2975 | This includes overlays, indentation, and, depending on the value |
| 2608 | 2976 | of `todos-print-function', faces. With non-nil argument TO-FILE | |
| 2609 | Number of entries for each category is given by NOF-PRIORITIES which | 2977 | write the printable version to a file; otherwise, send it to the |
| 2610 | defaults to `todos-show-priorities'." | 2978 | default 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) | 3113 | The 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. |
| 3126 | The 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 | ||
| 3128 | number of todo or done items in the category indicating the | ||
| 3129 | item's priority. Todo and done items are numbered independently | ||
| 3130 | of 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? | 3289 | This 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." |
| 2880 | Called by post-command-hook if `todos-auto-switch-todos-file' is | 3291 | (setq todos-global-current-todos-file todos-current-todos-file)) |
| 2881 | non-nil (and also in `todos-top-priorities'), it makes the | 3292 | ;; (and (eq major-mode 'todos-mode) |
| 2882 | current buffer the current Todos file if it is visiting a Todos | 3293 | ;; (setq todos-global-current-todos-file (buffer-file-name)))) |
| 2883 | file." | 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'. |
| 3298 | This value is one more than the index of category CAT, starting | ||
| 3299 | with one instead of zero, so that the highest priority | ||
| 3300 | category (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 | ||
| 2926 | With non-nil `todos-show-with-done' display the category's done | 3313 | With non-nil user option `todos-show-done-only' display only the |
| 2927 | \(but not archived) items below the unfinished todo items; else | 3314 | category's done (but not archived) items; else (the default) |
| 2928 | display just the todo items." | 3315 | display just the todo items, or with non-nil user option |
| 3316 | `todos-show-with-done' also display the category's done items | ||
| 3317 | below 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.") |
| 3040 | The counted types are all todo items, todo items for diary | ||
| 3041 | inclusion, 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. | ||
| 3443 | If 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. | ||
| 3454 | If 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. |
| 3088 | The items counts are contained in a vector specifying the numbers | 3534 | With non-nil argument FORCE parse the entire file to build the |
| 3089 | of todo items, done items and archived items in the category, in | 3535 | list; otherwise, get the value by reading the sexp at the top of |
| 3090 | that order." | 3536 | the 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. |
| 3149 | Categories containing only archived items are omitted. This list | 3595 | Categories containing only archived items are omitted. This list |
| 3150 | is used in Todos mode when `todos-ignore-archived-categories' is | 3596 | is used in Todos mode when `todos-ignore-archived-categories' is |
| 3151 | non-nil." | 3597 | non-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) | 3626 | Show completions with TAB or SPC; the names are shown in short |
| 3183 | (todos-set-count 'diary counts 1)) | 3627 | form but the absolute truename is returned. With non-nil ARCHIVE |
| 3184 | ((eq type 'nondiary) | 3628 | return the absolute truename of a Todos archive file. With non-nil |
| 3185 | (todos-set-count 'diary counts -1)) | 3629 | MUSTMATCH the name of an existing file must be chosen; |
| 3186 | ((eq type 'delete) | 3630 | otherwise, 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. |
| 3233 | Prompt with PROMPT." | 3644 | Show completions with TAB or SPC. With non-nil MUSTMATCH the |
| 3234 | ;; allow SPC to insert spaces, for adding new category names with | 3645 | name must be that of an existing category; otherwise, a new |
| 3235 | ;; todos-move-item | 3646 | category 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. |
| 3279 | Also accepts `*' as an unspecified month, day, or year." | 3744 | Also 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. |
| 3794 | Valid 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 | |||
| 3809 | In the initial display the categories are numbered, indicating | ||
| 3810 | their current order for navigating by \\[todos-forward-category] | ||
| 3811 | and \\[todos-backward-category]. You can persistantly change the | ||
| 3812 | order of the category at point by typing \\[todos-raise-category] | ||
| 3813 | or \\[todos-lower-category]. | ||
| 3814 | |||
| 3815 | The labels above the category names and item counts are buttons, | ||
| 3816 | and clicking these changes the display: sorted by category name | ||
| 3817 | or by the respective item counts (alternately descending or | ||
| 3818 | ascending). In these displays the categories are not numbered | ||
| 3819 | and \\[todos-raise-category] and \\[todos-lower-category] are | ||
| 3820 | disabled. (Programmatically, the sorting is triggered by passing | ||
| 3821 | a non-nil SORTKEY argument.) | ||
| 3822 | |||
| 3823 | In addition, the lines with the category names and item counts | ||
| 3824 | are buttonized, and pressing one of these button jumps to the | ||
| 3825 | category in Todos mode (or Todos Archive mode, for categories | ||
| 3826 | containing only archived items, provided user option | ||
| 3827 | `todos-ignore-archived-categories' is non-nil. These categories | ||
| 3828 | are 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. |
| 3930 | The placement of the padding is determined by the value of user | ||
| 3931 | option `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. |
| 3996 | LABEL 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)) | 4024 | With non-nil argument NONUM show only these; otherwise, insert a |
| 4025 | number in front of the button indicating the category's priority. | ||
| 4026 | The number and the category name are separated by the string | ||
| 4027 | which 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"))))) | ||