Add 2012 to FSF copyright years for Emacs files
[bpt/emacs.git] / lisp / calendar / todo-mode.el
CommitLineData
e8af40ee 1;;; todo-mode.el --- major mode for editing TODO list files
3cb152f9 2
acaf905b 3;; Copyright (C) 1997, 1999, 2001-2012 Free Software Foundation, Inc.
3cb152f9 4
3a67b387
CY
5;; Author: Oliver Seidel <privat@os10000.net>
6;; Maintainer: Stephen Berman <stephen.berman@gmx.net>
7c896f63 7;; Created: 2 Aug 1997
cb9222cf 8;; Keywords: calendar, todo
3cb152f9 9
7c896f63 10;; This file is part of GNU Emacs.
595b2334 11
2ed66575 12;; GNU Emacs is free software: you can redistribute it and/or modify
595b2334 13;; it under the terms of the GNU General Public License as published by
2ed66575
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
7c896f63
OS
16
17;; GNU Emacs is distributed in the hope that it will be useful,
595b2334
OS
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
7c896f63 21
595b2334 22;; You should have received a copy of the GNU General Public License
2ed66575 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
595b2334
OS
24
25;; ---------------------------------------------------------------------------
26
7c896f63
OS
27;;; Commentary:
28
85b3b166
OS
29;; Mode Description
30;;
31;; TODO is a major mode for EMACS which offers functionality to
32;; treat most lines in one buffer as a list of items one has to
33;; do. There are facilities to add new items, which are
da6062e6 34;; categorized, to edit or even delete items from the buffer.
85b3b166
OS
35;; The buffer contents are currently compatible with the diary,
36;; so that the list of todo-items will show up in the FANCY diary
37;; mode.
38;;
39;; Notice: Besides the major mode, this file also exports the
40;; function `todo-show' which will change to the one specific
41;; TODO file that has been specified in the todo-file-do
42;; variable. If this file does not conform to the TODO mode
43;; conventions, the todo-show function will add the appropriate
44;; header and footer. I don't anticipate this to cause much
45;; grief, but be warned, in case you attempt to read a plain text
46;; file.
47;;
01b864bc 48;; Preface, Quickstart Installation
595b2334 49;;
7e83e8b4 50;; To get this to work, make Emacs execute the line
3d8105fb 51;;
e4541b67 52;; (autoload 'todo-mode "todo-mode"
4cbc9d5a
OS
53;; "Major mode for editing TODO lists." t)
54;; (autoload 'todo-show "todo-mode"
55;; "Show TODO items." t)
56;; (autoload 'todo-insert-item "todo-mode"
57;; "Add TODO item." t)
7c896f63 58;;
e4541b67
OS
59;; You may now enter new items by typing "M-x todo-insert-item",
60;; or enter your TODO list file by typing "M-x todo-show".
61;;
62;; The TODO list file has a special format and some auxiliary
63;; information, which will be added by the todo-show function if
e1dbe924 64;; it attempts to visit an un-initialized file. Hence it is
e4541b67 65;; recommended to use the todo-show function for the first time,
e1dbe924 66;; in order to initialize the file, but it is not necessary
e4541b67
OS
67;; afterwards.
68;;
69;; As these commands are quite long to type, I would recommend
70;; the addition of two bindings to your to your global keymap. I
e1dbe924 71;; personally have the following in my initialization file:
e4541b67 72;;
17a1af53
GM
73;; (global-set-key "\C-ct" 'todo-show) ; switch to TODO buffer
74;; (global-set-key "\C-ci" 'todo-insert-item) ; insert new item
7c896f63 75;;
e4541b67
OS
76;; Note, however, that this recommendation has prompted some
77;; criticism, since the keys C-c LETTER are reserved for user
78;; functions. I believe my recommendation is acceptable, since
79;; the Emacs Lisp Manual *Tips* section also details that the
80;; mode itself should not bind any functions to those keys. The
81;; express aim of the above two bindings is to work outside the
82;; mode, which doesn't need the show function and offers a
83;; different binding for the insert function. They serve as
84;; shortcuts and are not even needed (since the TODO mode will be
85;; entered by visiting the TODO file, and later by switching to
86;; its buffer).
87;;
5d035cad
OS
88;; If you are an advanced user of this package, please consult
89;; the whole source code for autoloads, because there are several
90;; extensions that are not explicitly listed in the above quick
91;; installation.
92;;
cb9222cf
DL
93;; Pre-Requisites
94;;
95;; This package will require the following packages to be
96;; available on the load-path:
97;;
98;; time-stamp
99;; easymenu
01b864bc 100;;
e4541b67 101;; Operation
01b864bc
OS
102;;
103;; You will have the following facilities available:
104;;
105;; M-x todo-show will enter the todo list screen, here type
106;;
107;; + to go to next category
108;; - to go to previous category
e4541b67
OS
109;; d to file the current entry, including a
110;; comment and timestamp
01b864bc 111;; e to edit the current entry
1966902e 112;; E to edit a multi-line entry
01b864bc
OS
113;; f to file the current entry, including a
114;; comment and timestamp
4dc1a160 115;; i to insert a new entry, with prefix, omit category
1966902e 116;; I to insert a new entry at current cursor position
e4541b67 117;; j jump to category
01b864bc
OS
118;; k to kill the current entry
119;; l to lower the current entry's priority
120;; n for the next entry
121;; p for the previous entry
e4541b67 122;; P print
01b864bc
OS
123;; q to save the list and exit the buffer
124;; r to raise the current entry's priority
125;; s to save the list
f1757bdd 126;; S to save the list of top priorities
85b3b166 127;; t show top priority items for each category
01b864bc 128;;
e4541b67
OS
129;; When you add a new entry, you are asked for the text and then
130;; for the category. I for example have categories for things
131;; that I want to do in the office (like mail my mum), that I
132;; want to do in town (like buy cornflakes) and things I want to
133;; do at home (move my suitcases). The categories can be
134;; selected with the cursor keys and if you type in the name of a
135;; category which didn't exist before, an empty category of the
136;; desired name will be added and filled with the new entry.
01b864bc
OS
137;;
138;; Configuration
139;;
140;; Variable todo-prefix
141;;
142;; I would like to recommend that you use the prefix "*/*" (by
e4541b67
OS
143;; leaving the variable 'todo-prefix' untouched) so that the
144;; diary displays each entry every day.
01b864bc 145;;
e4541b67
OS
146;; To understand what I mean, please read the documentation that
147;; goes with the calendar since that will tell you how you can
148;; set up the fancy diary display and use the #include command to
149;; include your todo list file as part of your diary.
01b864bc 150;;
e4541b67
OS
151;; If you have the diary package set up to usually display more
152;; than one day's entries at once, consider using
01b864bc
OS
153;;
154;; "&%%(equal (calendar-current-date) date)"
155;;
e4541b67
OS
156;; as the value of `todo-prefix'. Please note that this may slow
157;; down the processing of your diary file some.
01b864bc 158;;
5d035cad
OS
159;; Carsten Dominik <dominik@strw.LeidenUniv.nl> suggested that
160;;
161;; "&%%(todo-cp)"
162;;
163;; might be nicer and to that effect a function has been declared
164;; further down in the code. You may wish to auto-load this.
165;;
166;; Carsten also writes that that *changing* the prefix after the
167;; todo list is already established is not as simple as changing
168;; the variable - the todo files have to be changed by hand.
169;;
01b864bc
OS
170;; Variable todo-file-do
171;;
e4541b67
OS
172;; This variable is fairly self-explanatory. You have to store
173;; your TODO list somewhere. This variable tells the package
174;; where to go and find this file.
01b864bc
OS
175;;
176;; Variable todo-file-done
177;;
e4541b67
OS
178;; Even when you're done, you may wish to retain the entries.
179;; Given that they're timestamped and you are offered to add a
180;; comment, this can make a useful diary of past events. It will
181;; even blend in with the EMACS diary package. So anyway, this
182;; variable holds the name of the file for the filed todo-items.
01b864bc 183;;
f1757bdd
OS
184;; Variable todo-file-top
185;;
186;; File storing the top priorities of your TODO list when
187;; todo-save-top-priorities is non-nil. Nice to include in your
188;; diary instead of the complete TODO list.
189;;
01b864bc
OS
190;; Variable todo-mode-hook
191;;
e4541b67
OS
192;; Just like other modes, too, this mode offers to call your
193;; functions before it goes about its business. This variable
194;; will be inspected for any functions you may wish to have
195;; called once the other TODO mode preparations have been
196;; completed.
197;;
198;; Variable todo-insert-threshold
199;;
200;; Another nifty feature is the insertion accuracy. If you have
201;; 8 items in your TODO list, then you may get asked 4 questions
202;; by the binary insertion algorithm. However, you may not
203;; really have a need for such accurate priorities amongst your
204;; TODO items. If you now think about the binary insertion
bd95c2c0 205;; halving the size of the window each time, then the threshold
e4541b67 206;; is the window size at which it will stop. If you set the
745465ec 207;; threshold to zero, the upper and lower bound will coincide at
e4541b67 208;; the end of the loop and you will insert your item just before
745465ec 209;; that point. If you set the threshold to, e.g. 8, it will stop
e4541b67 210;; as soon as the window size drops below that amount and will
6772c8e1 211;; insert the item in the approximate center of that window. I
e4541b67
OS
212;; got the idea for this feature after reading a very helpful
213;; e-mail reply from Trey Jackson <trey@cs.berkeley.edu> who
214;; corrected some of my awful coding and pointed me towards some
215;; good reading. Thanks Trey!
01b864bc
OS
216;;
217;; Things to do
218;;
e4541b67
OS
219;; These originally were my ideas, but now also include all the
220;; suggestions that I included before forgetting them:
221;;
e4541b67
OS
222;; o Fancy fonts for todo/top-priority buffer
223;; o Remove todo-prefix option in todo-top-priorities
224;; o Rename category
225;; o Move entry from one category to another one
226;; o Entries which both have the generic */* prefix and a
227;; "deadline" entry which are understood by diary, indicating
228;; an event (unless marked by &)
229;; o The optional COUNT variable of todo-forward-item should be
230;; applied to the other functions performing similar tasks
bd95c2c0 231;; o Modularization could be done for repeated elements of
dad8ca4c 232;; the code, like the completing-read lines of code.
e4541b67 233;; o license / version function
01b864bc
OS
234;; o export to diary file
235;; o todo-report-bug
236;; o GNATS support
e4541b67
OS
237;; o elide multiline (as in bbdb, or, to a lesser degree, in
238;; outline mode)
7e83e8b4 239;; o rewrite complete package to store data as Lisp objects
e4541b67 240;; and have display modes for display, for diary export,
bd95c2c0 241;; etc. (Richard Stallman pointed out this is a bad idea)
e4541b67 242;; o so base todo-mode.el on generic-mode.el instead
01b864bc 243;;
e4541b67 244;; History and Gossip
01b864bc 245;;
e4541b67
OS
246;; Many thanks to all the ones who have contributed to the
247;; evolution of this package! I hope I have listed all of you
248;; somewhere in the documentation or at least in the RCS history!
01b864bc 249;;
e4541b67
OS
250;; Enjoy this package and express your gratitude by sending nice
251;; things to my parents' address!
01b864bc
OS
252;;
253;; Oliver Seidel
bd95c2c0 254;; (Lessingstr. 8, 65760 Eschborn, Federal Republic of Germany)
7e6ed9b9 255
7c896f63
OS
256;;; Code:
257
4f9b5264
PJ
258(require 'time-stamp)
259
260
3cb152f9
OS
261;; User-configurable variables:
262
85b3b166
OS
263(defgroup todo nil
264 "Maintain a list of todo items."
ade0f0b8
DL
265 :link '(emacs-commentary-link "todo-mode")
266 :version "21.1"
85b3b166
OS
267 :group 'calendar)
268
cb9222cf 269(defcustom todo-prefix "*/*"
08f7d912 270 "TODO mode prefix for entries.
85b3b166
OS
271
272This is useful in conjunction with `calendar' and `diary' if you use
273
274#include \"~/.todo-do\"
275
276in your diary file to include your todo list file as part of your
277diary. With the default value \"*/*\" the diary displays each entry
278every day and it may also be marked on every day of the calendar.
279Using \"&%%(equal (calendar-current-date) date)\" instead will only
bd95c2c0 280show and mark todo entries for today, but may slow down processing of
85b3b166
OS
281the diary file somewhat."
282 :type 'string
283 :group 'todo)
6d83def6 284(defcustom todo-file-do (convert-standard-filename "~/.todo-do")
08f7d912 285 "TODO mode list file."
85b3b166
OS
286 :type 'file
287 :group 'todo)
6d83def6 288(defcustom todo-file-done (convert-standard-filename "~/.todo-done")
08f7d912 289 "TODO mode archive file."
85b3b166
OS
290 :type 'file
291 :group 'todo)
292(defcustom todo-mode-hook nil
08f7d912 293 "TODO mode hooks."
85b3b166
OS
294 :type 'hook
295 :group 'todo)
296(defcustom todo-edit-mode-hook nil
08f7d912 297 "TODO Edit mode hooks."
85b3b166
OS
298 :type 'hook
299 :group 'todo)
300(defcustom todo-insert-threshold 0
08f7d912 301 "TODO mode insertion accuracy.
85b3b166
OS
302
303If you have 8 items in your TODO list, then you may get asked 4
304questions by the binary insertion algorithm. However, you may not
305really have a need for such accurate priorities amongst your TODO
bd95c2c0 306items. If you now think about the binary insertion halving the size
745465ec
GM
307of the window each time, then the threshold is the window size at
308which it will stop. If you set the threshold to zero, the upper and
85b3b166 309lower bound will coincide at the end of the loop and you will insert
745465ec 310your item just before that point. If you set the threshold to,
85b3b166 311e.g. 8, it will stop as soon as the window size drops below that
77602fc7 312amount and will insert the item in the approximate center of that
85b3b166
OS
313window."
314 :type 'integer
315 :group 'todo)
cb9222cf
DL
316(defvar todo-edit-buffer " *TODO Edit*"
317 "TODO Edit buffer name.")
6d83def6 318(defcustom todo-file-top (convert-standard-filename "~/.todo-top")
08f7d912 319 "TODO mode top priorities file.
85b3b166 320
f1757bdd 321Not in TODO format, but diary compatible.
85b3b166
OS
322Automatically generated when `todo-save-top-priorities' is non-nil."
323 :type 'string
324 :group 'todo)
325
326(defcustom todo-print-function 'ps-print-buffer-with-faces
08f7d912 327 "Function to print the current buffer."
85b3b166
OS
328 :type 'symbol
329 :group 'todo)
330(defcustom todo-show-priorities 1
08f7d912 331 "Default number of priorities to show by \\[todo-top-priorities].
85b3b166
OS
3320 means show all entries."
333 :type 'integer
334 :group 'todo)
335(defcustom todo-print-priorities 0
08f7d912 336 "Default number of priorities to print by \\[todo-print].
85b3b166
OS
3370 means print all entries."
338 :type 'integer
339 :group 'todo)
340(defcustom todo-remove-separator t
08f7d912 341 "Non-nil to remove category separators in\
85b3b166
OS
342\\[todo-top-priorities] and \\[todo-print]."
343 :type 'boolean
344 :group 'todo)
345(defcustom todo-save-top-priorities-too t
08f7d912 346 "Non-nil makes `todo-save' automatically save top-priorities in `todo-file-top'."
85b3b166
OS
347 :type 'boolean
348 :group 'todo)
e4541b67 349
7c896f63
OS
350;; Thanks for the ISO time stamp format go to Karl Eichwalder <ke@suse.de>
351;; My format string for the appt.el package is "%3b %2d, %y, %02I:%02M%p".
352;;
85b3b166 353(defcustom todo-time-string-format
cb9222cf 354 "%:y-%02m-%02d %02H:%02M"
08f7d912 355 "TODO mode time string format for done entries.
85b3b166
OS
356For details see the variable `time-stamp-format'."
357 :type 'string
358 :group 'todo)
359
360(defcustom todo-entry-prefix-function 'todo-entry-timestamp-initials
08f7d912 361 "Function producing text to insert at start of todo entry."
85b3b166
OS
362 :type 'symbol
363 :group 'todo)
364(defcustom todo-initials (or (getenv "INITIALS") (user-login-name))
08f7d912 365 "Initials of todo item author."
85b3b166
OS
366 :type 'string
367 :group 'todo)
e4541b67
OS
368
369(defun todo-entry-timestamp-initials ()
f1757bdd 370 "Prepend timestamp and your initials to the head of a TODO entry."
e4541b67
OS
371 (let ((time-stamp-format todo-time-string-format))
372 (concat (time-stamp-string) " " todo-initials ": ")))
373
cb9222cf
DL
374;; ---------------------------------------------------------------------------
375
cf1ebf43
OS
376;; Set up some helpful context ...
377
9278c60d
DL
378(defvar todo-categories nil
379 "TODO categories.")
cb9222cf 380
9278c60d 381(defvar todo-cats nil
85b3b166
OS
382 "Old variable for holding the TODO categories.
383Use `todo-categories' instead.")
e4541b67 384
cb9222cf
DL
385(defvar todo-previous-line 0
386 "Previous line asked about.")
e4541b67 387
cb9222cf
DL
388(defvar todo-previous-answer 0
389 "Previous answer got.")
cf1ebf43 390
9278c60d 391(defvar todo-mode-map
3d8105fb
OS
392 (let ((map (make-keymap)))
393 (suppress-keymap map t)
7f6241ea
OS
394 (define-key map "+" 'todo-forward-category)
395 (define-key map "-" 'todo-backward-category)
1966902e 396 (define-key map "d" 'todo-file-item) ;done/delete
7f6241ea
OS
397 (define-key map "e" 'todo-edit-item)
398 (define-key map "E" 'todo-edit-multiline)
399 (define-key map "f" 'todo-file-item)
400 (define-key map "i" 'todo-insert-item)
d145aa83 401 (define-key map "I" 'todo-insert-item-here)
e4541b67 402 (define-key map "j" 'todo-jump-to-category)
7f6241ea
OS
403 (define-key map "k" 'todo-delete-item)
404 (define-key map "l" 'todo-lower-item)
405 (define-key map "n" 'todo-forward-item)
406 (define-key map "p" 'todo-backward-item)
e4541b67 407 (define-key map "P" 'todo-print)
7f6241ea
OS
408 (define-key map "q" 'todo-quit)
409 (define-key map "r" 'todo-raise-item)
410 (define-key map "s" 'todo-save)
f1757bdd 411 (define-key map "S" 'todo-save-top-priorities)
e4541b67 412 (define-key map "t" 'todo-top-priorities)
9278c60d
DL
413 map)
414 "TODO mode keymap.")
415
cb9222cf
DL
416(defvar todo-category-number 0 "TODO category number.")
417
418(defvar todo-tmp-buffer-name " *todo tmp*")
419
420(defvar todo-category-sep (make-string 75 ?-)
421 "Category separator.")
422
423(defvar todo-category-beg " --- "
424 "Category start separator to be prepended onto category name.")
425
426(defvar todo-category-end "--- End"
427 "Separator after a category.")
428
429(defvar todo-header "-*- mode: todo; "
430 "Header of todo files.")
431
432;; ---------------------------------------------------------------------------
3cb152f9 433
7f6241ea
OS
434(defun todo-category-select ()
435 "Make TODO mode display the current category correctly."
436 (let ((name (nth todo-category-number todo-categories)))
579e1c67 437 (setq mode-line-buffer-identification
cb9222cf 438;; (concat "Category: " name))
6fe3681b 439 (concat "Category: " (format "%18s" name)))
da2ee685
OS
440 (widen)
441 (goto-char (point-min))
7f6241ea 442 (search-forward-regexp
e4541b67
OS
443 (concat "^"
444 (regexp-quote (concat todo-prefix todo-category-beg name))
445 "$"))
9278c60d 446 (let ((begin (1+ (line-end-position))))
e4541b67 447 (search-forward-regexp (concat "^" todo-category-end))
9278c60d 448 (narrow-to-region begin (line-beginning-position))
7f6241ea
OS
449 (goto-char (point-min)))))
450(defalias 'todo-cat-slct 'todo-category-select)
451
9278c60d
DL
452(defun todo-forward-category ()
453 "Go forward to TODO list of next category."
da2ee685 454 (interactive)
7f6241ea 455 (setq todo-category-number
01b864bc 456 (mod (1+ todo-category-number) (length todo-categories)))
7f6241ea
OS
457 (todo-category-select))
458(defalias 'todo-cmd-forw 'todo-forward-category)
da2ee685 459
9278c60d
DL
460(defun todo-backward-category ()
461 "Go back to TODO list of previous category."
da2ee685 462 (interactive)
7f6241ea 463 (setq todo-category-number
01b864bc 464 (mod (1- todo-category-number) (length todo-categories)))
7f6241ea
OS
465 (todo-category-select))
466(defalias 'todo-cmd-back 'todo-backward-category)
da2ee685 467
9278c60d
DL
468(defun todo-backward-item ()
469 "Select previous entry of TODO list."
3cb152f9 470 (interactive)
7f6241ea 471 (search-backward-regexp (concat "^" (regexp-quote todo-prefix)) nil t)
cf1ebf43 472 (message ""))
7f6241ea 473(defalias 'todo-cmd-prev 'todo-backward-item)
3cb152f9 474
e4541b67 475(defun todo-forward-item (&optional count)
85b3b166 476 "Select COUNT-th next entry of TODO list."
e4541b67
OS
477 (interactive "P")
478 (if (listp count) (setq count (car count)))
7f6241ea 479 (end-of-line)
e4541b67
OS
480 (search-forward-regexp (concat "^" (regexp-quote todo-prefix))
481 nil 'goto-end count)
7f6241ea 482 (beginning-of-line)
cf1ebf43 483 (message ""))
7f6241ea 484(defalias 'todo-cmd-next 'todo-forward-item)
3cb152f9 485
cb9222cf
DL
486(defun todo-save ()
487 "Save the TODO list."
da2ee685 488 (interactive)
ade67f6a
RS
489 (save-excursion
490 (save-restriction
887c6c1f
RS
491 (save-buffer)))
492 (if todo-save-top-priorities-too (todo-save-top-priorities)))
7f6241ea 493(defalias 'todo-cmd-save 'todo-save)
da2ee685 494
cb9222cf
DL
495(defun todo-quit ()
496 "Done with TODO list for now."
3cb152f9 497 (interactive)
da2ee685 498 (widen)
f1757bdd 499 (todo-save)
7e6ed9b9 500 (message "")
cf1ebf43 501 (bury-buffer))
7f6241ea 502(defalias 'todo-cmd-done 'todo-quit)
3cb152f9 503
cb9222cf
DL
504(defun todo-edit-item ()
505 "Edit current TODO list entry."
3cb152f9 506 (interactive)
d566f228
GM
507 (if (< (point-min) (point-max))
508 (let ((item (todo-item-string)))
509 (if (todo-string-multiline-p item)
510 (todo-edit-multiline)
511 (let ((new (read-from-minibuffer "Edit: " item)))
512 (todo-remove-item)
513 (insert new "\n")
514 (todo-backward-item)
515 (message ""))))
516 (error "No TODO list entry to edit")))
7f6241ea
OS
517(defalias 'todo-cmd-edit 'todo-edit-item)
518
519(defun todo-edit-multiline ()
520 "Set up a buffer for editing a multiline TODO list entry."
521 (interactive)
522 (let ((buffer-name (generate-new-buffer-name todo-edit-buffer)))
e4541b67
OS
523 (switch-to-buffer
524 (make-indirect-buffer
cb9222cf 525 (file-name-nondirectory todo-file-do) buffer-name))
2186b974 526 (message "To exit, simply kill this buffer and return to list.")
7f6241ea
OS
527 (todo-edit-mode)
528 (narrow-to-region (todo-item-start) (todo-item-end))))
3cb152f9 529
ade0f0b8 530;;;###autoload
685b5479 531(defun todo-add-category (&optional cat)
85b3b166 532 "Add new category CAT to the TODO list."
685b5479
CY
533 (interactive)
534 (let ((buf (find-file-noselect todo-file-do t))
535 (prompt "Category: "))
536 (unless (zerop (buffer-size buf))
537 (and (null todo-categories)
538 (null todo-cats)
da6062e6 539 (error "Error in %s: File is non-empty but contains no category"
685b5479
CY
540 todo-file-do)))
541 (unless cat (setq cat (read-from-minibuffer prompt)))
542 (with-current-buffer buf
543 ;; reject names that could induce bugs and confusion
544 (while (and (cond ((string= "" cat)
545 (setq prompt "Enter a non-empty category name: "))
546 ((string-match "\\`\\s-+\\'" cat)
547 (setq prompt "Enter a category name that is not only white space: "))
548 ((member cat todo-categories)
549 (setq prompt "Enter a non-existing category name: ")))
550 (setq cat (read-from-minibuffer prompt))))
551 ;; initialize a newly created Todo buffer for Todo mode
552 (unless (file-exists-p todo-file-do) (todo-mode))
553 (setq todo-categories (cons cat todo-categories))
554 (widen)
555 (goto-char (point-min))
7e83e8b4 556 (if (search-forward "-*- mode: todo; " (+ (point-min) 16) t)
685b5479
CY
557 (kill-line)
558 (insert "-*- mode: todo; \n")
559 (forward-char -1))
560 (insert (format "todo-categories: %S; -*-" todo-categories))
561 (forward-char 1)
562 (insert (format "%s%s%s\n%s\n%s %s\n"
563 todo-prefix todo-category-beg cat
564 todo-category-end
565 todo-prefix todo-category-sep))
32226619 566 (if (called-interactively-p 'interactive)
685b5479
CY
567 ;; properly display the newly added category
568 (progn (setq todo-category-number 0) (todo-show))
569 0))))
da2ee685 570
ade0f0b8 571;;;###autoload
4dc1a160 572(defun todo-add-item-non-interactively (new-item category)
85b3b166 573 "Insert NEW-ITEM in TODO list as a new entry in CATEGORY."
4cbc9d5a 574 (save-excursion
49b2ae0e
OS
575 (todo-show))
576 (save-excursion
e4541b67
OS
577 (if (string= "" category)
578 (setq category (nth todo-category-number todo-categories)))
cb9222cf
DL
579 (let ((cat-exists (member category todo-categories)))
580 (setq todo-category-number
581 (if cat-exists
582 (- (length todo-categories) (length cat-exists))
583 (todo-add-category category))))
4dc1a160
OS
584 (todo-show)
585 (setq todo-previous-line 0)
586 (let ((top 1)
587 (bottom (1+ (count-lines (point-min) (point-max)))))
588 (while (> (- bottom top) todo-insert-threshold)
589 (let* ((current (/ (+ top bottom) 2))
590 (answer (if (< current bottom)
cb9222cf 591 (todo-more-important-p current) nil)))
4dc1a160
OS
592 (if answer
593 (setq bottom current)
594 (setq top (1+ current)))))
595 (setq top (/ (+ top bottom) 2))
17a1af53 596 ;; goto-line doesn't have the desired behavior in a narrowed buffer.
4dc1a160
OS
597 (goto-char (point-min))
598 (forward-line (1- top)))
cb9222cf 599 (insert new-item "\n")
7f6241ea 600 (todo-backward-item)
f1757bdd 601 (todo-save)
3d8105fb 602 (message "")))
4cbc9d5a 603
ade0f0b8 604;;;###autoload
89802f43 605(defun todo-insert-item (arg)
85b3b166 606 "Insert new TODO list entry.
7e83e8b4 607With a prefix argument ARG solicit the category, otherwise use the current
85b3b166 608category."
4cbc9d5a 609 (interactive "P")
49b2ae0e 610 (save-excursion
3da360a7 611 (if (not (derived-mode-p 'todo-mode)) (todo-show))
1966902e
OS
612 (let* ((new-item (concat todo-prefix " "
613 (read-from-minibuffer
614 "New TODO entry: "
615 (if todo-entry-prefix-function
616 (funcall todo-entry-prefix-function)))))
1966902e 617 (current-category (nth todo-category-number todo-categories))
685b5479 618 (category (if arg (todo-completing-read) current-category)))
4dc1a160 619 (todo-add-item-non-interactively new-item category))))
4cbc9d5a 620
7f6241ea 621(defalias 'todo-cmd-inst 'todo-insert-item)
3d8105fb 622
d145aa83 623(defun todo-insert-item-here ()
685b5479
CY
624 "Insert a new TODO list entry directly above the entry at point.
625If point is on an empty line, insert the entry there."
626 (interactive)
627 (if (not (derived-mode-p 'todo-mode)) (todo-show))
628 (let ((new-item (concat todo-prefix " "
629 (read-from-minibuffer
630 "New TODO entry: "
631 (if todo-entry-prefix-function
632 (funcall todo-entry-prefix-function))))))
7e83e8b4 633 (unless (and (bolp) (eolp)) (todo-item-start))
685b5479
CY
634 (insert (concat new-item "\n"))
635 (backward-char)
636 ;; put point at start of new entry
7e83e8b4 637 (todo-item-start)))
d145aa83 638
01b864bc 639(defun todo-more-important-p (line)
7f6241ea 640 "Ask whether entry is more important than the one at LINE."
17a1af53
GM
641 (unless (equal todo-previous-line line)
642 (setq todo-previous-line line)
643 (goto-char (point-min))
644 (forward-line (1- todo-previous-line))
645 (let ((item (todo-item-string-start)))
646 (setq todo-previous-answer
647 (y-or-n-p (concat "More important than '" item "'? ")))))
7f6241ea
OS
648 todo-previous-answer)
649(defalias 'todo-ask-p 'todo-more-important-p)
650
cb9222cf
DL
651(defun todo-delete-item ()
652 "Delete current TODO list entry."
3cb152f9
OS
653 (interactive)
654 (if (> (count-lines (point-min) (point-max)) 0)
7f6241ea 655 (let* ((todo-entry (todo-item-string-start))
01b864bc
OS
656 (todo-answer (y-or-n-p (concat "Permanently remove '"
657 todo-entry "'? "))))
17a1af53
GM
658 (when todo-answer
659 (todo-remove-item)
660 (todo-backward-item))
01b864bc 661 (message ""))
7f6241ea
OS
662 (error "No TODO list entry to delete")))
663(defalias 'todo-cmd-kill 'todo-delete-item)
3cb152f9 664
cb9222cf
DL
665(defun todo-raise-item ()
666 "Raise priority of current entry."
8cdc3b3d 667 (interactive)
7f6241ea
OS
668 (if (> (count-lines (point-min) (point)) 0)
669 (let ((item (todo-item-string)))
01b864bc
OS
670 (todo-remove-item)
671 (todo-backward-item)
672 (save-excursion
cb9222cf 673 (insert item "\n"))
01b864bc 674 (message ""))
7f6241ea 675 (error "No TODO list entry to raise")))
cb9222cf 676(defalias 'todo-cmd-rais 'todo-raise-item)
8cdc3b3d 677
cb9222cf
DL
678(defun todo-lower-item ()
679 "Lower priority of current entry."
8cdc3b3d 680 (interactive)
e4541b67
OS
681 (if (> (count-lines (point) (point-max)) 1)
682 ;; Assume there is a final newline
7f6241ea 683 (let ((item (todo-item-string)))
01b864bc
OS
684 (todo-remove-item)
685 (todo-forward-item)
686 (save-excursion
cb9222cf 687 (insert item "\n"))
01b864bc 688 (message ""))
7f6241ea
OS
689 (error "No TODO list entry to lower")))
690(defalias 'todo-cmd-lowr 'todo-lower-item)
8cdc3b3d 691
0561decb 692(defun todo-file-item (&optional comment)
a554b301 693 "File the current TODO list entry away, annotated with an optional COMMENT."
0561decb
OS
694 (interactive "sComment: ")
695 (or (> (count-lines (point-min) (point-max)) 0)
696 (error "No TODO list entry to file away"))
697 (let ((time-stamp-format todo-time-string-format))
17a1af53
GM
698 (when (and comment (> (length comment) 0))
699 (goto-char (todo-item-end))
700 (insert
701 (if (save-excursion (beginning-of-line)
702 (looking-at (regexp-quote todo-prefix)))
703 " "
704 "\n\t")
705 "(" comment ")"))
0561decb
OS
706 (goto-char (todo-item-end))
707 (insert " [" (nth todo-category-number todo-categories) "]")
7e83e8b4 708 (todo-item-start)
0561decb
OS
709 (let ((temp-point (point)))
710 (if (looking-at (regexp-quote todo-prefix))
711 (replace-match (time-stamp-string))
712 ;; Standard prefix -> timestamp
713 ;; Else prefix non-standard item start with timestamp
714 (insert (time-stamp-string)))
7e83e8b4
SM
715 (append-to-file temp-point (todo-item-end 'include-sep) todo-file-done)
716 (delete-region temp-point (todo-item-end 'include-sep)))
0561decb
OS
717 (todo-backward-item)
718 (message "")))
3cb152f9
OS
719
720;; ---------------------------------------------------------------------------
721
7f6241ea
OS
722;; Utility functions:
723
cb9222cf
DL
724
725;;;###autoload
7e83e8b4
SM
726(defun todo-top-priorities (&optional nof-priorities category-pr-page
727 interactive)
e4541b67
OS
728 "List top priorities for each category.
729
730Number of entries for each category is given by NOF-PRIORITIES which
7e83e8b4 731defaults to `todo-show-priorities'.
e4541b67
OS
732
733If CATEGORY-PR-PAGE is non-nil, a page separator \'^L\' is inserted
7e83e8b4
SM
734between each category.
735INTERACTIVE should be non-nil if this function is called interactively."
e4541b67 736
7e83e8b4 737 (interactive "P\ni\nP")
e4541b67
OS
738 (or nof-priorities (setq nof-priorities todo-show-priorities))
739 (if (listp nof-priorities) ;universal argument
740 (setq nof-priorities (car nof-priorities)))
a54d1c94 741 (let ((todo-print-buffer-name todo-tmp-buffer-name)
85b3b166
OS
742 ;;(todo-print-category-number 0)
743 (todo-category-break (if category-pr-page "\f" ""))
e4541b67
OS
744 (cat-end
745 (concat
746 (if todo-remove-separator
747 (concat todo-category-end "\n"
748 (regexp-quote todo-prefix) " " todo-category-sep "\n")
749 (concat todo-category-end "\n"))))
750 beg end)
e4541b67 751 (save-excursion
d566f228 752 (todo-show)
e4541b67 753 (save-restriction
d566f228
GM
754 (save-current-buffer
755 (widen)
756 (copy-to-buffer todo-print-buffer-name (point-min) (point-max))
757 (set-buffer todo-print-buffer-name)
758 (goto-char (point-min))
759 (when (re-search-forward (regexp-quote todo-header) nil t)
760 (beginning-of-line 1)
761 (delete-region (point) (line-end-position)))
762 (while (re-search-forward ;Find category start
763 (regexp-quote (concat todo-prefix todo-category-beg))
764 nil t)
765 (setq beg (+ (line-end-position) 1)) ;Start of first entry.
766 (re-search-forward cat-end nil t)
767 (setq end (match-beginning 0))
768 (replace-match todo-category-break)
769 (narrow-to-region beg end) ;In case we have too few entries.
770 (goto-char (point-min))
771 (if (zerop nof-priorities) ;Traverse entries.
772 (goto-char end) ;All entries
773 (todo-forward-item nof-priorities))
774 (setq beg (point))
775 (delete-region beg end)
776 (widen))
777 (and (looking-at "\f") (replace-match "")) ;Remove trailing form-feed.
778 (goto-char (point-min)) ;Due to display buffer
779 )))
7e83e8b4 780 (when interactive (display-buffer todo-print-buffer-name))
e4541b67 781 (message "Type C-x 1 to remove %s window. M-C-v to scroll the help."
9278c60d 782 todo-print-buffer-name)))
e4541b67 783
f1757bdd
OS
784(defun todo-save-top-priorities (&optional nof-priorities)
785 "Save top priorities for each category in `todo-file-top'.
786
787Number of entries for each category is given by NOF-PRIORITIES which
788defaults to `todo-show-priorities'."
789 (interactive "P")
cb9222cf
DL
790 (save-window-excursion
791 (save-excursion
792 (save-restriction
793 (todo-top-priorities nof-priorities)
794 (set-buffer todo-tmp-buffer-name)
795 (write-file todo-file-top)
796 (kill-this-buffer)))))
f1757bdd 797
e4541b67
OS
798;;;###autoload
799(defun todo-print (&optional category-pr-page)
a554b301 800 "Print todo summary using `todo-print-function'.
cb9222cf 801If CATEGORY-PR-PAGE is non-nil, a page separator `^L' is inserted
e4541b67
OS
802between each category.
803
cb9222cf 804Number of entries for each category is given by `todo-print-priorities'."
e4541b67 805 (interactive "P")
cb9222cf
DL
806 (save-window-excursion
807 (save-excursion
808 (save-restriction
809 (todo-top-priorities todo-print-priorities
9278c60d 810 category-pr-page)
cb9222cf
DL
811 (set-buffer todo-tmp-buffer-name)
812 (and (funcall todo-print-function)
813 (kill-this-buffer))
814 (message "Todo printing done.")))))
e4541b67
OS
815
816(defun todo-jump-to-category ()
817 "Jump to a category. Default is previous category."
818 (interactive)
685b5479 819 (let ((category (todo-completing-read)))
e4541b67
OS
820 (if (string= "" category)
821 (setq category (nth todo-category-number todo-categories)))
822 (setq todo-category-number
cb9222cf
DL
823 (if (member category todo-categories)
824 (- (length todo-categories)
825 (length (member category todo-categories)))
826 (todo-add-category category)))
e4541b67
OS
827 (todo-show)))
828
9278c60d
DL
829(defun todo-line-string ()
830 "Return current line in buffer as a string."
831 (buffer-substring (line-beginning-position) (line-end-position)))
7f6241ea
OS
832
833(defun todo-item-string-start ()
834 "Return the start of this TODO list entry as a string."
835 ;; Suitable for putting in the minibuffer when asking the user
836 (let ((item (todo-item-string)))
837 (if (> (length item) 60)
01b864bc 838 (setq item (concat (substring item 0 56) "...")))
7f6241ea
OS
839 item))
840
9278c60d 841(defun todo-item-start ()
7e83e8b4
SM
842 "Go to start of current TODO list item and return point."
843 (beginning-of-line)
844 (if (not (looking-at (regexp-quote todo-prefix)))
845 (search-backward-regexp
846 (concat "^" (regexp-quote todo-prefix)) nil t))
847 (point))
848
849(defun todo-item-end (&optional include-sep)
850 "Return point at end of current TODO list item.
851If INCLUDE-SEP is non-nil, return point after the separator."
7f6241ea
OS
852 (save-excursion
853 (end-of-line)
7e83e8b4
SM
854 (if (search-forward-regexp
855 (concat "^" (regexp-quote todo-prefix)) nil 'goto-end)
856 (goto-char (match-beginning 0)))
857 (unless include-sep (skip-chars-backward "\n"))
858 (point)))
7f6241ea 859
9278c60d
DL
860(defun todo-remove-item ()
861 "Delete the current entry from the TODO list."
7e83e8b4 862 (delete-region (todo-item-start) (todo-item-end 'include-sep)))
7f6241ea 863
cb9222cf
DL
864(defun todo-item-string ()
865 "Return current TODO list entry as a string."
7f6241ea
OS
866 (buffer-substring (todo-item-start) (todo-item-end)))
867
868(defun todo-string-count-lines (string)
869 "Return the number of lines STRING spans."
870 (length (split-string string "\n")))
871
872(defun todo-string-multiline-p (string)
85b3b166 873 "Return non-nil if STRING spans several lines."
7f6241ea
OS
874 (> (todo-string-count-lines string) 1))
875
685b5479
CY
876(defun todo-completing-read ()
877 "Return a category name, with completion, for use in Todo mode."
878 ;; make a copy of todo-categories in case history-delete-duplicates is
879 ;; non-nil, which makes completing-read alter todo-categories
880 (let* ((categories (copy-sequence todo-categories))
881 (history (cons 'todo-categories (1+ todo-category-number)))
882 (default (nth todo-category-number todo-categories))
883 (category (completing-read
884 (concat "Category [" default "]: ")
885 todo-categories nil nil nil history default)))
886 ;; restore the original value of todo-categories
887 (setq todo-categories categories)
888 category))
7f6241ea 889
da2ee685
OS
890;; ---------------------------------------------------------------------------
891
cb9222cf
DL
892(easy-menu-define todo-menu todo-mode-map "Todo Menu"
893 '("Todo"
894 ["Next category" todo-forward-category t]
895 ["Previous category" todo-backward-category t]
896 ["Jump to category" todo-jump-to-category t]
897 ["Show top priority items" todo-top-priorities t]
898 ["Print categories" todo-print t]
899 "---"
900 ["Edit item" todo-edit-item t]
901 ["File item" todo-file-item t]
902 ["Insert new item" todo-insert-item t]
903 ["Insert item here" todo-insert-item-here t]
904 ["Kill item" todo-delete-item t]
905 "---"
906 ["Lower item priority" todo-lower-item t]
907 ["Raise item priority" todo-raise-item t]
908 "---"
909 ["Next item" todo-forward-item t]
910 ["Previous item" todo-backward-item t]
911 "---"
912 ["Save" todo-save t]
913 ["Save Top Priorities" todo-save-top-priorities t]
914 "---"
915 ["Quit" todo-quit t]
916 ))
a360be79 917
e4541b67 918;; As calendar reads .todo-do before todo-mode is loaded.
ade0f0b8 919;;;###autoload
528b9ea9
JB
920(define-derived-mode todo-mode nil "TODO"
921 "Major mode for editing TODO lists."
922 (easy-menu-add todo-menu))
da2ee685 923
4d39c4f7
GM
924(defvar date)
925(defvar entry)
cb9222cf 926
92f0a338 927;; t-c should be used from diary code, which requires calendar.
55d27f41 928(declare-function calendar-current-date "calendar" (&optional offset))
92f0a338 929
5d035cad 930;; Read about this function in the setup instructions above!
ade0f0b8 931;;;###autoload
5d035cad 932(defun todo-cp ()
a554b301 933 "Make a diary entry appear only in the current date's diary."
5d035cad 934 (if (equal (calendar-current-date) date)
a554b301 935 entry))
5d035cad 936
cb9222cf 937(define-derived-mode todo-edit-mode text-mode "TODO Edit"
a554b301
DL
938 "Major mode for editing items in the TODO list.
939
cb9222cf 940\\{todo-edit-mode-map}")
7f6241ea 941
ade0f0b8 942;;;###autoload
a554b301
DL
943(defun todo-show ()
944 "Show TODO list."
da2ee685 945 (interactive)
685b5479
CY
946 ;; Call todo-initial-setup only if there is neither a Todo file nor
947 ;; a corresponding unsaved buffer.
948 (if (or (file-exists-p todo-file-do)
949 (let* ((buf (get-buffer (file-name-nondirectory todo-file-do)))
950 (bufname (buffer-file-name buf)))
951 (equal (expand-file-name todo-file-do) bufname)))
7f6241ea
OS
952 (find-file todo-file-do)
953 (todo-initial-setup))
cb9222cf
DL
954 (if (null todo-categories)
955 (if (null todo-cats)
956 (error "Error in %s: No categories in list `todo-categories'"
957 todo-file-do)
958 (goto-char (point-min))
959 (and (search-forward "todo-cats:" nil t)
960 (replace-match "todo-categories:"))
961 (make-local-variable 'todo-categories)
962 (setq todo-categories todo-cats)))
7f6241ea
OS
963 (beginning-of-line)
964 (todo-category-select))
965
a554b301
DL
966(defun todo-initial-setup ()
967 "Set up things to work properly in TODO mode."
da2ee685 968 (find-file todo-file-do)
7f6241ea
OS
969 (erase-buffer)
970 (todo-mode)
971 (todo-add-category "Todo"))
3cb152f9 972
7c896f63 973(provide 'todo-mode)
3cb152f9 974
7c896f63 975;;; todo-mode.el ends here