Browse Source

Merge branch 'improve-consistency-in-filter-commands'

Carsten Dominik 3 months ago
parent
commit
6f32e7af88
2 changed files with 149 additions and 133 deletions
  1. 37 45
      doc/org-manual.org
  2. 112 88
      lisp/org-agenda.el

+ 37 - 45
doc/org-manual.org

@@ -8928,12 +8928,10 @@ If you would like to have a special category for a single entry or
 a (sub)tree, give the entry a =CATEGORY= property with the special
 category you want to apply as the value.
 
-The display in the agenda buffer looks best if the category is not
-longer than 10 characters.
-
 #+vindex: org-agenda-category-icon-alist
-You can set up icons for category by customizing the
-~org-agenda-category-icon-alist~ variable.
+The display in the agenda buffer looks best if the category is not
+longer than 10 characters.  You can set up icons for category by
+customizing the ~org-agenda-category-icon-alist~ variable.
 
 *** Time-of-day specifications
 :PROPERTIES:
@@ -9039,20 +9037,16 @@ the estimated effort of an entry (see [[*Effort Estimates]]).
 #+vindex: org-agenda-tag-filter-preset
 #+vindex: org-agenda-effort-filter-preset
 #+vindex: org-agenda-regexp-filter-preset
-Agenda built-in or customized commands are statically defined.  Agenda
-filters and limits provide two ways of narrowing down the list of
-agenda entries.
+Agenda built-in or custom commands are statically defined.  Agenda
+filters and limits allow to flexibly narrow down the list of agenda
+entries.
 
-Filters only change the visibility of items, are very fast and are
+/Filters/ only change the visibility of items, are very fast and are
 mostly used interactively[fn:96]. You can switch quickly between
-different filters without having to recreate the agenda.  If creating
-the agenda seems slow, one solution would be to create a view that
-contains everything you might want to work on for a while, and then
-use filtering to drill down.
-
-Limits on the other hand take effect before the agenda buffer is
-populated, so they are mostly useful when defined as local variables
-within custom agenda commands.
+different filters without having to recreate the agenda.  /Limits/ on
+the other hand take effect before the agenda buffer is populated, so
+they are mostly useful when defined as local variables within custom
+agenda commands.
 
 **** Filtering in the agenda
 :PROPERTIES:
@@ -9068,7 +9062,11 @@ within custom agenda commands.
 
 The general filtering command is ~org-agenda-filter~, bound to
 {{{kbd(/)}}}.  Before we introduce it, we describe commands for
-individual filter types.
+individual filter types.  All filtering commands handle prefix
+arguments in the same way:  A single {{{kbd(C-u)}}} prefix negates the
+filter, so it removes lines selected by the filter.  A double prefix
+adds the new filter condition to the one(s) already in place, so
+filter elements are accumulated.
 
 - {{{kbd(\)}}} (~org-agenda-filter-by-tag~) ::
 
@@ -9078,37 +9076,32 @@ individual filter types.
   Pressing {{{kbd(TAB)}}} at that prompt offers completion to select a
   tag, including any tags that do not have a selection character.  The
   command then hides all entries that do not contain or inherit this
-  tag.  Call the command repeatedly to add several tags to the
-  filter. When called with prefix argument, remove the entries that
-  /do/ have the tag.  Pressing {{{kbd(+)}}} or {{{kbd(-)}}} at the
-  prompt also switches between filtering for and against the next tag.
-  {{{kbd(\)}}} at the prompt turns off the filter and shows any hidden
-  entries.
-
+  tag.  Pressing {{{kbd(+)}}} or {{{kbd(-)}}} at the prompt switches
+  between filtering for and against the next tag.  To clear the
+  filter, press {{{kbd(\)}}} twice (once to call the command again,
+  and once at the prompt).
 
 - {{{kbd(<)}}} (~org-agenda-filter-by-category~) ::
 
   #+findex: org-agenda-filter-by-category
   Filter by category of the line at point, and show only entries with
-  this category.  Pressing {{{kbd(<)}}} again removes this filter.
-  When called with a prefix argument exclude the category of the item
-  at point from the agenda.
+  this category.  When called with a prefix argument, hide all entries
+  with the category at point.  To clear the filter, call this command
+  again by pressing {{{kbd(<)}}}.
 
 - {{{kbd(=)}}} (~org-agenda-filter-by-regexp~) ::
 
   #+findex: org-agenda-filter-by-regexp
   Filter the agenda view by a regular expression: only show agenda
