todo-mode.el 249 KB
Newer Older
1
;;; todo-mode.el --- facilities for making and maintaining todo lists
2

3
;; Copyright (C) 1997, 1999, 2001-2014 Free Software Foundation, Inc.
4

5 6 7
;; Author: Oliver Seidel <privat@os10000.net>
;;	Stephen Berman <stephen.berman@gmx.net>
;; Maintainer: Stephen Berman <stephen.berman@gmx.net>
8 9
;; Keywords: calendar, todo

10
;; This file is part of GNU Emacs.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; 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.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

27 28 29 30 31 32 33 34
;; This package provides facilities for making, displaying, navigating
;; and editing todo lists, which are prioritized lists of todo items.
;; Todo lists are identified with named categories, so you can group
;; together and separately prioritize thematically related todo items.
;; Each category is stored in a file, which thus provides a further
;; level of organization.  You can create as many todo files, and in
;; each as many categories, as you want.

35
;; With Todo mode you can navigate among the items of a category, and
36 37 38 39
;; between categories in the same and in different todo files.  You
;; can edit todo items, reprioritize them within their category, move
;; them to another category, delete them, or mark items as done and
;; store them separately from the not yet done items in a category.
40 41 42 43 44 45 46 47
;; You can add new todo files, edit and delete them.  You can add new
;; categories, rename and delete them, move categories to another file
;; and merge the items of two categories.  You can also reorder the
;; sequence of categories in a todo file for the purpose of
;; navigation.  You can display summary tables of the categories in a
;; file and the types of items they contain.  And you can compile
;; lists of existing items from multiple categories in one or more
;; todo files, which are filtered by various criteria.
48

49
;; To get started, load this package and type `M-x todo-show'.  This
50 51
;; will prompt you for the name of the first todo file, its first
;; category and the category's first item, create these and display
52
;; them in Todo mode.  Now you can insert further items into the list
53 54
;; (i.e., the category) and assign them priorities by typing `i i'.

55
;; You will probably find it convenient to give `todo-show' a global
56
;; key binding in your init file, since it is one of the entry points
57 58
;; to Todo mode; a good choice is `C-c t', since `todo-show' is
;; bound to `t' in Todo mode.
59

60 61
;; To see a list of all Todo mode commands and their key bindings,
;; including other entry points, type `C-h m' in Todo mode.  Consult
62 63 64 65
;; the documentation strings of the commands for details of their use.
;; The `todo' customization group and its subgroups list the options
;; you can set to alter the behavior of many commands and various
;; aspects of the display.
66 67 68

;; This package is a new version of Oliver Seidel's todo-mode.el.
;; While it retains the same basic organization and handling of todo
69
;; lists and the basic UI, it significantly extends these and adds
70 71 72 73 74 75 76
;; many features.  This required also making changes to the internals,
;; including the file format.  If you have a todo file in old format,
;; then the first time you invoke `todo-show' (i.e., before you have
;; created any todo file in the current format), it will ask you
;; whether to convert that file and show it.  If you choose not to
;; convert the old-style file at this time, you can do so later by
;; calling the command `todo-convert-legacy-files'.
77

78 79
;;; Code:

80
(require 'diary-lib)
81
;; For cl-remove-duplicates (in todo-insertion-commands-args) and
82
;; cl-oddp.
83
(require 'cl-lib)
84

85
;; -----------------------------------------------------------------------------
86
;;; Setting up todo files, categories, and items
87
;; -----------------------------------------------------------------------------
88

89
(defcustom todo-directory (locate-user-emacs-file "todo/")
90
  "Directory where user's todo files are saved."
91
  :type 'directory
92
  :group 'todo)
93

94 95
(defun todo-files (&optional archives)
  "Default value of `todo-files-function'.
96
This returns the case-insensitive alphabetically sorted list of
97
file truenames in `todo-directory' with the extension
98 99
\".todo\".  With non-nil ARCHIVES return the list of archive file
truenames (those with the extension \".toda\")."
100
  (let ((files (if (file-exists-p todo-directory)
101
		   (mapcar 'file-truename
102
		    (directory-files todo-directory t
103 104 105 106 107
				     (if archives "\.toda$" "\.todo$") t)))))
    (sort files (lambda (s1 s2) (let ((cis1 (upcase s1))
				      (cis2 (upcase s2)))
				  (string< cis1 cis2))))))

108 109
(defcustom todo-files-function 'todo-files
  "Function returning the value of the variable `todo-files'.
110
This function should take an optional argument that, if non-nil,
111
makes it return the value of the variable `todo-archives'."
112
  :type 'function
113
  :group 'todo)
114

115
(defvar todo-files (funcall todo-files-function)
116
  "List of truenames of user's todo files.")
117

118
(defvar todo-archives (funcall todo-files-function t)
119
  "List of truenames of user's todo archives.")
120

121
(defvar todo-visited nil
122
  "List of todo files visited in this session by `todo-show'.
