3

I've read through all the questions here, and searched extensively online.

Since I've started using org-drill, I have a lot of scheduled tasks with the tag :drill:. Instead of having all these tasks show up in my agenda, I just want to show something like Org-Drill: 42 cards today..

I ripped this off org-effectiveness: (defun drill-card-count() "Print a message with the number of todo tasks in the current buffer" (interactive) (save-excursion (goto-char (point-min)) (message "Number of cards: %d" (count-matches ":drill:")))) I can run this in my agenda but of course it doesn't do what I'd like.

To summarize, I'd like to: 1. Filter any tasks tagged drill from my agenda. 2. Display instead the count of those tagged tasks. 3. Otherwise display the agenda as normal.

Thanks for any help!

ETA: Lawlist's comment below is helpful. I've added this, and it hides the tasks tagged :drill:, but doesn't count them. So, 1 and 3 accomplished, but don't know how to insert a task showing the drill items count. (defun my-skip-tag(tag) "Skip entries that are tagged TAG" (let* ((entry-tags (org-get-tags-at (point)))) (if (member tag entry-tags) (progn (outline-next-heading) (point)) nil)))

(setq org-agenda-custom-commands '(("x" "again" ((agenda "" ((org-agenda-overriding-header (drill-card-count)) (org-agenda-skip-function '(my-skip-tag "drill"))))))))

