Add: parent and ancestors predicates

These predicates are analogs to the existing "children" and
"descendents" predicates.

They may be useful for testing parents for specific todo states, or if
they have non-inherited tags (i.e. tags from
"org-tags-exclude-from-inheritance").
This commit is contained in:
Josh Moller-Mara 2019-12-18 01:01:47 +08:00 committed by Adam Porter
parent b8a4a53983
commit 65fb37c30d
2 changed files with 47 additions and 29 deletions

View file

@ -181,7 +181,9 @@ Arguments are listed next to predicate names, where applicable.
+ =category (&optional categories)= :: Return non-nil if current heading is in one or more of ~CATEGORIES~ (a list of strings). + =category (&optional categories)= :: Return non-nil if current heading is in one or more of ~CATEGORIES~ (a list of strings).
+ =children (&optional query)= :: Return non-nil if current heading has direct child headings. If ~QUERY~, test it against child headings. This selector may be nested, e.g. to match grandchild headings. + =children (&optional query)= :: Return non-nil if current heading has direct child headings. If ~QUERY~, test it against child headings. This selector may be nested, e.g. to match grandchild headings.
+ =parent (&optional query)= :: Return non-nil if current heading has a direct parent heading. If ~QUERY~, test it against the parent heading. This selector may be nested, e.g. to match grandparent headings.
+ =descendants (&optional query)= :: Return non-nil if current heading has descendant headings. If ~QUERY~, test it against descendant headings. This selector may be nested (if you can grok the nesting!). + =descendants (&optional query)= :: Return non-nil if current heading has descendant headings. If ~QUERY~, test it against descendant headings. This selector may be nested (if you can grok the nesting!).
+ =ancestors (&optional query)= :: Return non-nil if current heading has ancestor headings (which is true if it has a parent heading). With ~QUERY~, return non-nil if some ancestor heading matches it. This selector may also be nested.
+ =done= :: Return non-nil if entry's ~TODO~ keyword is in ~org-done-keywords~. + =done= :: Return non-nil if entry's ~TODO~ keyword is in ~org-done-keywords~.
+ =habit= :: Return non-nil if entry is a habit. + =habit= :: Return non-nil if entry is a habit.
+ =heading (&rest regexps)= :: Return non-nil if current entry's heading matches all ~REGEXPS~ (regexp strings). + =heading (&rest regexps)= :: Return non-nil if current entry's heading matches all ~REGEXPS~ (regexp strings).
@ -395,6 +397,7 @@ Expands into a call to ~org-ql-select~ with the same arguments. For convenience
- Negation of terms in plain queries using ~!~. For example, ~tags:space !moon~ to exclude entries which contain ~moon~. - Negation of terms in plain queries using ~!~. For example, ~tags:space !moon~ to exclude entries which contain ~moon~.
- Predicates =outline-path= (alias =olp=) and =outline-path-segment= (alias =olps=). - Predicates =outline-path= (alias =olp=) and =outline-path-segment= (alias =olps=).
- Predicate ~src~, which matches Org Babel source blocks. - Predicate ~src~, which matches Org Babel source blocks.
- Predicates =parent= and =ancestors=.
- Alias =h= for =heading= predicate. - Alias =h= for =heading= predicate.
- Alias =r= for =regexp= predicate. (Thanks to [[https://github.com/tumashu][Feng Shu]].) - Alias =r= for =regexp= predicate. (Thanks to [[https://github.com/tumashu][Feng Shu]].)
+ Info manual. + Info manual.

View file

@ -548,6 +548,10 @@ Replaces bare strings with (regexp) selectors, and appropriate
(`(children) '(children (lambda () t))) (`(children) '(children (lambda () t)))
(`(descendants ,query) `(descendants ',query)) (`(descendants ,query) `(descendants ',query))
(`(descendants) '(descendants (lambda () t))) (`(descendants) '(descendants (lambda () t)))
(`(parent ,query) `(parent ,(org-ql--query-predicate (rec query))))
(`(parent) `(parent ,(org-ql--query-predicate (rec '(lambda () t)))))
(`(ancestors ,query) `(ancestors ,(org-ql--query-predicate (rec query))))
(`(ancestors) `(ancestors ,(org-ql--query-predicate (rec '(lambda () t)))))
;; Timestamp-based predicates. I think this is the way that makes the most sense: ;; Timestamp-based predicates. I think this is the way that makes the most sense:
;; set the limit to N days in the future, adjusted to 23:59:59 (since Org doesn't ;; set the limit to N days in the future, adjusted to 23:59:59 (since Org doesn't
;; support timestamps down to the second, anyway, there should be no need to adjust ;; support timestamps down to the second, anyway, there should be no need to adjust
@ -931,38 +935,49 @@ Arguments STRING, POS, FILL, and LEVEL are according to
(org-ql--defpred children (query) (org-ql--defpred children (query)
"Return non-nil if current entry has children matching QUERY." "Return non-nil if current entry has children matching QUERY."
(save-excursion (org-with-wide-buffer
(save-restriction ;; Widening is needed if inside an "ancestors" query
(org-narrow-to-subtree) (org-narrow-to-subtree)
(when (org-goto-first-child) (when (org-goto-first-child)
;; Lisp makes this easy and elegant: all we do is modify the query, ;; Lisp makes this easy and elegant: all we do is modify the query,
;; nesting it inside an (and), and it doesn't descend into grandchildren. ;; nesting it inside an (and), and it doesn't descend into grandchildren.
(let* ((level (org-current-level)) (let* ((level (org-current-level))
(query (cl-typecase query (query (cl-typecase query
(byte-code-function `(and (level ,level) (byte-code-function `(and (level ,level)
(funcall ,query))) (funcall ,query)))
(t `(and (level ,level) (t `(and (level ,level)
,query))))) ,query)))))
(catch 'found (catch 'found
(org-ql-select (current-buffer) (org-ql-select (current-buffer)
query query
:narrow t :narrow t
:action (lambda () :action (lambda ()
(throw 'found t))))))))) (throw 'found t))))))))
(org-ql--defpred parent (predicate)
"Return non-nil if the current entry's parent satisfies PREDICATE."
(org-with-wide-buffer
(when (org-up-heading-safe)
(org-ql--value-at (point) predicate))))
(org-ql--defpred descendants (query) (org-ql--defpred descendants (query)
"Return non-nil if current entry has descendants matching QUERY." "Return non-nil if current entry has descendants matching QUERY."
(save-excursion (org-with-wide-buffer
(save-restriction (org-narrow-to-subtree)
(org-narrow-to-subtree) (when (org-goto-first-child)
(when (org-goto-first-child) (narrow-to-region (point) (point-max))
(narrow-to-region (point) (point-max)) (catch 'found
(catch 'found (org-ql-select (current-buffer)
(org-ql-select (current-buffer) query
query :narrow t
:narrow t :action (lambda ()
:action (lambda () (throw 'found t)))))))
(throw 'found t))))))))
(org-ql--defpred ancestors (predicate)
"Return non-nil if any of current entry's ancestors satisfy PREDICATE."
(org-with-wide-buffer
(cl-loop while (org-up-heading-safe)
thereis (org-ql--value-at (point) predicate))))
(org-ql--defpred category (&rest categories) (org-ql--defpred category (&rest categories)
"Return non-nil if current heading is in one or more of CATEGORIES (a list of strings)." "Return non-nil if current heading is in one or more of CATEGORIES (a list of strings)."