123
Used to determine initial display according to the value of
124
`todo-show-first'.")
125

126 127
(defvar todo-file-buffers nil
  "List of file names of live Todo mode buffers.")
128

129
(defvar todo-global-current-todo-file nil
130
  "Variable holding name of current todo file.
131
Used by functions called from outside of Todo mode to visit the
132
current todo file rather than the default todo file (i.e. when
133
users option `todo-show-current-file' is non-nil).")
134

135
(defvar todo-current-todo-file nil
136
  "Variable holding the name of the currently active todo file.")
137

138
(defvar todo-categories nil
139
  "Alist of categories in the current todo file.
140 141 142 143 144
The elements are cons cells whose car is a category name and
whose cdr is a vector of the category's item counts.  These are,
in order, the numbers of todo items, of todo items included in
the Diary, of done items and of archived items.")

145
(defvar todo-category-number 1
146
  "Variable holding the number of the current todo category.
147
Todo categories are numbered starting from 1.")
148

149
(defvar todo-categories-with-marks nil
150 151
  "Alist of categories and number of marked items they contain.")

152
(defconst todo-category-beg "--==-- "
153 154
  "String marking beginning of category (inserted with its name).")

155
(defconst todo-category-done "==--== DONE "
156 157
  "String marking beginning of category's done items.")

158 159
(defcustom todo-done-separator-string "="
  "String determining the value of variable `todo-done-separator'.
160
If the string consists of a single character,
161
`todo-done-separator' will be the string made by repeating this
162 163 164
character for the width of the window, and the length is
automatically recalculated when the window width changes.  If the
string consists of more (or less) than one character, it will be
165
the value of `todo-done-separator'."
166
  :type 'string
167
  :initialize 'custom-initialize-default
168 169
  :set 'todo-reset-done-separator-string
  :group 'todo-display)
170

171 172 173
(defun todo-done-separator ()
  "Return string used as value of variable `todo-done-separator'."
  (let ((sep todo-done-separator-string))
174
    (propertize (if (= 1 (length sep))
175
		    (make-string (window-width) (string-to-char sep))
176 177
		  todo-done-separator-string)
		'face 'todo-done-sep)))
178

179
(defvar todo-done-separator (todo-done-separator)
180
  "String used to visually separate done from not done items.
181
Displayed as an overlay instead of `todo-category-done' when
182
done items are shown.  Its value is determined by user option
183
`todo-done-separator-string'.")
184

185
(defvar todo-show-done-only nil
186
  "If non-nil display only done items in current category.
187 188
Set by the command `todo-toggle-view-done-only' and used by
`todo-category-select'.")
189

190
(defcustom todo-nondiary-marker '("[" "]")
191 192 193 194 195 196
  "List of strings surrounding item date to block diary inclusion.
The first string is inserted before the item date and must be a
non-empty string that does not match a diary date in order to
have its intended effect.  The second string is inserted after
the diary date."
  :type '(list string string)
197
  :group 'todo-edit
198
  :initialize 'custom-initialize-default
199
  :set 'todo-reset-nondiary-marker)
200

201
(defconst todo-nondiary-start (nth 0 todo-nondiary-marker)
202 203
  "String inserted before item date to block diary inclusion.")

204 205
(defconst todo-nondiary-end (nth 1 todo-nondiary-marker)
  "String inserted after item date matching `todo-nondiary-start'.")
206

207
(defconst todo-month-name-array
208 209 210 211
  (vconcat calendar-month-name-array (vector "*"))
  "Array of month names, in order.
The final element is \"*\", indicating an unspecified month.")

212
(defconst todo-month-abbrev-array
213 214 215 216
  (vconcat calendar-month-abbrev-array (vector "*"))
  "Array of abbreviated month names, in order.
The final element is \"*\", indicating an unspecified month.")

217
(defconst todo-date-pattern
218
  (let ((dayname (diary-name-pattern calendar-day-name-array nil t)))
219
    (concat "\\(?4:\\(?5:" dayname "\\)\\|"
220 221
	    (let ((dayname)
		  (monthname (format "\\(?6:%s\\)" (diary-name-pattern
222 223
						    todo-month-name-array
						    todo-month-abbrev-array)))
224 225 226 227 228
		  (month "\\(?7:[0-9]+\\|\\*\\)")
		  (day "\\(?8:[0-9]+\\|\\*\\)")
		  (year "-?\\(?9:[0-9]+\\|\\*\\)"))
	      (mapconcat 'eval calendar-date-display-form ""))
	    "\\)"))
229
  "Regular expression matching a todo item date header.")
230 231

;; By itself this matches anything, because of the `?'; however, it's only
232
;; used in the context of `todo-date-pattern' (but Emacs Lisp lacks
233
;; lookahead).
234 235
(defconst todo-date-string-start
  (concat "^\\(" (regexp-quote todo-nondiary-start) "\\|"
236 237 238
	  (regexp-quote diary-nonmarking-symbol) "\\)?")
  "Regular expression matching part of item header before the date.")

239 240
(defcustom todo-done-string "DONE "
  "Identifying string appended to the front of done todo items."
241
  :type 'string
242
  :initialize 'custom-initialize-default
243 244
  :set 'todo-reset-done-string
  :group 'todo-edit)
245

246 247
(defconst todo-done-string-start
  (concat "^\\[" (regexp-quote todo-done-string))
248
  "Regular expression matching start of done item.")
249

250 251 252
(defconst todo-item-start (concat "\\(" todo-date-string-start "\\|"
				 todo-done-string-start "\\)"
				 todo-date-pattern)
253
  "String identifying start of a todo item.")
254

255
;; -----------------------------------------------------------------------------
256
;;; Todo mode display options
257
;; -----------------------------------------------------------------------------
258

259
(defcustom todo-prefix ""
260 261 262
  "String prefixed to todo items for visual distinction."
  :type '(string :validate
		 (lambda (widget)
263
		   (when (string= (widget-value widget) todo-item-mark)
264 265
		     (widget-put
		      widget :error
266
		      "Invalid value: must be distinct from `todo-item-mark'")
267 268
		     widget)))
  :initialize 'custom-initialize-default
269 270
  :set 'todo-reset-prefix
  :group 'todo-display)
271

272
(defcustom todo-number-prefix t
273 274 275 276
  "Non-nil to prefix items with consecutively increasing integers.
These reflect the priorities of the items in each category."
  :type 'boolean
  :initialize 'custom-initialize-default
277 278
  :set 'todo-reset-prefix
  :group 'todo-display)
279

280
(defun todo-mode-line-control (cat)
281
  "Return a mode line control for todo or archive file buffers.
282
Argument CAT is the name of the current todo category.
283
This function is the value of the user variable
284 285 286
`todo-mode-line-function'."
  (let ((file (todo-short-file-name todo-current-todo-file)))
    (format "%s category %d: %s" file todo-category-number cat)))
287

288
(defcustom todo-mode-line-function 'todo-mode-line-control
289
  "Function that returns a mode line control for Todo mode buffers.
290
The function expects one argument holding the name of the current
291 292
todo category.  The resulting control becomes the local value of
`mode-line-buffer-identification' in each Todo mode buffer."
293
  :type 'function
294
  :group 'todo-display)
295

296
(defcustom todo-highlight-item nil
297 298 299
  "Non-nil means highlight items at point."
  :type 'boolean
  :initialize 'custom-initialize-default
300 301
  :set 'todo-reset-highlight-item
  :group 'todo-display)
302

303
(defcustom todo-wrap-lines t
304 305
  "Non-nil to activate Visual Line mode and use wrap prefix."
  :type 'boolean
306
  :group 'todo-display)
307

308
(defcustom todo-indent-to-here 3
309 310 311 312 313 314 315 316 317
  "Number of spaces to indent continuation lines of items.
This must be a positive number to ensure such items are fully
shown in the Fancy Diary display."
  :type '(integer :validate
		  (lambda (widget)
		    (unless (> (widget-value widget) 0)
		      (widget-put widget :error
				  "Invalid value: must be a positive integer")
		      widget)))
318
  :group 'todo-display)
319

320 321 322
(defun todo-indent ()
  "Indent from point to `todo-indent-to-here'."
  (indent-to todo-indent-to-here todo-indent-to-here))
323

324
(defcustom todo-show-with-done nil
325 326
  "Non-nil to display done items in all categories."
  :type 'boolean
327
  :group 'todo-display)
328

329
;; -----------------------------------------------------------------------------
330
;;; Faces
331
;; -----------------------------------------------------------------------------
332

333 334 335 336 337
(defface todo-key-prompt
  '((t (:weight bold)))
  "Face for making keys in item insertion prompt stand out."
  :group 'todo-faces)

338
(defface todo-mark
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
  ;; '((t :inherit font-lock-warning-face))
  '((((class color)
      (min-colors 88)
      (background light))
     (:weight bold :foreground "Red1"))
    (((class color)
      (min-colors 88)
      (background dark))
     (:weight bold :foreground "Pink"))
    (((class color)
      (min-colors 16)
      (background light))
     (:weight bold :foreground "Red1"))
    (((class color)
      (min-colors 16)
      (background dark))
     (:weight bold :foreground "Pink"))
    (((class color)
      (min-colors 8))
     (:foreground "red"))
    (t
     (:weight bold :inverse-video t)))
  "Face for marks on marked items."
362
  :group 'todo-faces)
363

364
(defface todo-prefix-string
365 366 367 368 369 370 371 372 373 374 375
  ;; '((t :inherit font-lock-constant-face))
  '((((class grayscale) (background light))
     (:foreground "LightGray" :weight bold :underline t))
    (((class grayscale) (background dark))
     (:foreground "Gray50" :weight bold :underline t))
    (((class color) (min-colors 88) (background light)) (:foreground "dark cyan"))
    (((class color) (min-colors 88) (background dark)) (:foreground "Aquamarine"))
    (((class color) (min-colors 16) (background light)) (:foreground "CadetBlue"))
    (((class color) (min-colors 16) (background dark)) (:foreground "Aquamarine"))
    (((class color) (min-colors 8)) (:foreground "magenta"))
    (t (:weight bold :underline t)))
376
  "Face for todo item prefix or numerical priority string."
377
  :group 'todo-faces)
378

379
(defface todo-top-priority
380 381 382 383 384 385 386 387 388 389 390
  ;; bold font-lock-comment-face
  '((default :weight bold)
    (((class grayscale) (background light)) :foreground "DimGray" :slant italic)
    (((class grayscale) (background dark)) :foreground "LightGray" :slant italic)
    (((class color) (min-colors 88) (background light)) :foreground "Firebrick")
    (((class color) (min-colors 88) (background dark)) :foreground "chocolate1")
    (((class color) (min-colors 16) (background light)) :foreground "red")
    (((class color) (min-colors 16) (background dark)) :foreground "red1")
    (((class color) (min-colors 8) (background light)) :foreground "red")
    (((class color) (min-colors 8) (background dark)) :foreground "yellow")
    (t :slant italic))
391
  "Face for top priority todo item numerical priority string.
392 393
The item's priority number string has this face if the number is
less than or equal the category's top priority setting."
394
  :group 'todo-faces)
395

396
(defface todo-nondiary
397 398 399 400 401 402 403 404 405 406
  ;; '((t :inherit font-lock-type-face))
  '((((class grayscale) (background light)) :foreground "Gray90" :weight bold)
    (((class grayscale) (background dark))  :foreground "DimGray" :weight bold)
    (((class color) (min-colors 88) (background light)) :foreground "ForestGreen")
    (((class color) (min-colors 88) (background dark))  :foreground "PaleGreen")
    (((class color) (min-colors 16) (background light)) :foreground "ForestGreen")
    (((class color) (min-colors 16) (background dark))  :foreground "PaleGreen")
    (((class color) (min-colors 8)) :foreground "green")
    (t :weight bold :underline t))
  "Face for non-diary markers around todo item date/time header."
407
  :group 'todo-faces)
408

409
(defface todo-date
410
  '((t :inherit diary))
411
  "Face for the date string of a todo item."
412
  :group 'todo-faces)
413

414
(defface todo-time
415
  '((t :inherit diary-time))
416
  "Face for the time string of a todo item."
417
  :group 'todo-faces)
418

419 420
(defface todo-diary-expired
  ;; Doesn't contrast enough with todo-date (= diary) face.
421 422 423 424 425 426 427 428 429 430 431 432 433
  ;; ;; '((t :inherit warning))
  ;; '((default :weight bold)
  ;;   (((class color) (min-colors 16)) :foreground "DarkOrange")
  ;;   (((class color)) :foreground "yellow"))
  ;; bold font-lock-function-name-face
  '((default :weight bold)
    (((class color) (min-colors 88) (background light)) :foreground "Blue1")
    (((class color) (min-colors 88) (background dark))  :foreground "LightSkyBlue")
    (((class color) (min-colors 16) (background light)) :foreground "Blue")
    (((class color) (min-colors 16) (background dark))  :foreground "LightSkyBlue")
    (((class color) (min-colors 8)) :foreground "blue")
    (t :inverse-video t))
  "Face for expired dates of diary items."
434
  :group 'todo-faces)
435

436
(defface todo-done-sep
437 438 439 440 441 442 443 444 445
  ;; '((t :inherit font-lock-builtin-face))
  '((((class grayscale) (background light)) :foreground "LightGray" :weight bold)
    (((class grayscale) (background dark))  :foreground "DimGray" :weight bold)
    (((class color) (min-colors 88) (background light)) :foreground "dark slate blue")
    (((class color) (min-colors 88) (background dark))  :foreground "LightSteelBlue")
    (((class color) (min-colors 16) (background light)) :foreground "Orchid")
    (((class color) (min-colors 16) (background dark)) :foreground "LightSteelBlue")
    (((class color) (min-colors 8)) :foreground "blue" :weight bold)
    (t :weight bold))
Paul Eggert's avatar
Paul Eggert committed
446
  "Face for separator string between done and not done todo items."
447
  :group 'todo-faces)
448

449
(defface todo-done
450 451 452 453 454 455 456 457 458
  ;; '((t :inherit font-lock-keyword-face))
  '((((class grayscale) (background light)) :foreground "LightGray" :weight bold)
    (((class grayscale) (background dark))  :foreground "DimGray" :weight bold)
    (((class color) (min-colors 88) (background light)) :foreground "Purple")
    (((class color) (min-colors 88) (background dark))  :foreground "Cyan1")
    (((class color) (min-colors 16) (background light)) :foreground "Purple")
    (((class color) (min-colors 16) (background dark))  :foreground "Cyan")
    (((class color) (min-colors 8)) :foreground "cyan" :weight bold)
    (t :weight bold))
459
  "Face for done todo item header string."
460
  :group 'todo-faces)
461

462
(defface todo-comment
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
  ;; '((t :inherit font-lock-comment-face))
  '((((class grayscale) (background light))
     :foreground "DimGray" :weight bold :slant italic)
    (((class grayscale) (background dark))
     :foreground "LightGray" :weight bold :slant italic)
    (((class color) (min-colors 88) (background light))
     :foreground "Firebrick")
    (((class color) (min-colors 88) (background dark))
     :foreground "chocolate1")
    (((class color) (min-colors 16) (background light))
     :foreground "red")
    (((class color) (min-colors 16) (background dark))
     :foreground "red1")
    (((class color) (min-colors 8) (background light))
     :foreground "red")
    (((class color) (min-colors 8) (background dark))
     :foreground "yellow")
    (t :weight bold :slant italic))
481
  "Face for comments appended to done todo items."
482
  :group 'todo-faces)
483

484
(defface todo-search
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
  ;; '((t :inherit match))
  '((((class color)
      (min-colors 88)
      (background light))
     (:background "yellow1"))
    (((class color)
      (min-colors 88)
      (background dark))
     (:background "RoyalBlue3"))
    (((class color)
      (min-colors 8)
      (background light))
     (:foreground "black" :background "yellow"))
    (((class color)
      (min-colors 8)
      (background dark))
     (:foreground "white" :background "blue"))
    (((type tty)
      (class mono))
     (:inverse-video t))
    (t
     (:background "gray")))
507 508
  "Face for matches found by `todo-search'."
  :group 'todo-faces)
509

510
(defface todo-button
511 512 513 514 515 516 517 518 519 520 521 522
  ;; '((t :inherit widget-field))
  '((((type tty))
     (:foreground "black" :background "yellow3"))
    (((class grayscale color)
      (background light))
     (:background "gray85"))
    (((class grayscale color)
      (background dark))
     (:background "dim gray"))
    (t
     (:slant italic)))
  "Face for buttons in table of categories."
523
  :group 'todo-faces)
524

525
(defface todo-sorted-column
526 527 528 529 530 531 532 533 534 535 536
  '((((type tty))
     (:inverse-video t))
    (((class color)
      (background light))
     (:background "grey85"))
    (((class color)
      (background dark))
      (:background "grey85" :foreground "grey10"))
    (t
     (:background "gray")))
  "Face for sorted column in table of categories."
537
  :group 'todo-faces)
538

539
(defface todo-archived-only
540 541 542 543 544 545 546 547 548 549
  ;; '((t (:inherit (shadow))))
  '((((class color)
      (background light))
     (:foreground "grey50"))
    (((class color)
      (background dark))
     (:foreground "grey70"))
    (t
     (:foreground "gray")))
  "Face for archived-only category names in table of categories."
550
  :group 'todo-faces)
551

552
(defface todo-category-string
553 554 555 556 557 558 559 560 561
    ;; '((t :inherit font-lock-type-face))
  '((((class grayscale) (background light)) :foreground "Gray90" :weight bold)
    (((class grayscale) (background dark))  :foreground "DimGray" :weight bold)
    (((class color) (min-colors 88) (background light)) :foreground "ForestGreen")
    (((class color) (min-colors 88) (background dark))  :foreground "PaleGreen")
    (((class color) (min-colors 16) (background light)) :foreground "ForestGreen")
    (((class color) (min-colors 16) (background dark))  :foreground "PaleGreen")
    (((class color) (min-colors 8)) :foreground "green")
    (t :weight bold :underline t))
562 563
  "Face for category-file header in Todo Filtered Items mode."
  :group 'todo-faces)
564

565
;; -----------------------------------------------------------------------------
566
;;; Entering and exiting
567
;; -----------------------------------------------------------------------------
568

569 570 571 572 573 574 575
;; (defcustom todo-visit-files-commands (list 'find-file 'dired-find-file)
;;   "List of file finding commands for `todo-display-as-todo-file'.
;; Invoking these commands to visit a todo file or todo archive file
;; calls `todo-show' or `todo-find-archive', so that the file is
;; displayed correctly."
;;   :type '(repeat function)
;;   :group 'todo)
576

577
(defun todo-short-file-name (file)
578
  "Return the short form of todo file FILE's name.
579 580 581
This lacks the extension and directory components."
  (when (stringp file)
    (file-name-sans-extension (file-name-nondirectory file))))
582

583
(defcustom todo-default-todo-file (todo-short-file-name
584
				   (car (funcall todo-files-function)))
585
  "Todo file visited by first session invocation of `todo-show'."
586 587 588 589
  :type (when todo-files
	  `(radio ,@(mapcar (lambda (f) (list 'const f))
			    (mapcar 'todo-short-file-name
				    (funcall todo-files-function)))))
590
  :group 'todo)
591

592
(defcustom todo-show-current-file t
593
  "Non-nil to make `todo-show' visit the current todo file.
594
Otherwise, `todo-show' always visits `todo-default-todo-file'."
595 596
  :type 'boolean
  :initialize 'custom-initialize-default
597 598
  :set 'todo-set-show-current-file
  :group 'todo)
599

600 601
(defcustom todo-show-first 'first
  "What action to take on first use of `todo-show' on a file."
602 603 604 605 606
  :type '(choice (const :tag "Show first category" first)
		 (const :tag "Show table of categories" table)
		 (const :tag "Show top priorities" top)
		 (const :tag "Show diary items" diary)
		 (const :tag "Show regexp items" regexp))
607
  :group 'todo)
608

609
(defcustom todo-add-item-if-new-category t
610 611
  "Non-nil to prompt for an item after adding a new category."
  :type 'boolean
612
  :group 'todo-edit)
613

614
(defcustom todo-initial-file "Todo"
615
  "Default file name offered on adding first todo file."
616
  :type 'string
617
  :group 'todo)
618

619
(defcustom todo-initial-category "Todo"
620
  "Default category name offered on initializing a new todo file."
621
  :type 'string
622
  :group 'todo)
623

624 625
(defcustom todo-category-completions-files nil
  "List of files for building `todo-read-category' completions."
626
  :type `(set ,@(mapcar (lambda (f) (list 'const f))
627 628 629
			(mapcar 'todo-short-file-name
				(funcall todo-files-function))))
  :group 'todo)
630

631 632
(defcustom todo-completion-ignore-case nil
  "Non-nil means case is ignored by `todo-read-*' functions."
633
  :type 'boolean
634
  :group 'todo)
635

636
;;;###autoload
637
(defun todo-show (&optional solicit-file interactive)
638
  "Visit a todo file and display one of its categories.
639

640 641
When invoked in Todo mode, prompt for which todo file to visit.
When invoked outside of Todo mode with non-nil prefix argument
642
SOLICIT-FILE prompt for which todo file to visit; otherwise visit
643 644
`todo-default-todo-file'.  Subsequent invocations from outside
of Todo mode revisit this file or, with option
645
`todo-show-current-file' non-nil (the default), whichever todo
646
file was last visited.
647

648 649 650
If you call this command before you have created any todo file in
the current format, and you have an todo file in old format, it
will ask you whether to convert that file and show it.
651
Otherwise, calling this command before any todo file exists
652 653 654 655 656
prompts for a file name and an initial category (defaulting to
`todo-initial-file' and `todo-initial-category'), creates both of
these, visits the file and displays the category, and if option
`todo-add-item-if-new-category' is non-nil (the default), prompts
for the first item.
657

658
The first invocation of this command on an existing todo file
659
interacts with the option `todo-show-first': if its value is
660 661 662 663 664 665
`first' (the default), show the first category in the file; if
its value is `table', show the table of categories in the file;
if its value is one of `top', `diary' or `regexp', show the
corresponding saved top priorities, diary items, or regexp items
file, if any.  Subsequent invocations always show the file's
current (i.e., last displayed) category.
666

667
In Todo mode just the category's unfinished todo items are shown
668
by default.  The done items are hidden, but typing
669 670
`\\[todo-toggle-view-done-items]' displays them below the todo
items.  With non-nil user option `todo-show-with-done' both todo
671
and done items are always shown on visiting a category.
672

673
Invoking this command in Todo Archive mode visits the
674
corresponding todo file, displaying the corresponding category."
675 676 677
  (interactive "P\np")
  (when todo-default-todo-file
    (todo-check-file (todo-absolute-file-name todo-default-todo-file)))
678
  (catch 'shown
679 680 681
    ;; Before initializing the first todo first, check if there is a
    ;; legacy todo file and if so, offer to convert to the current
    ;; format and make it the first new todo file.
682
    (unless todo-default-todo-file
683
      (let ((legacy-todo-file (if (boundp 'todo-file-do)
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
    				  todo-file-do
    				(locate-user-emacs-file "todo-do" ".todo-do"))))
    	(when (and (file-exists-p legacy-todo-file)
    		   (y-or-n-p (concat "Do you want to convert a copy of your "
    				     "old todo file to the new format? ")))
    	  (when (todo-convert-legacy-files)
    	    (throw 'shown nil)))))
    (catch 'end
      (let* ((cat)
	     (show-first todo-show-first)
	     (file (cond ((or solicit-file
			      (and interactive
				   (memq major-mode '(todo-mode
						      todo-archive-mode
						      todo-filtered-items-mode))))
			  (if (funcall todo-files-function)
			      (todo-read-file-name "Choose a todo file to visit: "
						    nil t)
			    (user-error "There are no todo files")))
			 ((and (eq major-mode 'todo-archive-mode)
			       ;; Called noninteractively via todo-quit
			       ;; to jump to corresponding category in
			       ;; todo file.
			       (not interactive))
			  (setq cat (todo-current-category))
			  (concat (file-name-sans-extension
				   todo-current-todo-file) ".todo"))
			 (t
			  (or todo-current-todo-file
			      (and todo-show-current-file
				   todo-global-current-todo-file)
			      (todo-absolute-file-name todo-default-todo-file)
			      (todo-add-file)))))
	     add-item first-file)
	(unless todo-default-todo-file
	  ;; We just initialized the first todo file, so make it the default.
	  (setq todo-default-todo-file (todo-short-file-name file)
		first-file t)
	  (todo-reevaluate-default-file-defcustom))
	(unless (member file todo-visited)
	  ;; Can't setq t-c-t-f here, otherwise wrong file shown when
	  ;; todo-show is called from todo-show-categories-table.
	  (let ((todo-current-todo-file file))
	    (cond ((eq todo-show-first 'table)
		   (todo-show-categories-table))
		  ((memq todo-show-first '(top diary regexp))
		   (let* ((shortf (todo-short-file-name file))
			  (fi-file (todo-absolute-file-name
				    shortf todo-show-first)))
		     (when (eq todo-show-first 'regexp)
		       (let ((rxfiles (directory-files todo-directory t
						       ".*\\.todr$" t)))
			 (when (and rxfiles (> (length rxfiles) 1))
			   (let ((rxf (mapcar 'todo-short-file-name rxfiles)))
			     (setq fi-file (todo-absolute-file-name
					    (completing-read
					     "Choose a regexp items file: "
					     rxf) 'regexp))))))
		     (if (file-exists-p fi-file)
743 744 745 746 747 748
			 (progn
			   (set-window-buffer
			    (selected-window)
			    (set-buffer (find-file-noselect fi-file 'nowarn)))
			   (unless (derived-mode-p 'todo-filtered-items-mode)
			     (todo-filtered-items-mode)))
749 750 751 752 753 754 755 756 757 758 759 760 761 762
		       (message "There is no %s file for %s"
				(cond ((eq todo-show-first 'top)
				       "top priorities")
				      ((eq todo-show-first 'diary)
				       "diary items")
				      ((eq todo-show-first 'regexp)
				       "regexp items"))
				shortf)
		       (setq todo-show-first 'first)))))))
	(when (or (member file todo-visited)
		  (eq todo-show-first 'first))
	  (unless (todo-check-file file) (throw 'end nil))
	  (set-window-buffer (selected-window)
			     (set-buffer (find-file-noselect file 'nowarn)))
763 764 765
	  (if (equal (file-name-extension (buffer-file-name)) "toda")
	      (unless (derived-mode-p 'todo-archive-mode) (todo-archive-mode))
	    (unless (derived-mode-p 'todo-mode) (todo-mode)))
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
	  ;; When quitting an archive file, show the corresponding
	  ;; category in the corresponding todo file, if it exists.
	  (when (assoc cat todo-categories)
	    (setq todo-category-number (todo-category-number cat)))
	  ;; If this is a new todo file, add its first category.
	  (when (zerop (buffer-size))
	    (let (cat-added)
	      (unwind-protect
		  (setq todo-category-number
			(todo-add-category todo-current-todo-file "")
			add-item todo-add-item-if-new-category
			cat-added t)
		(if cat-added
		    ;; If the category was added, save the file now, so we
		    ;; don't risk having an empty todo file, which would
		    ;; signal an error if we tried to visit it later,
		    ;; since doing that looks for category boundaries.
		    (save-buffer 0)
		  ;; If user cancels before adding the category, clean up
		  ;; and exit, so we have a fresh slate the next time.
		  (delete-file file)
		  ;; (setq todo-files (funcall todo-files-function))
		  (setq todo-files (delete file todo-files))
		  (when first-file
		    (setq todo-default-todo-file nil
			  todo-current-todo-file nil)
		    (todo-reevaluate-default-file-defcustom))
		  (kill-buffer)
		  (keyboard-quit)))))
	  (save-excursion (todo-category-select))
796
	  (when add-item (todo-insert-item--basic)))
797 798
	(setq todo-show-first show-first)
	(add-to-list 'todo-visited file)))))
799

800
(defun todo-save ()
801
  "Save the current todo file."
802
  (interactive)
803 804 805
  (cond ((eq major-mode 'todo-filtered-items-mode)
	 (todo-check-filtered-items-file)
	 (todo-save-filtered-items-buffer))
806 807 808
	(t
	 (save-buffer))))

809
(defvar todo-descending-counts)
810

811 812
(defun todo-quit ()
  "Exit the current Todo-related buffer.
813 814 815 816
Depending on the specific mode, this either kills the buffer or
buries it and restores state as needed."
  (interactive)
  (let ((buf (current-buffer)))
817 818 819 820 821
    (cond ((eq major-mode 'todo-categories-mode)
	   ;; Postpone killing buffer till after calling todo-show, to
	   ;; prevent killing todo-mode buffer.
	   (setq todo-descending-counts nil)
	   ;; Ensure todo-show calls todo-show-categories-table only on
822
	   ;; first invocation per file.
823 824 825
	   (when (eq todo-show-first 'table)
	     (add-to-list 'todo-visited todo-current-todo-file))
	   (todo-show)
826
	   (kill-buffer buf))
827
	  ((eq major-mode 'todo-filtered-items-mode)
828
	   (kill-buffer)
829 830
	   (unless (eq major-mode 'todo-mode) (todo-show)))
	  ((eq major-mode 'todo-archive-mode)
831 832
	   ;; Have to write a newly created archive to file to avoid
	   ;; subsequent errors.
833
	   (todo-save)
834 835 836 837 838 839 840 841 842
	   (let ((todo-file (concat todo-directory
				    (todo-short-file-name todo-current-todo-file)
				    ".todo")))
	     (if (todo-check-file todo-file)
		 (todo-show)
	       (message "There is no todo file for this archive")))
	   ;; When todo-check-file runs in todo-show, it kills the
	   ;; buffer if the archive file was deleted externally.
	   (when (buffer-live-p buf) (bury-buffer buf)))
843 844
	  ((eq major-mode 'todo-mode)
	   (todo-save)
845
	   ;; If we just quit archive mode, just burying the buffer
846
	   ;; in todo-mode would return to archive.
847 848 849
	   (set-window-buffer (selected-window)
			      (set-buffer (other-buffer)))
	   (bury-buffer buf)))))
850

851
;; -----------------------------------------------------------------------------
852
;;; Navigation between and within categories
853
;; -----------------------------------------------------------------------------
854

855
(defcustom todo-skip-archived-categories nil
856
  "Non-nil to handle categories with only archived items specially.
857

858 859
Sequential category navigation using \\[todo-forward-category]
or \\[todo-backward-category] skips categories that contain only
860
archived items.  Other commands still recognize these categories.
861 862
In Todo Categories mode (\\[todo-show-categories-table]) these
categories shown in `todo-archived-only' face and pressing the
863 864 865
category button visits the category in the archive instead of the
todo file."
  :type 'boolean
866
  :group 'todo-display)
867

868
(defun todo-forward-category (&optional back)
869
  "Visit the numerically next category in this todo file.
870 871 872 873 874
If the current category is the highest numbered, visit the first
category.  With non-nil argument BACK, visit the numerically
previous category (the highest numbered one, if the current
category is the first)."
  (interactive)
875 876 877 878 879 880 881 882 883 884
  (setq todo-category-number
        (1+ (mod (- todo-category-number (if back 2 0))
		 (length todo-categories))))
  (when todo-skip-archived-categories
    (while (and (zerop (todo-get-count 'todo))
		(zerop (todo-get-count 'done))
		(not (zerop (todo-get-count 'archived))))
      (setq todo-category-number
	    (apply (if back '1- '1+) (list todo-category-number)))))
  (todo-category-select)
885
  (goto-char (point-min)))
886

887
(defun todo-backward-category ()
888
  "Visit the numerically previous category in this todo file.
889 890 891
If the current category is the highest numbered, visit the first
category."
  (interactive)
892
  (todo-forward-category t))
893

894
(defvar todo-categories-buffer)
895

896
(defun todo-jump-to-category (&optional file where)
897
  "Prompt for a category in a todo file and jump to it.
898

899
With non-nil FILE (interactively a prefix argument), prompt for a
900
specific todo file and choose (with TAB completion) a category
901
in it to jump to; otherwise, choose and jump to any category in
902
either the current todo file or a file in
903
`todo-category-completions-files'.
904

905 906
Also accept a non-existing category name and ask whether to add a
new category by that name; on confirmation, add it and jump to
907
that category, and if option `todo-add-item-if-new-category' is