From mboxrd@z Thu Jan 1 00:00:00 1970 From: Eric Abrahamsen Subject: Re: Dimming ancestors in the agenda (relevant to indenting nested TODOs in agenda views) Date: Sat, 24 Sep 2011 21:51:39 +0800 Message-ID: <87sjnmoxp0.fsf@ericabrahamsen.net> References: <87mxez8eze.fsf@ericabrahamsen.net> <87ipoyyuoq.fsf@ericabrahamsen.net> Mime-Version: 1.0 Content-Type: text/plain Return-path: Received: from eggs.gnu.org ([140.186.70.92]:60740) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1R7SeC-0005es-8x for emacs-orgmode@gnu.org; Sat, 24 Sep 2011 09:52:05 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1R7SeA-0007xH-MC for emacs-orgmode@gnu.org; Sat, 24 Sep 2011 09:52:04 -0400 Received: from lo.gmane.org ([80.91.229.12]:56204) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1R7SeA-0007x9-8k for emacs-orgmode@gnu.org; Sat, 24 Sep 2011 09:52:02 -0400 Received: from list by lo.gmane.org with local (Exim 4.69) (envelope-from ) id 1R7Se8-0002kv-Nm for emacs-orgmode@gnu.org; Sat, 24 Sep 2011 15:52:00 +0200 Received: from 125.33.23.247 ([125.33.23.247]) by main.gmane.org with esmtp (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Sat, 24 Sep 2011 15:52:00 +0200 Received: from eric by 125.33.23.247 with local (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Sat, 24 Sep 2011 15:52:00 +0200 List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: emacs-orgmode@gnu.org Okay, here's an attempt at indicating nested todos in the todo agenda view. The more I futzed with comparing consecutive TODOs the hackier it seemed, so I went with something more fundamental. Right now I think this is a bigger solution than the problem warranted, but it might also open the way to other interesting features, so I'm floating it here. With the attached patch, and the variable `org-agenda-todo-list-sublevels' set to anything but nil, `org-agenda-get-todos' will put todos into nested lists, rather than a flat list. While that's happening, TODOs can be formatted differently to indicate their depth within TODO subtrees. Currently that's hardcoded to the venerable leading dots, but symbol values for `org-agenda-todo-list-sublevels' could provide for a wider variety of formatting options, or a custom function. Then `org-finalize-agenda-entries' unwinds the nested lists back into a flat list (using a "flatten" pattern straight out of Paul Graham), applying the necessary filtering, highlighting and sorting functions along the way. One bonus is that each level of TODO subtrees gets sorted distinctly. Two questions: 1. Is this buggy or broken or unnecessarily slow? 2. Is this justified? Can we do other cool stuff with this? That's all. Hope it works! Eric diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index b1fa5f5..417566d 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -587,12 +587,19 @@ When nil, these trees are also scanned by agenda commands." :type 'boolean) (defcustom org-agenda-todo-list-sublevels t - "Non-nil means check also the sublevels of a TODO entry for TODO entries. -When nil, the sublevels of a TODO entry are not checked, resulting in -potentially much shorter TODO lists." + "How to display TODO entries that are sublevels of a TODO entry. +When nil, the sublevels of a TODO entry are not returned, +resulting in potentially much shorter TODO lists. When t, the +default, show all TODO entries as a flat list. 'indent and 'fade +change the way TODO entries that follow a parent TODO are +displayed." :group 'org-agenda-skip :group 'org-agenda-todo-list - :type 'boolean) + :type '(choice + (const :tag "Create a flat list of sublevels" t) + (const :tag "Do not list sublevel todos" nil) + (const :tag "Indent sublevel todos" indent) + (const :tag "Fade out sublevel todos" fade))) (defcustom org-agenda-todo-ignore-with-date nil "Non-nil means don't show entries with a date in the global todo list. @@ -4582,40 +4590,57 @@ the documentation of `org-diary'." (mapconcat 'identity (org-split-string org-select-this-todo-keyword "|") "\\|") - "\\)\\>")) + "\\)\\>")) org-not-done-regexp) "[^\n\r]*\\)")) - marker priority category category-pos tags todo-state ee txt beg end) - (goto-char (point-min)) - (while (re-search-forward regexp nil t) - (catch :skip - (save-match-data - (beginning-of-line) - (org-agenda-skip) - (setq beg (point) end (save-excursion (outline-next-heading) (point))) - (when (org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item end) - (goto-char (1+ beg)) - (or org-agenda-todo-list-sublevels (org-end-of-subtree 'invisible)) - (throw :skip nil))) - (goto-char (match-beginning 1)) - (setq marker (org-agenda-new-marker (match-beginning 0)) - category (org-get-category) - category-pos (get-text-property (point) 'org-category-position) - txt (match-string 1) - tags (org-get-tags-at (point)) - txt (org-format-agenda-item "" txt category tags) - priority (1+ (org-get-priority txt)) - todo-state (org-get-todo-state)) - (org-add-props txt props - 'org-marker marker 'org-hd-marker marker - 'priority priority 'org-category category - 'org-category-position category-pos - 'type "todo" 'todo-state todo-state) - (push txt ee) - (if org-agenda-todo-list-sublevels - (goto-char (match-end 1)) - (org-end-of-subtree 'invisible)))) - (nreverse ee))) + (depth 0)) + + (flet ((mk-td (start finish) + (goto-char start) + (let (marker priority category category-pos tags todo-state ee txt beg end) + (while (re-search-forward regexp finish t) + (catch :skip + (save-match-data + (beginning-of-line) + (org-agenda-skip) + (setq beg (point) end (save-excursion (outline-next-heading) (point))) + (when (org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item end) + (goto-char (1+ beg)) + (or org-agenda-todo-list-sublevels (org-end-of-subtree 'invisible)) + (throw :skip nil))) + (goto-char (match-beginning 1)) + (setq marker (org-agenda-new-marker (match-beginning 0)) + category (org-get-category) + category-pos (get-text-property (point) 'org-category-position) + txt (concat (if + (> depth 0) + (make-string (* 2 depth) ?.) + "") (match-string 1)) + tags (org-get-tags-at (point)) + txt (org-format-agenda-item "" txt category tags depth) + priority (1+ (org-get-priority txt)) + todo-state (org-get-todo-state)) + (org-add-props txt props + 'org-marker marker 'org-hd-marker marker + 'priority priority 'org-category category + 'org-category-position category-pos + 'type "todo" 'todo-state todo-state) + (if (not org-agenda-todo-list-sublevels) + (progn + (push txt ee) + (org-end-of-subtree 'invisible)) + (goto-char (match-end 0)) + (let* ((depth (1+ depth)) + (subtree-todos + (mk-td (point) + (save-excursion (org-end-of-subtree 'invisible) (point))))) + (when subtree-todos + (setq txt (cons txt (nreverse subtree-todos)))) + (push txt ee))))) + ee))) + (goto-char (point-min)) + (nreverse (mk-td (point) (point-max)))))) + (defun org-agenda-todo-custom-ignore-p (time n) "Check whether timestamp is farther away then n number of days. @@ -5785,12 +5810,18 @@ could bind the variable in the options section of a custom command.") (defun org-finalize-agenda-entries (list &optional nosort) "Sort and concatenate the agenda items." - (setq list (mapcar 'org-agenda-highlight-todo list)) - (if nosort - list - (when org-agenda-before-sorting-filter-function - (setq list (delq nil (mapcar org-agenda-before-sorting-filter-function list)))) - (mapconcat 'identity (sort list 'org-entries-lessp) "\n"))) + (flet ((flatten (l) + (cond + ((null l) nil) + ((atom l) (if (and org-agenda-before-sorting-filter-function + (not (org-agenda-before-sorting-filter-function l))) + nil + (list (org-agenda-highlight-todo l)))) + (t (append (flatten (car l)) + (flatten (if nosort + (cdr l) + (sort (cdr l) 'org-entries-lessp)))))))) + (mapconcat 'identity (flatten list) "\n"))) (defun org-agenda-highlight-todo (x) (let ((org-done-keywords org-done-keywords-for-agenda) @@ -5926,6 +5957,10 @@ could bind the variable in the options section of a custom command.") (defun org-entries-lessp (a b) "Predicate for sorting agenda entries." + (when (consp a) + (setq a (car a))) + (when (consp b) + (setq b (car b))) ;; The following variables will be used when the form is evaluated. ;; So even though the compiler complains, keep them. (let* ((ss org-agenda-sorting-strategy-selected)