]> code.delx.au - gnu-emacs/blobdiff - lisp/calendar/todo-mode.el
Merge from origin/emacs-25
[gnu-emacs] / lisp / calendar / todo-mode.el
index 1b15317ffc65fd993c50bcb6b835dcea5298e096..9574c03043e24894cc24e7dd148711a4123dd363 100644 (file)
@@ -1,6 +1,6 @@
 ;;; todo-mode.el --- facilities for making and maintaining todo lists
 
-;; Copyright (C) 1997, 1999, 2001-2014 Free Software Foundation, Inc.
+;; Copyright (C) 1997, 1999, 2001-2016 Free Software Foundation, Inc.
 
 ;; Author: Oliver Seidel <privat@os10000.net>
 ;;     Stephen Berman <stephen.berman@gmx.net>
 
 ;;; Commentary:
 
-;; 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.
+;; This package provides facilities for making and maintaining
+;; prioritized lists of things to do.  These todo lists are identified
+;; with named categories, so you can group together thematically
+;; related todo items.  Each category is stored in a file, providing a
+;; further level of organization.  You can create as many todo files,
+;; and in each as many categories, as you want.
 
 ;; With Todo mode you can navigate among the items of a category, and
 ;; 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.
-;; 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.
-
-;; To get started, load this package and type `M-x todo-show'.  This
-;; will prompt you for the name of the first todo file, its first
-;; category and the category's first item, create these and display
-;; them in Todo mode.  Now you can insert further items into the list
-;; (i.e., the category) and assign them priorities by typing `i i'.
-
-;; You will probably find it convenient to give `todo-show' a global
-;; key binding in your init file, since it is one of the entry points
-;; to Todo mode; a good choice is `C-c t', since `todo-show' is
-;; bound to `t' in Todo mode.
-
-;; 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
-;; 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.
-
-;; This package is a new version of Oliver Seidel's todo-mode.el.
-;; While it retains the same basic organization and handling of todo
-;; lists and the basic UI, it significantly extends these and adds
-;; 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'.
+;; can add and edit todo items, reprioritize them, move them to
+;; another category, or delete them.  You can also mark items as done
+;; and store them within their category or in separate archive files.
+;; You can include todo items in the Emacs Fancy Diary display and
+;; treat them as appointments.  You can add new todo files, and rename
+;; or delete them.  You can add new categories to a file, rename or
+;; delete them, move a category 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
+;; sortable summary tables of the categories in a file and the types
+;; of items they contain.  And you can filter items by various
+;; criteria from multiple categories in one or more todo files to
+;; create prioritizable cross-category overviews of your todo items.
+
+;; To get started, type `M-x todo-show'.  For full details of the user
+;; interface, commands and options, consult the Todo mode user manual,
+;; which is included in the Info documentation.
 
 ;;; Code:
 
 (require 'diary-lib)
-;; For cl-remove-duplicates (in todo-insertion-commands-args) and
-;; cl-oddp.
-(require 'cl-lib)
+(require 'cl-lib)                      ; For cl-oddp and cl-assert.
 
 ;; -----------------------------------------------------------------------------
 ;;; Setting up todo files, categories, and items
@@ -100,7 +74,7 @@ truenames (those with the extension \".toda\")."
   (let ((files (if (file-exists-p todo-directory)
                   (mapcar 'file-truename
                    (directory-files todo-directory t
-                                    (if archives "\.toda$" "\.todo$") t)))))
+                                    (if archives "\\.toda$" "\\.todo$") t)))))
     (sort files (lambda (s1 s2) (let ((cis1 (upcase s1))
                                      (cis2 (upcase s2)))
                                  (string< cis1 cis2))))))
@@ -263,7 +237,8 @@ The final element is \"*\", indicating an unspecified month.")
                   (when (string= (widget-value widget) todo-item-mark)
                     (widget-put
                      widget :error
-                     "Invalid value: must be distinct from `todo-item-mark'")
+                     (format-message
+                      "Invalid value: must be distinct from `todo-item-mark'"))
                     widget)))
   :initialize 'custom-initialize-default
   :set 'todo-reset-prefix
@@ -566,13 +541,13 @@ less than or equal the category's top priority setting."
 ;;; Entering and exiting
 ;; -----------------------------------------------------------------------------
 
