Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Library-provided org-agenda-skip-function builder? #377

Open
7ocb opened this issue Oct 3, 2023 · 4 comments
Open

Library-provided org-agenda-skip-function builder? #377

7ocb opened this issue Oct 3, 2023 · 4 comments

Comments

@7ocb
Copy link

7ocb commented Oct 3, 2023

The org-ql is great for filtering the agenda, but as far as I can see it does not provide "out of the box" method to build filters suitable to be used in org-agenda-skip-function.

So I suggest to add one:

(defmacro org-ql-agenda-skip-if (query)
  (cl-labels (
             ;; recurses into list form and adds prefix
             ;; "org-ql--predicate-" for all functions except of "and", "or" and "not"
             (replace-predicate-names (form) 
               (if (listp form)
                   (let ((head (car form))
                         (tail (cdr form)))

                     (cons 
                      (cl-case head
                        ;; don't replace boolean expressions
                        ((and or not)
                         head)
                        ;; for other symbols add "org-ql--predicate-" prefix
                        (t (intern (concat "org-ql--predicate-" (symbol-name head)))))

                      ;; also replace for all args
                      (mapcar #'replace-predicate-names tail))
                     )
                 form)))
    `(lambda () 
       (save-excursion
         ;; org-ql predicates work best on the start of heading
         (outline-back-to-heading)

         (flet ((next-headline () (save-excursion
                                    (or (outline-next-heading) 
                                        (point-max)))))
           ;; if condition matched, go to next headline
           (when ,(replace-predicate-names query)
             (next-headline)))))))

This macro takes form, and adds "org-ql--predicate-" prefix to any symbol in function position except of "and", "or" and "not".

The usage is pretty straighforward:

(let ((org-agenda-custom-commands  
      '(
        ("t" "test" 
         agenda ""
         (
          (org-agenda-files '("/home/elk/test-for-org-ql.org"))
          ;; it can be just used inplace to build filter
          (org-agenda-skip-function 
           (org-ql-agenda-skip-if (and (tags "noise") 
                                       (not (tags "important"))))))))))
  (org-agenda nil "t")
)

test file:

* heading one :test:noise:
  <2023-10-03 Tue>
*** heading two :shmest:
    <2023-10-03 Tue>
*** heading three                                                    :shmest:extra:important:
    <2023-10-03 Tue>
*** heading four                                                     :shmest:
    <2023-10-03 Tue>

@yantar92
Copy link
Contributor

yantar92 commented Oct 3, 2023 via email

@alphapapa
Copy link
Owner

The org-ql is great for filtering the agenda,

org-ql is more intended as an alternative to org-agenda, i.e. org-ql-search provides a similar view with similar features (the buffer is actually in org-agenda-mode) and better performance.

but as far as I can see it does not provide "out of the box" method to build filters suitable to be used in org-agenda-skip-function.

That's because it's designed to replace org-agenda's backend, which is very slow by comparison. The notion of a "skip function" is obsoleted by org-ql's query functionality.

Nevertheless, org-agenda provides some features which aren't implemented in org-ql, so we provide some ways to integrate with it, like org-ql-block.

So I'm not opposed to offering a feature like this, for circumstances in which org-agenda is still necessary.

An important caveat to note is that using org-ql's predicates in an agenda skip function will skip the optimizations that org-ql performs when running a query, so most of the performance benefits will be discarded. So it would generally be better to use org-ql-search directly.

yantar92's code example is an interesting way to use a cache to avoid re-running the same skip function more than necessary; I'd probably decline to add that to org-ql as it seems confusing to me, but it's a neat hack.

I'd also recommend writing this feature with a function rather than a macro, as generally there's no need for it to be one.

Finally, the function that rewrites the query's symbols should take the org-ql-predicates variable into account rather than doing it on all unrecognized symbols. (org-ql used to work that way, early in its development, but having org-ql-predicate-- prefixing all of the query symbols was unpleasant to look at. Now and then, however, I consider whether it should work that way internally, rewriting the symbols before executing the query rather than using fset around the running of each query.)

@7ocb
Copy link
Author

7ocb commented Oct 5, 2023

@alphapapa

Please take a look:

It's now a function, not a macro (now it requires lexical bindings). Also it now converts by referring org-ql-predicates.

(defun org-ql-agenda-skip-if (query)
  (cl-labels (
              ;; recurses into list form and replaces all predicates which are
              ;; registered in org-ql-predicates
              (replace-predicate-names (form) 
                (if (listp form)
                    (let* ((fn (car form))
                           (as-predicate (alist-get fn org-ql-predicates))
                           (rest (cdr form)))

                      (cons 
                       (if as-predicate
                           (plist-get as-predicate :fn)
                         fn)

                       ;; also replace for all args
                       (mapcar #'replace-predicate-names rest))
                      )
                  form))

              (next-headline () (save-excursion
                                  (or (outline-next-heading) 
                                      (point-max)))))

    (let ((updated-query (replace-predicate-names query)))
      (lambda ()
        (save-excursion
          
          ;; org-ql predicates work best on the start of heading
          (outline-back-to-heading)

          ;; if condition matched, go to next headline
          (when (eval updated-query)
            (next-headline)))))))

@7ocb
Copy link
Author

7ocb commented Oct 5, 2023

@alphapapa

An important caveat to note is that using org-ql's predicates in an agenda skip function will skip the optimizations that org-ql performs when running a query, so most of the performance benefits will be discarded

For my case, using provided function speeds up generating (actually _re_generating, first generating still rather slow) big weekly agenda 5-7 times, so maybe some optimizations are lost, but still this is much faster than what I had before. And also org-ql provides much more predicates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants