todo-mode.el 25.8 KB
Newer Older
Oliver Seidel's avatar
Oliver Seidel committed
1
;;; todo-mode.el -- Major mode for editing TODO list files
Oliver Seidel's avatar
Oliver Seidel committed
2

Oliver Seidel's avatar
Oliver Seidel committed
3
;; Copyright (C) 1997 Free Software Foundation, Inc.
Oliver Seidel's avatar
Oliver Seidel committed
4

Oliver Seidel's avatar
Oliver Seidel committed
5 6
;; Author: Oliver.Seidel@cl.cam.ac.uk (was valid on Aug 2, 1997)
;; Created: 2 Aug 1997
7
;; Version: $Id: todo-mode.el,v 1.16 1997/10/15 14:00:12 os10000 Exp os10000 $
Oliver Seidel's avatar
Oliver Seidel committed
8
;; Keywords: Categorised TODO list editor, todo-mode
Oliver Seidel's avatar
Oliver Seidel committed
9

Oliver Seidel's avatar
Oliver Seidel committed
10
;; This file is part of GNU Emacs.
11

Oliver Seidel's avatar
Oliver Seidel committed
12
;; GNU Emacs is free software; you can redistribute it and/or modify
13 14 15
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
Oliver Seidel's avatar
Oliver Seidel committed
16 17

;; GNU Emacs is distributed in the hope that it will be useful,
18 19 20
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
Oliver Seidel's avatar
Oliver Seidel committed
21

22 23 24 25 26 27 28
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;; ---------------------------------------------------------------------------

Oliver Seidel's avatar
Oliver Seidel committed
29 30
;;; Commentary:

31 32
;; Quickstart Installation:
;; ========================
33
;;
34
;; To get this to work, make emacs execute the line
35
;;
Oliver Seidel's avatar
Oliver Seidel committed
36
;; (require 'todo-mode)				;; load the TODO package
37
;;
38
;; You may now enter new items by typing "M-x todo-insert-item", or enter
Oliver Seidel's avatar
Oliver Seidel committed
39 40 41 42 43 44 45 46 47 48 49
;; your the TODO list file by typing "M-x todo-show".
;;
;; The TODO list file has a special format and some auxiliary information,
;; which will be added by the todo-show function if it attempts to visit
;; an un-initialised file.  Hence it is recommended to use the todo-show
;; function for the first time, in order to initialise the file, but it
;; is not necessary afterwards.
;;
;; As these commands are quite long to type, I would recommend the addition
;; of two bindings to your to your global keymap.  I personally have the
;; following in my initialisation file:
50
;;
51
;; (global-set-key "\C-ct" 'todo-show)		;; switch to TODO buffer
52
;; (global-set-key "\C-ci" 'todo-insert-item)	;; insert new item
Oliver Seidel's avatar
Oliver Seidel committed
53
;;
Oliver Seidel's avatar
Oliver Seidel committed
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
;; Note, however, that this recommendation has prompted some criticism,
;; since the keys C-c LETTER are reserved for user functions.  I believe
;; my recommendation is acceptable, since the Emacs Lisp Manual *Tips*
;; section also details that the mode itself should not bind any functions
;; to those keys.  The express aim of the above two bindings is to work
;; outside the mode, which doesn't need the show function and offers
;; a different binding for the insert function.  They serve as shortcuts
;; and are not even needed (since the TODO mode will be entered by
;; visiting the TODO file, and later by switching to its buffer).
;;
;;
;;
;; Pre-Requisites
;; ==============
;;
;; This package will require the following packages to be available on
;; the load-path:
;;                 - time-stamp
;;                 - easymenu
;;
74
;;
Oliver Seidel's avatar
Oliver Seidel committed
75
;;
76
;; Description:
77
;; ============
78
;;
79 80 81 82 83 84
;; TODO is a major mode for EMACS which offers functionality to treat
;; most lines in one buffer as a list of items one has to do.  There
;; are facilities to add new items, which are categorised, to edit or
;; even delete items from the buffer.  The buffer contents are currently
;; compatible with the diary, so that the list of todo-items will show
;; up in the FANCY diary mode.
85
;;
86 87 88 89 90 91 92 93 94 95 96
;; Notice:  Besides the major mode, this file also exports the function
;; "todo-show" which will change to the one specific TODO file that has
;; been specified in the todo-file-do variable.  If this file does not
;; conform to the TODO mode conventions, the todo-show function will add
;; the appropriate header and footer.  I don't anticipate this to cause
;; much grief, but be warned, in case you attempt to read a plain text file.
;;
;;
;;
;; Operation:
;; ==========
97 98
;;
;; You will have the following facilities available:
99
;;
100 101
;; M-x todo-show              will enter the todo list screen, here type
;;
102 103
;; +                          to go to next category
;; -                          to go to previous category
104
;; e                          to edit the current entry
105 106
;; f                          to file the current entry, including a
;;                                                 comment and timestamp
107 108
;; i                          to insert a new entry
;; k                          to kill the current entry
109
;; l                          to lower the current entry's priority
110 111 112
;; n                          for the next entry
;; p                          for the previous entry
;; q                          to save the list and exit the buffer
113
;; r                          to raise the current entry's priority
114
;; s                          to save the list
115
;;
116 117 118 119 120 121
;; When you add a new entry, you are asked for the text and then for the
;; category.  I for example have categories for things that I want to do
;; in the office (like mail my mum), that I want to do in town (like buy
;; cornflakes) and things I want to do at home (move my suitcases).  The
;; categories can be selected with the cursor keys and if you type in the
;; name of a category which didn't exist before, an empty category of the
122
;; desired name will be added and filled with the new entry.
123
;;
124 125
;;
;;
126 127
;; Configuration:
;; ==============
128
;;
129 130 131 132 133
;; --- todo-prefix
;;
;; I would like to recommend that you use the prefix "*/*" (by
;; leaving the variable 'todo-prefix' untouched) so that the diary
;; displays each entry every day.
134
;;
135 136 137 138
;; To understand what I mean, please read the documentation that goes
;; with the calendar since that will tell you how you can set up the
;; fancy diary display and use the #include command to include your
;; todo list file as part of your diary.
139
;;
140 141 142 143 144 145
;; If you have the diary package set up to usually display more than
;; one day's entries at once, consider using
;;   "&%%(equal (calendar-current-date) date)"
;; as the value of `todo-prefix'.  Please note that this may slow down
;; the processing of your diary file some.
;;
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
;;
;; --- todo-file-do
;;
;; This variable is fairly self-explanatory.  You have to store your TODO
;; list somewhere.  This variable tells the package where to go and find
;; this file.
;;
;;
;; --- todo-file-done
;;
;; Even when you're done, you may wish to retain the entries.  Given
;; that they're timestamped and you are offered to add a comment, this
;; can make a useful diary of past events.  It will even blend in with
;; the EMACS diary package.  So anyway, this variable holds the name
;; of the file for the filed todo-items.
;;
;;
;; --- todo-mode-hook
;;
;; Just like other modes, too, this mode offers to call your functions
;; before it goes about its business.  This variable will be inspected
;; for any functions you may wish to have called once the other TODO
;; mode preparations have been completed.
;;
;;
171
;; --- todo-insert-treshold
172
;;
173 174 175 176 177 178 179 180 181 182 183
;; Another nifty feature is the insertion accuracy.  If you have 8 items
;; in your TODO list, then you may get asked 4 questions by the binary
;; insertion algorithm.  However, you may not really have a need for such
;; accurate priorities amongst your TODO items.  If you now think about
;; the binary insertion halfing the size of the window each time, then
;; the threshhold is the window size at which it will stop.  If you set
;; the threshhold to zero, the upper and lower bound will coincide at the
;; end of the loop and you will insert your item just before that point.
;; If you set the threshhold to i.e. 8, it will stop as soon as the window
;; size drops below that amount and will insert the item in the approximate
;; centre of that window.  I got the idea for this feature after reading
Oliver Seidel's avatar
Oliver Seidel committed
184
;; a very helpful e-mail reply from Trey Jackson <trey@cs.berkeley.edu>
185 186 187
;; who corrected some of my awful coding and pointed me towards some good
;; reading.  Thanks Trey!
;;
188 189
;;
;;
Oliver Seidel's avatar
Oliver Seidel committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
;;
;; Things to do:
;; =============
;;
;; - licence / version function
;; - export to diary file
;; - todo-report-bug
;; - GNATS support
;; - add idea from Urban Boquist <boquist@cs.chalmers.se>: multi-line-entries
;; - 'e' opens buffer for multi-line entry
;; - elide multiline
;; - rewrite complete package to store data as lisp objects and have
;;   display modes for display, for diary export, etc.
;;
;;
;;
206 207 208
;; History and Gossip:
;; ===================
;;
209 210 211 212 213 214
;; Many thanks to all the ones who have contributed to the evolution of this
;; package!  I hope I have listed all of you somewhere in the documentation
;; or at least in the RCS history!
;;
;; Enjoy this package and express your gratitude by sending nice things
;; to my parents' address!
215 216
;;
;; Oliver Seidel
217
;;
218
;; (O Seidel, Lessingstr. 8, 65760 Eschborn, Federal Republic of Germany)
219 220 221 222
;;

;; ---------------------------------------------------------------------------

Oliver Seidel's avatar
Oliver Seidel committed
223 224 225 226 227
;; ---------------------------------------------------------------------------

;;; Change Log:

;; $Log: todo-mode.el,v $
228 229 230
;; Revision 1.16  1997/10/15  14:00:12  os10000
;; Fixed 'file-item' and added 20.02 split-string function.
;;
231 232 233 234 235
;; Revision 1.15  1997/10/14  22:22:35  os10000
;; Added string-split (which I stole from ediff-util), changed
;; pop-to-buffer to switch-to-buffer and added message on how
;; to exit the multi-line-edit mode.
;;
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
;; Revision 1.14  1997/10/09  09:24:50  os10000
;; Harald Meland <harald.meland@usit.uio.no> asked for
;; the latest version, got 1.13, and returned this.
;; He writes:
;;
;; Thanks a lot for the new version of todo-mode.el.  As you will see I
;; have messed it up a bit, hopefully for the better -- I don't like
;; short, cryptic names for variables and functions, so I renamed most of
;; them, and `defalias'ed the old function names.  I hope you don't mind
;; too much, I just kinda couldn't stop myself.
;;
;; Additionally, I included some support for multiline entries, cleaned
;; up (IMHO :) a lot of the code, included completion-support for which
;; category to install a new entry in, and possibly some other changes I
;; can't remember :)
;;
;; It's getting rather late, and I have just done some preliminary
;; testing on whether all of this really works, but so far it looks
;; good.
;;
256 257 258 259 260 261
;; Revision 1.13  1997/08/19  14:00:36  seidel
;; - changed name to todo-mode
;; - fixed menu descriptions
;; - fixed "pressing abort while filing"
;; - attempted Emacs Lisp Manual *Tips* section compliance
;;
Oliver Seidel's avatar
Oliver Seidel committed
262 263
;; Revision 1.12  1997/08/06  10:56:15  os10000
;; Fixed header, typos, layout, documentation.
264
;;
265 266 267 268
;; Revision 1.11  1997/08/06  09:14:25  os10000
;; Applied patch from Istvan Marko <istvan@cmdmail.amd.com>
;; to make menus work anywhere.
;;
269 270 271 272
;; Revision 1.10  1997/08/06  08:56:03  os10000
;; Acted upon suggestion from Shane Holder <holder@rsn.hp.com>:
;; Cancelling the editing of an entry will not delete it any more.
;;
273 274 275 276 277
;; Revision 1.9  1997/08/06 08:12:03  os10000
;; Improved documentation.  Broke some lines to comply with
;; Richard Stallman's email to please keep in sync with the
;; rest of the Emacs distribution files.
;;
278
;; Revision 1.8  1997/08/05 22:39:04  os10000
Oliver Seidel's avatar
Oliver Seidel committed
279
;; Made todo-mode.el available under GPL.
280 281 282
;;
;; Revision 1.7  1997/08/05 22:34:14  os10000
;; Fixed insertion routine with help from Trey Jackson
283
;; <trey@cs.berkeley.edu>; added todo-inst-tresh;
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
;; fixed keyboard layout to remove unwanted keys.
;;
;; Revision 1.6  1997/08/05 16:47:01  os10000
;; Incorporated menus for XEmacs from Allan.Cochrane@soton.sc.philips.com,
;; fixed TYPO, fixed todo-file-cmd, cleaned up rcs history.
;;
;; Revision 1.5  1997/08/05  14:43:39  os10000
;; Added improvements from Ron Gut <rgut@aware.com>.
;; Added category management.
;;
;; Revision 1.4  1997/08/04  16:18:45  os10000
;; Added Raise/Lower item.
;;
;; Revision 1.3  1997/08/03  12:47:26  os10000
;; Cleaned up variables, prefix and cursor position.
;;
;; Revision 1.2  1997/08/03 12:15:28  os10000
;; It appears to work.
;;
;; Revision 1.1  1997/08/03 12:15:13  os10000
;; Initial revision
;;
306 307 308

;; ---------------------------------------------------------------------------

Oliver Seidel's avatar
Oliver Seidel committed
309 310
;;; Code:

Oliver Seidel's avatar
Oliver Seidel committed
311 312
;; User-configurable variables:

313 314 315 316 317 318
(defvar todo-prefix	"*/*"		"*TODO mode prefix for entries.")
(defvar todo-file-do	"~/.todo-do"	"*TODO mode list file.")
(defvar todo-file-done	"~/.todo-done"	"*TODO mode archive file.")
(defvar todo-mode-hook	nil		"*TODO mode hooks.")
(defvar todo-edit-mode-hook nil		"*TODO Edit mode hooks.")
(defvar todo-insert-treshold 0		"*TODO mode insertion accuracy.")
319
(defvar todo-edit-buffer " *TODO Edit*"	"TODO Edit buffer name.")
Oliver Seidel's avatar
Oliver Seidel committed
320 321 322 323 324 325 326 327

;; Thanks for the ISO time stamp format go to Karl Eichwalder <ke@suse.de>
;; My format string for the appt.el package is "%3b %2d, %y, %02I:%02M%p".
;;
(defvar todo-time-string-format "%y-%02m-%02d %02H:%02M"
  "TODO mode time string format for done entries.
For details see the variable `time-stamp-format'.")

Oliver Seidel's avatar
Oliver Seidel committed
328 329
;; ---------------------------------------------------------------------------

330 331
;; Get some outside help ...

Oliver Seidel's avatar
Oliver Seidel committed
332
(require 'time-stamp)
333 334 335
(require 'easymenu)

;; ---------------------------------------------------------------------------
Oliver Seidel's avatar
Oliver Seidel committed
336

337 338
;; Set up some helpful context ...

339
(defvar todo-categories		nil	"TODO categories.")
340 341 342 343
(defvar todo-cats		nil
  "Old variable for holding the TODO categories.  Use `todo-categories' instead.")
(defvar todo-previous-line	0	"Previous line that I asked about.")
(defvar todo-previous-answer	0	"Previous answer that I got.")
344 345 346 347 348
(defvar todo-mode-map		nil	"TODO mode keymap.")
(defvar todo-category-number	0	"TODO category number.")

;; ---------------------------------------------------------------------------

349 350 351 352
(if todo-mode-map
    nil
  (let ((map (make-keymap)))
    (suppress-keymap map t)
353 354 355 356 357 358 359 360 361 362 363 364 365
    (define-key map "+" 'todo-forward-category)
    (define-key map "-" 'todo-backward-category)
    (define-key map "e" 'todo-edit-item)
    (define-key map "E" 'todo-edit-multiline)
    (define-key map "f" 'todo-file-item)
    (define-key map "i" 'todo-insert-item)
    (define-key map "k" 'todo-delete-item)
    (define-key map "l" 'todo-lower-item)
    (define-key map "n" 'todo-forward-item)
    (define-key map "p" 'todo-backward-item)
    (define-key map "q" 'todo-quit)
    (define-key map "r" 'todo-raise-item)
    (define-key map "s" 'todo-save)
366
    (setq todo-mode-map map)))
Oliver Seidel's avatar
Oliver Seidel committed
367

368 369 370
(defun todo-category-select ()
  "Make TODO mode display the current category correctly."
  (let ((name (nth todo-category-number todo-categories)))
371
    (setq mode-line-buffer-identification
372
	  (concat "Category: " name))
373 374
    (widen)
    (goto-char (point-min))
375 376 377 378 379 380 381 382 383
    (search-forward-regexp
     (concat "^" (regexp-quote (concat todo-prefix " --- " name))))
    (let ((begin (1+ (point-at-eol))))
      (search-forward-regexp "^--- End")
      (narrow-to-region begin (point-at-bol))
      (goto-char (point-min)))))
(defalias 'todo-cat-slct 'todo-category-select)

(defun todo-forward-category () "Go forward to TODO list of next category."
384
  (interactive)
385 386 387 388
  (setq todo-category-number
	(mod (1+ todo-category-number) (length todo-categories)))
  (todo-category-select))
(defalias 'todo-cmd-forw 'todo-forward-category)
389

390
(defun todo-backward-category () "Go back to TODO list of previous category."
391
  (interactive)
392 393 394 395
  (setq todo-category-number
	(mod (1- todo-category-number) (length todo-categories)))
  (todo-category-select))
(defalias 'todo-cmd-back 'todo-backward-category)
396

397
(defun todo-backward-item () "Select previous entry of TODO list."
Oliver Seidel's avatar
Oliver Seidel committed
398
  (interactive)
399
  (search-backward-regexp (concat "^" (regexp-quote todo-prefix)) nil t)
400
  (message ""))
401
(defalias 'todo-cmd-prev 'todo-backward-item)
Oliver Seidel's avatar
Oliver Seidel committed
402

403
(defun todo-forward-item () "Select next entry of TODO list."
Oliver Seidel's avatar
Oliver Seidel committed
404
  (interactive)
405 406 407
  (end-of-line)
  (search-forward-regexp (concat "^" (regexp-quote todo-prefix)) nil 'goto-end)
  (beginning-of-line)
408
  (message ""))
409
(defalias 'todo-cmd-next 'todo-forward-item)
Oliver Seidel's avatar
Oliver Seidel committed
410

411
(defun todo-save () "Save the TODO list."
412
  (interactive)
413
  (save-buffer))
414
(defalias 'todo-cmd-save 'todo-save)
415

416
(defun todo-quit () "Done with TODO list for now."
Oliver Seidel's avatar
Oliver Seidel committed
417
  (interactive)
418 419
  (widen)
  (save-buffer)
420
  (message "")
421
  (bury-buffer))
422
(defalias 'todo-cmd-done 'todo-quit)
Oliver Seidel's avatar
Oliver Seidel committed
423

424
(defun todo-edit-item () "Edit current TODO list entry."
Oliver Seidel's avatar
Oliver Seidel committed
425
  (interactive)
426 427 428 429 430 431 432 433 434 435 436 437 438 439
  (let ((item (todo-item-string)))
    (if (todo-string-multiline-p item)
	(todo-edit-multiline)
      (let ((new (read-from-minibuffer "Edit: " item)))
	(todo-remove-item)
	(insert new "\n")
	(todo-backward-item)
	(message "")))))
(defalias 'todo-cmd-edit 'todo-edit-item)

(defun todo-edit-multiline ()
  "Set up a buffer for editing a multiline TODO list entry."
  (interactive)
  (let ((buffer-name (generate-new-buffer-name todo-edit-buffer)))
440
    (switch-to-buffer (make-indirect-buffer (file-name-nondirectory todo-file-do)
441
					 buffer-name))
442
    (message "To exit, simply kill this buffer and return to list.")
443 444
    (todo-edit-mode)
    (narrow-to-region (todo-item-start) (todo-item-end))))
Oliver Seidel's avatar
Oliver Seidel committed
445

446
(defun todo-add-category (cat) "Add a new category to the TODO list."
Oliver Seidel's avatar
Oliver Seidel committed
447 448
  (interactive)
  (save-window-excursion
449
    (setq todo-categories (cons cat todo-categories))
Oliver Seidel's avatar
Oliver Seidel committed
450
    (find-file todo-file-do)
451 452 453 454
    (widen)
    (goto-char (point-min))
    (let ((posn (search-forward "-*- mode: todo; " 17 t)))
      (if (not (null posn)) (goto-char posn))
455 456 457 458 459
      (if (equal posn nil)
	  (progn
	    (insert "-*- mode: todo; \n")
	    (forward-char -1))
	(kill-line)))
460
    (insert (format "todo-categories: %S; -*-" todo-categories))
461
    (forward-char 1)
462
    (insert (format "%s --- %s\n--- End\n%s %s\n"
463 464
		    todo-prefix cat todo-prefix (make-string 75 ?-))))
  0)
465

466
(defun todo-insert-item ()
467
  "Insert new TODO list entry."
468
  (interactive)
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
  (let* ((new-item (concat todo-prefix " "
			   (read-from-minibuffer "New TODO entry: ")))
         (categories todo-categories)
         (history (cons 'categories (1+ todo-category-number)))
	 (category (completing-read "Category: "
				    (todo-category-alist) nil nil
				    (nth todo-category-number todo-categories)
				    history)))
    (let ((cat-exists (member category todo-categories)))
      (setq todo-category-number
	    (if cat-exists
		(- (length todo-categories) (length cat-exists))
	      (todo-add-category category))))
    (todo-show)
    (setq todo-previous-line 0)
    (let ((top 1)
	  (bottom (1+ (count-lines (point-min) (point-max)))))
      (while (> (- bottom top) todo-insert-treshold)
	(let* ((current (/ (+ top bottom) 2))
	       (answer (if (< current bottom)
			   (todo-more-important-p current) nil)))
	  (if answer
	      (setq bottom current)
	    (setq top (1+ current)))))
      (setq top (/ (+ top bottom) 2))
      ;; goto-line doesn't have the desired behavior in a narrowed buffer
      (goto-char (point-min))
      (forward-line (1- top)))
    (insert new-item "\n")
    (todo-backward-item)
Oliver Seidel's avatar
Oliver Seidel committed
499
    (save-buffer)
500
    (message "")))
501
(defalias 'todo-cmd-inst 'todo-insert-item)
502

503 504 505
(defun todo-more-important-p (line) 
  "Ask whether entry is more important than the one at LINE."
  (if (not (equal todo-previous-line line))
506
      (progn
507
        (setq todo-previous-line line)
508
        (goto-char (point-min))
509 510 511 512 513 514 515 516
        (forward-line (1- todo-previous-line))
	(let ((item (todo-item-string-start)))
	  (setq todo-previous-answer
		(y-or-n-p (concat "More important than '" item "'? "))))))
  todo-previous-answer)
(defalias 'todo-ask-p 'todo-more-important-p)

(defun todo-delete-item () "Delete current TODO list entry."
Oliver Seidel's avatar
Oliver Seidel committed
517 518
  (interactive)
  (if (> (count-lines (point-min) (point-max)) 0)
519 520 521 522 523 524 525
      (let* ((todo-entry (todo-item-string-start))
	     (todo-answer (y-or-n-p (concat "Permanently remove '"
					    todo-entry "'? "))))
	(if todo-answer
	    (progn
	      (todo-remove-item)
	      (todo-backward-item)))
526
	(message ""))
527 528
    (error "No TODO list entry to delete")))
(defalias 'todo-cmd-kill 'todo-delete-item)
Oliver Seidel's avatar
Oliver Seidel committed
529

530
(defun todo-raise-item () "Raise priority of current entry."
Oliver Seidel's avatar
Oliver Seidel committed
531
  (interactive)
532 533 534 535 536 537
  (if (> (count-lines (point-min) (point)) 0)
      (let ((item (todo-item-string)))
	(todo-remove-item)
	(todo-backward-item)
	(save-excursion
	  (insert item "\n"))
538
	(message ""))
539 540
    (error "No TODO list entry to raise")))
(defalias 'todo-cmd-rais 'todo-raise-item)
Oliver Seidel's avatar
Oliver Seidel committed
541

542
(defun todo-lower-item () "Lower priority of current entry."
Oliver Seidel's avatar
Oliver Seidel committed
543
  (interactive)
544 545 546 547 548 549
  (if (> (count-lines (point) (point-max)) 1) ; Assume there is a final newline
      (let ((item (todo-item-string)))
	(todo-remove-item)
	(todo-forward-item)
	(save-excursion
	  (insert item "\n"))
550
	(message ""))
551 552
    (error "No TODO list entry to lower")))
(defalias 'todo-cmd-lowr 'todo-lower-item)
Oliver Seidel's avatar
Oliver Seidel committed
553

554
(defun todo-file-item () "File the current TODO list entry away."
Oliver Seidel's avatar
Oliver Seidel committed
555 556
  (interactive)
  (if (> (count-lines (point-min) (point-max)) 0)
557 558 559 560 561 562 563 564
      (let ((comment (read-from-minibuffer "Comment: "))
	    (time-stamp-format todo-time-string-format))
	(goto-char (todo-item-end))
	(insert (if (save-excursion (beginning-of-line)
				    (looking-at (regexp-quote todo-prefix)))
		    " "
		  "\n\t")
		"(" (nth todo-category-number todo-categories) ": "
565
		comment ")\n")
566 567
	(goto-char (todo-item-start))
	(let ((temp-point (point)))
568 569 570 571
	(if (looking-at (regexp-quote todo-prefix))
	    (replace-match (time-stamp-string))	; Standard prefix -> timestamp
	  ;; Else prefix non-standard item start with timestamp
	  (insert (time-stamp-string)))
572 573 574
	  (append-to-file temp-point (todo-item-end) todo-file-done)
	  (delete-region temp-point (1+ (todo-item-end)))
	  )
575
	(todo-backward-item)
576
	(message ""))
577
    (error "No TODO list entry to file away")))
Oliver Seidel's avatar
Oliver Seidel committed
578 579 580

;; ---------------------------------------------------------------------------

581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
;; Utility functions:

(defun todo-line-string () "Return current line in buffer as a string."
  (buffer-substring (point-at-bol) (point-at-eol)))

(defun todo-item-string-start ()
  "Return the start of this TODO list entry as a string."
  ;; Suitable for putting in the minibuffer when asking the user
  (let ((item (todo-item-string)))
    (if (> (length item) 60)
	(setq item (concat (substring item 0 56) "...")))
    item))

(defun todo-item-start () "Return point at start of current TODO list item."
  (save-excursion
    (beginning-of-line)
    (if (not (looking-at (regexp-quote todo-prefix)))
	(search-backward-regexp
	 (concat "^" (regexp-quote todo-prefix)) nil t))
    (point)))

(defun todo-item-end () "Return point at end of current TODO list item."
  (save-excursion
    (end-of-line)
    (search-forward-regexp (concat "^" (regexp-quote todo-prefix)) nil 'goto-end)
    (1- (point-at-bol))))

(defun todo-remove-item () "Delete the current entry from the TODO list."
  (delete-region (todo-item-start) (1+ (todo-item-end))))

(defun todo-item-string () "Return current TODO list entry as a string."
  (buffer-substring (todo-item-start) (todo-item-end)))

(defun todo-string-count-lines (string)
  "Return the number of lines STRING spans."
  (length (split-string string "\n")))

(defun todo-string-multiline-p (string)
  "Returns non-nil if STRING spans several lines"
  (> (todo-string-count-lines string) 1))

(defun todo-category-alist ()
  "Generate an alist fro use in `completing-read' from `todo-categories'"
624 625
  (mapcar (lambda (cat) (cons cat nil))
	  todo-categories))
626

627 628 629
;; utility functions:  These are available in XEmacs, but not in Emacs 19.34

(if (not (fboundp 'point-at-bol))
630
    (defun point-at-bol () "Return value of point at beginning of line."
631 632 633 634 635
      (save-excursion
	(beginning-of-line)
	(point))))

(if (not (fboundp 'point-at-eol))
636
    (defun point-at-eol () "Return value of point at end of line."
637 638 639 640
      (save-excursion
	(end-of-line)
	(point))))

641 642
;; splits at a white space, returns a list
(if (not (fboundp 'split-string))
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
    (defun split-string (string &optional separators)
      "Splits STRING into substrings where there are matches for SEPARATORS.
Each match for SEPARATORS is a splitting point.
The substrings between the splitting points are made into a list
which is returned.
If SEPARATORS is absent, it defaults to \"[ \\f\\t\\n\\r\\v]+\"."
      (let ((rexp (or separators "[ \f\t\n\r\v]+"))
	    (start 0)
	    (list nil))
	(while (string-match rexp string start)
	  (or (eq (match-beginning 0) 0)
	      (setq list
		    (cons (substring string start (match-beginning 0))
			  list)))
	  (setq start (match-end 0)))
	(or (eq start (length string))
	    (setq list
		  (cons (substring string start)
			list)))
	(nreverse list))))
663

664 665
;; ---------------------------------------------------------------------------

666
(easy-menu-define todo-menu todo-mode-map "Todo Menu"
Oliver Seidel's avatar
Oliver Seidel committed
667
		  '("Todo"
668 669
		    ["Next category"        todo-forward-category t]
		    ["Previous category"    todo-backward-category t]
Oliver Seidel's avatar
Oliver Seidel committed
670
		    "---"
671 672 673 674
		    ["Edit item"            todo-edit-item t]
		    ["File item"            todo-file-item t]
		    ["Insert new item"      todo-insert-item t]
		    ["Kill item"            todo-delete-item t]
Oliver Seidel's avatar
Oliver Seidel committed
675
		    "---"
676 677
		    ["Lower item priority"  todo-lower-item t]
		    ["Raise item priority"  todo-raise-item t]
Oliver Seidel's avatar
Oliver Seidel committed
678
		    "---"
679 680
		    ["Next item"            todo-forward-item t]
		    ["Previous item"        todo-backward-item t]
Oliver Seidel's avatar
Oliver Seidel committed
681
		    "---"
682
		    ["Save"                 todo-save t]
Oliver Seidel's avatar
Oliver Seidel committed
683
		    "---"
684
		    ["Quit"                 todo-quit t]
Oliver Seidel's avatar
Oliver Seidel committed
685
		    ))
686

687
(defun todo-mode () "Major mode for editing TODO lists.\n\n\\{todo-mode-map}"
Oliver Seidel's avatar
Oliver Seidel committed
688 689 690 691
  (interactive)
  (setq major-mode 'todo-mode)
  (setq mode-name "TODO")
  (use-local-map todo-mode-map)
692 693
  (easy-menu-add todo-menu)
  (run-hooks 'todo-mode-hook))
694

695 696 697 698 699 700 701
(defun todo-edit-mode ()
  "Major mode for editing items in the TODO list\n\n\\{todo-edit-mode-map}"
  (text-mode)
  (setq major-mode 'todo-edit-mode)
  (setq mode-name "TODO Edit")
  (run-hooks 'todo-edit-mode-hook))

702 703
(defun todo-show () "Show TODO list."
  (interactive)
704 705 706 707 708 709 710
  (if (file-exists-p todo-file-do)
      (find-file todo-file-do)
    (todo-initial-setup))
  (if (null todo-categories)
      (if (null todo-cats)
	  (error "Error in %s: No categories in list `todo-categories'"
		 todo-file-do)
711 712 713
	(goto-char (point-min))
	(and (search-forward "todo-cats:" nil t)
	     (replace-match "todo-categories:"))
714 715 716 717 718 719
	(make-local-variable todo-categories)
	(setq todo-categories todo-cats)))
  (beginning-of-line)
  (todo-category-select))

(defun todo-initial-setup () "Set up things to work properly in TODO mode."
720
  (find-file todo-file-do)
721 722 723
  (erase-buffer)
  (todo-mode)
  (todo-add-category "Todo"))
Oliver Seidel's avatar
Oliver Seidel committed
724

Oliver Seidel's avatar
Oliver Seidel committed
725
(provide 'todo-mode)
Oliver Seidel's avatar
Oliver Seidel committed
726 727

;; ---------------------------------------------------------------------------
Oliver Seidel's avatar
Oliver Seidel committed
728
;;; todo-mode.el ends here
Oliver Seidel's avatar
Oliver Seidel committed
729
;; ---------------------------------------------------------------------------