* calendar/todos.el: Further significant code rearrangement;

further comment revision.
(todos-mode-display): New defgroup.
(todos-prefix, todos-number-priorities)
(todos-done-separator-string, todos-done-string)
(todos-comment-string, todos-show-with-done)
(todos-mode-line-function, todos-skip-archived-categories)
(todos-highlight-item, todos-wrap-lines)
(todos-line-wrapping-function): Use it.
(todos-item-insertion): New defgroup.
(todos-include-in-diary, todos-diary-nonmarking)
(todos-nondiary-marker, todos-always-add-time-string)
(todos-use-only-highlighted-region): Use it.
(todos-forward-button, todos-backward-button): New commands.
(todos-categories-mode-map): Use them, replacing forward-button
and backward-button.
(todos-merge-category): Fix and improve implementation; handle
archived items.
(todos-insert-item, todos-set-date-from-calendar): Handle setting
date by calling todos-insert-item-from-calendar.
(todos-delete-item): Fix overlay handling.
(todos-move-item): Highlight item to be moved.
(todos-item-undo): Handle marked items.
(todos-insert-item-from-calendar): Rewrite using
todos-date-from-calendar.
This commit is contained in:
Stephen Berman 2012-06-24 18:31:14 +01:00
parent 616ffa8b81
commit 18aef8a33e
2 changed files with 482 additions and 296 deletions

View file

@ -1,3 +1,31 @@
2012-09-23 Stephen Berman <stephen.berman@gmx.net>
* calendar/todos.el: Further significant code rearrangement;
further comment revision.
(todos-mode-display): New defgroup.
(todos-prefix, todos-number-priorities)
(todos-done-separator-string, todos-done-string)
(todos-comment-string, todos-show-with-done)
(todos-mode-line-function, todos-skip-archived-categories)
(todos-highlight-item, todos-wrap-lines)
(todos-line-wrapping-function): Use it.
(todos-item-insertion): New defgroup.
(todos-include-in-diary, todos-diary-nonmarking)
(todos-nondiary-marker, todos-always-add-time-string)
(todos-use-only-highlighted-region): Use it.
(todos-forward-button, todos-backward-button): New commands.
(todos-categories-mode-map): Use them, replacing forward-button
and backward-button.
(todos-merge-category): Fix and improve implementation; handle
archived items.
(todos-insert-item, todos-set-date-from-calendar): Handle setting
date by calling todos-insert-item-from-calendar.
(todos-delete-item): Fix overlay handling.
(todos-move-item): Highlight item to be moved.
(todos-item-undo): Handle marked items.
(todos-insert-item-from-calendar): Rewrite using
todos-date-from-calendar.
2012-09-23 Stephen Berman <stephen.berman@gmx.net>
* calendar/todos.el: Further comment revision.

View file

