5

I very often create tasks that have no content but only consist of the TODO headline itself plus a SCHEDULED or DEADLINE so they will appear on my agenda. But for some tasks, I add some additional info (telefon numbers or whatever).

I'd like to have a small indicator similar to the ellipsis "…" in normal org buffers to remind me that I put additional notes below that heading. Perhaps an indicator like (5) for an entry that has five lines of text. This should not count the "SCHEDULED:" line, otherwise, each and every line will have that indicator, making it useless.

org-agenda-entry-text-mode shows the first few lines so obviously there is information about the content acessible.

So is there a simple setting for this or something similar I simply didn't find, or can anyone provide a general idea where to start if I wanted to set this up myself?

Thanks, Jan

1 Answer 1

4

The following elisp function org-agenda-add-num-of-contents-lines adds the content size indicator (%d) to agenda lines as you have specified. The pattern %d stands for the number of heading contents lines after cleaning up through org-agenda-get-some-entry-text. org-agenda-get-some-entry-text also removes the DEADLINE and SHEDULED entries.

The function org-agenda-add-num-of-contents-lines is hooked to org-agenda-finalize-hook which finishes the agenda buffer.

(defun org-agenda-delete-property-regions (property value)
  "Kill all regions where text PROPERTY has VALUE."
  (let ((pos (point-min)))
    (while (setq pos (next-single-property-change pos property))
      (when (eq (get-text-property pos property) value)
        (kill-region pos (next-single-property-change pos property))))))

(defcustom org-agenda-ignore-lines-list
  '(org-scheduled-time-regexp
    "$" ;; empty lines are ignored
    (":PROPERTIES:" . ":END:") ;; property lists are ignored
    )
  "Regular expressions for lines that are ignored when lines are counted.
Each entry is either a regular expression as string or as variable containing the regexp string
or a cons cell with the car being the start regexp and the cdr being the end regexp."
  :type '(repeat
      (choice
       regexp
       symbol
       (cons (choice regexp symbol)
         (choice regexp symbol))))
  :group 'org-agenda)

(defsubst org-agenda-count-lines-regexp (matcher)
  "If MATCHER is a symbol return its value
otherwise return MATCHER directly."
  (or (and (symbolp matcher) (symbol-value matcher))
      matcher))

(defun org-agenda-count-lines-looking-at (list)
  "A variant of `looking-at'.
It takes a LIST of form `org-agenda-ignore-lines-list'
instead of a regular expression and returns the
matching entry of that list."
  (cl-loop
   with cmp
   for item in list do
   (setq cmp item)
   (when (consp cmp)
     (setq cmp (car cmp)))
   when (looking-at (org-agenda-count-lines-regexp cmp))
   return item))
;; Test:
;; (org-agenda-count-lines-looking-at '(org-scheduled-time-regexp (":properties:" . ":end:")))SCHEDULED: <2019-01-04>
;; (org-agenda-count-lines-looking-at '(org-scheduled-time-regexp (":properties:" . ":end:"))):PROPERTIES:

(defun org-agenda-count-lines (&optional b e)
  "Count lines in the region from B to E.
If B is a string the lines in that string are counted.
Whitespace at the beginning of the lines is skipped over.
Afterwards lines are ignored if they match
one of the entries in `org-agenda-ignore-lines-list'.
B and E default to `point-min' and `point-max', respectively."
  (if (stringp b)
      (with-temp-buffer
    (insert b)
    (org-agenda-count-lines))
    (unless b (setq b (point-min)))
    (if e
    (save-excursion
      (goto-char e)
      (setq e (line-end-position)))
      (setq e (point-max)))
    (let ((count 0)
      match)
      (save-excursion
    (goto-char b)
    (forward-line 0)
    (while
        (< (point) e)
      (skip-syntax-forward " ")
      (if (setq match (org-agenda-count-lines-looking-at org-agenda-ignore-lines-list))
          (when (consp match)
        (re-search-forward (concat "^ *" (org-agenda-count-lines-regexp (cdr match))) e))
        (cl-incf count))
      (forward-line)))
      count)))
;; Test: (org-agenda-count-lines " SCHEDULED: <2019-01-01>\n Some text.\n :PROPERTIES:\n :TEST: some line\n :END:\n\n\n")

(defun org-agenda-add-num-of-contents-lines ()
  "Add number of contents lines for each entry in the agenda buffer."
  ;; Text properties are already added.
  (message "Running org-agenda-add-num-of-contents-lines.")
  (org-agenda-delete-property-regions 'org-agenda-num-of-contents-lines t)
  (cl-loop with hd-marker
           for int being the intervals property 'org-hd-marker
           if (setq hd-marker (get-text-property (car int) 'org-hd-marker))
           do
           (goto-char (cdr int))
           (insert
        (propertize
         (format
          " (%d)"
          (org-agenda-count-lines (org-agenda-get-some-entry-text hd-marker (buffer-size))))
             'org-agenda-num-of-contents-lines t)
            )))

(add-hook 'org-agenda-finalize-hook #'org-agenda-add-num-of-contents-lines)

Example

The orgmode text

* TODO task 0
  SHEDULED: <2018-01-11 Thu 06:00>
  one line
  second line
  third line

* TODO task 1
  SHEDULED: <2018-01-11 Thu 07:00>

* TODO task 2
  SHEDULED: <2018-01-11 Thu 08:00>
  one line

gives the following agenda buffer:

Week-agenda (W02):
Monday      8 January 2018 W02
Tuesday     9 January 2018
Wednesday  10 January 2018
Thursday   11 January 2018
               4:13...... now - - - - - - - - - - - - - - - - - - - - - - - - -
  test:        6:00...... TODO task 0 (3)
  test:        7:00...... TODO task 1 (0)
  test:        8:00...... TODO task 2 (1)
               8:00...... ----------------
              10:00...... ----------------
              12:00...... ----------------
              14:00...... ----------------
              16:00...... ----------------
              18:00...... ----------------
              20:00...... ----------------
Friday     12 January 2018
Saturday   13 January 2018
Sunday     14 January 2018
7
  • Wow, looks great. That's more than I asked for, thanks very much. I just added an (unless…) statement around the last line to hide the entries that get (0) so it's less cluttered – I have a lot of those. There remains a weird effect, though: when I apply or remove a filter with org-agenda-filter-by-tag, the line counting cookies get duplicated…
    – schmuu
    Jan 11, 2018 at 22:37
  • @schmuu Could you try again with the modified version?
    – Tobias
    Jan 12, 2018 at 9:25
  • Worked fine, but for some reason it broke lately… Now there is no distinction between entries with zero text and entries with one line of text, plus the counted number is one to low: (0) for entries with no text or one line of text, (1) for two lines of text etc.
    – schmuu
    Feb 2, 2019 at 21:37
  • I somewhat clumsily remedies this problem by modifying the function as follows: (unless (equal "" (org-agenda-get-some-entry-text hd-marker (buffer-size))) (insert (propertize (format " (%d)" (1+ (cl-count ?\n (org-agenda-get-some-entry-text hd-marker (buffer-size))))) 'org-agenda-num-of-contents-lines t)
    – schmuu
    Feb 2, 2019 at 21:39
  • @schmuu Could it be that the problems occurred after an orgmode update? Please add version information to your question.
    – Tobias
    Feb 4, 2019 at 0:48

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.