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