]> code.delx.au - gnu-emacs/blobdiff - lisp/calendar/todo-mode.el
Update copyright year to 2015
[gnu-emacs] / lisp / calendar / todo-mode.el
index 15fce457dcf51b62c80f4123cfb55a53b4e00c3c..7ca57a42b768364ce43e5102ca11ed89c5fbb134 100644 (file)
@@ -1,6 +1,6 @@
 ;;; todo-mode.el --- facilities for making and maintaining todo lists
 
-;; Copyright (C) 1997, 1999, 2001-201 Free Software Foundation, Inc.
+;; Copyright (C) 1997, 1999, 2001-2015 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 and categories, rename categories, move
-;; them to another file or delete them.  You can also display summary
-;; tables of the categories in a file and the types of items they
-;; contain.  And you can build cross-categorial lists of items that
-;; satisfy 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
@@ -169,12 +146,7 @@ the value of `todo-done-separator'."
   "Return string used as value of variable `todo-done-separator'."
   (let ((sep todo-done-separator-string))
     (propertize (if (= 1 (length sep))
-                   ;; Until bug#2749 is fixed, if separator's length
-                   ;; is window-width and todo-wrap-lines is
-                   ;; non-nil, an indented empty line appears between
-                   ;; the separator and the first done item.
-                   ;; (make-string (window-width) (string-to-char sep))
-                   (make-string (1- (window-width)) (string-to-char sep))
+                   (make-string (window-width) (string-to-char sep))
                  todo-done-separator-string)
                'face 'todo-done-sep)))
 
@@ -332,6 +304,11 @@ shown in the Fancy Diary display."
 ;;; Faces
 ;; -----------------------------------------------------------------------------
 
+(defface todo-key-prompt
+  '((t (:weight bold)))
+  "Face for making keys in item insertion prompt stand out."
+  :group 'todo-faces)
+
 (defface todo-mark
   ;; '((t :inherit font-lock-warning-face))
   '((((class color)
@@ -440,7 +417,7 @@ less than or equal the category's top priority setting."
     (((class color) (min-colors 16) (background dark)) :foreground "LightSteelBlue")
     (((class color) (min-colors 8)) :foreground "blue" :weight bold)
     (t :weight bold))
-  "Face for separator string bewteen done and not done todo items."
+  "Face for separator string between done and not done todo items."
   :group 'todo-faces)
 
 (defface todo-done
@@ -563,13 +540,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.
@@ -578,11 +555,12 @@ This lacks the extension and directory components."
     (file-name-sans-extension (file-name-nondirectory file))))
 
 (defcustom todo-default-todo-file (todo-short-file-name
-                                    (car (funcall todo-files-function)))
+                                  (car (funcall todo-files-function)))
   "Todo file visited by first session invocation of `todo-show'."
