Well, I think it’s unreasonable to ask users to reinforce manually and continuously something they’ve already specified in their settings. Besides, my example was intentionally trivial—imagine managing a large tree and having to remember which tags are mutually exclusive to what, all the time.
But, no matter! I have created a patch! It works great, even for edge cases like when an entry has two tags that are set as mutually exclusive to each other. First, we have two new functions: one which returns the tags currently defined as mutually exclusive, and one which checks for tags that conflict with the given tag (with the help of the cl-loop “if” clause and cl-set-difference). (defun org-get-exclusive-tags (&optional alist) "Return a list of mutually exclusive tags occuring in ALIST. ALIST defaults to `org-current-tag-alist'. The result is a list of lists of strings, where each string represents a tag, and each list of strings represents a group of mutually exclusive tags." ;; Most of this code was culled from `org-fast-tag-selection' (let ((alist (or alist org-current-tag-alist)) groups ingroup intaggroup tag) (dolist (e alist groups) (cond ((eq (car e) :startgroup) (push '() groups) (setq ingroup t)) ((eq (car e) :endgroup) (setq ingroup nil)) ((eq (car e) :startgrouptag) (setq intaggroup t)) ((eq (car e) :endgrouptag) (setq intaggroup nil)) ((equal e '(:newline))) ; do nothing ((equal e '(:grouptags))) ; do nothing (t (setq tag (copy-sequence (car e))) (when ingroup (push tag (car groups)))))))) (defun org-get-conflicting-tags (tags &optional alist) "For list TAGS, return tags which would conflict according to ALIST. ALIST defaults to `org-current-tag-alist'. For more information on mutually exclusive tags, see Info node `(org)Tag Hierarchy'." (let* ((alist (or alist org-current-tag-alist)) (groups (org-get-exclusive-tags alist))) (cl-loop for group in groups if (cl-some (lambda (x) (member x group)) tags) append (cl-set-difference group tags :test 'equal)))) Then, we redefine org-get-tags slightly to remove conflicting tags from the list of inherited tags. (defun org-get-tags (&optional pos local) ;; "the usual docstring, skipped here for email brevity" (if (and org-trust-scanner-tags (or (not pos) (eq pos (point))) (not local)) org-scanner-tags (org-with-point-at (or pos (point)) (unless (org-before-first-heading-p) (org-back-to-heading t) (let ((ltags (org--get-local-tags)) itags) (if (or local (not org-use-tag-inheritance)) ltags (let* ((conflicting-tags (org-get-conflicting-tags ltags)) (ct-predicate (lambda (x) (member x conflicting-tags)))) (while (org-up-heading-safe) (nconc conflicting-tags (org-get-conflicting-tags itags org-current-tag-alist)) (setq itags (append (mapcar #'org-add-prop-inherited (cl-remove-if ct-predicate (org--get-local-tags))) itags))) (setq itags (append (cl-remove-if ct-predicate org-file-tags) itags))) (delete-dups (append (org-remove-uninherited-tags itags) ltags)))))))) If everything looks good, I’ll figure out how to submit it as a proper Git patch (right now it’s just “code that’s been sitting in my config for weeks”), and do the copyright-assignment thing so we can get it merged. I hope you like it! —Tina On Sat, Feb 9, 2019 at 10:03 AM Nicolas Goaziou <m...@nicolasgoaziou.fr> wrote: > > Hello, > > Tina Russell <tinakellyruss...@gmail.com> writes: > > > So, according to the Org documentation: “You can also group together tags > > that are mutually exclusive by using braces … Selecting a tag in a group of > > mutually exclusive tags will turn off any other tags from that group. > > > > But, if I do this… > > > > #+TAGS: { place(c) container(c) object(o) } > > > > * Room :place: > > ** Box :container: > > *** Toy :object: > > > > …and then use (org-get-tags) on “Toy,” it reports that it has the tags > > “place”, “container”, and “object”, even though these tags are all defined > > to be mutually exclusive! This is a problem, > > Not really. `org-get-tags' is a low-level function, i.e., it has no > knowledge about tag groups or mutually exclusive tags. > > > since turning off tag > > inheritance (for a document or for specific tags) seems to be an > > all-or-nothing affair. That means if I wanted to do this: > > > > * Room :place: > > ** Bookcase > > ** Dresser > > ** Desk > > ** Nightstand > > ** Closet > > *** Box :container: > > **** Toy :object: > > > > …and then search for all headings with the tag “place,” either (with tag > > inheritance) everything, including “Box” and “Toy,” will be returned, or > > (without tag inheritance) only “Room” would be returned. (I could put a tag > > on every heading where I want it inherited, but that would both defeat the > > purpose of inheritance and make it difficult to manage large trees.) > > You don't need to use mutually exclusive tags for this example. You > could search for "place-container", assuming tag inheritance. > > Regards, > > -- > Nicolas Goaziou