8
  • Probably the easiest solution will be to just fold them and make them invisible after you count them (or delete them from the agenda view if you won't be needing them again in that particular buffer) -- you can attach your custom function to the org-agenda-finalize-hook. You will want to (require 'org-agenda) at the beginning of your session before attaching your custom function to the above-mentioned hook.
    – lawlist
    Dec 8, 2015 at 21:45
  • I think you are right; that's the solution I'm moving towards. The following removes tasks tagged :drill:, but doesn't count them. ``` (defun my-skip-tag(tag) "Skip entries that are tagged TAG" (let* ((entry-tags (org-get-tags-at (point)))) (if (member tag entry-tags) (progn (outline-next-heading) (point)) nil))) (setq org-agenda-custom-commands '(("x" "My agenda without :drill: tags" ((agenda "" ((org-agenda-overriding-header (drill-card-count)) (org-agenda-skip-function '(my-skip-tag "drill")))))))) ``` Dec 8, 2015 at 21:51
  • You can fold text like this: (overlay-put (make-overlay (line-beginning-position) (line-end-position)) 'invisible t) Example sets beginning point at the beginning of the line and end point at the end of the line. You'll just need to define a few statements to calculate the beginning and ending of each region as you do your search -- (while (re-search-forward . . . or (while (re-search-backward . . . depending upon where you are in the buffer when you commence the search.
    – lawlist
    Dec 8, 2015 at 21:55
  • I don't recommend that you attempt to count them and then skip them and then populate the org-agenda buffer. If you really want to get fancy and understand how to examine text properties of the underlying data that is being gathered as the org-agenda buffer is being populated and modify the data in mid-stream, I can give you some links -- but very few people are that motivated :) My suggestion is that you populate the buffer completely, count your stuff, and then hide or delete what you don't want to see, and then insert your count wherever you want it to appear.
    – lawlist
    Dec 8, 2015 at 21:58
  • You could also do your count ahead of time, store the information in a temporary variable, and then skip them during the org-agenda data gathering process, and insert the total in the org-agenda buffer. Basically, you'd be doing two searches. And, I suppose you could also put in a counter into the skip function and when a match is found, add a +-one to the count -- storing the information in a temporary variable. I guess there are many ways to accomplish your goal, so don't let me discourage you if you really want to count them and skip them and then populate the org-agenda buffer.
    – lawlist
    Dec 8, 2015 at 22:08

1 Answer 1

1

As it happens, I just implemented something useful for this for myself before I came across your question. To avoid having all of the source code just in a link, it's also at the bottom of this answer.

To use this, you would construct an empty agenda block like so:

'("x" "Example block agenda"
   ((tags
     "drill"
     ((org-agenda-overriding-header
       (format "Org-Drill: %s cards today."
               (org-agenda-count "drill")))
      (org-agenda-max-entries 0)))

This block would contain just the block header, with a count of all :drill: tasks. In reality, you would probably want an agenda-type block with an appropriate skip function so you only pick up the scheduled cards, but the principle is the same. And of course you'd have other blocks where you'd filter out the drill tasks.

The way this works is as follows:

  1. When the function org-agenda-count is called, it sets up the rest of the process using hooks and advice, then returns a proxy string (by default for this example, "##drill##"), which is incorporated into the header.

  2. During agenda construction, the function org-agenda-finalize-entries is called. Its first argument is a list with one item per agenda entry. We run advice here that counts that list and stores the result.

  3. At some point in here, org-agenda-max-entries is applied and all of the actual entries are discarded.

  4. Finally, org-agenda-finalize-hook is run, along with our hook function that looks for the proxy string from step 1 and replaces it with the appropriate count, then removes all of the advice and so forth.

You should note that the code has one non-Org dependency, a macro called alist-put (which is essentially an alist-only backport of map-put from map.el for use in Emacs 24). Its source code is elsewhere in the repository linked above and included in the code block below, or you can replace it with whatever similar idiom you desire.

Full source code:

(require 'cl-lib)

(defun alist--put (alist key value &optional test)
  "Subroutine used by `alist-put'.

As `alist-put', except back-assignment may be necessary, as with
`delete'."
  (let* ((test  (or test #'eql))
         (elt   (cl-assoc key alist :test test)))
    (if elt (setf (cdr elt) value)
      (setq alist (push (cons key value) alist)))
    alist))

(defmacro alist-put (alist key value &optional test)
  "Associate KEY with VALUE in ALIST.  Return ALIST.

If ALIST does not already contain an association for KEY, it is
added; otherwise, the existing association is updated.

If the optional parameter TEST is supplied, it is used in place
of `eql' to compare elements.

Here, ALIST may be any generalized variable containing an alist."
  (declare (debug (gv-place form form &optional function-form)))
  `(setf ,alist (alist--put ,alist ,key ,value ,test)))

(defcustom org-agenda-count-delimiter "##"
  "String used to delimit the proxy for `org-agenda-count'."
  :group 'org-agenda
  :type  'string)

(defvar org-agenda-count--alist nil
  "Alist containing agenda block counts for `org-agenda-count'.")

(defvar org-agenda-count--block nil
  "Agenda block being counted for `org-agenda-count'.")

(defun org-agenda-count (&optional block)
  "Display a count of the number of items in this agenda block.

For use in custom agenda commands, specifically with
`org-agenda-overriding-header'.  To use this function, call it
while constructing the value for `org-agenda-overriding-header'
and use the return value as if it were the number of items in
this block (before filtering by `org-agenda-max-entries' and
similar).

The optional BLOCK parameter identifies the agenda block being
counted.  To count multiple blocks in the same agenda, each
invocation of `org-agenda-count' should have a different BLOCK
value.

The actual return value is a proxy string incorporating the BLOCK
parameter.  This string is replaced with the actual value during
agenda construction."
  ;; Set up replacement process
  (setq org-agenda-count--block (or block ""))
  (alist-put org-agenda-count--alist
             org-agenda-count--block
             0
             #'equal)
  (advice-add 'org-agenda-finalize-entries :before #'org-agenda--count)
  (add-hook 'org-agenda-finalize-hook #'org-agenda-count--replace)
  ;; Return proxy value
  (concat org-agenda-count-delimiter
          org-agenda-count--block
          org-agenda-count-delimiter))

(defun org-agenda--count (list &optional type)
  "Count the number of entries in this block for `org-agenda-count'.

Intended as (temporary) :before advice for the function
`org-agenda-finalize-hook'."
  (alist-put org-agenda-count--alist
             org-agenda-count--block
             (length list)
             #'equal))

(defun org-agenda-count--regexp ()
  "Return a regexp matching `org-agenda-count' proxy values."
  (let ((delim (regexp-quote org-agenda-count-delimiter)))
    (concat delim "\\([^\n]*?\\)" delim)))

(defun org-agenda-count--reset ()
  "Reset state variables used by `org-agenda-count'.

This includes removing hook functions and temporary advice
functions."
  (setq org-agenda-count--alist nil
        org-agenda-count--block  nil)
  (advice-remove 'org-agenda-finalize-entries #'org-agenda--count)
  (remove-hook 'org-agenda-finalize-hook #'org-agenda-count--replace))

(defun org-agenda-count--replace ()
  "Replace `org-agenda-count' proxy values with entry counts.

Intended for use in `org-agenda-finalize-hook'."
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward (org-agenda-count--regexp) nil :noerror)
      (let* ((block  (match-string 1))
             (count  (cdr (assoc block org-agenda-count--alist))))
        (when count (replace-match (format "%d" count)))))
    (org-agenda-count--reset)))

Your Answer

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

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