I have a BLOCKED keyword that I want to be able to temporarily set on repeating tasks in Org mode. However, if I set this up with (org-todo "BLOCKED"), this triggers a repeat. I don't want to trigger a repeat, I just want to change the status.

There seems to be some special behavior in org-mode where if you run org-todo interactively with a prefix argument of -1, then this makes the task TODO without triggering a repeat. See this question for details. I want to be able to do something similar, where I make a task BLOCKED without triggering a repeat.

The function org-cancel-repeater (which is used for the C-u -1 behavior) does not seem to help since it just sets the repeater interval to 0 permanently without providing a way to restore it. This seems almost roundabout. I'm not sure why Org was not simply written to change the keyword without affecting the repeater interval. Setting it to zero so that it works implicitly seems like a hack.

up vote 2 down vote accepted
+50

The following Elisp code defines a new command org-todo-keep-repeater. Used interactively it prompts the user for a new TODO state with completion and applies it without modifying the repeater.

The OP Matthew Piziak mentioned that org-cancel-repeater should save information for restoring the repeater. That idea is implemented in org-todo-keep-repeater.

It works by temporarily augmenting replace-match used in org-cancel-repeater such that it saves the match string, the beginning position, and end position of the match before replacing it. With this information the repeater is restored after calling org-todo.

(defun org-todo-keep-repeater (todo)
  "Call `org-todo' with TODO keyword but keep repeater."
  (interactive (list (completing-read "Set todo state of current headline to:" org-todo-keywords-1)))
  (let ((my-marker-b (make-marker))
    (my-marker-e (make-marker))
    my-original-repeater)
    (cl-letf* ((old-replace-match (symbol-function 'replace-match))
           ((symbol-function 'replace-match)
        (lambda (&rest args)
          (setq my-original-repeater (match-string 0))
          (set-marker my-marker-b (match-beginning 0))
          (set-marker my-marker-e (match-end 0))
          (apply old-replace-match args))))
      (org-cancel-repeater))
    (if my-original-repeater
    (progn
      (org-todo todo)
      (save-excursion
        (goto-char my-marker-b)
        (delete-region my-marker-b my-marker-e)
        (insert my-original-repeater))
      (set-marker my-marker-b nil)
      (set-marker my-marker-e nil))
      (org-todo todo))))

Alternatively one could write a new function that works like org-cancel-repeater saving the original repeater before canceling it.

Both approaches

  1. temporarily modifying the old org-cancel-repeater
  2. writing an new function working like org-cancel-repeater

have advantages and disadvantages.

The first approach works as long as org-cancel-repeater uses replace-match for the repeater. It follows all other potential future modifications on org-cancel-repeater. The second approach would make org-todo-keep-repeater more robust against modifications of org-cancel-repeater but it would also not follow potential development on org-cancel-repeater.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.