-(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)
+;; (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)
 
 (defun todo-short-file-name (file)
   "Return the short form of todo file FILE's name.
@@ -698,7 +673,7 @@ corresponding todo file, displaying the corresponding category."
                                                      todo-filtered-items-mode))))
                          (if (funcall todo-files-function)
                              (todo-read-file-name "Choose a todo file to visit: "
-                                                   nil t)
+                                                  nil t)
                            (user-error "There are no todo files")))
                         ((and (eq major-mode 'todo-archive-mode)
                               ;; Called noninteractively via todo-quit
@@ -740,9 +715,12 @@ corresponding todo file, displaying the corresponding category."
                                             "Choose a regexp items file: "
                                             rxf) 'regexp))))))
                     (if (file-exists-p fi-file)
-                        (set-window-buffer
-                         (selected-window)
-                         (set-buffer (find-file-noselect fi-file 'nowarn)))
+                        (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)))
                       (message "There is no %s file for %s"
                                (cond ((eq todo-show-first 'top)
                                       "top priorities")
@@ -755,14 +733,25 @@ corresponding todo file, displaying the corresponding category."
        (when (or (member file todo-visited)
                  (eq todo-show-first 'first))
          (unless (todo-check-file file) (throw 'end nil))
-         (set-window-buffer (selected-window)
+          ;; If todo-show is called from the minibuffer, don't visit
+          ;; the todo file there.
+         (set-window-buffer (if (minibufferp) (minibuffer-selected-window)
+                              (selected-window))
                             (set-buffer (find-file-noselect file 'nowarn)))
+         (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)))
          ;; 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))
+            ;; Don't confuse an erased buffer with a fresh buffer for
+            ;; adding a new todo file -- it might have been erased by
+            ;; mistake or due to a bug (e.g. Bug#20832).
+            (when (buffer-modified-p)
+              (error "Buffer is empty but modified, please report a bug"))
            (let (cat-added)
              (unwind-protect
                  (setq todo-category-number
@@ -1123,7 +1112,7 @@ these files, also rename them accordingly."
         (snname (todo-short-file-name nname))
         (files (directory-files todo-directory t
                                 (concat ".*" (regexp-quote soname)
-                                        ".*\.tod[aorty]$") t)))
+                                        ".*\\.tod[aorty]$") t)))
     (dolist (f files)
       (let* ((sfname (todo-short-file-name f))
             (fext (file-name-extension f t))
@@ -1354,12 +1343,13 @@ todo or done items."
                            "deleting it will also delete the file.\n"
                            "Do you want to proceed? ")))
                  ((> archived 0)
-                  (todo-y-or-n-p (concat "This category has archived items; "
+                  (todo-y-or-n-p (format-message
+                                  (concat "This category has archived items; "
                                     "the archived category will remain\n"
                                     "after deleting the todo category.  "
                                     "Do you still want to delete it\n"
                                     "(see `todo-skip-archived-categories' "
-                                    "for another option)? ")))
+                                    "for another option)? "))))
                  (t
                   (todo-y-or-n-p (concat "Permanently remove category \"" cat
                                     "\"" (and arg " and all its entries")
@@ -1424,7 +1414,12 @@ the archive of the file moved to, creating it if it does not exist."
        (setq todo-files (funcall todo-files-function))
        (todo-reevaluate-filelist-defcustoms))
       (dolist (buf buffers)
+        ;; Make sure archive file is in Todo Archive mode so that
+        ;; todo-categories has correct value.
        (with-current-buffer (find-file-noselect buf)
+          (when (equal (file-name-extension (buffer-file-name)) "toda")
+            (unless (derived-mode-p 'todo-archive-mode)
+              (todo-archive-mode)))
          (widen)
          (goto-char (point-max))
          (let* ((beg (re-search-backward
@@ -1449,6 +1444,10 @@ the archive of the file moved to, creating it if it does not exist."
                 (if (member buf (funcall todo-files-function t))
                     (concat (file-name-sans-extension nfile) ".toda")
                   nfile))
+             (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)))
              (let* ((nfile-short (todo-short-file-name nfile))
                     (prompt (concat
                              (format "Todo file \"%s\" already has "
@@ -1472,10 +1471,18 @@ the archive of the file moved to, creating it if it does not exist."
                  (re-search-backward
                   (concat "^" (regexp-quote todo-category-beg)
                           "\\(" (regexp-quote cat) "\\)$") nil t)
-                 (replace-match new nil nil nil 1)))
-             (setq todo-categories
-                   (append todo-categories (list (cons (or new cat) counts))))
-             (todo-update-categories-sexp)
+                 (replace-match new nil nil nil 1))
+                (setq todo-categories
+                      (append todo-categories (list (cons (or new cat) counts))))
+                (goto-char (point-min))
+                (if (looking-at "((\"")
+                    ;; Delete existing sexp.
+                    (delete-region (line-beginning-position) (line-end-position))
+                  ;; Otherwise, file is new, so make space for categories sexp.
+                  (insert "\n")
+                  (goto-char (point-min)))
+                ;; Insert (new or updated) sexp.
+                (prin1 todo-categories (current-buffer)))
              ;; If archive was just created, save it to avoid "File
              ;; <xyz> no longer exists!" message on invoking
              ;; `todo-view-archived-items'.
@@ -1506,9 +1513,7 @@ the archive of the file moved to, creating it if it does not exist."
                (setq todo-category-number 1))
              (todo-category-select)))))
       (set-window-buffer (selected-window)
-                        (set-buffer (find-file-noselect nfile)))
-      (todo-category-number (or new cat))
-      (todo-category-select))))
+                        (set-buffer (find-file-noselect nfile))))))
 
 (defun todo-merge-category (&optional file)
   "Merge current category into another existing category.
@@ -1564,6 +1569,7 @@ archive file and the source category is deleted."
             (done-count (todo-get-count 'done cat)))
        ;; Merge into goal todo category.
        (with-current-buffer (get-buffer (find-file-noselect gfile))
+         (unless (derived-mode-p 'todo-mode) (todo-mode))
          (widen)
          (goto-char (point-min))
          (let ((buffer-read-only nil))
@@ -1701,7 +1707,8 @@ only when no items are marked."
                   (when (string= (widget-value widget) todo-prefix)
                     (widget-put
                      widget :error
-                     "Invalid value: must be distinct from `todo-prefix'")
+                     (format-message
+                      "Invalid value: must be distinct from `todo-prefix'"))
                     widget)))
   :set (lambda (symbol value)
         (custom-set-default symbol (propertize value 'face 'todo-mark)))
@@ -1725,31 +1732,40 @@ means prompt user and omit comment only on confirmation."
 
 (defun todo-toggle-mark-item (&optional n)
   "Mark item with `todo-item-mark' if unmarked, otherwise unmark it.
-With a positive numerical prefix argument N, change the
-marking of the next N items."
+With positive numerical prefix argument N, change the marking of
+the next N items in the current category.  If both the todo and
+done items sections are visible, the sequence of N items can
+consist of the the last todo items and the first done items."
   (interactive "p")
   (when (todo-item-string)
     (unless (> n 1) (setq n 1))
-    (dotimes (i n)
-      (let* ((cat (todo-current-category))
-            (marks (assoc cat todo-categories-with-marks))
-            (ov (progn
-                  (unless (looking-at todo-item-start)
-                    (todo-item-start))
-                  (todo-get-overlay 'prefix)))
-            (pref (overlay-get ov 'before-string)))
-       (if (todo-marked-item-p)
-           (progn
-             (overlay-put ov 'before-string (substring pref 1))
-             (if (= (cdr marks) 1)     ; Deleted last mark in this category.
-                 (setq todo-categories-with-marks
-                       (assq-delete-all cat todo-categories-with-marks))
-               (setcdr marks (1- (cdr marks)))))
-         (overlay-put ov 'before-string (concat todo-item-mark pref))
-         (if marks
-             (setcdr marks (1+ (cdr marks)))
-           (push (cons cat 1) todo-categories-with-marks))))
-      (todo-forward-item))))
+    (catch 'end
+      (dotimes (i n)
+       (let* ((cat (todo-current-category))
+              (marks (assoc cat todo-categories-with-marks))
+              (ov (progn
+                    (unless (looking-at todo-item-start)
+                      (todo-item-start))
+                    (todo-get-overlay 'prefix)))
+              (pref (overlay-get ov 'before-string)))
+         (if (todo-marked-item-p)
+             (progn
+               (overlay-put ov 'before-string (substring pref 1))
+               (if (= (cdr marks) 1)   ; Deleted last mark in this category.
+                   (setq todo-categories-with-marks
+                         (assq-delete-all cat todo-categories-with-marks))
+                 (setcdr marks (1- (cdr marks)))))
+           (overlay-put ov 'before-string (concat todo-item-mark pref))
+           (if marks
+               (setcdr marks (1+ (cdr marks)))
+             (push (cons cat 1) todo-categories-with-marks))))
+       (todo-forward-item)
+       ;; Don't try to mark the empty lines at the end of the todo
+       ;; and done items sections.
+       (when (looking-at "^$")
+         (if (eobp)
+             (throw 'end nil)
+           (todo-forward-item)))))))
 
 (defun todo-mark-category ()
   "Mark all visible items in this category with `todo-item-mark'."
@@ -1766,7 +1782,12 @@ marking of the next N items."
            (if marks
                (setcdr marks (1+ (cdr marks)))
              (push (cons cat 1) todo-categories-with-marks))))
-       (todo-forward-item)))))
+       (todo-forward-item)
+       ;; Don't try to mark the empty line between the todo and done
+       ;; items sections.
+       (when (looking-at "^$")
+         (unless (eobp)
+           (todo-forward-item)))))))
 
 (defun todo-unmark-category ()
   "Remove `todo-item-mark' from all visible items in this category."
@@ -1951,13 +1972,12 @@ their associated keys and their effects."
            ;; If user cancels before setting priority, restore
            ;; display.
            (unless item-added
-             (if ocat
-                 (progn
-                   (unless (equal cat ocat)
-                     (todo-category-number ocat)
-                     (todo-category-select))
-                   (and done-only (todo-toggle-view-done-only)))
-               (set-window-buffer (selected-window) (set-buffer obuf)))
+             (set-window-buffer (selected-window) (set-buffer obuf))
+             (when ocat
+               (unless (equal cat ocat)
+                 (todo-category-number ocat)
+                 (todo-category-select))
+               (and done-only (todo-toggle-view-done-only)))
              (goto-char opoint))
            ;; If the todo items section is not visible when the
            ;; insertion command is called (either because only done
@@ -2070,85 +2090,101 @@ the item at point."
 (defun todo-edit-item (&optional arg)
   "Choose an editing operation for the current item and carry it out."
   (interactive "P")
-  (cond ((todo-done-item-p)
-        (todo-edit-item--next-key todo-edit-done-item--param-key-alist))
-       ((todo-item-string)
-        (todo-edit-item--next-key todo-edit-item--param-key-alist arg))))
+  (let ((marked (assoc (todo-current-category) todo-categories-with-marks)))
+    (cond ((and (todo-done-item-p) (not marked))
+          (todo-edit-item--next-key todo-edit-done-item--param-key-alist))
+         ((or marked (todo-item-string))
+          (todo-edit-item--next-key todo-edit-item--param-key-alist arg)))))
 
 (defun todo-edit-item--text (&optional arg)
   "Function providing the text editing facilities of `todo-edit-item'."
-  (let* ((opoint (point))
-        (start (todo-item-start))
-        (end (save-excursion (todo-item-end)))
-        (item-beg (progn
-                    (re-search-forward
-                     (concat todo-date-string-start todo-date-pattern
-                             "\\( " diary-time-regexp "\\)?"
-                             (regexp-quote todo-nondiary-end) "?")
-                     (line-end-position) t)
-                    (1+ (- (point) start))))
-        (include-header (eq arg 'include-header))
-        (comment-edit (eq arg 'comment-edit))
-        (comment-delete (eq arg 'comment-delete))
-        (header-string (substring (todo-item-string) 0 item-beg))
-        (item (if (or include-header comment-edit comment-delete)
-                  (todo-item-string)
-                (substring (todo-item-string) item-beg)))
-        (multiline (> (length (split-string item "\n")) 1))
-        (comment (save-excursion
-                   (todo-item-start)
-                   (re-search-forward
-                    (concat " \\[" (regexp-quote todo-comment-string)
-                            ": \\([^]]+\\)\\]") end t)))
-        (prompt (if comment "Edit comment: " "Enter a comment: "))
-        (buffer-read-only nil))
-    (cond
-     ((or comment-edit comment-delete)
-      (save-excursion
-       (todo-item-start)
-       (if (re-search-forward (concat " \\[" (regexp-quote todo-comment-string)
-                                      ": \\([^]]+\\)\\]") end t)
-           (if comment-delete
-               (when (todo-y-or-n-p "Delete comment? ")
-                 (delete-region (match-beginning 0) (match-end 0)))
-             (replace-match (read-string prompt (cons (match-string 1) 1))
-                            nil nil nil 1))
-         (if comment-delete
-             (user-error "There is no comment to delete")
-           (insert " [" todo-comment-string ": "
-                   (prog1 (read-string prompt)
-                     ;; If user moved point during editing,
-                     ;; make sure it moves back.
-                     (goto-char opoint)
-                     (todo-item-end))
-                     "]")))))
-     ((or multiline (eq arg 'multiline))
-      (let ((buf todo-edit-buffer))
-       (set-window-buffer (selected-window)
-                          (set-buffer (make-indirect-buffer (buffer-name) buf)))
-       (narrow-to-region (todo-item-start) (todo-item-end))
-       (todo-edit-mode)
-       (message "%s" (substitute-command-keys
-                      (concat "Type \\[todo-edit-quit] "
-                              "to return to Todo mode.\n")))))
-     (t
-      (let ((new (concat (if include-header "" header-string)
-                         (read-string "Edit: " (if include-header
-                                                   (cons item item-beg)
-                                                 (cons item 0))))))
-        (when include-header
-          (while (not (string-match (concat todo-date-string-start
-                                            todo-date-pattern) new))
-            (setq new (read-from-minibuffer
-                       "Item must start with a date: " new))))
-        ;; Ensure lines following hard newlines are indented.
-        (setq new (replace-regexp-in-string "\\(\n\\)[^[:blank:]]"
-                                            "\n\t" new nil nil 1))
-        ;; If user moved point during editing, make sure it moves back.
-        (goto-char opoint)
-        (todo-remove-item)
-        (todo-insert-with-overlays new)
-        (move-to-column item-beg))))))
+  (let ((full-item (todo-item-string)))
+    ;; If there are marked items and user invokes a text-editing
+    ;; commands with point not on an item, todo-item-start is nil and
+    ;; 1+ signals an error, so just make this a noop.
+    (when full-item
+      (let* ((opoint (point))
+            (start (todo-item-start))
+            (end (save-excursion (todo-item-end)))
+            (item-beg (progn
+                        (re-search-forward
+                         (concat todo-date-string-start todo-date-pattern
+                                 "\\( " diary-time-regexp "\\)?"
+                                 (regexp-quote todo-nondiary-end) "?")
+                         (line-end-position) t)
+                        (1+ (- (point) start))))
+            (include-header (eq arg 'include-header))
+            (comment-edit (eq arg 'comment-edit))
+            (comment-delete (eq arg 'comment-delete))
+            (header-string (substring full-item 0 item-beg))
+            (item (if (or include-header comment-edit comment-delete)
+                      full-item
+                    (substring full-item item-beg)))
+            (multiline (or (eq arg 'multiline)
+                           (> (length (split-string item "\n")) 1)))
+            (comment (save-excursion
+                       (todo-item-start)
+                       (re-search-forward
+                        (concat " \\[" (regexp-quote todo-comment-string)
+                                ": \\([^]]+\\)\\]") end t)))
+            (prompt (if comment "Edit comment: " "Enter a comment: "))
+            (buffer-read-only nil))
+       ;; When there are marked items, user can invoke todo-edit-item
+       ;; even if point is not on an item, but text editing only
+       ;; applies to the item at point.
+       (when (or (and (todo-done-item-p)
+                      (or comment-edit comment-delete))
+                 (and (not (todo-done-item-p))
+                      (or (not arg) include-header multiline)))
+         (cond
+          ((or comment-edit comment-delete)
+           (save-excursion
+             (todo-item-start)
+             (if (re-search-forward (concat " \\["
+                                            (regexp-quote todo-comment-string)
+                                            ": \\([^]]+\\)\\]") end t)
+                 (if comment-delete
+                     (when (todo-y-or-n-p "Delete comment? ")
+                       (delete-region (match-beginning 0) (match-end 0)))
+                   (replace-match (read-string prompt (cons (match-string 1) 1))
+                                  nil nil nil 1))
+               (if comment-delete
+                   (user-error "There is no comment to delete")
+                 (insert " [" todo-comment-string ": "
+                         (prog1 (read-string prompt)
+                           ;; If user moved point during editing,
+                           ;; make sure it moves back.
+                           (goto-char opoint)
+                           (todo-item-end))
+                         "]")))))
+          (multiline
+           (let ((buf todo-edit-buffer))
+             (set-window-buffer (selected-window)
+                                (set-buffer (make-indirect-buffer
+                                             (buffer-name) buf)))
+             (narrow-to-region (todo-item-start) (todo-item-end))
+             (todo-edit-mode)
+             (message "%s" (substitute-command-keys
+                            (concat "Type \\[todo-edit-quit] "
+                                    "to return to Todo mode.\n")))))
+          (t
+           (let ((new (concat (if include-header "" header-string)
+                              (read-string "Edit: " (if include-header
+                                                        (cons item item-beg)
+                                                      (cons item 0))))))
+             (when include-header
+               (while (not (string-match (concat todo-date-string-start
+                                                 todo-date-pattern) new))
+                 (setq new (read-from-minibuffer
+                            "Item must start with a date: " new))))
+             ;; Ensure lines following hard newlines are indented.
+             (setq new (replace-regexp-in-string "\\(\n\\)[^[:blank:]]"
+                                                 "\n\t" new nil nil 1))
+             ;; If user moved point during editing, make sure it moves back.
+             (goto-char opoint)
+             (todo-remove-item)
+             (todo-insert-with-overlays new)
+             (move-to-column item-beg)))))))))
 
 (defun todo-edit-quit ()
   "Return from Todo Edit mode to Todo mode.
@@ -2203,16 +2239,16 @@ made in the number or names of categories."
 
 (defun todo-edit-item--header (what &optional inc)
   "Function providing header editing facilities of `todo-edit-item'."
-  (let* ((cat (todo-current-category))
-        (marked (assoc cat todo-categories-with-marks))
-        (first t)
-        (todo-date-from-calendar t)
-        ;; INC must be an integer, but users could pass it via
-        ;; `todo-edit-item' as e.g. `-' or `C-u'.
-        (inc (prefix-numeric-value inc))
-        (buffer-read-only nil)
-        ndate ntime year monthname month day
-        dayname)       ; Needed by calendar-date-display-form.
+  (let ((marked (assoc (todo-current-category) todo-categories-with-marks))
+       (first t)
+       (todo-date-from-calendar t)
+       ;; INC must be an integer, but users could pass it via
+       ;; `todo-edit-item' as e.g. `-' or `C-u'.
+       (inc (prefix-numeric-value inc))
+       (buffer-read-only nil)
+       ndate ntime year monthname month day
+       dayname)        ; Needed by calendar-date-display-form.
+    (when marked (todo--user-error-if-marked-done-item))
     (save-excursion
       (or (and marked (goto-char (point-min))) (todo-item-start))
       (catch 'end
@@ -2237,9 +2273,8 @@ made in the number or names of categories."
                 (mlist (append tmn-array nil))
                 (tma-array todo-month-abbrev-array)
                 (mablist (append tma-array nil))
-                (yy (and oyear (unless (string= oyear "*")
-                                 (string-to-number oyear))))
-                (mm (or (and omonth (unless (string= omonth "*")
+                (yy (and oyear (string-to-number oyear))) ; 0 if year is "*".
+                (mm (or (and omonth (if (string= omonth "*") 13
                                       (string-to-number omonth)))
                         (1+ (- (length mlist)
                                (length (or (member omonthname mlist)
@@ -2305,12 +2340,11 @@ made in the number or names of categories."
                             (if omonth
                                 (number-to-string mm)
                               (aref tma-array (1- mm))))))
-               (let ((yy (string-to-number year)) ; 0 if year is "*".
-                     ;; When mm is 13 (corresponding to "*" as value
-                     ;; of month), this raises an args-out-of-range
-                     ;; error in calendar-last-day-of-month, so use 1
-                     ;; (corresponding to January) to get 31 days.
-                     (mm (if (= mm 13) 1 mm)))
+                ;; Since the number corresponding to the arbitrary
+                ;; month name "*" is out of the range of
+                ;; calendar-last-day-of-month, set it to 1
+                ;; (corresponding to January) to allow 31 days.
+                (let ((mm (if (= mm 13) 1 mm)))
                  (if (> (string-to-number day)
                         (calendar-last-day-of-month mm yy))
                      (user-error "%s %s does not have %s days"
@@ -2322,7 +2356,7 @@ made in the number or names of categories."
                      monthname omonthname
                      day (cond
                           ((not current-prefix-arg)
-                           (todo-read-date 'day mm oyear))
+                           (todo-read-date 'day mm yy))
                           ((string= oday "*")
                            (user-error "Cannot increment *"))
                           ((or (string= omonth "*") (string= omonthname "*"))
@@ -2374,47 +2408,45 @@ made in the number or names of categories."
 (defun todo-edit-item--diary-inclusion (&optional nonmarking)
   "Function providing diary marking facilities of `todo-edit-item'."
   (let ((buffer-read-only)
-       (marked (assoc (todo-current-category)
-                      todo-categories-with-marks)))
+       (marked (assoc (todo-current-category) todo-categories-with-marks)))
+    (when marked (todo--user-error-if-marked-done-item))
     (catch 'stop
       (save-excursion
        (when marked (goto-char (point-min)))
        (while (not (eobp))
-         (if (todo-done-item-p)
-             (throw 'stop (message "Done items cannot be edited"))
-           (unless (and marked (not (todo-marked-item-p)))
-             (let* ((beg (todo-item-start))
-                    (lim (save-excursion (todo-item-end)))
-                    (end (save-excursion
-                           (or (todo-time-string-matcher lim)
-                               (todo-date-string-matcher lim)))))
-               (if nonmarking
-                   (if (looking-at (regexp-quote diary-nonmarking-symbol))
-                       (replace-match "")
-                     (when (looking-at (regexp-quote todo-nondiary-start))
-                       (save-excursion
-                         (replace-match "")
-                         (search-forward todo-nondiary-end (1+ end) t)
-                         (replace-match "")
-                         (todo-update-count 'diary 1)))
-                     (insert diary-nonmarking-symbol))
-                 (if (looking-at (regexp-quote todo-nondiary-start))
-                     (progn
+         (unless (and marked (not (todo-marked-item-p)))
+           (let* ((beg (todo-item-start))
+                  (lim (save-excursion (todo-item-end)))
+                  (end (save-excursion
+                         (or (todo-time-string-matcher lim)
+                             (todo-date-string-matcher lim)))))
+             (if nonmarking
+                 (if (looking-at (regexp-quote diary-nonmarking-symbol))
+                     (replace-match "")
+                   (when (looking-at (regexp-quote todo-nondiary-start))
+                     (save-excursion
                        (replace-match "")
                        (search-forward todo-nondiary-end (1+ end) t)
                        (replace-match "")
-                       (todo-update-count 'diary 1))
-                   (when end
-                     (when (looking-at (regexp-quote diary-nonmarking-symbol))
-                       (replace-match "")
-                       (setq end (1- end))) ; Since we deleted nonmarking symbol.
-                     (insert todo-nondiary-start)
-                     (goto-char (1+ end))
-                     (insert todo-nondiary-end)
-                     (todo-update-count 'diary -1))))))
-           (unless marked (throw 'stop nil))
-           (todo-forward-item)))))
-    (todo-update-categories-sexp)))
+                       (todo-update-count 'diary 1)))
+                   (insert diary-nonmarking-symbol))
+               (if (looking-at (regexp-quote todo-nondiary-start))
+                   (progn
+                     (replace-match "")
+                     (search-forward todo-nondiary-end (1+ end) t)
+                     (replace-match "")
+                     (todo-update-count 'diary 1))
+                 (when end
+                   (when (looking-at (regexp-quote diary-nonmarking-symbol))
+                     (replace-match "")
+                     (setq end (1- end))) ; Since we deleted nonmarking symbol.
+                   (insert todo-nondiary-start)
+                   (goto-char (1+ end))
+                   (insert todo-nondiary-end)
+                   (todo-update-count 'diary -1))))))
+         (unless marked (throw 'stop nil))
+         (todo-forward-item)))))
+  (todo-update-categories-sexp))
 
 (defun todo-edit-category-diary-inclusion (arg)
   "Make all items in this category diary items.
@@ -2542,9 +2574,9 @@ meaning to raise or lower the item's priority by one."
                (goto-char (point-min))
                (setq done (re-search-forward todo-done-string-start nil t))))
            (let ((todo-show-with-done done))
-             (todo-category-select)
-             ;; Keep top of category in view while setting priority.
-             (goto-char (point-min)))))
+             ;; Keep current item or top of moved to category in view
+             ;; while setting priority.
+             (save-excursion (todo-category-select)))))
        ;; Prompt for priority only when the category has at least one
        ;; todo item.
        (when (> maxnum 1)
@@ -2594,7 +2626,8 @@ meaning to raise or lower the item's priority by one."
            ;; separator.
            (when (looking-back (concat "^"
                                        (regexp-quote todo-category-done)
-                                       "\n"))
+                                       "\n")
+                                (line-beginning-position 0))
              (todo-backward-item))))
        (todo-insert-with-overlays item)
        ;; If item was marked, restore the mark.
@@ -2785,21 +2818,7 @@ visible."
   (interactive "P")
   (let* ((cat (todo-current-category))
         (marked (assoc cat todo-categories-with-marks)))
-    (when marked
-      (save-excursion
-       (save-restriction
-         (goto-char (point-max))
-         (todo-backward-item)
-         (unless (todo-done-item-p)
-           (widen)
-           (unless (re-search-forward
-                    (concat "^" (regexp-quote todo-category-beg)) nil t)
-             (goto-char (point-max)))
-           (forward-line -1))
-         (while (todo-done-item-p)
-           (when (todo-marked-item-p)
-             (user-error "This command does not apply to done items"))
-           (todo-backward-item)))))
+    (when marked (todo--user-error-if-marked-done-item))
     (unless (and (not marked)
                 (or (todo-done-item-p)
                     ;; Point is between todo and done items.
@@ -2818,7 +2837,8 @@ visible."
                          (goto-char (point-min))
                          (re-search-forward todo-done-string-start nil t)))
             (buffer-read-only nil)
-            item done-item opoint)
+            item done-item
+            (opoint (point)))
        ;; Don't add empty comment to done item.
        (setq comment (unless (zerop (length comment))
                        (concat " [" todo-comment-string ": " comment "]")))
@@ -2856,7 +2876,9 @@ visible."
        (todo-update-categories-sexp)
        (let ((todo-show-with-done show-done))
          (todo-category-select)
-         ;; When done items are shown, put cursor on first just done item.
+         ;; When done items are visible, put point at the top of the
+         ;; done items section.  When done items are hidden, restore
+         ;; point to its location prior to invoking this command.
          (when opoint (goto-char opoint)))))))
 
 (defun todo-item-undone ()
@@ -2887,7 +2909,9 @@ comments without asking."
          (while (not (eobp))
            (when (or (not marked) (and marked (todo-marked-item-p)))
              (if (not (todo-done-item-p))
-                 (user-error "Only done items can be undone")
+                 (progn
+                   (goto-char opoint)
+                   (user-error "Only done items can be undone"))
                (todo-item-start)
                (unless marked
                  (setq ov (make-overlay (save-excursion (todo-item-start))
@@ -2995,6 +3019,7 @@ displayed."
       (when place
        (set-window-buffer (selected-window)
                           (set-buffer (find-file-noselect archive)))
+       (unless (derived-mode-p 'todo-archive-mode) (todo-archive-mode))
        (if (member place '(other-archive other-cat))
            (setq todo-category-number 1)
          (todo-category-number cat))
@@ -3070,6 +3095,7 @@ this category does not exist in the archive, it is created."
          (if (not (or marked all item))
              (throw 'end (message "Only done items can be archived"))
            (with-current-buffer archive
+             (unless (derived-mode-p 'todo-archive-mode) (todo-archive-mode))
              (let (buffer-read-only)
                (widen)
                (goto-char (point-min))
@@ -3091,12 +3117,12 @@ this category does not exist in the archive, it is created."
                (todo-update-categories-sexp)
                ;; If archive is new, save to file now (with
                ;; write-region to avoid prompt for file to save to)
-               ;; to update todo-archives, and to let auto-mode-alist
-               ;; take effect below on visiting the archive.
+               ;; to update todo-archives, and set the mode for
+               ;; visiting the archive below.
                (unless (nth 7 (file-attributes afile))
                  (write-region nil nil afile t t)
                  (setq todo-archives (funcall todo-files-function t))
-                 (kill-buffer))))
+                 (todo-archive-mode))))
            (with-current-buffer tbuf
              (cond
               (all
@@ -3946,7 +3972,7 @@ regexp items."
 (defun todo-find-filtered-items-file ()
   "Choose a filtered items file and visit it."
   (interactive)
-  (let ((files (directory-files todo-directory t "\.tod[rty]$" t))
+  (let ((files (directory-files todo-directory t "\\.tod[rty]$" t))
        falist file)
     (dolist (f files)
       (let ((type (cond ((equal (file-name-extension f) "todr") "regexp")
@@ -3957,7 +3983,10 @@ regexp items."
     (setq file (completing-read "Choose a filtered items file: "
                                falist nil t nil nil (car falist)))
     (setq file (cdr (assoc-string file falist)))
-    (find-file file)))
+    (find-file file)
+    (unless (derived-mode-p 'todo-filtered-items-mode)
+      (todo-filtered-items-mode))
+    (todo-prefix-overlays)))
 
 (defun todo-go-to-source-item ()
   "Display the file and category of the filtered item at point."
@@ -4066,7 +4095,6 @@ multifile commands for further details."
                        (progn (todo-multiple-filter-files)
                               todo-multiple-filter-files))
                  (list todo-current-todo-file)))
-        (multi (> (length flist) 1))
         (fname (if (equal flist 'quit)
                    ;; Pressed `cancel' in t-m-f-f file selection dialog.
                    (keyboard-quit)
@@ -4075,6 +4103,7 @@ multifile commands for further details."
                          (cond (top ".todt")
                                (diary ".tody")
                                (regexp ".todr")))))
+        (multi (> (length flist) 1))
         (rxfiles (when regexp
                    (directory-files todo-directory t ".*\\.todr$" t)))
         (file-exists (or (file-exists-p fname) rxfiles))
@@ -4088,6 +4117,8 @@ multifile commands for further details."
                            (completing-read "Choose a regexp items file: "
                                             rxf) 'regexp))))
           (find-file fname)
+          (unless (derived-mode-p 'todo-filtered-items-mode)
+            (todo-filtered-items-mode))
           (todo-prefix-overlays)
           (todo-check-filtered-items-file))
          (t
@@ -4221,7 +4252,8 @@ the values of FILTER and FILE-LIST."
                           (if (and (eobp)
                                    (looking-back
                                     (concat (regexp-quote todo-done-string)
-                                            "\n")))
+                                            "\n")
+                                     (line-beginning-position 0)))
                               (delete-region (point) (progn
                                                        (forward-line -2)
                                                        (point))))))
@@ -4276,30 +4308,31 @@ set the user customizable option `todo-top-priorities-overrides'."
         (file todo-current-todo-file)
         (rules todo-top-priorities-overrides)
         (frule (assoc-string file rules))
-        (crule (assoc-string cat (nth 2 frule)))
         (crules (nth 2 frule))
-        (cur (or (if arg (cdr crule) (nth 1 frule))
-                 todo-top-priorities))
+        (crule (assoc-string cat crules))
+        (fcur (or (nth 1 frule)
+                  todo-top-priorities))
+        (ccur (or (and arg (cdr crule))
+                  fcur))
         (prompt (if arg (concat "Number of top priorities in this category"
                                 " (currently %d): ")
                   (concat "Default number of top priorities per category"
                                 " in this file (currently %d): ")))
-        (new -1)
-        nrule)
+        (new -1))
     (while (< new 0)
-      (let ((cur0 cur))
-       (setq new (read-number (format prompt cur0))
+      (let ((cur (if arg ccur fcur)))
+       (setq new (read-number (format prompt cur))
              prompt "Enter a non-negative number: "
-             cur0 nil)))
-    (setq nrule (if arg
-                   (append (delete crule crules) (list (cons cat new)))
-                 (append (list file new) (list crules))))
-    (setq rules (cons (if arg
-                         (list file cur nrule)
-                       nrule)
-                     (delete frule rules)))
-    (customize-save-variable 'todo-top-priorities-overrides rules)
-    (todo-prefix-overlays)))
+             cur nil)))
+    (let ((nrule (if arg
+                    (append (delete crule crules) (list (cons cat new)))
+                  (append (list file new) (list crules)))))
+      (setq rules (cons (if arg
+                           (list file fcur nrule)
+                         nrule)
+                       (delete frule rules)))
+      (customize-save-variable 'todo-top-priorities-overrides rules)
+      (todo-prefix-overlays))))
 
 (defun todo-find-item (str)
   "Search for filtered item STR in its saved todo file.
@@ -4341,6 +4374,9 @@ its priority has changed, and `same' otherwise."
                   todo-global-current-todo-file)))
     (find-file-noselect file)
     (with-current-buffer (find-buffer-visiting file)
+      (if archive
+         (unless (derived-mode-p 'todo-archive-mode) (todo-archive-mode))
+       (unless (derived-mode-p 'todo-mode) (todo-mode)))
       (save-restriction
        (widen)
        (goto-char (point-min))
@@ -4627,14 +4663,16 @@ name in `todo-directory'.  See also the documentation string of
                    (goto-char (match-beginning 0))
                  (goto-char (point-max)))
                (backward-char)
-               (when (looking-back "\\[\\([^][]+\\)\\]")
+               (when (looking-back "\\[\\([^][]+\\)\\]"
+                                    (line-beginning-position))
                  (setq cat (match-string 1))
                  (goto-char (match-beginning 0))
                  (replace-match ""))
                ;; If the item ends with a non-comment parenthesis not
                ;; followed by a period, we lose (but we inherit that
                ;; problem from the legacy code).
-               (when (looking-back "(\\(.*\\)) ")
+                ;; FIXME: fails on multiline comment
+               (when (looking-back "(\\(.*\\)) " (line-beginning-position))
                  (setq comment (match-string 1))
                  (replace-match "")
                  (insert "[" todo-comment-string ": " comment "]"))
@@ -4865,7 +4903,7 @@ With nil or omitted CATEGORY, default to the current category."
        (widen)
        (goto-char (point-min))
        (setq todo-categories
-             (if (looking-at "\(\(\"")
+             (if (looking-at "((\"")
                  (read (buffer-substring-no-properties
                         (line-beginning-position)
                         (line-end-position)))
@@ -4917,23 +4955,28 @@ the file."
                 ;; Make sure to include newly created archives, e.g. due to
                 ;; todo-move-category.
                 (when (member archive (funcall todo-files-function t))
-                  (let ((archive-count 0))
-                    (with-current-buffer (find-file-noselect archive)
-                      (widen)
-                      (goto-char (point-min))
-                      (when (re-search-forward
-                             (concat "^" (regexp-quote todo-category-beg)
-                                     cat "$")
-                             (point-max) t)
-                        (forward-line)
-                        (while (not (or (looking-at
-                                         (concat
-                                          (regexp-quote todo-category-beg)
-                                          "\\(.*\\)\n"))
-                                        (eobp)))
-                          (when (looking-at todo-done-string-start)
-                            (setq archive-count (1+ archive-count)))
-                          (forward-line))))
+                  (let ((archive-count 0)
+                        (visiting (find-buffer-visiting archive)))
+                    (with-current-buffer (or visiting
+                                             (find-file-noselect archive))
+                      (save-excursion
+                        (save-restriction
+                          (widen)
+                          (goto-char (point-min))
+                          (when (re-search-forward
+                                 (concat "^" (regexp-quote todo-category-beg)
+                                         cat "$")
+                                 (point-max) t)
+                            (forward-line)
+                            (while (not (or (looking-at
+                                             (concat
+                                              (regexp-quote todo-category-beg)
+                                              "\\(.*\\)\n"))
+                                            (eobp)))
+                              (when (looking-at todo-done-string-start)
+                                (setq archive-count (1+ archive-count)))
+                              (forward-line)))))
+                      (unless visiting (kill-buffer)))
                     (todo-update-count 'archived archive-count cat))))
                ((looking-at todo-done-string-start)
                 (todo-update-count 'done 1 cat))
@@ -5005,7 +5048,7 @@ but the categories sexp differs from the current value of
        ;; Warn user if categories sexp has changed.
        (unless (string= ssexp cats)
          (message (concat "The sexp at the beginning of the file differs "
-                          "from the value of `todo-categories.\n"
+                          "from the value of `todo-categories'.\n"
                           "If the sexp is wrong, you can fix it with "
                           "M-x todo-repair-categories-sexp,\n"
                           "but note this reverts any changes you have "
@@ -5157,6 +5200,11 @@ Overrides `diary-goto-entry'."
     (if (not (and (file-exists-p file)
                  (find-file-other-window file)))
        (message "Unable to locate this diary entry")
+      ;; If it's a Todo file, make sure it's in Todo mode.
+      (when (and (equal (file-name-directory (file-truename file))
+                       (file-truename todo-directory))
+                (not (derived-mode-p 'todo-mode)))
+       (todo-mode))
       (when (eq major-mode 'todo-mode) (widen))
       (goto-char (point-min))
       (when (re-search-forward (format "%s.*\\(%s\\)" date content) nil t)
@@ -5173,6 +5221,15 @@ Overrides `diary-goto-entry'."
 
 (add-function :override diary-goto-entry-function #'todo-diary-goto-entry)
 
+(defun todo-revert-buffer (&optional ignore-auto noconfirm)
+  "Call `revert-buffer', preserving buffer's current modes.
+Also preserve category display, if applicable."
+  (interactive (list (not current-prefix-arg)))
+  (let ((revert-buffer-function nil))
+    (revert-buffer ignore-auto noconfirm 'preserve-modes)
+    (when (memq major-mode '(todo-mode todo-archive-mode))
+      (todo-category-select))))
+
 (defun todo-desktop-save-buffer (_dir)
   `((catnum . ,(todo-category-number (todo-current-category)))))
 
@@ -5184,7 +5241,8 @@ Overrides `diary-goto-entry'."
   (with-current-buffer buffer
     (widen)
     (let ((todo-category-number (cdr (assq 'catnum misc))))
-      (todo-category-select))))
+      (todo-category-select)
+      (current-buffer))))
 
 (add-to-list 'desktop-buffer-mode-handlers
             '(todo-mode . todo-restore-desktop-buffer))
@@ -5203,6 +5261,25 @@ Overrides `diary-goto-entry'."
        (progn (goto-char (point-min))
               (looking-at todo-done-string-start)))))
 
+(defun todo--user-error-if-marked-done-item ()
+  "Signal user error on marked done items.
+Helper function for editing commands that apply only to (possibly
+marked) not done todo items."
+  (save-excursion
+    (save-restriction
+      (goto-char (point-max))
+      (todo-backward-item)
+      (unless (todo-done-item-p)
+       (widen)
+       (unless (re-search-forward
+                (concat "^" (regexp-quote todo-category-beg)) nil t)
+         (goto-char (point-max)))
+       (forward-line -1))
+      (while (todo-done-item-p)
+       (when (todo-marked-item-p)
+         (user-error "This command does not apply to done items"))
+       (todo-backward-item)))))
+
 (defun todo-reset-done-separator (sep)
   "Replace existing overlays of done items separator string SEP."
   (save-excursion
@@ -5274,6 +5351,8 @@ of each other."
                          (todo-current-category)
                          (nth 2 (assoc-string todo-current-todo-file
                                               todo-top-priorities-overrides))))
+                   (nth 1 (assoc-string todo-current-todo-file
+                                        todo-top-priorities-overrides))
                    todo-top-priorities))
        done prefix)
     (save-excursion
@@ -5288,7 +5367,8 @@ of each other."
                     (looking-at todo-done-string-start)
                     (looking-back (concat "^"
                                           (regexp-quote todo-category-done)
-                                          "\n")))
+                                          "\n")
+                                   (line-beginning-position 0)))
            (setq num 1
                  done t))
          (setq prefix (concat (propertize
@@ -5386,7 +5466,7 @@ dynamically create item insertion commands.")
 The list consists of item insertion parameters that can be passed
 as insertion command arguments in fixed positions.  If a position
 in the list is not occupied by the corresponding parameter, it is
-occupied by `nil'."
+occupied by nil."
   (let* ((arg (list (car todo-insert-item--args)))
         (args (nconc (cdr todo-insert-item--args)
                      (list (car (todo-insert-item--argsleft
@@ -5509,8 +5589,9 @@ already entered and those still available."
                                                        '(add/edit delete))
                                              " comment"))))
                          params " "))
-        (this-key (char-to-string
-                   (read-key (concat todo-edit-item--prompt p->k))))
+        (key-prompt (substitute-command-keys todo-edit-item--prompt))
+        (this-key (let ((key (read-key (concat key-prompt p->k))))
+                    (and (characterp key) (char-to-string key))))
         (this-param (car (rassoc this-key params))))
     (pcase this-param
       (`edit (todo-edit-item--text))
@@ -5574,9 +5655,10 @@ have been removed."
     (when deleted
       (let ((pl (> (length deleted) 1))
            (names (mapconcat (lambda (f) (concat "\"" f "\"")) deleted ", ")))
-       (message (concat "File" (if pl "s" "") " " names " ha" (if pl "ve" "s")
+       (message (concat "File" (if pl "s" "") " %s ha" (if pl "ve" "s")
                         " been deleted and removed from\n"
-                        "the list of category completion files")))
+                        "the list of category completion files")
+                names))
       (todo-reevaluate-category-completions-files-defcustom)
       (custom-set-default 'todo-category-completions-files
                          (symbol-value 'todo-category-completions-files))
@@ -5596,6 +5678,9 @@ have been removed."
        (add-to-list 'files curfile))
       (dolist (f files listall)
        (with-current-buffer (find-file-noselect f 'nowarn)
+         (if archive
+             (unless (derived-mode-p 'todo-archive-mode) (todo-archive-mode))
+           (unless (derived-mode-p 'todo-mode) (todo-mode)))
          ;; Ensure category is properly displayed in case user
          ;; switches to file via a non-Todo mode command.  And if
          ;; done items in category are visible, keep them visible.
@@ -5681,6 +5766,7 @@ categories from `todo-category-completions-files'."
           (categories (cond (file0
                              (with-current-buffer
                                  (find-file-noselect file0 'nowarn)
+                               (unless (derived-mode-p 'todo-mode) (todo-mode))
                                (let ((todo-current-todo-file file0))
                                  todo-categories)))
                             ((and add (not file))
@@ -5856,7 +5942,7 @@ number of the last the day of the month."
     (and day (setq day (if (eq day '*)
                           (symbol-name '*)
                         (number-to-string day))))
-    (and month (setq month (if (eq month '*)
+    (and month (setq month (if (= month 13)
                               (symbol-name '*)
                             (number-to-string month))))
     (if arg
@@ -5949,7 +6035,7 @@ the empty string (i.e., no time string)."
   "The :set function for user option `todo-nondiary-marker'."
   (let* ((oldvalue (symbol-value symbol))
         (files (append todo-files todo-archives
-                       (directory-files todo-directory t "\.tod[rty]$" t))))
+                       (directory-files todo-directory t "\\.tod[rty]$" t))))
     (custom-set-default symbol value)
     ;; Need to reset these to get font-locking right.
     (setq todo-nondiary-start (nth 0 todo-nondiary-marker)
@@ -5960,23 +6046,28 @@ the empty string (i.e., no time string)."
                  (regexp-quote diary-nonmarking-symbol) "\\)?"))
     (when (not (equal value oldvalue))
       (dolist (f files)
-       (with-current-buffer (find-file-noselect f)
-         (let (buffer-read-only)
-           (widen)
-           (goto-char (point-min))
-           (while (not (eobp))
-             (if (re-search-forward
-                  (concat "^\\(" todo-done-string-start "[^][]+] \\)?"
-                          "\\(?1:" (regexp-quote (car oldvalue))
-                          "\\)" todo-date-pattern "\\( "
-                          diary-time-regexp "\\)?\\(?2:"
-                          (regexp-quote (cadr oldvalue)) "\\)")
-                  nil t)
-                 (progn
-                   (replace-match (nth 0 value) t t nil 1)
-                   (replace-match (nth 1 value) t t nil 2))
-               (forward-line)))
-           (todo-category-select)))))))
+       (let ((buf (find-buffer-visiting f)))
+         (with-current-buffer (find-file-noselect f)
+           (let (buffer-read-only)
+             (widen)
+             (goto-char (point-min))
+             (while (not (eobp))
+               (if (re-search-forward
+                    (concat "^\\(" todo-done-string-start "[^][]+] \\)?"
+                            "\\(?1:" (regexp-quote (car oldvalue))
+                            "\\)" todo-date-pattern "\\( "
+                            diary-time-regexp "\\)?\\(?2:"
+                            (regexp-quote (cadr oldvalue)) "\\)")
+                    nil t)
+                   (progn
+                     (replace-match (nth 0 value) t t nil 1)
+                     (replace-match (nth 1 value) t t nil 2))
+                 (forward-line)))
+             (if buf
+                 (when (derived-mode-p 'todo-mode 'todo-archive-mode)
+                   (todo-category-select))
+               (save-buffer)
+               (kill-buffer)))))))))
 
 (defun todo-reset-done-separator-string (symbol value)
   "The :set function for `todo-done-separator-string'."
@@ -5997,53 +6088,62 @@ the empty string (i.e., no time string)."
   "The :set function for user option `todo-done-string'."
   (let ((oldvalue (symbol-value symbol))
        (files (append todo-files todo-archives
-                      (directory-files todo-directory t "\.todr$" t))))
+                      (directory-files todo-directory t "\\.todr$" t))))
     (custom-set-default symbol value)
     ;; Need to reset this to get font-locking right.
     (setq todo-done-string-start
          (concat "^\\[" (regexp-quote todo-done-string)))
     (when (not (equal value oldvalue))
       (dolist (f files)
-       (with-current-buffer (find-file-noselect f)
-         (let (buffer-read-only)
-           (widen)
-           (goto-char (point-min))
-           (while (not (eobp))
-             (if (re-search-forward
-                  (concat "^" (regexp-quote todo-nondiary-start)
-                          "\\(" (regexp-quote oldvalue) "\\)")
-                  nil t)
-                 (replace-match value t t nil 1)
-               (forward-line)))
-           (todo-category-select)))))))
+       (let ((buf (find-buffer-visiting f)))
+         (with-current-buffer (find-file-noselect f)
+           (let (buffer-read-only)
+             (widen)
+             (goto-char (point-min))
+             (while (not (eobp))
+               (if (re-search-forward
+                    (concat "^" (regexp-quote todo-nondiary-start)
+                            "\\(" (regexp-quote oldvalue) "\\)")
+                    nil t)
+                   (replace-match value t t nil 1)
+                 (forward-line)))
+             (if buf
+                 (when (derived-mode-p 'todo-mode 'todo-archive-mode)
+                   (todo-category-select))
+               (save-buffer)
+               (kill-buffer)))))))))
 
 (defun todo-reset-comment-string (symbol value)
   "The :set function for user option `todo-comment-string'."
   (let ((oldvalue (symbol-value symbol))
        (files (append todo-files todo-archives
-                      (directory-files todo-directory t "\.todr$" t))))
+                      (directory-files todo-directory t "\\.todr$" t))))
     (custom-set-default symbol value)
     (when (not (equal value oldvalue))
       (dolist (f files)
-       (with-current-buffer (find-file-noselect f)
-         (let (buffer-read-only)
-           (save-excursion
+       (let ((buf (find-buffer-visiting f)))
+         (with-current-buffer (find-file-noselect f)
+           (let (buffer-read-only)
              (widen)
              (goto-char (point-min))
              (while (not (eobp))
                (if (re-search-forward
-                    (concat
-                            "\\[\\(" (regexp-quote oldvalue) "\\): [^]]*\\]")
+                    (concat "\\[\\(" (regexp-quote oldvalue)
+                            "\\): [^]]*\\]")
                     nil t)
                    (replace-match value t t nil 1)
                  (forward-line)))
-             (todo-category-select))))))))
+             (if buf
+                 (when (derived-mode-p 'todo-mode 'todo-archive-mode)
+                   (todo-category-select))
+               (save-buffer)
+               (kill-buffer)))))))))
 
 (defun todo-reset-highlight-item (symbol value)
   "The :set function for user option `todo-highlight-item'."
   (let ((oldvalue (symbol-value symbol))
        (files (append todo-files todo-archives
-                      (directory-files todo-directory t "\.tod[rty]$" t))))
+                      (directory-files todo-directory t "\\.tod[rty]$" t))))
     (custom-set-default symbol value)
     (when (not (equal value oldvalue))
       (dolist (f files)
@@ -6435,20 +6535,20 @@ Added to `pre-command-hook' in Todo mode when user option
 `todo-show-current-file' is set to non-nil."
   (setq todo-global-current-todo-file todo-current-todo-file))
 
-(defun todo-display-as-todo-file ()
-  "Show todo files correctly when visited from outside of Todo mode.
-Added to `find-file-hook' in Todo mode and Todo Archive mode."
-  (and (member this-command todo-visit-files-commands)
-       (= (- (point-max) (point-min)) (buffer-size))
-       (member major-mode '(todo-mode todo-archive-mode))
-       (todo-category-select)))
-
-(defun todo-add-to-buffer-list ()
-  "Add name of just visited todo file to `todo-file-buffers'.
-This function is added to `find-file-hook' in Todo mode."
-  (let ((filename (file-truename (buffer-file-name))))
-    (when (member filename todo-files)
-      (add-to-list 'todo-file-buffers filename))))
+;; (defun todo-display-as-todo-file ()
+;;   "Show todo files correctly when visited from outside of Todo mode.
+;; Added to `find-file-hook' in Todo mode and Todo Archive mode."
+;;   (and (member this-command todo-visit-files-commands)
+;;        (= (- (point-max) (point-min)) (buffer-size))
+;;        (member major-mode '(todo-mode todo-archive-mode))
+;;        (todo-category-select)))
+
+;; (defun todo-add-to-buffer-list ()
+;;   "Add name of just visited todo file to `todo-file-buffers'.
+;; This function is added to `find-file-hook' in Todo mode."
+;;   (let ((filename (file-truename (buffer-file-name))))
+;;     (when (member filename todo-files)
+;;       (add-to-list 'todo-file-buffers filename))))
 
 (defun todo-update-buffer-list ()
   "Make current Todo mode buffer file car of `todo-file-buffers'.
@@ -6480,6 +6580,7 @@ Added to `window-configuration-change-hook' in Todo mode."
 (defun todo-modes-set-1 ()
   "Make some settings that apply to multiple Todo modes."
   (setq-local font-lock-defaults '(todo-font-lock-keywords t))
+  (setq-local revert-buffer-function 'todo-revert-buffer)
   (setq-local tab-width todo-indent-to-here)
   (setq-local indent-line-function 'todo-indent)
   (when todo-wrap-lines
@@ -6490,8 +6591,7 @@ Added to `window-configuration-change-hook' in Todo mode."
   "Make some settings that apply to multiple Todo modes."
   (add-to-invisibility-spec 'todo)
   (setq buffer-read-only t)
-  (when (and (boundp 'desktop-save-mode) desktop-save-mode)
-    (setq-local desktop-save-buffer 'todo-desktop-save-buffer))
+  (setq-local desktop-save-buffer 'todo-desktop-save-buffer)
   (when (boundp 'hl-line-range-function)
     (setq-local hl-line-range-function
                (lambda() (save-excursion
@@ -6503,7 +6603,8 @@ Added to `window-configuration-change-hook' in Todo mode."
   "Make some settings that apply to multiple Todo modes."
   (setq-local todo-categories (todo-set-categories))
   (setq-local todo-category-number 1)
-  (add-hook 'find-file-hook 'todo-display-as-todo-file nil t))
+  ;; (add-hook 'find-file-hook 'todo-display-as-todo-file nil t)
+  )
 
 (put 'todo-mode 'mode-class 'special)
 
@@ -6512,23 +6613,26 @@ Added to `window-configuration-change-hook' in Todo mode."
   "Major mode for displaying, navigating and editing todo lists.
 
 \\{todo-mode-map}"
-  ;; (easy-menu-add todo-menu)
-  (todo-modes-set-1)
-  (todo-modes-set-2)
-  (todo-modes-set-3)
-  ;; Initialize todo-current-todo-file.
-  (when (member (file-truename (buffer-file-name))
-               (funcall todo-files-function))
-    (setq-local todo-current-todo-file (file-truename (buffer-file-name))))
-  (setq-local todo-show-done-only nil)
-  (setq-local todo-categories-with-marks nil)
-  (add-hook 'find-file-hook 'todo-add-to-buffer-list nil t)
-  (add-hook 'post-command-hook 'todo-update-buffer-list nil t)
-  (when todo-show-current-file
-    (add-hook 'pre-command-hook 'todo-show-current-file nil t))
-  (add-hook 'window-configuration-change-hook
-           'todo-reset-and-enable-done-separator nil t)
-  (add-hook 'kill-buffer-hook 'todo-reset-global-current-todo-file nil t))
+  (if (called-interactively-p 'any)
+      (message "%s"
+               (substitute-command-keys
+                "Type `\\[todo-show]' to enter Todo mode"))
+    (todo-modes-set-1)
+    (todo-modes-set-2)
+    (todo-modes-set-3)
+    ;; Initialize todo-current-todo-file.
+    (when (member (file-truename (buffer-file-name))
+                 (funcall todo-files-function))
+      (setq-local todo-current-todo-file (file-truename (buffer-file-name))))
+    (setq-local todo-show-done-only nil)
+    (setq-local todo-categories-with-marks nil)
+    ;; (add-hook 'find-file-hook 'todo-add-to-buffer-list nil t)
+    (add-hook 'post-command-hook 'todo-update-buffer-list nil t)
+    (when todo-show-current-file
+      (add-hook 'pre-command-hook 'todo-show-current-file nil t))
+    (add-hook 'window-configuration-change-hook
+             'todo-reset-and-enable-done-separator nil t)
+    (add-hook 'kill-buffer-hook 'todo-reset-global-current-todo-file nil t)))
 
 (put 'todo-archive-mode 'mode-class 'special)
 
@@ -6591,13 +6695,6 @@ Added to `window-configuration-change-hook' in Todo mode."
   (todo-modes-set-1)
   (todo-modes-set-2))
 
-;;;###autoload
-(add-to-list 'auto-mode-alist '("\\.todo\\'" . todo-mode))
-;;;###autoload
-(add-to-list 'auto-mode-alist '("\\.toda\\'" . todo-archive-mode))
-;;;###autoload
-(add-to-list 'auto-mode-alist '("\\.tod[tyr]\\'" . todo-filtered-items-mode))
-
 ;; -----------------------------------------------------------------------------
 (provide 'todo-mode)