Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Org-mode: Filter on tag in agenda view?

Tags:

emacs

org-mode

Is it possible to filter on tags when agenda constructs its view? I have tried the following to show only work related appointments:

("j" "Jobb"
   ((agenda ""
       ((org-agenda-skip-function '(org-agenda-skip-entry-if 'notregexp":jobb:"))))
    (tags-todo "jobb"))
    ((org-agenda-compact-blocks nil)))

This works only if the actual appointment is directly tagged, but not if the appointment inherits its tag from a parent headline like this:

 * Tider                                                              :jobb:                                                                                                                                                         
 ** Millas arbetstider                                                                                                                                                                                                               
   <2012-04-11 ons 05:00-09:00>                                                                                                                                                                                                     
   <2012-04-12 tor 04:15-08:30>                                                                                                                                                                                                     
   <2012-04-13 fre 14:30-18:30>                           

Is there another way to do this so that appointments that inherits its tag shows up?

like image 646
Fredrik Jambrén Avatar asked Apr 09 '12 13:04

Fredrik Jambrén


2 Answers

The issue is in how org-agenda-skip-entries-if interacts with 'notregexp. It will skip any entries that do not match :jobb:. Even though the later entries inherit the tag, it is not explicitly listed and so they are skipped. There also does not seem to be any built-in method to match (or not match) on tags using org-agenda-skip-entries-if. If there is such a function it would likely be the more efficient method of looking for the tags, but I'm not aware of such a function.

You instead have to create a custom function that will provide the desired search-format.

If you change your agenda command to:

("j" "Jobb"
         ((agenda ""
                  ((org-agenda-skip-function '(zin/org-agenda-skip-tag "jobb" 't))))
          (tags-todo "jobb"))
         ((org-agenda-compact-blocks nil)))

and define zin/org-agenda-skip-tag as:

(defun zin/org-agenda-skip-tag (tag &optional others)
  "Skip all entries that correspond to TAG.

If OTHERS is true, skip all entries that do not correspond to TAG."
  (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))
        (current-headline (or (and (org-at-heading-p)
                                   (point))
                              (save-excursion (org-back-to-heading)))))
    (if others
        (if (not (member tag (org-get-tags-at current-headline)))
            next-headline
          nil)
      (if (member tag (org-get-tags-at current-headline))
          next-headline
        nil))))

You will get what I understand to be your desired agenda view. If I have it backwards and the entries on the next 3 days should not be present, you simply have to change the function to (zin/org-agenda-skip-tag "jobb") or (zin/org-agenda-skip-tag "jobb" 'nil), they are equivalent in this case.

Agenda View

In this case test-new is the name of the org-file I was using, it can be ignored. I also set both headlines to TODO to have them visible when testing the function, since I was restricting the agenda to only the one file.

Week-agenda (W15):
Monday      9 April 2012 W15
Tuesday    10 April 2012
Wednesday  11 April 2012
  test-new:    5:00- 9:00 TODO Millas arbetstider                        :jobb::
Thursday   12 April 2012
  test-new:    4:15- 8:30 TODO Millas arbetstider                        :jobb::
Friday     13 April 2012
  test-new:   14:30-18:30 TODO Millas arbetstider                        :jobb::
Saturday   14 April 2012
Sunday     15 April 2012

================================================================================
Headlines with TAGS match: jobb
  test-new:   TODO Tider                                                  :jobb:
  test-new:   TODO Millas arbetstider                                    :jobb::
like image 171
Jonathan Leech-Pepin Avatar answered Oct 18 '22 07:10

Jonathan Leech-Pepin


After using the function in Jonathan's answer for several months to do this kind of per-block filtering, I found myself wanting something capable of dealing with more sophisticated queries (like the match strings used by the other block types), and I figured I'd post it here for anyone who stumbles across this question in the future.

EDIT: The original implementation of my/org-match-at-point-p is now somewhat out of date. The Org mode sources are now lexically scoped, which changes the contract of org-make-tags-matcher. It used to be that the todo and tags-list variables needed to be dynamically scoped around the call to org-make-tags-matcher, whereas now they seem to be passed to the function returned from the call. (Yay! This is so much better!) I've adapted the code below to match the new version, but I don't use Org mode much anymore, so it's only been lightly tested.

(defun my/org-match-at-point-p (match)
  "Return non-nil if headline at point matches MATCH.
Here MATCH is a match string of the same format used by
`org-tags-view'."
  (funcall (cdr (org-make-tags-matcher match))
           (org-get-todo-state)
           (org-get-tags-at)
           (org-reduced-level (org-current-level))))

(defun my/org-agenda-skip-without-match (match)
  "Skip current headline unless it matches MATCH.

Return nil if headline containing point matches MATCH (which
should be a match string of the same format used by
`org-tags-view').  If headline does not match, return the
position of the next headline in current buffer.

Intended for use with `org-agenda-skip-function', where this will
skip exactly those headlines that do not match." 
  (save-excursion
    (unless (org-at-heading-p) (org-back-to-heading)) 
    (let ((next-headline (save-excursion
                           (or (outline-next-heading) (point-max)))))
      (if (my/org-match-at-point-p match) nil next-headline))))

In case you're still using the older version of Org mode, here is the original code. Note that this version assumes that the file in which my/org-match-at-point-p is defined is lexically scoped; if not, you can safely replace the my/defun-dyn macro with a simple defun.

(defmacro my/defun-dyn (&rest def)
  "As `defun', but always in dynamic scope.
A function defined in this way ignores the value of
`lexical-binding' and treats it as if it were nil.

\(fn NAME ARGLIST &optional DOCSTRING DECL &rest BODY)"
  (declare (debug defun)
           (indent defun)
           (doc-string 3))
  `(eval '(defun ,@def) nil))

(my/defun-dyn my/org-match-at-point-p (match &optional todo-only)
  "Return non-nil if headline at point matches MATCH.
Here MATCH is a match string of the same format used by
`org-tags-view'.

If the optional argument TODO-ONLY is non-nil, do not declare a
match unless headline at point is a todo item."
  (let ((todo      (org-get-todo-state))
        (tags-list (org-get-tags-at)))
    (eval (cdr (org-make-tags-matcher match)))))
like image 43
Aaron Harris Avatar answered Oct 18 '22 05:10

Aaron Harris