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