Fix and improve setting priority of todo-mode items (bug#64433)

* lisp/calendar/todo-mode.el (todo-set-item-priority): Bugfixes:
Prevent interactively setting item priority to its current
priority in the same category and prompt user for a different
priority (but allow using the same priority when item is moved to
another category).  Ensure that the priority passed as a prefix
argument is suitable: if it is not an integer between 1 and the
highest item number, signal a user error.  New feature: Use the
sequence of numbers of the category's items as the minibuffer
history.

* doc/misc/todo-mode.texi (Inserting New Items):
(Reprioritizing Items): Document using the minibuffer history.

* test/lisp/calendar/todo-mode-tests.el
(todo-test-item-insertion-with-priority-1)
(todo-test-item-insertion-with-priority-2)
(todo-test-item-insertion-with-priority-3): New tests.
This commit is contained in:
Stephen Berman 2023-07-03 14:19:41 +02:00
parent 3ac4b2de77
commit 14ae210141
3 changed files with 128 additions and 20 deletions

View file

@ -580,7 +580,14 @@ on every invocation of @code{todo-insert-item}.
the highest or lowest priority in the category, if you do not
explicitly assign it a priority on invoking @code{todo-insert-item}.
By default, such new items are given highest priority, i.e., inserted
at the top of the list.
at the top of the list. In addition, when setting an item's priority
you can use the minibuffer history to quickly call up the lowest or
highest priority number in the minibuffer by typing @kbd{M-p} or
@kbd{M-n}, and you can scroll through all priority numbers for the
current category with these keys. For example, with the default
setting of @code{todo-default-priority}, you can insert a new item as
second to last in the category by typing @kbd{M-p M-p} at the prompt
for setting the priority.
@item
@code{todo-always-add-time-string} is for including or omitting the
@ -983,7 +990,10 @@ category, i.e., gives it third highest priority; all lower priority
items are pushed down by one. You can also pass the desired priority
as a numeric prefix argument, e.g., @kbd{3 #} gives the item third
highest priority without prompting. (Prefix arguments have no effect
with @kbd{r} or @kbd{l}.)
with @kbd{r} or @kbd{l}.) And you can type @kbd{M-p} and @kbd{M-n} in
the minibuffer to scroll through all priority numbers for the current
category. If you mistakenly choose the item's current priority, you
will be prompted to choose a different priority.
@end table
@node Moving and Deleting Items

View file

@ -2646,16 +2646,26 @@ meaning to raise or lower the item's priority by one."
(save-excursion
(re-search-forward regexp1 nil t)
(match-string-no-properties 1)))))))
curnum
(count 1)
(curnum (save-excursion
(let ((curstart
;; If point is in done items section or not on an
;; item, use position of first todo item to avoid
;; the while-loop.
(or (and (not (todo-done-item-section-p))
(todo-item-start))
(point-min))))
(goto-char (point-min))
(while (/= (point) curstart)
(setq count (1+ count))
(todo-forward-item))
count)))
(todo (cond ((or (memq arg '(raise lower))
(eq major-mode 'todo-filtered-items-mode))
(save-excursion
(let ((curstart (todo-item-start))
(count 0))
(goto-char (point-min))
(let ((count curnum))
(while (looking-at todo-item-start)
(setq count (1+ count))
(when (= (point) curstart) (setq curnum count))
(todo-forward-item))
count)))
((eq major-mode 'todo-mode)
@ -2667,11 +2677,16 @@ meaning to raise or lower the item's priority by one."
((and (eq arg 'raise) (>= curnum 1))
(1- curnum))
((and (eq arg 'lower) (<= curnum maxnum))
(1+ curnum))))
candidate)
(1+ curnum)))))
(and (called-interactively-p 'any)
priority ; Check further only if arg or prefix arg was passed.
(or (< priority 1) (> priority maxnum))
(user-error (format "Priority must be an integer between 1 and %d"
maxnum)))
(unless (and priority
(/= priority curnum)
(or (and (eq arg 'raise) (zerop priority))
(and (eq arg 'lower) (> priority maxnum))))
(and (eq arg 'lower) (>= priority maxnum))))
;; When moving item to another category, show the category before
;; prompting for its priority.
(unless (or arg (called-interactively-p 'any))
@ -2687,16 +2702,34 @@ meaning to raise or lower the item's priority by one."
;; while setting priority.
(save-excursion (todo-category-select)))))
;; Prompt for priority only when the category has at least one
;; todo item.
(when (> maxnum 1)
(while (not priority)
(setq candidate (read-number prompt
(if (eq todo-default-priority 'first)
1 maxnum)))
(setq prompt (when (or (< candidate 1) (> candidate maxnum))
(format "Priority must be an integer between 1 and %d.\n"
maxnum)))
(unless prompt (setq priority candidate))))
;; todo item or when passing the current priority as prefix arg.
(when (and (or (not priority) (= priority curnum))
(> maxnum 1))
(let* ((read-number-history (mapcar #'number-to-string
(if (eq todo-default-priority
'first)
(number-sequence maxnum 1 -1)
(number-sequence 1 maxnum))))
(history-add-new-input nil)
(candidate (or priority
(read-number prompt
(if (eq todo-default-priority
'first)
1 maxnum))))
(success nil))
(while (not success)
(setq prompt
(cond
((and (= candidate curnum)
;; Allow same priority in a different category
;; (only possible when called non-interactively).
(called-interactively-p 'any))
"New priority must be different from current priority: ")
(t (when (or (< candidate 1) (> candidate maxnum))
(format "Priority must be an integer between 1 and %d: "
maxnum)))))
(when prompt (setq candidate (read-number prompt)))
(unless prompt (setq priority candidate success t)))))
;; In Top Priorities buffer, an item's priority can be changed
;; wrt items in another category, but not wrt items in the same
;; category.

View file

@ -934,5 +934,70 @@ since all non-initial item lines must begin with whitespace."
(insert (concat "\n" item1))
(should-error (todo-edit-quit) :type 'user-error))))
(ert-deftest todo-test-item-insertion-with-priority-1 ()
"Test inserting new item when point is not on a todo item.
When point is on the empty line at the end of the todo items
section, insertion with priority setting should succeed."
(with-todo-test
(todo-test--show 1)
(goto-char (point-max))
;; Now point should not be on a todo item.
(should-not (todo-item-start))
(let ((item "Point was on empty line at end of todo items section."))
(todo-test--insert-item item 1)
;; Move point to item that was just inserted.
(goto-char (point-min))
(re-search-forward (concat todo-date-string-start todo-date-pattern
(regexp-quote todo-nondiary-end) " ")
(pos-eol) t)
(should (looking-at (regexp-quote item))))))
(ert-deftest todo-test-item-insertion-with-priority-2 ()
"Test inserting new item when point is not on a todo item.
When point is on the empty line at the end of the done items
section, insertion with priority setting should succeed."
(with-todo-test
(todo-test--show 1)
(goto-char (point-max))
;; See comment about recentering in todo-test-raise-lower-priority.
(set-window-buffer nil (current-buffer))
(todo-toggle-view-done-items)
(todo-next-item)
(goto-char (point-max))
;; Now point should be at end of done items section, so not be on a
;; todo item.
(should (todo-done-item-section-p))
(should-not (todo-item-start))
(let ((item "Point was on empty line at end of done items section."))
(todo-test--insert-item item 1)
;; Move point to item that was just inserted.
(goto-char (point-min))
(re-search-forward (concat todo-date-string-start todo-date-pattern
(regexp-quote todo-nondiary-end) " ")
(pos-eol) t)
(should (looking-at (regexp-quote item))))))
(ert-deftest todo-test-item-insertion-with-priority-3 ()
"Test inserting new item when point is not on a todo item.
When point is on a done item, insertion with priority setting
should succeed."
(with-todo-test
(todo-test--show 1)
(goto-char (point-max))
;; See comment about recentering in todo-test-raise-lower-priority.
(set-window-buffer nil (current-buffer))
(todo-toggle-view-done-items)
(todo-next-item)
;; Now point should be on first done item.
(should (and (todo-item-start) (todo-done-item-section-p)))
(let ((item "Point was on a done item."))
(todo-test--insert-item item 1)
;; Move point to item that was just inserted.
(goto-char (point-min))
(re-search-forward (concat todo-date-string-start todo-date-pattern
(regexp-quote todo-nondiary-end) " ")
(pos-eol) t)
(should (looking-at (regexp-quote item))))))
(provide 'todo-mode-tests)
;;; todo-mode-tests.el ends here