0

I use org-mode for my shopping lists and I would like to be able to remove all checked items in one action.

i.e.

  • [X] cotton buds
  • [ ] cordial
  • [X] milk
  • [X] sugar

In the list above remove all the lines except + [ ] cordial

Is this currently available or has anyone written a function to perform this?

1
  • 1
    In general, I don't write-up anything that deletes text for fear of unintended consequences. I see the function org-at-item-checkbox-p in an older version of org-mode, which may still exist in current releases. delete-region does not save to the kill-ring, whereas kill-region does. ... I'll leave it up to another forum participant more daring than myself to write something up, if there is no built-in solution.
    – lawlist
    Commented Feb 11, 2021 at 2:01

1 Answer 1

1

I'm not sure I'm more daring than @lawlist, but I'm almost certainly foolish for even encouraging you in this: after all, the X's are exactly there to mark those list items as done; so why delete them?

That said, here's some code that seems to work in the above situation (the only situation tested). The code does use kill-region, so you should be able to undo your way back from disaster. Event though org-at-item-checkbox-p still exists in the code, I did not use it, although a different solution could almost certainly be built around it, but I'm not sure it would be any simpler.

 #+begin_src emacs-lisp :results drawer
    (defun delx (items)
      (while (not (null items))
        (let ((item (car items)))
          (if (equal (nth 4 item) "[X]")
            (kill-region (nth 0 item) (car (last item)))))
        (setq items (cdr items))))

    (defun ndk/clean-up-shopping-list ()
      (interactive)
      (save-excursion
        (let* ((elt (org-element-at-point))
               (type (org-element-type elt)))
          (when (eq type 'plain-list)
            (let ((items (plist-get (cadr elt) :structure)))
              (delx (reverse items)))))))
  #+end_src

To invoke it, you put your cursor at the beginning of the list and say M-x ndk/clean-up-shopping-list.

Some explanations:

  • the ndk/clean-up-shopping-list function uses the org-element parser to figure out whether it's at the beginning of a plain list. If so, it retrieves the list of items in the list, and calls the auxiliary function delx with the reverse of the item list (see below for the reason).

  • delx loops over the list of items and for each item, if the item is checked, it kills the region from the beginning to the end of the item.

  • the reason that we reverse the list before passing to delx is that the list of items looks like this: ((126 0 "- " nil "[X]" nil 144) (144 0 "- " nil "[ ]" nil 158) (158 0 "- " nil "[X]" nil 169) (169 0 "- " nil "[ ]" nil 186) (186 0 "- " nil "[X]" nil 198)), a list of items in increasing order of position; each item is a list of the form (beg _ _ _ "[x]" _ end) where beg is the character position of the beginning of the item, end is the character position at the end of the item, _ indicates "don't care" stuff and "[x]" can be either a checked box or an empty box. delx figures out whether each item is checked or unchecked and if it is checked, it calls kill-region with beg and end as arguments. But it has to process the list of items in reverse order, because if it starts deleting things from the top, the character positions of the later items will change, so we would not be able to use the beg/end numbers. Doing the deletions from the end does not have that problem: the character positions of the earlier items do not change, so the beg/end info from the item list remains valid.

BTW, note that the end position of each item is the same as the beginning position of the next item. So another way to do it is to delete from the beginning but then recalculate the beg/end positions of subsequent items. That's not too difficult but I think it's messier, so I opted for deleting from the bottom.

1
  • Thank you. I will go away and give that a try. Commented Feb 11, 2021 at 11:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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