aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lisp/ChangeLog28
-rw-r--r--lisp/calendar/todos.el750
2 files changed, 482 insertions, 296 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 0520dc97576..fff7be6e20b 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,5 +1,33 @@
12012-09-23 Stephen Berman <stephen.berman@gmx.net> 12012-09-23 Stephen Berman <stephen.berman@gmx.net>
2 2
3 * calendar/todos.el: Further significant code rearrangement;
4 further comment revision.
5 (todos-mode-display): New defgroup.
6 (todos-prefix, todos-number-priorities)
7 (todos-done-separator-string, todos-done-string)
8 (todos-comment-string, todos-show-with-done)
9 (todos-mode-line-function, todos-skip-archived-categories)
10 (todos-highlight-item, todos-wrap-lines)
11 (todos-line-wrapping-function): Use it.
12 (todos-item-insertion): New defgroup.
13 (todos-include-in-diary, todos-diary-nonmarking)
14 (todos-nondiary-marker, todos-always-add-time-string)
15 (todos-use-only-highlighted-region): Use it.
16 (todos-forward-button, todos-backward-button): New commands.
17 (todos-categories-mode-map): Use them, replacing forward-button
18 and backward-button.
19 (todos-merge-category): Fix and improve implementation; handle
20 archived items.
21 (todos-insert-item, todos-set-date-from-calendar): Handle setting
22 date by calling todos-insert-item-from-calendar.
23 (todos-delete-item): Fix overlay handling.
24 (todos-move-item): Highlight item to be moved.
25 (todos-item-undo): Handle marked items.
26 (todos-insert-item-from-calendar): Rewrite using
27 todos-date-from-calendar.
28
292012-09-23 Stephen Berman <stephen.berman@gmx.net>
30
3 * calendar/todos.el: Further comment revision. 31 * calendar/todos.el: Further comment revision.
4 (todos-sorted-column): Change default value, also taking tty into 32 (todos-sorted-column): Change default value, also taking tty into
5 account. 33 account.
diff --git a/lisp/calendar/todos.el b/lisp/calendar/todos.el
index 33d68936e23..e5b9996d9b4 100644
--- a/lisp/calendar/todos.el
+++ b/lisp/calendar/todos.el
@@ -127,12 +127,42 @@ displayed correctly."
127 :type 'boolean 127 :type 'boolean
128 :group 'todos) 128 :group 'todos)
129 129
130(defcustom todos-completion-ignore-case nil
131 "Non-nil means case is ignored by `todos-read-*' functions."
132 :type 'boolean
133 :group 'todos)
134
135(defcustom todos-print-function 'ps-print-buffer-with-faces
136 "Function called to print buffer content; see `todos-print'."
137 :type 'symbol
138 :group 'todos)
139
140(defcustom todos-todo-mode-date-time-regexp
141 (concat "\\(?1:[0-9]\\{4\\}\\)-\\(?2:[0-9]\\{2\\}\\)-"
142 "\\(?3:[0-9]\\{2\\}\\) \\(?4:[0-9]\\{2\\}:[0-9]\\{2\\}\\)")
143 "Regexp matching legacy todo-mode.el item date-time strings.
144In order for `todos-convert-legacy-files' to correctly convert this
145string to the current Todos format, the regexp must contain four
146explicitly numbered groups (see `(elisp) Regexp Backslash'),
147where group 1 matches a string for the year, group 2 a string for
148the month, group 3 a string for the day and group 4 a string for
149the time. The default value converts date-time strings built
150using the default value of `todo-time-string-format' from
151todo-mode.el."
152 :type 'regexp
153 :group 'todos)
154
155(defgroup todos-mode-display nil
156 "User display options for Todos mode."
157 :version "24.2"
158 :group 'todos)
159
130(defcustom todos-prefix "" 160(defcustom todos-prefix ""
131 "String prefixed to todo items for visual distinction." 161 "String prefixed to todo items for visual distinction."
132 :type 'string 162 :type 'string
133 :initialize 'custom-initialize-default 163 :initialize 'custom-initialize-default
134 :set 'todos-reset-prefix 164 :set 'todos-reset-prefix
135 :group 'todos) 165 :group 'todos-mode-display)
136 166
137(defcustom todos-number-priorities t 167(defcustom todos-number-priorities t
138 "Non-nil to prefix items with consecutively increasing integers. 168 "Non-nil to prefix items with consecutively increasing integers.
@@ -140,7 +170,7 @@ These reflect the priorities of the items in each category."
140 :type 'boolean 170 :type 'boolean
141 :initialize 'custom-initialize-default 171 :initialize 'custom-initialize-default
142 :set 'todos-reset-prefix 172 :set 'todos-reset-prefix
143 :group 'todos) 173 :group 'todos-mode-display)
144 174
145(defun todos-reset-prefix (symbol value) 175(defun todos-reset-prefix (symbol value)
146 "The :set function for `todos-prefix' and `todos-number-priorities'." 176 "The :set function for `todos-prefix' and `todos-number-priorities'."
@@ -173,7 +203,7 @@ the value of `todos-done-separator'."
173 :type 'string 203 :type 'string
174 :initialize 'custom-initialize-default 204 :initialize 'custom-initialize-default
175 :set 'todos-reset-done-separator-string 205 :set 'todos-reset-done-separator-string
176 :group 'todos) 206 :group 'todos-mode-display)
177 207
178(defun todos-reset-done-separator-string (symbol value) 208(defun todos-reset-done-separator-string (symbol value)
179 "The :set function for `todos-done-separator-string'." 209 "The :set function for `todos-done-separator-string'."
@@ -190,7 +220,7 @@ the value of `todos-done-separator'."
190 :type 'string 220 :type 'string
191 :initialize 'custom-initialize-default 221 :initialize 'custom-initialize-default
192 :set 'todos-reset-done-string 222 :set 'todos-reset-done-string
193 :group 'todos) 223 :group 'todos-mode-display)
194 224
195(defun todos-reset-done-string (symbol value) 225(defun todos-reset-done-string (symbol value)
196 "The :set function for user option `todos-done-string'." 226 "The :set function for user option `todos-done-string'."
@@ -220,7 +250,7 @@ the value of `todos-done-separator'."
220 :type 'string 250 :type 'string
221 :initialize 'custom-initialize-default 251 :initialize 'custom-initialize-default
222 :set 'todos-reset-comment-string 252 :set 'todos-reset-comment-string
223 :group 'todos) 253 :group 'todos-mode-display)
224 254
225(defun todos-reset-comment-string (symbol value) 255(defun todos-reset-comment-string (symbol value)
226 "The :set function for user option `todos-comment-string'." 256 "The :set function for user option `todos-comment-string'."
@@ -246,7 +276,7 @@ the value of `todos-done-separator'."
246(defcustom todos-show-with-done nil 276(defcustom todos-show-with-done nil
247 "Non-nil to display done items in all categories." 277 "Non-nil to display done items in all categories."
248 :type 'boolean 278 :type 'boolean
249 :group 'todos) 279 :group 'todos-mode-display)
250 280
251(defun todos-mode-line-control (cat) 281(defun todos-mode-line-control (cat)
252 "Return a mode line control for Todos buffers. 282 "Return a mode line control for Todos buffers.
@@ -262,7 +292,7 @@ The function expects one argument holding the name of the current
262Todos category. The resulting control becomes the local value of 292Todos category. The resulting control becomes the local value of
263`mode-line-buffer-identification' in each Todos buffer." 293`mode-line-buffer-identification' in each Todos buffer."
264 :type 'function 294 :type 'function
265 :group 'todos) 295 :group 'todos-mode-display)
266 296
267(defcustom todos-skip-archived-categories nil 297(defcustom todos-skip-archived-categories nil
268 "Non-nil to skip categories with only archived items when browsing. 298 "Non-nil to skip categories with only archived items when browsing.
@@ -275,24 +305,81 @@ mode (reached with \\[todos-display-categories]) these categories
275shown in `todos-archived-only' face and clicking them in Todos 305shown in `todos-archived-only' face and clicking them in Todos
276Categories mode visits the archived categories." 306Categories mode visits the archived categories."
277 :type 'boolean 307 :type 'boolean
278 :group 'todos) 308 :group 'todos-mode-display)
279 309
280(defcustom todos-use-only-highlighted-region t 310(defcustom todos-highlight-item nil
281 "Non-nil to enable inserting only highlighted region as new item." 311 "Non-nil means highlight items at point."
282 :type 'boolean 312 :type 'boolean
313 :initialize 'custom-initialize-default
314 :set 'todos-reset-highlight-item
315 :group 'todos-mode-display)
316
317(defun todos-reset-highlight-item (symbol value)
318 "The :set function for `todos-highlight-item'."
319 (let ((oldvalue (symbol-value symbol))
320 (files (append todos-files todos-archives)))
321 (custom-set-default symbol value)
322 (when (not (equal value oldvalue))
323 (dolist (f files)
324 (let ((buf (find-buffer-visiting f)))
325 (when buf
326 (with-current-buffer buf
327 (require 'hl-line)
328 (if value
329 (hl-line-mode 1)
330 (hl-line-mode -1)))))))))
331
332(defcustom todos-wrap-lines t
333 "Non-nil to wrap long lines via `todos-line-wrapping-function'."
334 :group 'todos-mode-display
335 :type 'boolean)
336
337(defcustom todos-line-wrapping-function 'todos-wrap-and-indent
338 "Line wrapping function used with non-nil `todos-wrap-lines'."
339 :group 'todos-mode-display
340 :type 'function)
341
342(defun todos-wrap-and-indent ()
343 "Use word wrapping on long lines and indent with a wrap prefix.
344The amount of indentation is given by user option
345`todos-indent-to-here'."
346 (set (make-local-variable 'word-wrap) t)
347 (set (make-local-variable 'wrap-prefix) (make-string todos-indent-to-here 32))
348 (unless (member '(continuation) fringe-indicator-alist)
349 (push '(continuation) fringe-indicator-alist)))
350
351;; FIXME: :set function to refill items with hard newlines and to immediately
352;; update wrapped prefix display
353(defcustom todos-indent-to-here 6
354 "Number of spaces `todos-line-wrapping-function' indents to."
355 :type '(integer :validate
356 (lambda (widget)
357 (unless (> (widget-value widget) 0)
358 (widget-put widget :error
359 "Invalid value: must be a positive integer")
360 widget)))
361 :group 'todos)
362
363(defun todos-indent ()
364 "Indent from point to `todos-indent-to-here'."
365 (indent-to todos-indent-to-here todos-indent-to-here))
366
367(defgroup todos-item-insertion nil
368 "User options for adding new todo items."
369 :version "24.2"
283 :group 'todos) 370 :group 'todos)
284 371
285(defcustom todos-include-in-diary nil 372(defcustom todos-include-in-diary nil
286 "Non-nil to allow new Todo items to be included in the diary." 373 "Non-nil to allow new Todo items to be included in the diary."
287 :type 'boolean 374 :type 'boolean
288 :group 'todos) 375 :group 'todos-item-insertion)
289 376
290(defcustom todos-diary-nonmarking nil 377(defcustom todos-diary-nonmarking nil
291 "Non-nil to insert new Todo diary items as nonmarking by default. 378 "Non-nil to insert new Todo diary items as nonmarking by default.
292This appends `diary-nonmarking-symbol' to the front of an item on 379This appends `diary-nonmarking-symbol' to the front of an item on
293insertion provided it doesn't begin with `todos-nondiary-marker'." 380insertion provided it doesn't begin with `todos-nondiary-marker'."
294 :type 'boolean 381 :type 'boolean
295 :group 'todos) 382 :group 'todos-item-insertion)
296 383
297(defcustom todos-nondiary-marker '("[" "]") 384(defcustom todos-nondiary-marker '("[" "]")
298 "List of strings surrounding item date to block diary inclusion. 385 "List of strings surrounding item date to block diary inclusion.
@@ -301,7 +388,7 @@ non-empty string that does not match a diary date in order to
301have its intended effect. The second string is inserted after 388have its intended effect. The second string is inserted after
302the diary date." 389the diary date."
303 :type '(list string string) 390 :type '(list string string)
304 :group 'todos 391 :group 'todos-item-insertion
305 :initialize 'custom-initialize-default 392 :initialize 'custom-initialize-default
306 :set 'todos-reset-nondiary-marker) 393 :set 'todos-reset-nondiary-marker)
307 394
@@ -344,89 +431,12 @@ argument, this reverses the effect of
344`todos-always-add-time-string': if t, these commands omit the 431`todos-always-add-time-string': if t, these commands omit the
345current time, if nil, they include it." 432current time, if nil, they include it."
346 :type 'boolean 433 :type 'boolean
347 :group 'todos) 434 :group 'todos-item-insertion)
348
349(defcustom todos-completion-ignore-case nil
350 "Non-nil means case of user input in `todos-read-*' is ignored."
351 :type 'boolean
352 :group 'todos)
353 435
354(defcustom todos-highlight-item nil 436(defcustom todos-use-only-highlighted-region t
355 "Non-nil means highlight items at point." 437 "Non-nil to enable inserting only highlighted region as new item."
356 :type 'boolean 438 :type 'boolean
357 :initialize 'custom-initialize-default 439 :group 'todos-item-insertion)
358 :set 'todos-reset-highlight-item
359 :group 'todos)
360
361(defun todos-reset-highlight-item (symbol value)
362 "The :set function for `todos-highlight-item'."
363 (let ((oldvalue (symbol-value symbol))
364 (files (append todos-files todos-archives)))
365 (custom-set-default symbol value)
366 (when (not (equal value oldvalue))
367 (dolist (f files)
368 (let ((buf (find-buffer-visiting f)))
369 (when buf
370 (with-current-buffer buf
371 (require 'hl-line)
372 (if value
373 (hl-line-mode 1)
374 (hl-line-mode -1)))))))))
375
376(defcustom todos-wrap-lines t
377 "Non-nil to wrap long lines via `todos-line-wrapping-function'."
378 :group 'todos
379 :type 'boolean)
380
381(defcustom todos-line-wrapping-function 'todos-wrap-and-indent
382 "Line wrapping function used with non-nil `todos-wrap-lines'."
383 :group 'todos
384 :type 'function)
385
386(defun todos-wrap-and-indent ()
387 "Use word wrapping on long lines and indent with a wrap prefix.
388The amount of indentation is given by user option
389`todos-indent-to-here'."
390 (set (make-local-variable 'word-wrap) t)
391 (set (make-local-variable 'wrap-prefix) (make-string todos-indent-to-here 32))
392 (unless (member '(continuation) fringe-indicator-alist)
393 (push '(continuation) fringe-indicator-alist)))
394
395;; FIXME: :set function to refill items with hard newlines and to immediately
396;; update wrapped prefix display
397(defcustom todos-indent-to-here 6
398 "Number of spaces `todos-line-wrapping-function' indents to."
399 :type '(integer :validate
400 (lambda (widget)
401 (unless (> (widget-value widget) 0)
402 (widget-put widget :error
403 "Invalid value: must be a positive integer")
404 widget)))
405 :group 'todos)
406
407(defun todos-indent ()
408 "Indent from point to `todos-indent-to-here'."
409 (indent-to todos-indent-to-here todos-indent-to-here))
410
411(defcustom todos-todo-mode-date-time-regexp
412 (concat "\\(?1:[0-9]\\{4\\}\\)-\\(?2:[0-9]\\{2\\}\\)-"
413 "\\(?3:[0-9]\\{2\\}\\) \\(?4:[0-9]\\{2\\}:[0-9]\\{2\\}\\)")
414 "Regexp matching legacy todo-mode.el item date-time strings.
415In order for `todos-convert-legacy-files' to correctly convert this
416string to the current Todos format, the regexp must contain four
417explicitly numbered groups (see `(elisp) Regexp Backslash'),
418where group 1 matches a string for the year, group 2 a string for
419the month, group 3 a string for the day and group 4 a string for
420the time. The default value converts date-time strings built
421using the default value of `todo-time-string-format' from
422todo-mode.el."
423 :type 'regexp
424 :group 'todos)
425
426(defcustom todos-print-function 'ps-print-buffer-with-faces
427 "Function called to print buffer content; see `todos-print'."
428 :type 'symbol
429 :group 'todos)
430 440
431(defgroup todos-filtered nil 441(defgroup todos-filtered nil
432 "User options for Todos Filter Items mode." 442 "User options for Todos Filter Items mode."
@@ -930,6 +940,26 @@ See `todos-display-categories-first'.")
930Set by the command `todos-show-done-only' and used by 940Set by the command `todos-show-done-only' and used by
931`todos-category-select'.") 941`todos-category-select'.")
932 942
943(defun todos-reset-and-enable-done-separator ()
944 "Show resized catagory separator overlay after window size change.
945Added to `window-configuration-change-hook' in `todos-mode'."
946 (when (= 1 (length todos-done-separator-string))
947 (let ((sep todos-done-separator))
948 (setq todos-done-separator (todos-done-separator))
949 (save-match-data (todos-reset-done-separator sep)))
950 ;; FIXME: If this is called while the separator overlay is shown, the
951 ;; separator with deleted overlay becomes visible when waiting for user
952 ;; input and remains so. The following workaround prevents this, but it
953 ;; also prevents widening when edebugging todos.el.
954 ;; (save-excursion
955 ;; (goto-char (point-min))
956 ;; (when (re-search-forward todos-done-string-start nil t)
957 ;; (let ((todos-show-with-done nil))
958 ;; (todos-category-select))
959 ;; (let ((todos-show-with-done t))
960 ;; (todos-category-select))))
961 ))
962
933;; --------------------------------------------------------------------------- 963;; ---------------------------------------------------------------------------
934;;; Global variables and helper functions 964;;; Global variables and helper functions
935 965
@@ -1054,26 +1084,6 @@ done items are shown. Its value is determined by user option
1054 (overlay-put new-sep 'display 1084 (overlay-put new-sep 'display
1055 todos-done-separator))))))) 1085 todos-done-separator)))))))
1056 1086
1057(defun todos-reset-and-enable-done-separator ()
1058 "Hook function for activating new separator overlay.
1059Added to `window-configuration-change-hook' in `todos-mode'."
1060 (when (= 1 (length todos-done-separator-string))
1061 (let ((sep todos-done-separator))
1062 (setq todos-done-separator (todos-done-separator))
1063 (save-match-data (todos-reset-done-separator sep)))
1064 ;; If the separator overlay is now shown, we have to hide and then show it
1065 ;; again in order to let the change in length take effect.
1066 ;; FIXME: But this breaks e.g. (widen) when edebugging. But how to
1067 ;; restrict it?
1068 ;; (save-excursion
1069 ;; (goto-char (point-min))
1070 ;; (when (re-search-forward todos-done-string-start nil t)
1071 ;; (let ((todos-show-with-done nil))
1072 ;; (todos-category-select))
1073 ;; (let ((todos-show-with-done t))
1074 ;; (todos-category-select))))
1075 ))
1076
1077(defun todos-category-select () 1087(defun todos-category-select ()
1078 "Display the current category correctly." 1088 "Display the current category correctly."
1079 (let ((name (todos-current-category)) 1089 (let ((name (todos-current-category))
@@ -2506,10 +2516,10 @@ which is the value of the user option
2506 (define-key map "+" 'todos-lower-category-priority) 2516 (define-key map "+" 'todos-lower-category-priority)
2507 (define-key map "r" 'todos-raise-category-priority) 2517 (define-key map "r" 'todos-raise-category-priority)
2508 (define-key map "-" 'todos-raise-category-priority) 2518 (define-key map "-" 'todos-raise-category-priority)
2509 (define-key map "n" 'forward-button) 2519 (define-key map "n" 'todos-forward-button)
2510 (define-key map "p" 'backward-button) 2520 (define-key map "p" 'todos-backward-button)
2511 (define-key map [tab] 'forward-button) 2521 (define-key map [tab] 'todos-forward-button)
2512 (define-key map [backtab] 'backward-button) 2522 (define-key map [backtab] 'todos-backward-button)
2513 (define-key map "q" 'todos-quit) 2523 (define-key map "q" 'todos-quit)
2514 ;; (define-key map "A" 'todos-add-category) 2524 ;; (define-key map "A" 'todos-add-category)
2515 ;; (define-key map "D" 'todos-delete-category) 2525 ;; (define-key map "D" 'todos-delete-category)
@@ -2585,7 +2595,7 @@ which is the value of the user option
2585 (when todos-show-current-file 2595 (when todos-show-current-file
2586 (add-hook 'pre-command-hook 'todos-show-current-file nil t)) 2596 (add-hook 'pre-command-hook 'todos-show-current-file nil t))
2587 (add-hook 'window-configuration-change-hook 2597 (add-hook 'window-configuration-change-hook
2588 'todos-reset-and-enable-done-separator nil t) 2598 'todos-reset-and-enable-done-separator nil t)
2589 (add-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file nil t)) 2599 (add-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file nil t))
2590 2600
2591(defun todos-unload-hook () 2601(defun todos-unload-hook ()
@@ -3239,6 +3249,22 @@ upward."
3239 nil t) 3249 nil t)
3240 (forward-line -1)))))) 3250 (forward-line -1))))))
3241 3251
3252(defun todos-forward-button (n &optional wrap display-message)
3253 ""
3254 (interactive "p\nd\nd")
3255 (forward-button n wrap display-message)
3256 (and (bolp) (button-at (point))
3257 ;; Align with beginning of category label.
3258 (forward-char (+ 4 (length todos-categories-number-separator)))))
3259
3260(defun todos-backward-button (n &optional wrap display-message)
3261 ""
3262 (interactive "p\nd\nd")
3263 (backward-button n wrap display-message)
3264 (and (bolp) (button-at (point))
3265 ;; Align with beginning of category label.
3266 (forward-char (+ 4 (length todos-categories-number-separator)))))
3267
3242;; FIXME: (i) Extend search to other Todos files. (ii) Allow navigating among 3268;; FIXME: (i) Extend search to other Todos files. (ii) Allow navigating among
3243;; hits. (But these features are effectively available with 3269;; hits. (But these features are effectively available with
3244;; todos-regexp-items-multifile, so maybe it's not worth the trouble here.) 3270;; todos-regexp-items-multifile, so maybe it's not worth the trouble here.)
@@ -3766,59 +3792,133 @@ archive of the file moved to, creating it if it does not exist."
3766 3792
3767(defun todos-merge-category () 3793(defun todos-merge-category ()
3768 "Merge current category into another category in this file. 3794 "Merge current category into another category in this file.
3795
3769The current category's todo and done items are appended to the 3796The current category's todo and done items are appended to the
3770chosen category's todo and done items, respectively, which 3797chosen goal category's todo and done items, respectively. The
3771becomes the current category, and the category moved from is 3798goal category becomes the current category, and the previous
3772deleted." 3799current category is deleted.
3800
3801If both the first and goal categories also have archived items,
3802the former are merged to the latter. If only the first category
3803has archived items, the archived category is renamed to the goal
3804category."
3773 (interactive) 3805 (interactive)
3774 (let ((buffer-read-only nil) 3806 (let* ((tfile todos-current-todos-file)
3775 (cat (todos-current-category)) 3807 (archive (concat (file-name-sans-extension tfile) ".toda"))
3776 (goal (todos-read-category "Category to merge to: " t))) 3808 (cat (todos-current-category))
3777 (widen) 3809 (goal (todos-read-category "Category to merge to: " t))
3778 ;; FIXME: check if cat has archived items and merge those too 3810 archived-count here)
3779 (let* ((cbeg (progn 3811 ;; Merge in todo file.
3780 (re-search-backward 3812 (with-current-buffer (get-buffer (find-file-noselect tfile))
3781 (concat "^" (regexp-quote todos-category-beg)) nil t) 3813 (widen)
3782 (point))) 3814 (let* ((buffer-read-only nil)
3783 (tbeg (progn (forward-line) (point))) 3815 (cbeg (progn
3784 (dbeg (progn 3816 (re-search-backward
3785 (re-search-forward
3786 (concat "^" (regexp-quote todos-category-done)) nil t)
3787 (forward-line) (point)))
3788 (tend (progn (forward-line -2) (point)))
3789 (cend (progn
3790 (if (re-search-forward
3791 (concat "^" (regexp-quote todos-category-beg)) nil t)
3792 (match-beginning 0)
3793 (point-max))))
3794 (todo (buffer-substring-no-properties tbeg tend))
3795 (done (buffer-substring-no-properties dbeg cend))
3796 here)
3797 (goto-char (point-min))
3798 (re-search-forward
3799 (concat "^" (regexp-quote (concat todos-category-beg goal))) nil t)
3800 (re-search-forward
3801 (concat "^" (regexp-quote todos-category-done)) nil t)
3802 (forward-line -1)
3803 (setq here (point))
3804 (insert todo)
3805 (goto-char (if (re-search-forward
3806 (concat "^" (regexp-quote todos-category-beg)) nil t) 3817 (concat "^" (regexp-quote todos-category-beg)) nil t)
3807 (match-beginning 0) 3818 (point-marker)))
3808 (point-max))) 3819 (tbeg (progn (forward-line) (point-marker)))
3809 (insert done) 3820 (dbeg (progn
3810 (remove-overlays cbeg cend) 3821 (re-search-forward
3811 (delete-region cbeg cend) 3822 (concat "^" (regexp-quote todos-category-done)) nil t)
3812 (todos-update-count 'todo (todos-get-count 'todo cat) goal) 3823 (forward-line) (point-marker)))
3813 (todos-update-count 'done (todos-get-count 'done cat) goal) 3824 ;; Omit empty line between todo and done items.
3814 (setq todos-categories (delete (assoc cat todos-categories) 3825 (tend (progn (forward-line -2) (point-marker)))
3815 todos-categories)) 3826 (cend (progn
3816 (todos-update-categories-sexp) 3827 (if (re-search-forward
3828 (concat "^" (regexp-quote todos-category-beg)) nil t)
3829 (progn
3830 (goto-char (match-beginning 0))
3831 (point-marker))
3832 (point-max-marker))))
3833 (todo (buffer-substring-no-properties tbeg tend))
3834 (done (buffer-substring-no-properties dbeg cend)))
3835 (goto-char (point-min))
3836 ;; Merge any todo items.
3837 (unless (zerop (length todo))
3838 (re-search-forward
3839 (concat "^" (regexp-quote (concat todos-category-beg goal))) nil t)
3840 (re-search-forward
3841 (concat "^" (regexp-quote todos-category-done)) nil t)
3842 (forward-line -1)
3843 (setq here (point-marker))
3844 (insert todo)
3845 (todos-update-count 'todo (todos-get-count 'todo cat) goal))
3846 ;; Merge any done items.
3847 (unless (zerop (length done))
3848 (goto-char (if (re-search-forward
3849 (concat "^" (regexp-quote todos-category-beg)) nil t)
3850 (match-beginning 0)
3851 (point-max)))
3852 (when (zerop (length todo)) (setq here (point-marker)))
3853 (insert done)
3854 (todos-update-count 'done (todos-get-count 'done cat) goal))
3855 (remove-overlays cbeg cend)
3856 (delete-region cbeg cend)
3857 (setq todos-categories (delete (assoc cat todos-categories)
3858 todos-categories))
3859 (todos-update-categories-sexp)
3860 (mapc (lambda (m) (set-marker m nil)) (list cbeg tbeg dbeg tend cend))))
3861 (when (file-exists-p archive)
3862 ;; Merge in archive file.
3863 (with-current-buffer (get-buffer (find-file-noselect archive))
3864 (widen)
3865 (goto-char (point-min))
3866 (let ((buffer-read-only nil)
3867 (cbeg (save-excursion
3868 (when (re-search-forward
3869 (concat "^" (regexp-quote
3870 (concat todos-category-beg cat)))
3871 nil t)
3872 (goto-char (match-beginning 0))
3873 (point-marker))))
3874 (gbeg (save-excursion
3875 (when (re-search-forward
3876 (concat "^" (regexp-quote
3877 (concat todos-category-beg goal)))
3878 nil t)
3879 (goto-char (match-beginning 0))
3880 (point-marker))))
3881 cend carch)
3882 (when cbeg
3883 (setq archived-count (todos-get-count 'done cat))
3884 (setq cend (save-excursion
3885 (if (re-search-forward
3886 (concat "^" (regexp-quote todos-category-beg))
3887 nil t)
3888 (match-beginning 0)
3889 (point-max))))
3890 (setq carch (save-excursion (goto-char cbeg) (forward-line)
3891 (buffer-substring-no-properties (point) cend)))
3892 ;; If both categories of the merge have archived items, merge the
3893 ;; source items to the goal items, else "merge" by renaming the
3894 ;; source category to goal.
3895 (if gbeg
3896 (progn
3897 (goto-char (if (re-search-forward
3898 (concat "^" (regexp-quote todos-category-beg))
3899 nil t)
3900 (match-beginning 0)
3901 (point-max)))
3902 (insert carch)
3903 (remove-overlays cbeg cend)
3904 (delete-region cbeg cend))
3905 (goto-char cbeg)
3906 (search-forward cat)
3907 (replace-match goal))
3908 (setq todos-categories (todos-make-categories-list t))
3909 (todos-update-categories-sexp)))))
3910 (with-current-buffer (get-file-buffer tfile)
3911 (when archived-count
3912 (unless (zerop archived-count)
3913 (todos-update-count 'archived archived-count goal)
3914 (todos-update-categories-sexp)))
3817 (todos-category-number goal) 3915 (todos-category-number goal)
3818 (todos-category-select) 3916 ;; If there are only merged done items, show them.
3819 ;; Put point at the start of the merged todo items. 3917 (let ((todos-show-with-done (zerop (todos-get-count 'todo goal))))
3820 ;; FIXME: what if there are no merged todo items but only done items? 3918 (todos-category-select)
3821 (goto-char here)))) 3919 ;; Put point on the first merged item.
3920 (goto-char here)))
3921 (set-marker here nil)))
3822 3922
3823(defun todos-set-category-priority (&optional arg) 3923(defun todos-set-category-priority (&optional arg)
3824 "Change priority of category at point in Todos Categories buffer. 3924 "Change priority of category at point in Todos Categories buffer.
@@ -3922,6 +4022,11 @@ mandatory date header string and how it is added:
3922 when the user puts the cursor on a date and hits RET, that 4022 when the user puts the cursor on a date and hits RET, that
3923 date, in the format set by `calendar-date-display-form', 4023 date, in the format set by `calendar-date-display-form',
3924 becomes the date in the header. 4024 becomes the date in the header.
4025- If DATE-TYPE is a string matching the regexp
4026 `todos-date-pattern', that string becomes the date in the
4027 header. This case is for the command
4028 `todos-insert-item-from-calendar' which is called from the
4029 Calendar.
3925- If DATE-TYPE is the symbol `date', the header contains the date 4030- If DATE-TYPE is the symbol `date', the header contains the date
3926 in the format set by `calendar-date-display-form', with year, 4031 in the format set by `calendar-date-display-form', with year,
3927 month and day individually prompted for (month with tab 4032 month and day individually prompted for (month with tab
@@ -3999,6 +4104,9 @@ the priority is not given by HERE but by prompting."
3999 ((eq date-type 'calendar) 4104 ((eq date-type 'calendar)
4000 (setq todos-date-from-calendar t) 4105 (setq todos-date-from-calendar t)
4001 (todos-set-date-from-calendar)) 4106 (todos-set-date-from-calendar))
4107 ((string-match todos-date-pattern date-type)
4108 (setq todos-date-from-calendar date-type)
4109 (todos-set-date-from-calendar))
4002 (t (calendar-date-string (calendar-current-date) t t)))) 4110 (t (calendar-date-string (calendar-current-date) t t))))
4003 (time-string (or (and time (todos-read-time)) 4111 (time-string (or (and time (todos-read-time))
4004 (and todos-always-add-time-string 4112 (and todos-always-add-time-string
@@ -4055,19 +4163,21 @@ the priority is not given by HERE but by prompting."
4055 4163
4056(defun todos-set-date-from-calendar () 4164(defun todos-set-date-from-calendar ()
4057 "Return string of date chosen from Calendar." 4165 "Return string of date chosen from Calendar."
4058 (when todos-date-from-calendar 4166 (cond ((string-match todos-date-pattern todos-date-from-calendar)
4059 (let (calendar-view-diary-initially-flag) 4167 todos-date-from-calendar)
4060 (calendar)) 4168 ((todos-date-from-calendar t)
4061 ;; *Calendar* is now current buffer. 4169 (let (calendar-view-diary-initially-flag)
4062 (local-set-key (kbd "RET") 'exit-recursive-edit) 4170 (calendar))
4063 (message "Put cursor on a date and type <return> to set it.") 4171 ;; *Calendar* is now current buffer.
4064 ;; FIXME: is there a better way than recursive-edit? Use unwind-protect? 4172 (local-set-key (kbd "RET") 'exit-recursive-edit)
4065 ;; Check recursive-depth? 4173 (message "Put cursor on a date and type <return> to set it.")
4066 (recursive-edit) 4174 ;; FIXME: is there a better way than recursive-edit? Use unwind-protect?
4067 (setq todos-date-from-calendar 4175 ;; Check recursive-depth?
4068 (calendar-date-string (calendar-cursor-to-date t) t t)) 4176 (recursive-edit)
4069 (calendar-exit) 4177 (setq todos-date-from-calendar
4070 todos-date-from-calendar)) 4178 (calendar-date-string (calendar-cursor-to-date t) t t))
4179 (calendar-exit)
4180 todos-date-from-calendar)))
4071 4181
4072(defun todos-delete-item () 4182(defun todos-delete-item ()
4073 "Delete at least one item in this category. 4183 "Delete at least one item in this category.
@@ -4075,45 +4185,49 @@ the priority is not given by HERE but by prompting."
4075If there are marked items, delete all of these; otherwise, delete 4185If there are marked items, delete all of these; otherwise, delete
4076the item at point." 4186the item at point."
4077 (interactive) 4187 (interactive)
4078 (let* ((cat (todos-current-category)) 4188 (let (ov)
4079 (marked (assoc cat todos-categories-with-marks)) 4189 (unwind-protect
4080 (item (unless marked (todos-item-string))) 4190 (let* ((cat (todos-current-category))
4081 (ov (make-overlay (save-excursion (todos-item-start)) 4191 (marked (assoc cat todos-categories-with-marks))
4082 (save-excursion (todos-item-end)))) 4192 (item (unless marked (todos-item-string)))
4083 ;; FIXME: make confirmation an option? 4193 ;; FIXME: make confirmation an option?
4084 (answer (if marked 4194 (answer (if marked
4085 (y-or-n-p "Permanently delete all marked items? ") 4195 (y-or-n-p "Permanently delete all marked items? ")
4086 (when item 4196 (when item
4087 (overlay-put ov 'face 'todos-search) 4197 (setq ov (make-overlay
4088 (y-or-n-p (concat "Permanently delete this item? "))))) 4198 (save-excursion (todos-item-start))
4089 (opoint (point)) 4199 (save-excursion (todos-item-end))))
4090 buffer-read-only) 4200 (overlay-put ov 'face 'todos-search)
4091 (when answer 4201 (y-or-n-p (concat "Permanently delete this item? ")))))
4092 (and marked (goto-char (point-min))) 4202 (opoint (point))
4093 (catch 'done 4203 buffer-read-only)
4094 (while (not (eobp)) 4204 (when answer
4095 (if (or (and marked (todos-marked-item-p)) item) 4205 (and marked (goto-char (point-min)))
4096 (progn 4206 (catch 'done
4097 (if (todos-done-item-p) 4207 (while (not (eobp))
4098 (todos-update-count 'done -1) 4208 (if (or (and marked (todos-marked-item-p)) item)
4099 (todos-update-count 'todo -1 cat) 4209 (progn
4100 (and (todos-diary-item-p) (todos-update-count 'diary -1))) 4210 (if (todos-done-item-p)
4101 (delete-overlay ov) 4211 (todos-update-count 'done -1)
4102 (todos-remove-item) 4212 (todos-update-count 'todo -1 cat)
4103 ;; Don't leave point below last item. 4213 (and (todos-diary-item-p) (todos-update-count 'diary -1)))
4104 (and item (bolp) (eolp) (< (point-min) (point-max)) 4214 (if ov (delete-overlay ov))
4105 (todos-backward-item)) 4215 (todos-remove-item)
4106 (when item 4216 ;; Don't leave point below last item.
4107 (throw 'done (setq item nil)))) 4217 (and item (bolp) (eolp) (< (point-min) (point-max))
4108 (todos-forward-item)))) 4218 (todos-backward-item))
4109 (when marked 4219 (when item
4110 (remove-overlays (point-min) (point-max) 'before-string todos-item-mark) 4220 (throw 'done (setq item nil))))
4111 (setq todos-categories-with-marks 4221 (todos-forward-item))))
4112 (assq-delete-all cat todos-categories-with-marks)) 4222 (when marked
4113 (goto-char opoint)) 4223 (remove-overlays (point-min) (point-max)
4114 (todos-update-categories-sexp) 4224 'before-string todos-item-mark)
4115 (todos-prefix-overlays)) 4225 (setq todos-categories-with-marks
4116 (if ov (delete-overlay ov)))) 4226 (assq-delete-all cat todos-categories-with-marks))
4227 (goto-char opoint))
4228 (todos-update-categories-sexp)
4229 (todos-prefix-overlays)))
4230 (if ov (delete-overlay ov)))))
4117 4231
4118(defun todos-edit-item () 4232(defun todos-edit-item ()
4119 "Edit the Todo item at point. 4233 "Edit the Todo item at point.
@@ -4539,35 +4653,42 @@ entry/entries in that category."
4539 file1)) 4653 file1))
4540 (count 0) 4654 (count 0)
4541 (count-diary 0) 4655 (count-diary 0)
4542 cat2 nmark) 4656 ov cat2 nmark)
4543 (set-buffer (find-file-noselect file2)) 4657 (set-buffer (find-file-noselect file2))
4544 (setq cat2 (let* ((pl (if (and marked (> (cdr marked) 1)) "s" "")) 4658 (unwind-protect
4545 (name (todos-read-category 4659 (progn
4546 (concat "Move item" pl " to category: "))) 4660 (unless marked
4547 (prompt (concat "Choose a different category than " 4661 (setq ov (make-overlay (save-excursion (todos-item-start))
4548 "the current one\n(type `" 4662 (save-excursion (todos-item-end))))
4549 (key-description 4663 (overlay-put ov 'face 'todos-search))
4550 (car (where-is-internal 4664 (setq cat2 (let* ((pl (if (and marked (> (cdr marked) 1)) "s" ""))
4551 'todos-set-item-priority))) 4665 (name (todos-read-category
4552 "' to reprioritize item " 4666 (concat "Move item" pl " to category: ")))
4553 "within the same category): "))) 4667 (prompt (concat "Choose a different category than "
4554 (while (equal name cat1) 4668 "the current one\n(type `"
4555 (setq name (todos-read-category prompt))) 4669 (key-description
4556 name)) 4670 (car (where-is-internal
4671 'todos-set-item-priority)))
4672 "' to reprioritize item "
4673 "within the same category): ")))
4674 (while (equal name cat1)
4675 (setq name (todos-read-category prompt)))
4676 name)))
4677 (if ov (delete-overlay ov)))
4557 (set-buffer (find-buffer-visiting file1)) 4678 (set-buffer (find-buffer-visiting file1))
4558 (if marked 4679 (if marked
4559 (progn 4680 (progn
4560 (setq item nil) 4681 (setq item nil)
4561 (goto-char (point-min)) 4682 (goto-char (point-min))
4562 (while (not (eobp)) 4683 (while (not (eobp))
4563 (when (todos-marked-item-p) 4684 (when (todos-marked-item-p)
4564 (setq item (concat item (todos-item-string) "\n")) 4685 (setq item (concat item (todos-item-string) "\n"))
4565 (setq count (1+ count)) 4686 (setq count (1+ count))
4566 (when (todos-diary-item-p) 4687 (when (todos-diary-item-p)
4567 (setq count-diary (1+ count-diary)))) 4688 (setq count-diary (1+ count-diary))))
4568 (todos-forward-item)) 4689 (todos-forward-item))
4569 ;; Chop off last newline. 4690 ;; Chop off last newline.
4570 (setq item (substring item 0 -1))) 4691 (setq item (substring item 0 -1)))
4571 (setq count 1) 4692 (setq count 1)
4572 (when (todos-diary-item-p) (setq count-diary 1))) 4693 (when (todos-diary-item-p) (setq count-diary 1)))
4573 (set-window-buffer (selected-window) 4694 (set-window-buffer (selected-window)
@@ -4598,6 +4719,7 @@ entry/entries in that category."
4598 (if (todos-marked-item-p) 4719 (if (todos-marked-item-p)
4599 (todos-remove-item) 4720 (todos-remove-item)
4600 (todos-forward-item)))) 4721 (todos-forward-item))))
4722 (if ov (delete-overlay ov))
4601 (todos-remove-item)))) 4723 (todos-remove-item))))
4602 (todos-update-count 'todo (- count) cat1) 4724 (todos-update-count 'todo (- count) cat1)
4603 (todos-update-count 'diary (- count-diary) cat1) 4725 (todos-update-count 'diary (- count-diary) cat1)
@@ -4712,47 +4834,90 @@ With prefix ARG delete an existing comment."
4712 (insert " [" todos-comment-string ": " comment "]")))))) 4834 (insert " [" todos-comment-string ": " comment "]"))))))
4713 4835
4714;; FIXME: also with marked items 4836;; FIXME: also with marked items
4715;; FIXME: delete comment from restored item or just leave it up to user?
4716(defun todos-item-undo () 4837(defun todos-item-undo ()
4717 "Restore this done item to the todo section of this category. 4838 "Restore this done item to the todo section of this category.
4718If done item has a comment, ask whether to omit the comment from 4839If done item has a comment, ask whether to omit the comment from
4719the restored item." 4840the restored item."
4720 (interactive) 4841 (interactive)
4721 (when (todos-done-item-p) 4842 (let* ((cat (todos-current-category))
4722 (let* ((buffer-read-only) 4843 (marked (assoc cat todos-categories-with-marks)))
4723 (done-item (todos-item-string)) 4844 (when (or marked (todos-done-item-p))
4724 (opoint (point)) 4845 (let ((buffer-read-only)
4725 (orig-mrk (progn (todos-item-start) (point-marker))) 4846 (done-item (todos-item-string))
4726 ;; Find the end of the date string added upon tagging item as done. 4847 (opoint (point))
4727 (start (search-forward "] ")) 4848 (orig-mrk (progn (todos-item-start) (point-marker)))
4728 (end (save-excursion (todos-item-end))) 4849 (first 'first)
4729 item undone) 4850 (item-count 0)
4730 (todos-item-start) 4851 (diary-count 0)
4731 (when (and (re-search-forward (concat " \\[" 4852 start end item undone)
4732 (regexp-quote todos-comment-string) 4853 (and marked (goto-char (point-min)))
4733 ": \\([^]]+\\)\\]") end t) 4854 (catch 'done
4734 (y-or-n-p "Omit comment from restored item? ")) 4855 (while (not (eobp))
4735 (delete-region (match-beginning 0) (match-end 0))) 4856 (if (or (not marked) (and marked (todos-marked-item-p)))
4736 (setq item (buffer-substring start end)) 4857 (if (not (todos-done-item-p))
4737 (todos-remove-item) 4858 (error "Only done items can be undone")
4738 ;; If user cancels before setting new priority, then leave the done item 4859 (todos-item-start)
4739 ;; unchanged. 4860 ;; Find the end of the date string added upon tagging item as
4740 (unwind-protect 4861 ;; done.
4741 (progn 4862 (setq start (search-forward "] "))
4742 (todos-set-item-priority item (todos-current-category) t) 4863 (setq item-count (1+ item-count))
4743 (setq undone t) 4864 (unless (looking-at (regexp-quote todos-nondiary-start))
4744 (todos-update-count 'todo 1) 4865 (setq diary-count (1+ diary-count)))
4745 (todos-update-count 'done -1) 4866 (setq end (save-excursion (todos-item-end)))
4746 (and (todos-diary-item-p) (todos-update-count 'diary 1)) 4867 ;; Ask (once) whether to omit done item's comment. If
4747 (todos-update-categories-sexp)) 4868 ;; affirmed, omit subsequent comments without asking.
4748 (unless undone 4869 (when (re-search-forward
4749 (widen) 4870 (concat " \\[" (regexp-quote todos-comment-string)
4750 (goto-char orig-mrk) 4871 ": [^]]+\\]") end t)
4751 (todos-insert-with-overlays done-item) 4872 (if (eq first 'first)
4752 (let ((todos-show-with-done t)) 4873 (setq first
4753 (todos-category-select) 4874 ;; FIXME: make this a user option?
4754 (goto-char opoint))) 4875 (when (y-or-n-p "Omit comment from restored item? ")
4755 (set-marker orig-mrk nil))))) 4876 'omit))
4877 t)
4878 (when (eq first 'omit)
4879 (delete-region (match-beginning 0) (match-end 0))
4880 (setq end (point))))
4881 (setq item (concat item (buffer-substring start end)
4882 (when marked "\n")))
4883 (todos-remove-item)
4884 (unless marked (throw 'done nil)))
4885 (todos-forward-item))))
4886 (if marked
4887 (progn
4888 (remove-overlays (point-min) (point-max)
4889 'before-string todos-item-mark)
4890 (setq todos-categories-with-marks
4891 (assq-delete-all cat todos-categories-with-marks))
4892 ;; Insert undone items that were marked at end of todo item list.
4893 (widen)
4894 (re-search-forward (concat "^" (regexp-quote todos-category-done))
4895 nil t)
4896 (forward-line -1)
4897 (insert item)
4898 (todos-update-count 'todo item-count)
4899 (todos-update-count 'done (- item-count))
4900 (when diary-count (todos-update-count 'diary diary-count))
4901 (todos-update-categories-sexp))
4902 ;; With an unmarked undone item, prompt for its priority. If user
4903 ;; cancels before setting new priority, then leave the done item
4904 ;; unchanged.
4905 (unwind-protect
4906 (progn
4907 (todos-set-item-priority item (todos-current-category) t)
4908 (setq undone t)
4909 (todos-update-count 'todo 1)
4910 (todos-update-count 'done -1)
4911 (and (todos-diary-item-p) (todos-update-count 'diary 1))
4912 (todos-update-categories-sexp))
4913 (unless undone
4914 (widen)
4915 (goto-char orig-mrk)
4916 (todos-insert-with-overlays done-item)
4917 (let ((todos-show-with-done t))
4918 (todos-category-select)
4919 (goto-char opoint)))
4920 (set-marker orig-mrk nil)))))))
4756 4921
4757(defun todos-archive-done-item (&optional all) 4922(defun todos-archive-done-item (&optional all)
4758 "Archive at least one done item in this category. 4923 "Archive at least one done item in this category.
@@ -4996,31 +5161,24 @@ archive, the archive file is deleted."
4996 5161
4997;;; todos.el ends here 5162;;; todos.el ends here
4998 5163
4999;; ---------------------------------------------------------------------------
5000
5001;; FIXME: remove when part of Emacs 5164;; FIXME: remove when part of Emacs
5165;; ---------------------------------------------------------------------------
5002(add-to-list 'auto-mode-alist '("\\.todo\\'" . todos-mode)) 5166(add-to-list 'auto-mode-alist '("\\.todo\\'" . todos-mode))
5003(add-to-list 'auto-mode-alist '("\\.toda\\'" . todos-archive-mode)) 5167(add-to-list 'auto-mode-alist '("\\.toda\\'" . todos-archive-mode))
5004 5168
5005;;; Addition to calendar.el 5169;;; Addition to calendar.el
5006;; FIXME: autoload when key-binding is defined in calendar.el 5170;; FIXME: autoload when key-binding is defined in calendar.el
5007(defun todos-insert-item-from-calendar () 5171(defun todos-insert-item-from-calendar (&optional arg)
5008 "" 5172 ""
5009 (interactive) 5173 (interactive "P")
5010 ;; FIXME: todos-current-todos-file is nil here, better to solicit Todos 5174 (setq todos-date-from-calendar
5011 ;; file? todos-global-current-todos-file is nil if no Todos file has been 5175 (calendar-date-string (calendar-cursor-to-date t) t t))
5012 ;; visited 5176 (calendar-exit)
5013 (pop-to-buffer (file-name-nondirectory todos-global-current-todos-file))
5014 (todos-show) 5177 (todos-show)
5015 ;; FIXME: this now calls todos-set-date-from-calendar 5178 (todos-insert-item arg nil nil todos-date-from-calendar))
5016 (todos-insert-item t 'calendar))
5017 5179
5018;; FIXME: calendar is loaded before todos 5180(define-key calendar-mode-map "it" 'todos-insert-item-from-calendar)
5019;; (add-hook 'calendar-load-hook
5020 ;; (lambda ()
5021(define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);))
5022 5181
5023;; ---------------------------------------------------------------------------
5024;;; necessitated adaptations to diary-lib.el 5182;;; necessitated adaptations to diary-lib.el
5025 5183
5026;; (defun diary-goto-entry (button) 5184;; (defun diary-goto-entry (button)