-  :type `(radio ,@(mapcar (lambda (f) (list 'const f))
-                         (mapcar 'todo-short-file-name
-                                 (funcall todo-files-function))))
+  :type (when todo-files
+         `(radio ,@(mapcar (lambda (f) (list 'const f))
+                           (mapcar 'todo-short-file-name
+                                   (funcall todo-files-function)))))
   :group 'todo)
 
 (defcustom todo-show-current-file t
@@ -630,7 +608,7 @@ Otherwise, `todo-show' always visits `todo-default-todo-file'."
   :group 'todo)
 
 ;;;###autoload
-(defun todo-show (&optional solicit-file)
+(defun todo-show (&optional solicit-file interactive)
   "Visit a todo file and display one of its categories.
 
 When invoked in Todo mode, prompt for which todo file to visit.
@@ -668,117 +646,130 @@ and done items are always shown on visiting a category.
 
 Invoking this command in Todo Archive mode visits the
 corresponding todo file, displaying the corresponding category."
-  (interactive "P")
+  (interactive "P\np")
+  (when todo-default-todo-file
+    (todo-check-file (todo-absolute-file-name todo-default-todo-file)))
   (catch 'shown
-    ;; If there is a legacy todo file but no todo file in the current
-    ;; format, offer to convert the legacy file and show it.
+    ;; 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.
     (unless todo-default-todo-file
       (let ((legacy-todo-file (if (boundp 'todo-file-do)
-                                 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)))))
-    (let* ((cat)
-          (show-first todo-show-first)
-          (file (cond ((or solicit-file
-                           (and (called-interactively-p 'any)
-                                (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 (called-interactively-p 'any)))
-                       (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)
-                      (set-window-buffer
-                       (selected-window)
-                       (set-buffer (find-file-noselect fi-file 'nowarn)))
-                    (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))
-       (set-window-buffer (selected-window)
-                          (set-buffer (find-file-noselect file 'nowarn)))
-       ;; 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 (delete file todo-files))
-               (when first-file
-                 (setq todo-default-todo-file nil
-                       todo-current-todo-file nil))
-               (kill-buffer)
-               (keyboard-quit)))))
-       (save-excursion (todo-category-select))
-       (when add-item (todo-basic-insert-item)))
-      (setq todo-show-first show-first)
-      (add-to-list 'todo-visited file))))
+                                 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)
+                        (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")
+                                     ((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)))
+         (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))
+           (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))
+         (when add-item (todo-insert-item--basic)))
+       (setq todo-show-first show-first)
+       (add-to-list 'todo-visited file)))))
 
 (defun todo-save ()
   "Save the current todo file."
@@ -814,8 +805,15 @@ buries it and restores state as needed."
           ;; Have to write a newly created archive to file to avoid
           ;; subsequent errors.
           (todo-save)
-          (todo-show)
-          (bury-buffer buf))
+          (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)))
          ((eq major-mode 'todo-mode)
           (todo-save)
           ;; If we just quit archive mode, just burying the buffer
@@ -893,7 +891,7 @@ Categories mode."
   (interactive "P")
   ;; If invoked outside of Todo mode and there is not yet any Todo
   ;; file, initialize one.
-  (if (null todo-files)
+  (if (null (funcall todo-files-function))
       (todo-show)
     (let* ((archive (eq where 'archive))
           (cat (unless archive where))
@@ -929,7 +927,7 @@ Categories mode."
        (todo-category-number category)
        (todo-category-select)
        (goto-char (point-min))
-       (when add-item (todo-basic-insert-item))))))
+       (when add-item (todo-insert-item--basic))))))
 
 (defun todo-next-item (&optional count)
   "Move point down to the beginning of the next item.
@@ -1027,7 +1025,7 @@ empty line above the done items separator."
 (defun todo-toggle-item-highlighting ()
   "Highlight or unhighlight the todo item the cursor is on."
   (interactive)
-  (eval-when-compile (require 'hl-line))
+  (eval-and-compile (require 'hl-line))
   (when (memq major-mode
              '(todo-mode todo-archive-mode todo-filtered-items-mode))
     (if hl-line-mode
@@ -1069,10 +1067,12 @@ option `todo-add-item-if-new-category' is non-nil (the default),
 prompt for the first item.
 Noninteractively, return the name of the new file."
   (interactive)
-  (let ((prompt (concat "Enter name of new todo file "
-                       "(TAB or SPC to see current names): "))
-       file)
-    (setq file (todo-read-file-name prompt))
+  (let* ((prompt (concat "Enter name of new todo file "
+                        "(TAB or SPC to see current names): "))
+        (file (todo-read-file-name prompt)))
+    ;; Don't accept the name of an existing todo file.
+    (setq file (todo-absolute-file-name
+               (todo-validate-name (todo-short-file-name file) 'file)))
     (with-current-buffer (get-buffer-create file)
       (erase-buffer)
       (write-region (point-min) (point-max) file nil 'nomessage nil t)
@@ -1087,15 +1087,129 @@ Noninteractively, return the name of the new file."
          (todo-show))
       file)))
 
+(defun todo-rename-file (&optional arg)
+  "Rename the current todo file.
+With prefix ARG, prompt for a todo file and rename it.
+If there are corresponding archive or filtered items files,
+rename these accordingly.  If there are live buffers visiting
+these files, also rename them accordingly."
+  (interactive "P")
+  (let* ((oname (or (and arg
+                        (todo-read-file-name "Choose a file to rename: "
+                                             nil t))
+                   (buffer-file-name)))
+        (soname (todo-short-file-name oname))
+        (nname (todo-read-file-name "New name for this file: "))
+        (snname (todo-short-file-name nname))
+        (files (directory-files todo-directory t
+                                (concat ".*" (regexp-quote soname)
+                                        ".*\.tod[aorty]$") t)))
+    (dolist (f files)
+      (let* ((sfname (todo-short-file-name f))
+            (fext (file-name-extension f t))
+            (fbuf (find-buffer-visiting f))
+            (fbname (buffer-name fbuf)))
+       (when (string-match (regexp-quote soname) sfname)
+         (let* ((snfname (replace-match snname t t sfname))
+                (nfname (concat todo-directory snfname fext)))
+           (rename-file f nfname)
+           (when fbuf
+             (with-current-buffer fbuf
+               (set-visited-file-name nfname t t)
+               (cond ((member fext '(".todo" ".toda"))
+                      (setq todo-current-todo-file (buffer-file-name))
+                      (setq mode-line-buffer-identification
+                            (funcall todo-mode-line-function
+                                     (todo-current-category))))
+                     (t
+                      (rename-buffer
+                       (replace-regexp-in-string
+                        (regexp-quote soname) snname fbname))))))))))
+    (setq todo-files (funcall todo-files-function)
+         todo-archives (funcall todo-files-function t))
+    (when (string= todo-default-todo-file soname)
+      (setq todo-default-todo-file snname))
+    (when (string= todo-global-current-todo-file oname)
+      (setq todo-global-current-todo-file nname))
+    (todo-reevaluate-filelist-defcustoms)))
+
+(defun todo-delete-file ()
+  "Delete the current todo, archive or filtered items file.
+If the todo file has a corresponding archive file, or vice versa,
+prompt whether to delete that as well.  Also kill the buffers
+visiting the deleted files."
+  (interactive)
+  (let* ((file1 (buffer-file-name))
+        (todo (eq major-mode 'todo-mode))
+        (archive (eq major-mode 'todo-archive-mode))
+        (filtered (eq major-mode 'todo-filtered-items-mode))
+        (file1-sn (todo-short-file-name file1))
+        (file2 (concat todo-directory file1-sn (cond (todo ".toda")
+                                                     (archive ".todo"))))
+        (buf1 (current-buffer))
+        (buf2 (when file2 (find-buffer-visiting file2)))
+        (prompt1 (concat "Delete " (cond (todo "todo")
+                                         (archive "archive")
+                                         (filtered "filtered items"))
+                         " file \"%s\"? "))
+        (prompt2 (concat "Also delete the corresponding "
+                         (cond (todo "archive") (archive "todo")) " file "
+                         (when buf2 "and kill the buffer visiting it? ")))
+        (delete1 (yes-or-no-p (format prompt1 file1-sn)))
+        (delete2 (when (and delete1 (or (file-exists-p file2) buf2))
+                   (yes-or-no-p prompt2))))
+    (when delete1
+      (when (file-exists-p file1) (delete-file file1))
+      (setq todo-visited (delete file1 todo-visited))
+      (kill-buffer buf1)
+      (if delete2
+         (progn
+           (when (file-exists-p file2) (delete-file file2))
+           (setq todo-visited (delete file2 todo-visited))
+           (and buf2 (kill-buffer buf2)))
+       ;; If we deleted an archive but not its todo file, update the
+       ;; latter's category sexp.
+       (when (equal (file-name-extension file2) "todo")
+         (with-current-buffer (or buf2 (find-file-noselect file2))
+           (save-excursion
+             (save-restriction
+               (widen)
+               (goto-char (point-min))
+               (let ((sexp (read (buffer-substring-no-properties
+                                  (line-beginning-position)
+                                  (line-end-position))))
+                     (buffer-read-only nil))
+                 (mapc (lambda (x) (aset (cdr x) 3 0)) sexp)
+                 (delete-region (line-beginning-position) (line-end-position))
+                 (prin1 sexp (current-buffer)))))
+           (todo-set-categories)
+           (unless buf2 (kill-buffer)))))
+      (setq todo-files (funcall todo-files-function)
+           todo-archives (funcall todo-files-function t))
+      (when (or (string=  file1-sn todo-default-todo-file)
+               (and delete2 (string= file1-sn todo-default-todo-file)))
+       (setq todo-default-todo-file (todo-short-file-name (car todo-files))))
+      (when (or (string= file1 todo-global-current-todo-file)
+               (and delete2 (string= file2 todo-global-current-todo-file)))
+       (setq todo-global-current-todo-file nil))
+      (todo-reevaluate-filelist-defcustoms)
+      (message (concat (cond (todo "Todo") (archive "Archive")) " file \"%s\" "
+                      (when delete2
+                        (concat "and its "
+                                (cond (todo "archive") (archive "todo"))
+                                " file "))
+                      "deleted")
+              file1-sn))))
+
 (defvar todo-edit-buffer "*Todo Edit*"
   "Name of current buffer in Todo Edit mode.")
 
 (defun todo-edit-file ()
   "Put current buffer in `todo-edit-mode'.
-This makes the entire file visible and the buffer writeable and
+This makes the entire file visible and the buffer writable and
 you can use the self-insertion keys and standard Emacs editing
 commands to make changes.  To return to Todo mode, type
-\\[todo-edit-quit].  This runs a file format check, signalling
+\\[todo-edit-quit].  This runs a file format check, signaling
 an error if the format has become invalid.  However, this check
 cannot tell if the number of items changed, which could result in
 the file containing inconsistent information.  For this reason
@@ -1104,9 +1218,19 @@ this command should be used with caution."
   (widen)
   (todo-edit-mode)
   (remove-overlays)
-  (message "%s" (substitute-command-keys
-                (concat "Type \\[todo-edit-quit] to check file format "
-                        "validity and return to Todo mode.\n"))))
+  (display-warning 'todo (format "\
+
+Type %s to return to Todo mode.
+
+This also runs a file format check and signals an error if
+the format has become invalid.  However, this check cannot
+tell if the number of items or categories changed, which
+could result in the file containing inconsistent information.
+You can repair this inconsistency by invoking the command
+`todo-repair-categories-sexp', but this will revert any
+renumbering of the categories you have made, so you will
+have to renumber them again (see `(todo-mode) Reordering
+Categories')." (substitute-command-keys "\\[todo-edit-quit]"))))
 
 (defun todo-add-category (&optional file cat)
   "Add a new category to a todo file.
@@ -1155,7 +1279,7 @@ return the new category number."
            (setq todo-category-number num)
            (todo-category-select)
            (when todo-add-item-if-new-category
-             (todo-basic-insert-item)))
+             (todo-insert-item--basic)))
        num))))
 
 (defun todo-rename-category ()
@@ -1190,9 +1314,9 @@ category there as well."
   (save-excursion (todo-category-select)))
 
 (defun todo-delete-category (&optional arg)
-  "Delete current todo category provided it is empty.
-With ARG non-nil delete the category unconditionally,
-i.e. including all existing todo and done items."
+  "Delete current todo category provided it contains no items.
+With prefix ARG delete the category even if it does contain
+todo or done items."
   (interactive "P")
   (let* ((file todo-current-todo-file)
         (cat (todo-current-category))
@@ -1253,8 +1377,9 @@ i.e. including all existing todo and done items."
 
 (defun todo-move-category ()
   "Move current category to a different todo file.
-If current category has archived items, also move those to the
-archive of the file moved to, creating it if it does not exist."
+If the todo file chosen does not exist, it is created.
+If the current category has archived items, also move those to
+the archive of the file moved to, creating it if it does not exist."
   (interactive)
   (when (or (> (length todo-categories) 1)
            (todo-y-or-n-p (concat "This is the only category in this file; "
@@ -1262,16 +1387,22 @@ archive of the file moved to, creating it if it does not exist."
                                    "Do you want to proceed? ")))
     (let* ((ofile todo-current-todo-file)
           (cat (todo-current-category))
-          (nfile (todo-read-file-name
-                  "Choose a todo file to move this category to: " nil t))
+          (nfile (todo-read-file-name "Todo file to move this category to: "))
           (archive (concat (file-name-sans-extension ofile) ".toda"))
           (buffers (append (list ofile)
                            (unless (zerop (todo-get-count 'archived cat))
                              (list archive))))
           new)
-      (while (equal (file-truename nfile) (file-truename ofile))
+      (while (equal nfile (file-truename ofile))
        (setq nfile (todo-read-file-name
-                    "Choose a file distinct from this file: " nil t)))
+                    "Choose a file distinct from this file: ")))
+      (unless (member nfile todo-files)
+       (with-current-buffer (get-buffer-create nfile)
+         (erase-buffer)
+         (write-region (point-min) (point-max) nfile nil 'nomessage nil t)
+         (kill-buffer nfile))
+       (setq todo-files (funcall todo-files-function))
+       (todo-reevaluate-filelist-defcustoms))
       (dolist (buf buffers)
        (with-current-buffer (find-file-noselect buf)
          (widen)
@@ -1298,6 +1429,10 @@ 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 "
@@ -1323,7 +1458,7 @@ archive of the file moved to, creating it if it does not exist."
                           "\\(" (regexp-quote cat) "\\)$") nil t)
                  (replace-match new nil nil nil 1)))
              (setq todo-categories
-                   (append todo-categories (list (cons new counts))))
+                   (append todo-categories (list (cons (or new cat) counts))))
              (todo-update-categories-sexp)
              ;; If archive was just created, save it to avoid "File
              ;; <xyz> no longer exists!" message on invoking
@@ -1351,6 +1486,8 @@ archive of the file moved to, creating it if it does not exist."
              (setq todo-categories (delete (assoc cat todo-categories)
                                             todo-categories))
              (todo-update-categories-sexp)
+             (when (> todo-category-number (length todo-categories))
+               (setq todo-category-number 1))
              (todo-category-select)))))
       (set-window-buffer (selected-window)
                         (set-buffer (find-file-noselect nfile)))
@@ -1365,25 +1502,25 @@ choose (with TAB completion) a category in it to merge into;
 otherwise, choose and merge into a category in either the
 current todo file or a file in `todo-category-completions-files'.
 
-After merging, the current category's todo and done items are
+After merging, the source category's todo and done items are
 appended to the chosen goal category's todo and done items,
 respectively.  The goal category becomes the current category,
-and the previous current category is deleted.
+and the source category is deleted.
 
-If both the first and goal categories also have archived items,
-the former are merged to the latter.  If only the first category
-has archived items, the archived category is renamed to the goal
-category."
+If both the source and goal categories also have archived items,
+they are also merged.  If only the source category has archived
+items, the goal category is added as a new category to the
+archive file and the source category is deleted."
   (interactive "P")
   (let* ((tfile todo-current-todo-file)
         (cat (todo-current-category))
         (cat+file (todo-read-category "Merge into category: " 'todo file))
         (goal (car cat+file))
         (gfile  (cdr cat+file))
-        (archive (concat (file-name-sans-extension (if file gfile tfile))
-                         ".toda"))
-        archived-count here)
-    ;; Merge in todo file.
+        (tarchive (concat (file-name-sans-extension tfile) ".toda"))
+        (garchive (concat (file-name-sans-extension gfile) ".toda"))
+        (archived-count (todo-get-count 'archived))
+        here)
     (with-current-buffer (get-buffer (find-file-noselect tfile))
       (widen)
       (let* ((buffer-read-only nil)
@@ -1406,94 +1543,102 @@ category."
                           (point-marker))
                       (point-max-marker))))
             (todo (buffer-substring-no-properties tbeg tend))
-            (done (buffer-substring-no-properties dbeg cend)))
-       (goto-char (point-min))
-       ;; Merge any todo items.
-       (unless (zerop (length todo))
-         (re-search-forward
-          (concat "^" (regexp-quote (concat todo-category-beg goal)) "$")
-          nil t)
-         (re-search-forward
-          (concat "^" (regexp-quote todo-category-done)) nil t)
-         (forward-line -1)
-         (setq here (point-marker))
-         (insert todo)
-         (todo-update-count 'todo (todo-get-count 'todo cat) goal))
-       ;; Merge any done items.
-       (unless (zerop (length done))
-         (goto-char (if (re-search-forward
-                         (concat "^" (regexp-quote todo-category-beg)) nil t)
-                        (match-beginning 0)
-                      (point-max)))
-         (when (zerop (length todo)) (setq here (point-marker)))
-         (insert done)
-         (todo-update-count 'done (todo-get-count 'done cat) goal))
+            (done (buffer-substring-no-properties dbeg cend))
+            (todo-count (todo-get-count 'todo cat))
+            (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))
+           ;; Merge any todo items.
+           (unless (zerop (length todo))
+             (re-search-forward
+              (concat "^" (regexp-quote (concat todo-category-beg goal)) "$")
+              nil t)
+             (re-search-forward
+              (concat "^" (regexp-quote todo-category-done)) nil t)
+             (forward-line -1)
+             (setq here (point-marker))
+             (insert todo)
+             (todo-update-count 'todo todo-count goal))
+           ;; Merge any done items.
+           (unless (zerop (length done))
+             (goto-char (if (re-search-forward
+                             (concat "^" (regexp-quote todo-category-beg))
+                             nil t)
+                            (match-beginning 0)
+                          (point-max)))
+             (when (zerop (length todo)) (setq here (point-marker)))
+             (insert done)
+             (todo-update-count 'done done-count goal)))
+         (todo-update-categories-sexp))
+       ;; Update and clean up source todo file.
        (remove-overlays cbeg cend)
        (delete-region cbeg cend)
        (setq todo-categories (delete (assoc cat todo-categories)
-                                      todo-categories))
+                                     todo-categories))
        (todo-update-categories-sexp)
-       (mapc (lambda (m) (set-marker m nil)) (list cbeg tbeg dbeg tend cend))))
-    (when (file-exists-p archive)
-      ;; Merge in archive file.
-      (with-current-buffer (get-buffer (find-file-noselect archive))
+       (when (> todo-category-number (length todo-categories))
+               (setq todo-category-number 1))
+       (todo-category-select)
+       (mapc (lambda (m) (set-marker m nil))
+             (list cbeg tbeg dbeg tend cend))))
+    (when (> archived-count 0)
+      (with-current-buffer (get-buffer (find-file-noselect tarchive))
        (widen)
        (goto-char (point-min))
-       (let ((buffer-read-only nil)
-             (cbeg (save-excursion
-                     (when (re-search-forward
-                            (concat "^" (regexp-quote
-                                         (concat todo-category-beg cat)) "$")
-                            nil t)
-                       (goto-char (match-beginning 0))
-                       (point-marker))))
-             (gbeg (save-excursion
-                     (when (re-search-forward
-                            (concat "^" (regexp-quote
-                                         (concat todo-category-beg goal)) "$")
-                            nil t)
-                       (goto-char (match-beginning 0))
-                       (point-marker))))
-             cend carch)
-         (when cbeg
-           (setq archived-count (todo-get-count 'done cat))
-           (setq cend (save-excursion
-                        (if (re-search-forward
-                             (concat "^" (regexp-quote todo-category-beg))
+       (let* ((buffer-read-only nil)
+              (cbeg (progn
+                      (when (re-search-forward
+                             (concat "^" (regexp-quote
+                                          (concat todo-category-beg cat)) "$")
                              nil t)
+                        (goto-char (match-beginning 0))
+                        (point-marker))))
+              (cend (if (re-search-forward
+                         (concat "^" (regexp-quote todo-category-beg)) nil t)
+                        (match-beginning 0)
+                      (point-max)))
+              (carch (progn
+                       (goto-char cbeg)
+                       (forward-line)
+                       (buffer-substring-no-properties (point) cend))))
+         ;; Merge into goal archive category, if it exists, else create it.
+         (with-current-buffer (get-buffer (find-file-noselect garchive))
+           (let ((gbeg (when (re-search-forward
+                              (concat "^" (regexp-quote
+                                           (concat todo-category-beg goal))
+                                      "$")
+                              nil t)
+                         (goto-char (match-beginning 0))
+                         (point-marker))))
+             (goto-char (if (and gbeg
+                                 (re-search-forward
+                                  (concat "^" (regexp-quote todo-category-beg))
+                                  nil t))
                             (match-beginning 0)
-                          (point-max))))
-           (setq carch (save-excursion (goto-char cbeg) (forward-line)
-                         (buffer-substring-no-properties (point) cend)))
-           ;; If both categories of the merge have archived items, merge the
-           ;; source items to the goal items, else "merge" by renaming the
-           ;; source category to goal.
-           (if gbeg
-               (progn
-                 (goto-char (if (re-search-forward
-                                 (concat "^" (regexp-quote todo-category-beg))
-                                 nil t)
-                                (match-beginning 0)
-                              (point-max)))
-                 (insert carch)
-                 (remove-overlays cbeg cend)
-                 (delete-region cbeg cend))
-             (goto-char cbeg)
-             (search-forward cat)
-             (replace-match goal))
-           (setq todo-categories (todo-make-categories-list t))
-           (todo-update-categories-sexp)))))
-    (with-current-buffer (get-file-buffer tfile)
-      (when archived-count
-       (unless (zerop archived-count)
-         (todo-update-count 'archived archived-count goal)
-         (todo-update-categories-sexp)))
-      (todo-category-number goal)
-      ;; If there are only merged done items, show them.
-      (let ((todo-show-with-done (zerop (todo-get-count 'todo goal))))
-       (todo-category-select)
-       ;; Put point on the first merged item.
-       (goto-char here)))
+                          (point-max)))
+             (unless gbeg (todo-add-category nil goal))
+             (insert carch)
+             (todo-update-categories-sexp)))
+         ;; Update and clean up source archive file.
+         (remove-overlays cbeg cend)
+         (delete-region cbeg cend)
+         (setq todo-categories (todo-make-categories-list t))
+         (todo-update-categories-sexp))))
+    ;; Update goal todo file for merged archived items and display it.
+    (set-window-buffer (selected-window) (set-buffer (get-file-buffer gfile)))
+    (unless (zerop archived-count)
+      (todo-update-count 'archived archived-count goal)
+      (todo-update-categories-sexp))
+    (todo-category-number goal)
+    ;; If there are only merged done items, show them.
+    (let ((todo-show-with-done (zerop (todo-get-count 'todo goal))))
+      (todo-category-select)
+      ;; Put point on the first merged item.
+      (goto-char here))
     (set-marker here nil)))
 
 ;; -----------------------------------------------------------------------------
@@ -1526,6 +1671,12 @@ current time, if nil, they include it."
   :type 'boolean
   :group 'todo-edit)
 
+(defcustom todo-default-priority 'first
+  "Default priority of new and moved items."
+  :type '(choice (const :tag "Highest priority" first)
+                (const :tag "Lowest priority" last))
+  :group 'todo-edit)
+
 (defcustom todo-item-mark "*"
   "String used to mark items.
 To ensure item marking works, change the value of this option
@@ -1559,34 +1710,43 @@ 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 visiblw items in this category with `todo-item-mark'."
+  "Mark all visible items in this category with `todo-item-mark'."
   (interactive)
   (let* ((cat (todo-current-category))
         (marks (assoc cat todo-categories-with-marks)))
@@ -1600,7 +1760,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."
@@ -1622,111 +1787,50 @@ marking of the next N items."
 (defvar todo-date-from-calendar nil
   "Helper variable for setting item date from the Emacs Calendar.")
 
-(defun todo-basic-insert-item (&optional arg diary nonmarking date-type time
-                                   region-or-here)
-  "Insert a new todo item into a category.
-This is the function from which the generated Todo mode item
-insertion commands derive.
-
-The generated commands have mnenomic key bindings based on the
-arguments' values and their order in the command's argument list,
-as follows: (1) for DIARY `d', (2) for NONMARKING `k', (3) for
-DATE-TYPE either `c' for calendar or `d' for date or `n' for
-weekday name, (4) for TIME `t', (5) for REGION-OR-HERE either `r'
-for region or `h' for here.  Sequences of these keys are appended
-to the insertion prefix key `i'.  Keys that allow a following
-key (i.e., any but `r' or `h') must be doubled when used finally.
-For example, the command bound to the key sequence `i y h' will
-insert a new item with today's date, marked according to the
-DIARY argument described below, and with priority according to
-the HERE argument; `i y y' does the same except that the priority
-is not given by HERE but by prompting.
-
-In command invocations, ARG is passed as a prefix argument as
-follows.  With no prefix argument, add the item to the current
+(defvar todo-insert-item--keys-so-far)
+(defvar todo-insert-item--parameters)
+
+(defun todo-insert-item (&optional arg)
+  "Choose an item insertion operation and carry it out.
+This inserts a new todo item into a category.
+
+With no prefix argument ARG, add the item to the current
 category; with one prefix argument (`C-u'), prompt for a category
-from the current todo file; with two prefix arguments (`C-u C-u'),
-first prompt for a todo file, then a category in that file.  If
-a non-existing category is entered, ask whether to add it to the
-todo file; if answered affirmatively, add the category and
-insert the item there.
-
-The remaining arguments are set or left nil by the generated item
-insertion commands; their meanings are described in the follows
-paragraphs.
-
-When argument DIARY is non-nil, this overrides the intent of the
-user option `todo-include-in-diary' for this item: if
-`todo-include-in-diary' is nil, include the item in the Fancy
-Diary display, and if it is non-nil, exclude the item from the
-Fancy Diary display.  When DIARY is nil, `todo-include-in-diary'
-has its intended effect.
-
-When the item is included in the Fancy Diary display and the
-argument NONMARKING is non-nil, this overrides the intent of the
-user option `todo-diary-nonmarking' for this item: if
-`todo-diary-nonmarking' is nil, append `diary-nonmarking-symbol'
-to the item, and if it is non-nil, omit `diary-nonmarking-symbol'.
-
-The argument DATE-TYPE determines the content of the item's
-mandatory date header string and how it is added:
-- If DATE-TYPE is the symbol `calendar', the Calendar pops up and
-  when the user puts the cursor on a date and hits RET, that
-  date, in the format set by `calendar-date-display-form',
-  becomes the date in the header.
-- If DATE-TYPE is a string matching the regexp
-  `todo-date-pattern', that string becomes the date in the
-  header.  This case is for the command
-  `todo-insert-item-from-calendar' which is called from the
-  Calendar.
-- If DATE-TYPE is the symbol `date', the header contains the date
-  in the format set by `calendar-date-display-form', with year,
-  month and day individually prompted for (month with tab
-  completion).
-- If DATE-TYPE is the symbol `dayname' the header contains a
-  weekday name instead of a date, prompted for with tab
-  completion.
-- If DATE-TYPE has any other value (including nil or none) the
-  header contains the current date (in the format set by
-  `calendar-date-display-form').
-
-With non-nil argument TIME prompt for a time string, which must
-match `diary-time-regexp'.  Typing `<return>' at the prompt
-returns the current time, if the user option
-`todo-always-add-time-string' is non-nil, otherwise the empty
-string (i.e., no time string).  If TIME is absent or nil, add or
-omit the current time string according as
-`todo-always-add-time-string' is non-nil or nil, respectively.
-
-The argument REGION-OR-HERE determines the source and location of
-the new item:
-- If the REGION-OR-HERE is the symbol `here', prompt for the text of
-  the new item and, if the command was invoked with point in the todo
-  items section of the current category, give the new item the
-  priority of the item at point, lowering the latter's priority and
-  the priority of the remaining items.  If point is in the done items
-  section of the category, insert the new item as the first todo item
-  in the category.  Likewise, if the command with `here' is invoked
-  outside of the current category, jump to the chosen category and
-  insert the new item as the first item in the category.
-- If REGION-OR-HERE is the symbol `region', use the region of the
-  current buffer as the text of the new item, depending on the
-  value of user option `todo-use-only-highlighted-region': if
-  this is non-nil, then use the region only when it is
-  highlighted; otherwise, use the region regardless of
-  highlighting.  An error is signalled if there is no region in
-  the current buffer.  Prompt for the item's priority in the
-  category (an integer between 1 and one more than the number of
-  items in the category), and insert the item accordingly.
-- If REGION-OR-HERE has any other value (in particular, nil or
-  none), prompt for the text and the item's priority, and insert
-  the item accordingly."
+from the current todo file; with two prefix arguments (`C-u
+C-u'), first prompt for a todo file, then a category in that
+file.  If a non-existing category is entered, ask whether to add
+it to the todo file; if answered affirmatively, add the category
+and insert the item there.
+
+There are a number of item insertion parameters which can be
+combined by entering specific keys to produce different insertion
+commands.  After entering each key, a message shows which have
+already been entered and which remain available.  See
+`(todo-mode) Inserting New Items' for details of the parameters,
+their associated keys and their effects."
+  (interactive "P")
+  (setq todo-insert-item--keys-so-far "i")
+  (todo-insert-item--next-param nil (list arg) todo-insert-item--parameters))
+
+(defun todo-insert-item--basic (&optional arg diary-type date-type time where)
+  "Function implementing the core of `todo-insert-item'."
   ;; If invoked outside of Todo mode and there is not yet any Todo
   ;; file, initialize one.
-  (if (null todo-files)
+  (if (null (funcall todo-files-function))
       (todo-show)
-    (let ((region (eq region-or-here 'region))
-         (here (eq region-or-here 'here)))
+    (let ((copy (eq where 'copy))
+         (region (eq where 'region))
+         (here (eq where 'here))
+         diary-item)
+      (when copy
+       (cond
+        ((not (eq major-mode 'todo-mode))
+         (user-error "You must be in Todo mode to copy a todo item"))
+        ((todo-done-item-p)
+         (user-error "You cannot copy a done item as a new todo item"))
+        ((looking-at "^$")
+         (user-error "Point must be on a todo item to copy it")))
+       (setq diary-item (todo-diary-item-p)))
       (when region
        (let (use-empty-active-region)
          (unless (and todo-use-only-highlighted-region (use-region-p))
@@ -1749,10 +1853,10 @@ the new item:
                                         todo-default-todo-file))))))
             (cat (car cat+file))
             (file (cdr cat+file))
-            (new-item (if region
-                          (buffer-substring-no-properties
-                           (region-beginning) (region-end))
-                        (read-from-minibuffer "Todo item: ")))
+            (new-item (cond (copy (todo-item-string))
+                            (region (buffer-substring-no-properties
+                                     (region-beginning) (region-end)))
+                            (t (read-from-minibuffer "Todo item: "))))
             (date-string (cond
                           ((eq date-type 'date)
                            (todo-read-date))
@@ -1791,22 +1895,26 @@ the new item:
        (let ((buffer-read-only nil)
              (called-from-outside (not (and todo-mm (equal cat ocat))))
              done-only item-added)
-         (setq new-item
-               ;; Add date, time and diary marking as required.
-               (concat (if (not (and diary (not todo-include-in-diary)))
-                           todo-nondiary-start
-                         (when (and nonmarking (not todo-diary-nonmarking))
-                           diary-nonmarking-symbol))
-                       date-string (when (and time-string ; Can be empty.
-                                              (not (zerop (length
-                                                           time-string))))
-                                     (concat " " time-string))
-                       (when (not (and diary (not todo-include-in-diary)))
-                         todo-nondiary-end)
-                       " " new-item))
-         ;; Indent newlines inserted by C-q C-j if nonspace char follows.
-         (setq new-item (replace-regexp-in-string "\\(\n\\)[^[:blank:]]"
-                                                  "\n\t" new-item nil nil 1))
+         (unless copy
+           (setq new-item
+                 ;; Add date, time and diary marking as required.
+                 (concat (if (not (and diary-type
+                                       (not todo-include-in-diary)))
+                             todo-nondiary-start
+                           (when (and (eq diary-type 'nonmarking)
+                                      (not todo-diary-nonmarking))
+                             diary-nonmarking-symbol))
+                         date-string (when (and time-string ; Can be empty.
+                                                (not (zerop (length
+                                                             time-string))))
+                                       (concat " " time-string))
+                         (when (not (and diary-type
+                                         (not todo-include-in-diary)))
+                           todo-nondiary-end)
+                         " " new-item))
+           ;; Indent newlines inserted by C-q C-j if nonspace char follows.
+           (setq new-item (replace-regexp-in-string "\\(\n\\)[^[:blank:]]"
+                                                    "\n\t" new-item nil nil 1)))
          (unwind-protect
              (progn
                ;; Make sure the correct category is selected.  There
@@ -1842,13 +1950,12 @@ the new item:
            ;; 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
@@ -1860,7 +1967,8 @@ the new item:
            ;; items are displayed in the window.
            (when item-added (recenter)))
          (todo-update-count 'todo 1)
-         (if (or diary todo-include-in-diary) (todo-update-count 'diary 1))
+         (when (or diary-item diary-type todo-include-in-diary)
+           (todo-update-count 'diary 1))
          (todo-update-categories-sexp))))))
 
 (defun todo-set-date-from-calendar ()
@@ -1904,21 +2012,10 @@ prompt for a todo file and then for a category in it."
   (setq todo-date-from-calendar
        (calendar-date-string (calendar-cursor-to-date t) t t))
   (calendar-exit)
-  (todo-basic-insert-item arg nil nil todo-date-from-calendar))
+  (todo-insert-item--basic arg nil todo-date-from-calendar))
 
 (define-key calendar-mode-map "it" 'todo-insert-item-from-calendar)
 
-(defun todo-copy-item ()
-  "Copy item at point and insert the copy as a new item."
-  (interactive)
-  (unless (or (todo-done-item-p) (looking-at "^$"))
-    (let ((copy (todo-item-string))
-         (diary-item (todo-diary-item-p)))
-      (todo-set-item-priority copy (todo-current-category) t)
-      (todo-update-count 'todo 1)
-      (when diary-item (todo-update-count 'diary 1))
-      (todo-update-categories-sexp))))
-
 (defun todo-delete-item ()
   "Delete at least one item in this category.
 If there are marked items, delete all of these; otherwise, delete
@@ -1965,64 +2062,107 @@ the item at point."
            (todo-prefix-overlays)))
       (if ov (delete-overlay ov)))))
 
-(defun todo-edit-item (&optional arg)
-  "Edit the todo item at point.
-With non-nil prefix argument ARG, include the item's date/time
-header, making it also editable; otherwise, include only the item
-content.
+(defvar todo-edit-item--param-key-alist)
+(defvar todo-edit-done-item--param-key-alist)
 
-If the item consists of only one logical line, edit it in the
-minibuffer; otherwise, edit it in Todo Edit mode."
+(defun todo-edit-item (&optional arg)
+  "Choose an editing operation for the current item and carry it out."
   (interactive "P")
-  (when (todo-item-string)
-    (let* ((opoint (point))
-          (start (todo-item-start))
-          (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))))
-          (header (substring (todo-item-string) 0 item-beg))
-          (item (if arg (todo-item-string)
-                  (substring (todo-item-string) item-beg)))
-          (multiline (> (length (split-string item "\n")) 1))
-          (buffer-read-only nil))
-      (if multiline
-         (todo-edit-multiline-item)
-       (let ((new (concat (if arg "" header)
-                          (read-string "Edit: " (if arg
-                                                    (cons item item-beg)
-                                                  (cons item 0))))))
-         (when arg
-           (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-multiline-item ()
-  "Edit current todo item in Todo Edit mode.
-Use of newlines invokes `todo-indent' to insure compliance with
-the format of Diary entries."
-  (interactive)
-  (when (todo-item-string)
-    (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"))))))
+  (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 ((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.
@@ -2075,38 +2215,18 @@ made in the number or names of categories."
        (todo-category-select)
        (goto-char (point-min))))))
 
-(defun todo-basic-edit-item-header (what &optional inc)
-  "Function underlying commands to edit item date/time header.
-
-The argument WHAT (passed by invoking commands) specifies what
-part of the header to edit; possible values are these symbols:
-`date', to edit the year, month, and day of the date string;
-`time', to edit just the time string; `calendar', to select the
-date from the Calendar; `today', to set the date to today's date;
-`dayname', to set the date string to the name of a day or to
-change the day name; and `year', `month' or `day', to edit only
-these respective parts of the date string (`day' is the number of
-the given day of the month, and `month' is either the name of the
-given month or its number, depending on the value of
-`calendar-date-display-form').
-
-The optional argument INC is a positive or negative integer
-\(passed by invoking commands as a numerical prefix argument)
-that in conjunction with the WHAT values `year', `month' or
-`day', increments or decrements the specified date string
-component by the specified number of suitable units, i.e., years,
-months, or days, with automatic adjustment of the other date
-string components as necessary.
-
-If there are marked items, apply the same edit to all of these;
-otherwise, edit just the item at point."
-  (let* ((cat (todo-current-category))
-        (marked (assoc cat todo-categories-with-marks))
-        (first t)
-        (todo-date-from-calendar t)
-        (buffer-read-only nil)
-        ndate ntime year monthname month day
-        dayname)       ; Needed by calendar-date-display-form.
+(defun todo-edit-item--header (what &optional inc)
+  "Function providing header editing facilities of `todo-edit-item'."
+  (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
@@ -2222,7 +2342,8 @@ otherwise, edit just the item at point."
                           ((or (string= omonth "*") (string= omonthname "*"))
                            (setq dd (+ dd inc))
                            (if (> dd 31)
-                               (user-error "A month cannot have more than 31 days")
+                               (user-error
+                                "A month cannot have more than 31 days")
                              (number-to-string dd)))
                           ;; Increment or decrement day by INC,
                           ;; adjusting month and year if necessary
@@ -2264,80 +2385,31 @@ otherwise, edit just the item at point."
              (todo-forward-item)
            (goto-char (point-max))))))))
 
-(defun todo-edit-item-header ()
-  "Interactively edit at least the date of item's date/time header.
-If user option `todo-always-add-time-string' is non-nil, also
-edit item's time string."
-  (interactive)
-  (todo-basic-edit-item-header 'date)
-  (when todo-always-add-time-string
-    (todo-edit-item-time)))
-
-(defun todo-edit-item-time ()
-  "Interactively edit the time string of item's date/time header."
-  (interactive)
-  (todo-basic-edit-item-header 'time))
-
-(defun todo-edit-item-date-from-calendar ()
-  "Interactively edit item's date using the Calendar."
-  (interactive)
-  (todo-basic-edit-item-header 'calendar))
-
-(defun todo-edit-item-date-to-today ()
-  "Set item's date to today's date."
-  (interactive)
-  (todo-basic-edit-item-header 'today))
-
-(defun todo-edit-item-date-day-name ()
-  "Replace item's date with the name of a day of the week."
-  (interactive)
-  (todo-basic-edit-item-header 'dayname))
-
-(defun todo-edit-item-date-year (&optional inc)
-  "Interactively edit the year of item's date string.
-With prefix argument INC a positive or negative integer,
-increment or decrement the year by INC."
-  (interactive "p")
-  (todo-basic-edit-item-header 'year inc))
-
-(defun todo-edit-item-date-month (&optional inc)
-  "Interactively edit the month of item's date string.
-With prefix argument INC a positive or negative integer,
-increment or decrement the month by INC."
-  (interactive "p")
-  (todo-basic-edit-item-header 'month inc))
-
-(defun todo-edit-item-date-day (&optional inc)
-  "Interactively edit the day of the month of item's date string.
-With prefix argument INC a positive or negative integer,
-increment or decrement the day by INC."
-  (interactive "p")
-  (todo-basic-edit-item-header 'day inc))
-
-(defun todo-edit-item-diary-inclusion ()
-  "Change diary status of one or more todo items in this category.
-That is, insert `todo-nondiary-marker' if the candidate items
-lack this marking; otherwise, remove it.
-
-If there are marked todo items, change the diary status of all
-and only these, otherwise change the diary status of the item at
-point."
-  (interactive)
+(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)))))
+         (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
                      (replace-match "")
@@ -2345,13 +2417,16 @@ point."
                      (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))))))
+         (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.
@@ -2374,6 +2449,9 @@ items."
                              (todo-date-string-matcher lim)))))
              (if arg
                  (unless (looking-at (regexp-quote todo-nondiary-start))
+                   (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))
@@ -2388,33 +2466,6 @@ items."
                                    (- todo-count diary-count))))
        (todo-update-categories-sexp)))))
 
-(defun todo-edit-item-diary-nonmarking ()
-  "Change non-marking of one or more diary items in this category.
-That is, insert `diary-nonmarking-symbol' if the candidate items
-lack this marking; otherwise, remove it.
-
-If there are marked todo items, change the non-marking status of
-all and only these, otherwise change the non-marking status of
-the item at point."
-  (interactive)
-  (let ((buffer-read-only)
-       (marked (assoc (todo-current-category)
-                      todo-categories-with-marks)))
-    (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)))
-             (todo-item-start)
-             (unless (looking-at (regexp-quote todo-nondiary-start))
-               (if (looking-at (regexp-quote diary-nonmarking-symbol))
-                   (replace-match "")
-                 (insert diary-nonmarking-symbol))))
-           (unless marked (throw 'stop nil))
-           (todo-forward-item)))))))
-
 (defun todo-edit-category-diary-nonmarking (arg)
   "Add `diary-nonmarking-symbol' to all diary items in this category.
 With prefix ARG, remove `diary-nonmarking-symbol' from all diary
@@ -2424,16 +2475,16 @@ items in this category."
     (goto-char (point-min))
     (let (buffer-read-only)
       (catch 'stop
-      (while (not (eobp))
-       (if (todo-done-item-p)          ; We've gone too far.
-           (throw 'stop nil)
-         (unless (looking-at (regexp-quote todo-nondiary-start))
-           (if arg
-               (when (looking-at (regexp-quote diary-nonmarking-symbol))
-                 (replace-match ""))
-             (unless (looking-at (regexp-quote diary-nonmarking-symbol))
-               (insert diary-nonmarking-symbol))))
-       (todo-forward-item)))))))
+       (while (not (eobp))
+         (if (todo-done-item-p)                ; We've gone too far.
+             (throw 'stop nil)
+           (unless (looking-at (regexp-quote todo-nondiary-start))
+             (if arg
+                 (when (looking-at (regexp-quote diary-nonmarking-symbol))
+                   (replace-match ""))
+               (unless (looking-at (regexp-quote diary-nonmarking-symbol))
+                 (insert diary-nonmarking-symbol))))
+           (todo-forward-item)))))))
 
 (defun todo-set-item-priority (&optional item cat new arg)
   "Prompt for and set ITEM's priority in CATegory.
@@ -2503,14 +2554,16 @@ 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)
          (while (not priority)
-           (setq candidate (read-number prompt))
+           (setq candidate (read-number prompt
+                                        (if (eq todo-default-priority 'first)
+                                            1 maxnum)))
            (setq prompt (when (or (< candidate 1) (> candidate maxnum))
                           (format "Priority must be an integer between 1 and %d.\n"
                                   maxnum)))
@@ -2744,21 +2797,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,32 +2857,6 @@ visible."
          ;; When done items are shown, put cursor on first just done item.
          (when opoint (goto-char opoint)))))))
 
-(defun todo-edit-done-item-comment (&optional arg)
-  "Add a comment to this done item or edit an existing comment.
-With prefix ARG delete an existing comment."
-  (interactive "P")
-  (when (todo-done-item-p)
-    (let ((item (todo-item-string))
-         (opoint (point))
-         (end (save-excursion (todo-item-end)))
-         comment buffer-read-only)
-      (save-excursion
-       (todo-item-start)
-       (if (re-search-forward (concat " \\["
-                                      (regexp-quote todo-comment-string)
-                                      ": \\([^]]+\\)\\]") end t)
-           (if arg
-               (when (todo-y-or-n-p "Delete comment? ")
-                 (delete-region (match-beginning 0) (match-end 0)))
-             (setq comment (read-string "Edit comment: "
-                                        (cons (match-string 1) 1)))
-             (replace-match comment nil nil nil 1))
-         (setq comment (read-string "Enter a comment: "))
-         ;; If user moved point during editing, make sure it moves back.
-         (goto-char opoint)
-         (todo-item-end)
-         (insert " [" todo-comment-string ": " comment "]"))))))
-
 (defun todo-item-undone ()
   "Restore at least one done item to this category's todo section.
 Prompt for the new priority.  If there are marked items, undo all
@@ -2872,7 +2885,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))
@@ -2958,31 +2973,33 @@ first visit in a session displays the first category in the
 archive, subsequent visits return to the last category
 displayed."
   (interactive)
-  (let* ((cat (todo-current-category))
-        (count (todo-get-count 'archived cat))
-        (archive (concat (file-name-sans-extension todo-current-todo-file)
-                         ".toda"))
-        place)
-    (setq place (cond (ask 'other-archive)
-                     ((file-exists-p archive) 'this-archive)
-                     (t (when (todo-y-or-n-p
-                               (concat "This file has no archive; "
-                                       "visit another archive? "))
-                          'other-archive))))
-    (when (eq place 'other-archive)
-      (setq archive (todo-read-file-name "Choose a todo archive: " t t)))
-    (when (and (eq place 'this-archive) (zerop count))
-      (setq place (when (todo-y-or-n-p
-                         (concat "This category has no archived items;"
-                                 " visit archive anyway? "))
-                    'other-cat)))
-    (when place
-      (set-window-buffer (selected-window)
-                        (set-buffer (find-file-noselect archive)))
-      (if (member place '(other-archive other-cat))
-         (setq todo-category-number 1)
-       (todo-category-number cat))
-      (todo-category-select))))
+  (if (null (funcall todo-files-function t))
+      (message "There are no archive files")
+    (let* ((cat (todo-current-category))
+          (count (todo-get-count 'archived cat))
+          (archive (concat (file-name-sans-extension todo-current-todo-file)
+                           ".toda"))
+          (place (cond (ask 'other-archive)
+                       ((file-exists-p archive) 'this-archive)
+                       (t (when (todo-y-or-n-p
+                                 (concat "This file has no archive; "
+                                         "visit another archive? "))
+                            'other-archive)))))
+      (when (eq place 'other-archive)
+       (setq archive (todo-read-file-name "Choose a todo archive: " t t)))
+      (when (and (eq place 'this-archive) (zerop count))
+       (setq place (when (todo-y-or-n-p
+                           (concat "This category has no archived items;"
+                                   " visit archive anyway? "))
+                      'other-cat)))
+      (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))
+       (todo-category-select)))))
 
 (defun todo-choose-archive ()
   "Choose an archive and visit it."
@@ -3010,10 +3027,8 @@ this category does not exist in the archive, it is created."
               (marked (assoc cat todo-categories-with-marks))
               (afile (concat (file-name-sans-extension
                               todo-current-todo-file) ".toda"))
-              (archive (if (file-exists-p afile)
-                           (find-file-noselect afile t)
-                         (get-buffer-create afile)))
-              (item (and (todo-done-item-p)
+              (archive (find-file-noselect afile t))
+              (item (and (not marked) (todo-done-item-p)
                          (concat (todo-item-string) "\n")))
               (count 0)
               (opoint (unless (todo-done-item-p) (point)))
@@ -3056,7 +3071,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 buffer-file-name (erase-buffer))
+             (unless (derived-mode-p 'todo-archive-mode) (todo-archive-mode))
              (let (buffer-read-only)
                (widen)
                (goto-char (point-min))
@@ -3076,12 +3091,14 @@ this category does not exist in the archive, it is created."
                              (item)))
                (todo-update-count 'done (if (or marked all) count 1) cat)
                (todo-update-categories-sexp)
-               ;; If archive is new, save to file now (using write-region in
-               ;; order not to get prompted for file to save to), to let
-               ;; auto-mode-alist take effect below.
-               (unless buffer-file-name
-                 (write-region nil nil afile)
-                 (kill-buffer))))
+               ;; If archive is new, save to file now (with
+               ;; write-region to avoid prompt for file to save to)
+               ;; 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))
+                 (todo-archive-mode))))
            (with-current-buffer tbuf
              (cond
               (all
@@ -3107,7 +3124,8 @@ this category does not exist in the archive, it is created."
                          (todo-update-count 'done -1)
                          (todo-update-count 'archived 1)
                          ;; Don't leave point below last item.
-                         (and item (bolp) (eolp) (< (point-min) (point-max))
+                         (and (or marked item) (bolp) (eolp)
+                              (< (point-min) (point-max))
                               (todo-backward-item))
                          (when item
                            (throw 'done (setq item nil))))
@@ -3197,15 +3215,16 @@ the only category in the archive, the archive file is deleted."
                  (throw 'done (setq item nil))))
            (todo-forward-item))))
       (todo-update-count 'done (if marked (- marked-count) -1) cat)
-      ;; If that was the last category in the archive, delete the whole file.
-      (if (= (length todo-categories) 1)
-         (progn
-           (delete-file todo-current-todo-file)
-           ;; Kill the archive buffer silently.
-           (set-buffer-modified-p nil)
-           (kill-buffer))
-       ;; Otherwise, if the archive category is now empty, delete it.
-       (when (eq (point-min) (point-max))
+      ;; If we unarchived the last item in category, then if that was
+      ;; the only category, delete the whole file, otherwise, just
+      ;; delete the category.
+      (when (= 0 (todo-get-count 'done))
+       (if (= 1 (length todo-categories))
+           (progn
+             (delete-file todo-current-todo-file)
+             ;; Kill the archive buffer silently.
+             (set-buffer-modified-p nil)
+             (kill-buffer))
          (widen)
          (let ((beg (re-search-backward
                      (concat "^" (regexp-quote todo-category-beg) cat "$")
@@ -3218,8 +3237,8 @@ the only category in the archive, the archive file is deleted."
            (remove-overlays beg end)
            (delete-region beg end)
            (setq todo-categories (delete (assoc cat todo-categories)
-                                          todo-categories))
-           (todo-update-categories-sexp))))
+                                         todo-categories)))))
+      (todo-update-categories-sexp)
       ;; Visit category in todo file and show restored done items.
       (let ((tfile (buffer-file-name tbuf))
            (todo-show-with-done t))
@@ -3286,19 +3305,24 @@ categories display according to priority."
 (defun todo-show-categories-table ()
   "Display a table of the current file's categories and item counts.
 
-In the initial display the categories are numbered, indicating
-their current order for navigating by \\[todo-forward-category]
-and \\[todo-backward-category].  You can persistantly change the
-order of the category at point by typing
-\\[todo-set-category-number], \\[todo-raise-category] or
-\\[todo-lower-category].
+In the initial display the lines of the table are numbered,
+indicating the current order of the categories when sequentially
+navigating through the todo file with `\\[todo-forward-category]'
+and `\\[todo-backward-category]'.  You can reorder the lines, and
+hence the category sequence, by typing `\\[todo-raise-category]'
+or `\\[todo-lower-category]' to raise or lower the category at
+point, or by typing `\\[todo-set-category-number]' and entering a
+number at the prompt or by typing `\\[todo-set-category-number]'
+with a numeric prefix.  If you save the todo file after
+reordering the categories, the new order persists in subsequent
+Emacs sessions.
 
 The labels above the category names and item counts are buttons,
 and clicking these changes the display: sorted by category name
 or by the respective item counts (alternately descending or
 ascending).  In these displays the categories are not numbered
-and \\[todo-set-category-number], \\[todo-raise-category] and
-\\[todo-lower-category] are disabled.  (Programmatically, the
+and `\\[todo-set-category-number]', `\\[todo-raise-category]' and
+`\\[todo-lower-category]' are disabled.  (Programmatically, the
 sorting is triggered by passing a non-nil SORTKEY argument.)
 
 In addition, the lines with the category names and item counts
@@ -3935,7 +3959,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."
@@ -4019,15 +4046,15 @@ regexp items."
   "Buffer type string for `todo-filter-items'.")
 
 (defun todo-filter-items (filter &optional new multifile)
-  "Display a cross-categorial list of items filtered by FILTER.
+  "Display a list of items filtered by FILTER.
 The values of FILTER can be `top' for top priority items, a cons
 of `top' and a number passed by the caller, `diary' for diary
-items, or `regexp' for items matching a regular expresion entered
-by the user.  The items can be from any categories in the current
-todo file or, with non-nil MULTIFILE, from several files.  If NEW
-is nil, visit an appropriate file containing the list of filtered
-items; if there is no such file, or with non-nil NEW, build the
-list and display it.
+items, or `regexp' for items matching a regular expression
+entered by the user.  The items can come from any categories in
+the current todo file or, with non-nil MULTIFILE, from several
+files.  If NEW is nil, visit an appropriate file containing the
+list of filtered items; if there is no such file, or with non-nil
+NEW, build the list and display it.
 
 See the documentation strings of the commands
 `todo-filter-top-priorities', `todo-filter-diary-items',
@@ -4044,7 +4071,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)
@@ -4053,9 +4079,11 @@ 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)))
+        (file-exists (or (file-exists-p fname) rxfiles))
+        bufname)
     (cond ((and top new (natnump new))
           (todo-filter-items-1 (cons 'top new) flist))
          ((and (not new) file-exists)
@@ -4065,14 +4093,21 @@ 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
           (todo-filter-items-1 filter flist)))
-    (setq fname (replace-regexp-in-string "-" ", "
-                                         (todo-short-file-name fname)))
+    (dolist (s (split-string (todo-short-file-name fname) "-"))
+      (setq bufname (if bufname
+                       (concat bufname (if (member s (mapcar
+                                                      'todo-short-file-name
+                                                      todo-files))
+                                           ", " "-") s)
+                     s)))
     (rename-buffer (format (concat "%s for file" (if multi "s" "")
-                                  " \"%s\"") buf fname))))
+                                  " \"%s\"") buf bufname))))
 
 (defun todo-filter-items-1 (filter file-list)
   "Build a list of items by applying FILTER to FILE-LIST.
@@ -4248,30 +4283,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.
@@ -4313,6 +4349,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))
@@ -4699,14 +4738,57 @@ short todo archive or top priorities file name, respectively."
                   ((eq type 'regexp) ".todr")
                   (t ".todo"))))))
 
+(defun todo-check-file (file)
+  "Check the state associated with FILE and update it if necessary.
+If FILE exists, return t.  If it does not exist and there is no
+live buffer with its content, return nil; if there is such a
+buffer and the user tries to show it, ask whether to restore
+FILE, and if confirmed, do so and return t; else delete the
+buffer, clean up the state and return nil."
+  (setq todo-files (funcall todo-files-function))
+  (setq todo-archives (funcall todo-files-function t))
+  (if (file-exists-p file)
+      t
+    (setq todo-visited (delete file todo-visited))
+    (let ((buf (find-buffer-visiting file)))
+      (if (and buf
+              (y-or-n-p
+               (concat
+                (format (concat "Todo file \"%s\" has been deleted but "
+                                "its content is still in a buffer!\n")
+                        (todo-short-file-name file))
+                "Save that buffer and restore the todo file? ")))
+         (progn
+           (with-current-buffer buf (save-buffer))
+           (setq todo-files (funcall todo-files-function))
+           (setq todo-archives (funcall todo-files-function t))
+           t)
+       (let* ((files (append todo-files todo-archives))
+              (tctf todo-current-todo-file)
+              (tgctf todo-global-current-todo-file)
+              (tdtf (todo-absolute-file-name todo-default-todo-file)))
+         (unless (or (not todo-current-todo-file)
+                     (member todo-current-todo-file files))
+           (setq todo-current-todo-file nil))
+         (unless (or (not todo-global-current-todo-file)
+                     (member todo-global-current-todo-file files))
+           (setq todo-global-current-todo-file nil))
+         (unless (or (not todo-default-todo-file)
+                     (member todo-default-todo-file files))
+           (setq todo-default-todo-file (todo-short-file-name
+                                         (car todo-files))))
+         (todo-reevaluate-filelist-defcustoms)
+         (when buf (kill-buffer buf))
+         nil)))))
+
 (defun todo-category-number (cat)
   "Return the number of category CAT in this todo file.
 The buffer-local variable `todo-category-number' holds this
 number as its value."
   (let ((categories (mapcar 'car todo-categories)))
     (setq todo-category-number
-         ;; Increment by one, so that the highest priority category in Todo
-         ;; Categories mode is numbered one rather than zero.
+         ;; Increment by one, so that the number of the first
+         ;; category is one rather than zero.
          (1+ (- (length categories)
                 (length (member cat categories)))))))
 
@@ -4846,23 +4928,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))
@@ -5069,7 +5156,7 @@ empty line above the done items separator."
       (not (looking-at (regexp-quote todo-nondiary-start))))))
 
 ;; This duplicates the item locating code from diary-goto-entry, but
-;; without the marker code, to test whether the latter is dispensible.
+;; without the marker code, to test whether the latter is dispensable.
 ;; If it is, diary-goto-entry can be simplified.  The code duplication
 ;; here can also be eliminated, leaving only the widening and category
 ;; selection, and instead of :override advice :around can be used.
@@ -5086,6 +5173,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)
@@ -5102,6 +5194,31 @@ 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)))))
+
+(declare-function desktop-restore-file-buffer "desktop"
+                  (buffer-filename buffer-name buffer-misc))
+
+(defun todo-restore-desktop-buffer (file buffer misc)
+  (desktop-restore-file-buffer file buffer misc)
+  (with-current-buffer buffer
+    (widen)
+    (let ((todo-category-number (cdr (assq 'catnum misc))))
+      (todo-category-select))))
+
+(add-to-list 'desktop-buffer-mode-handlers
+            '(todo-mode . todo-restore-desktop-buffer))
+
 (defun todo-done-item-p ()
   "Return non-nil if item at point is a done item."
   (save-excursion
@@ -5116,6 +5233,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
@@ -5187,6 +5323,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
@@ -5229,134 +5367,223 @@ of each other."
        (forward-line)))))
 
 ;; -----------------------------------------------------------------------------
-;;; Utilities for generating item insertion commands and key bindings
+;;; Generating and applying item insertion and editing key sequences
 ;; -----------------------------------------------------------------------------
 
-;; Wolfgang Jenkner posted this powerset definition to emacs-devel
-;; (http://lists.gnu.org/archive/html/emacs-devel/2013-06/msg00423.html)
-;; and kindly gave me permission to use it.
-
-(defun todo-powerset (list)
-  "Return the powerset of LIST."
-  (let ((powerset (list nil)))
-    (dolist (elt list (mapcar 'reverse powerset))
-      (nconc powerset (mapcar (apply-partially 'cons elt) powerset)))))
-
-(defun todo-gen-arglists (arglist)
-  "Return list of lists of non-nil atoms produced from ARGLIST.
-The elements of ARGLIST may be atoms or lists."
-  (let (arglists)
-    (while arglist
-      (let ((arg (pop arglist)))
-       (cond ((symbolp arg)
-              (setq arglists (if arglists
-                                 (mapcar (lambda (l) (push arg l)) arglists)
-                               (list (push arg arglists)))))
-             ((listp arg)
-              (setq arglists
-                    (mapcar (lambda (a)
-                              (if (= 1 (length arglists))
-                                  (apply (lambda (l) (push a l)) arglists)
-                                (mapcar (lambda (l) (push a l)) arglists)))
-                            arg))))))
-    (setq arglists (mapcar 'reverse (apply 'append (mapc 'car arglists))))))
-
-(defvar todo-insertion-commands-args-genlist
-  '(diary nonmarking (calendar date dayname) time (here region))
-  "Generator list for argument lists of item insertion commands.")
-
-(defvar todo-insertion-commands-args
-  (let ((argslist (todo-gen-arglists todo-insertion-commands-args-genlist))
-       res new)
-    (setq res (cl-remove-duplicates
-              (apply 'append (mapcar 'todo-powerset argslist)) :test 'equal))
-    (dolist (l res)
-      (unless (= 5 (length l))
-       (let ((v (make-vector 5 nil)) elt)
-         (while l
-           (setq elt (pop l))
-           (cond ((eq elt 'diary)
-                  (aset v 0 elt))
-                 ((eq elt 'nonmarking)
-                  (aset v 1 elt))
-                 ((or (eq elt 'calendar)
-                      (eq elt 'date)
-                      (eq elt 'dayname))
-                  (aset v 2 elt))
-                 ((eq elt 'time)
-                  (aset v 3 elt))
-                 ((or (eq elt 'here)
-                      (eq elt 'region))
-                  (aset v 4 elt))))
-         (setq l (append v nil))))
-      (setq new (append new (list l))))
-    new)
-  "List of all argument lists for Todo mode item insertion commands.")
-
-(defun todo-insertion-command-name (arglist)
-  "Generate Todo mode item insertion command name from ARGLIST."
-  (replace-regexp-in-string
-   "-\\_>" ""
-   (replace-regexp-in-string
-    "-+" "-"
-    (concat "todo-insert-item-"
-           (mapconcat (lambda (e) (if e (symbol-name e))) arglist "-")))))
-
-(defvar todo-insertion-commands-names
-  (mapcar (lambda (l)
-          (todo-insertion-command-name l))
-         todo-insertion-commands-args)
-  "List of names of Todo mode item insertion commands.")
-
-(defmacro todo-define-insertion-command (&rest args)
-  "Generate Todo mode item insertion command definitions from ARGS."
-  (let ((name (intern (todo-insertion-command-name args)))
-       (arg0 (nth 0 args))
-       (arg1 (nth 1 args))
-       (arg2 (nth 2 args))
-       (arg3 (nth 3 args))
-       (arg4 (nth 4 args)))
-    `(defun ,name (&optional arg &rest args)
-       "Todo mode item insertion command generated from ARGS.
-For descriptions of the individual arguments, their values, and
-their relation to key bindings, see `todo-basic-insert-item'."
-       (interactive (list current-prefix-arg))
-       (todo-basic-insert-item arg ',arg0 ',arg1 ',arg2 ',arg3 ',arg4))))
-
-(defvar todo-insertion-commands
-  (mapcar (lambda (c)
-           (eval `(todo-define-insertion-command ,@c)))
-         todo-insertion-commands-args)
-  "List of Todo mode item insertion commands.")
-
-(defvar todo-insertion-commands-arg-key-list
-  '(("diary" "y" "yy")
-    ("nonmarking" "k" "kk")
-    ("calendar" "c" "cc")
-    ("date" "d" "dd")
-    ("dayname" "n" "nn")
-    ("time" "t" "tt")
-    ("here" "h" "h")
-    ("region" "r" "r"))
-  "List of mappings of item insertion command arguments to key sequences.")
-
-(defun todo-insertion-key-bindings (map)
-  "Generate key binding definitions for item insertion keymap MAP."
-  (dolist (c todo-insertion-commands)
-    (let* ((key "")
-          (cname (symbol-name c)))
-      (mapc (lambda (l)
-             (let ((arg (nth 0 l))
-                   (key1 (nth 1 l))
-                   (key2 (nth 2 l)))
-               (if (string-match (concat (regexp-quote arg) "\\_>") cname)
-                   (setq key (concat key key2)))
-               (if (string-match (concat (regexp-quote arg) ".+") cname)
-                   (setq key (concat key key1)))))
-           todo-insertion-commands-arg-key-list)
-      (if (string-match (concat (regexp-quote "todo-insert-item") "\\_>") cname)
-         (setq key (concat key "i")))
-      (define-key map key c))))
+;; Thanks to Stefan Monnier for suggesting dynamically generating item
+;; insertion commands and their key bindings, and offering an elegant
+;; implementation, which, however, relies on lexical scoping and so
+;; cannot be used here until the Calendar code used by todo-mode.el is
+;; converted to lexical binding.  Hence, the following implementation
+;; uses dynamic binding.
+
+(defconst todo-insert-item--parameters
+  '((default copy) (diary nonmarking) (calendar date dayname) time (here region))
+  "List of all item insertion parameters.
+Passed by `todo-insert-item' to `todo-insert-item--next-param' to
+dynamically create item insertion commands.")
+
+(defconst todo-insert-item--param-key-alist
+  '((default    . "i")
+    (copy       . "p")
+    (diary      . "y")
+    (nonmarking . "k")
+    (calendar   . "c")
+    (date       . "d")
+    (dayname    . "n")
+    (time       . "t")
+    (here       . "h")
+    (region     . "r"))
+  "List pairing item insertion parameters with their completion keys.")
+
+(defsubst todo-insert-item--keyof (param)
+  "Return key paired with item insertion PARAM."
+  (cdr (assoc param todo-insert-item--param-key-alist)))
+
+(defun todo-insert-item--argsleft (key list)
+  "Return sublist of LIST whose first member corresponds to KEY."
+  (let (l sym)
+    (mapc (lambda (m)
+           (when (consp m)
+             (catch 'found1
+               (dolist (s m)
+                 (when (equal key (todo-insert-item--keyof s))
+                   (throw 'found1 (setq sym s))))))
+           (if sym
+               (progn
+                 (push sym l)
+                 (setq sym nil))
+             (push m l)))
+         list)
+    (setq list (reverse l)))
+  (memq (catch 'found2
+         (dolist (e todo-insert-item--param-key-alist)
+           (when (equal key (cdr e))
+             (throw 'found2 (car e)))))
+       list))
+
+(defsubst todo-insert-item--this-key () (char-to-string last-command-event))
+
+(defvar todo-insert-item--keys-so-far ""
+  "String of item insertion keys so far entered for this command.")
+
+(defvar todo-insert-item--args nil)
+(defvar todo-insert-item--argleft nil)
+(defvar todo-insert-item--argsleft nil)
+(defvar todo-insert-item--newargsleft nil)
+
+(defun todo-insert-item--apply-args ()
+  "Build list of arguments for item insertion and apply them.
+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'."
+  (let* ((arg (list (car todo-insert-item--args)))
+        (args (nconc (cdr todo-insert-item--args)
+                     (list (car (todo-insert-item--argsleft
+                                 (todo-insert-item--this-key)
+                                 todo-insert-item--argsleft)))))
+        (arglist (if (= 4 (length args))
+                     args
+                   (let ((v (make-vector 4 nil)) elt)
+                     (while args
+                       (setq elt (pop args))
+                       (cond ((memq elt '(diary nonmarking))
+                              (aset v 0 elt))
+                             ((memq elt '(calendar date dayname))
+                              (aset v 1 elt))
+                             ((eq elt 'time)
+                              (aset v 2 elt))
+                             ((memq elt '(copy here region))
+                              (aset v 3 elt))))
+                     (append v nil)))))
+    (apply #'todo-insert-item--basic (nconc arg arglist))))
+
+(defun todo-insert-item--next-param (last args argsleft)
+  "Build item insertion command from LAST, ARGS and ARGSLEFT and call it.
+Dynamically generate key bindings, prompting with the keys
+already entered and those still available."
+  (cl-assert argsleft)
+  (let* ((map (make-sparse-keymap))
+         (prompt nil)
+         (addprompt
+         (lambda (k name)
+           (setq prompt
+                 (concat prompt
+                         (format
+                          (concat
+                           (if (memq name '(default diary calendar here))
+                               " { " " ")
+                           "%s=>%s"
+                           (when (memq name '(copy nonmarking dayname region))
+                             " }"))
+                          (propertize k 'face 'todo-key-prompt)
+                          name))))))
+    (setq todo-insert-item--args args)
+    (setq todo-insert-item--argsleft argsleft)
+    (when last
+      (if (memq last '(default copy))
+         (progn
+           (setq todo-insert-item--argsleft nil)
+           (todo-insert-item--apply-args))
+       (let ((k (todo-insert-item--keyof last)))
+         (funcall addprompt k (make-symbol (concat (symbol-name last) ":GO!")))
+         (define-key map (todo-insert-item--keyof last)
+           (lambda () (interactive)
+             (todo-insert-item--apply-args))))))
+    (while todo-insert-item--argsleft
+      (let ((x (car todo-insert-item--argsleft)))
+       (setq todo-insert-item--newargsleft (cdr todo-insert-item--argsleft))
+        (dolist (argleft (if (consp x) x (list x)))
+         (let ((k (todo-insert-item--keyof argleft)))
+           (funcall addprompt k argleft)
+           (define-key map k
+             (if (null todo-insert-item--newargsleft)
+                 (lambda () (interactive)
+                   (todo-insert-item--apply-args))
+               (lambda () (interactive)
+                 (setq todo-insert-item--keys-so-far
+                       (concat todo-insert-item--keys-so-far " "
+                               (todo-insert-item--this-key)))
+                 (todo-insert-item--next-param
+                  (car (todo-insert-item--argsleft
+                        (todo-insert-item--this-key)
+                        todo-insert-item--argsleft))
+                  (nconc todo-insert-item--args
+                         (list (car (todo-insert-item--argsleft
+                                     (todo-insert-item--this-key)
+                                     todo-insert-item--argsleft))))
+                  (cdr (todo-insert-item--argsleft
+                        (todo-insert-item--this-key)
+                        todo-insert-item--argsleft)))))))))
+      (setq todo-insert-item--argsleft todo-insert-item--newargsleft))
+    (when prompt (message "Press a key (so far `%s'): %s"
+                         todo-insert-item--keys-so-far prompt))
+    (set-transient-map map)
+    (setq todo-insert-item--argsleft argsleft)))
+
+(defconst todo-edit-item--param-key-alist
+  '((edit       . "e")
+    (header     . "h")
+    (multiline  . "m")
+    (diary      . "y")
+    (nonmarking . "k")
+    (date       . "d")
+    (time       . "t"))
+  "Alist of item editing parameters and their keys.")
+
+(defconst todo-edit-item--date-param-key-alist
+  '((full       . "f")
+    (calendar   . "c")
+    (today      . "a")
+    (dayname    . "n")
+    (year       . "y")
+    (month      . "m")
+    (daynum     . "d"))
+  "Alist of item date editing parameters and their keys.")
+
+(defconst todo-edit-done-item--param-key-alist
+  '((add/edit   . "c")
+    (delete     . "d"))
+  "Alist of done item comment editing parameters and their keys.")
+
+(defvar        todo-edit-item--prompt "Press a key (so far `e'): ")
+
+(defun todo-edit-item--next-key (params &optional arg)
+  (let* ((map (make-sparse-keymap))
+        (p->k (mapconcat (lambda (elt)
+                           (format "%s=>%s"
+                                   (propertize (cdr elt) 'face
+                                               'todo-key-prompt)
+                                   (concat (symbol-name (car elt))
+                                           (when (memq (car elt)
+                                                       '(add/edit delete))
+                                             " comment"))))
+                         params " "))
+        (this-key (let ((key (read-key (concat todo-edit-item--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))
+      (`header (todo-edit-item--text 'include-header))
+      (`multiline (todo-edit-item--text 'multiline))
+      (`add/edit (todo-edit-item--text 'comment-edit))
+      (`delete (todo-edit-item--text 'comment-delete))
+      (`diary (todo-edit-item--diary-inclusion))
+      (`nonmarking (todo-edit-item--diary-inclusion 'nonmarking))
+      (`date (let ((todo-edit-item--prompt "Press a key (so far `e d'): "))
+              (todo-edit-item--next-key
+               todo-edit-item--date-param-key-alist arg)))
+      (`full (progn (todo-edit-item--header 'date)
+                   (when todo-always-add-time-string
+                     (todo-edit-item--header 'time))))
+      (`calendar (todo-edit-item--header 'calendar))
+      (`today (todo-edit-item--header 'today))
+      (`dayname (todo-edit-item--header 'dayname))
+      (`year (todo-edit-item--header 'year arg))
+      (`month (todo-edit-item--header 'month arg))
+      (`daynum (todo-edit-item--header 'day arg))
+      (`time (todo-edit-item--header 'time)))))
 
 ;; -----------------------------------------------------------------------------
 ;;; Todo minibuffer utilities
@@ -5384,7 +5611,27 @@ Each element of the list is a cons of a category name and the
 file or list of files (as short file names) it is in.  The files
 are either the current (or if there is none, the default) todo
 file plus the files listed in `todo-category-completions-files',
-or, with non-nil ARCHIVE, the current archive file."
+or, with non-nil ARCHIVE, the current archive file.
+
+Before calculating the completions, update the value of
+`todo-category-completions-files' in case any files named in it
+have been removed."
+  (let (deleted)
+    (dolist (f todo-category-completions-files)
+      (unless (file-exists-p (todo-absolute-file-name f))
+       (setq todo-category-completions-files
+             (delete f todo-category-completions-files))
+       (push f deleted)))
+    (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")
+                        " been deleted and removed from\n"
+                        "the list of category completion files")))
+      (todo-reevaluate-category-completions-files-defcustom)
+      (custom-set-default 'todo-category-completions-files
+                         (symbol-value 'todo-category-completions-files))
+      (sleep-for 1.5)))
   (let* ((curfile (or todo-current-todo-file
                      (and todo-show-current-file
                           todo-global-current-todo-file)
@@ -5400,6 +5647,9 @@ or, with non-nil ARCHIVE, the current archive file."
        (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.
@@ -5435,6 +5685,7 @@ MUSTMATCH the name of an existing file must be chosen;
 otherwise, a new file name is allowed."
   (let* ((completion-ignore-case todo-completion-ignore-case)
         (files (mapcar 'todo-short-file-name
+                       ;; (funcall todo-files-function archive)))
                        (if archive todo-archives todo-files)))
         (file (completing-read prompt files nil mustmatch nil nil
                                (if files
@@ -5451,7 +5702,7 @@ otherwise, a new file name is allowed."
                                  ""))))
     (unless (file-exists-p todo-directory)
       (make-directory todo-directory))
-    (unless mustmatch
+    (unless (or mustmatch (member file files))
       (setq file (todo-validate-name file 'file)))
     (setq file (file-truename (concat todo-directory file
                                      (if archive ".toda" ".todo"))))))
@@ -5484,6 +5735,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))
@@ -5524,12 +5776,12 @@ categories from `todo-category-completions-files'."
       ;; Default to the current file.
       (unless file0 (setq file0 todo-current-todo-file))
       ;; First validate only a name passed interactively from
-      ;; todo-add-category, which must be of a nonexisting category.
+      ;; todo-add-category, which must be of a nonexistent category.
       (unless (and (assoc cat categories) (not add))
        ;; Validate only against completion categories.
        (let ((todo-categories categories))
          (setq cat (todo-validate-name cat 'category)))
-       ;; When user enters a nonexisting category name by jumping or
+       ;; When user enters a nonexistent category name by jumping or
        ;; moving, confirm that it should be added, then validate.
        (unless add
          (if (todo-y-or-n-p (format "Add new category \"%s\" to file \"%s\"? "
@@ -5677,7 +5929,7 @@ number of the last the day of the month."
     (completing-read "Enter a day name: "
                     (append calendar-day-name-array nil)
                     nil t)))
-  
+
 (defun todo-read-time ()
   "Prompt for and return a valid clock time as a string.
 
@@ -5750,8 +6002,9 @@ the empty string (i.e., no time string)."
 
 (defun todo-reset-nondiary-marker (symbol value)
   "The :set function for user option `todo-nondiary-marker'."
-  (let ((oldvalue (symbol-value symbol))
-       (files (append todo-files todo-archives)))
+  (let* ((oldvalue (symbol-value symbol))
+        (files (append todo-files todo-archives
+                       (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)
@@ -5762,23 +6015,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'."
@@ -5798,51 +6056,63 @@ the empty string (i.e., no time string)."
 (defun todo-reset-done-string (symbol value)
   "The :set function for user option `todo-done-string'."
   (let ((oldvalue (symbol-value symbol))
-       (files (append todo-files todo-archives)))
+       (files (append todo-files todo-archives
+                      (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)))
+       (files (append todo-files todo-archives
+                      (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 `todo-toggle-item-highlighting'."
+  "The :set function for user option `todo-highlight-item'."
   (let ((oldvalue (symbol-value symbol))
-       (files (append todo-files todo-archives)))
+       (files (append todo-files todo-archives
+                      (directory-files todo-directory t "\.tod[rty]$" t))))
     (custom-set-default symbol value)
     (when (not (equal value oldvalue))
       (dolist (f files)
@@ -5867,13 +6137,24 @@ the empty string (i.e., no time string)."
 
 (defun todo-reevaluate-default-file-defcustom ()
   "Reevaluate defcustom of `todo-default-todo-file'.
-Called after adding or deleting a todo file."
-  (eval (defcustom todo-default-todo-file (car (funcall todo-files-function))
-         "Todo file visited by first session invocation of `todo-show'."
-         :type `(radio ,@(mapcar (lambda (f) (list 'const f))
-                                 (mapcar 'todo-short-file-name
-                                         (funcall todo-files-function))))
-         :group 'todo)))
+Called after adding or deleting a todo file.  If the value of
+`todo-default-todo-file' before calling this function was
+associated with an existing file, keep that value."
+  ;; (let ((curval todo-default-todo-file))
+    (eval
+     (defcustom todo-default-todo-file (todo-short-file-name
+                                       (car (funcall todo-files-function)))
+       "Todo file visited by first session invocation of `todo-show'."
+       :type (when todo-files
+              `(radio ,@(mapcar (lambda (f) (list 'const f))
+                                (mapcar 'todo-short-file-name
+                                        (funcall todo-files-function)))))
+       :group 'todo))
+    ;; (when (and curval (file-exists-p (todo-absolute-file-name curval)))
+    ;;   (custom-set-default 'todo-default-todo-file curval)
+    ;;   ;; (custom-reevaluate-setting 'todo-default-todo-file)
+    ;;   )))
+    )
 
 (defun todo-reevaluate-category-completions-files-defcustom ()
   "Reevaluate defcustom of `todo-category-completions-files'.
@@ -5999,13 +6280,6 @@ Filtered Items mode following todo (not done) items."
 ;;; Key binding
 ;; -----------------------------------------------------------------------------
 
-(defvar todo-insertion-map
-  (let ((map (make-keymap)))
-    (todo-insertion-key-bindings map)
-    (define-key map "p" 'todo-copy-item)
-    map)
-  "Keymap for Todo mode item insertion commands.")
-
 (defvar todo-key-bindings-t
   `(
     ("Af"           todo-find-archive)
@@ -6022,6 +6296,7 @@ Filtered Items mode following todo (not done) items."
     ("Cey"          todo-edit-category-diary-inclusion)
     ("Cek"          todo-edit-category-diary-nonmarking)
     ("Fa"           todo-add-file)
+    ("Fr"           todo-rename-file)
     ("Ff"           todo-find-filtered-items-file)
     ("FV"           todo-toggle-view-done-only)
     ("V"            todo-toggle-view-done-only)
@@ -6030,23 +6305,11 @@ Filtered Items mode following todo (not done) items."
     ("Fts"          todo-set-top-priorities-in-file)
     ("Fyy"          todo-filter-diary-items)
     ("Fym"          todo-filter-diary-items-multifile)
-    ("Frr"          todo-filter-regexp-items)
-    ("Frm"          todo-filter-regexp-items-multifile)
-    ("ee"           todo-edit-item)
-    ("em"           todo-edit-multiline-item)
-    ("edt"          todo-edit-item-header)
-    ("edc"          todo-edit-item-date-from-calendar)
-    ("eda"          todo-edit-item-date-to-today)
-    ("edn"          todo-edit-item-date-day-name)
-    ("edy"          todo-edit-item-date-year)
-    ("edm"          todo-edit-item-date-month)
-    ("edd"          todo-edit-item-date-day)
-    ("et"           todo-edit-item-time)
-    ("eyy"          todo-edit-item-diary-inclusion)
-    ("eyk"          todo-edit-item-diary-nonmarking)
-    ("ec"           todo-edit-done-item-comment)
+    ("Fxx"          todo-filter-regexp-items)
+    ("Fxm"          todo-filter-regexp-items-multifile)
+    ("e"            todo-edit-item)
     ("d"            todo-item-done)
-    ("i"            ,todo-insertion-map)
+    ("i"            todo-insert-item)
     ("k"            todo-delete-item)
     ("m"            todo-move-item)
     ("u"            todo-item-undone)
@@ -6060,6 +6323,7 @@ Filtered Items mode following todo (not done) items."
     ("Cu" todo-unmark-category)
     ("Fh" todo-toggle-item-header)
     ("h"  todo-toggle-item-header)
+    ("Fk" todo-delete-file)
     ("Fe" todo-edit-file)
     ("FH" todo-toggle-item-highlighting)
     ("H"  todo-toggle-item-highlighting)
@@ -6161,88 +6425,99 @@ Filtered Items mode following todo (not done) items."
     map)
   "Todo Filtered Items mode keymap.")
 
-;; FIXME: Is it worth having a menu and if so, which commands?
-;; (easy-menu-define
-;;   todo-menu todo-mode-map "Todo Menu"
-;;   '("Todo"
-;;     ("Navigation"
-;;      ["Next Item"            todo-forward-item t]
-;;      ["Previous Item"        todo-backward-item t]
-;;      "---"
-;;      ["Next Category"        todo-forward-category t]
-;;      ["Previous Category"    todo-backward-category t]
-;;      ["Jump to Category"     todo-jump-to-category t]
-;;      "---"
-;;      ["Search Todo File"    todo-search t]
-;;      ["Clear Highlighting on Search Matches" todo-category-done t])
-;;     ("Display"
-;;      ["List Current Categories" todo-show-categories-table t]
-;;      ;; ["List Categories Alphabetically" todo-display-categories-alphabetically t]
-;;      ["Turn Item Highlighting on/off" todo-toggle-item-highlighting t]
-;;      ["Turn Item Numbering on/off" todo-toggle-prefix-numbers t]
-;;      ["Turn Item Time Stamp on/off" todo-toggle-item-header t]
-;;      ["View/Hide Done Items" todo-toggle-view-done-items t]
-;;      "---"
-;;      ["View Diary Items" todo-filter-diary-items t]
-;;      ["View Top Priority Items" todo-filter-top-priorities t]
-;;      ["View Multifile Top Priority Items" todo-filter-top-priorities-multifile t]
-;;      "---"
-;;      ["Print Category"     todo-print-buffer t])
-;;     ("Editing"
-;;      ["Insert New Item"      todo-insert-item t]
-;;      ["Insert Item Here"     todo-insert-item-here t]
-;;      ("More Insertion Commands")
-;;      ["Edit Item"            todo-edit-item t]
-;;      ["Edit Multiline Item"  todo-edit-multiline-item t]
-;;      ["Edit Item Header"     todo-edit-item-header t]
-;;      ["Edit Item Date"       todo-edit-item-date t]
-;;      ["Edit Item Time"       todo-edit-item-time t]
-;;      "---"
-;;      ["Lower Item Priority"  todo-lower-item-priority t]
-;;      ["Raise Item Priority"  todo-raise-item-priority t]
-;;      ["Set Item Priority" todo-set-item-priority t]
-;;      ["Move (Recategorize) Item" todo-move-item t]
-;;      ["Delete Item"          todo-delete-item t]
-;;      ["Undo Done Item" todo-item-undone t]
-;;      ["Mark/Unmark Item for Diary" todo-toggle-item-diary-inclusion t]
-;;      ["Mark/Unmark Items for Diary" todo-edit-item-diary-inclusion t]
-;;      ["Mark & Hide Done Item" todo-item-done t]
-;;      ["Archive Done Items" todo-archive-category-done-items t]
-;;      "---"
-;;      ["Add New Todo File" todo-add-file t]
-;;      ["Add New Category" todo-add-category t]
-;;      ["Delete Current Category" todo-delete-category t]
-;;      ["Rename Current Category" todo-rename-category t]
-;;      "---"
-;;      ["Save Todo File"      todo-save t]
-;;      )
-;;     "---"
-;;     ["Quit"                 todo-quit t]
-;;     ))
+(easy-menu-define
+  todo-menu todo-mode-map "Todo Menu"
+  '("Todo"
+    ("Navigation"
+     ["Next Item"            todo-next-item t]
+     ["Previous Item"        todo-previous-item t]
+     "---"
+     ["Next Category"        todo-forward-category t]
+     ["Previous Category"    todo-backward-category t]
+     ["Jump to Another Category"     todo-jump-to-category t]
+     "---"
+     ["Visit Another Todo File"     todo-show t]
+     ["Visit Archive" todo-find-archive t]
+     ["Visit Filtered Items File" todo-find-filtered-items-file t]
+     )
+    ("Editing"
+     ["Insert New Item"      todo-insert-item t]
+     ["Edit Item"            todo-edit-item t]
+     ["Lower Item Priority"  todo-lower-item-priority t]
+     ["Raise Item Priority"  todo-raise-item-priority t]
+     ["Set Item Priority" todo-set-item-priority t]
+     ["Mark/Unmark Item" todo-toggle-mark-item t]
+     ["Move (Recategorize) Item" todo-move-item t]
+     ["Delete Item"          todo-delete-item t]
+     ["Mark and Bury Done Item" todo-item-done t]
+     ["Undo Done Item" todo-item-undone t]
+     ["Archive Done Item" todo-archive-done-item t]
+     "---"
+     ["Add New Category" todo-add-category t]
+     ["Rename Current Category" todo-rename-category t]
+     ["Delete Current Category" todo-delete-category t]
+     ["Move Current Category" todo-move-category t]
+     ["Merge Current Category" todo-merge-category t]
+     "---"
+     ["Add New Todo File" todo-add-file t]
+     ["Rename Todo File" todo-rename-file t]
+     ["Delete Todo File" todo-delete-file t]
+     ["Edit Todo File" todo-edit-file t]
+     )
+    ("Searching and Item Filtering"
+     ["Search Todo File" todo-search t]
+     ["Clear Match Highlighting" todo-clear-matches t]
+     "---"
+     ["Set Top Priorities in File" todo-set-top-priorities-in-file t]
+     ["Set Top Priorities in Category" todo-set-top-priorities-in-category t]
+     ["Filter Top Priorities" todo-filter-top-priorities t]
+     ["Filter Multifile Top Priorities" todo-filter-top-priorities-multifile t]
+     ["Filter Diary Items" todo-filter-diary-items t]
+     ["Filter Multifile Diary Items" todo-filter-diary-items-multifile t]
+     ["Filter Regexp" todo-filter-regexp-items t]
+     ["Filter Multifile Regexp" todo-filter-regexp-items-multifile t]
+     )
+    ("Display and Printing"
+     ["Show/Hide Done Items" todo-toggle-view-done-items t]
+     ["Show/Hide Done Items Only" todo-toggle-view-done-only t]
+     ["Show/Hide Item Highlighting" todo-toggle-item-highlighting t]
+     ["Show/Hide Item Numbering" todo-toggle-prefix-numbers t]
+     ["Show/Hide Item Header" todo-toggle-item-header t]
+     "---"
+     ["Display Table of Categories" todo-show-categories-table t]
+     "---"
+     ["Print Category" todo-print-buffer t]
+     ["Print Category to File" todo-print-buffer-to-file t]
+     )
+    "---"
+    ["Save Todo File" todo-save t]
+    ["Quit Todo Mode" todo-quit t]
+    ))
 
 ;; -----------------------------------------------------------------------------
-;;; Hook functions and mode definitions 
+;;; Hook functions and mode definitions
 ;; -----------------------------------------------------------------------------
 
 (defun todo-show-current-file ()
   "Visit current instead of default todo file with `todo-show'.
-This function is added to `pre-command-hook' when user option
+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."
-  (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'.
@@ -6265,7 +6540,7 @@ This function is added to `kill-buffer-hook' in Todo mode."
 
 (defun todo-reset-and-enable-done-separator ()
   "Show resized done items separator overlay after window change.
-Added to `window-configuration-change-hook' in `todo-mode'."
+Added to `window-configuration-change-hook' in Todo mode."
   (when (= 1 (length todo-done-separator-string))
     (let ((sep todo-done-separator))
       (setq todo-done-separator (todo-done-separator))
@@ -6274,6 +6549,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
@@ -6284,6 +6560,8 @@ 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))
   (when (boundp 'hl-line-range-function)
     (setq-local hl-line-range-function
                (lambda() (save-excursion
@@ -6295,36 +6573,40 @@ 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)
 
+;;;###autoload
 (define-derived-mode todo-mode special-mode "Todo"
   "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 "Type `M-x 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)
 
 ;; If todo-mode is parent, all todo-mode key bindings appear to be
 ;; available in todo-archive-mode (e.g. shown by C-h m).
+;;;###autoload
 (define-derived-mode todo-archive-mode special-mode "Todo-Arch"
   "Major mode for archived todo categories.
 
@@ -6373,6 +6655,7 @@ Added to `window-configuration-change-hook' in `todo-mode'."
 
 (put 'todo-filtered-items-mode 'mode-class 'special)
 
+;;;###autoload
 (define-derived-mode todo-filtered-items-mode special-mode "Todo-Fltr"
   "Mode for displaying and reprioritizing top priority Todo.
 
@@ -6380,10 +6663,6 @@ Added to `window-configuration-change-hook' in `todo-mode'."
   (todo-modes-set-1)
   (todo-modes-set-2))
 
-(add-to-list 'auto-mode-alist '("\\.todo\\'" . todo-mode))
-(add-to-list 'auto-mode-alist '("\\.toda\\'" . todo-archive-mode))
-(add-to-list 'auto-mode-alist '("\\.tod[tyr]\\'" . todo-filtered-items-mode))
-
 ;; -----------------------------------------------------------------------------
 (provide 'todo-mode)