diff options
| -rw-r--r-- | lisp/calendar/todos.el | 196 |
1 files changed, 179 insertions, 17 deletions
diff --git a/lisp/calendar/todos.el b/lisp/calendar/todos.el index 1689c80d820..591cf690758 100644 --- a/lisp/calendar/todos.el +++ b/lisp/calendar/todos.el | |||
| @@ -282,11 +282,11 @@ show and mark todo entries for today, but may slow down processing of | |||
| 282 | the diary file somewhat." | 282 | the diary file somewhat." |
| 283 | :type 'string | 283 | :type 'string |
| 284 | :group 'todos) | 284 | :group 'todos) |
| 285 | (defcustom todos-file-do (convert-standard-filename "~/.todos-do") | 285 | (defcustom todos-file-do (convert-standard-filename "~/.emacs.d/.todos-do") |
| 286 | "TODO mode list file." | 286 | "TODO mode list file." |
| 287 | :type 'file | 287 | :type 'file |
| 288 | :group 'todos) | 288 | :group 'todos) |
| 289 | (defcustom todos-file-done (convert-standard-filename "~/.todos-done") | 289 | (defcustom todos-file-done (convert-standard-filename "~/.emacs.d/.todos-done") |
| 290 | "TODO mode archive file." | 290 | "TODO mode archive file." |
| 291 | :type 'file | 291 | :type 'file |
| 292 | :group 'todos) | 292 | :group 'todos) |
| @@ -347,6 +347,10 @@ Automatically generated when `todos-save-top-priorities' is non-nil." | |||
| 347 | "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'." | 347 | "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'." |
| 348 | :type 'boolean | 348 | :type 'boolean |
| 349 | :group 'todos) | 349 | :group 'todos) |
| 350 | (defcustom todos-completion-ignore-case t ;; FIXME: nil for release | ||
| 351 | "Non-nil means don't consider case significant in todos-completing-read." | ||
| 352 | :type 'boolean | ||
| 353 | :group 'todos) | ||
| 350 | 354 | ||
| 351 | ;; Thanks for the ISO time stamp format go to Karl Eichwalder <ke@suse.de> | 355 | ;; Thanks for the ISO time stamp format go to Karl Eichwalder <ke@suse.de> |
| 352 | ;; My format string for the appt.el package is "%3b %2d, %y, %02I:%02M%p". | 356 | ;; My format string for the appt.el package is "%3b %2d, %y, %02I:%02M%p". |
| @@ -372,6 +376,29 @@ For details see the variable `time-stamp-format'." | |||
| 372 | (let ((time-stamp-format todos-time-string-format)) | 376 | (let ((time-stamp-format todos-time-string-format)) |
| 373 | (concat (time-stamp-string) " " todos-initials ": "))) | 377 | (concat (time-stamp-string) " " todos-initials ": "))) |
| 374 | 378 | ||
| 379 | (defface todos-prefix-string | ||
| 380 | '((t | ||
| 381 | :inherit font-lock-variable-name-face | ||
| 382 | )) | ||
| 383 | "Face for Todos prefix string." | ||
| 384 | :group 'todos) | ||
| 385 | (defvar todos-prefix-face 'todos-prefix-string) | ||
| 386 | |||
| 387 | (defface todos-item-header | ||
| 388 | '((t | ||
| 389 | :inherit font-lock-function-name-face | ||
| 390 | )) | ||
| 391 | "Face for Todos item header string." | ||
| 392 | :group 'todos) | ||
| 393 | (defvar todos-item-header-face 'todos-item-header) | ||
| 394 | |||
| 395 | (defvar todos-font-lock-keywords | ||
| 396 | (list | ||
| 397 | (list (concat "^" (regexp-quote todos-prefix)) 0 'todos-prefix-face t) | ||
| 398 | (list (concat "^" (regexp-quote todos-prefix) "\\(.*[0-9]+ [A-Ba-z0-9]*\\]?:\\)") | ||
| 399 | 1 'todos-item-header-face t)) | ||
| 400 | "Font-locking for Todos mode.") | ||
| 401 | |||
| 375 | ;; --------------------------------------------------------------------------- | 402 | ;; --------------------------------------------------------------------------- |
| 376 | 403 | ||
| 377 | ;; Set up some helpful context ... | 404 | ;; Set up some helpful context ... |
| @@ -390,7 +417,10 @@ For details see the variable `time-stamp-format'." | |||
| 390 | (suppress-keymap map t) | 417 | (suppress-keymap map t) |
| 391 | (define-key map "+" 'todos-forward-category) | 418 | (define-key map "+" 'todos-forward-category) |
| 392 | (define-key map "-" 'todos-backward-category) | 419 | (define-key map "-" 'todos-backward-category) |
| 420 | (define-key map "A" 'todos-add-category) | ||
| 421 | (define-key map "C" 'todos-display-categories) | ||
| 393 | (define-key map "d" 'todos-file-item) ;done/delete | 422 | (define-key map "d" 'todos-file-item) ;done/delete |
| 423 | (define-key map "D" 'todos-delete-category) | ||
| 394 | (define-key map "e" 'todos-edit-item) | 424 | (define-key map "e" 'todos-edit-item) |
| 395 | (define-key map "E" 'todos-edit-multiline) | 425 | (define-key map "E" 'todos-edit-multiline) |
| 396 | (define-key map "f" 'todos-file-item) | 426 | (define-key map "f" 'todos-file-item) |
| @@ -399,11 +429,13 @@ For details see the variable `time-stamp-format'." | |||
| 399 | (define-key map "j" 'todos-jump-to-category) | 429 | (define-key map "j" 'todos-jump-to-category) |
| 400 | (define-key map "k" 'todos-delete-item) | 430 | (define-key map "k" 'todos-delete-item) |
| 401 | (define-key map "l" 'todos-lower-item) | 431 | (define-key map "l" 'todos-lower-item) |
| 432 | (define-key map "m" 'todos-move-item) | ||
| 402 | (define-key map "n" 'todos-forward-item) | 433 | (define-key map "n" 'todos-forward-item) |
| 403 | (define-key map "p" 'todos-backward-item) | 434 | (define-key map "p" 'todos-backward-item) |
| 404 | (define-key map "P" 'todos-print) | 435 | (define-key map "P" 'todos-print) |
| 405 | (define-key map "q" 'todos-quit) | 436 | (define-key map "q" 'todos-quit) |
| 406 | (define-key map "r" 'todos-raise-item) | 437 | (define-key map "r" 'todos-raise-item) |
| 438 | (define-key map "R" 'todos-rename-category) | ||
| 407 | (define-key map "s" 'todos-save) | 439 | (define-key map "s" 'todos-save) |
| 408 | (define-key map "S" 'todos-save-top-priorities) | 440 | (define-key map "S" 'todos-save-top-priorities) |
| 409 | (define-key map "t" 'todos-top-priorities) | 441 | (define-key map "t" 'todos-top-priorities) |
| @@ -423,6 +455,13 @@ For details see the variable `time-stamp-format'." | |||
| 423 | (defvar todos-category-end "--- End" | 455 | (defvar todos-category-end "--- End" |
| 424 | "Separator after a category.") | 456 | "Separator after a category.") |
| 425 | 457 | ||
| 458 | (defvar todos-window-configuration nil | ||
| 459 | "Variable for storing current window configuration in Todos mode. | ||
| 460 | |||
| 461 | Set before leaving Todos mode buffer by todos-display-categories. | ||
| 462 | Restored before re-entering Todo mode buffer by todo-kill-buffer | ||
| 463 | and todo-jump-to-category-noninteractively.") | ||
| 464 | |||
| 426 | ;; --------------------------------------------------------------------------- | 465 | ;; --------------------------------------------------------------------------- |
| 427 | 466 | ||
| 428 | (defun todos-category-select () | 467 | (defun todos-category-select () |
| @@ -584,6 +623,102 @@ For details see the variable `time-stamp-format'." | |||
| 584 | (todos-save) | 623 | (todos-save) |
| 585 | (message ""))) | 624 | (message ""))) |
| 586 | 625 | ||
| 626 | (defun todos-rename-category (new) | ||
| 627 | "Rename current Todos category." | ||
| 628 | (interactive "sCategory: ") | ||
| 629 | (let ((cat (nth todos-category-number todos-categories)) | ||
| 630 | (vec (vconcat todos-categories)) | ||
| 631 | prompt) | ||
| 632 | (while (and (cond ((string= "" new) | ||
| 633 | (setq prompt "Enter a non-empty category name: ")) | ||
| 634 | ((string-match "\\`\\s-+\\'" new) | ||
| 635 | (setq prompt "Enter a category name that is not only white space: ")) | ||
| 636 | ((member new todos-categories) | ||
| 637 | (setq prompt "Enter a non-existing category name: "))) | ||
| 638 | (setq new (read-from-minibuffer prompt)))) | ||
| 639 | (aset vec todos-category-number new) | ||
| 640 | (setq todos-categories (append vec nil)) | ||
| 641 | (save-excursion | ||
| 642 | (widen) | ||
| 643 | (search-backward (concat todos-prefix todos-category-beg)) | ||
| 644 | (goto-char (match-end 0)) | ||
| 645 | (when (looking-at (regexp-quote cat)) | ||
| 646 | (replace-match new t)) | ||
| 647 | (goto-char (point-min)) | ||
| 648 | (setq mode-line-buffer-identification | ||
| 649 | (concat "Category: " new)))) | ||
| 650 | ;; (concat "Category: " (format "%18s" new))))) | ||
| 651 | (todos-category-select)) | ||
| 652 | |||
| 653 | (defun todos-delete-category () | ||
| 654 | "Delete current Todos category provided it is empty." | ||
| 655 | (interactive) | ||
| 656 | (if (not (eq (point-max) (point-min))) | ||
| 657 | (message "This category is not empty, so it cannot be deleted") | ||
| 658 | (let ((cat (nth todos-category-number todos-categories)) beg end) | ||
| 659 | (when (y-or-n-p (concat "Permanently remove category '" cat "'? ")) | ||
| 660 | (widen) | ||
| 661 | (setq beg (re-search-backward | ||
| 662 | (concat "^" (regexp-quote todos-prefix) todos-category-beg cat) | ||
| 663 | (point-min) nil) | ||
| 664 | end (1+ (re-search-forward | ||
| 665 | (concat "^" todos-category-end "\n" | ||
| 666 | (regexp-quote todos-prefix) " " todos-category-sep) | ||
| 667 | (point-max) nil))) | ||
| 668 | (kill-region beg end) | ||
| 669 | (setq todos-categories (delete cat todos-categories)) | ||
| 670 | (todos-category-select) | ||
| 671 | (message "Deleted category \"%s\"" cat))))) | ||
| 672 | |||
| 673 | (defcustom todos-categories-buffer "*TODOS Categories*" | ||
| 674 | "Name of buffer displayed by `todos-display-categories'" | ||
| 675 | :type 'string | ||
| 676 | :group 'todos) | ||
| 677 | |||
| 678 | (defun todos-display-categories () | ||
| 679 | "Display an alphabetical list of clickable Todos category names. | ||
| 680 | Click or type RET on a category name to go to it." | ||
| 681 | (interactive) | ||
| 682 | (setq todos-window-configuration (current-window-configuration)) | ||
| 683 | (let ((categories (copy-sequence todos-categories)) | ||
| 684 | beg) | ||
| 685 | ;; alphabetize the list case insensitively | ||
| 686 | (setq categories (sort categories (lambda (s1 s2) (let ((cis1 (upcase s1)) | ||
| 687 | (cis2 (upcase s2))) | ||
| 688 | (string< cis1 cis2))))) | ||
| 689 | (require 'widget) | ||
| 690 | (eval-when-compile | ||
| 691 | (require 'wid-edit)) | ||
| 692 | (with-current-buffer (get-buffer-create todos-categories-buffer) | ||
| 693 | (pop-to-buffer (current-buffer)) | ||
| 694 | (erase-buffer) | ||
| 695 | (kill-all-local-variables) | ||
| 696 | (widget-insert "Press a button to display the corresponding category.\n\n") | ||
| 697 | (setq beg (point)) | ||
| 698 | (mapc (lambda (cat) | ||
| 699 | (widget-create 'push-button | ||
| 700 | :notify (lambda (widget &rest ignore) | ||
| 701 | (todos-jump-to-category-noninteractively | ||
| 702 | (widget-get widget :value))) | ||
| 703 | |||
| 704 | cat) | ||
| 705 | (widget-insert "\n")) | ||
| 706 | categories) | ||
| 707 | (use-local-map widget-keymap) | ||
| 708 | (widget-setup)))) | ||
| 709 | |||
| 710 | (defun todos-jump-to-category-noninteractively (cat) | ||
| 711 | (let ((name todos-categories-buffer)) | ||
| 712 | (if (string= (buffer-name) name) | ||
| 713 | (kill-buffer name))) | ||
| 714 | (set-window-configuration todos-window-configuration) | ||
| 715 | (switch-to-buffer (file-name-nondirectory todos-file-do)) | ||
| 716 | (widen) | ||
| 717 | (goto-char (point-min)) | ||
| 718 | (setq todos-category-number (- (length todos-categories) | ||
| 719 | (length (member cat todos-categories)))) | ||
| 720 | (todos-category-select)) | ||
| 721 | |||
| 587 | ;;;###autoload | 722 | ;;;###autoload |
| 588 | (defun todos-insert-item (arg) | 723 | (defun todos-insert-item (arg) |
| 589 | "Insert new TODO list entry. | 724 | "Insert new TODO list entry. |
| @@ -666,6 +801,18 @@ If point is on an empty line, insert the entry there." | |||
| 666 | (message "")) | 801 | (message "")) |
| 667 | (error "No TODO list entry to lower"))) | 802 | (error "No TODO list entry to lower"))) |
| 668 | 803 | ||
| 804 | (defun todos-move-item () | ||
| 805 | "Move the current todo item to another, interactively named, category. | ||
| 806 | |||
| 807 | If the named category is not one of the current todo categories, then | ||
| 808 | it is created and the item becomes the first entry in that category." | ||
| 809 | (interactive) | ||
| 810 | (let ((item (todos-item-string)) | ||
| 811 | (inhibit-quit t) | ||
| 812 | (category (todos-completing-read))) | ||
| 813 | (todos-remove-item) | ||
| 814 | (todos-add-item-non-interactively item category))) | ||
| 815 | |||
| 669 | (defun todos-file-item (&optional comment) | 816 | (defun todos-file-item (&optional comment) |
| 670 | "File the current TODO list entry away, annotated with an optional COMMENT." | 817 | "File the current TODO list entry away, annotated with an optional COMMENT." |
| 671 | (interactive "sComment: ") | 818 | (interactive "sComment: ") |
| @@ -730,9 +877,9 @@ between each category." | |||
| 730 | (copy-to-buffer todos-print-buffer-name (point-min) (point-max)) | 877 | (copy-to-buffer todos-print-buffer-name (point-min) (point-max)) |
| 731 | (set-buffer todos-print-buffer-name) | 878 | (set-buffer todos-print-buffer-name) |
| 732 | (goto-char (point-min)) | 879 | (goto-char (point-min)) |
| 733 | (when (re-search-forward (regexp-quote todos-header) nil t) | 880 | ;; (when (re-search-forward (regexp-quote todos-header) nil t) |
| 734 | (beginning-of-line 1) | 881 | ;; (beginning-of-line 1) |
| 735 | (delete-region (point) (line-end-position))) | 882 | ;; (delete-region (point) (line-end-position))) |
| 736 | (while (re-search-forward ;Find category start | 883 | (while (re-search-forward ;Find category start |
| 737 | (regexp-quote (concat todos-prefix todos-category-beg)) | 884 | (regexp-quote (concat todos-prefix todos-category-beg)) |
| 738 | nil t) | 885 | nil t) |
| @@ -866,17 +1013,22 @@ Number of entries for each category is given by `todos-print-priorities'." | |||
| 866 | 1013 | ||
| 867 | (defun todos-completing-read () | 1014 | (defun todos-completing-read () |
| 868 | "Return a category name, with completion, for use in Todo mode." | 1015 | "Return a category name, with completion, for use in Todo mode." |
| 869 | ;; make a copy of todos-categories in case history-delete-duplicates is | 1016 | ;; allow SPC to insert spaces, for adding new category names with |
| 870 | ;; non-nil, which makes completing-read alter todos-categories | 1017 | ;; todos-move-item |
| 871 | (let* ((categories (copy-sequence todos-categories)) | 1018 | (let ((map minibuffer-local-completion-map)) |
| 872 | (history (cons 'todos-categories (1+ todos-category-number))) | 1019 | (define-key map " " nil) |
| 873 | (default (nth todos-category-number todos-categories)) | 1020 | ;; make a copy of todos-categories in case history-delete-duplicates is |
| 874 | (category (completing-read | 1021 | ;; non-nil, which makes completing-read alter todos-categories |
| 875 | (concat "Category [" default "]: ") | 1022 | (let* ((categories (copy-sequence todos-categories)) |
| 876 | todos-categories nil nil nil history default))) | 1023 | (history (cons 'todos-categories (1+ todos-category-number))) |
| 877 | ;; restore the original value of todos-categories | 1024 | (default (nth todos-category-number todos-categories)) |
| 878 | (setq todos-categories categories) | 1025 | (completion-ignore-case todos-completion-ignore-case) |
| 879 | category)) | 1026 | (category (completing-read |
| 1027 | (concat "Category [" default "]: ") | ||
| 1028 | todos-categories nil nil nil history default))) | ||
| 1029 | ;; restore the original value of todos-categories | ||
| 1030 | (setq todos-categories categories) | ||
| 1031 | category))) | ||
| 880 | 1032 | ||
| 881 | ;; --------------------------------------------------------------------------- | 1033 | ;; --------------------------------------------------------------------------- |
| 882 | 1034 | ||
| @@ -915,9 +1067,19 @@ Number of entries for each category is given by `todos-print-priorities'." | |||
| 915 | (interactive) | 1067 | (interactive) |
| 916 | (kill-all-local-variables) | 1068 | (kill-all-local-variables) |
| 917 | (setq major-mode 'todos-mode) | 1069 | (setq major-mode 'todos-mode) |
| 918 | (setq mode-name "TODO") | 1070 | (setq mode-name "TODOS") |
| 919 | (use-local-map todos-mode-map) | 1071 | (use-local-map todos-mode-map) |
| 920 | (easy-menu-add todos-menu) | 1072 | (easy-menu-add todos-menu) |
| 1073 | (make-local-variable 'font-lock-defaults) | ||
| 1074 | (setq font-lock-defaults '(todos-font-lock-keywords t)) | ||
| 1075 | (make-local-variable 'word-wrap) | ||
| 1076 | (setq word-wrap t) | ||
| 1077 | (make-local-variable 'wrap-prefix) | ||
| 1078 | (setq wrap-prefix | ||
| 1079 | (make-string (1+ (length (concat todos-prefix | ||
| 1080 | (todos-entry-timestamp-initials)))) 32)) | ||
| 1081 | (unless (member '(continuation) fringe-indicator-alist) | ||
| 1082 | (push '(continuation) fringe-indicator-alist)) | ||
| 921 | (run-mode-hooks 'todos-mode-hook)) | 1083 | (run-mode-hooks 'todos-mode-hook)) |
| 922 | 1084 | ||
| 923 | (defvar date) | 1085 | (defvar date) |