todo-mode.el 24.9 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.14 1997/10/09 09:24:50 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 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
;; 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.
;;
248 249 250 251 252 253
;; 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
254 255
;; Revision 1.12  1997/08/06  10:56:15  os10000
;; Fixed header, typos, layout, documentation.
256
;;
257 258 259 260
;; Revision 1.11  1997/08/06  09:14:25  os10000
;; Applied patch from Istvan Marko <istvan@cmdmail.amd.com>
;; to make menus work anywhere.
;;
261 262 263 264
;; 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.
;;
265 266 267 268 269
;; 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.
;;
270
;; Revision 1.8  1997/08/05 22:39:04  os10000
Oliver Seidel's avatar
Oliver Seidel committed
271
;; Made todo-mode.el available under GPL.
272 273 274
;;
;; Revision 1.7  1997/08/05 22:34:14  os10000
;; Fixed insertion routine with help from Trey Jackson
275
;; <trey@cs.berkeley.edu>; added todo-inst-tresh;
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
;; 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
;;
298 299 300

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

Oliver Seidel's avatar
Oliver Seidel committed
301 302
;;; Code:

Oliver Seidel's avatar
Oliver Seidel committed
303 304
;; User-configurable variables:

305 306 307 308
(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.")
309 310 311
(defvar todo-edit-mode-hook nil		"TODO Edit mode hooks.")
(defvar todo-insert-treshold 0		"TODO mode insertion accuracy.")
(defvar todo-edit-buffer " *TODO Edit*"	"TODO Edit buffer name.")
Oliver Seidel's avatar
Oliver Seidel committed
312 313 314 315 316 317 318 319

;; 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
320 321
;; ---------------------------------------------------------------------------

322 323
;; Get some outside help ...

Oliver Seidel's avatar
Oliver Seidel committed
324
(require 'time-stamp)
325 326 327
(require 'easymenu)

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

329 330
;; Set up some helpful context ...

331 332 333
(defvar todo-categories		nil	"TODO categories.")
(defvar todo-previous-line	0	"previous line that I asked about.")
(defvar todo-previous-answer	0	"previous answer that I got.")
334 335 336 337 338
(defvar todo-mode-map		nil	"TODO mode keymap.")
(defvar todo-category-number	0	"TODO category number.")

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

339 340 341 342
(if todo-mode-map
    nil
  (let ((map (make-keymap)))
    (suppress-keymap map t)
343 344 345 346 347 348 349 350 351 352 353 354 355
    (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)
356
    (setq todo-mode-map map)))
Oliver Seidel's avatar
Oliver Seidel committed
357

358 359 360
(defun todo-category-select ()
  "Make TODO mode display the current category correctly."
  (let ((name (nth todo-category-number todo-categories)))
361
    (setq mode-line-buffer-identification
362
	  (concat "Category: " name))
363 364
    (widen)
    (goto-char (point-min))
365 366 367 368 369 370 371 372 373
    (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."
374
  (interactive)
375 376 377 378
  (setq todo-category-number
	(mod (1+ todo-category-number) (length todo-categories)))
  (todo-category-select))
(defalias 'todo-cmd-forw 'todo-forward-category)
379

380
(defun todo-backward-category () "Go back to TODO list of previous category."
381
  (interactive)
382 383 384 385
  (setq todo-category-number
	(mod (1- todo-category-number) (length todo-categories)))
  (todo-category-select))
(defalias 'todo-cmd-back 'todo-backward-category)
386

387
(defun todo-backward-item () "Select previous entry of TODO list."
Oliver Seidel's avatar
Oliver Seidel committed
388
  (interactive)
389
  (search-backward-regexp (concat "^" (regexp-quote todo-prefix)) nil t)
390
  (message ""))
391
(defalias 'todo-cmd-prev 'todo-backward-item)
Oliver Seidel's avatar
Oliver Seidel committed
392

393
(defun todo-forward-item () "Select next entry of TODO list."
Oliver Seidel's avatar
Oliver Seidel committed
394
  (interactive)
395 396 397
  (end-of-line)
  (search-forward-regexp (concat "^" (regexp-quote todo-prefix)) nil 'goto-end)
  (beginning-of-line)
398
  (message ""))
399
(defalias 'todo-cmd-next 'todo-forward-item)
Oliver Seidel's avatar
Oliver Seidel committed
400

401
(defun todo-save () "Save the TODO list."
402
  (interactive)
403
  (save-buffer))
404
(defalias 'todo-cmd-save 'todo-save)
405

406
(defun todo-quit () "Done with TODO list for now."
Oliver Seidel's avatar
Oliver Seidel committed
407
  (interactive)
408 409
  (widen)
  (save-buffer)
410
  (message "")
411
  (bury-buffer))
412
(defalias 'todo-cmd-done 'todo-quit)
Oliver Seidel's avatar
Oliver Seidel committed
413

414
(defun todo-edit-item () "Edit current TODO list entry."
Oliver Seidel's avatar
Oliver Seidel committed
415
  (interactive)
416 417 418 419 420 421 422 423 424 425 426 427 428 429
  (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)))
430
    (switch-to-buffer (make-indirect-buffer (file-name-nondirectory todo-file-do)
431
					 buffer-name))
432
    (message "To exit, simply kill this buffer and return to list.")
433 434
    (todo-edit-mode)
    (narrow-to-region (todo-item-start) (todo-item-end))))
Oliver Seidel's avatar
Oliver Seidel committed
435

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

456
(defun todo-insert-item ()
457
  "Insert new TODO list entry."
458
  (interactive)
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
  (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
489
    (save-buffer)
490
    (message "")))
491
(defalias 'todo-cmd-inst 'todo-insert-item)
492

493 494 495
(defun todo-more-important-p (line) 
  "Ask whether entry is more important than the one at LINE."
  (if (not (equal todo-previous-line line))
496
      (progn
497
        (setq todo-previous-line line)
498
        (goto-char (point-min))
499 500 501 502 503 504 505 506
        (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
507 508
  (interactive)
  (if (> (count-lines (point-min) (point-max)) 0)
509 510 511 512 513 514 515
      (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)))
516
	(message ""))
517 518
    (error "No TODO list entry to delete")))
(defalias 'todo-cmd-kill 'todo-delete-item)
Oliver Seidel's avatar
Oliver Seidel committed
519

520
(defun todo-raise-item () "Raise priority of current entry."
Oliver Seidel's avatar
Oliver Seidel committed
521
  (interactive)
522 523 524 525 526 527
  (if (> (count-lines (point-min) (point)) 0)
      (let ((item (todo-item-string)))
	(todo-remove-item)
	(todo-backward-item)
	(save-excursion
	  (insert item "\n"))
528
	(message ""))
529 530
    (error "No TODO list entry to raise")))
(defalias 'todo-cmd-rais 'todo-raise-item)
Oliver Seidel's avatar
Oliver Seidel committed
531

532
(defun todo-lower-item () "Lower priority of current entry."
Oliver Seidel's avatar
Oliver Seidel committed
533
  (interactive)
534 535 536 537 538 539
  (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"))
540
	(message ""))
541 542
    (error "No TODO list entry to lower")))
(defalias 'todo-cmd-lowr 'todo-lower-item)
Oliver Seidel's avatar
Oliver Seidel committed
543

544
(defun todo-file-item () "File the current TODO list entry away."
Oliver Seidel's avatar
Oliver Seidel committed
545 546
  (interactive)
  (if (> (count-lines (point-min) (point-max)) 0)
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
      (let ((comment (read-from-minibuffer "Comment: "))
	    (time-stamp-format todo-time-string-format))
	(goto-char (todo-item-start))
	(delete-region (point) (search-forward todo-prefix))
	(insert (time-stamp-string))
	(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) ": "
		comment ")")
	(append-to-file (todo-item-start) (todo-item-end) todo-file-done)
	(todo-remove-item)
	(todo-backward-item)
562
	(message ""))
563
    (error "No TODO list entry to file away")))
Oliver Seidel's avatar
Oliver Seidel committed
564 565 566

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

567 568 569 570 571 572 573 574 575 576 577 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
;; 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'"
  (let (alist)
    (mapcar (lambda (cat) (setq alist (cons (cons cat nil) alist)))
	    todo-categories)
    alist))