@ -127,12 +127,42 @@ displayed correctly."
:type 'boolean
:group 'todos)
(defcustom todos-completion-ignore-case nil
"Non-nil means case is ignored by `todos-read-*' functions."
:type 'boolean
:group 'todos)
(defcustom todos-print-function 'ps-print-buffer-with-faces
"Function called to print buffer content; see `todos-print'."
:type 'symbol
:group 'todos)
(defcustom todos-todo-mode-date-time-regexp
(concat "\\(?1:[0-9]\\{4\\}\\)-\\(?2:[0-9]\\{2\\}\\)-"
"\\(?3:[0-9]\\{2\\}\\) \\(?4:[0-9]\\{2\\}:[0-9]\\{2\\}\\)")
"Regexp matching legacy todo-mode.el item date-time strings.
In order for `todos-convert-legacy-files' to correctly convert this
string to the current Todos format, the regexp must contain four
explicitly numbered groups (see `(elisp) Regexp Backslash'),
where group 1 matches a string for the year, group 2 a string for
the month, group 3 a string for the day and group 4 a string for
the time. The default value converts date-time strings built
using the default value of `todo-time-string-format' from
todo-mode.el."
:type 'regexp
:group 'todos)
(defgroup todos-mode-display nil
"User display options for Todos mode."
:version "24.2"
:group 'todos)
(defcustom todos-prefix ""
"String prefixed to todo items for visual distinction."
:type 'string
:initialize 'custom-initialize-default
:set 'todos-reset-prefix
:group 'todos)
:group 'todos-mode-display)
(defcustom todos-number-priorities t
"Non-nil to prefix items with consecutively increasing integers.
@ -140,7 +170,7 @@ These reflect the priorities of the items in each category."
:type 'boolean
:initialize 'custom-initialize-default
:set 'todos-reset-prefix
:group 'todos)
:group 'todos-mode-display)
(defun todos-reset-prefix (symbol value)
"The :set function for `todos-prefix' and `todos-number-priorities'."
@ -173,7 +203,7 @@ the value of `todos-done-separator'."
:type 'string
:initialize 'custom-initialize-default
:set 'todos-reset-done-separator-string
:group 'todos)
:group 'todos-mode-display)
(defun todos-reset-done-separator-string (symbol value)
"The :set function for `todos-done-separator-string'."
@ -190,7 +220,7 @@ the value of `todos-done-separator'."
:type 'string
:initialize 'custom-initialize-default
:set 'todos-reset-done-string
:group 'todos)
:group 'todos-mode-display)
(defun todos-reset-done-string (symbol value)
"The :set function for user option `todos-done-string'."
@ -220,7 +250,7 @@ the value of `todos-done-separator'."
:type 'string
:initialize 'custom-initialize-default
:set 'todos-reset-comment-string
:group 'todos)
:group 'todos-mode-display)
(defun todos-reset-comment-string (symbol value)
"The :set function for user option `todos-comment-string'."
@ -246,7 +276,7 @@ the value of `todos-done-separator'."
(defcustom todos-show-with-done nil
"Non-nil to display done items in all categories."
:type 'boolean
:group 'todos)
:group 'todos-mode-display)
(defun todos-mode-line-control (cat)
"Return a mode line control for Todos buffers.
@ -262,7 +292,7 @@ The function expects one argument holding the name of the current
Todos category. The resulting control becomes the local value of
`mode-line-buffer-identification' in each Todos buffer."
:type 'function
:group 'todos)
:group 'todos-mode-display)
(defcustom todos-skip-archived-categories nil
"Non-nil to skip categories with only archived items when browsing.
@ -275,24 +305,81 @@ mode (reached with \\[todos-display-categories]) these categories
shown in `todos-archived-only' face and clicking them in Todos
Categories mode visits the archived categories."
:type 'boolean
:group 'todos-mode-display)
(defcustom todos-highlight-item nil
"Non-nil means highlight items at point."
:type 'boolean
:initialize 'custom-initialize-default
:set 'todos-reset-highlight-item
:group 'todos-mode-display)
(defun todos-reset-highlight-item (symbol value)
"The :set function for `todos-highlight-item'."
(let ((oldvalue (symbol-value symbol))
(files (append todos-files todos-archives)))
(custom-set-default symbol value)
(when (not (equal value oldvalue))
(dolist (f files)
(let ((buf (find-buffer-visiting f)))
(when buf
(with-current-buffer buf
(require 'hl-line)
(if value
(hl-line-mode 1)
(hl-line-mode -1)))))))))
(defcustom todos-wrap-lines t
"Non-nil to wrap long lines via `todos-line-wrapping-function'."
:group 'todos-mode-display
:type 'boolean)
(defcustom todos-line-wrapping-function 'todos-wrap-and-indent
"Line wrapping function used with non-nil `todos-wrap-lines'."
:group 'todos-mode-display
:type 'function)
(defun todos-wrap-and-indent ()
"Use word wrapping on long lines and indent with a wrap prefix.
The amount of indentation is given by user option
`todos-indent-to-here'."
(set (make-local-variable 'word-wrap) t)
(set (make-local-variable 'wrap-prefix) (make-string todos-indent-to-here 32))
(unless (member '(continuation) fringe-indicator-alist)
(push '(continuation) fringe-indicator-alist)))
;; FIXME: :set function to refill items with hard newlines and to immediately
;; update wrapped prefix display
(defcustom todos-indent-to-here 6
"Number of spaces `todos-line-wrapping-function' indents to."
:type '(integer :validate
(lambda (widget)
(unless (> (widget-value widget) 0)
(widget-put widget :error
"Invalid value: must be a positive integer")
widget)))
:group 'todos)
(defcustom todos-use-only-highlighted-region t
"Non-nil to enable inserting only highlighted region as new item."
:type 'boolean
(defun todos-indent ()
"Indent from point to `todos-indent-to-here'."
(indent-to todos-indent-to-here todos-indent-to-here))
(defgroup todos-item-insertion nil
"User options for adding new todo items."
:version "24.2"
:group 'todos)
(defcustom todos-include-in-diary nil
"Non-nil to allow new Todo items to be included in the diary."
:type 'boolean
:group 'todos)
:group 'todos-item-insertion)
(defcustom todos-diary-nonmarking nil
"Non-nil to insert new Todo diary items as nonmarking by default.
This appends `diary-nonmarking-symbol' to the front of an item on
insertion provided it doesn't begin with `todos-nondiary-marker'."
:type 'boolean
:group 'todos)
:group 'todos-item-insertion)
(defcustom todos-nondiary-marker '("[" "]")
"List of strings surrounding item date to block diary inclusion.
@ -301,7 +388,7 @@ non-empty string that does not match a diary date in order to
have its intended effect. The second string is inserted after
the diary date."
:type '(list string string)
:group 'todos
:group 'todos-item-insertion
:initialize 'custom-initialize-default
:set 'todos-reset-nondiary-marker)
@ -344,89 +431,12 @@ argument, this reverses the effect of
`todos-always-add-time-string': if t, these commands omit the
current time, if nil, they include it."
:type 'boolean
:group 'todos)
:group 'todos-item-insertion)
(defcustom todos-completion-ignore-case nil
"Non-nil means case of user input in `todos-read-*' is ignored."
(defcustom todos-use-only-highlighted-region t
"Non-nil to enable inserting only highlighted region as new item."
:type 'boolean
:group 'todos)
(defcustom todos-highlight-item nil
"Non-nil means highlight items at point."
:type 'boolean
:initialize 'custom-initialize-default
:set 'todos-reset-highlight-item
:group 'todos)
(defun todos-reset-highlight-item (symbol value)
"The :set function for `todos-highlight-item'."
(let ((oldvalue (symbol-value symbol))
(files (append todos-files todos-archives)))
(custom-set-default symbol value)
(when (not (equal value oldvalue))
(dolist (f files)
(let ((buf (find-buffer-visiting f)))
(when buf
(with-current-buffer buf
(require 'hl-line)
(if value
(hl-line-mode 1)
(hl-line-mode -1)))))))))
(defcustom todos-wrap-lines t
"Non-nil to wrap long lines via `todos-line-wrapping-function'."
:group 'todos
:type 'boolean)
(defcustom todos-line-wrapping-function 'todos-wrap-and-indent
"Line wrapping function used with non-nil `todos-wrap-lines'."
:group 'todos
:type 'function)
(defun todos-wrap-and-indent ()
"Use word wrapping on long lines and indent with a wrap prefix.
The amount of indentation is given by user option
`todos-indent-to-here'."
(set (make-local-variable 'word-wrap) t)
(set (make-local-variable 'wrap-prefix) (make-string todos-indent-to-here 32))
(unless (member '(continuation) fringe-indicator-alist)
(push '(continuation) fringe-indicator-alist)))
;; FIXME: :set function to refill items with hard newlines and to immediately
;; update wrapped prefix display
(defcustom todos-indent-to-here 6
"Number of spaces `todos-line-wrapping-function' indents to."
:type '(integer :validate
(lambda (widget)
(unless (> (widget-value widget) 0)
(widget-put widget :error
"Invalid value: must be a positive integer")
widget)))
:group 'todos)
(defun todos-indent ()
"Indent from point to `todos-indent-to-here'."
(indent-to todos-indent-to-here todos-indent-to-here))
(defcustom todos-todo-mode-date-time-regexp
(concat "\\(?1:[0-9]\\{4\\}\\)-\\(?2:[0-9]\\{2\\}\\)-"
"\\(?3:[0-9]\\{2\\}\\) \\(?4:[0-9]\\{2\\}:[0-9]\\{2\\}\\)")
"Regexp matching legacy todo-mode.el item date-time strings.
In order for `todos-convert-legacy-files' to correctly convert this
string to the current Todos format, the regexp must contain four
explicitly numbered groups (see `(elisp) Regexp Backslash'),
where group 1 matches a string for the year, group 2 a string for
the month, group 3 a string for the day and group 4 a string for
the time. The default value converts date-time strings built
using the default value of `todo-time-string-format' from
todo-mode.el."
:type 'regexp
:group 'todos)
(defcustom todos-print-function 'ps-print-buffer-with-faces
"Function called to print buffer content; see `todos-print'."
:type 'symbol
:group 'todos)
:group 'todos-item-insertion)
(defgroup todos-filtered nil
"User options for Todos Filter Items mode."
@ -930,6 +940,26 @@ See `todos-display-categories-first'.")
Set by the command `todos-show-done-only' and used by
`todos-category-select'.")
(defun todos-reset-and-enable-done-separator ()
"Show resized catagory separator overlay after window size change.
Added to `window-configuration-change-hook' in `todos-mode'."
(when (= 1 (length todos-done-separator-string))
(let ((sep todos-done-separator))
(setq todos-done-separator (todos-done-separator))
(save-match-data (todos-reset-done-separator sep)))
;; FIXME: If this is called while the separator overlay is shown, the
;; separator with deleted overlay becomes visible when waiting for user
;; input and remains so. The following workaround prevents this, but it
;; also prevents widening when edebugging todos.el.
;; (save-excursion
;; (goto-char (point-min))
;; (when (re-search-forward todos-done-string-start nil t)
;; (let ((todos-show-with-done nil))
;; (todos-category-select))
;; (let ((todos-show-with-done t))
;; (todos-category-select))))
))
;; ---------------------------------------------------------------------------
;;; Global variables and helper functions
@ -1054,26 +1084,6 @@ done items are shown. Its value is determined by user option
(overlay-put new-sep 'display
todos-done-separator)))))))
(defun todos-reset-and-enable-done-separator ()
"Hook function for activating new separator overlay.
Added to `window-configuration-change-hook' in `todos-mode'."
(when (= 1 (length todos-done-separator-string))
(let ((sep todos-done-separator))
(setq todos-done-separator (todos-done-separator))
(save-match-data (todos-reset-done-separator sep)))
;; If the separator overlay is now shown, we have to hide and then show it
;; again in order to let the change in length take effect.
;; FIXME: But this breaks e.g. (widen) when edebugging. But how to
;; restrict it?
;; (save-excursion
;; (goto-char (point-min))
;; (when (re-search-forward todos-done-string-start nil t)
;; (let ((todos-show-with-done nil))
;; (todos-category-select))
;; (let ((todos-show-with-done t))
;; (todos-category-select))))
))
(defun todos-category-select ()
"Display the current category correctly."
(let ((name (todos-current-category))
@ -2506,10 +2516,10 @@ which is the value of the user option
(define-key map "+" 'todos-lower-category-priority)
(define-key map "r" 'todos-raise-category-priority)
(define-key map "-" 'todos-raise-category-priority)
(define-key map "n" 'forward-button)
(define-key map "p" 'backward-button)
(define-key map [tab] 'forward-button)
(define-key map [backtab] 'backward-button)
(define-key map "n" 'todos-forward-button)
(define-key map "p" 'todos-backward-button)
(define-key map [tab] 'todos-forward-button)
(define-key map [backtab] 'todos-backward-button)
(define-key map "q" 'todos-quit)
;; (define-key map "A" 'todos-add-category)
;; (define-key map "D" 'todos-delete-category)
@ -2585,7 +2595,7 @@ which is the value of the user option
(when todos-show-current-file
(add-hook 'pre-command-hook 'todos-show-current-file nil t))
(add-hook 'window-configuration-change-hook
'todos-reset-and-enable-done-separator nil t)
'todos-reset-and-enable-done-separator nil t)
(add-hook 'kill-buffer-hook 'todos-reset-global-current-todos-file nil t))
(defun todos-unload-hook ()
@ -3239,6 +3249,22 @@ upward."
nil t)
(forward-line -1))))))
(defun todos-forward-button (n &optional wrap display-message)
""
(interactive "p\nd\nd")
(forward-button n wrap display-message)
(and (bolp) (button-at (point))
;; Align with beginning of category label.
(forward-char (+ 4 (length todos-categories-number-separator)))))
(defun todos-backward-button (n &optional wrap display-message)
""
(interactive "p\nd\nd")
(backward-button n wrap display-message)
(and (bolp) (button-at (point))
;; Align with beginning of category label.
(forward-char (+ 4 (length todos-categories-number-separator)))))
;; FIXME: (i) Extend search to other Todos files. (ii) Allow navigating among
;; hits. (But these features are effectively available with
;; todos-regexp-items-multifile, so maybe it's not worth the trouble here.)
@ -3766,59 +3792,133 @@ archive of the file moved to, creating it if it does not exist."
(defun todos-merge-category ()
"Merge current category into another category in this file.
The current category's todo and done items are appended to the
chosen category's todo and done items, respectively, which
becomes the current category, and the category moved from is
deleted."
chosen goal category's todo and done items, respectively. The
goal category becomes the current category, and the previous
current category is deleted.
If both the first and goal categories also have archived items,
the former are merged to the latter. If only the first category
has archived items, the archived category is renamed to the goal
category."
(interactive)
(let ((buffer-read-only nil)
(cat (todos-current-category))
(goal (todos-read-category "Category to merge to: " t)))
(widen)
;; FIXME: check if cat has archived items and merge those too
(let* ((cbeg (progn
(re-search-backward
(concat "^" (regexp-quote todos-category-beg)) nil t)
(point)))
(tbeg (progn (forward-line) (point)))
(dbeg (progn
(re-search-forward
(concat "^" (regexp-quote todos-category-done)) nil t)
(forward-line) (point)))
(tend (progn (forward-line -2) (point)))
(cend (progn
(if (re-search-forward
(concat "^" (regexp-quote todos-category-beg)) nil t)
(match-beginning 0)
(point-max))))
(todo (buffer-substring-no-properties tbeg tend))
(done (buffer-substring-no-properties dbeg cend))
here)
(goto-char (point-min))
(re-search-forward
(concat "^" (regexp-quote (concat todos-category-beg goal))) nil t)
(re-search-forward
(concat "^" (regexp-quote todos-category-done)) nil t)
(forward-line -1)
(setq here (point))
(insert todo)
(goto-char (if (re-search-forward
(let* ((tfile todos-current-todos-file)
(archive (concat (file-name-sans-extension tfile) ".toda"))
(cat (todos-current-category))
(goal (todos-read-category "Category to merge to: " t))
archived-count here)
;; Merge in todo file.
(with-current-buffer (get-buffer (find-file-noselect tfile))
(widen)
(let* ((buffer-read-only nil)
(cbeg (progn
(re-search-backward
(concat "^" (regexp-quote todos-category-beg)) nil t)
(match-beginning 0)
(point-max)))
(insert done)
(remove-overlays cbeg cend)
(delete-region cbeg cend)
(todos-update-count 'todo (todos-get-count 'todo cat) goal)
(todos-update-count 'done (todos-get-count 'done cat) goal)
(setq todos-categories (delete (assoc cat todos-categories)
todos-categories))
(todos-update-categories-sexp)
(point-marker)))
(tbeg (progn (forward-line) (point-marker)))
(dbeg (progn
(re-search-forward
(concat "^" (regexp-quote todos-category-done)) nil t)
(forward-line) (point-marker)))
;; Omit empty line between todo and done items.
(tend (progn (forward-line -2) (point-marker)))
(cend (progn
(if (re-search-forward
(concat "^" (regexp-quote todos-category-beg)) nil t)
(progn
(goto-char (match-beginning 0))
(point-marker))
(point-max-marker))))
(todo (buffer-substring-no-properties tbeg tend))
(done (buffer-substring-no-properties dbeg cend)))
(goto-char (point-min))
;; Merge any todo items.
(unless (zerop (length todo))
(re-search-forward
(concat "^" (regexp-quote (concat todos-category-beg goal))) nil t)
(re-search-forward
(concat "^" (regexp-quote todos-category-done)) nil t)
(forward-line -1)
(setq here (point-marker))
(insert todo)
(todos-update-count 'todo (todos-get-count 'todo cat) goal))
;; Merge any done items.
(unless (zerop (length done))
(goto-char (if (re-search-forward
(concat "^" (regexp-quote todos-category-beg)) nil t)
(match-beginning 0)
(point-max)))
(when (zerop (length todo)) (setq here (point-marker)))
(insert done)
(todos-update-count 'done (todos-get-count 'done cat) goal))
(remove-overlays cbeg cend)
(delete-region cbeg cend)
(setq todos-categories (delete (assoc cat todos-categories)
todos-categories))
(todos-update-categories-sexp)
(mapc (lambda (m) (set-marker m nil)) (list cbeg tbeg dbeg tend cend))))
(when (file-exists-p archive)
;; Merge in archive file.
(with-current-buffer (get-buffer (find-file-noselect archive))
(widen)
(goto-char (point-min))
(let ((buffer-read-only nil)
(cbeg (save-excursion
(when (re-search-forward
(concat "^" (regexp-quote
(concat todos-category-beg cat)))
nil t)
(goto-char (match-beginning 0))
(point-marker))))
(gbeg (save-excursion
(when (re-search-forward
(concat "^" (regexp-quote
(concat todos-category-beg goal)))
nil t)
(goto-char (match-beginning 0))
(point-marker))))
cend carch)
(when cbeg
(setq archived-count (todos-get-count 'done cat))
(setq cend (save-excursion
(if (re-search-forward
(concat "^" (regexp-quote todos-category-beg))
nil t)
(match-beginning 0)
(point-max))))
(setq carch (save-excursion (goto-char cbeg) (forward-line)
(buffer-substring-no-properties (point) cend)))
;; If both categories of the merge have archived items, merge the
;; source items to the goal items, else "merge" by renaming the
;; source category to goal.
(if gbeg
(progn
(goto-char (if (re-search-forward
(concat "^" (regexp-quote todos-category-beg))
nil t)
(match-beginning 0)
(point-max)))
(insert carch)
(remove-overlays cbeg cend)
(delete-region cbeg cend))
(goto-char cbeg)
(search-forward cat)
(replace-match goal))
(setq todos-categories (todos-make-categories-list t))
(todos-update-categories-sexp)))))
(with-current-buffer (get-file-buffer tfile)
(when archived-count
(unless (zerop archived-count)
(todos-update-count 'archived archived-count goal)
(todos-update-categories-sexp)))
(todos-category-number goal)
(todos-category-select)
;; Put point at the start of the merged todo items.
;; FIXME: what if there are no merged todo items but only done items?
(goto-char here))))
;; If there are only merged done items, show them.
(let ((todos-show-with-done (zerop (todos-get-count 'todo goal))))
(todos-category-select)
;; Put point on the first merged item.
(goto-char here)))
(set-marker here nil)))
(defun todos-set-category-priority (&optional arg)
"Change priority of category at point in Todos Categories buffer.
@ -3922,6 +4022,11 @@ mandatory date header string and how it is added:
when the user puts the cursor on a date and hits RET, that
date, in the format set by `calendar-date-display-form',
becomes the date in the header.
- If DATE-TYPE is a string matching the regexp
`todos-date-pattern', that string becomes the date in the
header. This case is for the command
`todos-insert-item-from-calendar' which is called from the
Calendar.
- If DATE-TYPE is the symbol `date', the header contains the date
in the format set by `calendar-date-display-form', with year,
month and day individually prompted for (month with tab
@ -3999,6 +4104,9 @@ the priority is not given by HERE but by prompting."
((eq date-type 'calendar)
(setq todos-date-from-calendar t)
(todos-set-date-from-calendar))
((string-match todos-date-pattern date-type)
(setq todos-date-from-calendar date-type)
(todos-set-date-from-calendar))
(t (calendar-date-string (calendar-current-date) t t))))
(time-string (or (and time (todos-read-time))
(and todos-always-add-time-string
@ -4055,19 +4163,21 @@ the priority is not given by HERE but by prompting."
(defun todos-set-date-from-calendar ()
"Return string of date chosen from Calendar."
(when todos-date-from-calendar
(let (calendar-view-diary-initially-flag)
(calendar))
;; *Calendar* is now current buffer.
(local-set-key (kbd "RET") 'exit-recursive-edit)
(message "Put cursor on a date and type <return> to set it.")
;; FIXME: is there a better way than recursive-edit? Use unwind-protect?
;; Check recursive-depth?
(recursive-edit)
(setq todos-date-from-calendar
(calendar-date-string (calendar-cursor-to-date t) t t))
(calendar-exit)
todos-date-from-calendar))
(cond ((string-match todos-date-pattern todos-date-from-calendar)
todos-date-from-calendar)
((todos-date-from-calendar t)
(let (calendar-view-diary-initially-flag)
(calendar))
;; *Calendar* is now current buffer.
(local-set-key (kbd "RET") 'exit-recursive-edit)
(message "Put cursor on a date and type <return> to set it.")
;; FIXME: is there a better way than recursive-edit? Use unwind-protect?
;; Check recursive-depth?
(recursive-edit)
(setq todos-date-from-calendar
(calendar-date-string (calendar-cursor-to-date t) t t))
(calendar-exit)
todos-date-from-calendar)))
(defun todos-delete-item ()
"Delete at least one item in this category.
@ -4075,45 +4185,49 @@ the priority is not given by HERE but by prompting."
If there are marked items, delete all of these; otherwise, delete
the item at point."
(interactive)
(let* ((cat (todos-current-category))
(marked (assoc cat todos-categories-with-marks))
(item (unless marked (todos-item-string)))
(ov (make-overlay (save-excursion (todos-item-start))
(save-excursion (todos-item-end))))
;; FIXME: make confirmation an option?
(answer (if marked
(y-or-n-p "Permanently delete all marked items? ")
(when item
(overlay-put ov 'face 'todos-search)
(y-or-n-p (concat "Permanently delete this item? ")))))
(opoint (point))
buffer-read-only)
(when answer
(and marked (goto-char (point-min)))
(catch 'done
(while (not (eobp))
(if (or (and marked (todos-marked-item-p)) item)
(progn
(if (todos-done-item-p)
(todos-update-count 'done -1)
(todos-update-count 'todo -1 cat)
(and (todos-diary-item-p) (todos-update-count 'diary -1)))
(delete-overlay ov)
(todos-remove-item)
;; Don't leave point below last item.
(and item (bolp) (eolp) (< (point-min) (point-max))
(todos-backward-item))
(when item
(throw 'done (setq item nil))))
(todos-forward-item))))
(when marked
(remove-overlays (point-min) (point-max) 'before-string todos-item-mark)
(setq todos-categories-with-marks
(assq-delete-all cat todos-categories-with-marks))
(goto-char opoint))
(todos-update-categories-sexp)
(todos-prefix-overlays))
(if ov (delete-overlay ov))))
(let (ov)
(unwind-protect
(let* ((cat (todos-current-category))
(marked (assoc cat todos-categories-with-marks))
(item (unless marked (todos-item-string)))
;; FIXME: make confirmation an option?
(answer (if marked
(y-or-n-p "Permanently delete all marked items? ")
(when item
(setq ov (make-overlay
(save-excursion (todos-item-start))
(save-excursion (todos-item-end))))
(overlay-put ov 'face 'todos-search)
(y-or-n-p (concat "Permanently delete this item? ")))))
(opoint (point))
buffer-read-only)
(when answer
(and marked (goto-char (point-min)))
(catch 'done
(while (not (eobp))
(if (or (and marked (todos-marked-item-p)) item)
(progn
(if (todos-done-item-p)
(todos-update-count 'done -1)
(todos-update-count 'todo -1 cat)
(and (todos-diary-item-p) (todos-update-count 'diary -1)))
(if ov (delete-overlay ov))
(todos-remove-item)
;; Don't leave point below last item.
(and item (bolp) (eolp) (< (point-min) (point-max))
(todos-backward-item))
(when item
(throw 'done (setq item nil))))
(todos-forward-item))))
(when marked
(remove-overlays (point-min) (point-max)
'before-string todos-item-mark)
(setq todos-categories-with-marks
(assq-delete-all cat todos-categories-with-marks))
(goto-char opoint))
(todos-update-categories-sexp)
(todos-prefix-overlays)))
(if ov (delete-overlay ov)))))
(defun todos-edit-item ()
"Edit the Todo item at point.
@ -4539,35 +4653,42 @@ entry/entries in that category."
file1))
(count 0)
(count-diary 0)
cat2 nmark)
ov cat2 nmark)
(set-buffer (find-file-noselect file2))
(setq cat2 (let* ((pl (if (and marked (> (cdr marked) 1)) "s" ""))
(name (todos-read-category
(concat "Move item" pl " to category: ")))
(prompt (concat "Choose a different category than "
"the current one\n(type `"
(key-description
(car (where-is-internal
'todos-set-item-priority)))
"' to reprioritize item "
"within the same category): ")))
(while (equal name cat1)
(setq name (todos-read-category prompt)))
name))
(unwind-protect
(progn
(unless marked
(setq ov (make-overlay (save-excursion (todos-item-start))
(save-excursion (todos-item-end))))
(overlay-put ov 'face 'todos-search))
(setq cat2 (let* ((pl (if (and marked (> (cdr marked) 1)) "s" ""))
(name (todos-read-category
(concat "Move item" pl " to category: ")))
(prompt (concat "Choose a different category than "
"the current one\n(type `"
(key-description
(car (where-is-internal
'todos-set-item-priority)))
"' to reprioritize item "
"within the same category): ")))
(while (equal name cat1)
(setq name (todos-read-category prompt)))
name)))
(if ov (delete-overlay ov)))
(set-buffer (find-buffer-visiting file1))
(if marked
(progn
(setq item nil)
(goto-char (point-min))
(while (not (eobp))
(when (todos-marked-item-p)
(setq item (concat item (todos-item-string) "\n"))
(setq count (1+ count))
(when (todos-diary-item-p)
(setq count-diary (1+ count-diary))))
(todos-forward-item))
;; Chop off last newline.
(setq item (substring item 0 -1)))
(setq item nil)
(goto-char (point-min))
(while (not (eobp))
(when (todos-marked-item-p)
(setq item (concat item (todos-item-string) "\n"))
(setq count (1+ count))
(when (todos-diary-item-p)
(setq count-diary (1+ count-diary))))
(todos-forward-item))
;; Chop off last newline.
(setq item (substring item 0 -1)))
(setq count 1)
(when (todos-diary-item-p) (setq count-diary 1)))
(set-window-buffer (selected-window)
@ -4598,6 +4719,7 @@ entry/entries in that category."
(if (todos-marked-item-p)
(todos-remove-item)
(todos-forward-item))))
(if ov (delete-overlay ov))
(todos-remove-item))))
(todos-update-count 'todo (- count) cat1)
(todos-update-count 'diary (- count-diary) cat1)
@ -4712,47 +4834,90 @@ With prefix ARG delete an existing comment."
(insert " [" todos-comment-string ": " comment "]"))))))
;; FIXME: also with marked items
;; FIXME: delete comment from restored item or just leave it up to user?
(defun todos-item-undo ()
"Restore this done item to the todo section of this category.
If done item has a comment, ask whether to omit the comment from
the restored item."
(interactive)
(when (todos-done-item-p)
(let* ((buffer-read-only)
(done-item (todos-item-string))
(opoint (point))
(orig-mrk (progn (todos-item-start) (point-marker)))
;; Find the end of the date string added upon tagging item as done.
(start (search-forward "] "))
(end (save-excursion (todos-item-end)))
item undone)
(todos-item-start)
(when (and (re-search-forward (concat " \\["
(regexp-quote todos-comment-string)
": \\([^]]+\\)\\]") end t)
(y-or-n-p "Omit comment from restored item? "))
(delete-region (match-beginning 0) (match-end 0)))
(setq item (buffer-substring start end))
(todos-remove-item)
;; If user cancels before setting new priority, then leave the done item
;; unchanged.
(unwind-protect
(progn
(todos-set-item-priority item (todos-current-category) t)
(setq undone t)
(todos-update-count 'todo 1)
(todos-update-count 'done -1)
(and (todos-diary-item-p) (todos-update-count 'diary 1))
(todos-update-categories-sexp))
(unless undone
(widen)
(goto-char orig-mrk)
(todos-insert-with-overlays done-item)
(let ((todos-show-with-done t))
(todos-category-select)
(goto-char opoint)))
(set-marker orig-mrk nil)))))
(let* ((cat (todos-current-category))
(marked (assoc cat todos-categories-with-marks)))
(when (or marked (todos-done-item-p))
(let ((buffer-read-only)
(done-item (todos-item-string))
(opoint (point))
(orig-mrk (progn (todos-item-start) (point-marker)))
(first 'first)
(item-count 0)
(diary-count 0)
start end item undone)
(and marked (goto-char (point-min)))
(catch 'done
(while (not (eobp))
(if (or (not marked) (and marked (todos-marked-item-p)))
(if (not (todos-done-item-p))
(error "Only done items can be undone")
(todos-item-start)
;; Find the end of the date string added upon tagging item as
;; done.
(setq start (search-forward "] "))
(setq item-count (1+ item-count))
(unless (looking-at (regexp-quote todos-nondiary-start))
(setq diary-count (1+ diary-count)))
(setq end (save-excursion (todos-item-end)))
;; Ask (once) whether to omit done item's comment. If
;; affirmed, omit subsequent comments without asking.
(when (re-search-forward
(concat " \\[" (regexp-quote todos-comment-string)
": [^]]+\\]") end t)
(if (eq first 'first)
(setq first
;; FIXME: make this a user option?
(when (y-or-n-p "Omit comment from restored item? ")
'omit))
t)
(when (eq first 'omit)
(delete-region (match-beginning 0) (match-end 0))
(setq end (point))))
(setq item (concat item (buffer-substring start end)
(when marked "\n")))
(todos-remove-item)
(unless marked (throw 'done nil)))
(todos-forward-item))))
(if marked
(progn
(remove-overlays (point-min) (point-max)
'before-string todos-item-mark)
(setq todos-categories-with-marks
(assq-delete-all cat todos-categories-with-marks))
;; Insert undone items that were marked at end of todo item list.
(widen)
(re-search-forward (concat "^" (regexp-quote todos-category-done))
nil t)
(forward-line -1)
(insert item)
(todos-update-count 'todo item-count)
(todos-update-count 'done (- item-count))
(when diary-count (todos-update-count 'diary diary-count))
(todos-update-categories-sexp))
;; With an unmarked undone item, prompt for its priority. If user
;; cancels before setting new priority, then leave the done item
;; unchanged.
(unwind-protect
(progn
(todos-set-item-priority item (todos-current-category) t)
(setq undone t)
(todos-update-count 'todo 1)
(todos-update-count 'done -1)
(and (todos-diary-item-p) (todos-update-count 'diary 1))
(todos-update-categories-sexp))
(unless undone
(widen)
(goto-char orig-mrk)
(todos-insert-with-overlays done-item)
(let ((todos-show-with-done t))
(todos-category-select)
(goto-char opoint)))
(set-marker orig-mrk nil)))))))
(defun todos-archive-done-item (&optional all)
"Archive at least one done item in this category.
@ -4996,31 +5161,24 @@ archive, the archive file is deleted."
;;; todos.el ends here
;; ---------------------------------------------------------------------------
;; FIXME: remove when part of Emacs
;; ---------------------------------------------------------------------------
(add-to-list 'auto-mode-alist '("\\.todo\\'" . todos-mode))
(add-to-list 'auto-mode-alist '("\\.toda\\'" . todos-archive-mode))
;;; Addition to calendar.el
;; FIXME: autoload when key-binding is defined in calendar.el
(defun todos-insert-item-from-calendar ()
(defun todos-insert-item-from-calendar (&optional arg)
""
(interactive)
;; FIXME: todos-current-todos-file is nil here, better to solicit Todos
;; file? todos-global-current-todos-file is nil if no Todos file has been
;; visited
(pop-to-buffer (file-name-nondirectory todos-global-current-todos-file))
(interactive "P")
(setq todos-date-from-calendar
(calendar-date-string (calendar-cursor-to-date t) t t))
(calendar-exit)
(todos-show)
;; FIXME: this now calls todos-set-date-from-calendar
(todos-insert-item t 'calendar))
(todos-insert-item arg nil nil todos-date-from-calendar))
;; FIXME: calendar is loaded before todos
;; (add-hook 'calendar-load-hook
;; (lambda ()
(define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);))
(define-key calendar-mode-map "it" 'todos-insert-item-from-calendar)
;; ---------------------------------------------------------------------------
;;; necessitated adaptations to diary-lib.el
;; (defun diary-goto-entry (button)