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:
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.
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.
At some point in here, org-agenda-max-entries
is applied and all of the actual entries are discarded.
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)))
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.(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.