-  entries matching the regular expression the user entered.  When
-  called with a prefix argument, it filters /out/ entries matching the
-  regexp.  Called in a regexp-filtered agenda view, remove the filter,
-  unless there are two universal prefix arguments, in which case
-  filters are accumulated.
+  entries matching the regular expression the user entered. To clear
+  the filter, call the command again by pressing {{{kbd(=)}}}.
 
 - {{{kbd(_)}}} (~org-agenda-filter-by-effort~) ::
 
   #+findex: org-agenda-filter-by-effort
-  Filter the agenda view with respect to effort estimates.  You first
-  need to set up allowed efforts globally, for example
+  Filter the agenda view with respect to effort estimates, so select
+  tasks that take the right amount of time.  You first need to set up
+  a list of efforts globally, for example
 
   #+begin_src emacs-lisp
   (setq org-global-properties
@@ -9123,18 +9116,16 @@ individual filter types.
   restricts to entries with effort smaller-or-equal, equal, or
   larger-or-equal than the selected value.  For application of the
   operator, entries without a defined effort are treated according to
-  the value of ~org-sort-agenda-noeffort-is-high~.
-
-  When called with a prefix argument, it removes entries matching the
-  condition.  With two universal prefix arguments, it clears effort
-  filters, which can be accumulated.
+  the value of ~org-sort-agenda-noeffort-is-high~.  To clear the
+  filter, press {{{kbd(_)}}} twice (once to call the command again,
+  and once at the first prompt).
 
 - {{{kbd(^)}}} (~org-agenda-filter-by-top-headline~) ::
 
   #+findex: org-agenda-filter-by-top-headline
   Filter the current agenda view and only display items that fall
-  under the same top-level headline as the current entry.  Press
-  {{{kbd(^)}}} again to turn this filter off.
+  under the same top-level headline as the current entry.  To clear
+  the filter, call this command again by pressing {{{kbd(^)}}}.
 
 - {{{kbd(/)}}} (~org-agenda-filter~) ::
 
@@ -9155,9 +9146,10 @@ individual filter types.
   (tags will take priority).  If you reply to the prompt with the
   empty string, all filtering is removed.  If a filter is specified,
   it replaces all current filters.  But if you call the command with a
-  prefix argument, or if you add an additional `+' (e.g. `++work') to
-  the front of the string, the new filter elements are added to the
-  active ones.
+  double prefix argument, or if you add an additional `+'
+  (e.g. `++work') to the front of the string, the new filter elements
+  are added to the active ones.  A single prefix argument applies the
+  entire filter in a negative sense.
 
 - {{{kbd(|)}}} (~org-agenda-filter-remove-all~) ::
 

+ 112 - 88
lisp/org-agenda.el

@@ -7468,8 +7468,8 @@ With a prefix argument, do so in all agenda buffers."
 (defun org-agenda-filter-by-category (strip)
   "Filter lines in the agenda buffer that have a specific category.
 The category is that of the current line.
-Without prefix argument, keep only the lines of that category.
-With a prefix argument, exclude the lines of that category."
+With a `\\[universal-argument]' prefix argument, exclude the lines of that category.
+When there is already a category filter in place, this command removes the filter."
   (interactive "P")
   (if (and org-agenda-filtered-by-category
 	   org-agenda-category-filter)
@@ -7499,7 +7499,8 @@ search from."
 (defvar org-agenda-filtered-by-top-headline nil)
 (defun org-agenda-filter-by-top-headline (strip)
   "Keep only those lines that are descendants from the same top headline.
-The top headline is that of the current line."
+The top headline is that of the current line.  With prefix arg STRIP, hide
+all lines of the category at point."
   (interactive "P")
   (if org-agenda-filtered-by-top-headline
       (progn
@@ -7511,77 +7512,85 @@ The top headline is that of the current line."
         (error "No top-level headline at point")))))
 
 (defvar org-agenda-regexp-filter nil)
-(defun org-agenda-filter-by-regexp (strip)
-  "Filter agenda entries by regular expressions.
-
-With one prefix argument, filter out entries matching the regexp.
-If there is already a regexp filter, remove it unless called with
-two prefix arguments."
+(defun org-agenda-filter-by-regexp (strip-or-accumulate)
+  "Filter agenda entries by a regular expressions.
+You will be prompted for the regular expresssion, and the agenda
+view will only show entires that are matched by that expression.
+
+With one `\\[universal-argument]' prefix argument, hide entries matching the regexp.
+When there is already a regexp filter active, this command removed the
+filter.  However, with two `\\[universal-argument]' prefix arguments, add a new condition to
+an already existing regexp filter."
   (interactive "P")
-  (cond
-   ((and org-agenda-regexp-filter (not (equal strip '(16))))
-    (org-agenda-filter-show-all-re)
-    (message "Regexp filter removed"))
-   (t (let ((flt (concat (if (equal strip '(4)) "-" "+")
-			 (read-from-minibuffer
-			  (if (equal strip '(4))
-			      "Filter out entries matching regexp: "
-			    "Narrow to entries matching regexp: ")))))
-	(push flt org-agenda-regexp-filter)
-	(org-agenda-filter-apply org-agenda-regexp-filter 'regexp)))))
-
+  (let* ((strip (equal strip-or-accumulate '(4)))
+	 (accumulate (equal strip-or-accumulate '(16))))
+    (cond
+     ((and org-agenda-regexp-filter (not accumulate))
+      (org-agenda-filter-show-all-re)
+      (message "Regexp filter removed"))
+     (t (let ((flt (concat (if strip "-" "+")
+			   (read-from-minibuffer
+			    (if strip
+				"Hide entries matching regexp: "
+			      "Narrow to entries matching regexp: ")))))
+	  (push flt org-agenda-regexp-filter)
+	  (org-agenda-filter-apply org-agenda-regexp-filter 'regexp))))))
+  
 (defvar org-agenda-effort-filter nil)
-(defun org-agenda-filter-by-effort (strip)
+(defun org-agenda-filter-by-effort (strip-or-accumulate)
   "Filter agenda entries by effort.
-With no prefix argument, keep entries matching the effort condition.
-With one prefix argument, filter out entries matching the condition.
-With two prefix arguments, remove the effort filters."
+With no `\\[universal-argument]' prefix argument, keep entries matching the effort condition.
+With one `\\[universal-argument]' prefix argument, filter out entries matching the condition.
+With two `\\[universal-argument]' prefix arguments, add a second condition to the existing filter.
+This last option is in practice not very useful, but it is available for
+consistency with the other filter commands."
   (interactive "P")
-  (cond
-   ((member strip '(nil 4))
-    (let* ((efforts (split-string
-		     (or (cdr (assoc (concat org-effort-property "_ALL")
-				     org-global-properties))
-			 "0 0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00")))
-	   ;; XXX: the following handles only up to 10 different
-	   ;; effort values.
-	   (allowed-keys (if (null efforts) nil
-			   (mapcar (lambda (n) (mod n 10)) ;turn 10 into 0
-				   (number-sequence 1 (length efforts)))))
-	   (op nil))
-      (while (not (memq op '(?< ?> ?= ?_)))
-	(setq op (read-char-exclusive "Effort operator? (> = or <)     or press `_' again to remove filter")))
-      ;; Select appropriate duration.  Ignore non-digit characters.
-      (if (eq op ?_)
-	  (progn
-	    (org-agenda-filter-show-all-effort)
-	    (message "Effort filter removed"))
-	(let ((prompt
-	       (apply #'format
-		      (concat "Effort %c "
-			      (mapconcat (lambda (s) (concat "[%d]" s))
-					 efforts
-					 " "))
-		      op allowed-keys))
-	      (eff -1))
-	  (while (not (memq eff allowed-keys))
-	    (message prompt)
-	    (setq eff (- (read-char-exclusive) 48)))
-	  (setq org-agenda-effort-filter
-		(list (concat (if strip "-" "+")
-			      (char-to-string op)
-			      ;; Numbering is 1 2 3 ... 9 0, but we want
-			      ;; 0 1 2 ... 8 9.
-			      (nth (mod (1- eff) 10) efforts)))))
-	(org-agenda-filter-apply org-agenda-effort-filter 'effort))))
-   (t (org-agenda-filter-show-all-effort)
-      (message "Effort filter removed"))))
-
-
-(defun org-agenda-filter (&optional keep)
+  (let* ((efforts (split-string
+		   (or (cdr (assoc (concat org-effort-property "_ALL")
+				   org-global-properties))
+		       "0 0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00")))
+	 ;; XXX: the following handles only up to 10 different
+	 ;; effort values.
+	 (allowed-keys (if (null efforts) nil
+			 (mapcar (lambda (n) (mod n 10)) ;turn 10 into 0
+				 (number-sequence 1 (length efforts)))))
+	 (keep (equal strip-or-accumulate '(16)))
+	 (negative (equal strip-or-accumulate '(4)))
+	 (current org-agenda-effort-filter)
+	 (op nil))
+    (while (not (memq op '(?< ?> ?= ?_)))
+      (setq op (read-char-exclusive
+		"Effort operator? (> = or <)     or press `_' again to remove filter")))
+    ;; Select appropriate duration.  Ignore non-digit characters.
+    (if (eq op ?_)
+	(progn
+	  (org-agenda-filter-show-all-effort)
+	  (message "Effort filter removed"))
+      (let ((prompt
+	     (apply #'format
+		    (concat "Effort %c "
+			    (mapconcat (lambda (s) (concat "[%d]" s))
+				       efforts
+				       " "))
+		    op allowed-keys))
+	    (eff -1))
+	(while (not (memq eff allowed-keys))
+	  (message prompt)
+	  (setq eff (- (read-char-exclusive) 48)))
+	(org-agenda-filter-show-all-effort)
+	(setq org-agenda-effort-filter
+	      (append
+	       (list (concat (if negative "-" "+")
+			     (char-to-string op)
+			     ;; Numbering is 1 2 3 ... 9 0, but we want
+			     ;; 0 1 2 ... 8 9.
+			     (nth (mod (1- eff) 10) efforts)))
+	       (if keep current nil)))
+	(org-agenda-filter-apply org-agenda-effort-filter 'effort)))))
+
+
+(defun org-agenda-filter (&optional strip-or-accumulate)
   "Prompt for a general filter string and apply it to the agenda.
-The new filter replaces all existing elements.  When called with a
-prefix arg KEEP, add the new elements to the existing filter.
 
 The string may contain filter elements like
 
@@ -7604,14 +7613,17 @@ values is offered.  Since the syntax for categories and tags is identical
 there should be no overlap between categoroes and tags.  If there is, tags
 get priority.
 
-Instead of using the prefix argument to add to the current filter
-set, you can also add an additional leading `+' to filter string,
-like `+-John'.
+A single `\\[universal-argument]' prefix arg STRIP-OR-ACCUMULATE will negate the
+entire filter, which can be useful in connection with the prompt history.
 
-With a double prefix argument, execute the computed filtering defined in
+A double `\\[universal-argument] \\[universal-argument]' prefix arg will add the new filter elements to the
+existing ones. A shortcut for this is to add an additional `+' at the
+beginning of the string, like `+-John'.
+
+With a triple prefix argument, execute the computed filtering defined in
 the variable `org-agenda-auto-exclude-function'."
   (interactive "P")
-  (if (equal keep '(16))
+  (if (equal strip-or-accumulate '(64))
       ;; Execute the auto-exclude action
       (if (not org-agenda-auto-exclude-function)
 	  (user-error "`org-agenda-auto-exclude-function' is undefined")
@@ -7626,11 +7638,15 @@ the variable `org-agenda-auto-exclude-function'."
     ;; Prompt for a filter and act
     (let* ((tag-list (org-agenda-get-represented-tags))
 	   (category-list (org-agenda-get-represented-categories))
-	   (f-string (completing-read "Filter [+cat-tag<0:10-/regexp/]: "
-				      'org-agenda-filter-completion-function))
+	   (negate (equal strip-or-accumulate '(4)))
+	   (f-string (completing-read
+		      (concat
+		       (if negate "Negative filter" "Filter")
+		       " [+cat-tag<0:10-/regexp/]: ")
+		      'org-agenda-filter-completion-function))
 	   (keep (or (if (string-match "^+[-+]" f-string)
 			 (progn (setq f-string (substring f-string 1)) t))
-		     keep))
+		     (equal strip-or-accumulate '(16))))
 	   (fc (if keep org-agenda-category-filter))
 	   (ft (if keep org-agenda-tag-filter))
 	   (fe (if keep org-agenda-effort-filter))
@@ -7638,6 +7654,8 @@ the variable `org-agenda-auto-exclude-function'."
 	   pm s)
       (while (string-match "^[ \t]*\\([-+]\\)?\\(\\([^-+<>=/ \t]+\\)\\|\\([<>=][0-9:]+\\)\\|\\(/\\([^/]+\\)/?\\)\\)" f-string)
 	(setq pm (if (match-beginning 1) (match-string 1 f-string) "+"))
+	(when negate
+	  (setq pm (if (equal pm "+") "-" "+")))
 	(cond
 	 ((match-beginning 3)
 	  ;; category or tag
@@ -7648,7 +7666,7 @@ the variable `org-agenda-auto-exclude-function'."
 	   ((member s category-list)
 	    (add-to-list 'fc (concat pm s) 'append 'equal))
 	   (t (message
-	       "`%s%s' filter ignored tag/category is not represented"
+	       "`%s%s' filter ignored because tag/category is not represented"
 	       pm s))))
 	 ((match-beginning 4)
 	  ;; effort
@@ -7718,14 +7736,17 @@ which see."
     (org-agenda-filter-show-all-effort))
   (org-agenda-finalize))
 
-(defun org-agenda-filter-by-tag (arg &optional char exclude)
+(defun org-agenda-filter-by-tag (strip-or-accumulate &optional char exclude)
   "Keep only those lines in the agenda buffer that have a specific tag.
 
 The tag is selected with its fast selection letter, as configured.
 
-With a `\\[universal-argument]' prefix, exclude the agenda search.
+With a `\\[universal-argument]' prefix, apply the filter negatively, stripping all matches.
+
+With a `\\[universal-argument] \\[universal-argument]' prefix, add the new tag to the existing filter
+instead of replacing it.
 
-With a `\\[universal-argument] \\[universal-argument]' prefix, filter the literal tag, \
+With a `\\[universal-argument] \\[universal-argument] \\[universal-argument]' prefix, filter the literal tag, \
 i.e. don't
 filter on all its group members.
 
@@ -7746,8 +7767,9 @@ also press `-' or `+' to switch between filtering and excluding."
 		     org-tag-alist-for-agenda ""))
 	 (valid-char-list (append '(?\t ?\r ?\\ ?. ?\s ?q)
 				  (string-to-list tag-chars)))
-	 (exclude (or exclude (equal arg '(4))))
-	 (expand (not (equal arg '(16))))
+	 (exclude (or exclude (equal strip-or-accumulate '(4))))
+	 (accumulate (equal strip-or-accumulate '(16)))
+	 (expand (not (equal strip-or-accumulate '(64))))
 	 (inhibit-read-only t)
 	 (current org-agenda-tag-filter)
 	 a n tag)
@@ -7764,7 +7786,7 @@ also press `-' or `+' to switch between filtering and excluding."
 	(cond ((eq char ?-) (setq exclude t))
 	      ((eq char ?+) (setq exclude nil)))))
     (when (eq char ?\t)
-      (unless (local-variable-p 'org-global-tags-completion-table (current-buffer))
+      (unless (local-variable-p 'org-global-tags-completion-table)
 	(setq-local org-global-tags-completion-table
 		    (org-global-tags-completion-table)))
       (let ((completion-ignore-case t))
@@ -7798,7 +7820,7 @@ also press `-' or `+' to switch between filtering and excluding."
       (setq tag (car a))
       (setq org-agenda-tag-filter
 	    (cons (concat (if exclude "-" "+") tag)
-		  current))
+		  (if accumulate current nil)))
       (org-agenda-filter-apply org-agenda-tag-filter 'tag expand))
      (t (error "Invalid tag selection character %c" char)))))
 
@@ -7890,7 +7912,7 @@ function to set the right switches in the returned form."
     (dolist (x tags (cons (if (eq op ?-) 'and 'or) form))
       (let* ((tag (substring x 1))
 	     (f (cond
-		 ((string= "" tag) '(not tags))
+		 ((string= "" tag) 'tags)
 		 ((and (string-match-p "\\`{" tag) (string-match-p "}\\'" tag))
 		  ;; TAG is a regexp.
 		  (list 'org-match-any-p (substring tag 1 -1) 'tags))
@@ -7945,7 +7967,8 @@ tags in the FILTER if any of the tags in FILTER are grouptags."
   ;; Deactivate `org-agenda-entry-text-mode' when filtering
   (when org-agenda-entry-text-mode (org-agenda-entry-text-mode))
   (let (tags cat txt)
-    (setq org-agenda-filter-form (org-agenda-filter-make-matcher filter type expand))
+    (setq org-agenda-filter-form (org-agenda-filter-make-matcher
+				  filter type expand))
     ;; Only set `org-agenda-filtered-by-category' to t when a unique
     ;; category is used as the filter:
     (setq org-agenda-filtered-by-category
@@ -7998,7 +8021,8 @@ tags in the FILTER if any of the tags in FILTER are grouptags."
   (save-excursion
     (goto-char (point-min))
     (let ((inhibit-read-only t) pos)
-      (while (setq pos (text-property-any (point) (point-max) 'org-filter-type type))
+      (while (setq pos (text-property-any (point) (point-max)
+					  'org-filter-type type))
 	(goto-char pos)
 	(remove-text-properties
 	 (point) (next-single-property-change (point) 'org-filter-type)