merge trunk
[bpt/emacs.git] / lisp / calendar / todo-mode.el
index 15fce45..09cca20 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-2014 Free Software Foundation, Inc.
 
 ;; Author: Oliver Seidel <privat@os10000.net>
 ;;     Stephen Berman <stephen.berman@gmx.net>
 ;; 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.
+;; You can add new todo files, edit and delete them.  You can add new
+;; categories, rename and delete them, move categories to another file
+;; and merge the items of two categories.  You can also reorder the
+;; sequence of categories in a todo file for the purpose of
+;; navigation.  You can display summary tables of the categories in a
+;; file and the types of items they contain.  And you can compile
+;; lists of existing items from multiple categories in one or more
+;; todo files, which are filtered by various criteria.
 
 ;; To get started, load this package and type `M-x todo-show'.  This
 ;; will prompt you for the name of the first todo file, its first
@@ -169,12 +172,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 +330,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 +443,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
@@ -578,11 +581,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 +634,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 +672,124 @@ 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)
+                        (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))
+         (unless (todo-check-file file) (throw 'end nil))
+         (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 (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-basic-insert-item)))
+       (setq todo-show-first show-first)
+       (add-to-list 'todo-visited file)))))
 
 (defun todo-save ()
   "Save the current todo file."
@@ -814,8 +825,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 +911,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))
@@ -1027,7 +1045,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 +1087,9 @@ 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)))
     (with-current-buffer (get-buffer-create file)
       (erase-buffer)
       (write-region (point-min) (point-max) file nil 'nomessage nil t)
@@ -1087,15 +1104,110 @@ 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)
+      (when delete2
+       (when (file-exists-p file2) (delete-file file2))
+       (setq todo-visited (delete file2 todo-visited))
+       (and buf2 (kill-buffer buf2)))
+      (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
@@ -1190,9 +1302,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 +1365,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; "
@@ -1263,15 +1376,22 @@ archive of the file moved to, creating it if it does not exist."
     (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))
+                  "Todo file to move this category to: " nil))
           (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: " nil)))
+      (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)
@@ -1526,6 +1646,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
@@ -1586,7 +1712,7 @@ marking of the next N items."
       (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)))
@@ -1622,13 +1748,37 @@ marking of the next N items."
 (defvar todo-date-from-calendar nil
   "Helper variable for setting item date from the Emacs Calendar.")
 
+(defvar todo-insert-item--keys-so-far)
+(defvar todo-insert-item--parameters)
+
+(defun todo-insert-item (&optional arg)
+  "Insert 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.
+
+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-basic-insert-item' for details of the parameters 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-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
+The generated commands have mnemonic 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
@@ -1723,7 +1873,7 @@ the new item:
   the item accordingly."
   ;; 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)))
@@ -2510,7 +2660,9 @@ meaning to raise or lower the item's priority by 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)))
@@ -2958,31 +3110,32 @@ 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)))
+       (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,9 +3163,7 @@ 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)))
+              (archive (find-file-noselect afile t))
               (item (and (todo-done-item-p)
                          (concat (todo-item-string) "\n")))
               (count 0)
@@ -3056,7 +3207,6 @@ 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))
              (let (buffer-read-only)
                (widen)
                (goto-char (point-min))
@@ -3076,11 +3226,13 @@ 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)
+               ;; If archive is new, save to file now (with
+               ;; write-region to avoid prompt for file to save to)
+               ;; to update todo-archives, and to let auto-mode-alist
+               ;; take effect below on visiting the archive.
+               (unless (nth 7 (file-attributes afile))
+                 (write-region nil nil afile t t)
+                 (setq todo-archives (funcall todo-files-function t))
                  (kill-buffer))))
            (with-current-buffer tbuf
              (cond
@@ -3286,19 +3438,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
@@ -4019,15 +4176,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',
@@ -4055,7 +4212,8 @@ multifile commands for further details."
                                (regexp ".todr")))))
         (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)
@@ -4069,10 +4227,15 @@ multifile commands for further details."
           (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.
@@ -4699,14 +4862,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)))))))
 
@@ -5069,7 +5275,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.
@@ -5102,6 +5308,22 @@ Overrides `diary-goto-entry'."
 
 (add-function :override diary-goto-entry-function #'todo-diary-goto-entry)
 
+(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
@@ -5232,131 +5454,173 @@ of each other."
 ;;; Utilities for generating item insertion commands and key bindings
 ;; -----------------------------------------------------------------------------
 
-;; 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 (unless (= 5 (length args))
+                   (let ((v (make-vector 5 nil)) elt)
+                     (while args
+                       (setq elt (pop args))
+                       (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))))
+                     (append v nil)))))
+    (apply #'todo-basic-insert-item (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 (or (eq name 'default)
+                                                           (eq name 'calendar)
+                                                           (eq name 'here))
+                                                       " { " " ")
+                                                   "%s=>%s"
+                                                   (when (or (eq name 'copy)
+                                                             (eq name 'dayname)
+                                                             (eq name 'region))
+                                                     " }"))
+                                                  (propertize k 'face
+                                                              'todo-key-prompt)
+                                                  name))))))
+    (setq todo-insert-item--args args)
+    (setq todo-insert-item--argsleft argsleft)
+    (when last
+      (cond ((eq last 'default)
+            (apply #'todo-basic-insert-item (car todo-insert-item--args))
+            (setq todo-insert-item--argsleft nil))
+           ((eq last 'copy)
+            (todo-copy-item)
+            (setq todo-insert-item--argsleft nil))
+           (t (let ((k (todo-insert-item--keyof last)))
+                (funcall addprompt k '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)
+                 (when (equal "k" (todo-insert-item--this-key))
+                   (unless (string-match "y" todo-insert-item--keys-so-far)
+                     (when (y-or-n-p (concat "`k' only takes effect with `y';"
+                                             " add `y'? "))
+                       (setq todo-insert-item--keys-so-far
+                             (concat todo-insert-item--keys-so-far " y"))
+                       (setq todo-insert-item--args
+                             (nconc todo-insert-item--args (list 'diary))))))
+                 (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 "Enter a key (so far `%s'): %s"
+                         todo-insert-item--keys-so-far prompt))
+    (set-transient-map map)
+    (setq todo-insert-item--argsleft argsleft)))
 
 ;; -----------------------------------------------------------------------------
 ;;; Todo minibuffer utilities
@@ -5384,7 +5648,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)
@@ -5435,6 +5719,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
@@ -5524,12 +5809,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 +5962,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.
 
@@ -5867,13 +6152,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 +6295,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 +6311,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,8 +6320,8 @@ 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)
+    ("Fxx"          todo-filter-regexp-items)
+    ("Fxm"          todo-filter-regexp-items-multifile)
     ("ee"           todo-edit-item)
     ("em"           todo-edit-multiline-item)
     ("edt"          todo-edit-item-header)
@@ -6046,7 +6336,7 @@ Filtered Items mode following todo (not done) items."
     ("eyk"          todo-edit-item-diary-nonmarking)
     ("ec"           todo-edit-done-item-comment)
     ("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 +6350,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)
@@ -6221,17 +6512,18 @@ Filtered Items mode following todo (not done) items."
 ;;     ))
 
 ;; -----------------------------------------------------------------------------
-;;; 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."
+  "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))
@@ -6265,7 +6557,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))
@@ -6284,6 +6576,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
@@ -6299,6 +6593,7 @@ Added to `window-configuration-change-hook' in `todo-mode'."
 
 (put 'todo-mode 'mode-class 'special)
 
+;;;###autoload
 (define-derived-mode todo-mode special-mode "Todo"
   "Major mode for displaying, navigating and editing todo lists.
 
@@ -6325,6 +6620,7 @@ Added to `window-configuration-change-hook' in `todo-mode'."
 
 ;; 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 +6669,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,8 +6677,11 @@ Added to `window-configuration-change-hook' in `todo-mode'."
   (todo-modes-set-1)
   (todo-modes-set-2))
 
+;;;###autoload
 (add-to-list 'auto-mode-alist '("\\.todo\\'" . todo-mode))
+;;;###autoload
 (add-to-list 'auto-mode-alist '("\\.toda\\'" . todo-archive-mode))
+;;;###autoload
 (add-to-list 'auto-mode-alist '("\\.tod[tyr]\\'" . todo-filtered-items-mode))
 
 ;; -----------------------------------------------------------------------------