615 616 617
;; utility functions:  These are available in XEmacs, but not in Emacs 19.34

(if (not (fboundp 'point-at-bol))
618
    (defun point-at-bol () "Return value of point at beginning of line."
619 620 621 622 623
      (save-excursion
	(beginning-of-line)
	(point))))

(if (not (fboundp 'point-at-eol))
624
    (defun point-at-eol () "Return value of point at end of line."
625 626 627 628
      (save-excursion
	(end-of-line)
	(point))))

629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
;; splits at a white space, returns a list
(if (not (fboundp 'split-string))
    (defun split-string (string regex)
      (let ((start 0)
	    (result '())
	    substr)
	(while (string-match regex string start)
	  (let ((match (string-match regex string start)))
	    (setq substr (substring string start match))
	    (if (> (length substr) 0)
		(setq result (cons substr result)))
	    (setq start (match-end 0))))
	(setq substr (substring string start nil))
	(if (> (length substr) 0)
	    (setq result (cons substr result)))
	(nreverse result))))

646 647
;; ---------------------------------------------------------------------------

648
(easy-menu-define todo-menu todo-mode-map "Todo Menu"
Oliver Seidel's avatar
Oliver Seidel committed
649
		  '("Todo"
650 651
		    ["Next category"        todo-forward-category t]
		    ["Previous category"    todo-backward-category t]
Oliver Seidel's avatar
Oliver Seidel committed
652
		    "---"
653 654 655 656
		    ["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
657
		    "---"
658 659
		    ["Lower item priority"  todo-lower-item t]
		    ["Raise item priority"  todo-raise-item t]
Oliver Seidel's avatar
Oliver Seidel committed
660
		    "---"
661 662
		    ["Next item"            todo-forward-item t]
		    ["Previous item"        todo-backward-item t]
Oliver Seidel's avatar
Oliver Seidel committed
663
		    "---"
664
		    ["Save"                 todo-save t]
Oliver Seidel's avatar
Oliver Seidel committed
665
		    "---"
666
		    ["Quit"                 todo-quit t]
Oliver Seidel's avatar
Oliver Seidel committed
667
		    ))
668

669
(defun todo-mode () "Major mode for editing TODO lists.\n\n\\{todo-mode-map}"
Oliver Seidel's avatar
Oliver Seidel committed
670 671 672 673
  (interactive)
  (setq major-mode 'todo-mode)
  (setq mode-name "TODO")
  (use-local-map todo-mode-map)
674 675
  (easy-menu-add todo-menu)
  (run-hooks 'todo-mode-hook))
676

677 678 679 680 681 682 683
(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))

684 685
(defun todo-show () "Show TODO list."
  (interactive)
686 687 688 689 690 691 692 693 694 695 696 697 698
  (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)
	(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."
699
  (find-file todo-file-do)
700 701 702
  (erase-buffer)
  (todo-mode)
  (todo-add-category "Todo"))
Oliver Seidel's avatar
Oliver Seidel committed
703

Oliver Seidel's avatar
Oliver Seidel committed
704
(provide 'todo-mode)
Oliver Seidel's avatar
Oliver Seidel committed
705 706

;; ---------------------------------------------------------------------------
Oliver Seidel's avatar
Oliver Seidel committed
707
;;; todo-mode.el ends here
Oliver Seidel's avatar
Oliver Seidel committed
708
;; ---------------------------------------------------------------------------