diff options
| author | Stephen Berman | 2012-05-17 22:20:44 +0100 |
|---|---|---|
| committer | Stephen Berman | 2012-05-17 22:20:44 +0100 |
| commit | 0e89c3fc75c7de33bcb625c325600af227d2b1d1 (patch) | |
| tree | b9c1cfb8342a7208b874d5101ae3064c1be1e61f | |
| parent | 697bd4a3585bd7d9ef66d670d21b72a3e9a36e6a (diff) | |
| download | emacs-0e89c3fc75c7de33bcb625c325600af227d2b1d1.tar.gz emacs-0e89c3fc75c7de33bcb625c325600af227d2b1d1.zip | |
* calendar/todos.el Add and revise further doc strings and
comments; major code rearrangement.
(todos-merged-files, todos-prompt-merged-files)
(todos-print-priorities, todos-tmp-buffer-name)
(todos-top-priorities-widgets, todos-update-merged-files)
(todos-merged-top-priorities, todos-merged-diary-items)
(todos-merged-regexp-items, todos-merged-custom-items)
(todos-raw-mode, todos-change-default-file)
(todos-toggle-diary-inclusion, todos-toggle-item-diary-nonmarking)
(todos-toggle-diary-nonmarking, todos-validate-category-name):
Remove.
(todos-category-string-matcher): Comment out.
(todos-categories): New defgroup.
(todos-initial-file, todos-filter-buffer)
(todos-top-priorities-buffer, todos-categories-category-label)
(todos-diary-items-buffer, todos-regexp-items-buffer)
(todos-custom-items-buffer, todos-filter-files)
(todos-highlight-item, todos-todo-mode-date-time-regexp):
New defcustoms.
(todos-diary-expired): New face.
(todos-print-buffer, todos-multiple-files)
(todos-multiple-files-widget, todos-key-bindings): New variables.
(todos-short-file-name, todos-reevaluate-default-file-defcustom)
(todos-special-buffer-name)
(todos-reevaluate-filter-files-defcustom)
(todos-reset-highlight-item, todos-reevaluate-defcustoms)
(todos-nondiary-marker-matcher, todos-diary-nonmarking-matcher)
(todos-diary-expired-matcher, todos-category-string-matcher-1)
(todos-category-string-matcher-2, todos-repair-categories-sexp)
(todos-validate-name, todos-multiple-files)
(todos-display-categories-1, todos-update-categories-display)
(todos-modes-set-3, todos-mode-external-set): New functions.
(todos-set-top-priorities-in-file)
(todos-set-top-priorities-in-category)
(todos-top-priorities-multifile, todos-diary-items-multifile)
(todos-regexp-items-multifile, todos-custom-items-multifile)
(todos-convert-legacy-files, todos-jump-to-item)
(todos-edit-multiline-item, todos-edit-item-date-from-calendar)
(todos-edit-item-diary-inclusion)
(todos-edit-category-diary-inclusion)
(todos-edit-item-diary-nonmarking)
(todos-edit-category-diary-nonmarking): New commands.
(todos, todos-faces): Update :version.
(todos-done-separator, todos-completion-ignore-case):
Change default value.
(todos-done-separator): Change :set function.
(todos-indent-to-here): Add :validate function to :type.
(todos-prefix-string, todos-mark, todos-button)
(todos-sorted-column, todos-archived-only, todos-search)
(todos-done, todos-done-sep): Provide full face definitions
instead of inheriting.
(todos-edit-buffer, todos-categories-buffer): Change from
defcustom to defvar.
(todos-category-beg, todos-category-done): Change from defvar to
defconst.
(todos-files): Check if todos-files-directory exists.
(todos-default-todos-file, todos-mode-line-control, todos-print):
Use todos-short-file-name.
(todos-font-lock-keywords): Use todos-nondiary-marker-matcher,
todos-diary-nonmarking-matcher, todos-category-string-matcher-1,
todos-category-string-matcher-2, todos-diary-expired-matcher.
(todos-category-select): Use todos-done-string-start, and
condition search on todos-show-with-done; don't make display
overlay for done items separator string if there already is one;
use todos-highlight-item, require hl-line and activate
hl-line-mode here in order to avoid a hang if done in todos-mode
or the mode hook.
(todos-update-categories-sexp): Use todos-categories-full if set,
otherwise todos-categories.
(todos-make-categories-list): Don't test for archive file when
processing a legacy todo-mode file.
(todos-check-format): Add check for todos-categories sexp.
(todos-diary-item-p): Use todos-nondiary-start instead of
todos-date-pattern.
(todos-marked-item-p): Rename from todos-item-marked-p.
(todos-read-file-name): Don't accept empty name; validate.
(todos-read-category): Validate new name before prompting whether
to add new category; force quit if user answers no.
(todos-filter-items): Improve implementation.
(todos-set-top-priorities): Rewrite as a noninteractive function
using minibuffer input instead of widgets.
(todos-insert-sort-button): Call todos-display-sorted with
argument nil to display categories in numerical order, instead of
calling todos-display-categories.
(powerset-recursive): Borrow and slightly reformulate the (GDFL'd)
Common Lisp powerset function at
http://rosettacode.org/wiki/Power_set#Common_Lisp.
(powerset-bitwise): Implement in Emacs Lisp the (GDFL'd) C
powerset function at http://rosettacode.org/wiki/Power_set#C.
(todos-powerset): Defalias to powerset-bitwise.
(todos-mode-map): Generate from todos-key-bindings instead of
listing each key definition.
(todos-categories-mode-map): Add two bindings.
(todos-filter-items-mode-map): Add some bindings, remove others.
(todos-mode): Derive from special-mode; use todos-modes-set-3; add
function setting todos-done-separator to
window-configuration-change-hook.
(todos-unload-hook): Remove function setting todos-done-separator
from window-configuration-change-hook.
(todos-archive-mode): Derive from todos-mode; use todos-modes-set-3.
(todos-edit-mode): Derive from text-mode; use todos-mode-external-set.
(todos-categories-mode): Derive from special-mode; use
todos-mode-external-set.
(todos-filter-items-mode): Derive from special-mode.
(todos-quit): Save archive file if it hasn't yet been saved.
(todos-display-categories): Delegate all functionality to
todos-display-categories-1 and todos-update-categories-display.
(todos-toggle-view-done-items): Improve implementation.
(todos-highlight-item): Require hl-line.
(todos-toggle-display-date-time): Remove argument and make it
apply only to whole file.
(todos-top-priorities, todos-diary-items, todos-regexp-items)
(todos-custom-items): Use todos-special-buffer-name.
(todos-add-file): Use todos-short-file-name and
todos-reevaluate-defcustoms; remove validation, since it's now
done in todos-read-file-name.
(todos-add-category): Also update todos-categories-full if non-nil.
(todos-delete-category): Delete file after confirmation if only
category is deleted.
(todos-move-category): Use todos-short-file-name and
todos-reevaluate-defcustoms.
(todos-insert-item): Fix getting date from the calendar and
insertion of time string.
(todos-set-date-from-calendar): Enter calendar buffer, suppressing
display of diary entries.
(todos-edit-multiline): Add optional argument to restrict editing
buffer to current item, otherwise make entire buffer (i.e. whole
file) editable.
(todos-edit-quit): When whole file is editable, check file format
validity before killing buffer, and if valid, recalculate
categories sexp to be safe.
(todos-edit-item-header): Allow choosing date from calendar.
(todos-item-done): Handle marked items.
| -rw-r--r-- | lisp/ChangeLog | 136 | ||||
| -rw-r--r-- | lisp/calendar/todos.el | 4813 |
2 files changed, 2912 insertions, 2037 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 9fe801d3f06..1933b7cea01 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,139 @@ | |||
| 1 | 2012-09-20 Stephen Berman <stephen.berman@gmx.net> | ||
| 2 | |||
| 3 | * calendar/todos.el Add and revise further doc strings and | ||
| 4 | comments; major code rearrangement. | ||
| 5 | (todos-merged-files, todos-prompt-merged-files) | ||
| 6 | (todos-print-priorities, todos-tmp-buffer-name) | ||
| 7 | (todos-top-priorities-widgets, todos-update-merged-files) | ||
| 8 | (todos-merged-top-priorities, todos-merged-diary-items) | ||
| 9 | (todos-merged-regexp-items, todos-merged-custom-items) | ||
| 10 | (todos-raw-mode, todos-change-default-file) | ||
| 11 | (todos-toggle-diary-inclusion, todos-toggle-item-diary-nonmarking) | ||
| 12 | (todos-toggle-diary-nonmarking, todos-validate-category-name): | ||
| 13 | Remove. | ||
| 14 | (todos-category-string-matcher): Comment out. | ||
| 15 | (todos-categories): New defgroup. | ||
| 16 | (todos-initial-file, todos-filter-buffer) | ||
| 17 | (todos-top-priorities-buffer, todos-categories-category-label) | ||
| 18 | (todos-diary-items-buffer, todos-regexp-items-buffer) | ||
| 19 | (todos-custom-items-buffer, todos-filter-files) | ||
| 20 | (todos-highlight-item, todos-todo-mode-date-time-regexp): | ||
| 21 | New defcustoms. | ||
| 22 | (todos-diary-expired): New face. | ||
| 23 | (todos-print-buffer, todos-multiple-files) | ||
| 24 | (todos-multiple-files-widget, todos-key-bindings): New variables. | ||
| 25 | (todos-short-file-name, todos-reevaluate-default-file-defcustom) | ||
| 26 | (todos-special-buffer-name) | ||
| 27 | (todos-reevaluate-filter-files-defcustom) | ||
| 28 | (todos-reset-highlight-item, todos-reevaluate-defcustoms) | ||
| 29 | (todos-nondiary-marker-matcher, todos-diary-nonmarking-matcher) | ||
| 30 | (todos-diary-expired-matcher, todos-category-string-matcher-1) | ||
| 31 | (todos-category-string-matcher-2, todos-repair-categories-sexp) | ||
| 32 | (todos-validate-name, todos-multiple-files) | ||
| 33 | (todos-display-categories-1, todos-update-categories-display) | ||
| 34 | (todos-modes-set-3, todos-mode-external-set): New functions. | ||
| 35 | (todos-set-top-priorities-in-file) | ||
| 36 | (todos-set-top-priorities-in-category) | ||
| 37 | (todos-top-priorities-multifile, todos-diary-items-multifile) | ||
| 38 | (todos-regexp-items-multifile, todos-custom-items-multifile) | ||
| 39 | (todos-convert-legacy-files, todos-jump-to-item) | ||
| 40 | (todos-edit-multiline-item, todos-edit-item-date-from-calendar) | ||
| 41 | (todos-edit-item-diary-inclusion) | ||
| 42 | (todos-edit-category-diary-inclusion) | ||
| 43 | (todos-edit-item-diary-nonmarking) | ||
| 44 | (todos-edit-category-diary-nonmarking): New commands. | ||
| 45 | (todos, todos-faces): Update :version. | ||
| 46 | (todos-done-separator, todos-completion-ignore-case): | ||
| 47 | Change default value. | ||
| 48 | (todos-done-separator): Change :set function. | ||
| 49 | (todos-indent-to-here): Add :validate function to :type. | ||
| 50 | (todos-prefix-string, todos-mark, todos-button) | ||
| 51 | (todos-sorted-column, todos-archived-only, todos-search) | ||
| 52 | (todos-done, todos-done-sep): Provide full face definitions | ||
| 53 | instead of inheriting. | ||
| 54 | (todos-edit-buffer, todos-categories-buffer): Change from | ||
| 55 | defcustom to defvar. | ||
| 56 | (todos-category-beg, todos-category-done): Change from defvar to | ||
| 57 | defconst. | ||
| 58 | (todos-files): Check if todos-files-directory exists. | ||
| 59 | (todos-default-todos-file, todos-mode-line-control, todos-print): | ||
| 60 | Use todos-short-file-name. | ||
| 61 | (todos-font-lock-keywords): Use todos-nondiary-marker-matcher, | ||
| 62 | todos-diary-nonmarking-matcher, todos-category-string-matcher-1, | ||
| 63 | todos-category-string-matcher-2, todos-diary-expired-matcher. | ||
| 64 | (todos-category-select): Use todos-done-string-start, and | ||
| 65 | condition search on todos-show-with-done; don't make display | ||
| 66 | overlay for done items separator string if there already is one; | ||
| 67 | use todos-highlight-item, require hl-line and activate | ||
| 68 | hl-line-mode here in order to avoid a hang if done in todos-mode | ||
| 69 | or the mode hook. | ||
| 70 | (todos-update-categories-sexp): Use todos-categories-full if set, | ||
| 71 | otherwise todos-categories. | ||
| 72 | (todos-make-categories-list): Don't test for archive file when | ||
| 73 | processing a legacy todo-mode file. | ||
| 74 | (todos-check-format): Add check for todos-categories sexp. | ||
| 75 | (todos-diary-item-p): Use todos-nondiary-start instead of | ||
| 76 | todos-date-pattern. | ||
| 77 | (todos-marked-item-p): Rename from todos-item-marked-p. | ||
| 78 | (todos-read-file-name): Don't accept empty name; validate. | ||
| 79 | (todos-read-category): Validate new name before prompting whether | ||
| 80 | to add new category; force quit if user answers no. | ||
| 81 | (todos-filter-items): Improve implementation. | ||
| 82 | (todos-set-top-priorities): Rewrite as a noninteractive function | ||
| 83 | using minibuffer input instead of widgets. | ||
| 84 | (todos-insert-sort-button): Call todos-display-sorted with | ||
| 85 | argument nil to display categories in numerical order, instead of | ||
| 86 | calling todos-display-categories. | ||
| 87 | (powerset-recursive): Borrow and slightly reformulate the (GDFL'd) | ||
| 88 | Common Lisp powerset function at | ||
| 89 | http://rosettacode.org/wiki/Power_set#Common_Lisp. | ||
| 90 | (powerset-bitwise): Implement in Emacs Lisp the (GDFL'd) C | ||
| 91 | powerset function at http://rosettacode.org/wiki/Power_set#C. | ||
| 92 | (todos-powerset): Defalias to powerset-bitwise. | ||
| 93 | (todos-mode-map): Generate from todos-key-bindings instead of | ||
| 94 | listing each key definition. | ||
| 95 | (todos-categories-mode-map): Add two bindings. | ||
| 96 | (todos-filter-items-mode-map): Add some bindings, remove others. | ||
| 97 | (todos-mode): Derive from special-mode; use todos-modes-set-3; add | ||
| 98 | function setting todos-done-separator to | ||
| 99 | window-configuration-change-hook. | ||
| 100 | (todos-unload-hook): Remove function setting todos-done-separator | ||
| 101 | from window-configuration-change-hook. | ||
| 102 | (todos-archive-mode): Derive from todos-mode; use todos-modes-set-3. | ||
| 103 | (todos-edit-mode): Derive from text-mode; use todos-mode-external-set. | ||
| 104 | (todos-categories-mode): Derive from special-mode; use | ||
| 105 | todos-mode-external-set. | ||
| 106 | (todos-filter-items-mode): Derive from special-mode. | ||
| 107 | (todos-quit): Save archive file if it hasn't yet been saved. | ||
| 108 | (todos-display-categories): Delegate all functionality to | ||
| 109 | todos-display-categories-1 and todos-update-categories-display. | ||
| 110 | (todos-toggle-view-done-items): Improve implementation. | ||
| 111 | (todos-highlight-item): Require hl-line. | ||
| 112 | (todos-toggle-display-date-time): Remove argument and make it | ||
| 113 | apply only to whole file. | ||
| 114 | (todos-top-priorities, todos-diary-items, todos-regexp-items) | ||
| 115 | (todos-custom-items): Use todos-special-buffer-name. | ||
| 116 | (todos-add-file): Use todos-short-file-name and | ||
| 117 | todos-reevaluate-defcustoms; remove validation, since it's now | ||
| 118 | done in todos-read-file-name. | ||
| 119 | (todos-add-category): Also update todos-categories-full if non-nil. | ||
| 120 | (todos-delete-category): Delete file after confirmation if only | ||
| 121 | category is deleted. | ||
| 122 | (todos-move-category): Use todos-short-file-name and | ||
| 123 | todos-reevaluate-defcustoms. | ||
| 124 | (todos-insert-item): Fix getting date from the calendar and | ||
| 125 | insertion of time string. | ||
| 126 | (todos-set-date-from-calendar): Enter calendar buffer, suppressing | ||
| 127 | display of diary entries. | ||
| 128 | (todos-edit-multiline): Add optional argument to restrict editing | ||
| 129 | buffer to current item, otherwise make entire buffer (i.e. whole | ||
| 130 | file) editable. | ||
| 131 | (todos-edit-quit): When whole file is editable, check file format | ||
| 132 | validity before killing buffer, and if valid, recalculate | ||
| 133 | categories sexp to be safe. | ||
| 134 | (todos-edit-item-header): Allow choosing date from calendar. | ||
| 135 | (todos-item-done): Handle marked items. | ||
| 136 | |||
| 1 | 2012-09-19 Stephen Berman <stephen.berman@gmx.net> | 137 | 2012-09-19 Stephen Berman <stephen.berman@gmx.net> |
| 2 | 138 | ||
| 3 | * calendar/todos.el (todos-item-start): Restore commented out code | 139 | * calendar/todos.el (todos-item-start): Restore commented out code |
diff --git a/lisp/calendar/todos.el b/lisp/calendar/todos.el index 457e49c267e..b6b62808613 100644 --- a/lisp/calendar/todos.el +++ b/lisp/calendar/todos.el | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | ;;; Todos.el --- facilities for making and maintaining Todo lists | 1 | ;;; Todos.el --- facilities for making and maintaining Todo lists |
| 2 | 2 | ||
| 3 | ;; Copyright (C) 1997, 1999, 2001-2011 Free Software Foundation, Inc. | 3 | ;; Copyright (C) 1997, 1999, 2001-2012 Free Software Foundation, Inc. |
| 4 | 4 | ||
| 5 | ;; Author: Oliver Seidel <privat@os10000.net> | 5 | ;; Author: Oliver Seidel <privat@os10000.net> |
| 6 | ;; Stephen Berman <stephen.berman@gmx.net> | 6 | ;; Stephen Berman <stephen.berman@gmx.net> |
| @@ -8,7 +8,7 @@ | |||
| 8 | ;; Created: 2 Aug 1997 | 8 | ;; Created: 2 Aug 1997 |
| 9 | ;; Keywords: calendar, todo | 9 | ;; Keywords: calendar, todo |
| 10 | 10 | ||
| 11 | ;; This file is part of GNU Emacs. | 11 | ;; This file is [not yet] part of GNU Emacs. |
| 12 | 12 | ||
| 13 | ;; GNU Emacs is free software: you can redistribute it and/or modify | 13 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
| 14 | ;; it under the terms of the GNU General Public License as published by | 14 | ;; it under the terms of the GNU General Public License as published by |
| @@ -25,30 +25,11 @@ | |||
| 25 | 25 | ||
| 26 | ;;; Commentary: | 26 | ;;; Commentary: |
| 27 | 27 | ||
| 28 | ;; UI | ||
| 29 | ;; - display | ||
| 30 | ;; - show todos in cat | ||
| 31 | ;; - show done in cat | ||
| 32 | ;; - show catlist | ||
| 33 | ;; - show top priorities in all cats | ||
| 34 | ;; - show archived | ||
| 35 | ;; - navigation | ||
| 36 | ;; - | ||
| 37 | ;; - editing | ||
| 38 | ;; | ||
| 39 | ;; Internals | ||
| 40 | ;; - cat props: name, number, todos, done, archived | ||
| 41 | ;; - item props: priority, date-time, status? | ||
| 42 | ;; - file format | ||
| 43 | ;; - cat begin | ||
| 44 | ;; - todo items 0...n | ||
| 45 | ;; - empty line | ||
| 46 | ;; - done-separator | ||
| 47 | ;; - done item 0...n | ||
| 48 | |||
| 49 | ;;; Code: | 28 | ;;; Code: |
| 50 | 29 | ||
| 51 | (require 'diary-lib) | 30 | (require 'diary-lib) |
| 31 | ;; For remove-duplicates in todos-insertion-commands-args. | ||
| 32 | (eval-when-compile (require 'cl)) | ||
| 52 | 33 | ||
| 53 | ;; --------------------------------------------------------------------------- | 34 | ;; --------------------------------------------------------------------------- |
| 54 | ;;; User options | 35 | ;;; User options |
| @@ -56,9 +37,86 @@ | |||
| 56 | (defgroup todos nil | 37 | (defgroup todos nil |
| 57 | "Create and maintain categorized lists of todo items." | 38 | "Create and maintain categorized lists of todo items." |
| 58 | :link '(emacs-commentary-link "todos") | 39 | :link '(emacs-commentary-link "todos") |
| 59 | :version "24.1" | 40 | :version "24.2" |
| 60 | :group 'calendar) | 41 | :group 'calendar) |
| 61 | 42 | ||
| 43 | (defcustom todos-files-directory (locate-user-emacs-file "todos/") | ||
| 44 | "Directory where user's Todos files are saved." | ||
| 45 | :type 'directory | ||
| 46 | :group 'todos) | ||
| 47 | |||
| 48 | (defun todos-files (&optional archives) | ||
| 49 | "Default value of `todos-files-function'. | ||
| 50 | This returns the case-insensitive alphabetically sorted list of | ||
| 51 | file truenames in `todos-files-directory' with the extension | ||
| 52 | \".todo\". With non-nil ARCHIVES return the list of archive file | ||
| 53 | truenames (those with the extension \".toda\")." | ||
| 54 | (let ((files (if (file-exists-p todos-files-directory) | ||
| 55 | (mapcar 'file-truename | ||
| 56 | (directory-files todos-files-directory t | ||
| 57 | (if archives "\.toda$" "\.todo$") t))))) | ||
| 58 | (sort files (lambda (s1 s2) (let ((cis1 (upcase s1)) | ||
| 59 | (cis2 (upcase s2))) | ||
| 60 | (string< cis1 cis2)))))) | ||
| 61 | |||
| 62 | (defcustom todos-files-function 'todos-files | ||
| 63 | "Function returning the value of the variable `todos-files'. | ||
| 64 | This function should take an optional argument that, if non-nil, | ||
| 65 | makes it return the value of the variable `todos-archives'." | ||
| 66 | :type 'function | ||
| 67 | :group 'todos) | ||
| 68 | |||
| 69 | (defun todos-short-file-name (file) | ||
| 70 | "Return short form of Todos FILE. | ||
| 71 | This lacks the extension and directory components." | ||
| 72 | (file-name-sans-extension (file-name-nondirectory file))) | ||
| 73 | |||
| 74 | (defcustom todos-default-todos-file (car (funcall todos-files-function)) | ||
| 75 | "Todos file visited by first session invocation of `todos-show'." | ||
| 76 | :type `(radio ,@(mapcar (lambda (f) (list 'const f)) | ||
| 77 | (mapcar 'todos-short-file-name | ||
| 78 | (funcall todos-files-function)))) | ||
| 79 | :group 'todos) | ||
| 80 | |||
| 81 | ;; FIXME: is there a better alternative to this? | ||
| 82 | (defun todos-reevaluate-default-file-defcustom () | ||
| 83 | "Reevaluate defcustom of `todos-default-todos-file'. | ||
| 84 | Called after adding or deleting a Todos file." | ||
| 85 | (eval (defcustom todos-default-todos-file (car (funcall todos-files-function)) | ||
| 86 | "Todos file visited by first session invocation of `todos-show'." | ||
| 87 | :type `(radio ,@(mapcar (lambda (f) (list 'const f)) | ||
| 88 | (mapcar 'todos-short-file-name | ||
| 89 | (funcall todos-files-function)))) | ||
| 90 | :group 'todos))) | ||
| 91 | |||
| 92 | (defcustom todos-show-current-file t | ||
| 93 | "Non-nil to make `todos-show' visit the current Todos file. | ||
| 94 | Otherwise, `todos-show' always visits `todos-default-todos-file'." | ||
| 95 | :type 'boolean | ||
| 96 | :initialize 'custom-initialize-default | ||
| 97 | :set 'todos-toggle-show-current-file | ||
| 98 | :group 'todos) | ||
| 99 | |||
| 100 | (defun todos-toggle-show-current-file (symbol value) | ||
| 101 | "The :set function for user option `todos-show-current-file'." | ||
| 102 | (custom-set-default symbol value) | ||
| 103 | (if value | ||
| 104 | (add-hook 'pre-command-hook 'todos-show-current-file nil t) | ||
| 105 | (remove-hook 'pre-command-hook 'todos-show-current-file t))) | ||
| 106 | |||
| 107 | (defcustom todos-visit-files-commands (list 'find-file 'dired-find-file) | ||
| 108 | "List of commands to visit files for `todos-after-find-file'. | ||
| 109 | Invoking these commands to visit a Todos or Todos Archive file | ||
| 110 | calls `todos-show' or `todos-show-archive', so that the file is | ||
| 111 | displayed correctly." | ||
| 112 | :type '(repeat function) | ||
| 113 | :group 'todos) | ||
| 114 | |||
| 115 | (defcustom todos-initial-file "Todo" | ||
| 116 | "Default file name offered on adding first Todos file." | ||
| 117 | :type 'string | ||
| 118 | :group 'todos) | ||
| 119 | |||
| 62 | (defcustom todos-initial-category "Todo" | 120 | (defcustom todos-initial-category "Todo" |
| 63 | "Default category name offered on initializing a new Todos file." | 121 | "Default category name offered on initializing a new Todos file." |
| 64 | :type 'string | 122 | :type 'string |
| @@ -84,17 +142,46 @@ These reflect the priorities of the items in each category." | |||
| 84 | :set 'todos-reset-prefix | 142 | :set 'todos-reset-prefix |
| 85 | :group 'todos) | 143 | :group 'todos) |
| 86 | 144 | ||
| 145 | (defun todos-reset-prefix (symbol value) | ||
| 146 | "The :set function for `todos-prefix' and `todos-number-prefix'." | ||
| 147 | (let ((oldvalue (symbol-value symbol)) | ||
| 148 | (files (append todos-files todos-archives))) | ||
| 149 | (custom-set-default symbol value) | ||
| 150 | (when (not (equal value oldvalue)) | ||
| 151 | (dolist (f files) | ||
| 152 | (with-current-buffer (find-file-noselect f) | ||
| 153 | (save-window-excursion | ||
| 154 | (todos-show) | ||
| 155 | (save-excursion | ||
| 156 | (widen) | ||
| 157 | (goto-char (point-min)) | ||
| 158 | (while (not (eobp)) | ||
| 159 | (remove-overlays (point) (point)); 'before-string prefix) | ||
| 160 | (forward-line))) | ||
| 161 | ;; Activate the new setting (save-restriction does not help). | ||
| 162 | (save-excursion (todos-category-select)))))))) | ||
| 163 | |||
| 87 | ;; FIXME: Update when window-width changes. Add todos-reset-separator to | 164 | ;; FIXME: Update when window-width changes. Add todos-reset-separator to |
| 88 | ;; window-configuration-change-hook in todos-mode? But this depends on the | 165 | ;; window-configuration-change-hook in todos-mode? But this depends on the |
| 89 | ;; value being window-width instead of a constant length. | 166 | ;; value being window-width instead of a constant length. |
| 90 | (defcustom todos-done-separator (make-string (window-width) ?-) | 167 | (defcustom todos-done-separator (make-string (window-width) ?_) |
| 91 | "String used to visual separate done from not done items. | 168 | "String used to visual separate done from not done items. |
| 92 | Displayed in a before-string overlay by `todos-toggle-view-done-items'." | 169 | Displayed in a before-string overlay by `todos-toggle-view-done-items'." |
| 93 | :type 'string | 170 | :type 'string |
| 94 | :initialize 'custom-initialize-default | 171 | :initialize 'custom-initialize-default |
| 95 | :set 'todos-reset-prefix | 172 | :set 'todos-reset-separator |
| 96 | :group 'todos) | 173 | :group 'todos) |
| 97 | 174 | ||
| 175 | ;; (defun todos-reset-separator (symbol value) | ||
| 176 | ;; "The :set function for `todos-done-separator' | ||
| 177 | ;; Also added to `window-configuration-change-hook' in Todos mode." | ||
| 178 | ;; (let ((oldvalue (symbol-value symbol))) | ||
| 179 | ;; (custom-set-default symbol value) | ||
| 180 | ;; (when (not (equal value oldvalue)) | ||
| 181 | ;; (make-string (window-width) ?_) | ||
| 182 | ;; ;; (save-excursion (todos-category-select)) | ||
| 183 | ;; ))) | ||
| 184 | |||
| 98 | (defcustom todos-done-string "DONE " | 185 | (defcustom todos-done-string "DONE " |
| 99 | "Identifying string appended to the front of done todos items." | 186 | "Identifying string appended to the front of done todos items." |
| 100 | :type 'string | 187 | :type 'string |
| @@ -102,6 +189,29 @@ Displayed in a before-string overlay by `todos-toggle-view-done-items'." | |||
| 102 | :set 'todos-reset-done-string | 189 | :set 'todos-reset-done-string |
| 103 | :group 'todos) | 190 | :group 'todos) |
| 104 | 191 | ||
| 192 | (defun todos-reset-done-string (symbol value) | ||
| 193 | "The :set function for user option `todos-done-string'." | ||
| 194 | (let ((oldvalue (symbol-value symbol)) | ||
| 195 | (files (append todos-files todos-archives))) | ||
| 196 | (custom-set-default symbol value) | ||
| 197 | ;; Need to reset this to get font-locking right. | ||
| 198 | (setq todos-done-string-start | ||
| 199 | (concat "^\\[" (regexp-quote todos-done-string))) | ||
| 200 | (when (not (equal value oldvalue)) | ||
| 201 | (dolist (f files) | ||
| 202 | (with-current-buffer (find-file-noselect f) | ||
| 203 | (let (buffer-read-only) | ||
| 204 | (widen) | ||
| 205 | (goto-char (point-min)) | ||
| 206 | (while (not (eobp)) | ||
| 207 | (if (re-search-forward | ||
| 208 | (concat "^" (regexp-quote todos-nondiary-start) | ||
| 209 | "\\(" (regexp-quote oldvalue) "\\)") | ||
| 210 | nil t) | ||
| 211 | (replace-match value t t nil 1) | ||
| 212 | (forward-line))) | ||
| 213 | (todos-category-select))))))) | ||
| 214 | |||
| 105 | (defcustom todos-comment-string "COMMENT" | 215 | (defcustom todos-comment-string "COMMENT" |
| 106 | "String inserted before optional comment appended to done item." | 216 | "String inserted before optional comment appended to done item." |
| 107 | :type 'string | 217 | :type 'string |
| @@ -109,6 +219,27 @@ Displayed in a before-string overlay by `todos-toggle-view-done-items'." | |||
| 109 | :set 'todos-reset-comment-string | 219 | :set 'todos-reset-comment-string |
| 110 | :group 'todos) | 220 | :group 'todos) |
| 111 | 221 | ||
| 222 | (defun todos-reset-comment-string (symbol value) | ||
| 223 | "The :set function for user option `todos-comment-string'." | ||
| 224 | (let ((oldvalue (symbol-value symbol)) | ||
| 225 | (files (append todos-files todos-archives))) | ||
| 226 | (custom-set-default symbol value) | ||
| 227 | (when (not (equal value oldvalue)) | ||
| 228 | (dolist (f files) | ||
| 229 | (with-current-buffer (find-file-noselect f) | ||
| 230 | (let (buffer-read-only) | ||
| 231 | (save-excursion | ||
| 232 | (widen) | ||
| 233 | (goto-char (point-min)) | ||
| 234 | (while (not (eobp)) | ||
| 235 | (if (re-search-forward | ||
| 236 | (concat | ||
| 237 | "\\[\\(" (regexp-quote oldvalue) "\\): [^]]*\\]") | ||
| 238 | nil t) | ||
| 239 | (replace-match value t t nil 1) | ||
| 240 | (forward-line))) | ||
| 241 | (todos-category-select)))))))) | ||
| 242 | |||
| 112 | (defcustom todos-show-with-done nil | 243 | (defcustom todos-show-with-done nil |
| 113 | "Non-nil to display done items in all categories." | 244 | "Non-nil to display done items in all categories." |
| 114 | :type 'boolean | 245 | :type 'boolean |
| @@ -119,147 +250,98 @@ Displayed in a before-string overlay by `todos-toggle-view-done-items'." | |||
| 119 | Argument CAT is the name of the current Todos category. | 250 | Argument CAT is the name of the current Todos category. |
| 120 | This function is the value of the user variable | 251 | This function is the value of the user variable |
| 121 | `todos-mode-line-function'." | 252 | `todos-mode-line-function'." |
| 122 | (let ((file (file-name-sans-extension | 253 | (let ((file (todos-short-file-name todos-current-todos-file))) |
| 123 | (file-name-nondirectory todos-current-todos-file)))) | 254 | (format "%s category %d: %s" file todos-category-number cat))) |
| 124 | (format "%s category %d: %s" file todos-category-number cat))) | ||
| 125 | 255 | ||
| 126 | (defcustom todos-mode-line-function 'todos-mode-line-control | 256 | (defcustom todos-mode-line-function 'todos-mode-line-control |
| 127 | "Function that returns a mode line control for Todos buffers. | 257 | "Function that returns a mode line control for Todos buffers. |
| 128 | The function is expected to take one argument that holds the name | 258 | The function expects one argument holding the name of the current |
| 129 | of the current Todos category. The resulting control becomes the | 259 | Todos category. The resulting control becomes the local value of |
| 130 | local value of `mode-line-buffer-identification' in each Todos | 260 | `mode-line-buffer-identification' in each Todos buffer." |
| 131 | buffer." | ||
| 132 | :type 'function | ||
| 133 | :group 'todos) | ||
| 134 | |||
| 135 | (defcustom todos-files-directory (locate-user-emacs-file "todos/") | ||
| 136 | "Directory where user's Todos files are saved." | ||
| 137 | :type 'directory | ||
| 138 | :group 'todos) | ||
| 139 | |||
| 140 | (defun todos-files (&optional archives) | ||
| 141 | "Default value of `todos-files-function'. | ||
| 142 | This returns the case-insensitive alphabetically sorted list of | ||
| 143 | file truenames in `todos-files-directory' with the extension | ||
| 144 | \".todo\". With non-nil ARCHIVES return the list of archive file | ||
| 145 | truenames (those with the extension \".toda\")." | ||
| 146 | (let ((files (mapcar 'file-truename | ||
| 147 | (directory-files todos-files-directory t | ||
| 148 | (if archives "\.toda$" "\.todo$") t)))) | ||
| 149 | (sort files (lambda (s1 s2) (let ((cis1 (upcase s1)) | ||
| 150 | (cis2 (upcase s2))) | ||
| 151 | (string< cis1 cis2)))))) | ||
| 152 | |||
| 153 | (defcustom todos-files-function 'todos-files | ||
| 154 | "Function returning the value of the variable `todos-files'. | ||
| 155 | This function should take an optional argument that, if non-nil, | ||
| 156 | makes it return the value of the variable `todos-archives'." | ||
| 157 | :type 'function | 261 | :type 'function |
| 158 | :group 'todos) | 262 | :group 'todos) |
| 159 | 263 | ||
| 160 | (defcustom todos-filter-function nil | 264 | (defun todos-special-buffer-name (buffer-type file-list) |
| 161 | "" | 265 | "Rename Todos special buffer. |
| 162 | :type 'function | 266 | The new name is concatenated from the string BUFFER-TYPE and the |
| 163 | :group 'todos) | 267 | names of the files in FILE-LIST. Used in the mode-list of |
| 164 | 268 | buffers displaying top priorities, diary items, regexp items | |
| 165 | (defcustom todos-priorities-rules (list) | 269 | etc. for single and multiple files." |
| 166 | "List of rules for choosing top priorities of each Todos file. | 270 | (let* ((flist (if (listp file-list) file-list (list file-list))) |
| 167 | The rules should be set interactively by invoking | 271 | (multi (> (length flist) 1)) |
| 168 | `todos-set-top-priorities'. | 272 | (fnames (mapconcat (lambda (f) (todos-short-file-name f)) |
| 169 | 273 | flist ", "))) | |
| 170 | Each rule is a list whose first element is a member of | 274 | (rename-buffer (format (concat "%s for file" (if multi "s" "") |
| 171 | `todos-files', whose second element is a number specifying the | 275 | " \"%s\"") buffer-type fnames)))) |
| 172 | default number of top priority items for the categories in that | 276 | |
| 173 | file, and whose third element is an alist whose elements are | 277 | (defcustom todos-filter-buffer "Todos filtered items" |
| 174 | conses of a category name in that file and the number of top | 278 | "Initial name of buffer in Todos Filter mode." |
| 175 | priority items in that category that `todos-top-priorities' shows | ||
| 176 | by default, which overrides the number for the file." | ||
| 177 | :type 'list | ||
| 178 | :group 'todos) | ||
| 179 | |||
| 180 | (defcustom todos-merged-files nil | ||
| 181 | "List of files for `todos-merged-top-priorities'." | ||
| 182 | :type `(set ,@(mapcar (lambda (x) (list 'const x)) | ||
| 183 | (funcall todos-files-function))) | ||
| 184 | :group 'todos) | ||
| 185 | |||
| 186 | (defcustom todos-prompt-merged-files nil | ||
| 187 | "Non-nil to prompt for merging files for `todos-filter-items'." | ||
| 188 | :type 'boolean | ||
| 189 | :group 'todos) | ||
| 190 | |||
| 191 | (defcustom todos-show-current-file t | ||
| 192 | "Non-nil to make `todos-show' visit the current Todos file. | ||
| 193 | Otherwise, `todos-show' always visits `todos-default-todos-file'." | ||
| 194 | :type 'boolean | ||
| 195 | :initialize 'custom-initialize-default | ||
| 196 | :set 'todos-toggle-show-current-file | ||
| 197 | :group 'todos) | ||
| 198 | |||
| 199 | ;; FIXME: omit second sentence from doc string? | ||
| 200 | (defcustom todos-default-todos-file (car (funcall todos-files-function)) | ||
| 201 | "Todos file visited by first session invocation of `todos-show'. | ||
| 202 | Normally this should be set by invoking `todos-change-default-file' | ||
| 203 | either directly or as a side effect of `todos-add-file'." | ||
| 204 | :type `(radio ,@(mapcar (lambda (x) (list 'const x)) | ||
| 205 | (funcall todos-files-function))) | ||
| 206 | :group 'todos) | ||
| 207 | |||
| 208 | (defcustom todos-visit-files-commands (list 'find-file 'dired-find-file) | ||
| 209 | "List of commands to visit files for `todos-after-find-file'. | ||
| 210 | Invoking these commands to visit a Todos or Todos Archive file | ||
| 211 | calls `todos-show' or `todos-show-archive', so that the file is | ||
| 212 | displayed correctly." | ||
| 213 | :type '(repeat function) | ||
| 214 | :group 'todos) | ||
| 215 | |||
| 216 | (defcustom todos-categories-buffer "*Todos Categories*" | ||
| 217 | "Name of buffer displayed by `todos-display-categories'." | ||
| 218 | :type 'string | 279 | :type 'string |
| 219 | :group 'todos) | 280 | :group 'todos) |
| 220 | 281 | ||
| 221 | (defcustom todos-categories-category-label "Category" | 282 | (defcustom todos-top-priorities-buffer "Todos top priorities" |
| 222 | "Category button label in `todos-categories-buffer'." | 283 | "Name of buffer displaying top priorities in Todos Filter mode." |
| 223 | :type 'string | 284 | :type 'string |
| 224 | :group 'todos) | 285 | :group 'todos) |
| 225 | 286 | ||
| 226 | (defcustom todos-categories-todo-label "Todo" | 287 | (defcustom todos-diary-items-buffer "Todos diary items" |
| 227 | "Todo button label in `todos-categories-buffer'." | 288 | "Name of buffer displaying diary items in Todos Filter mode." |
| 228 | :type 'string | 289 | :type 'string |
| 229 | :group 'todos) | 290 | :group 'todos) |
| 230 | 291 | ||
| 231 | (defcustom todos-categories-diary-label "Diary" | 292 | (defcustom todos-regexp-items-buffer "Todos regexp items" |
| 232 | "Diary button label in `todos-categories-buffer'." | 293 | "Name of buffer displaying regexp items in Todos Filter mode." |
| 233 | :type 'string | 294 | :type 'string |
| 234 | :group 'todos) | 295 | :group 'todos) |
| 235 | 296 | ||
| 236 | (defcustom todos-categories-done-label "Done" | 297 | (defcustom todos-custom-items-buffer "Todos custom items" |
| 237 | "Done button label in `todos-categories-buffer'." | 298 | "Name of buffer displaying custom items in Todos Filter mode." |
| 238 | :type 'string | 299 | :type 'string |
| 239 | :group 'todos) | 300 | :group 'todos) |
| 240 | 301 | ||
| 241 | (defcustom todos-categories-archived-label "Archived" | 302 | (defcustom todos-priorities-rules nil |
| 242 | "Archived button label in `todos-categories-buffer'." | 303 | "List of rules giving how many items `todos-top-priorities' shows. |
| 243 | :type 'string | 304 | This variable should be set interactively by |
| 305 | `\\[todos-set-top-priorities-in-file]' or | ||
| 306 | `\\[todos-set-top-priorities-in-category]'. | ||
| 307 | |||
| 308 | Each rule is a list of the form (FILE NUM ALIST), where FILE is a | ||
| 309 | member of `todos-files', NUM is a number specifying the default | ||
| 310 | number of top priority items for each category in that file, and | ||
| 311 | ALIST, when non-nil, consists of conses of a category name in | ||
| 312 | FILE and a number specifying the default number of top priority | ||
| 313 | items in that category, which overrides NUM." | ||
| 314 | :type 'list | ||
| 244 | :group 'todos) | 315 | :group 'todos) |
| 245 | 316 | ||
| 246 | (defcustom todos-categories-totals-label "Totals" | 317 | (defcustom todos-show-priorities 1 |
| 247 | "String to label total item counts in `todos-categories-buffer'." | 318 | "Default number of top priorities shown by `todos-top-priorities'." |
| 248 | :type 'string | 319 | :type 'integer |
| 249 | :group 'todos) | 320 | :group 'todos) |
| 250 | 321 | ||
| 251 | (defcustom todos-categories-number-separator " | " | 322 | (defcustom todos-filter-function nil |
| 252 | "String between number and category in `todos-categories-buffer'. | 323 | "" |
| 253 | This separates the number from the category name in the default | 324 | :type 'function |
| 254 | categories display according to priority." | ||
| 255 | :type 'string | ||
| 256 | :group 'todos) | 325 | :group 'todos) |
| 257 | 326 | ||
| 258 | (defcustom todos-categories-align 'center | 327 | (defcustom todos-filter-files nil |
| 259 | "Alignment of category names in `todos-categories-buffer'." | 328 | "List of default files for multifile item filtering." |
| 260 | :type '(radio (const left) (const center) (const right)) | 329 | :type `(set ,@(mapcar (lambda (f) (list 'const f)) |
| 330 | (mapcar 'todos-short-file-name | ||
| 331 | (funcall todos-files-function)))) | ||
| 261 | :group 'todos) | 332 | :group 'todos) |
| 262 | 333 | ||
| 334 | ;; FIXME: is there a better alternative to this? | ||
| 335 | (defun todos-reevaluate-filter-files-defcustom () | ||
| 336 | "Reevaluate defcustom of `todos-filter-files'. | ||
| 337 | Called after adding or deleting a Todos file." | ||
| 338 | (eval (defcustom todos-filter-files nil | ||
| 339 | "List of files for multifile item filtering." | ||
| 340 | :type `(set ,@(mapcar (lambda (f) (list 'const f)) | ||
| 341 | (mapcar 'todos-short-file-name | ||
| 342 | (funcall todos-files-function)))) | ||
| 343 | :group 'todos))) | ||
| 344 | |||
| 263 | (defcustom todos-ignore-archived-categories nil | 345 | (defcustom todos-ignore-archived-categories nil |
| 264 | "Non-nil to ignore categories with only archived items. | 346 | "Non-nil to ignore categories with only archived items. |
| 265 | When non-nil such categories are omitted from `todos-categories' | 347 | When non-nil such categories are omitted from `todos-categories' |
| @@ -273,21 +355,17 @@ archived categories." | |||
| 273 | :set 'todos-reset-categories | 355 | :set 'todos-reset-categories |
| 274 | :group 'todos) | 356 | :group 'todos) |
| 275 | 357 | ||
| 276 | ;; FIXME | 358 | (defun todos-reset-categories (symbol value) |
| 277 | (defcustom todos-edit-buffer "*Todos Edit*" | 359 | "The :set function for `todos-ignore-archived-categories'." |
| 278 | "Name of current buffer in Todos Edit mode." | 360 | (custom-set-default symbol value) |
| 279 | :type 'string | 361 | (dolist (f (funcall todos-files-function)) |
| 280 | :group 'todos) | 362 | (with-current-buffer (find-file-noselect f) |
| 281 | 363 | (if value | |
| 282 | ;; (defcustom todos-edit-buffer "*Todos Top Priorities*" | 364 | (setq todos-categories-full todos-categories |
| 283 | ;; "TODO Edit buffer name." | 365 | todos-categories (todos-truncate-categories-list)) |
| 284 | ;; :type 'string | 366 | (setq todos-categories todos-categories-full |
| 285 | ;; :group 'todos) | 367 | todos-categories-full nil)) |
| 286 | 368 | (todos-category-select)))) | |
| 287 | ;; (defcustom todos-edit-buffer "*Todos Diary Entries*" | ||
| 288 | ;; "TODO Edit buffer name." | ||
| 289 | ;; :type 'string | ||
| 290 | ;; :group 'todos) | ||
| 291 | 369 | ||
| 292 | (defcustom todos-use-only-highlighted-region t | 370 | (defcustom todos-use-only-highlighted-region t |
| 293 | "Non-nil to enable inserting only highlighted region as new item." | 371 | "Non-nil to enable inserting only highlighted region as new item." |
| @@ -317,29 +395,70 @@ the diary date." | |||
| 317 | :initialize 'custom-initialize-default | 395 | :initialize 'custom-initialize-default |
| 318 | :set 'todos-reset-nondiary-marker) | 396 | :set 'todos-reset-nondiary-marker) |
| 319 | 397 | ||
| 398 | (defun todos-reset-nondiary-marker (symbol value) | ||
| 399 | "The :set function for user option `todos-nondiary-marker'." | ||
| 400 | (let ((oldvalue (symbol-value symbol)) | ||
| 401 | (files (append todos-files todos-archives))) | ||
| 402 | (custom-set-default symbol value) | ||
| 403 | ;; Need to reset these to get font-locking right. | ||
| 404 | (setq todos-nondiary-start (nth 0 todos-nondiary-marker) | ||
| 405 | todos-nondiary-end (nth 1 todos-nondiary-marker) | ||
| 406 | todos-date-string-start | ||
| 407 | ;; See comment in defvar of `todos-date-string-start'. | ||
| 408 | (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|" | ||
| 409 | (regexp-quote diary-nonmarking-symbol) "\\)?")) | ||
| 410 | (when (not (equal value oldvalue)) | ||
| 411 | (dolist (f files) | ||
| 412 | (with-current-buffer (find-file-noselect f) | ||
| 413 | (let (buffer-read-only) | ||
| 414 | (widen) | ||
| 415 | (goto-char (point-min)) | ||
| 416 | (while (not (eobp)) | ||
| 417 | (if (re-search-forward | ||
| 418 | (concat "^\\(" todos-done-string-start "[^][]+] \\)?" | ||
| 419 | "\\(?1:" (regexp-quote (car oldvalue)) | ||
| 420 | "\\)" todos-date-pattern "\\( " | ||
| 421 | diary-time-regexp "\\)?\\(?2:" | ||
| 422 | (regexp-quote (cadr oldvalue)) "\\)") | ||
| 423 | nil t) | ||
| 424 | (progn | ||
| 425 | (replace-match (nth 0 value) t t nil 1) | ||
| 426 | (replace-match (nth 1 value) t t nil 2)) | ||
| 427 | (forward-line))) | ||
| 428 | (todos-category-select))))))) | ||
| 429 | |||
| 320 | (defcustom todos-print-function 'ps-print-buffer-with-faces | 430 | (defcustom todos-print-function 'ps-print-buffer-with-faces |
| 321 | "Function called to print buffer content; see `todos-print'." | 431 | "Function called to print buffer content; see `todos-print'." |
| 322 | :type 'symbol | 432 | :type 'symbol |
| 323 | :group 'todos) | 433 | :group 'todos) |
| 324 | 434 | ||
| 325 | ;; FIXME: rename, change meaning of zero, refer to todos-priorities-rules | 435 | (defcustom todos-completion-ignore-case nil |
| 326 | (defcustom todos-show-priorities 1 | 436 | "Non-nil means case of user input in `todos-read-*' is ignored." |
| 327 | "Default number of priorities to show by `todos-top-priorities'. | 437 | :type 'boolean |
| 328 | 0 means show all entries." | ||
| 329 | :type 'integer | ||
| 330 | :group 'todos) | ||
| 331 | |||
| 332 | (defcustom todos-print-priorities 0 | ||
| 333 | "Default number of priorities to print by `todos-print'. | ||
| 334 | 0 means print all entries." | ||
| 335 | :type 'integer | ||
| 336 | :group 'todos) | 438 | :group 'todos) |
| 337 | 439 | ||
| 338 | (defcustom todos-completion-ignore-case t ;; FIXME: nil for release? | 440 | (defcustom todos-highlight-item nil |
| 339 | "Non-nil means don't consider case significant in `todos-read-category'." | 441 | "Non-nil means highlight items at point." |
| 340 | :type 'boolean | 442 | :type 'boolean |
| 443 | :initialize 'custom-initialize-default | ||
| 444 | :set 'todos-reset-highlight-item | ||
| 341 | :group 'todos) | 445 | :group 'todos) |
| 342 | 446 | ||
| 447 | (defun todos-reset-highlight-item (symbol value) | ||
| 448 | "The :set function for `todos-highlight-item'." | ||
| 449 | (let ((oldvalue (symbol-value symbol)) | ||
| 450 | (files (append todos-files todos-archives))) | ||
| 451 | (custom-set-default symbol value) | ||
| 452 | (when (not (equal value oldvalue)) | ||
| 453 | (dolist (f files) | ||
| 454 | (let ((buf (get-file-buffer f))) | ||
| 455 | (when buf | ||
| 456 | (with-current-buffer buf | ||
| 457 | (require 'hl-line) | ||
| 458 | (if value | ||
| 459 | (hl-line-mode 1) | ||
| 460 | (hl-line-mode -1))))))))) | ||
| 461 | |||
| 343 | (defcustom todos-always-add-time-string nil | 462 | (defcustom todos-always-add-time-string nil |
| 344 | "Non-nil adds current time to a new item's date header by default. | 463 | "Non-nil adds current time to a new item's date header by default. |
| 345 | When the Todos insertion commands have a non-nil \"maybe-notime\" | 464 | When the Todos insertion commands have a non-nil \"maybe-notime\" |
| @@ -350,99 +469,341 @@ current time, if nil, they include it." | |||
| 350 | :group 'todos) | 469 | :group 'todos) |
| 351 | 470 | ||
| 352 | (defcustom todos-wrap-lines t | 471 | (defcustom todos-wrap-lines t |
| 353 | "Non-nil to wrap long lines by `todos-line-wrapping-function'." ;FIXME | 472 | "Non-nil to wrap long lines via `todos-line-wrapping-function'." |
| 354 | :group 'todos | 473 | :group 'todos |
| 355 | :type 'boolean) | 474 | :type 'boolean) |
| 356 | 475 | ||
| 357 | (defcustom todos-line-wrapping-function 'todos-wrap-and-indent | 476 | (defcustom todos-line-wrapping-function 'todos-wrap-and-indent |
| 358 | "Function called when `todos-wrap-lines' is non-nil." ;FIXME | 477 | "Line wrapping function used with non-nil `todos-wrap-lines'." |
| 359 | :group 'todos | 478 | :group 'todos |
| 360 | :type 'function) | 479 | :type 'function) |
| 361 | 480 | ||
| 481 | (defun todos-wrap-and-indent () | ||
| 482 | "Use word wrapping on long lines and indent with a wrap prefix. | ||
| 483 | The amount of indentation is given by user option | ||
| 484 | `todos-indent-to-here'." | ||
| 485 | (set (make-local-variable 'word-wrap) t) | ||
| 486 | (set (make-local-variable 'wrap-prefix) (make-string todos-indent-to-here 32)) | ||
| 487 | (unless (member '(continuation) fringe-indicator-alist) | ||
| 488 | (push '(continuation) fringe-indicator-alist))) | ||
| 489 | |||
| 490 | ;; FIXME: :set function (otherwise change takes effect only after revisiting) | ||
| 362 | (defcustom todos-indent-to-here 6 | 491 | (defcustom todos-indent-to-here 6 |
| 363 | "Number of spaces `todos-line-wrapping-function' indents to." | 492 | "Number of spaces `todos-line-wrapping-function' indents to." |
| 364 | :type 'integer | 493 | :type '(integer :validate |
| 494 | (lambda (widget) | ||
| 495 | (unless (> (widget-value widget) 0) | ||
| 496 | (widget-put widget :error | ||
| 497 | "Invalid value: must be a positive integer") | ||
| 498 | widget))) | ||
| 365 | :group 'todos) | 499 | :group 'todos) |
| 366 | 500 | ||
| 501 | (defun todos-indent () | ||
| 502 | "Indent from point to `todos-indent-to-here'." | ||
| 503 | (indent-to todos-indent-to-here todos-indent-to-here)) | ||
| 504 | |||
| 505 | (defcustom todos-todo-mode-date-time-regexp | ||
| 506 | (concat "\\(?1:[0-9]\\{4\\}\\)-\\(?2:[0-9]\\{2\\}\\)-" | ||
| 507 | "\\(?3:[0-9]\\{2\\}\\) \\(?4:[0-9]\\{2\\}:[0-9]\\{2\\}\\)") | ||
| 508 | "Regexp matching legacy todo-mode.el item date-time strings. | ||
| 509 | In order for `todos-convert-legacy-files' to correctly convert this | ||
| 510 | string to the current Todos format, the regexp must contain four | ||
| 511 | explicitly numbered groups (see `(elisp) Regexp Backslash'), | ||
| 512 | where group 1 matches a string for the year, group 2 a string for | ||
| 513 | the month, group 3 a string for the day and group 4 a string for | ||
| 514 | the time. The default value converts date-time strings built | ||
| 515 | using the default value of `todo-time-string-format' from | ||
| 516 | todo-mode.el." | ||
| 517 | :type 'regexp | ||
| 518 | :group 'todos) | ||
| 519 | |||
| 520 | (defgroup todos-categories nil | ||
| 521 | "Faces for Todos Categories mode." | ||
| 522 | :version "24.2" | ||
| 523 | :group 'todos) | ||
| 524 | |||
| 525 | (defcustom todos-categories-category-label "Category" | ||
| 526 | "Category button label in Todos Categories mode." | ||
| 527 | :type 'string | ||
| 528 | :group 'todos-categories) | ||
| 529 | |||
| 530 | (defcustom todos-categories-todo-label "Todo" | ||
| 531 | "Todo button label in Todos Categories mode." | ||
| 532 | :type 'string | ||
| 533 | :group 'todos-categories) | ||
| 534 | |||
| 535 | (defcustom todos-categories-diary-label "Diary" | ||
| 536 | "Diary button label in Todos Categories mode." | ||
| 537 | :type 'string | ||
| 538 | :group 'todos-categories) | ||
| 539 | |||
| 540 | (defcustom todos-categories-done-label "Done" | ||
| 541 | "Done button label in Todos Categories mode." | ||
| 542 | :type 'string | ||
| 543 | :group 'todos-categories) | ||
| 544 | |||
| 545 | (defcustom todos-categories-archived-label "Archived" | ||
| 546 | "Archived button label in Todos Categories mode." | ||
| 547 | :type 'string | ||
| 548 | :group 'todos-categories) | ||
| 549 | |||
| 550 | (defcustom todos-categories-totals-label "Totals" | ||
| 551 | "String to label total item counts in Todos Categories mode." | ||
| 552 | :type 'string | ||
| 553 | :group 'todos-categories) | ||
| 554 | |||
| 555 | (defcustom todos-categories-number-separator " | " | ||
| 556 | "String between number and category in Todos Categories mode. | ||
| 557 | This separates the number from the category name in the default | ||
| 558 | categories display according to priority." | ||
| 559 | :type 'string | ||
| 560 | :group 'todos-categories) | ||
| 561 | |||
| 562 | (defcustom todos-categories-align 'center | ||
| 563 | "Alignment of category names in Todos Categories mode." | ||
| 564 | :type '(radio (const left) (const center) (const right)) | ||
| 565 | :group 'todos-categories) | ||
| 566 | |||
| 367 | ;; --------------------------------------------------------------------------- | 567 | ;; --------------------------------------------------------------------------- |
| 368 | ;;; Faces | 568 | ;;; Faces |
| 369 | 569 | ||
| 370 | (defgroup todos-faces nil | 570 | (defgroup todos-faces nil |
| 371 | "Faces for the Todos modes." | 571 | "Faces for the Todos modes." |
| 372 | :version "24.1" | 572 | :version "24.2" |
| 373 | :group 'todos) | 573 | :group 'todos) |
| 374 | 574 | ||
| 375 | (defface todos-prefix-string | 575 | (defface todos-prefix-string |
| 376 | '((t :inherit font-lock-constant-face)) | 576 | ;; '((t :inherit font-lock-constant-face)) |
| 577 | '((((class grayscale) (background light)) | ||
| 578 | (:foreground "LightGray" :weight bold :underline t)) | ||
| 579 | (((class grayscale) (background dark)) | ||
| 580 | (:foreground "Gray50" :weight bold :underline t)) | ||
| 581 | (((class color) (min-colors 88) (background light)) (:foreground "dark cyan")) | ||
| 582 | (((class color) (min-colors 88) (background dark)) (:foreground "Aquamarine")) | ||
| 583 | (((class color) (min-colors 16) (background light)) (:foreground "CadetBlue")) | ||
| 584 | (((class color) (min-colors 16) (background dark)) (:foreground "Aquamarine")) | ||
| 585 | (((class color) (min-colors 8)) (:foreground "magenta")) | ||
| 586 | (t (:weight bold :underline t))) | ||
| 377 | "Face for Todos prefix string." | 587 | "Face for Todos prefix string." |
| 378 | :group 'todos-faces) | 588 | :group 'todos-faces) |
| 379 | 589 | ||
| 380 | (defface todos-mark | 590 | (defface todos-mark |
| 381 | '((t :inherit font-lock-warning-face)) | 591 | ;; '((t :inherit font-lock-warning-face)) |
| 592 | '((((class color) | ||
| 593 | (min-colors 88) | ||
| 594 | (background light)) | ||
| 595 | (:weight bold :foreground "Red1")) | ||
| 596 | (((class color) | ||
| 597 | (min-colors 88) | ||
| 598 | (background dark)) | ||
| 599 | (:weight bold :foreground "Pink")) | ||
| 600 | (((class color) | ||
| 601 | (min-colors 16) | ||
| 602 | (background light)) | ||
| 603 | (:weight bold :foreground "Red1")) | ||
| 604 | (((class color) | ||
| 605 | (min-colors 16) | ||
| 606 | (background dark)) | ||
| 607 | (:weight bold :foreground "Pink")) | ||
| 608 | (((class color) | ||
| 609 | (min-colors 8)) | ||
| 610 | (:foreground "red")) | ||
| 611 | (t | ||
| 612 | (:weight bold :inverse-video t))) | ||
| 382 | "Face for marks on Todos items." | 613 | "Face for marks on Todos items." |
| 383 | :group 'todos-faces) | 614 | :group 'todos-faces) |
| 384 | 615 | ||
| 385 | (defface todos-button | 616 | (defface todos-button |
| 386 | '((t :inherit widget-field)) | 617 | ;; '((t :inherit widget-field)) |
| 618 | '((((type tty)) | ||
| 619 | (:foreground "black" :background "yellow3")) | ||
| 620 | (((class grayscale color) | ||
| 621 | (background light)) | ||
| 622 | (:background "gray85")) | ||
| 623 | (((class grayscale color) | ||
| 624 | (background dark)) | ||
| 625 | (:background "dim gray")) | ||
| 626 | (t | ||
| 627 | (:slant italic))) | ||
| 387 | "Face for buttons in todos-display-categories." | 628 | "Face for buttons in todos-display-categories." |
| 388 | :group 'todos-faces) | 629 | :group 'todos-faces) |
| 389 | 630 | ||
| 390 | (defface todos-sorted-column | 631 | (defface todos-sorted-column |
| 391 | '((t :inherit fringe)) | 632 | ;; '((t :inherit fringe)) |
| 633 | '((((class color) | ||
| 634 | (background light)) | ||
| 635 | (:foreground "grey95")) | ||
| 636 | (((class color) | ||
| 637 | (background dark)) | ||
| 638 | (:foreground "grey10")) | ||
| 639 | (t | ||
| 640 | (:foreground "gray"))) | ||
| 392 | "Face for buttons in todos-display-categories." | 641 | "Face for buttons in todos-display-categories." |
| 393 | :group 'todos-faces) | 642 | :group 'todos-faces) |
| 394 | 643 | ||
| 395 | (defface todos-archived-only | 644 | (defface todos-archived-only |
| 396 | '((t (:inherit (shadow)))) | 645 | ;; '((t (:inherit (shadow)))) |
| 646 | '((((class color) | ||
| 647 | (background light)) | ||
| 648 | (:foreground "grey50")) | ||
| 649 | (((class color) | ||
| 650 | (background dark)) | ||
| 651 | (:foreground "grey70")) | ||
| 652 | (t | ||
| 653 | (:foreground "gray"))) | ||
| 397 | "Face for archived-only categories in todos-display-categories." | 654 | "Face for archived-only categories in todos-display-categories." |
| 398 | :group 'todos-faces) | 655 | :group 'todos-faces) |
| 399 | 656 | ||
| 400 | (defface todos-search | 657 | (defface todos-search |
| 401 | '((t :inherit match)) | 658 | ;; '((t :inherit match)) |
| 659 | '((((class color) | ||
| 660 | (min-colors 88) | ||
| 661 | (background light)) | ||
| 662 | (:background "yellow1")) | ||
| 663 | (((class color) | ||
| 664 | (min-colors 88) | ||
| 665 | (background dark)) | ||
| 666 | (:background "RoyalBlue3")) | ||
| 667 | (((class color) | ||
| 668 | (min-colors 8) | ||
| 669 | (background light)) | ||
| 670 | (:foreground "black" :background "yellow")) | ||
| 671 | (((class color) | ||
| 672 | (min-colors 8) | ||
| 673 | (background dark)) | ||
| 674 | (:foreground "white" :background "blue")) | ||
| 675 | (((type tty) | ||
| 676 | (class mono)) | ||
| 677 | (:inverse-video t)) | ||
| 678 | (t | ||
| 679 | (:background "gray"))) | ||
| 402 | "Face for matches found by todos-search." | 680 | "Face for matches found by todos-search." |
| 403 | :group 'todos-faces) | 681 | :group 'todos-faces) |
| 404 | 682 | ||
| 683 | (defface todos-diary-expired | ||
| 684 | ;; '((t :inherit font-lock-warning-face)) | ||
| 685 | '((((class color) | ||
| 686 | (min-colors 16)) | ||
| 687 | (:weight bold :foreground "DarkOrange")) | ||
| 688 | (((class color)) | ||
| 689 | (:weight bold :foreground "yellow")) | ||
| 690 | (t | ||
| 691 | (:weight bold))) | ||
| 692 | "Face for expired dates of diary items." | ||
| 693 | :group 'todos-faces) | ||
| 694 | (defvar todos-diary-expired-face 'todos-diary-expired) | ||
| 695 | |||
| 405 | (defface todos-date | 696 | (defface todos-date |
| 406 | '((t :inherit diary)) | 697 | '((t :inherit diary)) |
| 407 | "Face for Todos prefix string." | 698 | "Face for the date string of a Todos item." |
| 408 | :group 'todos-faces) | 699 | :group 'todos-faces) |
| 409 | (defvar todos-date-face 'todos-date) | 700 | (defvar todos-date-face 'todos-date) |
| 410 | 701 | ||
| 411 | (defface todos-time | 702 | (defface todos-time |
| 412 | '((t :inherit diary-time)) | 703 | '((t :inherit diary-time)) |
| 413 | "Face for Todos prefix string." | 704 | "Face for the time string of a Todos item." |
| 414 | :group 'todos-faces) | 705 | :group 'todos-faces) |
| 415 | (defvar todos-time-face 'todos-time) | 706 | (defvar todos-time-face 'todos-time) |
| 416 | 707 | ||
| 417 | (defface todos-done | 708 | (defface todos-done |
| 418 | '((t :inherit font-lock-comment-face)) | 709 | ;; '((t :inherit font-lock-comment-face)) |
| 710 | '((((class grayscale) | ||
| 711 | (background light)) | ||
| 712 | (:slant italic :weight bold :foreground "DimGray")) | ||
| 713 | (((class grayscale) | ||
| 714 | (background dark)) | ||
| 715 | (:slant italic :weight bold :foreground "LightGray")) | ||
| 716 | (((class color) | ||
| 717 | (min-colors 88) | ||
| 718 | (background light)) | ||
| 719 | (:foreground "Firebrick")) | ||
| 720 | (((class color) | ||
| 721 | (min-colors 88) | ||
| 722 | (background dark)) | ||
| 723 | (:foreground "chocolate1")) | ||
| 724 | (((class color) | ||
| 725 | (min-colors 16) | ||
| 726 | (background light)) | ||
| 727 | (:foreground "red")) | ||
| 728 | (((class color) | ||
| 729 | (min-colors 16) | ||
| 730 | (background dark)) | ||
| 731 | (:foreground "red1")) | ||
| 732 | (((class color) | ||
| 733 | (min-colors 8) | ||
| 734 | (background light)) | ||
| 735 | (:foreground "red")) | ||
| 736 | (((class color) | ||
| 737 | (min-colors 8) | ||
| 738 | (background dark)) | ||
| 739 | (:foreground "yellow")) | ||
| 740 | (t | ||
| 741 | (:slant italic :weight bold))) | ||
| 419 | "Face for done Todos item header string." | 742 | "Face for done Todos item header string." |
| 420 | :group 'todos-faces) | 743 | :group 'todos-faces) |
| 421 | (defvar todos-done-face 'todos-done) | 744 | (defvar todos-done-face 'todos-done) |
| 422 | 745 | ||
| 423 | (defface todos-comment | 746 | (defface todos-comment |
| 424 | '((t :inherit font-lock-comment-face)) | 747 | '((t :inherit todos-done)) |
| 425 | "Face for comments appended to done Todos items." | 748 | "Face for comments appended to done Todos items." |
| 426 | :group 'todos-faces) | 749 | :group 'todos-faces) |
| 427 | (defvar todos-comment-face 'todos-comment) | 750 | (defvar todos-comment-face 'todos-comment) |
| 428 | 751 | ||
| 429 | (defface todos-done-sep | 752 | (defface todos-done-sep |
| 430 | '((t :inherit font-lock-type-face)) | 753 | ;; '((t :inherit font-lock-type-face)) |
| 754 | '((((class grayscale) | ||
| 755 | (background light)) | ||
| 756 | (:weight bold :foreground "Gray90")) | ||
| 757 | (((class grayscale) | ||
| 758 | (background dark)) | ||
| 759 | (:weight bold :foreground "DimGray")) | ||
| 760 | (((class color) | ||
| 761 | (min-colors 88) | ||
| 762 | (background light)) | ||
| 763 | (:foreground "ForestGreen")) | ||
| 764 | (((class color) | ||
| 765 | (min-colors 88) | ||
| 766 | (background dark)) | ||
| 767 | (:foreground "PaleGreen")) | ||
| 768 | (((class color) | ||
| 769 | (min-colors 16) | ||
| 770 | (background light)) | ||
| 771 | (:foreground "ForestGreen")) | ||
| 772 | (((class color) | ||
| 773 | (min-colors 16) | ||
| 774 | (background dark)) | ||
| 775 | (:foreground "PaleGreen")) | ||
| 776 | (((class color) | ||
| 777 | (min-colors 8)) | ||
| 778 | (:foreground "green")) | ||
| 779 | (t | ||
| 780 | (:underline t :weight bold))) | ||
| 431 | "Face for separator string bewteen done and not done Todos items." | 781 | "Face for separator string bewteen done and not done Todos items." |
| 432 | :group 'todos-faces) | 782 | :group 'todos-faces) |
| 433 | (defvar todos-done-sep-face 'todos-done-sep) | 783 | (defvar todos-done-sep-face 'todos-done-sep) |
| 434 | 784 | ||
| 435 | (defvar todos-font-lock-keywords | 785 | (defvar todos-font-lock-keywords |
| 436 | (list | 786 | (list |
| 787 | ;; '(todos-nondiary-marker-matcher 1 todos-nondiary-face t) | ||
| 788 | ;; '(todos-nondiary-marker-matcher 2 todos-nondiary-face t) | ||
| 789 | '(todos-nondiary-marker-matcher 1 todos-done-sep-face t) | ||
| 790 | '(todos-nondiary-marker-matcher 2 todos-done-sep-face t) | ||
| 791 | ;; This is the face used by diary-lib.el. | ||
| 792 | '(todos-diary-nonmarking-matcher 1 font-lock-constant-face t) | ||
| 437 | '(todos-date-string-matcher 1 todos-date-face t) | 793 | '(todos-date-string-matcher 1 todos-date-face t) |
| 438 | '(todos-time-string-matcher 1 todos-time-face t) | 794 | '(todos-time-string-matcher 1 todos-time-face t) |
| 439 | '(todos-done-string-matcher 0 todos-done-face t) | 795 | '(todos-done-string-matcher 0 todos-done-face t) |
| 440 | '(todos-comment-string-matcher 1 todos-done-face t) | 796 | '(todos-comment-string-matcher 1 todos-done-face t) |
| 441 | '(todos-category-string-matcher 1 todos-done-sep-face t)) | 797 | ;; '(todos-category-string-matcher 1 todos-done-sep-face t) |
| 442 | "Font-locking for Todos mode.") | 798 | '(todos-category-string-matcher-1 1 todos-done-sep-face t t) |
| 799 | '(todos-category-string-matcher-2 1 todos-done-sep-face t t) | ||
| 800 | '(todos-diary-expired-matcher 1 todos-diary-expired-face t) | ||
| 801 | '(todos-diary-expired-matcher 2 todos-diary-expired-face t t) | ||
| 802 | ) | ||
| 803 | "Font-locking for Todos modes.") | ||
| 443 | 804 | ||
| 444 | ;; --------------------------------------------------------------------------- | 805 | ;; --------------------------------------------------------------------------- |
| 445 | ;;; Modes setup | 806 | ;;; Todos mode local variables and hook functions |
| 446 | 807 | ||
| 447 | (defvar todos-files (funcall todos-files-function) | 808 | (defvar todos-files (funcall todos-files-function) |
| 448 | "List of truenames of user's Todos files.") | 809 | "List of truenames of user's Todos files.") |
| @@ -450,34 +811,29 @@ current time, if nil, they include it." | |||
| 450 | (defvar todos-archives (funcall todos-files-function t) | 811 | (defvar todos-archives (funcall todos-files-function t) |
| 451 | "List of truenames of user's Todos archives.") | 812 | "List of truenames of user's Todos archives.") |
| 452 | 813 | ||
| 453 | (defvar todos-categories nil | ||
| 454 | "Alist of categories in the current Todos file. | ||
| 455 | The elements are cons cells whose car is a category name and | ||
| 456 | whose cdr is a vector of the category's item counts. These are, | ||
| 457 | in order, the numbers of todo items, todo items included in the | ||
| 458 | Diary, done items and archived items.") | ||
| 459 | |||
| 460 | (defvar todos-categories-full nil | ||
| 461 | "Variable holding non-truncated copy of `todos-categories'. | ||
| 462 | Set when `todos-ignore-archived-categories' is set to non-nil, to | ||
| 463 | restore full `todos-categories' list when | ||
| 464 | `todos-ignore-archived-categories' is reset to nil.") | ||
| 465 | |||
| 466 | (defvar todos-current-todos-file nil | 814 | (defvar todos-current-todos-file nil |
| 467 | "Variable holding the name of the currently active Todos file.") | 815 | "Variable holding the name of the currently active Todos file.") |
| 468 | ;; Automatically set by `todos-switch-todos-file'.") | ||
| 469 | 816 | ||
| 470 | ;; FIXME: Add function to kill-buffer-hook that sets this to the latest | 817 | (defun todos-show-current-file () |
| 471 | ;; existing Todos file or else todos-default-todos-file on killing the buffer | 818 | "Visit current instead of default Todos file with `todos-show'. |
| 472 | ;; of a Todos file | 819 | This function is added to `pre-command-hook' when user option |
| 473 | (defvar todos-global-current-todos-file nil | 820 | `todos-show-current-file' is set to non-nil." |
| 474 | "Variable holding name of current Todos file. | 821 | (setq todos-global-current-todos-file todos-current-todos-file)) |
| 475 | Used by functions called from outside of Todos mode to visit the | 822 | ;; (and (eq major-mode 'todos-mode) |
| 476 | current Todos file rather than the default Todos file (i.e. when | 823 | ;; (setq todos-global-current-todos-file (buffer-file-name)))) |
| 477 | users option `todos-show-current-file' is non-nil).") | 824 | |
| 825 | (defun todos-after-find-file () | ||
| 826 | "Show Todos files correctly when visited from outside of Todos mode." | ||
| 827 | (and (member this-command todos-visit-files-commands) | ||
| 828 | (= (- (point-max) (point-min)) (buffer-size)) | ||
| 829 | (member major-mode '(todos-mode todos-archive-mode)) | ||
| 830 | (todos-category-select))) | ||
| 478 | 831 | ||
| 479 | (defun todos-reset-global-current-todos-file () | 832 | (defun todos-reset-global-current-todos-file () |
| 480 | "" | 833 | "Update the value of `todos-global-current-todos-file'. |
| 834 | This becomes the latest existing Todos file or, if there is none, | ||
| 835 | the value of `todos-default-todos-file'. | ||
| 836 | This function is added to `kill-buffer-hook' in Todos mode." | ||
| 481 | (let ((buflist (copy-sequence (buffer-list))) | 837 | (let ((buflist (copy-sequence (buffer-list))) |
| 482 | (cur todos-global-current-todos-file)) | 838 | (cur todos-global-current-todos-file)) |
| 483 | (catch 'done | 839 | (catch 'done |
| @@ -492,23 +848,77 @@ users option `todos-show-current-file' is non-nil).") | |||
| 492 | (if (equal cur todos-global-current-todos-file) | 848 | (if (equal cur todos-global-current-todos-file) |
| 493 | (setq todos-global-current-todos-file todos-default-todos-file)))) | 849 | (setq todos-global-current-todos-file todos-default-todos-file)))) |
| 494 | 850 | ||
| 851 | (defvar todos-categories nil | ||
| 852 | "Alist of categories in the current Todos file. | ||
| 853 | The elements are cons cells whose car is a category name and | ||
| 854 | whose cdr is a vector of the category's item counts. These are, | ||
| 855 | in order, the numbers of todo items, todo items included in the | ||
| 856 | Diary, done items and archived items.") | ||
| 857 | |||
| 858 | (defvar todos-categories-full nil | ||
| 859 | "Variable holding non-truncated copy of `todos-categories'. | ||
| 860 | Set when `todos-ignore-archived-categories' is set to non-nil, to | ||
| 861 | restore full `todos-categories' list when | ||
| 862 | `todos-ignore-archived-categories' is reset to nil.") | ||
| 863 | |||
| 864 | (defvar todos-categories-with-marks nil | ||
| 865 | "Alist of categories and number of marked items they contain.") | ||
| 866 | |||
| 495 | (defvar todos-category-number 1 | 867 | (defvar todos-category-number 1 |
| 496 | "Variable holding the number of the current Todos category. | 868 | "Variable holding the number of the current Todos category. |
| 497 | This number is one more than the index of the category in | 869 | Todos categories are numbered starting from 1.") |
| 498 | `todos-categories'.") | ||
| 499 | 870 | ||
| 500 | (defvar todos-first-visit t | 871 | (defvar todos-first-visit t |
| 501 | "Non-nil if first display of this file in the current session. | 872 | "Non-nil if first display of this file in the current session. |
| 502 | See `todos-display-categories-first'.") | 873 | See `todos-display-categories-first'.") |
| 503 | 874 | ||
| 504 | ;; FIXME: rename? | 875 | (defvar todos-show-done-only nil |
| 505 | (defvar todos-tmp-buffer-name " *todo tmp*") | 876 | "If non-nil display only done items in current category. |
| 877 | Set by `todos-toggle-show-done-only' and used by | ||
| 878 | `todos-category-select'.") | ||
| 506 | 879 | ||
| 507 | (defvar todos-category-beg "--==-- " | 880 | ;; --------------------------------------------------------------------------- |
| 508 | "String marking beginning of category (inserted with its name).") | 881 | ;;; Global variables and helper functions |
| 509 | 882 | ||
| 510 | (defvar todos-category-done "==--== DONE " | 883 | (defvar todos-global-current-todos-file nil |
| 511 | "String marking beginning of category's done items.") | 884 | "Variable holding name of current Todos file. |
| 885 | Used by functions called from outside of Todos mode to visit the | ||
| 886 | current Todos file rather than the default Todos file (i.e. when | ||
| 887 | users option `todos-show-current-file' is non-nil).") | ||
| 888 | |||
| 889 | (defun todos-reevaluate-defcustoms () | ||
| 890 | "Reevaluate defcustoms that show list of Todos files." | ||
| 891 | (custom-set-default 'todos-default-todos-file | ||
| 892 | (symbol-value 'todos-default-todos-file)) | ||
| 893 | (todos-reevaluate-default-file-defcustom) | ||
| 894 | (custom-set-default 'todos-filter-files (symbol-value 'todos-filter-files)) | ||
| 895 | (todos-reevaluate-filter-files-defcustom)) | ||
| 896 | |||
| 897 | (defvar todos-edit-buffer "*Todos Edit*" | ||
| 898 | "Name of current buffer in Todos Edit mode.") | ||
| 899 | |||
| 900 | (defvar todos-categories-buffer "*Todos Categories*" | ||
| 901 | "Name of buffer in Todos Categories mode.") | ||
| 902 | |||
| 903 | (defvar todos-print-buffer "*Todos Print*" | ||
| 904 | "Name of buffer containing printable Todos text.") | ||
| 905 | |||
| 906 | (defvar todos-date-pattern | ||
| 907 | (let ((dayname (diary-name-pattern calendar-day-name-array nil t))) | ||
| 908 | (concat "\\(?:" dayname "\\|" | ||
| 909 | (let ((dayname) | ||
| 910 | ;; FIXME: how to choose between abbreviated and unabbreviated | ||
| 911 | ;; month name? | ||
| 912 | (monthname (format "\\(?:%s\\|\\*\\)" | ||
| 913 | (diary-name-pattern | ||
| 914 | calendar-month-name-array | ||
| 915 | calendar-month-abbrev-array t))) | ||
| 916 | (month "\\(?:[0-9]+\\|\\*\\)") | ||
| 917 | (day "\\(?:[0-9]+\\|\\*\\)") | ||
| 918 | (year "-?\\(?:[0-9]+\\|\\*\\)")) | ||
| 919 | (mapconcat 'eval calendar-date-display-form "")) | ||
| 920 | "\\)")) | ||
| 921 | "Regular expression matching a Todos date header.") | ||
| 512 | 922 | ||
| 513 | (defvar todos-nondiary-start (nth 0 todos-nondiary-marker) | 923 | (defvar todos-nondiary-start (nth 0 todos-nondiary-marker) |
| 514 | "String inserted before item date to block diary inclusion.") | 924 | "String inserted before item date to block diary inclusion.") |
| @@ -516,20 +926,1171 @@ See `todos-display-categories-first'.") | |||
| 516 | (defvar todos-nondiary-end (nth 1 todos-nondiary-marker) | 926 | (defvar todos-nondiary-end (nth 1 todos-nondiary-marker) |
| 517 | "String inserted after item date matching `todos-nondiary-start'.") | 927 | "String inserted after item date matching `todos-nondiary-start'.") |
| 518 | 928 | ||
| 519 | (defvar todos-show-done-only nil | 929 | ;; By itself this matches anything, because of the `?'; however, it's only |
| 520 | "If non-nil display only done items in current category. | 930 | ;; used in the context of `todos-date-pattern' (but Emacs Lisp lacks |
| 521 | Set by `todos-toggle-show-done-only' and used by | 931 | ;; lookahead). |
| 522 | `todos-category-select'.") | 932 | (defvar todos-date-string-start |
| 933 | (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|" | ||
| 934 | (regexp-quote diary-nonmarking-symbol) "\\)?") | ||
| 935 | "Regular expression matching part of item header before the date.") | ||
| 936 | |||
| 937 | (defvar todos-done-string-start | ||
| 938 | (concat "^\\[" (regexp-quote todos-done-string)) | ||
| 939 | "Regular expression matching start of done item.") | ||
| 940 | |||
| 941 | (defun todos-date-string-matcher (lim) | ||
| 942 | "Search for Todos date string within LIM for font-locking." | ||
| 943 | (re-search-forward | ||
| 944 | (concat todos-date-string-start "\\(?1:" todos-date-pattern "\\)") lim t)) | ||
| 945 | |||
| 946 | (defun todos-time-string-matcher (lim) | ||
| 947 | "Search for Todos time string within LIM for font-locking." | ||
| 948 | (re-search-forward (concat todos-date-string-start todos-date-pattern | ||
| 949 | " \\(?1:" diary-time-regexp "\\)") lim t)) | ||
| 950 | |||
| 951 | (defun todos-nondiary-marker-matcher (lim) | ||
| 952 | "Search for Todos nondiary markers within LIM for font-locking." | ||
| 953 | (re-search-forward (concat "^\\(?1:" (regexp-quote todos-nondiary-start) "\\)" | ||
| 954 | todos-date-pattern "\\(?: " diary-time-regexp | ||
| 955 | "\\)?\\(?2:" (regexp-quote todos-nondiary-end) "\\)") | ||
| 956 | lim t)) | ||
| 957 | |||
| 958 | (defun todos-diary-nonmarking-matcher (lim) | ||
| 959 | "Search for diary nonmarking symbol within LIM for font-locking." | ||
| 960 | (re-search-forward (concat "^\\(?1:" (regexp-quote diary-nonmarking-symbol) | ||
| 961 | "\\)" todos-date-pattern) lim t)) | ||
| 962 | |||
| 963 | (defun todos-diary-expired-matcher (lim) | ||
| 964 | "Search for expired diary item date within LIM for font-locking." | ||
| 965 | (when (re-search-forward (concat "^\\(?:" | ||
| 966 | (regexp-quote diary-nonmarking-symbol) | ||
| 967 | "\\)?\\(?1:" todos-date-pattern "\\) \\(?2:" | ||
| 968 | diary-time-regexp "\\)?") lim t) | ||
| 969 | (let* ((date (match-string-no-properties 1)) | ||
| 970 | (time (match-string-no-properties 2)) | ||
| 971 | ;; days-between needs a non-empty time string. | ||
| 972 | (date-time (concat date " " (or time "00:00")))) | ||
| 973 | (or (and (not (string-match ".+day\\|\\*" date)) | ||
| 974 | (< (days-between date-time (current-time-string)) 0)) | ||
| 975 | (todos-diary-expired-matcher lim))))) | ||
| 976 | |||
| 977 | (defun todos-done-string-matcher (lim) | ||
| 978 | "Search for Todos done header within LIM for font-locking." | ||
| 979 | (re-search-forward (concat todos-done-string-start | ||
| 980 | "[^][]+]") | ||
| 981 | lim t)) | ||
| 982 | |||
| 983 | (defun todos-comment-string-matcher (lim) | ||
| 984 | "Search for Todos done comment within LIM for font-locking." | ||
| 985 | (re-search-forward (concat "\\[\\(?1:" todos-comment-string "\\):") | ||
| 986 | lim t)) | ||
| 523 | 987 | ||
| 988 | ;; (defun todos-category-string-matcher (lim) | ||
| 989 | ;; "Search for Todos category name within LIM for font-locking. | ||
| 990 | ;; This is for fontifying category names appearing in Todos filter | ||
| 991 | ;; mode." | ||
| 992 | ;; (if (eq major-mode 'todos-filter-items-mode) | ||
| 993 | ;; (re-search-forward | ||
| 994 | ;; (concat "^\\(?:" todos-date-string-start "\\)?" todos-date-pattern | ||
| 995 | ;; "\\(?: " diary-time-regexp "\\)?\\(?:" | ||
| 996 | ;; (regexp-quote todos-nondiary-end) "\\)? \\(?1:\\[.+\\]\\)") | ||
| 997 | ;; lim t))) | ||
| 998 | |||
| 999 | (defun todos-category-string-matcher-1 (lim) | ||
| 1000 | "Search for Todos category name within LIM for font-locking. | ||
| 1001 | This is for fontifying category names appearing in Todos filter | ||
| 1002 | mode following done items." | ||
| 1003 | (if (eq major-mode 'todos-filter-items-mode) | ||
| 1004 | (re-search-forward (concat todos-done-string-start todos-date-pattern | ||
| 1005 | "\\(?: " diary-time-regexp | ||
| 1006 | ;; Use non-greedy operator to prevent | ||
| 1007 | ;; capturing possible following non-diary | ||
| 1008 | ;; date string. | ||
| 1009 | "\\)?] \\(?1:\\[.+?\\]\\)") | ||
| 1010 | lim t))) | ||
| 1011 | |||
| 1012 | (defun todos-category-string-matcher-2 (lim) | ||
| 1013 | "Search for Todos category name within LIM for font-locking. | ||
| 1014 | This is for fontifying category names appearing in Todos filter | ||
| 1015 | mode following todo (not done) items." | ||
| 1016 | (if (eq major-mode 'todos-filter-items-mode) | ||
| 1017 | (re-search-forward (concat todos-date-string-start todos-date-pattern | ||
| 1018 | "\\(?: " diary-time-regexp "\\)?\\(?:" | ||
| 1019 | (regexp-quote todos-nondiary-end) | ||
| 1020 | "\\)? \\(?1:\\[.+\\]\\)") | ||
| 1021 | lim t))) | ||
| 1022 | |||
| 1023 | (defun todos-category-number (cat) | ||
| 1024 | "Return the number of category CAT in this Todos file. | ||
| 1025 | The buffer-local variable `todos-category-number' holds this | ||
| 1026 | number as its value." | ||
| 1027 | (let ((categories (mapcar 'car todos-categories))) | ||
| 1028 | (setq todos-category-number | ||
| 1029 | ;; Increment by one, so that the highest priority category in Todos | ||
| 1030 | ;; Categories mode is numbered one rather than zero. | ||
| 1031 | (1+ (- (length categories) | ||
| 1032 | (length (member cat categories))))))) | ||
| 1033 | |||
| 1034 | (defun todos-current-category () | ||
| 1035 | "Return the name of the current category." | ||
| 1036 | (car (nth (1- todos-category-number) todos-categories))) | ||
| 1037 | |||
| 1038 | (defconst todos-category-beg "--==-- " | ||
| 1039 | "String marking beginning of category (inserted with its name).") | ||
| 1040 | |||
| 1041 | (defconst todos-category-done "==--== DONE " | ||
| 1042 | "String marking beginning of category's done items.") | ||
| 1043 | |||
| 1044 | (defun todos-category-select () | ||
| 1045 | "Display the current category correctly." | ||
| 1046 | (let ((name (todos-current-category)) | ||
| 1047 | cat-begin cat-end done-start done-sep-start done-end) | ||
| 1048 | (widen) | ||
| 1049 | (goto-char (point-min)) | ||
| 1050 | (re-search-forward | ||
| 1051 | (concat "^" (regexp-quote (concat todos-category-beg name)) "$") nil t) | ||
| 1052 | (setq cat-begin (1+ (line-end-position))) | ||
| 1053 | (setq cat-end (if (re-search-forward | ||
| 1054 | (concat "^" (regexp-quote todos-category-beg)) nil t) | ||
| 1055 | (match-beginning 0) | ||
| 1056 | (point-max))) | ||
| 1057 | (setq mode-line-buffer-identification | ||
| 1058 | (funcall todos-mode-line-function name)) | ||
| 1059 | (narrow-to-region cat-begin cat-end) | ||
| 1060 | (todos-prefix-overlays) | ||
| 1061 | (goto-char (point-min)) | ||
| 1062 | (if (re-search-forward (concat "\n\\(" (regexp-quote todos-category-done) | ||
| 1063 | "\\)") nil t) | ||
| 1064 | (progn | ||
| 1065 | (setq done-start (match-beginning 0)) | ||
| 1066 | (setq done-sep-start (match-beginning 1)) | ||
| 1067 | (setq done-end (match-end 0))) | ||
| 1068 | (error "Category %s is missing todos-category-done string" name)) | ||
| 1069 | (if todos-show-done-only | ||
| 1070 | (narrow-to-region (1+ done-end) (point-max)) | ||
| 1071 | (when (and todos-show-with-done | ||
| 1072 | (re-search-forward todos-done-string-start nil t)) | ||
| 1073 | ;; Now we want to see the done items, so reset displayed end to end of | ||
| 1074 | ;; done items. | ||
| 1075 | (setq done-start cat-end) | ||
| 1076 | ;; Make display overlay for done items separator string, unless there | ||
| 1077 | ;; already is one. | ||
| 1078 | (let* ((done-sep todos-done-separator) | ||
| 1079 | (ovs (overlays-at done-sep-start)) | ||
| 1080 | ov-sep) | ||
| 1081 | (unless (and ovs (string= (overlay-get (car ovs) 'display) done-sep)) | ||
| 1082 | (setq ov-sep (make-overlay done-sep-start done-end)) | ||
| 1083 | (overlay-put ov-sep 'display done-sep)))) | ||
| 1084 | (narrow-to-region (point-min) done-start) | ||
| 1085 | ;; Loading this from todos-mode, or adding it to the mode hook, causes | ||
| 1086 | ;; Emacs to hang in todos-item-start, at looking-at. | ||
| 1087 | (when todos-highlight-item | ||
| 1088 | (require 'hl-line) | ||
| 1089 | (hl-line-mode 1))))) | ||
| 1090 | |||
| 1091 | (defun todos-get-count (type &optional category) | ||
| 1092 | "Return count of TYPE items in CATEGORY. | ||
| 1093 | If CATEGORY is nil, default to the current category." | ||
| 1094 | (let* ((cat (or category (todos-current-category))) | ||
| 1095 | (counts (cdr (assoc cat todos-categories))) | ||
| 1096 | (idx (cond ((eq type 'todo) 0) | ||
| 1097 | ((eq type 'diary) 1) | ||
| 1098 | ((eq type 'done) 2) | ||
| 1099 | ((eq type 'archived) 3)))) | ||
| 1100 | (aref counts idx))) | ||
| 1101 | |||
| 1102 | (defun todos-set-count (type increment &optional category) | ||
| 1103 | "Increment count of TYPE items in CATEGORY by INCREMENT. | ||
| 1104 | If CATEGORY is nil, default to the current category." | ||
| 1105 | (let* ((cat (or category (todos-current-category))) | ||
| 1106 | (counts (cdr (assoc cat todos-categories))) | ||
| 1107 | (idx (cond ((eq type 'todo) 0) | ||
| 1108 | ((eq type 'diary) 1) | ||
| 1109 | ((eq type 'done) 2) | ||
| 1110 | ((eq type 'archived) 3)))) | ||
| 1111 | (aset counts idx (+ increment (aref counts idx))))) | ||
| 1112 | |||
| 1113 | (defun todos-set-categories () | ||
| 1114 | "Set `todos-categories' from the sexp at the top of the file." | ||
| 1115 | ;; New archive files created by `todos-move-category' are empty, which would | ||
| 1116 | ;; make the sexp test fail and raise an error, so in this case we skip it. | ||
| 1117 | (unless (zerop (buffer-size)) | ||
| 1118 | (save-excursion | ||
| 1119 | (save-restriction | ||
| 1120 | (widen) | ||
| 1121 | (goto-char (point-min)) | ||
| 1122 | ;; todos-truncate-categories-list needs non-nil todos-categories. | ||
| 1123 | (setq todos-categories-full | ||
| 1124 | (if (looking-at "\(\(\"") | ||
| 1125 | (read (buffer-substring-no-properties | ||
| 1126 | (line-beginning-position) | ||
| 1127 | (line-end-position))) | ||
| 1128 | (error "Invalid or missing todos-categories sexp")) | ||
| 1129 | todos-categories todos-categories-full))) | ||
| 1130 | (if (and todos-ignore-archived-categories | ||
| 1131 | (eq major-mode 'todos-mode)) | ||
| 1132 | (todos-truncate-categories-list) | ||
| 1133 | todos-categories-full))) | ||
| 1134 | |||
| 1135 | (defun todos-update-categories-sexp () | ||
| 1136 | "Update the `todos-categories' sexp at the top of the file." | ||
| 1137 | (let (buffer-read-only) | ||
| 1138 | (save-excursion | ||
| 1139 | (save-restriction | ||
| 1140 | (widen) | ||
| 1141 | (goto-char (point-min)) | ||
| 1142 | (if (looking-at (concat "^" (regexp-quote todos-category-beg))) | ||
| 1143 | (progn (newline) (goto-char (point-min))) | ||
| 1144 | ;; With empty buffer (e.g. with new archive in | ||
| 1145 | ;; `todos-move-category') `kill-line' signals end of buffer. | ||
| 1146 | (kill-region (line-beginning-position) (line-end-position))) | ||
| 1147 | ;; todos-categories-full is nil on adding first category. | ||
| 1148 | (prin1 (or todos-categories-full todos-categories) | ||
| 1149 | (current-buffer)))))) | ||
| 1150 | |||
| 1151 | (defun todos-make-categories-list (&optional force) | ||
| 1152 | "Return an alist of Todos categories and their item counts. | ||
| 1153 | With non-nil argument FORCE parse the entire file to build the | ||
| 1154 | list; otherwise, get the value by reading the sexp at the top of | ||
| 1155 | the file." | ||
| 1156 | (setq todos-categories nil) | ||
| 1157 | (save-excursion | ||
| 1158 | (save-restriction | ||
| 1159 | (widen) | ||
| 1160 | (goto-char (point-min)) | ||
| 1161 | (let (counts cat archive) | ||
| 1162 | (when buffer-file-name ; Don't check with `todos-convert-legacy-files'. | ||
| 1163 | ;; FIXME: can todos-archives be too old here? | ||
| 1164 | (unless (member buffer-file-name (funcall todos-files-function t)) | ||
| 1165 | (setq archive (concat (file-name-sans-extension | ||
| 1166 | todos-current-todos-file) ".toda")))) | ||
| 1167 | (while (not (eobp)) | ||
| 1168 | (cond ((looking-at (concat (regexp-quote todos-category-beg) | ||
| 1169 | "\\(.*\\)\n")) | ||
| 1170 | (setq cat (match-string-no-properties 1)) | ||
| 1171 | ;; Counts for each category: [todo diary done archive] | ||
| 1172 | (setq counts (make-vector 4 0)) | ||
| 1173 | (setq todos-categories | ||
| 1174 | (append todos-categories (list (cons cat counts)))) | ||
| 1175 | ;; todos-archives may be too old here (e.g. during | ||
| 1176 | ;; todos-move-category). | ||
| 1177 | (when (member archive (funcall todos-files-function t)) | ||
| 1178 | (let ((archive-count 0)) | ||
| 1179 | (with-current-buffer (find-file-noselect archive) | ||
| 1180 | (widen) | ||
| 1181 | (goto-char (point-min)) | ||
| 1182 | (when (re-search-forward | ||
| 1183 | (concat (regexp-quote todos-category-beg) cat) | ||
| 1184 | (point-max) t) | ||
| 1185 | (forward-line) | ||
| 1186 | (while (not (or (looking-at | ||
| 1187 | (concat | ||
| 1188 | (regexp-quote todos-category-beg) | ||
| 1189 | "\\(.*\\)\n")) | ||
| 1190 | (eobp))) | ||
| 1191 | (when (looking-at todos-done-string-start) | ||
| 1192 | (setq archive-count (1+ archive-count))) | ||
| 1193 | (forward-line)))) | ||
| 1194 | (todos-set-count 'archived archive-count cat)))) | ||
| 1195 | ((looking-at todos-done-string-start) | ||
| 1196 | (todos-set-count 'done 1 cat)) | ||
| 1197 | ((looking-at (concat "^\\(" | ||
| 1198 | (regexp-quote diary-nonmarking-symbol) | ||
| 1199 | "\\)?" todos-date-pattern)) | ||
| 1200 | (todos-set-count 'diary 1 cat) | ||
| 1201 | (todos-set-count 'todo 1 cat)) | ||
| 1202 | ((looking-at (concat todos-date-string-start todos-date-pattern)) | ||
| 1203 | (todos-set-count 'todo 1 cat)) | ||
| 1204 | ;; If first line is todos-categories list, use it and end loop | ||
| 1205 | ;; -- unless FORCEd to scan whole file. | ||
| 1206 | ((bobp) | ||
| 1207 | (unless force | ||
| 1208 | (setq todos-categories (read (buffer-substring-no-properties | ||
| 1209 | (line-beginning-position) | ||
| 1210 | (line-end-position)))) | ||
| 1211 | (goto-char (1- (point-max)))))) | ||
| 1212 | (forward-line))))) | ||
| 1213 | todos-categories) | ||
| 1214 | |||
| 1215 | (defun todos-truncate-categories-list () | ||
| 1216 | "Return a truncated alist of Todos categories plus item counts. | ||
| 1217 | Categories containing only archived items are omitted. This list | ||
| 1218 | is used in Todos mode when `todos-ignore-archived-categories' is | ||
| 1219 | non-nil." | ||
| 1220 | (let (cats) | ||
| 1221 | (dolist (catcons todos-categories-full cats) | ||
| 1222 | (let ((cat (car catcons))) | ||
| 1223 | (setq cats | ||
| 1224 | (append cats | ||
| 1225 | (unless (and (zerop (todos-get-count 'todo cat)) | ||
| 1226 | (zerop (todos-get-count 'done cat)) | ||
| 1227 | (not (zerop (todos-get-count 'archived cat)))) | ||
| 1228 | (list catcons)))))))) | ||
| 1229 | |||
| 1230 | (defun todos-check-format () | ||
| 1231 | "Signal an error if the current Todos file is ill-formatted. | ||
| 1232 | Otherwise return t. The error message gives the line number | ||
| 1233 | where the invalid formatting was found." | ||
| 1234 | (save-excursion | ||
| 1235 | (save-restriction | ||
| 1236 | (widen) | ||
| 1237 | (goto-char (point-min)) | ||
| 1238 | ;; Check for `todos-categories' sexp as the first line | ||
| 1239 | (let ((cats (prin1-to-string (or todos-categories-full todos-categories)))) | ||
| 1240 | (unless (looking-at (regexp-quote cats)) | ||
| 1241 | (error "Invalid or missing todos-categories sexp"))) | ||
| 1242 | (forward-line) | ||
| 1243 | (let ((legit (concat "\\(^" (regexp-quote todos-category-beg) "\\)" | ||
| 1244 | "\\|\\(" todos-date-string-start todos-date-pattern "\\)" | ||
| 1245 | "\\|\\(^[ \t]+[^ \t]*\\)" | ||
| 1246 | "\\|^$" | ||
| 1247 | "\\|\\(^" (regexp-quote todos-category-done) "\\)" | ||
| 1248 | "\\|\\(" todos-done-string-start "\\)"))) | ||
| 1249 | (while (not (eobp)) | ||
| 1250 | (unless (looking-at legit) | ||
| 1251 | (error "Illegitimate Todos file format at line %d" | ||
| 1252 | (line-number-at-pos (point)))) | ||
| 1253 | (forward-line))))) | ||
| 1254 | ;; (message "This Todos file is well-formatted.") | ||
| 1255 | t) | ||
| 1256 | |||
| 1257 | (defun todos-repair-categories-sexp () | ||
| 1258 | "Repair corrupt Todos categories sexp." | ||
| 1259 | (interactive) | ||
| 1260 | (let ((todos-categories-full (todos-make-categories-list t))) | ||
| 1261 | (todos-update-categories-sexp))) | ||
| 1262 | |||
| 1263 | (defvar todos-item-start (concat "\\(" todos-date-string-start "\\|" | ||
| 1264 | todos-done-string-start "\\)" | ||
| 1265 | todos-date-pattern) | ||
| 1266 | "String identifying start of a Todos item.") | ||
| 1267 | |||
| 1268 | (defun todos-item-start () | ||
| 1269 | "Move to start of current Todos item and return its position." | ||
| 1270 | (unless (or | ||
| 1271 | ;; Point is either on last item in this category or on the empty | ||
| 1272 | ;; line between done and not done items. | ||
| 1273 | (looking-at "^$") | ||
| 1274 | ;; There are no done items in this category yet. | ||
| 1275 | (looking-at (regexp-quote todos-category-beg))) | ||
| 1276 | (goto-char (line-beginning-position)) | ||
| 1277 | (while (not (looking-at todos-item-start)) | ||
| 1278 | (forward-line -1)) | ||
| 1279 | (point))) | ||
| 1280 | |||
| 1281 | (defun todos-item-end () | ||
| 1282 | "Move to end of current Todos item and return its position." | ||
| 1283 | ;; Items cannot end with a blank line. | ||
| 1284 | (unless (looking-at "^$") | ||
| 1285 | (let ((done (todos-done-item-p))) | ||
| 1286 | (todos-forward-item) | ||
| 1287 | ;; Adjust if item is last unfinished one before displayed done items. | ||
| 1288 | (when (and (not done) (todos-done-item-p)) | ||
| 1289 | (forward-line -1)) | ||
| 1290 | (backward-char)) | ||
| 1291 | (point))) | ||
| 1292 | |||
| 1293 | (defun todos-item-string () | ||
| 1294 | "Return bare text of current item as a string." | ||
| 1295 | (let ((opoint (point)) | ||
| 1296 | (start (todos-item-start)) | ||
| 1297 | (end (todos-item-end))) | ||
| 1298 | (goto-char opoint) | ||
| 1299 | (and start end (buffer-substring-no-properties start end)))) | ||
| 1300 | |||
| 1301 | (defun todos-remove-item () | ||
| 1302 | "Internal function called in editing, deleting or moving items." | ||
| 1303 | (let* ((beg (todos-item-start)) | ||
| 1304 | (end (progn (todos-item-end) (1+ (point)))) | ||
| 1305 | (ovs (overlays-in beg beg))) | ||
| 1306 | ;; There can be both prefix/number and mark overlays. | ||
| 1307 | (while ovs (delete-overlay (car ovs)) (pop ovs)) | ||
| 1308 | (delete-region beg end))) | ||
| 1309 | |||
| 1310 | (defun todos-diary-item-p () | ||
| 1311 | "Return non-nil if item at point is marked for diary inclusion." | ||
| 1312 | (save-excursion | ||
| 1313 | (todos-item-start) | ||
| 1314 | ;; (looking-at todos-date-pattern))) | ||
| 1315 | (not (looking-at (regexp-quote todos-nondiary-start))))) | ||
| 1316 | |||
| 1317 | (defun todos-done-item-p () | ||
| 1318 | "Return non-nil if item at point is a done item." | ||
| 1319 | (save-excursion | ||
| 1320 | (todos-item-start) | ||
| 1321 | (looking-at todos-done-string-start))) | ||
| 1322 | |||
| 1323 | (defvar todos-item-mark (propertize (if (equal todos-prefix "*") "@" "*") | ||
| 1324 | 'face 'todos-mark) | ||
| 1325 | "String used to mark items.") | ||
| 1326 | |||
| 1327 | (defun todos-marked-item-p () | ||
| 1328 | "If this item is marked, return mark overlay." | ||
| 1329 | (let ((ovs (overlays-in (line-beginning-position) (line-beginning-position))) | ||
| 1330 | (mark todos-item-mark) | ||
| 1331 | ov marked) | ||
| 1332 | (catch 'stop | ||
| 1333 | (while ovs | ||
| 1334 | (setq ov (pop ovs)) | ||
| 1335 | (and (equal (overlay-get ov 'before-string) mark) | ||
| 1336 | (throw 'stop (setq marked t))))) | ||
| 1337 | (when marked ov))) | ||
| 1338 | |||
| 1339 | (defun todos-insert-with-overlays (item) | ||
| 1340 | "Insert ITEM at point and update prefix/priority number overlays." | ||
| 1341 | (todos-item-start) | ||
| 1342 | (insert item "\n") | ||
| 1343 | (todos-backward-item) | ||
| 1344 | (todos-prefix-overlays)) | ||
| 1345 | |||
| 1346 | (defun todos-prefix-overlays () | ||
| 1347 | "Put before-string overlay in front of this category's items. | ||
| 1348 | The overlay's value is the string `todos-prefix' or with non-nil | ||
| 1349 | `todos-number-prefix' an integer in the sequence from 1 to the | ||
| 1350 | number of todo or done items in the category indicating the | ||
| 1351 | item's priority. Todo and done items are numbered independently | ||
| 1352 | of each other." | ||
| 1353 | (when (or todos-number-prefix | ||
| 1354 | (not (string-match "^[[:space:]]*$" todos-prefix))) | ||
| 1355 | (let ((prefix (propertize (concat todos-prefix " ") | ||
| 1356 | 'face 'todos-prefix-string)) | ||
| 1357 | (num 0)) | ||
| 1358 | (save-excursion | ||
| 1359 | (goto-char (point-min)) | ||
| 1360 | (while (not (eobp)) | ||
| 1361 | (when (or (todos-date-string-matcher (line-end-position)) | ||
| 1362 | (todos-done-string-matcher (line-end-position))) | ||
| 1363 | (goto-char (match-beginning 0)) | ||
| 1364 | (when todos-number-prefix | ||
| 1365 | (setq num (1+ num)) | ||
| 1366 | ;; Reset number to 1 for first done item. | ||
| 1367 | (when (and (looking-at todos-done-string-start) | ||
| 1368 | (looking-back (concat "^" | ||
| 1369 | (regexp-quote todos-category-done) | ||
| 1370 | "\n"))) | ||
| 1371 | (setq num 1)) | ||
| 1372 | (setq prefix (propertize (concat (number-to-string num) " ") | ||
| 1373 | 'face 'todos-prefix-string))) | ||
| 1374 | (let ((ovs (overlays-in (point) (point))) | ||
| 1375 | marked ov-pref) | ||
| 1376 | (if ovs | ||
| 1377 | (dolist (ov ovs) | ||
| 1378 | (let ((val (overlay-get ov 'before-string))) | ||
| 1379 | (if (equal val "*") | ||
| 1380 | (setq marked t) | ||
| 1381 | (setq ov-pref val))))) | ||
| 1382 | (unless (equal ov-pref prefix) | ||
| 1383 | ;; Why doesn't this work? | ||
| 1384 | ;; (remove-overlays (point) (point) 'before-string) | ||
| 1385 | (remove-overlays (point) (point)) | ||
| 1386 | (overlay-put (make-overlay (point) (point)) | ||
| 1387 | 'before-string prefix) | ||
| 1388 | (and marked (overlay-put (make-overlay (point) (point)) | ||
| 1389 | 'before-string todos-item-mark))))) | ||
| 1390 | (forward-line)))))) | ||
| 1391 | |||
| 1392 | (defun todos-read-file-name (prompt &optional archive mustmatch) | ||
| 1393 | "Choose and return the name of a Todos file, prompting with PROMPT. | ||
| 1394 | |||
| 1395 | Show completions with TAB or SPC; the names are shown in short | ||
| 1396 | form but the absolute truename is returned. With non-nil ARCHIVE | ||
| 1397 | return the absolute truename of a Todos archive file. With non-nil | ||
| 1398 | MUSTMATCH the name of an existing file must be chosen; | ||
| 1399 | otherwise, a new file name is allowed." | ||
| 1400 | (unless (file-exists-p todos-files-directory) | ||
| 1401 | (make-directory todos-files-directory)) | ||
| 1402 | (let ((completion-ignore-case todos-completion-ignore-case) | ||
| 1403 | (files (mapcar 'file-name-sans-extension | ||
| 1404 | (directory-files todos-files-directory nil | ||
| 1405 | (if archive "\.toda$" "\.todo$")))) | ||
| 1406 | (file "")) | ||
| 1407 | (while (string= "" file) | ||
| 1408 | (setq file (completing-read prompt files nil mustmatch)) | ||
| 1409 | (setq prompt "Enter a non-empty name (TAB for list of current files): ")) | ||
| 1410 | (setq file (concat todos-files-directory file | ||
| 1411 | (if archive ".toda" ".todo"))) | ||
| 1412 | (unless mustmatch | ||
| 1413 | (when (not (member file todos-files)) | ||
| 1414 | (todos-validate-name file 'file))) | ||
| 1415 | (file-truename file))) | ||
| 1416 | |||
| 1417 | (defun todos-read-category (prompt &optional mustmatch) | ||
| 1418 | "Choose and return a category name, prompting with PROMPT. | ||
| 1419 | Show completions with TAB or SPC. With non-nil MUSTMATCH the | ||
| 1420 | name must be that of an existing category; otherwise, a new | ||
| 1421 | category name is allowed, after checking its validity." | ||
| 1422 | ;; Allow SPC to insert spaces, for adding new category names. | ||
| 1423 | (let ((map minibuffer-local-completion-map)) | ||
| 1424 | (define-key map " " nil) | ||
| 1425 | ;; Make a copy of todos-categories in case history-delete-duplicates is | ||
| 1426 | ;; non-nil, which makes completing-read alter todos-categories. | ||
| 1427 | (let* ((categories (copy-sequence todos-categories)) | ||
| 1428 | (history (cons 'todos-categories (1+ todos-category-number))) | ||
| 1429 | (completion-ignore-case todos-completion-ignore-case) | ||
| 1430 | (cat (completing-read prompt todos-categories nil | ||
| 1431 | mustmatch nil history | ||
| 1432 | ;; Default for existing categories is the | ||
| 1433 | ;; current category. | ||
| 1434 | (if todos-categories | ||
| 1435 | (todos-current-category) | ||
| 1436 | ;; Trigger prompt for initial category | ||
| 1437 | "")))) | ||
| 1438 | (unless mustmatch | ||
| 1439 | (when (not (assoc cat categories)) | ||
| 1440 | (todos-validate-name cat 'category) | ||
| 1441 | (if (y-or-n-p (format (concat "There is no category \"%s\" in " | ||
| 1442 | "this file; add it? ") cat)) | ||
| 1443 | (todos-add-category cat) | ||
| 1444 | (keyboard-quit)))) | ||
| 1445 | ;; Restore the original value of todos-categories. | ||
| 1446 | (setq todos-categories categories) | ||
| 1447 | cat))) | ||
| 1448 | |||
| 1449 | (defun todos-validate-name (name type) | ||
| 1450 | "Prompt for new NAME for TYPE until it is valid, then return it. | ||
| 1451 | TYPE can be either a file or a category" | ||
| 1452 | (let (prompt file cat shortname) | ||
| 1453 | (while | ||
| 1454 | (and (cond ((string= "" name) | ||
| 1455 | (setq prompt | ||
| 1456 | (cond ((eq type 'file) | ||
| 1457 | ;; FIXME: just todos-files ? | ||
| 1458 | (if (funcall (todos-files)) | ||
| 1459 | "Enter a non-empty file name: " | ||
| 1460 | ;; Empty string passed by todos-show to | ||
| 1461 | ;; prompt for initial Todos file. | ||
| 1462 | (concat "Initial file name [" | ||
| 1463 | todos-initial-file "]: "))) | ||
| 1464 | ((eq type 'category) | ||
| 1465 | (if todos-categories | ||
| 1466 | "Enter a non-empty category name: " | ||
| 1467 | ;; Empty string passed by todos-show to | ||
| 1468 | ;; prompt for initial category of a new | ||
| 1469 | ;; Todos file. | ||
| 1470 | (concat "Initial category name [" | ||
| 1471 | todos-initial-category "]: ")))))) | ||
| 1472 | ((string-match "\\`\\s-+\\'" name) | ||
| 1473 | (setq prompt | ||
| 1474 | "Enter a name that does not contain only white space: ")) | ||
| 1475 | ((and (eq type 'file) (member name todos-files)) | ||
| 1476 | (setq prompt "Enter a non-existing file name: ")) | ||
| 1477 | ((and (eq type 'category) (assoc name todos-categories)) | ||
| 1478 | (setq prompt "Enter a non-existing category name: "))) | ||
| 1479 | (setq name (if (or (and (eq type 'file) todos-files) | ||
| 1480 | (and (eq type 'category) todos-categories)) | ||
| 1481 | (read-from-minibuffer prompt) | ||
| 1482 | ;; Offer default initial name. | ||
| 1483 | (read-string prompt nil nil | ||
| 1484 | (cond ((eq type 'file) | ||
| 1485 | todos-initial-file) | ||
| 1486 | ((eq type 'category) | ||
| 1487 | todos-initial-category)))))))) | ||
| 1488 | name) | ||
| 1489 | |||
| 1490 | ;; Adapted from calendar-read-date and calendar-date-string. | ||
| 1491 | (defun todos-read-date () | ||
| 1492 | "Prompt for Gregorian date and return it in the current format. | ||
| 1493 | Also accepts `*' as an unspecified month, day, or year." | ||
| 1494 | (let* ((year (calendar-read | ||
| 1495 | ;; FIXME: maybe better like monthname with RET for current month | ||
| 1496 | "Year (>0 or * for any year): " | ||
| 1497 | (lambda (x) (or (eq x '*) (> x 0))) | ||
| 1498 | (number-to-string (calendar-extract-year | ||
| 1499 | (calendar-current-date))))) | ||
| 1500 | (month-array (vconcat calendar-month-name-array (vector "*"))) | ||
| 1501 | (abbrevs (vconcat calendar-month-abbrev-array (vector "*"))) | ||
| 1502 | (completion-ignore-case todos-completion-ignore-case) | ||
| 1503 | (monthname (completing-read | ||
| 1504 | "Month name (RET for current month, * for any month): " | ||
| 1505 | (mapcar 'list (append month-array nil)) | ||
| 1506 | nil t nil nil | ||
| 1507 | (calendar-month-name (calendar-extract-month | ||
| 1508 | (calendar-current-date)) t))) | ||
| 1509 | (month (cdr (assoc-string | ||
| 1510 | monthname (calendar-make-alist month-array nil nil | ||
| 1511 | abbrevs)))) | ||
| 1512 | (last (if (= month 13) | ||
| 1513 | 31 ; FIXME: what about shorter months? | ||
| 1514 | (let ((yr (if (eq year '*) | ||
| 1515 | 1999 ; FIXME: no Feb. 29 | ||
| 1516 | year))) | ||
| 1517 | (calendar-last-day-of-month month yr)))) | ||
| 1518 | day dayname) | ||
| 1519 | (while (if (numberp day) (or (< day 0) (< last day)) (not (eq day '*))) | ||
| 1520 | (setq day (read-from-minibuffer | ||
| 1521 | (format "Day (1-%d or RET for today or * for any day): " last) | ||
| 1522 | nil nil t nil | ||
| 1523 | (number-to-string | ||
| 1524 | (calendar-extract-day (calendar-current-date)))))) | ||
| 1525 | (setq year (if (eq year '*) (symbol-name '*) (number-to-string year))) | ||
| 1526 | (setq day (if (eq day '*) (symbol-name '*) (number-to-string day))) | ||
| 1527 | ;; FIXME: make abbreviation customizable | ||
| 1528 | (setq monthname | ||
| 1529 | (or (and (= month 13) "*") | ||
| 1530 | (calendar-month-name (calendar-extract-month (list month day year)) | ||
| 1531 | t))) | ||
| 1532 | (mapconcat 'eval calendar-date-display-form ""))) | ||
| 1533 | |||
| 1534 | (defun todos-read-dayname () | ||
| 1535 | "Choose name of a day of the week with completion and return it." | ||
| 1536 | (let ((completion-ignore-case todos-completion-ignore-case)) | ||
| 1537 | (completing-read "Enter a day name: " | ||
| 1538 | (append calendar-day-name-array nil) | ||
| 1539 | nil t))) | ||
| 1540 | |||
| 1541 | (defun todos-read-time () | ||
| 1542 | "Prompt for and return a valid clock time as a string. | ||
| 1543 | |||
| 1544 | Valid time strings are those matching `diary-time-regexp'. | ||
| 1545 | Typing `<return>' at the prompt returns the current time, if the | ||
| 1546 | user option `todos-always-add-time-string' is non-nil, otherwise | ||
| 1547 | the empty string (i.e., no time string)." | ||
| 1548 | (let (valid answer) | ||
| 1549 | (while (not valid) | ||
| 1550 | (setq answer (read-string "Enter a clock time: " nil nil | ||
| 1551 | (when todos-always-add-time-string | ||
| 1552 | (substring (current-time-string) 11 16)))) | ||
| 1553 | (when (or (string= "" answer) | ||
| 1554 | (string-match diary-time-regexp answer)) | ||
| 1555 | (setq valid t))) | ||
| 1556 | answer)) | ||
| 1557 | |||
| 1558 | (defun todos-convert-legacy-date-time () | ||
| 1559 | "Return converted date-time string. | ||
| 1560 | Helper function for `todos-convert-legacy-files'." | ||
| 1561 | (let* ((year (match-string 1)) | ||
| 1562 | (month (match-string 2)) | ||
| 1563 | (monthname (calendar-month-name (string-to-number month) t)) | ||
| 1564 | (day (match-string 3)) | ||
| 1565 | (time (match-string 4)) | ||
| 1566 | dayname) | ||
| 1567 | (replace-match "") | ||
| 1568 | (insert (mapconcat 'eval calendar-date-display-form "") | ||
| 1569 | (when time (concat " " time))))) | ||
| 1570 | |||
| 1571 | ;; --------------------------------------------------------------------------- | ||
| 1572 | ;;; Item filtering | ||
| 1573 | |||
| 1574 | (defvar todos-multiple-files nil | ||
| 1575 | "List of files returned by `todos-multiple-files' widget.") | ||
| 1576 | |||
| 1577 | (defvar todos-multiple-files-widget nil | ||
| 1578 | "Variable holding widget created by `todos-multiple-files'.") | ||
| 1579 | |||
| 1580 | (defun todos-multiple-files () | ||
| 1581 | "Pop to a buffer with a widget for choosing multiple filter files." | ||
| 1582 | (require 'widget) | ||
| 1583 | (eval-when-compile | ||
| 1584 | (require 'wid-edit)) | ||
| 1585 | (with-current-buffer (get-buffer-create "*Todos Filter Files*") | ||
| 1586 | (pop-to-buffer (current-buffer)) | ||
| 1587 | (erase-buffer) | ||
| 1588 | (kill-all-local-variables) | ||
| 1589 | (widget-insert "Select files for generating the top priorities list.\n\n") | ||
| 1590 | (setq todos-multiple-files-widget | ||
| 1591 | (widget-create | ||
| 1592 | `(set ,@(mapcar (lambda (x) (list 'const x)) | ||
| 1593 | (mapcar 'todos-short-file-name | ||
| 1594 | (funcall todos-files-function)))))) | ||
| 1595 | (widget-insert "\n") | ||
| 1596 | (widget-create 'push-button | ||
| 1597 | :notify (lambda (widget &rest ignore) | ||
| 1598 | (setq todos-multiple-files 'quit) | ||
| 1599 | (quit-window t) | ||
| 1600 | (exit-recursive-edit)) | ||
| 1601 | "Cancel") | ||
| 1602 | (widget-insert " ") | ||
| 1603 | (widget-create 'push-button | ||
| 1604 | :notify (lambda (&rest ignore) | ||
| 1605 | (setq todos-multiple-files | ||
| 1606 | (mapcar (lambda (f) | ||
| 1607 | (concat todos-files-directory | ||
| 1608 | f ".todo")) | ||
| 1609 | (widget-value | ||
| 1610 | todos-multiple-files-widget))) | ||
| 1611 | (quit-window t) | ||
| 1612 | (exit-recursive-edit)) | ||
| 1613 | "Apply") | ||
| 1614 | (use-local-map widget-keymap) | ||
| 1615 | (widget-setup)) | ||
| 1616 | (message "Click \"Apply\" after selecting files.") | ||
| 1617 | (recursive-edit)) | ||
| 1618 | |||
| 1619 | ;; FIXME: should done and archived items be included? Maybe just for regexp | ||
| 1620 | ;; and custom filters? | ||
| 1621 | (defun todos-filter-items (filter &optional multifile) | ||
| 1622 | "Build and display a list of items from different categories. | ||
| 1623 | |||
| 1624 | The items are selected according to the value of FILTER, which | ||
| 1625 | can be `top' for top priority items, `diary' for diary items, | ||
| 1626 | `regexp' for items matching a regular expresion entered by the | ||
| 1627 | user, or `custom' for items chosen by user-defined function | ||
| 1628 | `todos-filter-function'. | ||
| 1629 | |||
| 1630 | With non-nil argument MULTIFILE list top priorities of multiple | ||
| 1631 | Todos files, by default those in `todos-filter-files'." | ||
| 1632 | (let ((num (if (consp filter) (cdr filter) todos-show-priorities)) | ||
| 1633 | (buf (get-buffer-create todos-filter-buffer)) | ||
| 1634 | (files (list todos-current-todos-file)) | ||
| 1635 | regexp fname bufstr cat beg end done) | ||
| 1636 | (when multifile | ||
| 1637 | (setq files (if (or (consp filter) (null todos-filter-files)) | ||
| 1638 | (progn (todos-multiple-files) todos-multiple-files) | ||
| 1639 | todos-filter-files) | ||
| 1640 | todos-multiple-files nil)) | ||
| 1641 | (if (eq files 'quit) (keyboard-quit)) | ||
| 1642 | (if (null files) | ||
| 1643 | (error "No files have been chosen for filtering") | ||
| 1644 | (with-current-buffer buf | ||
| 1645 | (erase-buffer) | ||
| 1646 | (kill-all-local-variables) | ||
| 1647 | (todos-filter-items-mode)) | ||
| 1648 | (when (eq filter 'regexp) | ||
| 1649 | (setq regexp (read-string "Enter a regular expression: "))) | ||
| 1650 | (save-current-buffer | ||
| 1651 | (dolist (f files) | ||
| 1652 | ;; Before inserting file contents into temp buffer, save a modified | ||
| 1653 | ;; buffer visiting it. | ||
| 1654 | (let ((bf (find-buffer-visiting f))) | ||
| 1655 | (when (buffer-modified-p bf) | ||
| 1656 | (with-current-buffer bf (save-buffer)))) | ||
| 1657 | (setq fname (todos-short-file-name f)) | ||
| 1658 | (with-temp-buffer | ||
| 1659 | (insert-file-contents f) | ||
| 1660 | (goto-char (point-min)) | ||
| 1661 | (let (fnum) | ||
| 1662 | ;; Unless the number of items to show was supplied by prefix | ||
| 1663 | ;; argument of caller, override `todos-show-priorities' with the | ||
| 1664 | ;; file-wide value from `todos-priorities-rules'. | ||
| 1665 | (unless (consp filter) | ||
| 1666 | (setq fnum (nth 1 (assoc f todos-priorities-rules)))) | ||
| 1667 | (unless (looking-at (concat "^" (regexp-quote todos-category-beg))) | ||
| 1668 | (kill-line 1)) | ||
| 1669 | (while (re-search-forward | ||
| 1670 | (concat "^" (regexp-quote todos-category-beg) "\\(.+\\)\n") | ||
| 1671 | nil t) | ||
| 1672 | (setq cat (match-string 1)) | ||
| 1673 | (let (cnum) | ||
| 1674 | ;; Unless the number of items to show was supplied by prefix | ||
| 1675 | ;; argument of caller, override the file-wide value from | ||
| 1676 | ;; `todos-priorities-rules' if set, else | ||
| 1677 | ;; `todos-show-priorities' with non-nil category-wide value | ||
| 1678 | ;; from `todos-priorities-rules'. | ||
| 1679 | (unless (consp filter) | ||
| 1680 | (let ((cats (nth 2 (assoc f todos-priorities-rules)))) | ||
| 1681 | (setq cnum (or (cdr (assoc cat cats)) | ||
| 1682 | fnum | ||
| 1683 | ;; FIXME: need this? | ||
| 1684 | todos-show-priorities)))) | ||
| 1685 | (delete-region (match-beginning 0) (match-end 0)) | ||
| 1686 | (setq beg (point)) ; Start of first item. | ||
| 1687 | (setq end (if (re-search-forward | ||
| 1688 | (concat "^" (regexp-quote todos-category-beg)) | ||
| 1689 | nil t) | ||
| 1690 | (match-beginning 0) | ||
| 1691 | (point-max))) | ||
| 1692 | (goto-char beg) | ||
| 1693 | (setq done | ||
| 1694 | (if (re-search-forward | ||
| 1695 | (concat "\n" (regexp-quote todos-category-done)) | ||
| 1696 | end t) | ||
| 1697 | (match-beginning 0) | ||
| 1698 | end)) | ||
| 1699 | ;; Leave done items only with regexp filter. | ||
| 1700 | ;; FIXME: and custom filter? | ||
| 1701 | (unless (eq filter 'regexp) | ||
| 1702 | (delete-region done end) | ||
| 1703 | (setq end done)) | ||
| 1704 | (narrow-to-region beg end) ; Process current category. | ||
| 1705 | (goto-char (point-min)) | ||
| 1706 | ;; Apply the filter. | ||
| 1707 | (cond ((eq filter 'diary) | ||
| 1708 | (while (not (eobp)) | ||
| 1709 | (if (looking-at (regexp-quote todos-nondiary-start)) | ||
| 1710 | (todos-remove-item) | ||
| 1711 | (todos-forward-item)))) | ||
| 1712 | ((eq filter 'regexp) | ||
| 1713 | (while (not (eobp)) | ||
| 1714 | (if (looking-at todos-item-start) | ||
| 1715 | (if (string-match regexp (todos-item-string)) | ||
| 1716 | (todos-forward-item) | ||
| 1717 | (todos-remove-item)) | ||
| 1718 | ;; Kill lines that aren't part of a todo or done | ||
| 1719 | ;; item (empty or todos-category-done). | ||
| 1720 | (delete-region (line-beginning-position) | ||
| 1721 | (1+ (line-end-position)))) | ||
| 1722 | ;; If last todo item in file matches regexp and | ||
| 1723 | ;; there are no following done items, | ||
| 1724 | ;; todos-category-done string is left dangling, | ||
| 1725 | ;; because todos-forward-item jumps over it. | ||
| 1726 | (if (and (eobp) (looking-back | ||
| 1727 | (concat (regexp-quote todos-done-string) | ||
| 1728 | "\n"))) | ||
| 1729 | (delete-region (point) (progn | ||
| 1730 | (forward-line -2) | ||
| 1731 | (point)))))) | ||
| 1732 | ((eq filter 'custom) | ||
| 1733 | (if todos-filter-function | ||
| 1734 | (funcall todos-filter-function) | ||
| 1735 | (error "No custom filter function has been defined"))) | ||
| 1736 | (t ; Filter top priority items. | ||
| 1737 | (setq num (or cnum fnum num)) | ||
| 1738 | (unless (zerop num) | ||
| 1739 | (todos-forward-item num)))) | ||
| 1740 | (setq beg (point)) | ||
| 1741 | (unless (member filter '(diary regexp custom)) | ||
| 1742 | (delete-region beg end)) | ||
| 1743 | (goto-char (point-min)) | ||
| 1744 | ;; Add file (if using multiple files) and category tags to | ||
| 1745 | ;; item. | ||
| 1746 | (while (not (eobp)) | ||
| 1747 | (when (re-search-forward | ||
| 1748 | (concat "\\(" todos-done-string-start | ||
| 1749 | todos-date-pattern "\\( " diary-time-regexp | ||
| 1750 | "\\)?]\\)\\|\\(" | ||
| 1751 | ;; todos-date-string-start doesn't work | ||
| 1752 | ;; here because of `^' | ||
| 1753 | "\\(" (regexp-quote todos-nondiary-start) | ||
| 1754 | "\\|" (regexp-quote diary-nonmarking-symbol) | ||
| 1755 | "\\)?" todos-date-pattern "\\( " | ||
| 1756 | diary-time-regexp "\\)?" | ||
| 1757 | (regexp-quote todos-nondiary-end) "?\\)") | ||
| 1758 | nil t) | ||
| 1759 | (insert (concat " [" (if multifile (concat fname ":")) | ||
| 1760 | cat "]"))) | ||
| 1761 | (forward-line)) | ||
| 1762 | (widen))) | ||
| 1763 | (setq bufstr (buffer-string)) | ||
| 1764 | (with-current-buffer buf | ||
| 1765 | (let (buffer-read-only) | ||
| 1766 | (insert bufstr))))))) | ||
| 1767 | ;; FIXME: let-bind todos-mode-line-control according to FILTER? | ||
| 1768 | (set-window-buffer (selected-window) (set-buffer buf)) | ||
| 1769 | (todos-prefix-overlays) | ||
| 1770 | (goto-char (point-min)) | ||
| 1771 | ;; FIXME: this is necessary -- why? | ||
| 1772 | (font-lock-fontify-buffer)))) | ||
| 1773 | |||
| 1774 | (defun todos-set-top-priorities (&optional arg) | ||
| 1775 | "Set number of top priorities shown by `todos-top-priorities'. | ||
| 1776 | With non-nil ARG, set the number only for the current Todos | ||
| 1777 | category; otherwise, set the number for all categories in the | ||
| 1778 | current Todos file. | ||
| 1779 | |||
| 1780 | Calling this function via either of the commands | ||
| 1781 | `todos-set-top-priorities-in-file' or | ||
| 1782 | `todos-set-top-priorities-in-category' is the recommended way to | ||
| 1783 | set the user customizable option `todos-priorities-rules'." | ||
| 1784 | (let* ((cat (todos-current-category)) | ||
| 1785 | (file todos-current-todos-file) | ||
| 1786 | (rules todos-priorities-rules) | ||
| 1787 | (frule (assoc-string file rules)) | ||
| 1788 | (crule (assoc-string cat (nth 2 frule))) | ||
| 1789 | (cur (or (if arg (cdr crule) (nth 1 frule)) | ||
| 1790 | todos-show-priorities)) | ||
| 1791 | (prompt (concat "Current number of top priorities in this " | ||
| 1792 | (if arg "category" "file") ": %d; " | ||
| 1793 | "enter new number: ")) | ||
| 1794 | (new "-1") | ||
| 1795 | nrule) | ||
| 1796 | (while (or (not (string-match "[0-9]+" new)) ; Don't accept "" or "bla". | ||
| 1797 | (< (string-to-number new) 0)) | ||
| 1798 | (let ((cur0 cur)) | ||
| 1799 | (setq new (read-string (format prompt cur0) nil nil cur0) | ||
| 1800 | prompt "Enter a non-negative number: " | ||
| 1801 | cur0 nil))) | ||
| 1802 | (setq new (string-to-number new)) | ||
| 1803 | (setq nrule (if arg | ||
| 1804 | (append (nth 2 (delete crule frule)) (list (cons cat new))) | ||
| 1805 | (append (list file new) (list (nth 2 frule))))) | ||
| 1806 | (setq rules (cons (if arg | ||
| 1807 | (list file cur nrule) | ||
| 1808 | nrule) | ||
| 1809 | (delete frule rules))) | ||
| 1810 | (customize-save-variable 'todos-priorities-rules rules))) | ||
| 1811 | |||
| 1812 | |||
| 1813 | ;; --------------------------------------------------------------------------- | ||
| 1814 | ;;; Sorting and display routines for Todos Categories mode. | ||
| 1815 | |||
| 1816 | (defun todos-longest-category-name-length (categories) | ||
| 1817 | "Return the length of the longest name in list CATEGORIES." | ||
| 1818 | (let ((longest 0)) | ||
| 1819 | (dolist (c categories longest) | ||
| 1820 | (setq longest (max longest (length c)))))) | ||
| 1821 | |||
| 1822 | (defun todos-padded-string (str) | ||
| 1823 | "Return string STR padded with spaces. | ||
| 1824 | The placement of the padding is determined by the value of user | ||
| 1825 | option `todos-categories-align'." | ||
| 1826 | (let* ((categories (mapcar 'car todos-categories)) | ||
| 1827 | (len (max (todos-longest-category-name-length categories) | ||
| 1828 | (length todos-categories-category-label))) | ||
| 1829 | (strlen (length str)) | ||
| 1830 | (strlen-odd (eq (logand strlen 1) 1)) ; oddp from cl.el | ||
| 1831 | (padding (max 0 (/ (- len strlen) 2))) | ||
| 1832 | (padding-left (cond ((eq todos-categories-align 'left) 0) | ||
| 1833 | ((eq todos-categories-align 'center) padding) | ||
| 1834 | ((eq todos-categories-align 'right) | ||
| 1835 | (if strlen-odd (1+ (* padding 2)) (* padding 2))))) | ||
| 1836 | (padding-right (cond ((eq todos-categories-align 'left) | ||
| 1837 | (if strlen-odd (1+ (* padding 2)) (* padding 2))) | ||
| 1838 | ((eq todos-categories-align 'center) | ||
| 1839 | (if strlen-odd (1+ padding) padding)) | ||
| 1840 | ((eq todos-categories-align 'right) 0)))) | ||
| 1841 | (concat (make-string padding-left 32) str (make-string padding-right 32)))) | ||
| 1842 | |||
| 1843 | (defvar todos-descending-counts nil | ||
| 1844 | "List of keys for category counts sorted in descending order.") | ||
| 1845 | |||
| 1846 | (defun todos-sort (list &optional key) | ||
| 1847 | "Return a copy of LIST, possibly sorted according to KEY." | ||
| 1848 | (let* ((l (copy-sequence list)) | ||
| 1849 | (fn (if (eq key 'alpha) | ||
| 1850 | (lambda (x) (upcase x)) ; Alphabetize case insensitively. | ||
| 1851 | (lambda (x) (todos-get-count key x)))) | ||
| 1852 | (descending (member key todos-descending-counts)) | ||
| 1853 | (cmp (if (eq key 'alpha) | ||
| 1854 | 'string< | ||
| 1855 | (if descending '< '>))) | ||
| 1856 | (pred (lambda (s1 s2) (let ((t1 (funcall fn (car s1))) | ||
| 1857 | (t2 (funcall fn (car s2)))) | ||
| 1858 | (funcall cmp t1 t2))))) | ||
| 1859 | (when key | ||
| 1860 | (setq l (sort l pred)) | ||
| 1861 | (if descending | ||
| 1862 | (setq todos-descending-counts | ||
| 1863 | (delete key todos-descending-counts)) | ||
| 1864 | (push key todos-descending-counts))) | ||
| 1865 | l)) | ||
| 1866 | |||
| 1867 | (defun todos-display-sorted (type) | ||
| 1868 | "Keep point on the TYPE count sorting button just clicked." | ||
| 1869 | (let ((opoint (point))) | ||
| 1870 | (todos-update-categories-display type) | ||
| 1871 | (goto-char opoint))) | ||
| 1872 | |||
| 1873 | (defun todos-label-to-key (label) | ||
| 1874 | "Return symbol for sort key associated with LABEL." | ||
| 1875 | (let (key) | ||
| 1876 | (cond ((string= label todos-categories-category-label) | ||
| 1877 | (setq key 'alpha)) | ||
| 1878 | ((string= label todos-categories-todo-label) | ||
| 1879 | (setq key 'todo)) | ||
| 1880 | ((string= label todos-categories-diary-label) | ||
| 1881 | (setq key 'diary)) | ||
| 1882 | ((string= label todos-categories-done-label) | ||
| 1883 | (setq key 'done)) | ||
| 1884 | ((string= label todos-categories-archived-label) | ||
| 1885 | (setq key 'archived))) | ||
| 1886 | key)) | ||
| 1887 | |||
| 1888 | (defun todos-insert-sort-button (label) | ||
| 1889 | "Insert button for displaying categories sorted by item counts. | ||
| 1890 | LABEL determines which type of count is sorted." | ||
| 1891 | (setq str (if (string= label todos-categories-category-label) | ||
| 1892 | (todos-padded-string label) | ||
| 1893 | label)) | ||
| 1894 | (setq beg (point)) | ||
| 1895 | (setq end (+ beg (length str))) | ||
| 1896 | (insert-button str 'face nil | ||
| 1897 | 'action | ||
| 1898 | `(lambda (button) | ||
| 1899 | (let ((key (todos-label-to-key ,label))) | ||
| 1900 | (if (and (member key todos-descending-counts) | ||
| 1901 | (eq key 'alpha)) | ||
| 1902 | (progn | ||
| 1903 | ;; If display is alphabetical, switch back to | ||
| 1904 | ;; category order. | ||
| 1905 | (todos-display-sorted nil) | ||
| 1906 | (setq todos-descending-counts | ||
| 1907 | (delete key todos-descending-counts))) | ||
| 1908 | (todos-display-sorted key))))) | ||
| 1909 | (setq ovl (make-overlay beg end)) | ||
| 1910 | (overlay-put ovl 'face 'todos-button)) | ||
| 1911 | |||
| 1912 | (defun todos-total-item-counts () | ||
| 1913 | "Return a list of total item counts for the current file." | ||
| 1914 | (mapcar (lambda (i) (apply '+ (mapcar (lambda (l) (aref l i)) | ||
| 1915 | (mapcar 'cdr todos-categories)))) | ||
| 1916 | (list 0 1 2 3))) | ||
| 1917 | |||
| 1918 | (defun todos-insert-category-line (cat &optional nonum) | ||
| 1919 | "Insert button displaying category CAT's name and item counts. | ||
| 1920 | With non-nil argument NONUM show only these; otherwise, insert a | ||
| 1921 | number in front of the button indicating the category's priority. | ||
| 1922 | The number and the category name are separated by the string | ||
| 1923 | which is the value of the user option | ||
| 1924 | `todos-categories-number-separator'." | ||
| 1925 | (let* ((archive (member todos-current-todos-file todos-archives)) | ||
| 1926 | (str (todos-padded-string cat)) | ||
| 1927 | (opoint (point))) | ||
| 1928 | ;; num is declared in caller. | ||
| 1929 | (setq num (1+ num)) | ||
| 1930 | (insert-button | ||
| 1931 | (concat (if nonum | ||
| 1932 | (make-string (+ 4 (length todos-categories-number-separator)) | ||
| 1933 | 32) | ||
| 1934 | (format " %3d%s" num todos-categories-number-separator)) | ||
| 1935 | str | ||
| 1936 | (mapconcat (lambda (elt) | ||
| 1937 | (concat | ||
| 1938 | (make-string (1+ (/ (length (car elt)) 2)) 32) ; label | ||
| 1939 | (format "%3d" (todos-get-count (cdr elt) cat)) ; count | ||
| 1940 | ;; Add an extra space if label length is odd | ||
| 1941 | ;; (using def of oddp from cl.el). | ||
| 1942 | (if (eq (logand (length (car elt)) 1) 1) " "))) | ||
| 1943 | (if archive | ||
| 1944 | (list (cons todos-categories-done-label 'done)) | ||
| 1945 | (list (cons todos-categories-todo-label 'todo) | ||
| 1946 | (cons todos-categories-diary-label 'diary) | ||
| 1947 | (cons todos-categories-done-label 'done) | ||
| 1948 | (cons todos-categories-archived-label | ||
| 1949 | 'archived))) | ||
| 1950 | "")) | ||
| 1951 | 'face (if (and todos-ignore-archived-categories | ||
| 1952 | (zerop (todos-get-count 'todo cat)) | ||
| 1953 | (zerop (todos-get-count 'done cat)) | ||
| 1954 | (not (zerop (todos-get-count 'archived cat)))) | ||
| 1955 | 'todos-archived-only | ||
| 1956 | nil) | ||
| 1957 | 'action `(lambda (button) (let ((buf (current-buffer))) | ||
| 1958 | (todos-jump-to-category ,cat) | ||
| 1959 | (kill-buffer buf)))) | ||
| 1960 | ;; Highlight the sorted count column. | ||
| 1961 | (let* ((beg (+ opoint 6 (length str))) | ||
| 1962 | end ovl) | ||
| 1963 | (cond ((eq nonum 'todo) | ||
| 1964 | (setq beg (+ beg 1 (/ (length todos-categories-todo-label) 2)))) | ||
| 1965 | ((eq nonum 'diary) | ||
| 1966 | (setq beg (+ beg 1 (length todos-categories-todo-label) | ||
| 1967 | 2 (/ (length todos-categories-diary-label) 2)))) | ||
| 1968 | ((eq nonum 'done) | ||
| 1969 | (setq beg (+ beg 1 (length todos-categories-todo-label) | ||
| 1970 | 2 (length todos-categories-diary-label) | ||
| 1971 | 2 (/ (length todos-categories-done-label) 2)))) | ||
| 1972 | ((eq nonum 'archived) | ||
| 1973 | (setq beg (+ beg 1 (length todos-categories-todo-label) | ||
| 1974 | 2 (length todos-categories-diary-label) | ||
| 1975 | 2 (length todos-categories-done-label) | ||
| 1976 | 2 (/ (length todos-categories-archived-label) 2))))) | ||
| 1977 | (unless (= beg (+ opoint 6 (length str))) | ||
| 1978 | (setq end (+ beg 4)) | ||
| 1979 | (setq ovl (make-overlay beg end)) | ||
| 1980 | (overlay-put ovl 'face 'todos-sorted-column))) | ||
| 1981 | (newline))) | ||
| 1982 | |||
| 1983 | (defun todos-display-categories-1 () | ||
| 1984 | "Prepare buffer for displaying table of categories and item counts." | ||
| 1985 | (unless (eq major-mode 'todos-categories-mode) | ||
| 1986 | (setq todos-global-current-todos-file (or todos-current-todos-file | ||
| 1987 | todos-default-todos-file)) | ||
| 1988 | (set-window-buffer (selected-window) | ||
| 1989 | (set-buffer (get-buffer-create todos-categories-buffer))) | ||
| 1990 | (kill-all-local-variables) | ||
| 1991 | (todos-categories-mode) | ||
| 1992 | (let (buffer-read-only) | ||
| 1993 | (erase-buffer) | ||
| 1994 | ;; FIXME: add usage tips? | ||
| 1995 | (insert (format "Category counts for Todos file \"%s\"." | ||
| 1996 | (todos-short-file-name todos-current-todos-file))) | ||
| 1997 | (newline 2) | ||
| 1998 | ;; Make space for the column of category numbers. | ||
| 1999 | (insert (make-string (+ 4 (length todos-categories-number-separator)) 32)) | ||
| 2000 | ;; Add the category and item count buttons (if this is the list of | ||
| 2001 | ;; categories in an archive, show only done item counts). | ||
| 2002 | (todos-insert-sort-button todos-categories-category-label) | ||
| 2003 | (if (member todos-current-todos-file todos-archives) | ||
| 2004 | (insert (concat (make-string 6 32) | ||
| 2005 | (format "%s" todos-categories-archived-label))) | ||
| 2006 | (insert (make-string 3 32)) | ||
| 2007 | (todos-insert-sort-button todos-categories-todo-label) | ||
| 2008 | (insert (make-string 2 32)) | ||
| 2009 | (todos-insert-sort-button todos-categories-diary-label) | ||
| 2010 | (insert (make-string 2 32)) | ||
| 2011 | (todos-insert-sort-button todos-categories-done-label) | ||
| 2012 | (insert (make-string 2 32)) | ||
| 2013 | (todos-insert-sort-button todos-categories-archived-label)) | ||
| 2014 | (newline 2)))) | ||
| 2015 | |||
| 2016 | (defun todos-update-categories-display (sortkey) | ||
| 2017 | "" | ||
| 2018 | (let* ((cats0 (if (and todos-ignore-archived-categories | ||
| 2019 | (not (eq major-mode 'todos-categories-mode))) | ||
| 2020 | todos-categories-full | ||
| 2021 | todos-categories)) | ||
| 2022 | (cats (todos-sort cats0 sortkey)) | ||
| 2023 | (archive (member todos-current-todos-file todos-archives)) | ||
| 2024 | ;; `num' is used by todos-insert-category-line. | ||
| 2025 | (num 0) | ||
| 2026 | ;; Find start of Category button if we just entered Todos Categories | ||
| 2027 | ;; mode. | ||
| 2028 | (pt (if (eq (point) (point-max)) | ||
| 2029 | (save-excursion | ||
| 2030 | (forward-line -2) | ||
| 2031 | (goto-char (next-single-char-property-change | ||
| 2032 | (point) 'face nil (line-end-position)))))) | ||
| 2033 | (buffer-read-only)) | ||
| 2034 | (forward-line 2) | ||
| 2035 | (delete-region (point) (point-max)) | ||
| 2036 | ;; Fill in the table with buttonized lines, each showing a category and | ||
| 2037 | ;; its item counts. | ||
| 2038 | (mapc (lambda (cat) (todos-insert-category-line cat sortkey)) | ||
| 2039 | (mapcar 'car cats)) | ||
| 2040 | (newline) | ||
| 2041 | ;; Add a line showing item count totals. | ||
| 2042 | (insert (make-string (+ 4 (length todos-categories-number-separator)) 32) | ||
| 2043 | (todos-padded-string todos-categories-totals-label) | ||
| 2044 | (mapconcat | ||
| 2045 | (lambda (elt) | ||
| 2046 | (concat | ||
| 2047 | (make-string (1+ (/ (length (car elt)) 2)) 32) | ||
| 2048 | (format "%3d" (nth (cdr elt) (todos-total-item-counts))) | ||
| 2049 | ;; Add an extra space if label length is odd (using | ||
| 2050 | ;; definition of oddp from cl.el). | ||
| 2051 | (if (eq (logand (length (car elt)) 1) 1) " "))) | ||
| 2052 | (if archive | ||
| 2053 | (list (cons todos-categories-done-label 2)) | ||
| 2054 | (list (cons todos-categories-todo-label 0) | ||
| 2055 | (cons todos-categories-diary-label 1) | ||
| 2056 | (cons todos-categories-done-label 2) | ||
| 2057 | (cons todos-categories-archived-label 3))) | ||
| 2058 | "")) | ||
| 2059 | ;; Put cursor on Category button initially. | ||
| 2060 | (if pt (goto-char pt)) | ||
| 2061 | (setq buffer-read-only t))) | ||
| 2062 | |||
| 2063 | ;; --------------------------------------------------------------------------- | ||
| 524 | ;;; Todos insertion commands, key bindings and keymap | 2064 | ;;; Todos insertion commands, key bindings and keymap |
| 525 | 2065 | ||
| 526 | ;; http://rosettacode.org/wiki/Power_set#Common_Lisp (GFDL) | 2066 | ;; Can either of these be included in Emacs? The originals are GFDL'd. |
| 527 | (defun powerset (l) | 2067 | ;; Slightly reformulated from |
| 528 | (if (null l) | 2068 | ;; http://rosettacode.org/wiki/Power_set#Common_Lisp. |
| 529 | (list nil) | 2069 | (defun powerset-recursive (l) |
| 530 | (let ((prev (powerset (cdr l)))) | 2070 | (cond ((null l) |
| 531 | (append (mapcar #'(lambda (elt) (cons (car l) elt)) prev) | 2071 | (list nil)) |
| 532 | prev)))) | 2072 | (t |
| 2073 | (let ((prev (todos-powerset (cdr l)))) | ||
| 2074 | (append (mapcar (lambda (elt) (cons (car l) elt)) prev) | ||
| 2075 | prev))))) | ||
| 2076 | ;; Elisp implementation of http://rosettacode.org/wiki/Power_set#C | ||
| 2077 | (defun powerset-bitwise (l) | ||
| 2078 | (let ((binnum (lsh 1 (length l))) | ||
| 2079 | pset elt) | ||
| 2080 | (dotimes (i binnum) | ||
| 2081 | (let ((bits i) | ||
| 2082 | (ll l)) | ||
| 2083 | (while (not (zerop bits)) | ||
| 2084 | (let ((arg (pop ll))) | ||
| 2085 | (unless (zerop (logand bits 1)) | ||
| 2086 | (setq elt (append elt (list arg)))) | ||
| 2087 | (setq bits (lsh bits -1)))) | ||
| 2088 | (setq pset (append pset (list elt))) | ||
| 2089 | (setq elt nil))) | ||
| 2090 | pset)) | ||
| 2091 | |||
| 2092 | ;; (defalias 'todos-powerset 'powerset-recursive) | ||
| 2093 | (defalias 'todos-powerset 'powerset-bitwise) | ||
| 533 | 2094 | ||
| 534 | ;; Return list of lists of non-nil atoms produced from ARGLIST. The elements | 2095 | ;; Return list of lists of non-nil atoms produced from ARGLIST. The elements |
| 535 | ;; of ARGLIST may be atoms or lists. | 2096 | ;; of ARGLIST may be atoms or lists. |
| @@ -554,13 +2115,11 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 554 | '(diary nonmarking (calendar date dayname) time (here region)) | 2115 | '(diary nonmarking (calendar date dayname) time (here region)) |
| 555 | "Generator list for argument lists of Todos insertion commands.") | 2116 | "Generator list for argument lists of Todos insertion commands.") |
| 556 | 2117 | ||
| 557 | (eval-when-compile (require 'cl)) ; remove-duplicates | ||
| 558 | |||
| 559 | (defvar todos-insertion-commands-args | 2118 | (defvar todos-insertion-commands-args |
| 560 | (let ((argslist (todos-gen-arglists todos-insertion-commands-args-genlist)) | 2119 | (let ((argslist (todos-gen-arglists todos-insertion-commands-args-genlist)) |
| 561 | res new) | 2120 | res new) |
| 562 | (setq res (remove-duplicates | 2121 | (setq res (remove-duplicates |
| 563 | (apply 'append (mapcar 'powerset argslist)) :test 'equal)) | 2122 | (apply 'append (mapcar 'todos-powerset argslist)) :test 'equal)) |
| 564 | (dolist (l res) | 2123 | (dolist (l res) |
| 565 | (unless (= 5 (length l)) | 2124 | (unless (= 5 (length l)) |
| 566 | (let ((v (make-vector 5 nil)) elt) | 2125 | (let ((v (make-vector 5 nil)) elt) |
| @@ -613,8 +2172,8 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 613 | 2172 | ||
| 614 | (defvar todos-insertion-commands | 2173 | (defvar todos-insertion-commands |
| 615 | (mapcar (lambda (c) | 2174 | (mapcar (lambda (c) |
| 616 | (eval `(todos-define-insertion-command ,@c))) | 2175 | (eval `(todos-define-insertion-command ,@c))) |
| 617 | todos-insertion-commands-args) | 2176 | todos-insertion-commands-args) |
| 618 | "List of Todos insertion commands.") | 2177 | "List of Todos insertion commands.") |
| 619 | 2178 | ||
| 620 | (defvar todos-insertion-commands-arg-key-list | 2179 | (defvar todos-insertion-commands-arg-key-list |
| @@ -633,20 +2192,6 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 633 | (dolist (c todos-insertion-commands) | 2192 | (dolist (c todos-insertion-commands) |
| 634 | (let* ((key "") | 2193 | (let* ((key "") |
| 635 | (cname (symbol-name c))) | 2194 | (cname (symbol-name c))) |
| 636 | ;; (if (string-match "diary\\_>" cname) (setq key (concat key "yy"))) | ||
| 637 | ;; (if (string-match "diary.+" cname) (setq key (concat key "y"))) | ||
| 638 | ;; (if (string-match "nonmarking\\_>" cname) (setq key (concat key "kk"))) | ||
| 639 | ;; (if (string-match "nonmarking.+" cname) (setq key (concat key "k"))) | ||
| 640 | ;; (if (string-match "calendar\\_>" cname) (setq key (concat key "cc"))) | ||
| 641 | ;; (if (string-match "calendar.+" cname) (setq key (concat key "c"))) | ||
| 642 | ;; (if (string-match "date\\_>" cname) (setq key (concat key "dd"))) | ||
| 643 | ;; (if (string-match "date.+" cname) (setq key (concat key "d"))) | ||
| 644 | ;; (if (string-match "dayname\\_>" cname) (setq key (concat key "nn"))) | ||
| 645 | ;; (if (string-match "dayname.+" cname) (setq key (concat key "n"))) | ||
| 646 | ;; (if (string-match "time\\_>" cname) (setq key (concat key "tt"))) | ||
| 647 | ;; (if (string-match "time.+" cname) (setq key (concat key "t"))) | ||
| 648 | ;; (if (string-match "here" cname) (setq key (concat key "h"))) | ||
| 649 | ;; (if (string-match "region" cname) (setq key (concat key "r"))) | ||
| 650 | (mapc (lambda (l) | 2195 | (mapc (lambda (l) |
| 651 | (let ((arg (nth 0 l)) | 2196 | (let ((arg (nth 0 l)) |
| 652 | (key1 (nth 1 l)) | 2197 | (key1 (nth 1 l)) |
| @@ -666,90 +2211,104 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 666 | map) | 2211 | map) |
| 667 | "Keymap for Todos mode insertion commands.") | 2212 | "Keymap for Todos mode insertion commands.") |
| 668 | 2213 | ||
| 2214 | ;; ??FIXME: use easy-mmode-define-keymap and easy-mmode-defmap | ||
| 2215 | (defvar todos-key-bindings | ||
| 2216 | `( | ||
| 2217 | ;; display | ||
| 2218 | ("Cd" . todos-display-categories) ;FIXME: Cs todos-show-categories? | ||
| 2219 | ;("" . todos-display-categories-alphabetically) | ||
| 2220 | ("H" . todos-highlight-item) | ||
| 2221 | ("N" . todos-toggle-item-numbering) | ||
| 2222 | ("D" . todos-toggle-display-date-time) | ||
| 2223 | ("*" . todos-toggle-mark-item) | ||
| 2224 | ("C*" . todos-mark-category) | ||
| 2225 | ("Cu" . todos-unmark-category) | ||
| 2226 | ("PP" . todos-print) | ||
| 2227 | ("PF" . todos-print-to-file) | ||
| 2228 | ("v" . todos-toggle-view-done-items) | ||
| 2229 | ("V" . todos-toggle-show-done-only) | ||
| 2230 | ("Av" . todos-view-archived-items) | ||
| 2231 | ("As" . todos-show-archive) | ||
| 2232 | ("Ac" . todos-choose-archive) | ||
| 2233 | ("Y" . todos-diary-items) | ||
| 2234 | ;;("" . todos-update-filter-files) | ||
| 2235 | ("Fe" . todos-edit-multiline) | ||
| 2236 | ("Fh" . todos-highlight-item) | ||
| 2237 | ("Fn" . todos-toggle-item-numbering) | ||
| 2238 | ("Fd" . todos-toggle-display-date-time) | ||
| 2239 | ("Ftt" . todos-top-priorities) | ||
| 2240 | ("Ftm" . todos-top-priorities-multifile) | ||
| 2241 | ("Fts" . todos-set-top-priorities-in-file) | ||
| 2242 | ("Cts" . todos-set-top-priorities-in-category) | ||
| 2243 | ("Fyy" . todos-diary-items) | ||
| 2244 | ("Fym" . todos-diary-items-multifile) | ||
| 2245 | ("Fxx" . todos-regexp-items) | ||
| 2246 | ("Fxm" . todos-regexp-items-multifile) | ||
| 2247 | ("Fcc" . todos-custom-items) | ||
| 2248 | ("Fcm" . todos-custom-items-multifile) | ||
| 2249 | ;;("" . todos-save-top-priorities) | ||
| 2250 | ;; navigation | ||
| 2251 | ("f" . todos-forward-category) | ||
| 2252 | ("b" . todos-backward-category) | ||
| 2253 | ("j" . todos-jump-to-category) | ||
| 2254 | ("J" . todos-jump-to-category-other-file) | ||
| 2255 | ("n" . todos-forward-item) | ||
| 2256 | ("p" . todos-backward-item) | ||
| 2257 | ("S" . todos-search) | ||
| 2258 | ("X" . todos-clear-matches) | ||
| 2259 | ;; editing | ||
| 2260 | ("Fa" . todos-add-file) | ||
| 2261 | ("Ca" . todos-add-category) | ||
| 2262 | ("Cr" . todos-rename-category) | ||
| 2263 | ("Cg" . todos-merge-category) | ||
| 2264 | ;;("" . todos-merge-categories) | ||
| 2265 | ("Cm" . todos-move-category) | ||
| 2266 | ("Ck" . todos-delete-category) | ||
| 2267 | ("d" . todos-item-done) | ||
| 2268 | ("ee" . todos-edit-item) | ||
| 2269 | ("em" . todos-edit-multiline-item) | ||
| 2270 | ("eh" . todos-edit-item-header) | ||
| 2271 | ("edd" . todos-edit-item-date) | ||
| 2272 | ("edc" . todos-edit-item-date-from-calendar) | ||
| 2273 | ("edt" . todos-edit-item-date-is-today) | ||
| 2274 | ("et" . todos-edit-item-time) | ||
| 2275 | ("eyy" . todos-edit-item-diary-inclusion) | ||
| 2276 | ;; ("" . todos-edit-category-diary-inclusion) | ||
| 2277 | ("eyn" . todos-edit-item-diary-nonmarking) | ||
| 2278 | ;;("" . todos-edit-category-diary-nonmarking) | ||
| 2279 | ("ec" . todos-comment-done-item) ;FIXME: or just "c"? | ||
| 2280 | ("i" . ,todos-insertion-map) | ||
| 2281 | ("k" . todos-delete-item) | ||
| 2282 | ("m" . todos-move-item) | ||
| 2283 | ("M" . todos-move-item-to-file) | ||
| 2284 | ;; FIXME: This binding prevents `-' from being used in a numerical prefix | ||
| 2285 | ;; argument without typing C-u | ||
| 2286 | ;; ("-" . todos-raise-item-priority) | ||
| 2287 | ("r" . todos-raise-item-priority) | ||
| 2288 | ;; ("+" . todos-lower-item-priority) | ||
| 2289 | ("l" . todos-lower-item-priority) | ||
| 2290 | ("#" . todos-set-item-priority) | ||
| 2291 | ("u" . todos-item-undo) | ||
| 2292 | ("Ad" . todos-archive-done-item-or-items) ;FIXME | ||
| 2293 | ("AD" . todos-archive-category-done-items) ;FIXME | ||
| 2294 | ("Au" . todos-unarchive-items) | ||
| 2295 | ("AU" . todos-unarchive-category) | ||
| 2296 | ("s" . todos-save) | ||
| 2297 | ("q" . todos-quit) | ||
| 2298 | ([remap newline] . newline-and-indent) | ||
| 2299 | ) | ||
| 2300 | "Alist pairing keys defined in Todos modes and their bindings.") | ||
| 2301 | |||
| 669 | (defvar todos-mode-map | 2302 | (defvar todos-mode-map |
| 670 | (let ((map (make-keymap))) | 2303 | (let ((map (make-keymap))) |
| 671 | ;; Don't suppress digit keys, so they can supply prefix arguments. | 2304 | ;; Don't suppress digit keys, so they can supply prefix arguments. |
| 672 | (suppress-keymap map) | 2305 | (suppress-keymap map) |
| 673 | ;; display commands | 2306 | (dolist (ck todos-key-bindings) |
| 674 | (define-key map "Cd" 'todos-display-categories) ;FIXME: Cs todos-show-categories? | 2307 | (define-key map (car ck) (cdr ck))) |
| 675 | ;; (define-key map "" 'todos-display-categories-alphabetically) | ||
| 676 | (define-key map "H" 'todos-highlight-item) | ||
| 677 | (define-key map "N" 'todos-toggle-item-numbering) | ||
| 678 | (define-key map "D" 'todos-toggle-display-date-time) | ||
| 679 | (define-key map "*" 'todos-toggle-mark-item) | ||
| 680 | (define-key map "C*" 'todos-mark-category) | ||
| 681 | (define-key map "Cu" 'todos-unmark-category) | ||
| 682 | (define-key map "P" 'todos-print) | ||
| 683 | ;; (define-key map "" 'todos-print-to-file) | ||
| 684 | (define-key map "v" 'todos-toggle-view-done-items) | ||
| 685 | (define-key map "V" 'todos-toggle-show-done-only) | ||
| 686 | (define-key map "Av" 'todos-view-archived-items) | ||
| 687 | (define-key map "As" 'todos-show-archive) | ||
| 688 | (define-key map "Ac" 'todos-choose-archive) | ||
| 689 | (define-key map "Y" 'todos-diary-items) | ||
| 690 | ;; (define-key map "" 'todos-update-merged-files) | ||
| 691 | ;; (define-key map "" 'todos-set-top-priorities) | ||
| 692 | (define-key map "Ftt" 'todos-top-priorities) | ||
| 693 | (define-key map "Ftm" 'todos-merged-top-priorities) | ||
| 694 | (define-key map "Fdd" 'todos-diary-items) | ||
| 695 | (define-key map "Fdm" 'todos-merged-diary-items) | ||
| 696 | (define-key map "Frr" 'todos-regexp-items) | ||
| 697 | (define-key map "Frm" 'todos-merged-regexp-items) | ||
| 698 | (define-key map "Fcc" 'todos-custom-items) | ||
| 699 | (define-key map "Fcm" 'todos-merged-custom-items) | ||
| 700 | ;; (define-key map "" 'todos-save-top-priorities) | ||
| 701 | ;; navigation commands | ||
| 702 | (define-key map "f" 'todos-forward-category) | ||
| 703 | (define-key map "b" 'todos-backward-category) | ||
| 704 | (define-key map "j" 'todos-jump-to-category) | ||
| 705 | (define-key map "J" 'todos-jump-to-category-other-file) | ||
| 706 | (define-key map "n" 'todos-forward-item) | ||
| 707 | (define-key map "p" 'todos-backward-item) | ||
| 708 | (define-key map "S" 'todos-search) | ||
| 709 | (define-key map "X" 'todos-clear-matches) | ||
| 710 | ;; editing commands | ||
| 711 | (define-key map "Fa" 'todos-add-file) | ||
| 712 | ;; (define-key map "" 'todos-change-default-file) | ||
| 713 | (define-key map "Ca" 'todos-add-category) | ||
| 714 | (define-key map "Cr" 'todos-rename-category) | ||
| 715 | (define-key map "Cg" 'todos-merge-category) | ||
| 716 | ;; (define-key map "" 'todos-merge-categories) | ||
| 717 | (define-key map "Cm" 'todos-move-category) | ||
| 718 | (define-key map "Ck" 'todos-delete-category) | ||
| 719 | (define-key map "d" 'todos-item-done) | ||
| 720 | (define-key map "ee" 'todos-edit-item) | ||
| 721 | (define-key map "em" 'todos-edit-multiline) | ||
| 722 | (define-key map "eh" 'todos-edit-item-header) | ||
| 723 | (define-key map "ed" 'todos-edit-item-date) | ||
| 724 | (define-key map "ey" 'todos-edit-item-date-is-today) | ||
| 725 | (define-key map "et" 'todos-edit-item-time) | ||
| 726 | (define-key map "ec" 'todos-comment-done-item) ;FIXME: or just "c"? | ||
| 727 | (define-key map "i" todos-insertion-map) | ||
| 728 | (define-key map "k" 'todos-delete-item) | ||
| 729 | (define-key map "m" 'todos-move-item) | ||
| 730 | (define-key map "M" 'todos-move-item-to-file) | ||
| 731 | ;; FIXME: This prevents `-' from being used in a numerical prefix argument | ||
| 732 | ;; without typing C-u | ||
| 733 | (define-key map "-" 'todos-raise-item-priority) | ||
| 734 | (define-key map "r" 'todos-raise-item-priority) | ||
| 735 | (define-key map "+" 'todos-lower-item-priority) | ||
| 736 | (define-key map "l" 'todos-lower-item-priority) | ||
| 737 | (define-key map "#" 'todos-set-item-priority) | ||
| 738 | (define-key map "u" 'todos-item-undo) | ||
| 739 | (define-key map "Ad" 'todos-archive-done-item-or-items) ;FIXME | ||
| 740 | (define-key map "AD" 'todos-archive-category-done-items) ;FIXME | ||
| 741 | ;; (define-key map "" 'todos-unarchive-items) | ||
| 742 | ;; (define-key map "" 'todos-unarchive-category) | ||
| 743 | (define-key map "y" 'todos-toggle-diary-inclusion) | ||
| 744 | ;; (define-key map "" 'todos-toggle-diary-inclusion) | ||
| 745 | ;; (define-key map "" 'todos-toggle-item-diary-nonmarking) | ||
| 746 | ;; (define-key map "" 'todos-toggle-diary-nonmarking) | ||
| 747 | (define-key map "s" 'todos-save) | ||
| 748 | (define-key map "q" 'todos-quit) | ||
| 749 | (define-key map [remap newline] 'newline-and-indent) | ||
| 750 | map) | 2308 | map) |
| 751 | "Todos mode keymap.") | 2309 | "Todos mode keymap.") |
| 752 | 2310 | ||
| 2311 | ;; FIXME | ||
| 753 | (easy-menu-define | 2312 | (easy-menu-define |
| 754 | todos-menu todos-mode-map "Todos Menu" | 2313 | todos-menu todos-mode-map "Todos Menu" |
| 755 | '("Todos" | 2314 | '("Todos" |
| @@ -774,10 +2333,10 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 774 | "---" | 2333 | "---" |
| 775 | ["View Diary Items" todos-diary-items t] | 2334 | ["View Diary Items" todos-diary-items t] |
| 776 | ["View Top Priority Items" todos-top-priorities t] | 2335 | ["View Top Priority Items" todos-top-priorities t] |
| 777 | ["View Merged Top Priority Items" todos-merged-top-priorities t] | 2336 | ["View Multifile Top Priority Items" todos-top-priorities-multifile t] |
| 778 | "---" | 2337 | "---" |
| 779 | ["View Archive" todos-view-archive t] | 2338 | ["View Archive" todos-view-archive t] |
| 780 | ["Print Category" todos-print t]) ;FIXME | 2339 | ["Print Category" todos-print t]) |
| 781 | ("Editing" | 2340 | ("Editing" |
| 782 | ["Insert New Item" todos-insert-item t] | 2341 | ["Insert New Item" todos-insert-item t] |
| 783 | ["Insert Item Here" todos-insert-item-here t] | 2342 | ["Insert Item Here" todos-insert-item-here t] |
| @@ -795,9 +2354,9 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 795 | ["Delete Item" todos-delete-item t] | 2354 | ["Delete Item" todos-delete-item t] |
| 796 | ["Undo Done Item" todos-item-undo t] | 2355 | ["Undo Done Item" todos-item-undo t] |
| 797 | ["Mark/Unmark Item for Diary" todos-toggle-item-diary-inclusion t] | 2356 | ["Mark/Unmark Item for Diary" todos-toggle-item-diary-inclusion t] |
| 798 | ["Mark/Unmark Items for Diary" todos-toggle-diary-inclusion t] | 2357 | ["Mark/Unmark Items for Diary" todos-edit-item-diary-inclusion t] |
| 799 | ["Mark & Hide Done Item" todos-item-done t] | 2358 | ["Mark & Hide Done Item" todos-item-done t] |
| 800 | ["Archive Done Items" todos-archive-category-done-items t] ;FIXME | 2359 | ["Archive Done Items" todos-archive-category-done-items t] |
| 801 | "---" | 2360 | "---" |
| 802 | ["Add New Todos File" todos-add-file t] | 2361 | ["Add New Todos File" todos-add-file t] |
| 803 | ["Add New Category" todos-add-category t] | 2362 | ["Add New Category" todos-add-category t] |
| @@ -829,7 +2388,7 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 829 | (define-key map "s" 'todos-save) | 2388 | (define-key map "s" 'todos-save) |
| 830 | (define-key map "S" 'todos-search) | 2389 | (define-key map "S" 'todos-search) |
| 831 | (define-key map "t" 'todos-show) ;FIXME: should show same category | 2390 | (define-key map "t" 'todos-show) ;FIXME: should show same category |
| 832 | ;; (define-key map "u" 'todos-unarchive-item) | 2391 | (define-key map "u" 'todos-unarchive-item) |
| 833 | (define-key map "U" 'todos-unarchive-category) | 2392 | (define-key map "U" 'todos-unarchive-category) |
| 834 | map) | 2393 | map) |
| 835 | "Todos Archive mode keymap.") | 2394 | "Todos Archive mode keymap.") |
| @@ -846,7 +2405,9 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 846 | (suppress-keymap map t) | 2405 | (suppress-keymap map t) |
| 847 | ;; (define-key map "a" 'todos-display-categories-alphabetically) | 2406 | ;; (define-key map "a" 'todos-display-categories-alphabetically) |
| 848 | (define-key map "c" 'todos-display-categories) | 2407 | (define-key map "c" 'todos-display-categories) |
| 2408 | (define-key map "l" 'todos-lower-category) | ||
| 849 | (define-key map "+" 'todos-lower-category) | 2409 | (define-key map "+" 'todos-lower-category) |
| 2410 | (define-key map "r" 'todos-raise-category) | ||
| 850 | (define-key map "-" 'todos-raise-category) | 2411 | (define-key map "-" 'todos-raise-category) |
| 851 | (define-key map "n" 'forward-button) | 2412 | (define-key map "n" 'forward-button) |
| 852 | (define-key map "p" 'backward-button) | 2413 | (define-key map "p" 'backward-button) |
| @@ -863,22 +2424,16 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 863 | (let ((map (make-keymap))) | 2424 | (let ((map (make-keymap))) |
| 864 | (suppress-keymap map t) | 2425 | (suppress-keymap map t) |
| 865 | ;; navigation commands | 2426 | ;; navigation commands |
| 866 | (define-key map "j" 'todos-jump-to-category) | 2427 | (define-key map "j" 'todos-jump-to-item) |
| 2428 | (define-key map [remap newline] 'todos-jump-to-item) | ||
| 867 | (define-key map "n" 'todos-forward-item) | 2429 | (define-key map "n" 'todos-forward-item) |
| 868 | (define-key map "p" 'todos-backward-item) | 2430 | (define-key map "p" 'todos-backward-item) |
| 869 | ;; (define-key map "S" 'todos-search) | ||
| 870 | ;; display commands | ||
| 871 | (define-key map "C" 'todos-display-categories) | ||
| 872 | ;; (define-key map "" 'todos-display-categories-alphabetically) | ||
| 873 | (define-key map "H" 'todos-highlight-item) | 2431 | (define-key map "H" 'todos-highlight-item) |
| 874 | (define-key map "N" 'todos-toggle-item-numbering) | 2432 | (define-key map "N" 'todos-toggle-item-numbering) |
| 875 | ;; (define-key map "" 'todos-toggle-display-date-time) | 2433 | (define-key map "D" 'todos-toggle-display-date-time) |
| 876 | (define-key map "P" 'todos-print) | 2434 | (define-key map "P" 'todos-print) |
| 877 | (define-key map "q" 'todos-quit) | 2435 | (define-key map "q" 'todos-quit) |
| 878 | (define-key map "s" 'todos-save) | 2436 | (define-key map "s" 'todos-save) |
| 879 | (define-key map "V" 'todos-view-archive) | ||
| 880 | (define-key map "v" 'todos-toggle-view-done-items) | ||
| 881 | (define-key map "Y" 'todos-diary-items) | ||
| 882 | ;; (define-key map "S" 'todos-save-top-priorities) | 2437 | ;; (define-key map "S" 'todos-save-top-priorities) |
| 883 | ;; editing commands | 2438 | ;; editing commands |
| 884 | (define-key map "l" 'todos-lower-item-priority) | 2439 | (define-key map "l" 'todos-lower-item-priority) |
| @@ -895,8 +2450,7 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 895 | "" | 2450 | "" |
| 896 | (set (make-local-variable 'font-lock-defaults) '(todos-font-lock-keywords t)) | 2451 | (set (make-local-variable 'font-lock-defaults) '(todos-font-lock-keywords t)) |
| 897 | (set (make-local-variable 'indent-line-function) 'todos-indent) | 2452 | (set (make-local-variable 'indent-line-function) 'todos-indent) |
| 898 | (when todos-wrap-lines (funcall todos-line-wrapping-function)) | 2453 | (when todos-wrap-lines (funcall todos-line-wrapping-function))) |
| 899 | ) | ||
| 900 | 2454 | ||
| 901 | (defun todos-modes-set-2 () | 2455 | (defun todos-modes-set-2 () |
| 902 | "" | 2456 | "" |
| @@ -904,75 +2458,72 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 904 | (setq buffer-read-only t) | 2458 | (setq buffer-read-only t) |
| 905 | (set (make-local-variable 'hl-line-range-function) | 2459 | (set (make-local-variable 'hl-line-range-function) |
| 906 | (lambda() (when (todos-item-end) | 2460 | (lambda() (when (todos-item-end) |
| 907 | (cons (todos-item-start) (todos-item-end))))) | 2461 | (cons (todos-item-start) (todos-item-end)))))) |
| 908 | ) | 2462 | |
| 2463 | (defun todos-modes-set-3 () | ||
| 2464 | (set (make-local-variable 'todos-categories-full) nil) | ||
| 2465 | ;; todos-set-categories sets todos-categories-full. | ||
| 2466 | (set (make-local-variable 'todos-categories) (todos-set-categories)) | ||
| 2467 | (set (make-local-variable 'todos-category-number) 1) | ||
| 2468 | (set (make-local-variable 'todos-first-visit) t) | ||
| 2469 | (add-hook 'post-command-hook 'todos-after-find-file nil t)) | ||
| 2470 | |||
| 2471 | (put 'todos-mode 'mode-class 'special) | ||
| 909 | 2472 | ||
| 910 | ;; Autoloading isn't needed if files are identified by auto-mode-alist | 2473 | ;; Autoloading isn't needed if files are identified by auto-mode-alist |
| 911 | ;; ;; As calendar reads included Todos file before todos-mode is loaded. | 2474 | ;; ;; As calendar reads included Todos file before todos-mode is loaded. |
| 912 | ;; ;;;###autoload | 2475 | ;; ;;;###autoload |
| 913 | (define-derived-mode todos-mode nil "Todos" () ;FIXME: derive from special-mode? | 2476 | (define-derived-mode todos-mode special-mode "Todos" () |
| 914 | "Major mode for displaying, navigating and editing Todo lists. | 2477 | "Major mode for displaying, navigating and editing Todo lists. |
| 915 | 2478 | ||
| 916 | \\{todos-mode-map}" | 2479 | \\{todos-mode-map}" |
| 917 | (easy-menu-add todos-menu) | 2480 | (easy-menu-add todos-menu) |
| 918 | (todos-modes-set-1) | 2481 | (todos-modes-set-1) |
| 919 | (todos-modes-set-2) | 2482 | (todos-modes-set-2) |
| 2483 | (todos-modes-set-3) | ||
| 2484 | ;; Initialize todos-current-todos-file. | ||
| 920 | (when (member (file-truename (buffer-file-name)) | 2485 | (when (member (file-truename (buffer-file-name)) |
| 921 | (funcall todos-files-function)) | 2486 | (funcall todos-files-function)) |
| 922 | (set (make-local-variable 'todos-current-todos-file) | 2487 | (set (make-local-variable 'todos-current-todos-file) |
| 923 | (file-truename (buffer-file-name)))) | 2488 | (file-truename (buffer-file-name)))) |
| 924 | (set (make-local-variable 'todos-categories-full) nil) | ||
| 925 | ;; todos-set-categories sets todos-categories-full. | ||
| 926 | (set (make-local-variable 'todos-categories) (todos-set-categories)) | ||
| 927 | (set (make-local-variable 'todos-first-visit) t) | 2489 | (set (make-local-variable 'todos-first-visit) t) |
| 928 | (set (make-local-variable 'todos-category-number) 1) ;0) | ||
| 929 | (set (make-local-variable 'todos-show-done-only) nil) | 2490 | (set (make-local-variable 'todos-show-done-only) nil) |
| 930 | (set (make-local-variable 'todos-categories-with-marks) nil) | 2491 | (set (make-local-variable 'todos-categoreis-with-marks) nil) |
| 931 | (when todos-show-current-file | 2492 | (when todos-show-current-file |
| 932 | (add-hook 'pre-command-hook 'todos-show-current-file nil t)) | 2493 | (add-hook 'pre-command-hook 'todos-show-current-file nil t)) |
| 933 | (add-hook 'post-command-hook 'todos-after-find-file nil t) | 2494 | ;; FIXME: works more or less, but should be tied to the defcustom |
| 2495 | (add-hook 'window-configuration-change-hook | ||
| 2496 | (lambda () | ||
| 2497 | (setq todos-done-separator (make-string (window-width) ?_))) | ||
| 2498 | nil t) | ||
| 934 | (add-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file nil t)) | 2499 | (add-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file nil t)) |
| 935 | 2500 | ||
| 936 | ;; FIXME: | 2501 | ;; FIXME: need this? |
| 937 | (defun todos-unload-hook () | 2502 | (defun todos-unload-hook () |
| 938 | "" | 2503 | "" |
| 939 | (remove-hook 'pre-command-hook 'todos-show-current-file t) | 2504 | (remove-hook 'pre-command-hook 'todos-show-current-file t) |
| 940 | (remove-hook 'post-command-hook 'todos-after-find-file t) | 2505 | (remove-hook 'post-command-hook 'todos-after-find-file t) |
| 2506 | (remove-hook 'window-configuration-change-hook | ||
| 2507 | (lambda () | ||
| 2508 | (setq todos-done-separator | ||
| 2509 | (make-string (window-width) ?_))) t) | ||
| 941 | (remove-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file t)) | 2510 | (remove-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file t)) |
| 942 | 2511 | ||
| 943 | (define-derived-mode todos-archive-mode nil "Todos-Arch" () | 2512 | (put 'todos-archive-mode 'mode-class 'special) |
| 2513 | |||
| 2514 | (define-derived-mode todos-archive-mode todos-mode "Todos-Arch" () | ||
| 944 | "Major mode for archived Todos categories. | 2515 | "Major mode for archived Todos categories. |
| 945 | 2516 | ||
| 946 | \\{todos-archive-mode-map}" | 2517 | \\{todos-archive-mode-map}" |
| 947 | (todos-modes-set-1) | 2518 | (todos-modes-set-1) |
| 948 | (todos-modes-set-2) | 2519 | (todos-modes-set-2) |
| 949 | (set (make-local-variable 'todos-show-done-only) t) | 2520 | (todos-modes-set-3) |
| 950 | (set (make-local-variable 'todos-current-todos-file) | 2521 | (set (make-local-variable 'todos-current-todos-file) |
| 951 | (file-truename (buffer-file-name))) | 2522 | (file-truename (buffer-file-name))) |
| 952 | (set (make-local-variable 'todos-categories) (todos-set-categories)) | 2523 | (set (make-local-variable 'todos-show-done-only) t)) |
| 953 | (set (make-local-variable 'todos-category-number) 1) ; 0) | ||
| 954 | (add-hook 'post-command-hook 'todos-after-find-file nil t)) | ||
| 955 | |||
| 956 | ;; FIXME: return to Todos or Archive mode | ||
| 957 | (define-derived-mode todos-raw-mode nil "Todos Raw" () | ||
| 958 | "Emergency repair mode for Todos files." | ||
| 959 | (when (member major-mode '(todos-mode todos-archive-mode)) | ||
| 960 | (setq buffer-read-only nil) | ||
| 961 | (set (make-local-variable 'font-lock-defaults) '(todos-font-lock-keywords t)) | ||
| 962 | (widen) | ||
| 963 | ;; FIXME: doesn't DTRT here | ||
| 964 | (todos-prefix-overlays))) | ||
| 965 | 2524 | ||
| 966 | (define-derived-mode todos-edit-mode nil "Todos-Ed" () | 2525 | (defun todos-mode-external-set () |
| 967 | "Major mode for editing multiline Todo items. | 2526 | "" |
| 968 | |||
| 969 | \\{todos-edit-mode-map}" | ||
| 970 | (todos-modes-set-1)) | ||
| 971 | |||
| 972 | (define-derived-mode todos-categories-mode nil "Todos-Cats" () | ||
| 973 | "Major mode for displaying and editing Todos categories. | ||
| 974 | |||
| 975 | \\{todos-categories-mode-map}" | ||
| 976 | (set (make-local-variable 'todos-current-todos-file) | 2527 | (set (make-local-variable 'todos-current-todos-file) |
| 977 | todos-global-current-todos-file) | 2528 | todos-global-current-todos-file) |
| 978 | (let ((cats (with-current-buffer (get-file-buffer todos-current-todos-file) | 2529 | (let ((cats (with-current-buffer (get-file-buffer todos-current-todos-file) |
| @@ -981,7 +2532,24 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 981 | (todos-set-categories))))) | 2532 | (todos-set-categories))))) |
| 982 | (set (make-local-variable 'todos-categories) cats))) | 2533 | (set (make-local-variable 'todos-categories) cats))) |
| 983 | 2534 | ||
| 984 | (define-derived-mode todos-filter-items-mode nil "Todos-Top" () | 2535 | (define-derived-mode todos-edit-mode text-mode "Todos-Ed" () |
| 2536 | "Major mode for editing multiline Todo items. | ||
| 2537 | |||
| 2538 | \\{todos-edit-mode-map}"{ | ||
| 2539 | (todos-modes-set-1) | ||
| 2540 | (todos-mode-external-set)) | ||
| 2541 | |||
| 2542 | (put 'todos-categories-mode 'mode-class 'special) | ||
| 2543 | |||
| 2544 | (define-derived-mode todos-categories-mode special-mode "Todos-Cats" () | ||
| 2545 | "Major mode for displaying and editing Todos categories. | ||
| 2546 | |||
| 2547 | \\{todos-categories-mode-map}" | ||
| 2548 | (todos-mode-external-set)) | ||
| 2549 | |||
| 2550 | (put 'todos-filter-mode 'mode-class 'special) | ||
| 2551 | |||
| 2552 | (define-derived-mode todos-filter-items-mode special-mode "Todos-Fltr" () | ||
| 985 | "Mode for displaying and reprioritizing top priority Todos. | 2553 | "Mode for displaying and reprioritizing top priority Todos. |
| 986 | 2554 | ||
| 987 | \\{todos-filter-items-mode-map}" | 2555 | \\{todos-filter-items-mode-map}" |
| @@ -992,15 +2560,14 @@ Set by `todos-toggle-show-done-only' and used by | |||
| 992 | (defun todos-save () | 2560 | (defun todos-save () |
| 993 | "Save the current Todos file." | 2561 | "Save the current Todos file." |
| 994 | (interactive) | 2562 | (interactive) |
| 995 | ;; (todos-update-categories-sexp) | ||
| 996 | (save-buffer) | 2563 | (save-buffer) |
| 997 | ;; (if todos-save-top-priorities-too (todos-save-top-priorities)) | 2564 | ;; (if todos-save-top-priorities-too (todos-save-top-priorities)) |
| 998 | ) | 2565 | ) |
| 999 | 2566 | ||
| 1000 | (defun todos-quit () | 2567 | (defun todos-quit () |
| 1001 | "Exit the current Todos-related buffer. | 2568 | "Exit the current Todos-related buffer. |
| 1002 | Depending on the specific mode, this either kills and the buffer | 2569 | Depending on the specific mode, this either kills the buffer or |
| 1003 | or buries it." | 2570 | buries it and restores state as needed." |
| 1004 | (interactive) | 2571 | (interactive) |
| 1005 | (cond ((eq major-mode 'todos-categories-mode) | 2572 | (cond ((eq major-mode 'todos-categories-mode) |
| 1006 | (kill-buffer) | 2573 | (kill-buffer) |
| @@ -1010,13 +2577,14 @@ or buries it." | |||
| 1010 | (kill-buffer) | 2577 | (kill-buffer) |
| 1011 | (todos-show)) | 2578 | (todos-show)) |
| 1012 | ((member major-mode (list 'todos-mode 'todos-archive-mode)) | 2579 | ((member major-mode (list 'todos-mode 'todos-archive-mode)) |
| 1013 | (todos-save) | 2580 | ;; Have to write previously nonexistant archives to file. |
| 2581 | (unless (file-exists-p (buffer-file-name)) (todos-save)) | ||
| 2582 | ;; FIXME: or should it save unconditionally? | ||
| 2583 | ;; (todos-save) | ||
| 1014 | (bury-buffer)))) | 2584 | (bury-buffer)))) |
| 1015 | 2585 | ||
| 1016 | ;; --------------------------------------------------------------------------- | 2586 | ;; --------------------------------------------------------------------------- |
| 1017 | ;;; Commands | 2587 | ;;; Display Commands |
| 1018 | |||
| 1019 | ;;; Display | ||
| 1020 | 2588 | ||
| 1021 | ;;;###autoload | 2589 | ;;;###autoload |
| 1022 | (defun todos-show (&optional solicit-file) | 2590 | (defun todos-show (&optional solicit-file) |
| @@ -1034,11 +2602,17 @@ of `todos-categories' for the current Todos file, on subsequent | |||
| 1034 | invocations whichever category was displayed last. If | 2602 | invocations whichever category was displayed last. If |
| 1035 | `todos-display-categories-first' is non-nil, then the first | 2603 | `todos-display-categories-first' is non-nil, then the first |
| 1036 | invocation of `todos-show' displays a clickable listing of the | 2604 | invocation of `todos-show' displays a clickable listing of the |
| 1037 | categories in the current Todos file." | 2605 | categories in the current Todos file. |
| 2606 | |||
| 2607 | In Todos mode just the category's unfinished todo items are shown | ||
| 2608 | by default. The done items are hidden, but typing | ||
| 2609 | `\\[todos-toggle-view-done-items]' displays them below the todo | ||
| 2610 | items. With non-nil user option `todos-show-with-done' both todo | ||
| 2611 | and done items are always shown on visiting a category." | ||
| 1038 | (interactive "P") | 2612 | (interactive "P") |
| 1039 | (let ((file (cond (solicit-file | 2613 | (let ((file (cond (solicit-file |
| 1040 | (if (funcall todos-files-function) | 2614 | (if (funcall todos-files-function) |
| 1041 | (todos-read-file-name "Select a Todos file to visit: " | 2615 | (todos-read-file-name "Choose a Todos file to visit: " |
| 1042 | nil t) | 2616 | nil t) |
| 1043 | (error "There are no Todos files"))) | 2617 | (error "There are no Todos files"))) |
| 1044 | ((eq major-mode 'todos-archive-mode) | 2618 | ((eq major-mode 'todos-archive-mode) |
| @@ -1062,6 +2636,61 @@ categories in the current Todos file." | |||
| 1062 | (save-excursion (todos-category-select))) | 2636 | (save-excursion (todos-category-select))) |
| 1063 | (setq todos-first-visit nil))) | 2637 | (setq todos-first-visit nil))) |
| 1064 | 2638 | ||
| 2639 | (defun todos-display-categories () | ||
| 2640 | "Display a table of the current file's categories and item counts. | ||
| 2641 | |||
| 2642 | In the initial display the categories are numbered, indicating | ||
| 2643 | their current order for navigating by \\[todos-forward-category] | ||
| 2644 | and \\[todos-backward-category]. You can persistantly change the | ||
| 2645 | order of the category at point by typing \\[todos-raise-category] | ||
| 2646 | or \\[todos-lower-category]. | ||
| 2647 | |||
| 2648 | The labels above the category names and item counts are buttons, | ||
| 2649 | and clicking these changes the display: sorted by category name | ||
| 2650 | or by the respective item counts (alternately descending or | ||
| 2651 | ascending). In these displays the categories are not numbered | ||
| 2652 | and \\[todos-raise-category] and \\[todos-lower-category] are | ||
| 2653 | disabled. (Programmatically, the sorting is triggered by passing | ||
| 2654 | a non-nil SORTKEY argument.) | ||
| 2655 | |||
| 2656 | In addition, the lines with the category names and item counts | ||
| 2657 | are buttonized, and pressing one of these button jumps to the | ||
| 2658 | category in Todos mode (or Todos Archive mode, for categories | ||
| 2659 | containing only archived items, provided user option | ||
| 2660 | `todos-ignore-archived-categories' is non-nil. These categories | ||
| 2661 | are shown in `todos-archived-only' face." | ||
| 2662 | (interactive) | ||
| 2663 | (todos-display-categories-1) | ||
| 2664 | (let (sortkey) | ||
| 2665 | (todos-update-categories-display sortkey))) | ||
| 2666 | |||
| 2667 | ;; ;; FIXME: make this toggle with todos-display-categories | ||
| 2668 | ;; (defun todos-display-categories-alphabetically () | ||
| 2669 | ;; "" | ||
| 2670 | ;; (interactive) | ||
| 2671 | ;; (todos-display-sorted 'alpha)) | ||
| 2672 | |||
| 2673 | ;; ;; FIXME: provide key bindings for these or delete them | ||
| 2674 | ;; (defun todos-display-categories-sorted-by-todo () | ||
| 2675 | ;; "" | ||
| 2676 | ;; (interactive) | ||
| 2677 | ;; (todos-display-sorted 'todo)) | ||
| 2678 | |||
| 2679 | ;; (defun todos-display-categories-sorted-by-diary () | ||
| 2680 | ;; "" | ||
| 2681 | ;; (interactive) | ||
| 2682 | ;; (todos-display-sorted 'diary)) | ||
| 2683 | |||
| 2684 | ;; (defun todos-display-categories-sorted-by-done () | ||
| 2685 | ;; "" | ||
| 2686 | ;; (interactive) | ||
| 2687 | ;; (todos-display-sorted 'done)) | ||
| 2688 | |||
| 2689 | ;; (defun todos-display-categories-sorted-by-archived () | ||
| 2690 | ;; "" | ||
| 2691 | ;; (interactive) | ||
| 2692 | ;; (todos-display-sorted 'archived)) | ||
| 2693 | |||
| 1065 | (defun todos-toggle-item-numbering () | 2694 | (defun todos-toggle-item-numbering () |
| 1066 | "" | 2695 | "" |
| 1067 | (interactive) | 2696 | (interactive) |
| @@ -1070,20 +2699,16 @@ categories in the current Todos file." | |||
| 1070 | (defun todos-toggle-view-done-items () | 2699 | (defun todos-toggle-view-done-items () |
| 1071 | "Show hidden or hide visible done items in current category." | 2700 | "Show hidden or hide visible done items in current category." |
| 1072 | (interactive) | 2701 | (interactive) |
| 1073 | (save-excursion | 2702 | (if (zerop (todos-get-count 'done (todos-current-category))) |
| 1074 | (goto-char (point-min)) | 2703 | (message "There are no done items in this category.") |
| 1075 | (let ((todos-show-with-done | 2704 | (save-excursion |
| 1076 | (if (re-search-forward todos-done-string-start nil t) | 2705 | (goto-char (point-min)) |
| 1077 | nil | 2706 | (let ((todos-show-with-done (not (re-search-forward |
| 1078 | t)) | 2707 | todos-done-string-start nil t)))) |
| 1079 | (cat (todos-current-category))) | 2708 | (todos-category-select))))) |
| 1080 | (todos-category-select) | ||
| 1081 | (when (zerop (todos-get-count 'done cat)) | ||
| 1082 | (message "There are no done items in this category."))))) | ||
| 1083 | 2709 | ||
| 1084 | ;; FIXME: should there be `todos-toggle-view-todo-items'? | ||
| 1085 | (defun todos-toggle-show-done-only () | 2710 | (defun todos-toggle-show-done-only () |
| 1086 | "Make category display done or back to todo items." ;FIXME | 2711 | "Switch between displaying only done or only todo items." |
| 1087 | (interactive) | 2712 | (interactive) |
| 1088 | (setq todos-show-done-only (not todos-show-done-only)) | 2713 | (setq todos-show-done-only (not todos-show-done-only)) |
| 1089 | (todos-category-select)) | 2714 | (todos-category-select)) |
| @@ -1126,15 +2751,16 @@ last category displayed." | |||
| 1126 | (todos-show-archive t)) | 2751 | (todos-show-archive t)) |
| 1127 | 2752 | ||
| 1128 | (defun todos-highlight-item () | 2753 | (defun todos-highlight-item () |
| 1129 | "Highlight the todo item the cursor is on." | 2754 | "Toggle highlighting the todo item the cursor is on." |
| 1130 | (interactive) | 2755 | (interactive) |
| 1131 | (if hl-line-mode ; todos-highlight-item | 2756 | (require 'hl-line) |
| 1132 | (hl-line-mode 0) | 2757 | (if hl-line-mode |
| 2758 | (hl-line-mode -1) | ||
| 1133 | (hl-line-mode 1))) | 2759 | (hl-line-mode 1))) |
| 1134 | 2760 | ||
| 1135 | (defun todos-toggle-display-date-time (&optional all) | 2761 | (defun todos-toggle-display-date-time () ;(&optional all) |
| 1136 | "Hide or show date/time of todo items in current category. | 2762 | "Hide or show date-time header of todo items.";; in current category. |
| 1137 | With non-nil prefix argument ALL do this in the whole file." | 2763 | ;; With non-nil prefix argument ALL do this in the whole file." |
| 1138 | (interactive "P") | 2764 | (interactive "P") |
| 1139 | (save-excursion | 2765 | (save-excursion |
| 1140 | (save-restriction | 2766 | (save-restriction |
| @@ -1145,7 +2771,9 @@ With non-nil prefix argument ALL do this in the whole file." | |||
| 1145 | (setq ov (pop ovs)) | 2771 | (setq ov (pop ovs)) |
| 1146 | (if (equal (overlay-get ov 'display) "") | 2772 | (if (equal (overlay-get ov 'display) "") |
| 1147 | (setq ovs nil hidden t))) | 2773 | (setq ovs nil hidden t))) |
| 1148 | (when all (widen) (goto-char (point-min))) | 2774 | ;; (when all |
| 2775 | (widen) | ||
| 2776 | (goto-char (point-min));) | ||
| 1149 | (if hidden | 2777 | (if hidden |
| 1150 | (remove-overlays (point-min) (point-max) 'display "") | 2778 | (remove-overlays (point-min) (point-max) 'display "") |
| 1151 | (while (not (eobp)) | 2779 | (while (not (eobp)) |
| @@ -1177,7 +2805,7 @@ is \"*\", then the mark is \"@\"." | |||
| 1177 | (while (or (and all (not (eobp))) | 2805 | (while (or (and all (not (eobp))) |
| 1178 | (< i n)) | 2806 | (< i n)) |
| 1179 | (let* ((cat (todos-current-category)) | 2807 | (let* ((cat (todos-current-category)) |
| 1180 | (ov (todos-item-marked-p)) | 2808 | (ov (todos-marked-item-p)) |
| 1181 | (marked (assoc cat todos-categories-with-marks))) | 2809 | (marked (assoc cat todos-categories-with-marks))) |
| 1182 | (if (and ov (not all)) | 2810 | (if (and ov (not all)) |
| 1183 | (progn | 2811 | (progn |
| @@ -1187,7 +2815,7 @@ is \"*\", then the mark is \"@\"." | |||
| 1187 | (assq-delete-all cat todos-categories-with-marks)) | 2815 | (assq-delete-all cat todos-categories-with-marks)) |
| 1188 | (setcdr marked (1- (cdr marked))))) | 2816 | (setcdr marked (1- (cdr marked))))) |
| 1189 | (when (todos-item-start) | 2817 | (when (todos-item-start) |
| 1190 | (unless (and all (todos-item-marked-p)) | 2818 | (unless (and all (todos-marked-item-p)) |
| 1191 | (setq ov (make-overlay (point) (point))) | 2819 | (setq ov (make-overlay (point) (point))) |
| 1192 | (overlay-put ov 'before-string todos-item-mark) | 2820 | (overlay-put ov 'before-string todos-item-mark) |
| 1193 | (if marked | 2821 | (if marked |
| @@ -1211,274 +2839,323 @@ is \"*\", then the mark is \"@\"." | |||
| 1211 | (delq (assoc (todos-current-category) todos-categories-with-marks) | 2839 | (delq (assoc (todos-current-category) todos-categories-with-marks) |
| 1212 | todos-categories-with-marks))) | 2840 | todos-categories-with-marks))) |
| 1213 | 2841 | ||
| 1214 | (defun todos-update-merged-files () | 2842 | (defun todos-set-top-priorities-in-file () |
| 1215 | "Interactively add files to or remove from `todos-merged-files'. | 2843 | "Set number of top priorities for this file. |
| 1216 | You can also customize `todos-merged-files' directly." | 2844 | See `todos-set-top-priorities' for more details." |
| 1217 | (interactive) ;FIXME | ||
| 1218 | (let ((files (funcall todos-files-function))) | ||
| 1219 | (dolist (f files) | ||
| 1220 | (if (member f todos-merged-files) | ||
| 1221 | (and (y-or-n-p | ||
| 1222 | (format "Remove \"%s\" from list of merged Todos files? " | ||
| 1223 | (file-name-sans-extension (file-name-nondirectory f)))) | ||
| 1224 | (setq todos-merged-files (delete f todos-merged-files))) | ||
| 1225 | (and (y-or-n-p | ||
| 1226 | (format "Add \"%s\" to list of merged Todos files? " | ||
| 1227 | (file-name-sans-extension (file-name-nondirectory f)))) | ||
| 1228 | (setq todos-merged-files | ||
| 1229 | (append todos-merged-files (list f))))))) | ||
| 1230 | (customize-save-variable 'todos-merged-files todos-merged-files)) | ||
| 1231 | |||
| 1232 | (defvar todos-top-priorities-widgets nil | ||
| 1233 | "Widget placeholder used by `todos-set-top-priorities'. | ||
| 1234 | This variable temporarily holds user changed values which are | ||
| 1235 | saved to `todos-priorities-rules'.") | ||
| 1236 | |||
| 1237 | (defun todos-set-top-priorities () | ||
| 1238 | "" | ||
| 1239 | (interactive) | 2845 | (interactive) |
| 1240 | (let ((buf (get-buffer-create "*Todos Top Priorities*")) | 2846 | (todos-set-top-priorities)) |
| 1241 | (files (funcall todos-files-function)) | ||
| 1242 | file frules cats fwidget cwidgets rules) | ||
| 1243 | (with-current-buffer buf | ||
| 1244 | (let ((inhibit-read-only t)) | ||
| 1245 | (erase-buffer)) | ||
| 1246 | (remove-overlays) | ||
| 1247 | (kill-all-local-variables) | ||
| 1248 | (setq todos-top-priorities-widgets nil) | ||
| 1249 | (dolist (f files) | ||
| 1250 | (with-temp-buffer | ||
| 1251 | (insert-file-contents f) | ||
| 1252 | (setq file (file-name-sans-extension (file-name-nondirectory f)) | ||
| 1253 | frules (assoc file todos-priorities-rules) | ||
| 1254 | cats (mapcar 'car (todos-set-categories)))) | ||
| 1255 | (setq fwidget | ||
| 1256 | (widget-create 'editable-field | ||
| 1257 | :size 2 | ||
| 1258 | :value (or (and frules (cadr frules)) | ||
| 1259 | "") | ||
| 1260 | :tag file | ||
| 1261 | :format " %v : %t\n")) | ||
| 1262 | (dolist (c cats) | ||
| 1263 | (let ((tp-num (cdr (assoc c cats))) | ||
| 1264 | cwidget) | ||
| 1265 | (widget-insert " ") | ||
| 1266 | (setq cwidget (widget-create 'editable-field | ||
| 1267 | :size 2 | ||
| 1268 | :value (or tp-num "") | ||
| 1269 | :tag c | ||
| 1270 | :format " %v : %t\n")) | ||
| 1271 | (push cwidget cwidgets))) | ||
| 1272 | (push (cons fwidget cwidgets) todos-top-priorities-widgets)) | ||
| 1273 | (widget-insert "\n\n") | ||
| 1274 | (widget-create 'push-button | ||
| 1275 | :notify (lambda (widget &rest ignore) | ||
| 1276 | (kill-buffer)) | ||
| 1277 | "Cancel") | ||
| 1278 | (widget-insert " ") | ||
| 1279 | (widget-create 'push-button | ||
| 1280 | :notify (lambda (&rest ignore) | ||
| 1281 | (let ((widgets todos-top-priorities-widgets) | ||
| 1282 | (rules todos-priorities-rules) | ||
| 1283 | tp-cats) | ||
| 1284 | (setq rules nil) | ||
| 1285 | (dolist (w widgets) | ||
| 1286 | (let* ((fwid (car w)) | ||
| 1287 | (cwids (cdr w)) | ||
| 1288 | (fname (widget-get fwid :tag)) | ||
| 1289 | (fval (widget-value fwid))) | ||
| 1290 | (dolist (c cwids) | ||
| 1291 | (let ((cat (widget-get c :tag)) | ||
| 1292 | (cval (widget-value c))) | ||
| 1293 | (push (cons cat cval) tp-cats))) | ||
| 1294 | (push (list fname fval tp-cats) rules))) | ||
| 1295 | (setq todos-priorities-rules rules) | ||
| 1296 | (customize-save-variable 'todos-priorities-rules | ||
| 1297 | todos-priorities-rules))) | ||
| 1298 | "Apply") | ||
| 1299 | (use-local-map widget-keymap) | ||
| 1300 | (widget-setup)) | ||
| 1301 | (set-window-buffer (selected-window) (set-buffer buf)))) | ||
| 1302 | |||
| 1303 | (defun todos-filter-items (&optional filter merge) | ||
| 1304 | "Display a filtered list of items from different categories. | ||
| 1305 | 2847 | ||
| 1306 | The special items are either the first NUM items (the top priority items) or the items marked as diary entries in each category of the current Todos file. | 2848 | (defun todos-set-top-priorities-in-category () |
| 1307 | 2849 | "Set number of top priorities for this category. | |
| 1308 | Number of entries for each category is given by NUM, which | 2850 | See `todos-set-top-priorities' for more details." |
| 1309 | defaults to `todos-show-priorities'. With non-nil argument | 2851 | (interactive) |
| 1310 | MERGE list top priorities of all Todos files in | 2852 | (todos-set-top-priorities t)) |
| 1311 | `todos-merged-files'. If `todos-prompt-merged-files' is non-nil, | ||
| 1312 | prompt to update the list of merged files." | ||
| 1313 | (let ((num (if (consp filter) (cdr filter) todos-show-priorities)) | ||
| 1314 | (buf (get-buffer-create todos-tmp-buffer-name)) | ||
| 1315 | (files (list todos-current-todos-file)) | ||
| 1316 | regexp fname bufstr cat beg end done) | ||
| 1317 | (when merge | ||
| 1318 | ;; FIXME: same or different treatment for top priorities and other | ||
| 1319 | ;; filters? And what about todos-prompt-merged-files? | ||
| 1320 | (setq files (if (member filter '(diary regexp custom)) | ||
| 1321 | (or (and todos-prompt-merged-files | ||
| 1322 | (todos-update-merged-files)) | ||
| 1323 | todos-merged-files | ||
| 1324 | (todos-update-merged-files)) | ||
| 1325 | ;; Set merged files for top priorities. | ||
| 1326 | (or (mapcar (lambda (f) | ||
| 1327 | (let ((file (car f)) | ||
| 1328 | (val (nth 1 f))) | ||
| 1329 | (and val (not (zerop val)) | ||
| 1330 | (push file files)))) | ||
| 1331 | todos-priorities-rules) | ||
| 1332 | (if (y-or-n-p "Choose files for merging top priorities? ") | ||
| 1333 | (progn (todos-set-top-priorities) (error "")) | ||
| 1334 | (error "No files are set for merging top priorities")))))) | ||
| 1335 | (with-current-buffer buf | ||
| 1336 | (erase-buffer) | ||
| 1337 | (kill-all-local-variables) | ||
| 1338 | (todos-filter-items-mode)) | ||
| 1339 | (when (eq filter 'regexp) | ||
| 1340 | (setq regexp (read-string "Enter a regular expression: "))) | ||
| 1341 | (save-current-buffer | ||
| 1342 | (dolist (f files) | ||
| 1343 | (setq fname (file-name-sans-extension (file-name-nondirectory f))) | ||
| 1344 | (with-temp-buffer | ||
| 1345 | (insert-file-contents f) | ||
| 1346 | (goto-char (point-min)) | ||
| 1347 | ;; Unless the number of items to show was supplied by prefix | ||
| 1348 | ;; argument of caller, override `todos-show-priorities' with the | ||
| 1349 | ;; nonzero file-wide value from `todos-priorities-rules'. | ||
| 1350 | (unless (consp filter) | ||
| 1351 | (let ((tp-val (nth 1 (assoc fname todos-priorities-rules)))) | ||
| 1352 | (unless (zerop (length tp-val)) | ||
| 1353 | (setq num (string-to-number tp-val))))) | ||
| 1354 | (unless (looking-at (concat "^" (regexp-quote todos-category-beg))) | ||
| 1355 | (kill-line 1)) | ||
| 1356 | (while (re-search-forward | ||
| 1357 | (concat "^" (regexp-quote todos-category-beg) "\\(.+\\)\n") | ||
| 1358 | nil t) | ||
| 1359 | (setq cat (match-string 1)) | ||
| 1360 | ;; Unless the number of items to show was supplied by prefix | ||
| 1361 | ;; argument of caller, override `todos-show-priorities' with the | ||
| 1362 | ;; nonzero category-wide value from `todos-priorities-rules'. | ||
| 1363 | (unless (consp filter) | ||
| 1364 | (let* ((cats (nth 2 (assoc fname todos-priorities-rules))) | ||
| 1365 | (tp-val (cdr (assoc cat cats)))) | ||
| 1366 | (unless (zerop (length tp-val)) | ||
| 1367 | (setq num (string-to-number tp-val))))) | ||
| 1368 | (delete-region (match-beginning 0) (match-end 0)) | ||
| 1369 | (setq beg (point)) ; Start of first entry. | ||
| 1370 | (setq end (if (re-search-forward | ||
| 1371 | (concat "^" (regexp-quote todos-category-beg)) nil t) | ||
| 1372 | (match-beginning 0) | ||
| 1373 | (point-max))) | ||
| 1374 | (goto-char beg) | ||
| 1375 | (setq done | ||
| 1376 | (if (re-search-forward | ||
| 1377 | (concat "\n" (regexp-quote todos-category-done)) end t) | ||
| 1378 | (match-beginning 0) | ||
| 1379 | end)) | ||
| 1380 | (delete-region done end) | ||
| 1381 | (setq end done) | ||
| 1382 | (narrow-to-region beg end) ; Process current category. | ||
| 1383 | (goto-char (point-min)) | ||
| 1384 | ;; Apply the filter. | ||
| 1385 | (cond ((eq filter 'diary) | ||
| 1386 | (while (not (eobp)) | ||
| 1387 | (if (looking-at (regexp-quote todos-nondiary-start)) | ||
| 1388 | (todos-remove-item) | ||
| 1389 | (todos-forward-item)))) | ||
| 1390 | ((eq filter 'regexp) | ||
| 1391 | (while (not (eobp)) | ||
| 1392 | (if (string-match regexp (todos-item-string)) | ||
| 1393 | (todos-forward-item) | ||
| 1394 | (todos-remove-item)))) | ||
| 1395 | ((eq filter 'custom) | ||
| 1396 | (if todos-filter-function | ||
| 1397 | (funcall todos-filter-function) | ||
| 1398 | (error "No custom filter function has been defined"))) | ||
| 1399 | (t ; Filter top priority items. | ||
| 1400 | (todos-forward-item num))) | ||
| 1401 | (setq beg (point)) | ||
| 1402 | (unless (member filter '(diary regexp custom)) | ||
| 1403 | (delete-region beg end)) | ||
| 1404 | (goto-char (point-min)) | ||
| 1405 | ;; Add file (if using merged files) and category tags to item. | ||
| 1406 | (while (not (eobp)) | ||
| 1407 | (when (re-search-forward | ||
| 1408 | (concat todos-date-string-start todos-date-pattern | ||
| 1409 | "\\( " diary-time-regexp "\\)?" | ||
| 1410 | (regexp-quote todos-nondiary-end) "?") | ||
| 1411 | nil t) | ||
| 1412 | (insert (concat " [" (if merge (concat fname ":")) cat "]"))) | ||
| 1413 | (forward-line)) | ||
| 1414 | (widen)) | ||
| 1415 | (setq bufstr (buffer-string)) | ||
| 1416 | (with-current-buffer buf | ||
| 1417 | (let (buffer-read-only) | ||
| 1418 | (insert bufstr)))))) | ||
| 1419 | (set-window-buffer (selected-window) (set-buffer buf)) | ||
| 1420 | (todos-prefix-overlays) | ||
| 1421 | (goto-char (point-min)) | ||
| 1422 | ;; FIXME: this is necessary -- why? | ||
| 1423 | (font-lock-fontify-buffer))) | ||
| 1424 | 2853 | ||
| 1425 | (defun todos-top-priorities (&optional num) | 2854 | (defun todos-top-priorities (&optional num) |
| 1426 | "List top priorities of each category in `todos-merged-files'. | 2855 | "List top priorities of each category in `todos-filter-files'. |
| 1427 | Number of entries for each category is given by NUM, which | 2856 | Number of entries for each category is given by NUM, which |
| 1428 | defaults to `todos-show-priorities'." | 2857 | defaults to `todos-show-priorities'." |
| 1429 | (interactive "p") | 2858 | (interactive "P") |
| 1430 | (let ((arg (if num (cons 'top num) 'top))) | 2859 | (let ((arg (if num (cons 'top num) 'top)) |
| 1431 | (todos-filter-items arg))) | 2860 | (buf todos-top-priorities-buffer) |
| 1432 | 2861 | (file todos-current-todos-file)) | |
| 1433 | (defun todos-merged-top-priorities (&optional num) | 2862 | (todos-filter-items arg) |
| 1434 | "List top priorities of each category in `todos-merged-files'. | 2863 | (todos-special-buffer-name buf file))) |
| 1435 | Number of entries for each category is given by NUM, which | 2864 | |
| 1436 | defaults to `todos-show-priorities'." | 2865 | (defun todos-top-priorities-multifile (&optional arg) |
| 1437 | (interactive "p") | 2866 | "List top priorities of each category in `todos-filter-files'. |
| 1438 | (let ((arg (if num (cons 'top num) 'top))) | 2867 | |
| 1439 | (todos-filter-items arg t))) | 2868 | If the prefix argument ARG is a number, this is the maximum |
| 2869 | number of top priorities to list in each category. If the prefix | ||
| 2870 | argument is `C-u', prompt for which files to filter and use | ||
| 2871 | `todos-show-priorities' as the number of top priorities to list | ||
| 2872 | in each category. If the prefix argument is `C-uC-u', prompt | ||
| 2873 | both for which files to filter and for how many top priorities to | ||
| 2874 | list in each category." | ||
| 2875 | (interactive "P") | ||
| 2876 | (let* ((buf todos-top-priorities-buffer) | ||
| 2877 | files | ||
| 2878 | (pref (if (numberp arg) | ||
| 2879 | (cons 'top arg) | ||
| 2880 | (setq files (if (or (consp arg) | ||
| 2881 | (null todos-filter-files)) | ||
| 2882 | (todos-multiple-files) | ||
| 2883 | todos-filter-files)) | ||
| 2884 | (if (equal arg '(16)) | ||
| 2885 | (cons 'top (read-number | ||
| 2886 | "Enter number of top priorities to show: " | ||
| 2887 | todos-show-priorities)) | ||
| 2888 | 'top)))) | ||
| 2889 | (todos-filter-items pref t) | ||
| 2890 | (todos-special-buffer-name buf files))) | ||
| 1440 | 2891 | ||
| 1441 | (defun todos-diary-items () | 2892 | (defun todos-diary-items () |
| 1442 | "Display todo items for diary inclusion in this Todos file." | 2893 | "Display todo items for diary inclusion in this Todos file." |
| 1443 | (interactive) | 2894 | (interactive) |
| 1444 | (todos-filter-items 'diary)) | 2895 | (let ((buf todos-diary-items-buffer) |
| 2896 | (file todos-current-todos-file)) | ||
| 2897 | (todos-filter-items 'diary) | ||
| 2898 | (todos-special-buffer-name buf file))) | ||
| 1445 | 2899 | ||
| 1446 | (defun todos-merged-diary-items () | 2900 | (defun todos-diary-items-multifile (&optional arg) |
| 1447 | "Display todo items for diary inclusion in one or more Todos file. | 2901 | "Display todo items for diary inclusion in one or more Todos file. |
| 1448 | The files are those listed in `todos-merged-files'." | 2902 | The files are those listed in `todos-filter-files'." |
| 1449 | (interactive) | 2903 | (interactive "P") |
| 1450 | (todos-filter-items 'diary t)) | 2904 | (let ((buf todos-diary-items-buffer) |
| 2905 | (files (if (or arg (null todos-filter-files)) | ||
| 2906 | (todos-multiple-files) | ||
| 2907 | todos-filter-files))) | ||
| 2908 | (todos-filter-items 'diary t) | ||
| 2909 | (todos-special-buffer-name buf files))) | ||
| 1451 | 2910 | ||
| 1452 | (defun todos-regexp-items () | 2911 | (defun todos-regexp-items () |
| 1453 | "Display todo items matching a user-entered regular expression. | 2912 | "Display todo items matching a user-entered regular expression. |
| 1454 | The items are those in the current Todos file." | 2913 | The items are those in the current Todos file." |
| 1455 | (interactive) | 2914 | (interactive) |
| 1456 | (todos-filter-items 'regexp)) | 2915 | (let ((buf todos-regexp-items-buffer) |
| 2916 | (file todos-current-todos-file)) | ||
| 2917 | (todos-filter-items 'regexp) | ||
| 2918 | (todos-special-buffer-name buf file))) | ||
| 1457 | 2919 | ||
| 1458 | (defun todos-merged-regexp-items () | 2920 | (defun todos-regexp-items-multifile (&optional arg) |
| 1459 | "Display todo items matching a user-entered regular expression. | 2921 | "Display todo items matching a user-entered regular expression. |
| 1460 | The items are those in the files listed in `todos-merged-files'." | 2922 | The items are those in the files listed in `todos-filter-files'." |
| 1461 | (interactive) | 2923 | (interactive "P") |
| 1462 | (todos-filter-items 'regexp t)) | 2924 | (let ((buf todos-regexp-items-buffer) |
| 2925 | (files (if (or arg (null todos-filter-files)) | ||
| 2926 | (todos-multiple-files) | ||
| 2927 | todos-filter-files))) | ||
| 2928 | (todos-filter-items 'regexp t) | ||
| 2929 | (todos-special-buffer-name buf files))) | ||
| 1463 | 2930 | ||
| 1464 | (defun todos-custom-items () | 2931 | (defun todos-custom-items () |
| 1465 | "Display todo items filtered by `todos-filter-function'. | 2932 | "Display todo items filtered by `todos-filter-function'. |
| 1466 | The items are those in the current Todos file." | 2933 | The items are those in the current Todos file." |
| 1467 | (interactive) | 2934 | (interactive) |
| 1468 | (todos-filter-items 'custom)) | 2935 | (let ((buf todos-custom-items-buffer) |
| 2936 | (file todos-current-todos-file)) | ||
| 2937 | (todos-filter-items 'custom) | ||
| 2938 | (todos-special-buffer-name buf file))) | ||
| 1469 | 2939 | ||
| 1470 | (defun todos-merged-custom-items () | 2940 | (defun todos-custom-items-multifile (&optional arg) |
| 1471 | "Display todo items filtered by `todos-filter-function'. | 2941 | "Display todo items filtered by `todos-filter-function'. |
| 1472 | The items are those in the files listed in `todos-merged-files'." | 2942 | The items are those in the files listed in `todos-filter-files'." |
| 2943 | (interactive "P") | ||
| 2944 | (let ((buf todos-custom-items-buffer) | ||
| 2945 | (files (if (or arg (null todos-filter-files)) | ||
| 2946 | (todos-multiple-files) | ||
| 2947 | todos-filter-files))) | ||
| 2948 | (todos-filter-items 'custom t) | ||
| 2949 | (todos-special-buffer-name buf files))) | ||
| 2950 | |||
| 2951 | (defun todos-print (&optional to-file) | ||
| 2952 | "Produce a printable version of the current Todos buffer. | ||
| 2953 | This converts overlays and soft line wrapping and, depending on | ||
| 2954 | the value of `todos-print-function', includes faces. With | ||
| 2955 | non-nil argument TO-FILE write the printable version to a file; | ||
| 2956 | otherwise, send it to the default printer." | ||
| 1473 | (interactive) | 2957 | (interactive) |
| 1474 | (todos-filter-items 'custom t)) | 2958 | (let ((buf todos-print-buffer) |
| 2959 | (header (cond | ||
| 2960 | ((eq major-mode 'todos-mode) | ||
| 2961 | (concat "Todos File: " | ||
| 2962 | (todos-short-file-name todos-current-todos-file) | ||
| 2963 | "\nCategory: " (todos-current-category))) | ||
| 2964 | ((eq major-mode 'todos-filter-items-mode) | ||
| 2965 | "Todos Top Priorities"))) | ||
| 2966 | (prefix (propertize (concat todos-prefix " ") | ||
| 2967 | 'face 'todos-prefix-string)) | ||
| 2968 | (num 0) | ||
| 2969 | (fill-prefix (make-string todos-indent-to-here 32)) | ||
| 2970 | (content (buffer-string)) | ||
| 2971 | file) | ||
| 2972 | (with-current-buffer (get-buffer-create buf) | ||
| 2973 | (insert content) | ||
| 2974 | (goto-char (point-min)) | ||
| 2975 | (while (not (eobp)) | ||
| 2976 | (let ((beg (point)) | ||
| 2977 | (end (save-excursion (todos-item-end)))) | ||
| 2978 | (when todos-number-prefix | ||
| 2979 | (setq num (1+ num)) | ||
| 2980 | (setq prefix (propertize (concat (number-to-string num) " ") | ||
| 2981 | 'face 'todos-prefix-string))) | ||
| 2982 | (insert prefix) | ||
| 2983 | (fill-region beg end)) | ||
| 2984 | ;; Calling todos-forward-item infloops at todos-item-start due to | ||
| 2985 | ;; non-overlay prefix, so search for item start instead. | ||
| 2986 | (if (re-search-forward todos-item-start nil t) | ||
| 2987 | (beginning-of-line) | ||
| 2988 | (goto-char (point-max)))) | ||
| 2989 | (if (re-search-backward (concat "^" (regexp-quote todos-category-done)) | ||
| 2990 | nil t) | ||
| 2991 | (replace-match todos-done-separator)) | ||
| 2992 | (goto-char (point-min)) | ||
| 2993 | (insert header) | ||
| 2994 | (newline 2) | ||
| 2995 | (if to-file | ||
| 2996 | (let ((file (read-file-name "Print to file: "))) | ||
| 2997 | (funcall todos-print-function file)) | ||
| 2998 | (funcall todos-print-function))) | ||
| 2999 | (kill-buffer buf))) | ||
| 3000 | |||
| 3001 | (defun todos-print-to-file () | ||
| 3002 | "Save printable version of this Todos buffer to a file." | ||
| 3003 | (interactive) | ||
| 3004 | (todos-print t)) | ||
| 3005 | |||
| 3006 | (defun todos-convert-legacy-files () | ||
| 3007 | "Convert legacy Todo files to the current Todos format. | ||
| 3008 | The files `todo-file-do' and `todo-file-done' are converted and | ||
| 3009 | saved (the latter as a Todos Archive file) with a new name in | ||
| 3010 | `todos-files-directory'. See also the documentation string of | ||
| 3011 | `todos-todo-mode-date-time-regexp' for further details." | ||
| 3012 | (interactive) | ||
| 3013 | (if (fboundp 'todo-mode) | ||
| 3014 | (require 'todo-mode) | ||
| 3015 | (error "Void function `todo-mode'")) | ||
| 3016 | ;; Convert `todo-file-do'. | ||
| 3017 | (if (file-exists-p todo-file-do) | ||
| 3018 | (let ((default "todo-do-conv") | ||
| 3019 | file archive-sexp) | ||
| 3020 | (with-temp-buffer | ||
| 3021 | (insert-file-contents todo-file-do) | ||
| 3022 | (let ((end (search-forward ")" (line-end-position) t)) | ||
| 3023 | (beg (search-backward "(" (line-beginning-position) t))) | ||
| 3024 | (setq todo-categories | ||
| 3025 | (read (buffer-substring-no-properties beg end)))) | ||
| 3026 | (todo-mode) | ||
| 3027 | (delete-region (line-beginning-position) (1+ (line-end-position))) | ||
| 3028 | (while (not (eobp)) | ||
| 3029 | (cond | ||
| 3030 | ((looking-at (regexp-quote (concat todo-prefix todo-category-beg))) | ||
| 3031 | (replace-match todos-category-beg)) | ||
| 3032 | ((looking-at (regexp-quote todo-category-end)) | ||
| 3033 | (replace-match "")) | ||
| 3034 | ((looking-at (regexp-quote (concat todo-prefix " " | ||
| 3035 | todo-category-sep))) | ||
| 3036 | (replace-match todos-category-done)) | ||
| 3037 | ((looking-at (concat (regexp-quote todo-prefix) " " | ||
| 3038 | todos-todo-mode-date-time-regexp " " | ||
| 3039 | (regexp-quote todo-initials) ":")) | ||
| 3040 | (todos-convert-legacy-date-time))) | ||
| 3041 | (forward-line)) | ||
| 3042 | (setq file (concat todos-files-directory | ||
| 3043 | (read-string | ||
| 3044 | (format "Save file as (default \"%s\"): " default) | ||
| 3045 | nil nil default) | ||
| 3046 | ".todo")) | ||
| 3047 | (write-region (point-min) (point-max) file nil 'nomessage nil t)) | ||
| 3048 | (with-temp-buffer | ||
| 3049 | (insert-file-contents file) | ||
| 3050 | (let ((todos-categories (todos-make-categories-list t))) | ||
| 3051 | (todos-update-categories-sexp)) | ||
| 3052 | (write-region (point-min) (point-max) file nil 'nomessage)) | ||
| 3053 | ;; Convert `todo-file-done'. | ||
| 3054 | (when (file-exists-p todo-file-done) | ||
| 3055 | (with-temp-buffer | ||
| 3056 | (insert-file-contents todo-file-done) | ||
| 3057 | (let ((beg (make-marker)) | ||
| 3058 | (end (make-marker)) | ||
| 3059 | cat cats comment item) | ||
| 3060 | (while (not (eobp)) | ||
| 3061 | (when (looking-at todos-todo-mode-date-time-regexp) | ||
| 3062 | (set-marker beg (point)) | ||
| 3063 | (todos-convert-legacy-date-time) | ||
| 3064 | (set-marker end (point)) | ||
| 3065 | (goto-char beg) | ||
| 3066 | (insert "[" todos-done-string) | ||
| 3067 | (goto-char end) | ||
| 3068 | (insert "]") | ||
| 3069 | (forward-char) | ||
| 3070 | (when (looking-at todos-todo-mode-date-time-regexp) | ||
| 3071 | (todos-convert-legacy-date-time)) | ||
| 3072 | (when (looking-at (concat " " (regexp-quote todo-initials) ":")) | ||
| 3073 | (replace-match ""))) | ||
| 3074 | (if (re-search-forward | ||
| 3075 | (concat "^" todos-todo-mode-date-time-regexp) nil t) | ||
| 3076 | (goto-char (match-beginning 0)) | ||
| 3077 | (goto-char (point-max))) | ||
| 3078 | (backward-char) | ||
| 3079 | (when (looking-back "\\[\\([^][]+\\)\\]") | ||
| 3080 | (setq cat (match-string 1)) | ||
| 3081 | (goto-char (match-beginning 0)) | ||
| 3082 | (replace-match "")) | ||
| 3083 | ;; If the item ends with a non-comment parenthesis not | ||
| 3084 | ;; followed by a period, we lose (but we inherit that problem | ||
| 3085 | ;; from todo-mode.el). | ||
| 3086 | (when (looking-back "(\\(.*\\)) ") | ||
| 3087 | (setq comment (match-string 1)) | ||
| 3088 | (replace-match "") | ||
| 3089 | (insert "[" todos-comment-string ": " comment "]")) | ||
| 3090 | (set-marker end (point)) | ||
| 3091 | (if (member cat cats) | ||
| 3092 | ;; If item is already in its category, leave it there. | ||
| 3093 | (unless (save-excursion | ||
| 3094 | (re-search-backward | ||
| 3095 | (concat "^" (regexp-quote todos-category-beg) | ||
| 3096 | "\\(.*\\)$") nil t) | ||
| 3097 | (string= (match-string 1) cat)) | ||
| 3098 | ;; Else move it to its category. | ||
| 3099 | (setq item (buffer-substring-no-properties beg end)) | ||
| 3100 | (delete-region beg (1+ end)) | ||
| 3101 | (set-marker beg (point)) | ||
| 3102 | (re-search-backward | ||
| 3103 | (concat "^" (regexp-quote (concat todos-category-beg cat))) | ||
| 3104 | nil t) | ||
| 3105 | (forward-line) | ||
| 3106 | (if (re-search-forward | ||
| 3107 | (concat "^" (regexp-quote todos-category-beg) | ||
| 3108 | "\\(.*\\)$") nil t) | ||
| 3109 | (progn (goto-char (match-beginning 0)) | ||
| 3110 | (newline) | ||
| 3111 | (forward-line -1)) | ||
| 3112 | (goto-char (point-max))) | ||
| 3113 | (insert item "\n") | ||
| 3114 | (goto-char beg)) | ||
| 3115 | (push cat cats) | ||
| 3116 | (goto-char beg) | ||
| 3117 | (insert todos-category-beg cat "\n\n" todos-category-done "\n")) | ||
| 3118 | (forward-line)) | ||
| 3119 | (set-marker beg nil) | ||
| 3120 | (set-marker end nil)) | ||
| 3121 | (setq file (concat (file-name-sans-extension file) ".toda")) | ||
| 3122 | (write-region (point-min) (point-max) file nil 'nomessage nil t)) | ||
| 3123 | (with-temp-buffer | ||
| 3124 | (insert-file-contents file) | ||
| 3125 | (let ((todos-categories (todos-make-categories-list t))) | ||
| 3126 | (todos-update-categories-sexp)) | ||
| 3127 | (write-region (point-min) (point-max) file nil 'nomessage) | ||
| 3128 | (setq archive-sexp (read (buffer-substring-no-properties | ||
| 3129 | (line-beginning-position) | ||
| 3130 | (line-end-position))))) | ||
| 3131 | (setq file (concat (file-name-sans-extension file) ".todo")) | ||
| 3132 | ;; Update categories sexp of converted Todos file again, adding | ||
| 3133 | ;; counts of archived items. | ||
| 3134 | (with-temp-buffer | ||
| 3135 | (insert-file-contents file) | ||
| 3136 | (let ((sexp (read (buffer-substring-no-properties | ||
| 3137 | (line-beginning-position) | ||
| 3138 | (line-end-position))))) | ||
| 3139 | (dolist (cat sexp) | ||
| 3140 | (let ((archive-cat (assoc (car cat) archive-sexp))) | ||
| 3141 | (if archive-cat | ||
| 3142 | (aset (cdr cat) 3 (aref (cdr archive-cat) 2))))) | ||
| 3143 | (delete-region (line-beginning-position) (line-end-position)) | ||
| 3144 | (prin1 sexp (current-buffer))) | ||
| 3145 | (write-region (point-min) (point-max) file nil 'nomessage))) | ||
| 3146 | (todos-reevaluate-defcustoms) | ||
| 3147 | (message "Format conversion done.")) | ||
| 3148 | (error "No legacy Todo file exists"))) | ||
| 1475 | 3149 | ||
| 1476 | ;;; Navigation | 3150 | ;; --------------------------------------------------------------------------- |
| 3151 | ;;; Navigation Commands | ||
| 1477 | 3152 | ||
| 1478 | (defun todos-forward-category (&optional back) | 3153 | (defun todos-forward-category (&optional back) |
| 1479 | "Visit the numerically next category in this Todos file. | 3154 | "Visit the numerically next category in this Todos file. |
| 1480 | With non-nil argument BACK, visit the numerically previous | 3155 | If the current category is the highest numbered, visit the first |
| 1481 | category." | 3156 | category. With non-nil argument BACK, visit the numerically |
| 3157 | previous category (the highest numbered one, if the current | ||
| 3158 | category is the first)." | ||
| 1482 | (interactive) | 3159 | (interactive) |
| 1483 | (setq todos-category-number | 3160 | (setq todos-category-number |
| 1484 | (1+ (mod (- todos-category-number (if back 2 0)) | 3161 | (1+ (mod (- todos-category-number (if back 2 0)) |
| @@ -1487,20 +3164,22 @@ category." | |||
| 1487 | (goto-char (point-min))) | 3164 | (goto-char (point-min))) |
| 1488 | 3165 | ||
| 1489 | (defun todos-backward-category () | 3166 | (defun todos-backward-category () |
| 1490 | "Visit the numerically previous category in this Todos file." | 3167 | "Visit the numerically previous category in this Todos file. |
| 3168 | If the current category is the highest numbered, visit the first | ||
| 3169 | category." | ||
| 1491 | (interactive) | 3170 | (interactive) |
| 1492 | (todos-forward-category t)) | 3171 | (todos-forward-category t)) |
| 1493 | 3172 | ||
| 1494 | ;; FIXME: autoload? | ||
| 1495 | (defun todos-jump-to-category (&optional cat other-file) | 3173 | (defun todos-jump-to-category (&optional cat other-file) |
| 1496 | "Jump to a category in this or another Todos file. | 3174 | "Jump to a category in this or another Todos file. |
| 1497 | Optional argument CAT provides the category name. Otherwise, | 3175 | |
| 1498 | prompt for the category, with TAB completion on existing | 3176 | Programmatically, optional argument CAT provides the category |
| 1499 | categories. If a non-existing category name is entered, ask | 3177 | name. When nil (as in interactive calls), prompt for the |
| 1500 | whether to add a new category with this name, if affirmed, do so, | 3178 | category, with TAB completion on existing categories. If a |
| 1501 | then jump to that category. With non-nil argument OTHER-FILE, | 3179 | non-existing category name is entered, ask whether to add a new |
| 1502 | prompt for a Todos file, otherwise jump within the current Todos | 3180 | category with this name; if affirmed, add it, then jump to that |
| 1503 | file." | 3181 | category. With non-nil argument OTHER-FILE, prompt for a Todos |
| 3182 | file, otherwise jump within the current Todos file." | ||
| 1504 | (interactive) | 3183 | (interactive) |
| 1505 | (let ((file (or (and other-file | 3184 | (let ((file (or (and other-file |
| 1506 | (todos-read-file-name "Choose a Todos file: " nil t)) | 3185 | (todos-read-file-name "Choose a Todos file: " nil t)) |
| @@ -1519,9 +3198,6 @@ file." | |||
| 1519 | (and other-file (setq todos-current-todos-file file)) | 3198 | (and other-file (setq todos-current-todos-file file)) |
| 1520 | (let ((category (or (and (assoc cat todos-categories) cat) | 3199 | (let ((category (or (and (assoc cat todos-categories) cat) |
| 1521 | (todos-read-category "Jump to category: ")))) | 3200 | (todos-read-category "Jump to category: ")))) |
| 1522 | ;; ;; FIXME: why is this needed? | ||
| 1523 | ;; (if (string= "" category) | ||
| 1524 | ;; (setq category (todos-current-category))) | ||
| 1525 | ;; Clean up after selecting category in Todos Categories mode. | 3201 | ;; Clean up after selecting category in Todos Categories mode. |
| 1526 | (if (string= (buffer-name) todos-categories-buffer) | 3202 | (if (string= (buffer-name) todos-categories-buffer) |
| 1527 | (kill-buffer)) | 3203 | (kill-buffer)) |
| @@ -1542,6 +3218,37 @@ The category is chosen by prompt, with TAB completion." | |||
| 1542 | (interactive) | 3218 | (interactive) |
| 1543 | (todos-jump-to-category nil t)) | 3219 | (todos-jump-to-category nil t)) |
| 1544 | 3220 | ||
| 3221 | (defun todos-jump-to-item () | ||
| 3222 | "Jump to the file and category of the filtered item at point." | ||
| 3223 | (interactive) | ||
| 3224 | (let ((str (todos-item-string)) | ||
| 3225 | (buf (current-buffer)) | ||
| 3226 | cat file beg) | ||
| 3227 | (string-match (concat todos-date-string-start todos-date-pattern | ||
| 3228 | "\\(?: " diary-time-regexp "\\)?" | ||
| 3229 | (regexp-quote todos-nondiary-end) "?" | ||
| 3230 | "\\(?3: \\[\\(?2:.*:\\)?\\(?1:.*\\)\\]\\).*$") str) | ||
| 3231 | (setq cat (match-string 1 str)) | ||
| 3232 | (setq file (match-string 2 str)) | ||
| 3233 | (setq str (replace-match "" nil nil str 3)) | ||
| 3234 | (setq file (if file | ||
| 3235 | (concat todos-files-directory (substring file 0 -1) ".todo") | ||
| 3236 | todos-global-current-todos-file)) | ||
| 3237 | (find-file-noselect file) | ||
| 3238 | (with-current-buffer (get-file-buffer file) | ||
| 3239 | (widen) | ||
| 3240 | (goto-char (point-min)) | ||
| 3241 | (re-search-forward | ||
| 3242 | (concat "^" (regexp-quote (concat todos-category-beg cat))) nil t) | ||
| 3243 | (search-forward str) | ||
| 3244 | (setq beg (match-beginning 0))) | ||
| 3245 | (kill-buffer buf) | ||
| 3246 | (set-window-buffer (selected-window) (set-buffer (get-file-buffer file))) | ||
| 3247 | (setq todos-current-todos-file file) | ||
| 3248 | (setq todos-category-number (todos-category-number cat)) | ||
| 3249 | (todos-category-select) | ||
| 3250 | (goto-char beg))) | ||
| 3251 | |||
| 1545 | ;; FIXME ? disallow prefix arg value < 1 (re-search-* allows these) | 3252 | ;; FIXME ? disallow prefix arg value < 1 (re-search-* allows these) |
| 1546 | (defun todos-forward-item (&optional count) | 3253 | (defun todos-forward-item (&optional count) |
| 1547 | "Move point down to start of item with next lower priority. | 3254 | "Move point down to start of item with next lower priority. |
| @@ -1579,6 +3286,8 @@ With numerical prefix COUNT, move point COUNT items upward," | |||
| 1579 | (re-search-forward (concat "^" (regexp-quote todos-category-done)) nil t) | 3286 | (re-search-forward (concat "^" (regexp-quote todos-category-done)) nil t) |
| 1580 | (forward-line -1)))) | 3287 | (forward-line -1)))) |
| 1581 | 3288 | ||
| 3289 | ;; FIXME: (i) Extend search to other Todos files. (ii) Allow navigating among | ||
| 3290 | ;; hits. | ||
| 1582 | (defun todos-search () | 3291 | (defun todos-search () |
| 1583 | "Search for a regular expression in this Todos file. | 3292 | "Search for a regular expression in this Todos file. |
| 1584 | The search runs through the whole file and encompasses all and | 3293 | The search runs through the whole file and encompasses all and |
| @@ -1645,59 +3354,35 @@ face." | |||
| 1645 | (interactive) | 3354 | (interactive) |
| 1646 | (remove-overlays 1 (1+ (buffer-size)) 'face 'todos-search)) | 3355 | (remove-overlays 1 (1+ (buffer-size)) 'face 'todos-search)) |
| 1647 | 3356 | ||
| 1648 | ;;; Editing | 3357 | ;; --------------------------------------------------------------------------- |
| 3358 | ;;; Editing Commands | ||
| 1649 | 3359 | ||
| 3360 | ;; FIXME: autoload? | ||
| 3361 | ;; FIXME: should there also be command todos-delete-file or is it enough to | ||
| 3362 | ;; delete empty file on deleting last category with todos-delete-category? | ||
| 1650 | (defun todos-add-file () | 3363 | (defun todos-add-file () |
| 1651 | "Name and add a new Todos file. | 3364 | "Name and add a new Todos file. |
| 1652 | Interactively, prompt for a category and display it. | 3365 | Interactively, prompt for a category and display it. |
| 1653 | Noninteractively, return the name of the new file." | 3366 | Noninteractively, return the name of the new file." |
| 1654 | (interactive) | 3367 | (interactive) |
| 1655 | (let ((default-file (if todos-default-todos-file | 3368 | (let ((default-file (if todos-default-todos-file |
| 1656 | (file-name-sans-extension | 3369 | (todos-short-file-name todos-default-todos-file))) |
| 1657 | (file-name-nondirectory todos-default-todos-file)))) | 3370 | (prompt (concat "Enter name of new Todos file " |
| 1658 | file prompt shortname) | 3371 | "(TAB or SPC to see current names): ")) |
| 1659 | (while | 3372 | file shortname) |
| 1660 | (and | 3373 | (setq file (todos-read-file-name prompt));)) |
| 1661 | (cond | 3374 | (setq shortname (todos-short-file-name file)) |
| 1662 | ((or (not file) (member file todos-files)) | ||
| 1663 | (setq prompt (concat "Enter name of new Todos file " | ||
| 1664 | "(TAB or SPC to see existing Todos files): "))) | ||
| 1665 | ((string-equal file "") | ||
| 1666 | (setq prompt "Enter a non-empty name: ")) | ||
| 1667 | ((string-match "\\`\\s-+\\'" file) | ||
| 1668 | (setq prompt "Enter a name that is not only white space: "))) | ||
| 1669 | (setq file (todos-read-file-name prompt)))) | ||
| 1670 | (setq shortname (file-name-sans-extension (file-name-nondirectory file))) | ||
| 1671 | (with-current-buffer (get-buffer-create file) | 3375 | (with-current-buffer (get-buffer-create file) |
| 1672 | (erase-buffer) | 3376 | (erase-buffer) |
| 1673 | (write-region (point-min) (point-max) file nil 'nomessage nil t) | 3377 | (write-region (point-min) (point-max) file nil 'nomessage nil t) |
| 1674 | (kill-buffer file)) | 3378 | (kill-buffer file)) |
| 1675 | ;; FIXME: todos-change-default-file yields a Custom mismatch | 3379 | (todos-reevaluate-defcustoms) |
| 1676 | ;; (if (or (not default-file) | ||
| 1677 | ;; (yes-or-no-p (concat (format "Make \"%s\" new default Todos file " | ||
| 1678 | ;; shortname) | ||
| 1679 | ;; (format "[current default is \"%s\"]? " | ||
| 1680 | ;; default-file)))) | ||
| 1681 | ;; (todos-change-default-file file) | ||
| 1682 | ;; (message "\"%s\" remains the default Todos file." default-file)) | ||
| 1683 | (if (called-interactively-p) | 3380 | (if (called-interactively-p) |
| 1684 | (progn | 3381 | (progn |
| 1685 | (setq todos-current-todos-file file) | 3382 | (setq todos-current-todos-file file) |
| 1686 | (todos-show)) | 3383 | (todos-show)) |
| 1687 | file))) | 3384 | file))) |
| 1688 | 3385 | ||
| 1689 | ;; FIXME: omit this and just use defcustom? Says "changed outside of Custom | ||
| 1690 | ;; (mismatch)" | ||
| 1691 | (defun todos-change-default-file (&optional file) | ||
| 1692 | "" | ||
| 1693 | (interactive) | ||
| 1694 | (let ((new-default (or file | ||
| 1695 | (todos-read-file-name "Choose new default Todos file: " | ||
| 1696 | nil t)))) | ||
| 1697 | (customize-save-variable 'todos-default-todos-file new-default) | ||
| 1698 | (message "\"%s\" is new default Todos file." | ||
| 1699 | (file-name-sans-extension (file-name-nondirectory new-default))))) | ||
| 1700 | |||
| 1701 | (defun todos-add-category (&optional cat) | 3386 | (defun todos-add-category (&optional cat) |
| 1702 | "Add a new category to the current Todos file. | 3387 | "Add a new category to the current Todos file. |
| 1703 | Called interactively, prompt for category name, then visit the | 3388 | Called interactively, prompt for category name, then visit the |
| @@ -1716,8 +3401,11 @@ the category name, which is also the return value." | |||
| 1716 | todos-current-todos-file))) | 3401 | todos-current-todos-file))) |
| 1717 | (unless cat (setq cat (read-from-minibuffer "Enter new category name: "))) | 3402 | (unless cat (setq cat (read-from-minibuffer "Enter new category name: "))) |
| 1718 | (with-current-buffer buf | 3403 | (with-current-buffer buf |
| 1719 | (setq cat (todos-validate-category-name cat)) | 3404 | (setq cat (todos-validate-name cat 'category)) |
| 1720 | (setq todos-categories (append todos-categories (list (cons cat counts)))) | 3405 | (setq todos-categories (append todos-categories (list (cons cat counts)))) |
| 3406 | (if todos-categories-full | ||
| 3407 | (setq todos-categories-full (append todos-categories-full | ||
| 3408 | (list (cons cat counts))))) | ||
| 1721 | (widen) | 3409 | (widen) |
| 1722 | (goto-char (point-max)) | 3410 | (goto-char (point-max)) |
| 1723 | (save-excursion ; Save point for todos-category-select. | 3411 | (save-excursion ; Save point for todos-category-select. |
| @@ -1738,7 +3426,7 @@ category there as well." | |||
| 1738 | (interactive) | 3426 | (interactive) |
| 1739 | (let* ((cat (todos-current-category)) | 3427 | (let* ((cat (todos-current-category)) |
| 1740 | (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat)))) | 3428 | (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat)))) |
| 1741 | (setq new (todos-validate-category-name new)) | 3429 | (setq new (todos-validate-name new 'category)) |
| 1742 | (let* ((ofile todos-current-todos-file) | 3430 | (let* ((ofile todos-current-todos-file) |
| 1743 | (archive (concat (file-name-sans-extension ofile) ".toda")) | 3431 | (archive (concat (file-name-sans-extension ofile) ".toda")) |
| 1744 | (buffers (append (list ofile) | 3432 | (buffers (append (list ofile) |
| @@ -1758,6 +3446,7 @@ category there as well." | |||
| 1758 | "\\(" (regexp-quote cat) "\\)\n") | 3446 | "\\(" (regexp-quote cat) "\\)\n") |
| 1759 | nil t) | 3447 | nil t) |
| 1760 | (replace-match new t t nil 1))))))) | 3448 | (replace-match new t t nil 1))))))) |
| 3449 | ;; FIXME: use force-mode-line-update instead? | ||
| 1761 | (setq mode-line-buffer-identification | 3450 | (setq mode-line-buffer-identification |
| 1762 | (funcall todos-mode-line-function new))) | 3451 | (funcall todos-mode-line-function new))) |
| 1763 | (save-excursion (todos-category-select))) | 3452 | (save-excursion (todos-category-select))) |
| @@ -1771,42 +3460,58 @@ i.e. including all existing todo and done items." | |||
| 1771 | (todo (todos-get-count 'todo cat)) | 3460 | (todo (todos-get-count 'todo cat)) |
| 1772 | (done (todos-get-count 'done cat)) | 3461 | (done (todos-get-count 'done cat)) |
| 1773 | (archived (todos-get-count 'archived cat))) | 3462 | (archived (todos-get-count 'archived cat))) |
| 1774 | (if (and (not arg) | 3463 | (when (or (> (length todos-categories) 1) |
| 1775 | (or (> todo 0) (> done 0))) | 3464 | (y-or-n-p (concat "This is the only category in this file; " |
| 1776 | (message "To delete a non-empty category, type C-u D.") | 3465 | "deleting it will also delete the file.\n" |
| 1777 | (when (yes-or-no-p (concat "Permanently remove category \"" cat | 3466 | "Do you want to proceed? "))) |
| 1778 | "\"" (and arg " and all its entries") "? ")) | 3467 | (if (and (not arg) |
| 1779 | ;; FIXME ? optionally delete archived category as well? | 3468 | (or (> todo 0) (> done 0))) |
| 1780 | (when (and archived | 3469 | (message "To delete a non-empty category, type C-u %s." |
| 1781 | (y-or-n-p (concat "This category has archived items; " | 3470 | (key-description |
| 1782 | "the archived category will remain\n" | 3471 | (car (where-is-internal 'todos-delete-category)))) |
| 1783 | "after deleting the todo category. " | 3472 | (when (yes-or-no-p (concat "Permanently remove category \"" cat |
| 1784 | "Do you still want to delete it\n" | 3473 | "\"" (and arg " and all its entries") "? ")) |
| 1785 | "(see 'todos-ignore-archived-categories' " | 3474 | (when (and archived |
| 1786 | "for another option)? "))) | 3475 | (y-or-n-p (concat "This category has archived items; " |
| 1787 | (widen) | 3476 | "the archived category will remain\n" |
| 1788 | (let ((buffer-read-only) | 3477 | "after deleting the todo category. " |
| 1789 | (beg (re-search-backward | 3478 | "Do you still want to delete it\n" |
| 1790 | (concat "^" (regexp-quote (concat todos-category-beg cat)) | 3479 | "(see 'todos-ignore-archived-categories' " |
| 1791 | "\n") nil t)) | 3480 | "for another option)? "))) |
| 1792 | (end (if (re-search-forward | 3481 | (widen) |
| 1793 | (concat "\n\\(" (regexp-quote todos-category-beg) | 3482 | (let ((buffer-read-only) |
| 1794 | ".*\n\\)") nil t) | 3483 | (beg (re-search-backward |
| 1795 | (match-beginning 1) | 3484 | (concat "^" (regexp-quote (concat todos-category-beg cat)) |
| 1796 | (point-max)))) | 3485 | "\n") nil t)) |
| 1797 | (remove-overlays beg end) | 3486 | (end (if (re-search-forward |
| 1798 | (delete-region beg end) | 3487 | (concat "\n\\(" (regexp-quote todos-category-beg) |
| 1799 | (setq todos-categories (delete (assoc cat todos-categories) | 3488 | ".*\n\\)") nil t) |
| 1800 | todos-categories)) | 3489 | (match-beginning 1) |
| 1801 | (todos-update-categories-sexp) | 3490 | (point-max)))) |
| 1802 | (setq todos-category-number | 3491 | (remove-overlays beg end) |
| 1803 | (1+ (mod todos-category-number (length todos-categories)))) | 3492 | (delete-region beg end) |
| 1804 | (todos-category-select) | 3493 | (if (= (length todos-categories) 1) |
| 1805 | (goto-char (point-min)) | 3494 | ;; If deleted category was the only one, delete the file. |
| 1806 | (message "Deleted category %s" cat))))))) | 3495 | (progn |
| 3496 | ;; FIXME: need this? | ||
| 3497 | (setq todos-categories nil) | ||
| 3498 | (todos-reevaluate-defcustoms) | ||
| 3499 | (delete-file todos-current-todos-file) | ||
| 3500 | (kill-buffer) | ||
| 3501 | (message "Deleted empty Todos file %s." | ||
| 3502 | todos-current-todos-file)) | ||
| 3503 | ;; FIXME: what about todos-categories-full ? | ||
| 3504 | (setq todos-categories (delete (assoc cat todos-categories) | ||
| 3505 | todos-categories)) | ||
| 3506 | (todos-update-categories-sexp) | ||
| 3507 | (setq todos-category-number | ||
| 3508 | (1+ (mod todos-category-number (length todos-categories)))) | ||
| 3509 | (todos-category-select) | ||
| 3510 | (goto-char (point-min)) | ||
| 3511 | (message "Deleted category %s." cat))))))))) | ||
| 1807 | 3512 | ||
| 1808 | (defun todos-raise-category (&optional lower) | 3513 | (defun todos-raise-category (&optional lower) |
| 1809 | "Raise priority of category point is on in Categories buffer. | 3514 | "Raise priority of category point is on in Todos Categories buffer. |
| 1810 | With non-nil argument LOWER, lower the category's priority." | 3515 | With non-nil argument LOWER, lower the category's priority." |
| 1811 | (interactive) | 3516 | (interactive) |
| 1812 | (let (num) | 3517 | (let (num) |
| @@ -1846,7 +3551,7 @@ With non-nil argument LOWER, lower the category's priority." | |||
| 1846 | (forward-char col))))) | 3551 | (forward-char col))))) |
| 1847 | 3552 | ||
| 1848 | (defun todos-lower-category () | 3553 | (defun todos-lower-category () |
| 1849 | "Lower priority of category point is on in Categories buffer." | 3554 | "Lower priority of category point is on in Todos Categories buffer." |
| 1850 | (interactive) | 3555 | (interactive) |
| 1851 | (todos-raise-category t)) | 3556 | (todos-raise-category t)) |
| 1852 | 3557 | ||
| @@ -1892,8 +3597,7 @@ archive of the file moved to, creating it if it does not exist." | |||
| 1892 | (if (member buf (funcall todos-files-function t)) | 3597 | (if (member buf (funcall todos-files-function t)) |
| 1893 | (concat (file-name-sans-extension nfile) ".toda") | 3598 | (concat (file-name-sans-extension nfile) ".toda") |
| 1894 | nfile)) | 3599 | nfile)) |
| 1895 | (let* ((nfile-short (file-name-sans-extension | 3600 | (let* ((nfile-short (todos-short-file-name nfile)) |
| 1896 | (file-name-nondirectory nfile))) | ||
| 1897 | (prompt (concat | 3601 | (prompt (concat |
| 1898 | (format "Todos file \"%s\" already has " | 3602 | (format "Todos file \"%s\" already has " |
| 1899 | nfile-short) | 3603 | nfile-short) |
| @@ -1909,7 +3613,7 @@ archive of the file moved to, creating it if it does not exist." | |||
| 1909 | (unless (member (file-truename (buffer-file-name)) | 3613 | (unless (member (file-truename (buffer-file-name)) |
| 1910 | (funcall todos-files-function t)) | 3614 | (funcall todos-files-function t)) |
| 1911 | (setq new (read-from-minibuffer prompt)) | 3615 | (setq new (read-from-minibuffer prompt)) |
| 1912 | (setq new (todos-validate-category-name new)))) | 3616 | (setq new (todos-validate-name new 'category)))) |
| 1913 | ;; Replace old with new name in Todos and archive files. | 3617 | ;; Replace old with new name in Todos and archive files. |
| 1914 | (when new | 3618 | (when new |
| 1915 | (goto-char (point-max)) | 3619 | (goto-char (point-max)) |
| @@ -1941,7 +3645,9 @@ archive of the file moved to, creating it if it does not exist." | |||
| 1941 | ;; Skip confirming killing the archive buffer. | 3645 | ;; Skip confirming killing the archive buffer. |
| 1942 | (set-buffer-modified-p nil) | 3646 | (set-buffer-modified-p nil) |
| 1943 | (delete-file todos-current-todos-file) | 3647 | (delete-file todos-current-todos-file) |
| 1944 | (kill-buffer)) | 3648 | (kill-buffer) |
| 3649 | (when (member todos-current-todos-file todos-files) | ||
| 3650 | (todos-reevaluate-defcustoms))) | ||
| 1945 | (setq todos-categories (delete (assoc cat todos-categories) | 3651 | (setq todos-categories (delete (assoc cat todos-categories) |
| 1946 | todos-categories)) | 3652 | todos-categories)) |
| 1947 | (todos-update-categories-sexp) | 3653 | (todos-update-categories-sexp) |
| @@ -1952,10 +3658,11 @@ archive of the file moved to, creating it if it does not exist." | |||
| 1952 | (todos-category-select)))) | 3658 | (todos-category-select)))) |
| 1953 | 3659 | ||
| 1954 | (defun todos-merge-category () | 3660 | (defun todos-merge-category () |
| 1955 | "Merge this category with chosen category in this file. The | 3661 | "Merge current category into another category in this file. |
| 1956 | current category's todo and done items are appended to the chosen | 3662 | The current category's todo and done items are appended to the |
| 1957 | category's todo and done items, respectively, which becomes the | 3663 | chosen category's todo and done items, respectively, which |
| 1958 | current category, and the category moved from is deleted." | 3664 | becomes the current category, and the category moved from is |
| 3665 | deleted." | ||
| 1959 | (interactive) | 3666 | (interactive) |
| 1960 | (let ((buffer-read-only nil) | 3667 | (let ((buffer-read-only nil) |
| 1961 | (cat (todos-current-category)) | 3668 | (cat (todos-current-category)) |
| @@ -2022,15 +3729,13 @@ current category, and the category moved from is deleted." | |||
| 2022 | (widen) | 3729 | (widen) |
| 2023 | )) | 3730 | )) |
| 2024 | 3731 | ||
| 2025 | ;; FIXME: make insertion options customizable per category | 3732 | ;; FIXME: make insertion options customizable per category? |
| 2026 | ;;;###autoload | 3733 | ;;;###autoload |
| 2027 | ;; (defun todos-insert-item (&optional arg use-point date-type time | ||
| 2028 | ;; diary nonmarking) | ||
| 2029 | (defun todos-insert-item (&optional arg diary nonmarking date-type time | 3734 | (defun todos-insert-item (&optional arg diary nonmarking date-type time |
| 2030 | region-or-here) | 3735 | region-or-here) |
| 2031 | "Add a new Todo item to a category. | 3736 | "Add a new Todo item to a category. |
| 2032 | See the note at the end of this document string about key | 3737 | \(See the note at the end of this document string about key |
| 2033 | bindings and convenience commands derived from this command. | 3738 | bindings and convenience commands derived from this command.) |
| 2034 | 3739 | ||
| 2035 | With no (or nil) prefix argument ARG, add the item to the current | 3740 | With no (or nil) prefix argument ARG, add the item to the current |
| 2036 | category; with one prefix argument (C-u), prompt for a category | 3741 | category; with one prefix argument (C-u), prompt for a category |
| @@ -2070,17 +3775,21 @@ mandatory date header string and how it is added: | |||
| 2070 | header contains the current date (in the format set by | 3775 | header contains the current date (in the format set by |
| 2071 | `calendar-date-display-form'). | 3776 | `calendar-date-display-form'). |
| 2072 | 3777 | ||
| 2073 | With non-nil argument TIME prompt for a time string; this must | 3778 | With non-nil argument TIME prompt for a time string, which must |
| 2074 | either be empty or else match `diary-time-regexp'. If TIME is | 3779 | match `diary-time-regexp'. Typing `<return>' at the prompt |
| 2075 | nil, add or omit the current time according to value of the user | 3780 | returns the current time, if the user option |
| 2076 | option `todos-always-add-time-string'. | 3781 | `todos-always-add-time-string' is non-nil, otherwise the empty |
| 3782 | string (i.e., no time string). If TIME is absent or nil, add or | ||
| 3783 | omit the current time string according as | ||
| 3784 | `todos-always-add-time-string' is non-nil or nil, respectively. | ||
| 2077 | 3785 | ||
| 2078 | The argument REGION-OR-HERE determines the source and location of | 3786 | The argument REGION-OR-HERE determines the source and location of |
| 2079 | the new item: | 3787 | the new item: |
| 2080 | - If the REGION-OR-HERE is the symbol `here', prompt for the text | 3788 | - If the REGION-OR-HERE is the symbol `here', prompt for the text |
| 2081 | of the new item and insert it directly above the todo item at | 3789 | of the new item and insert it directly above the todo item at |
| 2082 | point, or if point is on the empty line below the last todo | 3790 | point (hence lowering the priority of the remaining items), or |
| 2083 | item, insert the new item there. An error is signalled if | 3791 | if point is on the empty line below the last todo item, insert |
| 3792 | the new item there. An error is signalled if | ||
| 2084 | `todos-insert-item' is invoked with `here' outside of the | 3793 | `todos-insert-item' is invoked with `here' outside of the |
| 2085 | current category. | 3794 | current category. |
| 2086 | - If REGION-OR-HERE is the symbol `region', use the region of the | 3795 | - If REGION-OR-HERE is the symbol `region', use the region of the |
| @@ -2099,11 +3808,18 @@ the new item: | |||
| 2099 | To facilitate using these arguments when inserting a new todo | 3808 | To facilitate using these arguments when inserting a new todo |
| 2100 | item, convenience commands have been defined for all admissible | 3809 | item, convenience commands have been defined for all admissible |
| 2101 | combinations (96 in all!) together with mnenomic key bindings | 3810 | combinations (96 in all!) together with mnenomic key bindings |
| 2102 | based on on the name of the arguments and their order: _h_ere or | 3811 | based on on the name of the arguments and their order in the |
| 2103 | _r_egion - _c_alendar or _d_ate or day_n_ame - _t_ime - diar_y_ - | 3812 | command's argument list: diar_y_ - nonmar_k_ing - _c_alendar or |
| 2104 | nonmar_k_ing. An alternative interface for customizing key | 3813 | _d_ate or day_n_ame - _t_ime - _r_egion or _h_ere. These key |
| 2105 | binding is also provided with the function | 3814 | combinations are appended to the basic insertion key (i) and keys |
| 2106 | `todos-insertion-bindings'." ;FIXME | 3815 | that allow a following key must be doubled when used finally. |
| 3816 | For example, `iyh' will insert a new item with today's date, | ||
| 3817 | marked according to the DIARY argument described above, and with | ||
| 3818 | priority according to the HERE argument; while `iyy' does the | ||
| 3819 | same except the priority is not given by HERE but by prompting." | ||
| 3820 | ;; An alternative interface for customizing key | ||
| 3821 | ;; binding is also provided with the function | ||
| 3822 | ;; `todos-insertion-bindings'." ;FIXME | ||
| 2107 | (interactive "P") | 3823 | (interactive "P") |
| 2108 | (let ((region (eq region-or-here 'region)) | 3824 | (let ((region (eq region-or-here 'region)) |
| 2109 | (here (eq region-or-here 'here))) | 3825 | (here (eq region-or-here 'here))) |
| @@ -2127,15 +3843,8 @@ binding is also provided with the function | |||
| 2127 | (todos-read-dayname)) | 3843 | (todos-read-dayname)) |
| 2128 | ((eq date-type 'calendar) | 3844 | ((eq date-type 'calendar) |
| 2129 | (setq todos-date-from-calendar t) | 3845 | (setq todos-date-from-calendar t) |
| 2130 | (let (calendar-view-diary-initially-flag) | 3846 | (todos-set-date-from-calendar)) |
| 2131 | (calendar)) | ||
| 2132 | (with-current-buffer "*Calendar*" | ||
| 2133 | (todos-set-date-from-calendar)) | ||
| 2134 | todos-date-from-calendar) | ||
| 2135 | (t (calendar-date-string (calendar-current-date) t t)))) | 3847 | (t (calendar-date-string (calendar-current-date) t t)))) |
| 2136 | ;; FIXME: should TIME override `todos-always-add-time-string'? But | ||
| 2137 | ;; then add another option to use current time or prompt for time | ||
| 2138 | ;; string? | ||
| 2139 | (time-string (or (and time (todos-read-time)) | 3848 | (time-string (or (and time (todos-read-time)) |
| 2140 | (and todos-always-add-time-string | 3849 | (and todos-always-add-time-string |
| 2141 | (substring (current-time-string) 11 16))))) | 3850 | (substring (current-time-string) 11 16))))) |
| @@ -2159,7 +3868,8 @@ binding is also provided with the function | |||
| 2159 | todos-nondiary-start | 3868 | todos-nondiary-start |
| 2160 | (when (and nonmarking (not todos-diary-nonmarking)) | 3869 | (when (and nonmarking (not todos-diary-nonmarking)) |
| 2161 | diary-nonmarking-symbol)) | 3870 | diary-nonmarking-symbol)) |
| 2162 | date-string (when time-string | 3871 | date-string (unless (and time-string |
| 3872 | (string= time-string "")) | ||
| 2163 | (concat " " time-string)) | 3873 | (concat " " time-string)) |
| 2164 | (when (not (and diary (not todos-include-in-diary))) | 3874 | (when (not (and diary (not todos-include-in-diary))) |
| 2165 | todos-nondiary-end) | 3875 | todos-nondiary-end) |
| @@ -2185,6 +3895,25 @@ binding is also provided with the function | |||
| 2185 | (if (or diary todos-include-in-diary) (todos-set-count 'diary 1)) | 3895 | (if (or diary todos-include-in-diary) (todos-set-count 'diary 1)) |
| 2186 | (todos-update-categories-sexp))))) | 3896 | (todos-update-categories-sexp))))) |
| 2187 | 3897 | ||
| 3898 | (defvar todos-date-from-calendar nil | ||
| 3899 | "Helper variable for setting item date from the Emacs Calendar.") | ||
| 3900 | |||
| 3901 | (defun todos-set-date-from-calendar () | ||
| 3902 | "Return string of date chosen from Calendar." | ||
| 3903 | (when todos-date-from-calendar | ||
| 3904 | (let (calendar-view-diary-initially-flag) | ||
| 3905 | (calendar)) | ||
| 3906 | ;; *Calendar* is now current buffer. | ||
| 3907 | (local-set-key (kbd "RET") 'exit-recursive-edit) | ||
| 3908 | (message "Put cursor on a date and type <return> to set it.") | ||
| 3909 | ;; FIXME: is there a better way than recursive-edit? | ||
| 3910 | ;; FIXME: use unwind-protect? Check recursive-depth? | ||
| 3911 | (recursive-edit) | ||
| 3912 | (setq todos-date-from-calendar | ||
| 3913 | (calendar-date-string (calendar-cursor-to-date t) t t)) | ||
| 3914 | (calendar-exit) | ||
| 3915 | todos-date-from-calendar)) | ||
| 3916 | |||
| 2188 | ;; FIXME: autoload when key-binding is defined in calendar.el | 3917 | ;; FIXME: autoload when key-binding is defined in calendar.el |
| 2189 | (defun todos-insert-item-from-calendar () | 3918 | (defun todos-insert-item-from-calendar () |
| 2190 | "" | 3919 | "" |
| @@ -2201,19 +3930,6 @@ binding is also provided with the function | |||
| 2201 | ;; (lambda () | 3930 | ;; (lambda () |
| 2202 | (define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);)) | 3931 | (define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);)) |
| 2203 | 3932 | ||
| 2204 | (defvar todos-date-from-calendar nil) | ||
| 2205 | (defun todos-set-date-from-calendar () | ||
| 2206 | "" | ||
| 2207 | (when todos-date-from-calendar | ||
| 2208 | (local-set-key (kbd "RET") 'exit-recursive-edit) | ||
| 2209 | (message "Put cursor on a date and type <return> to set it.") | ||
| 2210 | ;; FIXME: is there a better way than recursive-edit? | ||
| 2211 | ;; FIXME: use unwind-protect? Check recursive-depth? | ||
| 2212 | (recursive-edit) | ||
| 2213 | (setq todos-date-from-calendar | ||
| 2214 | (calendar-date-string (calendar-cursor-to-date t) t t)) | ||
| 2215 | (calendar-exit))) | ||
| 2216 | |||
| 2217 | (defun todos-delete-item () | 3933 | (defun todos-delete-item () |
| 2218 | "Delete at least one item in this category. | 3934 | "Delete at least one item in this category. |
| 2219 | 3935 | ||
| @@ -2237,7 +3953,7 @@ the item at point." | |||
| 2237 | (and marked (goto-char (point-min))) | 3953 | (and marked (goto-char (point-min))) |
| 2238 | (catch 'done | 3954 | (catch 'done |
| 2239 | (while (not (eobp)) | 3955 | (while (not (eobp)) |
| 2240 | (if (or (and marked (todos-item-marked-p)) item) | 3956 | (if (or (and marked (todos-marked-item-p)) item) |
| 2241 | (progn | 3957 | (progn |
| 2242 | (if (todos-done-item-p) | 3958 | (if (todos-done-item-p) |
| 2243 | (todos-set-count 'done -1) | 3959 | (todos-set-count 'done -1) |
| @@ -2261,7 +3977,9 @@ the item at point." | |||
| 2261 | (if ov (delete-overlay ov)))) | 3977 | (if ov (delete-overlay ov)))) |
| 2262 | 3978 | ||
| 2263 | (defun todos-edit-item () | 3979 | (defun todos-edit-item () |
| 2264 | "Edit current Todo item in the minibuffer." | 3980 | "Edit the Todo item at point. |
| 3981 | If the item consists of only one logical line, edit it in the | ||
| 3982 | minibuffer; otherwise, edit it in Todos Edit mode." | ||
| 2265 | (interactive) | 3983 | (interactive) |
| 2266 | (when (todos-item-string) | 3984 | (when (todos-item-string) |
| 2267 | (let* ((buffer-read-only) | 3985 | (let* ((buffer-read-only) |
| @@ -2277,7 +3995,7 @@ the item at point." | |||
| 2277 | (multiline (> (length (split-string item "\n")) 1)) | 3995 | (multiline (> (length (split-string item "\n")) 1)) |
| 2278 | (opoint (point))) | 3996 | (opoint (point))) |
| 2279 | (if multiline | 3997 | (if multiline |
| 2280 | (todos-edit-multiline) | 3998 | (todos-edit-multiline t) |
| 2281 | (let ((new (read-string "Edit: " (cons item item-beg)))) | 3999 | (let ((new (read-string "Edit: " (cons item item-beg)))) |
| 2282 | (while (not (string-match | 4000 | (while (not (string-match |
| 2283 | (concat todos-date-string-start todos-date-pattern) new)) | 4001 | (concat todos-date-string-start todos-date-pattern) new)) |
| @@ -2293,29 +4011,50 @@ the item at point." | |||
| 2293 | (todos-remove-item) | 4011 | (todos-remove-item) |
| 2294 | (todos-insert-with-overlays new) | 4012 | (todos-insert-with-overlays new) |
| 2295 | (move-to-column item-beg)))))) | 4013 | (move-to-column item-beg)))))) |
| 2296 | 4014 | ||
| 2297 | ;; FIXME: run todos-check-format on exiting buffer (or check for date string | 4015 | (defun todos-edit-multiline-item () |
| 2298 | ;; and indentation) | ||
| 2299 | (defun todos-edit-multiline () | ||
| 2300 | "Edit current Todo item in Todos Edit mode. | 4016 | "Edit current Todo item in Todos Edit mode. |
| 2301 | Use of newlines invokes `todos-indent' to insure compliance with | 4017 | Use of newlines invokes `todos-indent' to insure compliance with |
| 2302 | the format of Diary entries." | 4018 | the format of Diary entries." |
| 2303 | (interactive) | 4019 | (interactive) |
| 4020 | (todos-edit-multiline t)) | ||
| 4021 | |||
| 4022 | (defun todos-edit-multiline (&optional item) | ||
| 4023 | "" | ||
| 4024 | (interactive) | ||
| 4025 | ;; FIXME: should there be only one live Todos Edit buffer? | ||
| 4026 | ;; (let ((buffer-name todos-edit-buffer)) | ||
| 2304 | (let ((buffer-name (generate-new-buffer-name todos-edit-buffer))) | 4027 | (let ((buffer-name (generate-new-buffer-name todos-edit-buffer))) |
| 2305 | (set-window-buffer | 4028 | (set-window-buffer |
| 2306 | (selected-window) | 4029 | (selected-window) |
| 2307 | (set-buffer (make-indirect-buffer | 4030 | (set-buffer (make-indirect-buffer |
| 2308 | (file-name-nondirectory todos-current-todos-file) | 4031 | (file-name-nondirectory todos-current-todos-file) |
| 2309 | buffer-name))) | 4032 | buffer-name))) |
| 2310 | (narrow-to-region (todos-item-start) (todos-item-end)) | 4033 | (if item |
| 4034 | (narrow-to-region (todos-item-start) (todos-item-end)) | ||
| 4035 | (widen)) | ||
| 2311 | (todos-edit-mode) | 4036 | (todos-edit-mode) |
| 2312 | (message "Type %s to return to Todos mode." | 4037 | ;; (message (concat "Type %s to check file format validity and " |
| 2313 | (key-description (car (where-is-internal 'todos-edit-quit)))))) | 4038 | ;; "return to Todos mode.\n") |
| 4039 | ;; (key-description (car (where-is-internal 'todos-edit-quit)))) | ||
| 4040 | (message "%s" (substitute-command-keys | ||
| 4041 | (concat "Type \\[todos-edit-quit] to check file format " | ||
| 4042 | "validity and return to Todos mode.\n"))))) | ||
| 2314 | 4043 | ||
| 2315 | (defun todos-edit-quit () | 4044 | (defun todos-edit-quit () |
| 2316 | "Return from Todos Edit mode to Todos mode." | 4045 | "Return from Todos Edit mode to Todos mode. |
| 4046 | |||
| 4047 | If the whole file was in Todos Edit mode, check before returning | ||
| 4048 | whether the file is still a valid Todos file and if so, also | ||
| 4049 | recalculate the Todos categories sexp, in case changes were made | ||
| 4050 | in the number or names of categories." | ||
| 2317 | (interactive) | 4051 | (interactive) |
| 4052 | ;; FIXME: worth doing this only if file was actually changed? | ||
| 4053 | (when (eq (buffer-size) (- (point-max) (point-min))) | ||
| 4054 | (when (todos-check-format) | ||
| 4055 | (todos-make-categories-list t))) | ||
| 2318 | (kill-buffer) | 4056 | (kill-buffer) |
| 4057 | ;; In case next buffer is not the one holding todos-current-todos-file. | ||
| 2319 | (todos-show)) | 4058 | (todos-show)) |
| 2320 | 4059 | ||
| 2321 | (defun todos-edit-item-header (&optional what) | 4060 | (defun todos-edit-item-header (&optional what) |
| @@ -2323,21 +4062,23 @@ the format of Diary entries." | |||
| 2323 | 4062 | ||
| 2324 | Interactively, ask whether to edit year, month and day or day of | 4063 | Interactively, ask whether to edit year, month and day or day of |
| 2325 | the week, as well as time. If there are marked items, apply the | 4064 | the week, as well as time. If there are marked items, apply the |
| 2326 | changes to all of these; otherwise, edit just the item at point. | 4065 | changes to all of these; otherwise, edit just the item at point. |
| 2327 | 4066 | ||
| 2328 | Non-interactively, argument WHAT specifies whether to edit only | 4067 | Non-interactively, argument WHAT specifies whether to set the |
| 2329 | the date or only the time, or to set the date to today." | 4068 | date from the Calendar or to today, or whether to edit only the |
| 4069 | date or day, or only the time." | ||
| 2330 | (interactive) | 4070 | (interactive) |
| 2331 | (let* ((cat (todos-current-category)) | 4071 | (let* ((cat (todos-current-category)) |
| 2332 | (marked (assoc cat todos-categories-with-marks)) | 4072 | (marked (assoc cat todos-categories-with-marks)) |
| 2333 | (first t) | 4073 | (first t) ; Match only first of marked items. |
| 4074 | (todos-date-from-calendar t) | ||
| 2334 | ndate ntime nheader) | 4075 | ndate ntime nheader) |
| 2335 | (save-excursion | 4076 | (save-excursion |
| 2336 | (or (and marked (goto-char (point-min))) (todos-item-start)) | 4077 | (or (and marked (goto-char (point-min))) (todos-item-start)) |
| 2337 | (catch 'stop | 4078 | (catch 'stop |
| 2338 | (while (not (eobp)) | 4079 | (while (not (eobp)) |
| 2339 | (and marked | 4080 | (and marked |
| 2340 | (while (not (todos-item-marked-p)) | 4081 | (while (not (todos-marked-item-p)) |
| 2341 | (todos-forward-item) | 4082 | (todos-forward-item) |
| 2342 | (and (eobp) (throw 'stop nil)))) | 4083 | (and (eobp) (throw 'stop nil)))) |
| 2343 | (re-search-forward (concat todos-date-string-start "\\(?1:" | 4084 | (re-search-forward (concat todos-date-string-start "\\(?1:" |
| @@ -2347,38 +4088,51 @@ the date or only the time, or to set the date to today." | |||
| 2347 | (let* ((odate (match-string-no-properties 1)) | 4088 | (let* ((odate (match-string-no-properties 1)) |
| 2348 | (otime (match-string-no-properties 2)) | 4089 | (otime (match-string-no-properties 2)) |
| 2349 | (buffer-read-only)) | 4090 | (buffer-read-only)) |
| 2350 | (if (eq what 'today) | 4091 | (cond ((eq what 'today) |
| 2351 | (progn | 4092 | (progn |
| 2352 | (setq ndate (calendar-date-string (calendar-current-date) t t)) | 4093 | (setq ndate (calendar-date-string |
| 2353 | (replace-match ndate nil nil nil 1)) | 4094 | (calendar-current-date) t t)) |
| 2354 | (unless (eq what 'timeonly) | 4095 | (replace-match ndate nil nil nil 1))) |
| 2355 | (when first | 4096 | ((eq what 'calendar) |
| 2356 | (setq ndate (if (save-match-data (string-match "[0-9]+" odate)) | 4097 | (setq ndate (save-match-data (todos-set-date-from-calendar))) |
| 2357 | (if (y-or-n-p "Change date? ") | 4098 | (replace-match ndate nil nil nil 1)) |
| 2358 | (todos-read-date) | 4099 | (t |
| 2359 | (todos-read-dayname)) | 4100 | (unless (eq what 'timeonly) |
| 2360 | (if (y-or-n-p "Change day? ") | 4101 | (when first |
| 2361 | (todos-read-dayname) | 4102 | (setq ndate (if (save-match-data |
| 2362 | (todos-read-date))))) | 4103 | (string-match "[0-9]+" odate)) |
| 2363 | (replace-match ndate nil nil nil 1)) | 4104 | (if (y-or-n-p "Change date? ") |
| 2364 | (unless (eq what 'dateonly) | 4105 | (todos-read-date) |
| 2365 | (when first | 4106 | (todos-read-dayname)) |
| 2366 | (setq ntime (save-match-data (todos-read-time))) | 4107 | (if (y-or-n-p "Change day? ") |
| 2367 | (when (< 0 (length ntime)) (setq ntime (concat " " ntime)))) | 4108 | (todos-read-dayname) |
| 2368 | (if otime | 4109 | (todos-read-date))))) |
| 2369 | (replace-match ntime nil nil nil 2) | 4110 | (replace-match ndate nil nil nil 1)) |
| 2370 | (goto-char (match-end 1)) | 4111 | (unless (eq what 'dateonly) |
| 2371 | (insert ntime)))) | 4112 | (when first |
| 4113 | (setq ntime (save-match-data (todos-read-time))) | ||
| 4114 | (when (< 0 (length ntime)) | ||
| 4115 | (setq ntime (concat " " ntime)))) | ||
| 4116 | (if otime | ||
| 4117 | (replace-match ntime nil nil nil 2) | ||
| 4118 | (goto-char (match-end 1)) | ||
| 4119 | (insert ntime))))) | ||
| 4120 | (setq todos-date-from-calendar nil) | ||
| 2372 | (setq first nil)) | 4121 | (setq first nil)) |
| 2373 | (if marked | 4122 | (if marked |
| 2374 | (todos-forward-item) | 4123 | (todos-forward-item) |
| 2375 | (goto-char (point-max)))))))) | 4124 | (goto-char (point-max)))))))) |
| 2376 | 4125 | ||
| 2377 | (defun todos-edit-item-date () | 4126 | (defun todos-edit-item-date () |
| 2378 | "Prompt For and apply changes to current item's date." | 4127 | "Prompt for and apply changes to current item's date." |
| 2379 | (interactive) | 4128 | (interactive) |
| 2380 | (todos-edit-item-header 'dateonly)) | 4129 | (todos-edit-item-header 'dateonly)) |
| 2381 | 4130 | ||
| 4131 | (defun todos-edit-item-date-from-calendar () | ||
| 4132 | "Prompt for changes to current item's date and apply from Calendar." | ||
| 4133 | (interactive) | ||
| 4134 | (todos-edit-item-header 'calendar)) | ||
| 4135 | |||
| 2382 | (defun todos-edit-item-date-is-today () | 4136 | (defun todos-edit-item-date-is-today () |
| 2383 | "Set item date to today's date." | 4137 | "Set item date to today's date." |
| 2384 | (interactive) | 4138 | (interactive) |
| @@ -2389,12 +4143,134 @@ the date or only the time, or to set the date to today." | |||
| 2389 | (interactive) | 4143 | (interactive) |
| 2390 | (todos-edit-item-header 'timeonly)) | 4144 | (todos-edit-item-header 'timeonly)) |
| 2391 | 4145 | ||
| 4146 | (defun todos-edit-item-diary-inclusion () | ||
| 4147 | "Change diary status of one or more todo items in this category. | ||
| 4148 | That is, insert `todos-nondiary-marker' if the candidate items | ||
| 4149 | lack this marking; otherwise, remove it. | ||
| 4150 | |||
| 4151 | If there are marked todo items, change the diary status of all | ||
| 4152 | and only these, otherwise change the diary status of the item at | ||
| 4153 | point." | ||
| 4154 | (interactive) | ||
| 4155 | (let ((buffer-read-only) | ||
| 4156 | (marked (assoc (todos-current-category) | ||
| 4157 | todos-categories-with-marks))) | ||
| 4158 | (catch 'stop | ||
| 4159 | (save-excursion | ||
| 4160 | (when marked (goto-char (point-min))) | ||
| 4161 | (while (not (eobp)) | ||
| 4162 | (if (todos-done-item-p) | ||
| 4163 | (throw 'stop (message "Done items cannot be edited")) | ||
| 4164 | (unless (and marked (not (todos-marked-item-p))) | ||
| 4165 | (let* ((beg (todos-item-start)) | ||
| 4166 | (lim (save-excursion (todos-item-end))) | ||
| 4167 | (end (save-excursion | ||
| 4168 | (or (todos-time-string-matcher lim) | ||
| 4169 | (todos-date-string-matcher lim))))) | ||
| 4170 | (if (looking-at (regexp-quote todos-nondiary-start)) | ||
| 4171 | (progn | ||
| 4172 | (replace-match "") | ||
| 4173 | (search-forward todos-nondiary-end (1+ end) t) | ||
| 4174 | (replace-match "") | ||
| 4175 | (todos-set-count 'diary 1)) | ||
| 4176 | (when end | ||
| 4177 | (insert todos-nondiary-start) | ||
| 4178 | (goto-char (1+ end)) | ||
| 4179 | (insert todos-nondiary-end) | ||
| 4180 | (todos-set-count 'diary -1))))) | ||
| 4181 | (unless marked (throw 'stop nil)) | ||
| 4182 | (todos-forward-item))))) | ||
| 4183 | (todos-update-categories-sexp))) | ||
| 4184 | |||
| 4185 | (defun todos-edit-category-diary-inclusion (arg) | ||
| 4186 | "Make all items in this category diary items. | ||
| 4187 | With prefix ARG, make all items in this category non-diary | ||
| 4188 | items." | ||
| 4189 | (interactive "P") | ||
| 4190 | (save-excursion | ||
| 4191 | (goto-char (point-min)) | ||
| 4192 | (let ((todo-count (todos-get-count 'todo)) | ||
| 4193 | (diary-count (todos-get-count 'diary)) | ||
| 4194 | (buffer-read-only)) | ||
| 4195 | (catch 'stop | ||
| 4196 | (while (not (eobp)) | ||
| 4197 | (if (todos-done-item-p) ; We've gone too far. | ||
| 4198 | (throw 'stop nil) | ||
| 4199 | (let* ((beg (todos-item-start)) | ||
| 4200 | (lim (save-excursion (todos-item-end))) | ||
| 4201 | (end (save-excursion | ||
| 4202 | (or (todos-time-string-matcher lim) | ||
| 4203 | (todos-date-string-matcher lim))))) | ||
| 4204 | (if arg | ||
| 4205 | (unless (looking-at (regexp-quote todos-nondiary-start)) | ||
| 4206 | (insert todos-nondiary-start) | ||
| 4207 | (goto-char (1+ end)) | ||
| 4208 | (insert todos-nondiary-end)) | ||
| 4209 | (when (looking-at (regexp-quote todos-nondiary-start)) | ||
| 4210 | (replace-match "") | ||
| 4211 | (search-forward todos-nondiary-end (1+ end) t) | ||
| 4212 | (replace-match ""))))) | ||
| 4213 | (todos-forward-item)) | ||
| 4214 | (unless (if arg (zerop diary-count) (= diary-count todo-count)) | ||
| 4215 | (todos-set-count 'diary (if arg | ||
| 4216 | (- diary-count) | ||
| 4217 | (- todo-count diary-count)))) | ||
| 4218 | (todos-update-categories-sexp))))) | ||
| 4219 | |||
| 4220 | (defun todos-edit-item-diary-nonmarking () | ||
| 4221 | "Change non-marking of one or more diary items in this category. | ||
| 4222 | That is, insert `diary-nonmarking-symbol' if the candidate items | ||
| 4223 | lack this marking; otherwise, remove it. | ||
| 4224 | |||
| 4225 | If there are marked todo items, change the non-marking status of | ||
| 4226 | all and only these, otherwise change the non-marking status of | ||
| 4227 | the item at point." | ||
| 4228 | (interactive) | ||
| 4229 | (let ((buffer-read-only) | ||
| 4230 | (marked (assoc (todos-current-category) | ||
| 4231 | todos-categories-with-marks))) | ||
| 4232 | (catch 'stop | ||
| 4233 | (save-excursion | ||
| 4234 | (when marked (goto-char (point-min))) | ||
| 4235 | (while (not (eobp)) | ||
| 4236 | (if (todos-done-item-p) | ||
| 4237 | (throw 'stop (message "Done items cannot be edited")) | ||
| 4238 | (unless (and marked (not (todos-marked-item-p))) | ||
| 4239 | (todos-item-start) | ||
| 4240 | (unless (looking-at (regexp-quote todos-nondiary-start)) | ||
| 4241 | (if (looking-at (regexp-quote diary-nonmarking-symbol)) | ||
| 4242 | (replace-match "") | ||
| 4243 | (insert diary-nonmarking-symbol)))) | ||
| 4244 | (unless marked (throw 'stop nil)) | ||
| 4245 | (todos-forward-item))))))) | ||
| 4246 | |||
| 4247 | (defun todos-edit-category-diary-nonmarking (arg) | ||
| 4248 | "Add `diary-nonmarking-symbol' to all diary items in this category. | ||
| 4249 | With prefix ARG, remove `diary-nonmarking-symbol' from all diary | ||
| 4250 | items in this category." | ||
| 4251 | (interactive "P") | ||
| 4252 | (save-excursion | ||
| 4253 | (goto-char (point-min)) | ||
| 4254 | (let (buffer-read-only) | ||
| 4255 | (catch 'stop | ||
| 4256 | (while (not (eobp)) | ||
| 4257 | (if (todos-done-item-p) ; We've gone too far. | ||
| 4258 | (throw 'stop nil) | ||
| 4259 | (unless (looking-at (regexp-quote todos-nondiary-start)) | ||
| 4260 | (if arg | ||
| 4261 | (when (looking-at (regexp-quote diary-nonmarking-symbol)) | ||
| 4262 | (replace-match "")) | ||
| 4263 | (unless (looking-at (regexp-quote diary-nonmarking-symbol)) | ||
| 4264 | (insert diary-nonmarking-symbol)))) | ||
| 4265 | (todos-forward-item))))))) | ||
| 4266 | |||
| 2392 | (defun todos-raise-item-priority (&optional lower) | 4267 | (defun todos-raise-item-priority (&optional lower) |
| 2393 | "Raise priority of current item by moving it up by one item. | 4268 | "Raise priority of current item by moving it up by one item. |
| 2394 | With non-nil argument LOWER lower item's priority." | 4269 | With non-nil argument LOWER lower item's priority." |
| 2395 | (interactive) | 4270 | (interactive) |
| 2396 | (unless (or (todos-done-item-p) | 4271 | (unless (or (todos-done-item-p) |
| 2397 | (looking-at "^$")) ; We're between todo and done items. | 4272 | ;; Point is between todo and done items. |
| 4273 | (looking-at "^$")) | ||
| 2398 | (let (buffer-read-only) | 4274 | (let (buffer-read-only) |
| 2399 | (if (or (and lower | 4275 | (if (or (and lower |
| 2400 | (save-excursion | 4276 | (save-excursion |
| @@ -2402,10 +4278,10 @@ With non-nil argument LOWER lower item's priority." | |||
| 2402 | (todos-forward-item) | 4278 | (todos-forward-item) |
| 2403 | (and (looking-at todos-item-start) | 4279 | (and (looking-at todos-item-start) |
| 2404 | (not (todos-done-item-p))))) | 4280 | (not (todos-done-item-p))))) |
| 2405 | ;; Can't raise or lower only todo item. | 4281 | ;; Can't raise or lower todo item when it's the only one. |
| 2406 | (> (count-lines (point-min) (point)) 0)) | 4282 | (> (count-lines (point-min) (point)) 0)) |
| 2407 | (let ((item (todos-item-string)) | 4283 | (let ((item (todos-item-string)) |
| 2408 | (marked (todos-item-marked-p))) | 4284 | (marked (todos-marked-item-p))) |
| 2409 | ;; In Todos Top Priorities mode, an item's priority can be changed | 4285 | ;; In Todos Top Priorities mode, an item's priority can be changed |
| 2410 | ;; wrt items in another category, but not wrt items in the same | 4286 | ;; wrt items in another category, but not wrt items in the same |
| 2411 | ;; category. | 4287 | ;; category. |
| @@ -2441,6 +4317,7 @@ With non-nil argument LOWER lower item's priority." | |||
| 2441 | (todos-raise-item-priority t)) | 4317 | (todos-raise-item-priority t)) |
| 2442 | 4318 | ||
| 2443 | ;; FIXME: incorporate todos-(raise|lower)-item-priority ? | 4319 | ;; FIXME: incorporate todos-(raise|lower)-item-priority ? |
| 4320 | ;; FIXME: does this DTRT in todos-categories-mode? | ||
| 2444 | (defun todos-set-item-priority (item cat &optional new) | 4321 | (defun todos-set-item-priority (item cat &optional new) |
| 2445 | "Set todo ITEM's priority in category CAT, moving item as needed. | 4322 | "Set todo ITEM's priority in category CAT, moving item as needed. |
| 2446 | Interactively, the item and the category are the current ones, | 4323 | Interactively, the item and the category are the current ones, |
| @@ -2473,7 +4350,6 @@ priority is one more than the number of items in CAT." | |||
| 2473 | (unless (= priority 1) (todos-forward-item (1- priority)))) | 4350 | (unless (= priority 1) (todos-forward-item (1- priority)))) |
| 2474 | (todos-insert-with-overlays item))) | 4351 | (todos-insert-with-overlays item))) |
| 2475 | 4352 | ||
| 2476 | ;; FIXME: apply to marked items? | ||
| 2477 | (defun todos-move-item (&optional file) | 4353 | (defun todos-move-item (&optional file) |
| 2478 | "Move at least one todo item to another category. | 4354 | "Move at least one todo item to another category. |
| 2479 | 4355 | ||
| @@ -2488,7 +4364,8 @@ then it is created and the item(s) become(s) the first | |||
| 2488 | entry/entries in that category." | 4364 | entry/entries in that category." |
| 2489 | (interactive) | 4365 | (interactive) |
| 2490 | (unless (or (todos-done-item-p) | 4366 | (unless (or (todos-done-item-p) |
| 2491 | (looking-at "^$")) ; We're between todo and done items. | 4367 | ;; Point is between todo and done items. |
| 4368 | (looking-at "^$")) | ||
| 2492 | (let* ((buffer-read-only) | 4369 | (let* ((buffer-read-only) |
| 2493 | (file1 todos-current-todos-file) | 4370 | (file1 todos-current-todos-file) |
| 2494 | (cat1 (todos-current-category)) | 4371 | (cat1 (todos-current-category)) |
| @@ -2523,7 +4400,7 @@ entry/entries in that category." | |||
| 2523 | (setq item nil) | 4400 | (setq item nil) |
| 2524 | (goto-char (point-min)) | 4401 | (goto-char (point-min)) |
| 2525 | (while (not (eobp)) | 4402 | (while (not (eobp)) |
| 2526 | (when (todos-item-marked-p) | 4403 | (when (todos-marked-item-p) |
| 2527 | (setq item (concat item (todos-item-string) "\n")) | 4404 | (setq item (concat item (todos-item-string) "\n")) |
| 2528 | (setq count (1+ count)) | 4405 | (setq count (1+ count)) |
| 2529 | (when (todos-diary-item-p) | 4406 | (when (todos-diary-item-p) |
| @@ -2558,7 +4435,7 @@ entry/entries in that category." | |||
| 2558 | (setq end (match-beginning 0)) | 4435 | (setq end (match-beginning 0)) |
| 2559 | (goto-char beg) | 4436 | (goto-char beg) |
| 2560 | (while (< (point) end) | 4437 | (while (< (point) end) |
| 2561 | (if (todos-item-marked-p) | 4438 | (if (todos-marked-item-p) |
| 2562 | (todos-remove-item) | 4439 | (todos-remove-item) |
| 2563 | (todos-forward-item)))) | 4440 | (todos-forward-item)))) |
| 2564 | (todos-remove-item)))) | 4441 | (todos-remove-item)))) |
| @@ -2576,40 +4453,82 @@ entry/entries in that category." | |||
| 2576 | (interactive) | 4453 | (interactive) |
| 2577 | (todos-move-item t)) | 4454 | (todos-move-item t)) |
| 2578 | 4455 | ||
| 2579 | ;; FIXME: apply to marked items? | 4456 | (defun todos-move-item-to-diary () |
| 4457 | "Move one or more items in current category to the diary file. | ||
| 4458 | |||
| 4459 | If there are marked items, move all of these; otherwise, move | ||
| 4460 | the item at point." | ||
| 4461 | (interactive) | ||
| 4462 | ;; FIXME | ||
| 4463 | ) | ||
| 4464 | |||
| 4465 | ;; FIXME: make adding date customizable, and make this and time customization | ||
| 4466 | ;; overridable via double prefix arg ?? | ||
| 2580 | (defun todos-item-done (&optional arg) | 4467 | (defun todos-item-done (&optional arg) |
| 2581 | "Tag this item as done and move it to category's done section. | 4468 | "Tag at least one item in this category as done and hide it. |
| 2582 | With prefix argument ARG prompt for a comment and append it to the | 4469 | |
| 2583 | done item." | 4470 | With prefix argument ARG prompt for a comment and append it to |
| 4471 | the done item; this is only possible if there are no marked | ||
| 4472 | items. If there are marked items, tag all of these with | ||
| 4473 | `todos-done-string' plus the current date and, if | ||
| 4474 | `todos-always-add-time-string' is non-nil, the current time; | ||
| 4475 | otherwise, just tag the item at point. Items tagged as done are | ||
| 4476 | relocated to the category's (by default hidden) done section." | ||
| 2584 | (interactive "P") | 4477 | (interactive "P") |
| 2585 | (unless (or (todos-done-item-p) | 4478 | (let* ((cat (todos-current-category)) |
| 2586 | (looking-at "^$")) | 4479 | (marked (assoc cat todos-categories-with-marks))) |
| 2587 | (let* ((buffer-read-only) | 4480 | (unless (or (todos-done-item-p) |
| 2588 | (item (todos-item-string)) | 4481 | (and (looking-at "^$") (not marked))) |
| 2589 | (diary-item (todos-diary-item-p)) | 4482 | (let* ((date-string (calendar-date-string (calendar-current-date) t t)) |
| 2590 | (date-string (calendar-date-string (calendar-current-date) t t)) | 4483 | (time-string (if todos-always-add-time-string |
| 2591 | (time-string (if todos-always-add-time-string ;FIXME: delete condition | 4484 | (concat " " (substring (current-time-string) 11 16)) |
| 2592 | (concat " " (substring (current-time-string) 11 16)) | 4485 | "")) |
| 2593 | "")) | 4486 | (done-prefix (concat "[" todos-done-string date-string time-string |
| 2594 | ;; FIXME: todos-nondiary-* ? | 4487 | "] ")) |
| 2595 | (done-item (concat "[" todos-done-string date-string time-string "] " | 4488 | (comment (and arg (not marked) (read-string "Enter a comment: "))) |
| 2596 | item)) | 4489 | (item-count 0) |
| 2597 | (comment (and arg (read-string "Enter a comment: ")))) | 4490 | (diary-count 0) |
| 2598 | (todos-remove-item) | 4491 | item done-item |
| 2599 | (unless (zerop (length comment)) | 4492 | (buffer-read-only)) |
| 2600 | (setq done-item (concat done-item " [" todos-comment-string ": " | 4493 | (and marked (goto-char (point-min))) |
| 2601 | comment "]"))) | 4494 | (catch 'done |
| 2602 | (save-excursion | 4495 | (while (not (eobp)) |
| 2603 | (widen) | 4496 | (if (or (not marked) (and marked (todos-marked-item-p))) |
| 2604 | (re-search-forward (concat "^" (regexp-quote todos-category-done)) nil t) | 4497 | (progn |
| 2605 | (forward-char) | 4498 | (setq item (todos-item-string)) |
| 2606 | (todos-insert-with-overlays done-item)) | 4499 | (setq done-item (cond (marked |
| 2607 | (todos-set-count 'todo -1) | 4500 | (concat done-item done-prefix item "\n")) |
| 2608 | (todos-set-count 'done 1) | 4501 | (comment |
| 2609 | (and diary-item (todos-set-count 'diary -1)) | 4502 | (concat done-prefix item " [" |
| 2610 | (todos-update-categories-sexp) | 4503 | todos-comment-string |
| 2611 | (save-excursion (todos-category-select))))) | 4504 | ": " comment "]")) |
| 2612 | 4505 | (t | |
| 4506 | (concat done-prefix item)))) | ||
| 4507 | (setq item-count (1+ item-count)) | ||
| 4508 | (when (todos-diary-item-p) | ||
| 4509 | (setq diary-count (1+ diary-count))) | ||
| 4510 | (todos-remove-item) | ||
| 4511 | (unless marked (throw 'done nil))) | ||
| 4512 | (todos-forward-item)))) | ||
| 4513 | (when marked | ||
| 4514 | ;; Chop off last newline of done item string. | ||
| 4515 | (setq done-item (substring done-item 0 -1)) | ||
| 4516 | (remove-overlays (point-min) (point-max) 'before-string todos-item-mark) | ||
| 4517 | (setq todos-categories-with-marks | ||
| 4518 | (assq-delete-all cat todos-categories-with-marks))) | ||
| 4519 | (save-excursion | ||
| 4520 | (widen) | ||
| 4521 | (re-search-forward | ||
| 4522 | (concat "^" (regexp-quote todos-category-done)) nil t) | ||
| 4523 | (forward-char) | ||
| 4524 | (insert done-item "\n")) | ||
| 4525 | (todos-set-count 'todo (- item-count)) | ||
| 4526 | (todos-set-count 'done item-count) | ||
| 4527 | (todos-set-count 'diary (- diary-count)) | ||
| 4528 | (todos-update-categories-sexp) | ||
| 4529 | (save-excursion (todos-category-select)))))) | ||
| 4530 | |||
| 4531 | ;; FIXME: only if there's no comment, or edit an existing comment? | ||
| 2613 | (defun todos-comment-done-item () | 4532 | (defun todos-comment-done-item () |
| 2614 | "Add a comment to this done item." | 4533 | "Add a comment to this done item." |
| 2615 | (interactive) | 4534 | (interactive) |
| @@ -2668,7 +4587,7 @@ If the archive of this file does not exist, it is created. If | |||
| 2668 | this category does not exist in the archive, it is created." | 4587 | this category does not exist in the archive, it is created." |
| 2669 | (interactive) | 4588 | (interactive) |
| 2670 | (when (not (member (buffer-file-name) (funcall todos-files-function t))) | 4589 | (when (not (member (buffer-file-name) (funcall todos-files-function t))) |
| 2671 | (if (and all (zerop (todos-get-count 'done cat))) | 4590 | (if (and all (zerop (todos-get-count 'done))) |
| 2672 | (message "No done items in this category") | 4591 | (message "No done items in this category") |
| 2673 | (catch 'end | 4592 | (catch 'end |
| 2674 | (let* ((cat (todos-current-category)) | 4593 | (let* ((cat (todos-current-category)) |
| @@ -2697,7 +4616,7 @@ this category does not exist in the archive, it is created." | |||
| 2697 | (save-excursion | 4616 | (save-excursion |
| 2698 | (goto-char (point-min)) | 4617 | (goto-char (point-min)) |
| 2699 | (while (not (eobp)) | 4618 | (while (not (eobp)) |
| 2700 | (if (todos-item-marked-p) | 4619 | (if (todos-marked-item-p) |
| 2701 | (if (not (todos-done-item-p)) | 4620 | (if (not (todos-done-item-p)) |
| 2702 | (throw 'end (message "Only done items can be archived")) | 4621 | (throw 'end (message "Only done items can be archived")) |
| 2703 | (concat marked-items (todos-item-string) "\n") | 4622 | (concat marked-items (todos-item-string) "\n") |
| @@ -2751,7 +4670,7 @@ this category does not exist in the archive, it is created." | |||
| 2751 | (and marked (goto-char (point-min))) | 4670 | (and marked (goto-char (point-min))) |
| 2752 | (catch 'done | 4671 | (catch 'done |
| 2753 | (while (not (eobp)) | 4672 | (while (not (eobp)) |
| 2754 | (if (or (and marked (todos-item-marked-p)) item) | 4673 | (if (or (and marked (todos-marked-item-p)) item) |
| 2755 | (progn | 4674 | (progn |
| 2756 | (todos-remove-item) | 4675 | (todos-remove-item) |
| 2757 | (todos-set-count 'done -1) | 4676 | (todos-set-count 'done -1) |
| @@ -2816,7 +4735,7 @@ archive, the archive file is deleted." | |||
| 2816 | (save-excursion | 4735 | (save-excursion |
| 2817 | (goto-char (point-min)) | 4736 | (goto-char (point-min)) |
| 2818 | (while (not (eobp)) | 4737 | (while (not (eobp)) |
| 2819 | (when (todos-item-marked-p) | 4738 | (when (todos-marked-item-p) |
| 2820 | (concat marked-items (todos-item-string) "\n") | 4739 | (concat marked-items (todos-item-string) "\n") |
| 2821 | (setq marked-count (1+ marked-count))) | 4740 | (setq marked-count (1+ marked-count))) |
| 2822 | (todos-forward-item))) | 4741 | (todos-forward-item))) |
| @@ -2854,7 +4773,7 @@ archive, the archive file is deleted." | |||
| 2854 | (and marked (goto-char (point-min))) | 4773 | (and marked (goto-char (point-min))) |
| 2855 | (catch 'done | 4774 | (catch 'done |
| 2856 | (while (not (eobp)) | 4775 | (while (not (eobp)) |
| 2857 | (if (or (and marked (todos-item-marked-p)) item) | 4776 | (if (or (and marked (todos-marked-item-p)) item) |
| 2858 | (progn | 4777 | (progn |
| 2859 | (todos-remove-item) | 4778 | (todos-remove-item) |
| 2860 | (todos-set-count 'done -1) | 4779 | (todos-set-count 'done -1) |
| @@ -2905,1191 +4824,11 @@ archive, the archive file is deleted." | |||
| 2905 | (interactive) | 4824 | (interactive) |
| 2906 | (todos-unarchive-items t)) | 4825 | (todos-unarchive-items t)) |
| 2907 | 4826 | ||
| 2908 | (defun todos-toggle-diary-inclusion (&optional all) | ||
| 2909 | "Toggle diary status of one or more todo items in this category. | ||
| 2910 | |||
| 2911 | If a candidate item is marked with `todos-nondiary-marker', | ||
| 2912 | remove this marker; otherwise, insert it. | ||
| 2913 | |||
| 2914 | With non-nil argument ALL toggle the diary status of all todo | ||
| 2915 | items in this category; otherwise, if there are marked todo | ||
| 2916 | items, toggle the diary status of all and only these, otherwise | ||
| 2917 | toggle the diary status of the item at point. " | ||
| 2918 | (interactive) | ||
| 2919 | (let ((marked (assoc (todos-current-category) | ||
| 2920 | todos-categories-with-marks))) | ||
| 2921 | (catch 'stop | ||
| 2922 | (save-excursion | ||
| 2923 | (save-restriction | ||
| 2924 | (when (or marked all) (goto-char (point-min))) | ||
| 2925 | (while (not (eobp)) | ||
| 2926 | (if (todos-done-item-p) | ||
| 2927 | (throw 'stop (message "Done items cannot be changed")) | ||
| 2928 | (unless (and marked (not (todos-item-marked-p))) | ||
| 2929 | (save-excursion | ||
| 2930 | (let* ((buffer-read-only) | ||
| 2931 | (beg (todos-item-start)) | ||
| 2932 | (lim (save-excursion (todos-item-end))) | ||
| 2933 | (end (save-excursion | ||
| 2934 | (or (todos-time-string-matcher lim) | ||
| 2935 | (todos-date-string-matcher lim))))) | ||
| 2936 | (if (looking-at (regexp-quote todos-nondiary-start)) | ||
| 2937 | (progn | ||
| 2938 | (replace-match "") | ||
| 2939 | (search-forward todos-nondiary-end (1+ end) t) | ||
| 2940 | (replace-match "") | ||
| 2941 | (todos-set-count 'diary 1)) | ||
| 2942 | (when end | ||
| 2943 | (insert todos-nondiary-start) | ||
| 2944 | (goto-char (1+ end)) | ||
| 2945 | (insert todos-nondiary-end) | ||
| 2946 | (todos-set-count 'diary -1)))))) | ||
| 2947 | (unless (or marked all) (throw 'stop nil)) | ||
| 2948 | (todos-forward-item)))))) | ||
| 2949 | (todos-update-categories-sexp))) | ||
| 2950 | |||
| 2951 | (defun todos-toggle-item-diary-nonmarking () | ||
| 2952 | "Mark or unmark this todos diary item for calendar display. | ||
| 2953 | See `diary-nonmarking-symbol'." | ||
| 2954 | (interactive) | ||
| 2955 | (let ((buffer-read-only)) | ||
| 2956 | (save-excursion | ||
| 2957 | (todos-item-start) | ||
| 2958 | (unless (looking-at (regexp-quote todos-nondiary-start)) | ||
| 2959 | (if (looking-at (regexp-quote diary-nonmarking-symbol)) | ||
| 2960 | (replace-match "") | ||
| 2961 | (insert diary-nonmarking-symbol)))))) | ||
| 2962 | |||
| 2963 | (defun todos-toggle-diary-nonmarking () | ||
| 2964 | "Mark or unmark this category's todos diary items for calendar. | ||
| 2965 | See `diary-nonmarking-symbol'." | ||
| 2966 | (interactive) | ||
| 2967 | (save-excursion | ||
| 2968 | (goto-char (point-min)) | ||
| 2969 | (while (not (eobp)) | ||
| 2970 | (todos-toggle-item-diary-nonmarking) | ||
| 2971 | (todos-forward-item)))) | ||
| 2972 | |||
| 2973 | (defun todos-print (&optional to-file) | ||
| 2974 | "Produce a printable version of the current Todos buffer. | ||
| 2975 | This includes overlays, indentation, and, depending on the value | ||
| 2976 | of `todos-print-function', faces. With non-nil argument TO-FILE | ||
| 2977 | write the printable version to a file; otherwise, send it to the | ||
| 2978 | default printer." | ||
| 2979 | (interactive) | ||
| 2980 | (let ((buf todos-tmp-buffer-name) ;FIXME | ||
| 2981 | (header (cond | ||
| 2982 | ((eq major-mode 'todos-mode) | ||
| 2983 | (concat "Todos File: " | ||
| 2984 | (file-name-sans-extension | ||
| 2985 | (file-name-nondirectory todos-current-todos-file)) | ||
| 2986 | "\nCategory: " (todos-current-category))) | ||
| 2987 | ((eq major-mode 'todos-filter-items-mode) | ||
| 2988 | "Todos Top Priorities"))) | ||
| 2989 | (prefix (propertize (concat todos-prefix " ") | ||
| 2990 | 'face 'todos-prefix-string)) | ||
| 2991 | (num 0) | ||
| 2992 | (fill-prefix (make-string todos-indent-to-here 32)) | ||
| 2993 | (content (buffer-string)) | ||
| 2994 | file) | ||
| 2995 | (with-current-buffer (get-buffer-create buf) | ||
| 2996 | (insert content) | ||
| 2997 | (goto-char (point-min)) | ||
| 2998 | (while (not (eobp)) | ||
| 2999 | (let ((beg (point)) | ||
| 3000 | (end (save-excursion (todos-item-end)))) | ||
| 3001 | (when todos-number-prefix | ||
| 3002 | (setq num (1+ num)) | ||
| 3003 | (setq prefix (propertize (concat (number-to-string num) " ") | ||
| 3004 | 'face 'todos-prefix-string))) | ||
| 3005 | (insert prefix) | ||
| 3006 | (fill-region beg end)) | ||
| 3007 | ;; Calling todos-forward-item infloops at todos-item-start due to | ||
| 3008 | ;; non-overlay prefix, so search for item start instead. | ||
| 3009 | (if (re-search-forward todos-item-start nil t) | ||
| 3010 | (beginning-of-line) | ||
| 3011 | (goto-char (point-max)))) | ||
| 3012 | (if (re-search-backward (concat "^" (regexp-quote todos-category-done)) | ||
| 3013 | nil t) | ||
| 3014 | (replace-match todos-done-separator)) | ||
| 3015 | (goto-char (point-min)) | ||
| 3016 | (insert header) | ||
| 3017 | (newline 2) | ||
| 3018 | (if to-file | ||
| 3019 | (let ((file (read-file-name "Print to file: "))) | ||
| 3020 | (funcall todos-print-function file)) | ||
| 3021 | (funcall todos-print-function))) | ||
| 3022 | (kill-buffer buf))) | ||
| 3023 | |||
| 3024 | (defun todos-print-to-file () | ||
| 3025 | "Save printable version of this Todos buffer to a file." | ||
| 3026 | (interactive) | ||
| 3027 | (todos-print t)) | ||
| 3028 | |||
| 3029 | ;; --------------------------------------------------------------------------- | ||
| 3030 | |||
| 3031 | ;;; Internals | ||
| 3032 | |||
| 3033 | (defvar todos-date-pattern ;FIXME: start with "^" ? | ||
| 3034 | (let ((dayname (diary-name-pattern calendar-day-name-array nil t))) | ||
| 3035 | (concat "\\(?:" dayname "\\|" | ||
| 3036 | (let ((dayname) | ||
| 3037 | (monthname (format "\\(?:%s\\|\\*\\)" | ||
| 3038 | (diary-name-pattern | ||
| 3039 | calendar-month-name-array | ||
| 3040 | calendar-month-abbrev-array t))) | ||
| 3041 | (month "\\(?:[0-9]+\\|\\*\\)") | ||
| 3042 | (day "\\(?:[0-9]+\\|\\*\\)") | ||
| 3043 | (year "-?\\(?:[0-9]+\\|\\*\\)")) | ||
| 3044 | (mapconcat 'eval calendar-date-display-form "")) | ||
| 3045 | "\\)")) | ||
| 3046 | "Regular expression matching a Todos date header.") | ||
| 3047 | |||
| 3048 | (defvar todos-date-string-start | ||
| 3049 | ;; FIXME: with ? matches anything | ||
| 3050 | (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|" | ||
| 3051 | (regexp-quote diary-nonmarking-symbol) "\\)?") | ||
| 3052 | "Regular expression matching part of item header before the date.") | ||
| 3053 | |||
| 3054 | (defvar todos-done-string-start | ||
| 3055 | (concat "^\\[" (regexp-quote todos-done-string)) | ||
| 3056 | "Regular expression matching start of done item.") | ||
| 3057 | |||
| 3058 | (defun todos-date-string-matcher (lim) | ||
| 3059 | "Search for Todos date strings within LIM for font-locking." | ||
| 3060 | (re-search-forward | ||
| 3061 | (concat todos-date-string-start "\\(?1:" todos-date-pattern "\\)") lim t)) | ||
| 3062 | |||
| 3063 | (defun todos-time-string-matcher (lim) | ||
| 3064 | "Search for Todos time strings within LIM for font-locking." | ||
| 3065 | (re-search-forward (concat todos-date-string-start todos-date-pattern | ||
| 3066 | " \\(?1:" diary-time-regexp "\\)") lim t)) | ||
| 3067 | |||
| 3068 | (defun todos-done-string-matcher (lim) | ||
| 3069 | "Search for Todos done headers within LIM for font-locking." | ||
| 3070 | (re-search-forward (concat todos-done-string-start | ||
| 3071 | "[^][]+]") | ||
| 3072 | lim t)) | ||
| 3073 | |||
| 3074 | (defun todos-comment-string-matcher (lim) | ||
| 3075 | "Search for Todos done comment within LIM for font-locking." | ||
| 3076 | (re-search-forward (concat "\\[\\(?1:" todos-comment-string "\\):") | ||
| 3077 | lim t)) | ||
| 3078 | |||
| 3079 | (defun todos-category-string-matcher (lim) | ||
| 3080 | "Search for Todos category headers within LIM for font-locking." | ||
| 3081 | (if (eq major-mode 'todos-filter-items-mode) | ||
| 3082 | (re-search-forward | ||
| 3083 | ;; (concat "^\\(?1:" (regexp-quote todos-category-beg) ".*\\)$") | ||
| 3084 | (concat "\\(?:^\\[?" todos-date-pattern "\\(?: " diary-time-regexp | ||
| 3085 | "\\)?\\]?\\) \\(?1:\\[.+\\]\\)") lim t))) | ||
| 3086 | |||
| 3087 | (defun todos-check-format () | ||
| 3088 | "Signal an error if the current Todos file is ill-formatted." | ||
| 3089 | (save-excursion | ||
| 3090 | (save-restriction | ||
| 3091 | (widen) | ||
| 3092 | (goto-char (point-min)) | ||
| 3093 | (let ((legit (concat "^\\(" (regexp-quote todos-category-beg) "\\)" | ||
| 3094 | "\\|\\(\\[?" todos-date-pattern "\\)" | ||
| 3095 | "\\|\\([ \t]+[^ \t]*\\)" | ||
| 3096 | "\\|$"))) | ||
| 3097 | (while (not (eobp)) | ||
| 3098 | (unless (looking-at legit) | ||
| 3099 | (error "Illegitimate Todos file format at line %d" | ||
| 3100 | (line-number-at-pos (point)))) | ||
| 3101 | (forward-line))))) | ||
| 3102 | (message "This Todos file is well-formatted.")) | ||
| 3103 | |||
| 3104 | (defun todos-after-find-file () | ||
| 3105 | "Show Todos files correctly when visited from outside of Todos mode." | ||
| 3106 | (and (member this-command todos-visit-files-commands) | ||
| 3107 | (= (- (point-max) (point-min)) (buffer-size)) | ||
| 3108 | (member major-mode '(todos-mode todos-archive-mode)) | ||
| 3109 | (todos-category-select))) | ||
| 3110 | |||
| 3111 | (defun todos-wrap-and-indent () | ||
| 3112 | "Use word wrapping on long lines and indent with a wrap prefix. | ||
| 3113 | The amount of indentation is given by user option | ||
| 3114 | `todos-indent-to-here'." | ||
| 3115 | (set (make-local-variable 'word-wrap) t) | ||
| 3116 | (set (make-local-variable 'wrap-prefix) (make-string todos-indent-to-here 32)) | ||
| 3117 | (unless (member '(continuation) fringe-indicator-alist) | ||
| 3118 | (push '(continuation) fringe-indicator-alist))) | ||
| 3119 | |||
| 3120 | (defun todos-indent () | ||
| 3121 | "Indent from point to `todos-indent-to-here'." | ||
| 3122 | (indent-to todos-indent-to-here todos-indent-to-here)) | ||
| 3123 | |||
| 3124 | (defun todos-prefix-overlays () | ||
| 3125 | "Put before-string overlay in front of this category's items. | ||
| 3126 | The overlay's value is the string `todos-prefix' or with non-nil | ||
| 3127 | `todos-number-prefix' an integer in the sequence from 1 to the | ||
| 3128 | number of todo or done items in the category indicating the | ||
| 3129 | item's priority. Todo and done items are numbered independently | ||
| 3130 | of each other." | ||
| 3131 | (when (or todos-number-prefix | ||
| 3132 | (not (string-match "^[[:space:]]*$" todos-prefix))) | ||
| 3133 | (let ((prefix (propertize (concat todos-prefix " ") | ||
| 3134 | 'face 'todos-prefix-string)) | ||
| 3135 | (num 0)) | ||
| 3136 | (save-excursion | ||
| 3137 | (goto-char (point-min)) | ||
| 3138 | (while (not (eobp)) | ||
| 3139 | (when (or (todos-date-string-matcher (line-end-position)) | ||
| 3140 | (todos-done-string-matcher (line-end-position))) | ||
| 3141 | (goto-char (match-beginning 0)) | ||
| 3142 | (when todos-number-prefix | ||
| 3143 | (setq num (1+ num)) | ||
| 3144 | ;; Reset number for done items. | ||
| 3145 | (when | ||
| 3146 | ;; FIXME: really need this? | ||
| 3147 | ;; If last not done item is multiline, then | ||
| 3148 | ;; todos-done-string-matcher skips empty line, so have | ||
| 3149 | ;; to look back. | ||
| 3150 | (and (looking-at todos-done-string-start) | ||
| 3151 | (looking-back (concat "^" | ||
| 3152 | (regexp-quote todos-category-done) | ||
| 3153 | "\n"))) | ||
| 3154 | (setq num 1)) | ||
| 3155 | (setq prefix (propertize (concat (number-to-string num) " ") | ||
| 3156 | 'face 'todos-prefix-string))) | ||
| 3157 | (let ((ovs (overlays-in (point) (point))) | ||
| 3158 | marked ov-pref) | ||
| 3159 | (if ovs | ||
| 3160 | (dolist (ov ovs) | ||
| 3161 | (let ((val (overlay-get ov 'before-string))) | ||
| 3162 | (if (equal val "*") | ||
| 3163 | (setq marked t) | ||
| 3164 | (setq ov-pref val))))) | ||
| 3165 | (unless (equal ov-pref prefix) | ||
| 3166 | (remove-overlays (point) (point)) ; 'before-string) doesn't work | ||
| 3167 | (overlay-put (make-overlay (point) (point)) | ||
| 3168 | 'before-string prefix) | ||
| 3169 | (and marked (overlay-put (make-overlay (point) (point)) | ||
| 3170 | 'before-string todos-item-mark))))) | ||
| 3171 | (forward-line)))))) | ||
| 3172 | |||
| 3173 | (defun todos-reset-prefix (symbol value) | ||
| 3174 | "The :set function for `todos-prefix' and `todos-number-prefix'." | ||
| 3175 | (let ((oldvalue (symbol-value symbol)) | ||
| 3176 | (files (append todos-files todos-archives))) | ||
| 3177 | (custom-set-default symbol value) | ||
| 3178 | (when (not (equal value oldvalue)) | ||
| 3179 | (dolist (f files) | ||
| 3180 | (with-current-buffer (find-file-noselect f) | ||
| 3181 | (save-window-excursion | ||
| 3182 | (todos-show) | ||
| 3183 | (save-excursion | ||
| 3184 | (widen) | ||
| 3185 | (goto-char (point-min)) | ||
| 3186 | (while (not (eobp)) | ||
| 3187 | (remove-overlays (point) (point)); 'before-string prefix) | ||
| 3188 | (forward-line))) | ||
| 3189 | ;; Activate the new setting (save-restriction does not help). | ||
| 3190 | (save-excursion (todos-category-select)))))))) | ||
| 3191 | |||
| 3192 | (defun todos-reset-nondiary-marker (symbol value) | ||
| 3193 | "The :set function for user option `todos-nondiary-marker'." | ||
| 3194 | (let ((oldvalue (symbol-value symbol)) | ||
| 3195 | (files (append todos-files todos-archives))) | ||
| 3196 | (custom-set-default symbol value) | ||
| 3197 | ;; Need to reset these to get font-locking right. | ||
| 3198 | (setq todos-nondiary-start (nth 0 todos-nondiary-marker) | ||
| 3199 | todos-nondiary-end (nth 1 todos-nondiary-marker) | ||
| 3200 | todos-date-string-start | ||
| 3201 | ;; FIXME: with ? matches anything | ||
| 3202 | (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|" | ||
| 3203 | (regexp-quote diary-nonmarking-symbol) "\\)?")) | ||
| 3204 | (when (not (equal value oldvalue)) | ||
| 3205 | (dolist (f files) | ||
| 3206 | (with-current-buffer (find-file-noselect f) | ||
| 3207 | (let (buffer-read-only) | ||
| 3208 | (widen) | ||
| 3209 | (goto-char (point-min)) | ||
| 3210 | (while (not (eobp)) | ||
| 3211 | (if (re-search-forward | ||
| 3212 | (concat "^\\(" todos-done-string-start "[^][]+] \\)?" | ||
| 3213 | "\\(?1:" (regexp-quote (car oldvalue)) | ||
| 3214 | "\\)" todos-date-pattern "\\( " | ||
| 3215 | diary-time-regexp "\\)?\\(?2:" | ||
| 3216 | (regexp-quote (cadr oldvalue)) "\\)") | ||
| 3217 | nil t) | ||
| 3218 | (progn | ||
| 3219 | (replace-match (nth 0 value) t t nil 1) | ||
| 3220 | (replace-match (nth 1 value) t t nil 2)) | ||
| 3221 | (forward-line))) | ||
| 3222 | (todos-category-select))))))) | ||
| 3223 | |||
| 3224 | (defun todos-reset-done-string (symbol value) | ||
| 3225 | "The :set function for user option `todos-done-string'." | ||
| 3226 | (let ((oldvalue (symbol-value symbol)) | ||
| 3227 | (files (append todos-files todos-archives))) | ||
| 3228 | (custom-set-default symbol value) | ||
| 3229 | ;; Need to reset this to get font-locking right. | ||
| 3230 | (setq todos-done-string-start | ||
| 3231 | (concat "^\\[" (regexp-quote todos-done-string))) | ||
| 3232 | (when (not (equal value oldvalue)) | ||
| 3233 | (dolist (f files) | ||
| 3234 | (with-current-buffer (find-file-noselect f) | ||
| 3235 | (let (buffer-read-only) | ||
| 3236 | (widen) | ||
| 3237 | (goto-char (point-min)) | ||
| 3238 | (while (not (eobp)) | ||
| 3239 | (if (re-search-forward | ||
| 3240 | (concat "^" (regexp-quote todos-nondiary-start) | ||
| 3241 | "\\(" (regexp-quote oldvalue) "\\)") | ||
| 3242 | nil t) | ||
| 3243 | (replace-match value t t nil 1) | ||
| 3244 | (forward-line))) | ||
| 3245 | (todos-category-select))))))) | ||
| 3246 | |||
| 3247 | (defun todos-reset-comment-string (symbol value) | ||
| 3248 | "The :set function for user option `todos-comment-string'." | ||
| 3249 | (let ((oldvalue (symbol-value symbol)) | ||
| 3250 | (files (append todos-files todos-archives))) | ||
| 3251 | (custom-set-default symbol value) | ||
| 3252 | (when (not (equal value oldvalue)) | ||
| 3253 | (dolist (f files) | ||
| 3254 | (with-current-buffer (find-file-noselect f) | ||
| 3255 | (let (buffer-read-only) | ||
| 3256 | (save-excursion | ||
| 3257 | (widen) | ||
| 3258 | (goto-char (point-min)) | ||
| 3259 | (while (not (eobp)) | ||
| 3260 | (if (re-search-forward | ||
| 3261 | (concat | ||
| 3262 | "\\[\\(" (regexp-quote oldvalue) "\\): [^]]*\\]") | ||
| 3263 | nil t) | ||
| 3264 | (replace-match value t t nil 1) | ||
| 3265 | (forward-line))) | ||
| 3266 | (todos-category-select)))))))) | ||
| 3267 | |||
| 3268 | (defun todos-reset-categories (symbol value) | ||
| 3269 | "The :set function for `todos-ignore-archived-categories'." | ||
| 3270 | (custom-set-default symbol value) | ||
| 3271 | (dolist (f (funcall todos-files-function)) | ||
| 3272 | (with-current-buffer (find-file-noselect f) | ||
| 3273 | (if value | ||
| 3274 | (setq todos-categories-full todos-categories | ||
| 3275 | todos-categories (todos-truncate-categories-list)) | ||
| 3276 | (setq todos-categories todos-categories-full | ||
| 3277 | todos-categories-full nil)) | ||
| 3278 | (todos-category-select)))) | ||
| 3279 | |||
| 3280 | (defun todos-toggle-show-current-file (symbol value) | ||
| 3281 | "The :set function for user option `todos-show-current-file'." | ||
| 3282 | (custom-set-default symbol value) | ||
| 3283 | (if value | ||
| 3284 | (add-hook 'pre-command-hook 'todos-show-current-file nil t) | ||
| 3285 | (remove-hook 'pre-command-hook 'todos-show-current-file t))) | ||
| 3286 | |||
| 3287 | (defun todos-show-current-file () | ||
| 3288 | "Visit current instead of default Todos file with `todos-show'. | ||
| 3289 | This function is added to `pre-command-hook' when user option | ||
| 3290 | `todos-show-current-file' is set to non-nil." | ||
| 3291 | (setq todos-global-current-todos-file todos-current-todos-file)) | ||
| 3292 | ;; (and (eq major-mode 'todos-mode) | ||
| 3293 | ;; (setq todos-global-current-todos-file (buffer-file-name)))) | ||
| 3294 | |||
| 3295 | ;; FIXME: rename to todos-set-category-number ? | ||
| 3296 | (defun todos-category-number (cat) | ||
| 3297 | "Set and return buffer-local value of `todos-category-number'. | ||
| 3298 | This value is one more than the index of category CAT, starting | ||
| 3299 | with one instead of zero, so that the highest priority | ||
| 3300 | category (see `todos-display-categories') has the number one." | ||
| 3301 | (let ((categories (mapcar 'car todos-categories))) | ||
| 3302 | (setq todos-category-number | ||
| 3303 | (1+ (- (length categories) | ||
| 3304 | (length (member cat categories))))))) | ||
| 3305 | |||
| 3306 | (defun todos-current-category () | ||
| 3307 | "Return the name of the current category." | ||
| 3308 | (car (nth (1- todos-category-number) todos-categories))) | ||
| 3309 | |||
| 3310 | (defun todos-category-select () | ||
| 3311 | "Display the current category correctly. | ||
| 3312 | |||
| 3313 | With non-nil user option `todos-show-done-only' display only the | ||
| 3314 | category's done (but not archived) items; else (the default) | ||
| 3315 | display just the todo items, or with non-nil user option | ||
| 3316 | `todos-show-with-done' also display the category's done items | ||
| 3317 | below the todo items." | ||
| 3318 | (let ((name (todos-current-category)) | ||
| 3319 | cat-begin cat-end done-start done-sep-start done-end) | ||
| 3320 | (widen) | ||
| 3321 | (goto-char (point-min)) | ||
| 3322 | (re-search-forward | ||
| 3323 | (concat "^" (regexp-quote (concat todos-category-beg name)) "$") nil t) | ||
| 3324 | (setq cat-begin (1+ (line-end-position))) | ||
| 3325 | (setq cat-end (if (re-search-forward | ||
| 3326 | (concat "^" (regexp-quote todos-category-beg)) nil t) | ||
| 3327 | (match-beginning 0) | ||
| 3328 | (point-max))) | ||
| 3329 | (setq mode-line-buffer-identification | ||
| 3330 | (funcall todos-mode-line-function name)) | ||
| 3331 | (narrow-to-region cat-begin cat-end) | ||
| 3332 | (todos-prefix-overlays) | ||
| 3333 | (goto-char (point-min)) | ||
| 3334 | (if (re-search-forward (concat "\n\\(" (regexp-quote todos-category-done) | ||
| 3335 | "\\)") nil t) | ||
| 3336 | (progn | ||
| 3337 | (setq done-start (match-beginning 0)) | ||
| 3338 | (setq done-sep-start (match-beginning 1)) | ||
| 3339 | (setq done-end (match-end 0))) | ||
| 3340 | (error "Category %s is missing todos-category-done string" name)) | ||
| 3341 | (if todos-show-done-only | ||
| 3342 | (narrow-to-region (1+ done-end) (point-max)) | ||
| 3343 | ;; Display or hide done items as per todos-show-with-done. | ||
| 3344 | ;; FIXME: use todos-done-string-start ? | ||
| 3345 | (when (re-search-forward (concat "\n\\(\\[" | ||
| 3346 | (regexp-quote todos-done-string) | ||
| 3347 | "\\)") nil t) | ||
| 3348 | (let (done-sep prefix ov-pref ov-done) | ||
| 3349 | ;; FIXME: delete overlay when not viewing done items? | ||
| 3350 | (when todos-show-with-done | ||
| 3351 | (setq done-sep todos-done-separator) | ||
| 3352 | (setq done-start cat-end) | ||
| 3353 | (setq ov-pref (make-overlay done-sep-start done-end)) | ||
| 3354 | (overlay-put ov-pref 'display done-sep)))) | ||
| 3355 | (narrow-to-region (point-min) done-start)))) | ||
| 3356 | |||
| 3357 | (defun todos-insert-with-overlays (item) | ||
| 3358 | "Insert ITEM and update prefix/priority number overlays." | ||
| 3359 | (todos-item-start) | ||
| 3360 | (insert item "\n") | ||
| 3361 | (todos-backward-item) | ||
| 3362 | (todos-prefix-overlays)) | ||
| 3363 | |||
| 3364 | (defvar todos-item-start ;; (concat "^\\(\\[\\(" (regexp-quote todos-done-string) | ||
| 3365 | ;; "\\)?\\)?" todos-date-pattern) | ||
| 3366 | (concat "\\(" todos-date-string-start "\\|" todos-done-string-start | ||
| 3367 | "\\)" todos-date-pattern) | ||
| 3368 | "String identifying start of a Todos item.") | ||
| 3369 | |||
| 3370 | (defun todos-item-start () | ||
| 3371 | "Move to start of current Todos item and return its position." | ||
| 3372 | (unless (or | ||
| 3373 | ;; Point is either on last item in this category or on the empty | ||
| 3374 | ;; line between done and not done items. | ||
| 3375 | (looking-at "^$") | ||
| 3376 | ;; There are no done items in this category yet. | ||
| 3377 | (looking-at (regexp-quote todos-category-beg))) | ||
| 3378 | (goto-char (line-beginning-position)) | ||
| 3379 | (while (not (looking-at todos-item-start)) | ||
| 3380 | (forward-line -1)) | ||
| 3381 | (point))) | ||
| 3382 | |||
| 3383 | (defun todos-item-end () | ||
| 3384 | "Move to end of current Todos item and return its position." | ||
| 3385 | ;; Items cannot end with a blank line. | ||
| 3386 | (unless (looking-at "^$") | ||
| 3387 | (let ((done (todos-done-item-p))) | ||
| 3388 | (todos-forward-item) | ||
| 3389 | ;; Adjust if item is last unfinished one before displayed done items. | ||
| 3390 | (when (and (not done) (todos-done-item-p)) | ||
| 3391 | (forward-line -1)) | ||
| 3392 | (backward-char)) | ||
| 3393 | (point))) | ||
| 3394 | |||
| 3395 | (defun todos-remove-item () | ||
| 3396 | "Internal function called in editing, deleting or moving items." | ||
| 3397 | (let* ((beg (todos-item-start)) | ||
| 3398 | (end (progn (todos-item-end) (1+ (point)))) | ||
| 3399 | (ovs (overlays-in beg beg))) | ||
| 3400 | ;; There can be both prefix/number and mark overlays. | ||
| 3401 | (while ovs (delete-overlay (car ovs)) (pop ovs)) | ||
| 3402 | (delete-region beg end))) | ||
| 3403 | |||
| 3404 | (defun todos-item-string () | ||
| 3405 | "Return bare text of current item as a string." | ||
| 3406 | (let ((opoint (point)) | ||
| 3407 | (start (todos-item-start)) | ||
| 3408 | (end (todos-item-end))) | ||
| 3409 | (goto-char opoint) | ||
| 3410 | (and start end (buffer-substring-no-properties start end)))) | ||
| 3411 | |||
| 3412 | (defun todos-diary-item-p () | ||
| 3413 | "Return non-nil if item at point is marked for diary inclusion." | ||
| 3414 | (save-excursion | ||
| 3415 | (todos-item-start) | ||
| 3416 | (looking-at todos-date-pattern))) | ||
| 3417 | |||
| 3418 | (defun todos-done-item-p () | ||
| 3419 | "Return non-nil if item at point is a done item." | ||
| 3420 | (save-excursion | ||
| 3421 | (todos-item-start) | ||
| 3422 | (looking-at todos-done-string-start))) | ||
| 3423 | |||
| 3424 | (defvar todos-item-mark (propertize (if (equal todos-prefix "*") "@" "*") | ||
| 3425 | 'face 'todos-mark) | ||
| 3426 | "String used to mark items.") | ||
| 3427 | |||
| 3428 | (defun todos-item-marked-p () | ||
| 3429 | "If this item is marked, return mark overlay." | ||
| 3430 | (let ((ovs (overlays-in (line-beginning-position) (line-beginning-position))) | ||
| 3431 | (mark todos-item-mark) | ||
| 3432 | ov marked) | ||
| 3433 | (catch 'stop | ||
| 3434 | (while ovs | ||
| 3435 | (setq ov (pop ovs)) | ||
| 3436 | (and (equal (overlay-get ov 'before-string) mark) | ||
| 3437 | (throw 'stop (setq marked t))))) | ||
| 3438 | (when marked ov))) | ||
| 3439 | |||
| 3440 | (defvar todos-categories-with-marks nil | ||
| 3441 | "Alist of categories and number of marked items they contain.") | ||
| 3442 | |||
| 3443 | (defun todos-get-count (type &optional category) | ||
| 3444 | "Return count of TYPE items in CATEGORY. | ||
| 3445 | If CATEGORY is nil, default to the current category." | ||
| 3446 | (let* ((cat (or category (todos-current-category))) | ||
| 3447 | (counts (cdr (assoc cat todos-categories))) | ||
| 3448 | (idx (cond ((eq type 'todo) 0) | ||
| 3449 | ((eq type 'diary) 1) | ||
| 3450 | ((eq type 'done) 2) | ||
| 3451 | ((eq type 'archived) 3)))) | ||
| 3452 | (aref counts idx))) | ||
| 3453 | |||
| 3454 | (defun todos-set-count (type increment &optional category) | ||
| 3455 | "Increment count of TYPE items in CATEGORY by INCREMENT. | ||
| 3456 | If CATEGORY is nil, default to the current category." | ||
| 3457 | (let* ((cat (or category (todos-current-category))) | ||
| 3458 | (counts (cdr (assoc cat todos-categories))) | ||
| 3459 | (idx (cond ((eq type 'todo) 0) | ||
| 3460 | ((eq type 'diary) 1) | ||
| 3461 | ((eq type 'done) 2) | ||
| 3462 | ((eq type 'archived) 3)))) | ||
| 3463 | (aset counts idx (+ increment (aref counts idx))))) | ||
| 3464 | |||
| 3465 | ;; (defun todos-item-counts (operation &optional cat1 cat2) | ||
| 3466 | ;; "Update item counts in category CAT1 changed by OPERATION. | ||
| 3467 | ;; If CAT1 is nil, update counts from the current category. With | ||
| 3468 | ;; non-nil CAT2 include specified counts from that category in the | ||
| 3469 | ;; calculation for CAT1. | ||
| 3470 | ;; After updating the item counts, update the `todos-categories' sexp." | ||
| 3471 | ;; (let* ((cat (or cat1 (todos-current-category)))) | ||
| 3472 | ;; (cond ((eq type 'insert) | ||
| 3473 | ;; (todos-set-count 'todo 1 cat)) | ||
| 3474 | ;; ((eq type 'diary) | ||
| 3475 | ;; (todos-set-count 'diary 1 cat)) | ||
| 3476 | ;; ((eq type 'nondiary) | ||
| 3477 | ;; (todos-set-count 'diary -1 cat)) | ||
| 3478 | ;; ((eq type 'delete) | ||
| 3479 | ;; ;; FIXME: ok if last done item was deleted? | ||
| 3480 | ;; (if (save-excursion | ||
| 3481 | ;; (re-search-backward (concat "^" (regexp-quote | ||
| 3482 | ;; todos-category-done)) nil t)) | ||
| 3483 | ;; (todos-set-count 'done -1 cat) | ||
| 3484 | ;; (todos-set-count 'todo -1 cat))) | ||
| 3485 | ;; ((eq type 'done) | ||
| 3486 | ;; (unless (member (buffer-file-name) (funcall todos-files-function t)) | ||
| 3487 | ;; (todos-set-count 'todo -1 cat)) | ||
| 3488 | ;; (todos-set-count 'done 1 cat)) | ||
| 3489 | ;; ((eq type 'undo) | ||
| 3490 | ;; (todos-set-count 'todo 1 cat) | ||
| 3491 | ;; (todos-set-count 'done -1 cat)) | ||
| 3492 | ;; ((eq type 'archive1) | ||
| 3493 | ;; (todos-set-count 'archived 1 cat) | ||
| 3494 | ;; (todos-set-count 'done -1 cat)) | ||
| 3495 | ;; ((eq type 'archive) | ||
| 3496 | ;; (if (member (buffer-file-name) (funcall todos-files-function t)) | ||
| 3497 | ;; ;; In Archive file augment done count with cat's previous | ||
| 3498 | ;; ;; done count, | ||
| 3499 | ;; (todos-set-count 'done (todos-get-count 'done cat) cat) | ||
| 3500 | ;; ;; In Todos file augment archive count with cat's previous | ||
| 3501 | ;; ;; done count, and make the latter zero. | ||
| 3502 | ;; (todos-set-count 'archived (todos-get-count 'done cat) cat) | ||
| 3503 | ;; (todos-set-count 'done (- (todos-get-count 'done cat)) cat))) | ||
| 3504 | ;; ((eq type 'merge) | ||
| 3505 | ;; ;; Augment todo and done counts of cat by those of cat2. | ||
| 3506 | ;; (todos-set-count 'todo (todos-get-count 'todo cat2) cat) | ||
| 3507 | ;; (todos-set-count 'done (todos-get-count 'done cat2) cat))) | ||
| 3508 | ;; (todos-update-categories-sexp))) | ||
| 3509 | |||
| 3510 | (defun todos-set-categories () | ||
| 3511 | "Set `todos-categories' from the sexp at the top of the file." | ||
| 3512 | ;; New archive files created by `todos-move-category' are empty, which would | ||
| 3513 | ;; make the sexp test fail and raise an error, so in this case we skip it. | ||
| 3514 | (unless (zerop (buffer-size)) | ||
| 3515 | (save-excursion | ||
| 3516 | (save-restriction | ||
| 3517 | (widen) | ||
| 3518 | (goto-char (point-min)) | ||
| 3519 | ;; todos-truncate-categories-list needs non-nil todos-categories. | ||
| 3520 | (setq todos-categories-full | ||
| 3521 | (if (looking-at "\(\(\"") | ||
| 3522 | (read (buffer-substring-no-properties | ||
| 3523 | (line-beginning-position) | ||
| 3524 | (line-end-position))) | ||
| 3525 | (error "Invalid or missing todos-categories sexp")) | ||
| 3526 | todos-categories todos-categories-full))) | ||
| 3527 | (if (and todos-ignore-archived-categories | ||
| 3528 | (eq major-mode 'todos-mode)) | ||
| 3529 | (todos-truncate-categories-list) | ||
| 3530 | todos-categories-full))) | ||
| 3531 | |||
| 3532 | ;; FIXME: currently unused -- make this a command to rebuild a corrupted | ||
| 3533 | ;; todos-cats sexp ? | ||
| 3534 | (defun todos-make-categories-list (&optional force) | ||
| 3535 | "Return an alist of Todos categories and their item counts. | ||
| 3536 | With non-nil argument FORCE parse the entire file to build the | ||
| 3537 | list; otherwise, get the value by reading the sexp at the top of | ||
| 3538 | the file." | ||
| 3539 | (setq todos-categories nil) | ||
| 3540 | (save-excursion | ||
| 3541 | (save-restriction | ||
| 3542 | (widen) | ||
| 3543 | (goto-char (point-min)) | ||
| 3544 | (let (counts cat archive) | ||
| 3545 | ;; FIXME: can todos-archives be too old here? | ||
| 3546 | (unless (member buffer-file-name (funcall todos-files-function t)) | ||
| 3547 | (setq archive (concat (file-name-sans-extension | ||
| 3548 | todos-current-todos-file) ".toda"))) | ||
| 3549 | (while (not (eobp)) | ||
| 3550 | (cond ((looking-at (concat (regexp-quote todos-category-beg) | ||
| 3551 | "\\(.*\\)\n")) | ||
| 3552 | (setq cat (match-string-no-properties 1)) | ||
| 3553 | ;; Counts for each category: [todo diary done archive] | ||
| 3554 | (setq counts (make-vector 4 0)) | ||
| 3555 | (setq todos-categories | ||
| 3556 | (append todos-categories (list (cons cat counts)))) | ||
| 3557 | ;; todos-archives may be too old here (e.g. during | ||
| 3558 | ;; todos-move-category). | ||
| 3559 | (when (member archive (funcall todos-files-function t)) | ||
| 3560 | (with-current-buffer (find-file-noselect archive) | ||
| 3561 | (widen) | ||
| 3562 | (goto-char (point-min)) | ||
| 3563 | (when (re-search-forward | ||
| 3564 | (concat (regexp-quote todos-category-beg) cat) | ||
| 3565 | (point-max) t) | ||
| 3566 | (forward-line) | ||
| 3567 | (while (not (or (looking-at | ||
| 3568 | (concat | ||
| 3569 | (regexp-quote todos-category-beg) | ||
| 3570 | "\\(.*\\)\n")) | ||
| 3571 | (eobp))) | ||
| 3572 | (when (looking-at todos-done-string-start) | ||
| 3573 | (todos-set-count 'archived 1 cat)) | ||
| 3574 | (forward-line)))))) | ||
| 3575 | ((looking-at todos-done-string-start) | ||
| 3576 | (todos-set-count 'done 1 cat)) | ||
| 3577 | ((looking-at (concat "^\\(" | ||
| 3578 | (regexp-quote diary-nonmarking-symbol) | ||
| 3579 | "\\)?" todos-date-pattern)) | ||
| 3580 | (todos-set-count 'diary 1 cat) | ||
| 3581 | (todos-set-count 'todo 1 cat)) | ||
| 3582 | ((looking-at (concat todos-date-string-start todos-date-pattern)) | ||
| 3583 | (todos-set-count 'todo 1 cat)) | ||
| 3584 | ;; If first line is todos-categories list, use it and end loop | ||
| 3585 | ;; unless forced by non-nil parameter `force' to scan whole file. | ||
| 3586 | ((bobp) | ||
| 3587 | (unless force | ||
| 3588 | (setq todos-categories (read (buffer-substring-no-properties | ||
| 3589 | (line-beginning-position) | ||
| 3590 | (line-end-position)))) | ||
| 3591 | (goto-char (1- (point-max)))))) | ||
| 3592 | (forward-line))))) | ||
| 3593 | todos-categories) | ||
| 3594 | |||
| 3595 | (defun todos-truncate-categories-list () | ||
| 3596 | "Return a truncated alist of Todos categories plus item counts. | ||
| 3597 | Categories containing only archived items are omitted. This list | ||
| 3598 | is used in Todos mode when `todos-ignore-archived-categories' is | ||
| 3599 | non-nil." | ||
| 3600 | (let (cats) | ||
| 3601 | (dolist (catcons todos-categories-full cats) | ||
| 3602 | (let ((cat (car catcons))) | ||
| 3603 | (setq cats | ||
| 3604 | (append cats | ||
| 3605 | (unless (and (zerop (todos-get-count 'todo cat)) | ||
| 3606 | (zerop (todos-get-count 'done cat)) | ||
| 3607 | (not (zerop (todos-get-count 'archived cat)))) | ||
| 3608 | (list catcons)))))))) | ||
| 3609 | |||
| 3610 | (defun todos-update-categories-sexp () | ||
| 3611 | "Update the `todos-categories' sexp at the top of the file." | ||
| 3612 | (let (buffer-read-only) | ||
| 3613 | (save-excursion | ||
| 3614 | (save-restriction | ||
| 3615 | (widen) | ||
| 3616 | (goto-char (point-min)) | ||
| 3617 | (if (looking-at (concat "^" (regexp-quote todos-category-beg))) | ||
| 3618 | (progn (newline) (goto-char (point-min))) | ||
| 3619 | ;; With empty buffer (e.g. with new archive in | ||
| 3620 | ;; `todos-move-category') `kill-line' signals end of buffer. | ||
| 3621 | (kill-region (line-beginning-position) (line-end-position))) | ||
| 3622 | ;; FIXME | ||
| 3623 | ;; (prin1 todos-categories (current-buffer)))))) | ||
| 3624 | (prin1 todos-categories-full (current-buffer)))))) | ||
| 3625 | |||
| 3626 | (defun todos-read-file-name (prompt &optional archive mustmatch) | ||
| 3627 | "Choose and return the name of a Todos file, prompting with PROMPT. | ||
| 3628 | Show completions with TAB or SPC; the names are shown in short | ||
| 3629 | form but the absolute truename is returned. With non-nil ARCHIVE | ||
| 3630 | return the absolute truename of a Todos archive file. With non-nil | ||
| 3631 | MUSTMATCH the name of an existing file must be chosen; | ||
| 3632 | otherwise, a new file name is allowed." ;FIXME: is this possible? | ||
| 3633 | (unless (file-exists-p todos-files-directory) | ||
| 3634 | (make-directory todos-files-directory)) | ||
| 3635 | (let* ((completion-ignore-case t) | ||
| 3636 | (files (mapcar 'file-name-sans-extension | ||
| 3637 | (directory-files todos-files-directory nil | ||
| 3638 | (if archive "\.toda$" "\.todo$")))) | ||
| 3639 | (file (concat todos-files-directory | ||
| 3640 | (completing-read prompt files nil mustmatch) | ||
| 3641 | (if archive ".toda" ".todo")))) | ||
| 3642 | (file-truename file))) | ||
| 3643 | |||
| 3644 | (defun todos-read-category (prompt &optional mustmatch) | ||
| 3645 | "Choose and return a category name, prompting with PROMPT. | ||
| 3646 | Show completions with TAB or SPC. With non-nil MUSTMATCH the | ||
| 3647 | name must be that of an existing category; otherwise, a new | ||
| 3648 | category name is allowed, after checking its validity." | ||
| 3649 | ;; Allow SPC to insert spaces, for adding new category names. | ||
| 3650 | (let ((map minibuffer-local-completion-map)) | ||
| 3651 | (define-key map " " nil) | ||
| 3652 | ;; Make a copy of todos-categories in case history-delete-duplicates is | ||
| 3653 | ;; non-nil, which makes completing-read alter todos-categories. | ||
| 3654 | (let* ((categories (copy-sequence todos-categories)) | ||
| 3655 | (history (cons 'todos-categories (1+ todos-category-number))) | ||
| 3656 | (completion-ignore-case todos-completion-ignore-case) | ||
| 3657 | (category (completing-read prompt todos-categories nil | ||
| 3658 | mustmatch nil history | ||
| 3659 | (if todos-categories | ||
| 3660 | (todos-current-category) | ||
| 3661 | ;; Trigger prompt for initial category | ||
| 3662 | "")))) | ||
| 3663 | ;; FIXME: let "" return todos-current-category | ||
| 3664 | (unless mustmatch | ||
| 3665 | (when (and (not (assoc category categories)) | ||
| 3666 | (y-or-n-p (format (concat "There is no category \"%s\" in " | ||
| 3667 | "this file; add it? ") category))) | ||
| 3668 | (todos-validate-category-name category) | ||
| 3669 | (todos-add-category category))) | ||
| 3670 | ;; Restore the original value of todos-categories. | ||
| 3671 | (setq todos-categories categories) | ||
| 3672 | category))) | ||
| 3673 | |||
| 3674 | (defun todos-validate-category-name (cat) | ||
| 3675 | "Check new category name CAT and when valid return it." | ||
| 3676 | (let (prompt) | ||
| 3677 | (while | ||
| 3678 | (and (cond ((string= "" cat) | ||
| 3679 | ;; (if todos-categories | ||
| 3680 | ;; (setq prompt "Enter a non-empty category name: ") | ||
| 3681 | ;; Prompt for initial category of a new Todos file. | ||
| 3682 | (setq prompt (concat "Initial category name [" | ||
| 3683 | todos-initial-category "]: ")));) | ||
| 3684 | ((string-match "\\`\\s-+\\'" cat) | ||
| 3685 | (setq prompt | ||
| 3686 | "Enter a category name that is not only white space: ")) | ||
| 3687 | ;; FIXME: add completion | ||
| 3688 | ((assoc cat todos-categories) | ||
| 3689 | (setq prompt "Enter a non-existing category name: "))) | ||
| 3690 | (setq cat (if todos-categories | ||
| 3691 | (read-from-minibuffer prompt) | ||
| 3692 | ;; Offer default initial category name. | ||
| 3693 | (prin1-to-string | ||
| 3694 | (read-from-minibuffer prompt nil nil t nil | ||
| 3695 | (list todos-initial-category)))))))) | ||
| 3696 | cat) | ||
| 3697 | |||
| 3698 | ;; (defun todos-read-category (prompt) | ||
| 3699 | ;; "Prompt with PROMPT for an existing category name and return it. | ||
| 3700 | ;; Show completions with TAB or SPC." | ||
| 3701 | ;; ;; Make a copy of todos-categories in case history-delete-duplicates is | ||
| 3702 | ;; ;; non-nil, which makes completing-read alter todos-categories. | ||
| 3703 | ;; (let* ((categories (copy-sequence todos-categories)) | ||
| 3704 | ;; (history (cons 'todos-categories (1+ todos-category-number))) | ||
| 3705 | ;; (completion-ignore-case todos-completion-ignore-case) | ||
| 3706 | ;; (category (completing-read prompt todos-categories nil | ||
| 3707 | ;; mustmatch nil history))) | ||
| 3708 | ;; (setq category (completing-read prompt todos-categories nil t)) | ||
| 3709 | ;; ;; Restore the original value of todos-categories. | ||
| 3710 | ;; (setq todos-categories categories) | ||
| 3711 | ;; category)) | ||
| 3712 | |||
| 3713 | ;; (defun todos-new-category-name (prompt) | ||
| 3714 | ;; "Prompt with PROMPT for a new category name and return it." | ||
| 3715 | ;; (let ((map minibuffer-local-completion-map) | ||
| 3716 | ;; prompt-n) | ||
| 3717 | ;; ;; Allow SPC to insert spaces, for adding new category names. | ||
| 3718 | ;; (define-key map " " nil) | ||
| 3719 | ;; (while | ||
| 3720 | ;; ;; Validate entered category name. | ||
| 3721 | ;; (and (cond ((string= "" cat) | ||
| 3722 | ;; (setq prompt-n | ||
| 3723 | ;; (if todos-categories | ||
| 3724 | ;; "Enter a non-empty category name: " | ||
| 3725 | ;; ;; Prompt for initial category of a new Todos file. | ||
| 3726 | ;; (concat "Initial category name [" | ||
| 3727 | ;; todos-initial-category "]: ")))) | ||
| 3728 | ;; ((string-match "\\`\\s-+\\'" cat) | ||
| 3729 | ;; (setq prompt-n | ||
| 3730 | ;; "Enter a category name that is not only white space: ")) | ||
| 3731 | ;; ((assoc cat todos-categories) | ||
| 3732 | ;; (setq prompt-n "Enter a non-existing category name: "))) | ||
| 3733 | ;; (setq cat (if todos-categories | ||
| 3734 | ;; (read-from-minibuffer prompt) | ||
| 3735 | ;; ;; Offer default initial category name. | ||
| 3736 | ;; (prin1-to-string | ||
| 3737 | ;; (read-from-minibuffer | ||
| 3738 | ;; (or prompt prompt-n) nil nil t nil | ||
| 3739 | ;; (list todos-initial-category)))))) | ||
| 3740 | ;; (setq prompt nil))) | ||
| 3741 | ;; cat) | ||
| 3742 | |||
| 3743 | ;; ;; Adapted from calendar-read-date and calendar-date-string. | ||
| 3744 | (defun todos-read-date () | ||
| 3745 | "Prompt for Gregorian date and return it in the current format. | ||
| 3746 | Also accepts `*' as an unspecified month, day, or year." | ||
| 3747 | (let* ((year (calendar-read | ||
| 3748 | ;; FIXME: maybe better like monthname with RET for current month | ||
| 3749 | "Year (>0 or * for any year): " | ||
| 3750 | (lambda (x) (or (eq x '*) (> x 0))) | ||
| 3751 | (number-to-string (calendar-extract-year | ||
| 3752 | (calendar-current-date))))) | ||
| 3753 | (month-array (vconcat calendar-month-name-array (vector "*"))) | ||
| 3754 | (abbrevs (vconcat calendar-month-abbrev-array (vector "*"))) | ||
| 3755 | (completion-ignore-case t) | ||
| 3756 | (monthname (completing-read | ||
| 3757 | "Month name (RET for current month, * for any month): " | ||
| 3758 | (mapcar 'list (append month-array nil)) | ||
| 3759 | nil t nil nil | ||
| 3760 | (calendar-month-name (calendar-extract-month | ||
| 3761 | (calendar-current-date)) t))) | ||
| 3762 | (month (cdr (assoc-string | ||
| 3763 | monthname (calendar-make-alist month-array nil nil | ||
| 3764 | abbrevs)))) | ||
| 3765 | (last (if (= month 13) | ||
| 3766 | 31 ; FIXME: what about shorter months? | ||
| 3767 | (let ((yr (if (eq year '*) | ||
| 3768 | 1999 ; FIXME: no Feb. 29 | ||
| 3769 | year))) | ||
| 3770 | (calendar-last-day-of-month month yr)))) | ||
| 3771 | day dayname) | ||
| 3772 | (while (if (numberp day) (or (< day 0) (< last day)) (not (eq day '*))) | ||
| 3773 | (setq day (read-from-minibuffer | ||
| 3774 | (format "Day (1-%d or RET for today or * for any day): " last) | ||
| 3775 | nil nil t nil | ||
| 3776 | (number-to-string | ||
| 3777 | (calendar-extract-day (calendar-current-date)))))) | ||
| 3778 | (setq year (if (eq year '*) (symbol-name '*) (number-to-string year))) | ||
| 3779 | (setq day (if (eq day '*) (symbol-name '*) (number-to-string day))) | ||
| 3780 | ;; FIXME: make abbreviation customizable | ||
| 3781 | (setq monthname | ||
| 3782 | (or (and (= month 13) "*") | ||
| 3783 | (calendar-month-name (calendar-extract-month (list month day year)) | ||
| 3784 | t))) | ||
| 3785 | (mapconcat 'eval calendar-date-display-form ""))) | ||
| 3786 | |||
| 3787 | (defun todos-read-dayname () | ||
| 3788 | "Choose name of a day of the week with completion and return it." | ||
| 3789 | (let ((completion-ignore-case t)) | ||
| 3790 | (completing-read "Enter a day name: " | ||
| 3791 | (append calendar-day-name-array nil) | ||
| 3792 | nil t))) | ||
| 3793 | |||
| 3794 | (defun todos-read-time () | ||
| 3795 | "Prompt for and return a valid clock time as a string. | ||
| 3796 | Valid time strings are those matching `diary-time-regexp'." | ||
| 3797 | (let (valid answer) | ||
| 3798 | (while (not valid) | ||
| 3799 | (setq answer (read-from-minibuffer | ||
| 3800 | "Enter a clock time (or return for none): ")) | ||
| 3801 | (when (or (string= "" answer) | ||
| 3802 | (string-match diary-time-regexp answer)) | ||
| 3803 | (setq valid t))) | ||
| 3804 | answer)) | ||
| 3805 | |||
| 3806 | ;;; Sorting and display routines for todos-categories-mode. | ||
| 3807 | |||
| 3808 | (defun todos-display-categories (&optional sortkey) | ||
| 3809 | "Display a table of the current file's categories and item counts. | ||
| 3810 | |||
| 3811 | In the initial display the categories are numbered, indicating | ||
| 3812 | their current order for navigating by \\[todos-forward-category] | ||
| 3813 | and \\[todos-backward-category]. You can persistantly change the | ||
| 3814 | order of the category at point by typing \\[todos-raise-category] | ||
| 3815 | or \\[todos-lower-category]. | ||
| 3816 | |||
| 3817 | The labels above the category names and item counts are buttons, | ||
| 3818 | and clicking these changes the display: sorted by category name | ||
| 3819 | or by the respective item counts (alternately descending or | ||
| 3820 | ascending). In these displays the categories are not numbered | ||
| 3821 | and \\[todos-raise-category] and \\[todos-lower-category] are | ||
| 3822 | disabled. (Programmatically, the sorting is triggered by passing | ||
| 3823 | a non-nil SORTKEY argument.) | ||
| 3824 | |||
| 3825 | In addition, the lines with the category names and item counts | ||
| 3826 | are buttonized, and pressing one of these button jumps to the | ||
| 3827 | category in Todos mode (or Todos Archive mode, for categories | ||
| 3828 | containing only archived items, provided user option | ||
| 3829 | `todos-ignore-archived-categories' is non-nil. These categories | ||
| 3830 | are shown in `todos-archived-only' face." | ||
| 3831 | (interactive) | ||
| 3832 | (unless (eq major-mode 'todos-categories-mode) | ||
| 3833 | (setq todos-global-current-todos-file (or todos-current-todos-file | ||
| 3834 | todos-default-todos-file))) | ||
| 3835 | (let* ((cats0 (if (and todos-ignore-archived-categories | ||
| 3836 | (not (eq major-mode 'todos-categories-mode))) | ||
| 3837 | todos-categories-full | ||
| 3838 | todos-categories)) | ||
| 3839 | (cats (todos-sort cats0 sortkey)) | ||
| 3840 | (archive (member todos-current-todos-file todos-archives)) | ||
| 3841 | ;; `num' is used by todos-insert-category-line. | ||
| 3842 | (num 0)) | ||
| 3843 | (set-window-buffer (selected-window) | ||
| 3844 | (set-buffer (get-buffer-create todos-categories-buffer))) | ||
| 3845 | (let (buffer-read-only) | ||
| 3846 | (erase-buffer) | ||
| 3847 | (kill-all-local-variables) | ||
| 3848 | (todos-categories-mode) | ||
| 3849 | ;; FIXME: add usage tips? | ||
| 3850 | (insert (format "Category counts for Todos file \"%s\"." | ||
| 3851 | (file-name-sans-extension | ||
| 3852 | (file-name-nondirectory todos-current-todos-file)))) | ||
| 3853 | (newline 2) | ||
| 3854 | ;; Make space for the column of category numbers. | ||
| 3855 | (insert (make-string (+ 4 (length todos-categories-number-separator)) 32)) | ||
| 3856 | ;; Add the category and item count buttons (if this is the list of | ||
| 3857 | ;; categories in an archive, show only done item counts). | ||
| 3858 | (save-excursion | ||
| 3859 | (todos-insert-sort-button todos-categories-category-label) | ||
| 3860 | (if (member todos-current-todos-file todos-archives) | ||
| 3861 | (insert (concat (make-string 6 32) | ||
| 3862 | (format "%s" todos-categories-archived-label))) | ||
| 3863 | (insert (make-string 3 32)) | ||
| 3864 | (todos-insert-sort-button todos-categories-todo-label) | ||
| 3865 | (insert (make-string 2 32)) | ||
| 3866 | (todos-insert-sort-button todos-categories-diary-label) | ||
| 3867 | (insert (make-string 2 32)) | ||
| 3868 | (todos-insert-sort-button todos-categories-done-label) | ||
| 3869 | (insert (make-string 2 32)) | ||
| 3870 | (todos-insert-sort-button todos-categories-archived-label)) | ||
| 3871 | (newline 2) | ||
| 3872 | ;; Fill in the table with buttonized lines, each showing a category and | ||
| 3873 | ;; its item counts. | ||
| 3874 | (mapc (lambda (cat) (todos-insert-category-line cat sortkey)) | ||
| 3875 | (mapcar 'car cats)) | ||
| 3876 | (newline) | ||
| 3877 | ;; Add a line showing item count totals. | ||
| 3878 | (insert (make-string (+ 4 (length todos-categories-number-separator)) 32) | ||
| 3879 | (todos-padded-string todos-categories-totals-label) | ||
| 3880 | (mapconcat | ||
| 3881 | (lambda (elt) | ||
| 3882 | (concat | ||
| 3883 | (make-string (1+ (/ (length (car elt)) 2)) 32) | ||
| 3884 | (format "%3d" (nth (cdr elt) (todos-total-item-counts))) | ||
| 3885 | ;; Add an extra space if label length is odd (using | ||
| 3886 | ;; definition of oddp from cl.el). | ||
| 3887 | (if (eq (logand (length (car elt)) 1) 1) " "))) | ||
| 3888 | (if archive | ||
| 3889 | (list (cons todos-categories-done-label 2)) | ||
| 3890 | (list (cons todos-categories-todo-label 0) | ||
| 3891 | (cons todos-categories-diary-label 1) | ||
| 3892 | (cons todos-categories-done-label 2) | ||
| 3893 | (cons todos-categories-archived-label 3))) | ||
| 3894 | "")))) | ||
| 3895 | (setq buffer-read-only t))) | ||
| 3896 | |||
| 3897 | ;; ;; FIXME: make this toggle with todos-display-categories | ||
| 3898 | ;; (defun todos-display-categories-alphabetically () | ||
| 3899 | ;; "" | ||
| 3900 | ;; (interactive) | ||
| 3901 | ;; (todos-display-sorted 'alpha)) | ||
| 3902 | |||
| 3903 | ;; ;; FIXME: provide key bindings for these or delete them | ||
| 3904 | ;; (defun todos-display-categories-sorted-by-todo () | ||
| 3905 | ;; "" | ||
| 3906 | ;; (interactive) | ||
| 3907 | ;; (todos-display-sorted 'todo)) | ||
| 3908 | |||
| 3909 | ;; (defun todos-display-categories-sorted-by-diary () | ||
| 3910 | ;; "" | ||
| 3911 | ;; (interactive) | ||
| 3912 | ;; (todos-display-sorted 'diary)) | ||
| 3913 | |||
| 3914 | ;; (defun todos-display-categories-sorted-by-done () | ||
| 3915 | ;; "" | ||
| 3916 | ;; (interactive) | ||
| 3917 | ;; (todos-display-sorted 'done)) | ||
| 3918 | |||
| 3919 | ;; (defun todos-display-categories-sorted-by-archived () | ||
| 3920 | ;; "" | ||
| 3921 | ;; (interactive) | ||
| 3922 | ;; (todos-display-sorted 'archived)) | ||
| 3923 | |||
| 3924 | (defun todos-longest-category-name-length (categories) | ||
| 3925 | "Return the length of the longest name in list CATEGORIES." | ||
| 3926 | (let ((longest 0)) | ||
| 3927 | (dolist (c categories longest) | ||
| 3928 | (setq longest (max longest (length c)))))) | ||
| 3929 | |||
| 3930 | (defun todos-padded-string (str) | ||
| 3931 | "Return string STR padded with spaces. | ||
| 3932 | The placement of the padding is determined by the value of user | ||
| 3933 | option `todos-categories-align'." | ||
| 3934 | (let* ((categories (mapcar 'car todos-categories)) | ||
| 3935 | (len (max (todos-longest-category-name-length categories) | ||
| 3936 | (length todos-categories-category-label))) | ||
| 3937 | (strlen (length str)) | ||
| 3938 | (strlen-odd (eq (logand strlen 1) 1)) ; oddp from cl.el | ||
| 3939 | (padding (max 0 (/ (- len strlen) 2))) | ||
| 3940 | (padding-left (cond ((eq todos-categories-align 'left) 0) | ||
| 3941 | ((eq todos-categories-align 'center) padding) | ||
| 3942 | ((eq todos-categories-align 'right) | ||
| 3943 | (if strlen-odd (1+ (* padding 2)) (* padding 2))))) | ||
| 3944 | (padding-right (cond ((eq todos-categories-align 'left) | ||
| 3945 | (if strlen-odd (1+ (* padding 2)) (* padding 2))) | ||
| 3946 | ((eq todos-categories-align 'center) | ||
| 3947 | (if strlen-odd (1+ padding) padding)) | ||
| 3948 | ((eq todos-categories-align 'right) 0)))) | ||
| 3949 | (concat (make-string padding-left 32) str (make-string padding-right 32)))) | ||
| 3950 | |||
| 3951 | (defvar todos-descending-counts nil | ||
| 3952 | "List of keys for category counts sorted in descending order.") | ||
| 3953 | |||
| 3954 | (defun todos-sort (list &optional key) | ||
| 3955 | "Return a copy of LIST, possibly sorted according to KEY." | ||
| 3956 | (let* ((l (copy-sequence list)) | ||
| 3957 | (fn (if (eq key 'alpha) | ||
| 3958 | (lambda (x) (upcase x)) ; Alphabetize case insensitively. | ||
| 3959 | (lambda (x) (todos-get-count key x)))) | ||
| 3960 | (descending (member key todos-descending-counts)) | ||
| 3961 | (cmp (if (eq key 'alpha) | ||
| 3962 | 'string< | ||
| 3963 | (if descending '< '>))) | ||
| 3964 | (pred (lambda (s1 s2) (let ((t1 (funcall fn (car s1))) | ||
| 3965 | (t2 (funcall fn (car s2)))) | ||
| 3966 | (funcall cmp t1 t2))))) | ||
| 3967 | (when key | ||
| 3968 | (setq l (sort l pred)) | ||
| 3969 | (if descending | ||
| 3970 | (setq todos-descending-counts | ||
| 3971 | (delete key todos-descending-counts)) | ||
| 3972 | (push key todos-descending-counts))) | ||
| 3973 | l)) | ||
| 3974 | |||
| 3975 | (defun todos-display-sorted (type) | ||
| 3976 | "Keep point on the TYPE count sorting button just clicked." | ||
| 3977 | (let ((opoint (point))) | ||
| 3978 | (todos-display-categories type) | ||
| 3979 | (goto-char opoint))) | ||
| 3980 | |||
| 3981 | (defun todos-label-to-key (label) | ||
| 3982 | "Return symbol for sort key associated with LABEL." | ||
| 3983 | (let (key) | ||
| 3984 | (cond ((string= label todos-categories-category-label) | ||
| 3985 | (setq key 'alpha)) | ||
| 3986 | ((string= label todos-categories-todo-label) | ||
| 3987 | (setq key 'todo)) | ||
| 3988 | ((string= label todos-categories-diary-label) | ||
| 3989 | (setq key 'diary)) | ||
| 3990 | ((string= label todos-categories-done-label) | ||
| 3991 | (setq key 'done)) | ||
| 3992 | ((string= label todos-categories-archived-label) | ||
| 3993 | (setq key 'archived))) | ||
| 3994 | key)) | ||
| 3995 | |||
| 3996 | (defun todos-insert-sort-button (label) | ||
| 3997 | "Insert button for displaying categories sorted by item counts. | ||
| 3998 | LABEL determines which type of count is sorted." | ||
| 3999 | (setq str (if (string= label todos-categories-category-label) | ||
| 4000 | (todos-padded-string label) | ||
| 4001 | label)) | ||
| 4002 | (setq beg (point)) | ||
| 4003 | (setq end (+ beg (length str))) | ||
| 4004 | (insert-button str 'face nil | ||
| 4005 | 'action | ||
| 4006 | `(lambda (button) | ||
| 4007 | (let ((key (todos-label-to-key ,label))) | ||
| 4008 | (if (and (member key todos-descending-counts) | ||
| 4009 | (eq key 'alpha)) | ||
| 4010 | (progn | ||
| 4011 | (todos-display-categories) | ||
| 4012 | (setq todos-descending-counts | ||
| 4013 | (delete key todos-descending-counts))) | ||
| 4014 | (todos-display-sorted key))))) | ||
| 4015 | (setq ovl (make-overlay beg end)) | ||
| 4016 | (overlay-put ovl 'face 'todos-button)) | ||
| 4017 | |||
| 4018 | (defun todos-total-item-counts () | ||
| 4019 | "Return a list of total item counts for the current file." | ||
| 4020 | (mapcar (lambda (i) (apply '+ (mapcar (lambda (l) (aref l i)) | ||
| 4021 | (mapcar 'cdr todos-categories)))) | ||
| 4022 | (list 0 1 2 3))) | ||
| 4023 | |||
| 4024 | (defun todos-insert-category-line (cat &optional nonum) | ||
| 4025 | "Insert button displaying category CAT's name and item counts. | ||
| 4026 | With non-nil argument NONUM show only these; otherwise, insert a | ||
| 4027 | number in front of the button indicating the category's priority. | ||
| 4028 | The number and the category name are separated by the string | ||
| 4029 | which is the value of the user option | ||
| 4030 | `todos-categories-number-separator'." | ||
| 4031 | (let* ((archive (member todos-current-todos-file todos-archives)) | ||
| 4032 | (str (todos-padded-string cat)) | ||
| 4033 | (opoint (point))) | ||
| 4034 | ;; num is declared in caller. | ||
| 4035 | (setq num (1+ num)) | ||
| 4036 | (insert-button | ||
| 4037 | (concat (if nonum | ||
| 4038 | (make-string (+ 4 (length todos-categories-number-separator)) | ||
| 4039 | 32) | ||
| 4040 | (format " %3d%s" num todos-categories-number-separator)) | ||
| 4041 | str | ||
| 4042 | (mapconcat (lambda (elt) | ||
| 4043 | (concat | ||
| 4044 | (make-string (1+ (/ (length (car elt)) 2)) 32) ; label | ||
| 4045 | (format "%3d" (todos-get-count (cdr elt) cat)) ; count | ||
| 4046 | ;; Add an extra space if label length is odd | ||
| 4047 | ;; (using def of oddp from cl.el). | ||
| 4048 | (if (eq (logand (length (car elt)) 1) 1) " "))) | ||
| 4049 | (if archive | ||
| 4050 | (list (cons todos-categories-done-label 'done)) | ||
| 4051 | (list (cons todos-categories-todo-label 'todo) | ||
| 4052 | (cons todos-categories-diary-label 'diary) | ||
| 4053 | (cons todos-categories-done-label 'done) | ||
| 4054 | (cons todos-categories-archived-label | ||
| 4055 | 'archived))) | ||
| 4056 | "")) | ||
| 4057 | 'face (if (and todos-ignore-archived-categories | ||
| 4058 | (zerop (todos-get-count 'todo cat)) | ||
| 4059 | (zerop (todos-get-count 'done cat)) | ||
| 4060 | (not (zerop (todos-get-count 'archived cat)))) | ||
| 4061 | 'todos-archived-only | ||
| 4062 | nil) | ||
| 4063 | 'action `(lambda (button) (let ((buf (current-buffer))) | ||
| 4064 | (todos-jump-to-category ,cat) | ||
| 4065 | (kill-buffer buf)))) | ||
| 4066 | ;; Highlight the sorted count column. | ||
| 4067 | (let* ((beg (+ opoint 6 (length str))) | ||
| 4068 | end ovl) | ||
| 4069 | (cond ((eq nonum 'todo) | ||
| 4070 | (setq beg (+ beg 1 (/ (length todos-categories-todo-label) 2)))) | ||
| 4071 | ((eq nonum 'diary) | ||
| 4072 | (setq beg (+ beg 1 (length todos-categories-todo-label) | ||
| 4073 | 2 (/ (length todos-categories-diary-label) 2)))) | ||
| 4074 | ((eq nonum 'done) | ||
| 4075 | (setq beg (+ beg 1 (length todos-categories-todo-label) | ||
| 4076 | 2 (length todos-categories-diary-label) | ||
| 4077 | 2 (/ (length todos-categories-done-label) 2)))) | ||
| 4078 | ((eq nonum 'archived) | ||
| 4079 | (setq beg (+ beg 1 (length todos-categories-todo-label) | ||
| 4080 | 2 (length todos-categories-diary-label) | ||
| 4081 | 2 (length todos-categories-done-label) | ||
| 4082 | 2 (/ (length todos-categories-archived-label) 2))))) | ||
| 4083 | (unless (= beg (+ opoint 6 (length str))) | ||
| 4084 | (setq end (+ beg 4)) | ||
| 4085 | (setq ovl (make-overlay beg end)) | ||
| 4086 | (overlay-put ovl 'face 'todos-sorted-column))) | ||
| 4087 | (newline))) | ||
| 4088 | |||
| 4089 | (provide 'todos) | 4827 | (provide 'todos) |
| 4090 | 4828 | ||
| 4091 | ;;; todos.el ends here | 4829 | ;;; todos.el ends here |
| 4092 | 4830 | ||
| 4831 | ;; --------------------------------------------------------------------------- | ||
| 4093 | ;;; necessitated adaptations to diary-lib.el | 4832 | ;;; necessitated adaptations to diary-lib.el |
| 4094 | 4833 | ||
| 4095 | ;; (defun diary-goto-entry (button) | 4834 | ;; (defun diary-goto-entry (button) |