unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Tino Calancha <tino.calancha@gmail.com>
To: Christopher Genovese <genovese@cmu.edu>
Cc: tino.calancha@gmail.com, emacs-devel <emacs-devel@gnu.org>
Subject: Re: Ibuffer improvements: filtering, documentation, bug fix, tests
Date: Sat, 03 Dec 2016 00:56:36 +0900	[thread overview]
Message-ID: <87twamnx3f.fsf@gmail.com> (raw)
In-Reply-To: <CAPum5Fh2h3BoeF=GYmTJH=b5p2WVrQ7e4E5quwPobR3Z5p1XWQ@mail.gmail.com> (Christopher Genovese's message of "Wed, 30 Nov 2016 22:10:58 -0500")

Christopher Genovese <genovese@cmu.edu> writes:

> Tino,
>
>   Sorry it took so long to get this to you; it's been a crazy week.
> I've attached a patch file with all the changes we have discussed
> (except one, see below) to the code, change logs, and NEWS
Without the fix to Bug#25049 is easier to review.  Thank you!
You made a great job and very fast!

I have divided the patch in two parts.

I) The first part includes all but the reassignment of original keybindings,
that is, it keeps `ibuffer-filter-disable' and `ibuffer-exchange-filters'
bound to '//' and '/TAB' respectively.
It also includes following trivial changes:

1) I have added (ignore qualifier) to silent the byte compiler
in `ibuffer-filter-by-starred-name', `ibuffer-filter-by-modified'
and `ibuffer-filter-by-visiting-file'.

2) Catched a typo in the menu definition of filter-by-visiting-file;
dropped the '...' in those menu entries not prompting user for
an argument.
     (define-key-after map [menu-bar view filter filter-by-filename]
-      '(menu-item "Add filter by full pathname..." ibuffer-filter-by-filename
+      '(menu-item "Add filter by full filename..." ibuffer-filter-by-filename
     (define-key-after map [menu-bar view filter filter-by-modified]
-      '(menu-item "Add filter by modified buffer..." ibuffer-filter-by-modified
+      '(menu-item "Add filter by modified buffer" ibuffer-filter-by-modified
                   :help "List buffers that are marked as modified"))
     (define-key-after map [menu-bar view filter filter-by-visiting-file]
-      '(menu-item "Add filter by modified buffer..."
+      '(menu-item "Add filter by buffer visiting a file"
                   ibuffer-filter-by-visiting-file

II) The second part of the patch completes your proposal:
it binds `ibuffer-filter-chosen-by-completion' and`ibuffer-filter-by-directory'
to '/TAB' and '//' respectively, and it reassigns `ibuffer-filter-disable'
to '/DEL'.

The first part of the patch I), it's OK for me: it improves a lot
the documentation of this mode; it adds new convenient commands and
lot of tests.

For the second part II) i would like to hear suggestion/opinion from
other colleagues.  Reassign key bindings might annoy other users
of this mode: it's something you want to do only when everyone agrees that
is for better.

Regards,
Tino

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
From 125f531ba4a5e5b162e701737631d0b14ba94a93 Mon Sep 17 00:00:00 2001
From: Christopher Genovese <genovese@cmu.edu>
Date: Sat, 3 Dec 2016 00:06:47 +0900
Subject: [PATCH 1/2] ibuffer: New filters and commands

Add several new filters and improve documentation.
See discussion on:
https://lists.gnu.org/archive/html/emacs-devel/2016-11/msg00399.html
* lisp/ibuf-ext.el: Add paragraph to file commentary.
(ibuffer-saved-filters, ibuffer-filtering-qualifiers)
(ibuffer-filter-groups): Update doc string.
(ibuffer-unary-operand): Add new function that transparently
handles 'not' formats for compound filters.
(ibuffer-included-in-filter-p): Handle 'not' fully; update doc string.
(ibuffer-included-in-filter-p-1): Handle 'and' compound filters.
(ibuffer-decompose-filter): Handle 'and' as well,
and handle 'not' consistently with other uses.
(ibuffer-and-filter): New defun analogous to 'ibuffer-or-filter'.
(ibuffer--or-and-filter): New defun.
(ibuffer-or-filter, ibuffer-and-filter): Use it.
(ibuffer-format-qualifier): Handle 'and' filters as well.
(ibuffer-filter-by-basename, ibuffer-filter-by-file-extension)
(ibuffer-filter-by-directory, ibuffer-filter-by-starred-name)
(ibuffer-filter-by-modified, ibuffer-filter-by-visiting-file):
Add new pre-defined filters.
(ibuffer-filter-chosen-by-completion): Add new interactive command
for easily choosing a filter from the descriptions.
* lisp/ibuffer.el (ibuffer-mode-map):
Bind ibuffer-filter-by-basename, ibuffer-filter-by-file-extension,
ibuffer-filter-by-starred-name, ibuffer-filter-by-modified,
ibuffer-filter-by-visiting-file to '/b', '/.', '/*', '/i', '/v'
respectively; bind 'ibuffer-or-filter', 'ibuffer-and-filter',
'ibuffer-pop-filter' ,'ibuffer-pop-filter-group' and
'ibuffer-filter-disable' to '/|', '/&', '/<up>', '/S-<up>'
and '/ DEL' respectively.
* test/lisp/ibuffer-tests.el (ibuffer-autoload): Add appropriate
skip specification.
Add menu entries for the new filters.
(ibuffer-filter-inclusion-1, ibuffer-filter-inclusion-2
ibuffer-filter-inclusion-3, ibuffer-filter-inclusion-4
ibuffer-filter-inclusion-5, ibuffer-filter-inclusion-6
ibuffer-filter-inclusion-7, ibuffer-filter-inclusion-8
ibuffer-decompose-filter, ibuffer-and-filter
ibuffer-or-filter): Add new tests; they are skipped unless
ibuf-ext is loaded.
; * etc/NEWS: Add entries for new user-facing features.
---
 etc/NEWS                   |  21 ++
 lisp/ibuf-ext.el           | 426 +++++++++++++++++++++--------
 lisp/ibuffer.el            |  57 +++-
 test/lisp/ibuffer-tests.el | 667 ++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 1054 insertions(+), 117 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index cbce027..2dd6b5c 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -304,6 +304,27 @@ bound to 'Buffer-menu-unmark-all-buffers'.
 ** Ibuffer
 
 ---
+*** New filter commands `ibuffer-filter-by-basename',
+`ibuffer-filter-by-file-extension', `ibuffer-filter-by-directory',
+`ibuffer-filter-by-starred-name', `ibuffer-filter-by-modified'
+and `ibuffer-filter-by-visiting-file'; bound respectively
+to '/b', '/.', '//', '/*', '/i' and '/v'.
+
+---
+*** Two new commands 'ibuffer-filter-chosen-by-completion'
+and `ibuffer-and-filter', the second bound to '/&'.
+
+---
+*** The commands `ibuffer-pop-filter', `ibuffer-pop-filter-group',
+`ibuffer-or-filter' and `ibuffer-filter-disable' have the alternative
+bindings '/<up>', '/S-<up>', '/|' and '/DEL', respectively.
+
+---
+*** The data format specifying filters has been extended to allow
+explicit logical 'and', and a more flexible form for logical 'not'.
+See 'ibuffer-filtering-qualifiers' doc string for full details.
+
+---
 *** A new command 'ibuffer-copy-buffername-as-kill'; bound
 to 'B'.
 
diff --git a/lisp/ibuf-ext.el b/lisp/ibuf-ext.el
index 5ef0746..3421a01 100644
--- a/lisp/ibuf-ext.el
+++ b/lisp/ibuf-ext.el
@@ -28,6 +28,13 @@
 ;; These functions should be automatically loaded when called, but you
 ;; can explicitly (require 'ibuf-ext) in your ~/.emacs to have them
 ;; preloaded.
+;;
+;; For details on the structure of ibuffer filters and filter groups,
+;; see the documentation for variables `ibuffer-filtering-qualifiers',
+;; `ibuffer-filter-groups', and `ibuffer-saved-filters' in that order.
+;; The variable `ibuffer-filtering-alist' contains names and
+;; descriptions of the currently defined filters; also see the macro
+;; `define-ibuffer-filter'.
 
 ;;; Code:
 
@@ -35,9 +42,12 @@
 
 (eval-when-compile
   (require 'ibuf-macs)
-  (require 'cl-lib))
+  (require 'cl-lib)
+  (require 'subr-x))
+
 
 ;;; Utility functions
+
 (defun ibuffer-delete-alist (key alist)
   "Delete all entries in ALIST that have a key equal to KEY."
   (let (entry)
@@ -119,35 +129,96 @@ ibuffer-tmp-show-regexps
 
 (defvar ibuffer-auto-buffers-changed nil)
 
-(defcustom ibuffer-saved-filters '(("gnus"
-				    ((or (mode . message-mode)
-					 (mode . mail-mode)
-					 (mode . gnus-group-mode)
-					 (mode . gnus-summary-mode)
-					 (mode . gnus-article-mode))))
-				   ("programming"
-				    ((or (mode . emacs-lisp-mode)
-					 (mode . cperl-mode)
-					 (mode . c-mode)
-					 (mode . java-mode)
-					 (mode . idl-mode)
-					 (mode . lisp-mode)))))
-
-  "An alist of filter qualifiers to switch between.
-
-This variable should look like ((\"STRING\" QUALIFIERS)
-                                (\"STRING\" QUALIFIERS) ...), where
-QUALIFIERS is a list of the same form as
-`ibuffer-filtering-qualifiers'.
-See also the variables `ibuffer-filtering-qualifiers',
-`ibuffer-filtering-alist', and the functions
-`ibuffer-switch-to-saved-filters', `ibuffer-save-filters'."
-  :type '(repeat sexp)
+(defcustom ibuffer-saved-filters '(("programming"
+                                    ((or (derived-mode . prog-mode)
+                                         (mode         . ess-mode)
+                                         (mode         . compilation-mode))))
+                                   ("text document"
+                                    ((and (derived-mode . text-mode)
+                                          (not (starred-name)))))
+                                   ("TeX"
+                                    ((or (derived-mode . tex-mode)
+                                         (mode         . latex-mode)
+                                         (mode         . context-mode)
+                                         (mode         . ams-tex-mode)
+                                         (mode         . bibtex-mode))))
+                                   ("web"
+                                    ((or (derived-mode . sgml-mode)
+                                         (derived-mode . css-mode)
+                                         (mode         . javascript-mode)
+                                         (mode         . js2-mode)
+                                         (mode         . scss-mode)
+                                         (derived-mode . haml-mode)
+                                         (mode         . sass-mode))))
+                                   ("gnus"
+                                    ((or (mode . message-mode)
+                                         (mode . mail-mode)
+                                         (mode . gnus-group-mode)
+                                         (mode . gnus-summary-mode)
+                                         (mode . gnus-article-mode)))))
+
+  "An alist mapping saved filter names to filter specifications.
+
+Each element should look like (\"NAME\" FILTER-LIST), where
+FILTER-LIST has the same structure as the variable
+`ibuffer-filtering-qualifiers', which see. The filters defined
+here are joined with an implicit logical `and' and associated
+with NAME. The combined specification can be used by name in
+other filter specifications via the `saved' qualifier (again, see
+`ibuffer-filtering-qualifiers'). They can also be switched to by
+name (see the functions `ibuffer-switch-to-saved-filters' and
+`ibuffer-save-filters'). The variable `ibuffer-save-with-custom'
+affects how this information is saved for future sessions. This
+variable can be set directly from lisp code."
+  :version "26.1"
+  :type '(alist :key-type (string :tag "Filter name")
+                :value-type (list :tag "Filter list"
+                                  (repeat (sexp :tag "Filter specification"))))
   :group 'ibuffer)
 
 (defvar ibuffer-filtering-qualifiers nil
-  "A list like (SYMBOL . QUALIFIER) which filters the current buffer list.
-See also `ibuffer-filtering-alist'.")
+  "A list specifying the filters currently acting on the buffer list.
+
+If this list is nil, then no filters are currently in
+effect. Otherwise, each element of this list specifies a single
+filter, and all of the specified filters in the list are applied
+successively to the buffer list.
+
+Each filter specification can be of two types: simple or compound.
+
+A simple filter specification has the form (SYMBOL . QUALIFIER),
+where SYMBOL is a key in the alist `ibuffer-filtering-alist' that
+determines the filter function to use and QUALIFIER is the data
+passed to that function (along with the buffer being considered).
+
+A compound filter specification can have one of four forms:
+
+-- (not FILTER-SPEC)
+
+   Represents the logical complement of FILTER-SPEC, which
+   is any single filter specification, simple or compound.
+   The form (not . FILTER-SPEC) is also accepted here.
+
+-- (and FILTER-SPECS...)
+
+   Represents the logical-and of the filters defined by one or
+   more filter specifications FILTER-SPECS..., where each
+   specification can be simple or compound.  Note that and is
+   implicitly applied to the filters in the top-level list.
+
+-- (or FILTER-SPECS...)
+
+   Represents the logical-or of the filters defined by one or
+   more filter specifications FILTER-SPECS..., where each
+   specification can be simple or compound.
+
+-- (saved . \"NAME\")
+
+   Represents the filter saved under the string NAME
+   in the alist `ibuffer-saved-filters'. It is an
+   error to name a filter that has not been saved.
+
+This variable is local to each ibuffer buffer.")
 
 ;; This is now frobbed by `define-ibuffer-filter'.
 (defvar ibuffer-filtering-alist nil
@@ -179,10 +250,18 @@ ibuffer-cached-filter-formats
 (defvar ibuffer-compiled-filter-formats nil)
 
 (defvar ibuffer-filter-groups nil
-  "A list like ((\"NAME\" ((SYMBOL . QUALIFIER) ...) ...) which groups buffers.
-The SYMBOL should be one from `ibuffer-filtering-alist'.
-The QUALIFIER should be the same as QUALIFIER in
-`ibuffer-filtering-qualifiers'.")
+  "An alist giving this buffer's active filter groups, or nil if none.
+
+This alist maps filter group labels to filter specification
+lists.  Each element has the form (\"LABEL\" FILTER-SPECS...),
+where FILTER-SPECS... represents one or more filter
+specifications of the same form as allowed as elements of
+`ibuffer-filtering-qualifiers'.
+
+Each filter group is displayed as a separate section in the
+ibuffer list, headed by LABEL and displaying only the buffers
+that pass through all the filters associated with NAME in this
+list.")
 
 (defcustom ibuffer-show-empty-filter-groups t
   "If non-nil, then show the names of filter groups which are empty."
@@ -192,20 +271,21 @@ ibuffer-show-empty-filter-groups
 (defcustom ibuffer-saved-filter-groups nil
   "An alist of filtering groups to switch between.
 
-This variable should look like ((\"STRING\" QUALIFIERS)
-                                (\"STRING\" QUALIFIERS) ...), where
-QUALIFIERS is a list of the same form as
-`ibuffer-filtering-qualifiers'.
+Each element is of the form (\"NAME\" . FILTER-GROUP-LIST),
+where NAME is a unique but arbitrary name and FILTER-GROUP-LIST
+is a list of filter groups with the same structure as
+allowed for `ibuffer-filter-groups'.
 
-See also the variables `ibuffer-filter-groups',
-`ibuffer-filtering-qualifiers', `ibuffer-filtering-alist', and the
-functions `ibuffer-switch-to-saved-filter-groups',
-`ibuffer-save-filter-groups'."
+See also the functions `ibuffer-save-filter-groups' and
+`ibuffer-switch-to-saved-filter-groups' for saving and switching
+between sets of filter groups, and the variable
+`ibuffer-save-with-custom' that affects how this information is
+saved."
   :type '(repeat sexp)
   :group 'ibuffer)
 
 (defvar ibuffer-hidden-filter-groups nil
-  "A list of filtering groups which are currently hidden.")
+  "The list of filter groups that are currently hidden.")
 
 (defvar ibuffer-filter-group-kill-ring nil)
 
@@ -512,18 +592,38 @@ print
 
 ;;;###autoload
 (defun ibuffer-included-in-filters-p (buf filters)
+  "Return non-nil if BUF passes all FILTERS.
+
+BUF is a lisp buffer object, and FILTERS is a list of filter
+specifications with the same structure as
+`ibuffer-filtering-qualifiers'."
   (not
    (memq nil ;; a filter will return nil if it failed
-	 (mapcar
-	  ;; filter should be like (TYPE . QUALIFIER), or
-	  ;; (or (TYPE . QUALIFIER) (TYPE . QUALIFIER) ...)
-	  #'(lambda (qual)
-	      (ibuffer-included-in-filter-p buf qual))
-	  filters))))
+	 (mapcar #'(lambda (filter)
+                     (ibuffer-included-in-filter-p buf filter))
+                 filters))))
+
+(defun ibuffer-unary-operand (filter)
+  "Extracts operand from a unary compound FILTER specification.
+
+FILTER should be a cons cell of either form (f . d) or (f d),
+where operand d is itself a cons cell, or nil. Returns d."
+  (let* ((tail (cdr filter))
+         (maybe-q (car-safe tail)))
+    (if (consp maybe-q) maybe-q tail)))
 
 (defun ibuffer-included-in-filter-p (buf filter)
+  "Return non-nil if BUF pass FILTER.
+
+BUF is a lisp buffer object, and FILTER is a filter
+specification, with the same structure as an element of the list
+`ibuffer-filtering-qualifiers'."
   (if (eq (car filter) 'not)
-      (not (ibuffer-included-in-filter-p-1 buf (cdr filter)))
+      (let ((inner (ibuffer-unary-operand filter)))
+        ;; Allows (not (not ...)) etc, which may be overkill
+        (if (eq (car inner) 'not)
+            (ibuffer-included-in-filter-p buf (ibuffer-unary-operand inner))
+          (not (ibuffer-included-in-filter-p-1 buf inner))))
     (ibuffer-included-in-filter-p-1 buf filter)))
 
 (defun ibuffer-included-in-filter-p-1 (buf filter)
@@ -531,17 +631,25 @@ ibuffer-included-in-filter-p-1
    (not
     (pcase (car filter)
       (`or
+       ;;; ATTN: Short-circuiting alternative with parallel structure w/`and
+       ;;(catch 'has-match
+       ;;  (dolist (filter-spec (cdr filter) nil)
+       ;;    (when (ibuffer-included-in-filter-p buf filter-spec)
+       ;;      (throw 'has-match t))))
        (memq t (mapcar #'(lambda (x)
-			   (ibuffer-included-in-filter-p buf x))
-		       (cdr filter))))
+                           (ibuffer-included-in-filter-p buf x))
+                       (cdr filter))))
+      (`and
+       (catch 'no-match
+         (dolist (filter-spec (cdr filter) t)
+           (unless (ibuffer-included-in-filter-p buf filter-spec)
+             (throw 'no-match nil)))))
       (`saved
-       (let ((data
-	      (assoc (cdr filter)
-		     ibuffer-saved-filters)))
-	 (unless data
-	   (ibuffer-filter-disable t)
-	   (error "Unknown saved filter %s" (cdr filter)))
-	 (ibuffer-included-in-filters-p buf (cadr data))))
+       (let ((data (assoc (cdr filter) ibuffer-saved-filters)))
+         (unless data
+           (ibuffer-filter-disable t)
+           (error "Unknown saved filter %s" (cdr filter)))
+         (ibuffer-included-in-filters-p buf (cadr data))))
       (_
        (pcase-let ((`(,_type ,_desc ,func)
                     (assq (car filter) ibuffer-filtering-alist)))
@@ -828,39 +936,34 @@ ibuffer-pop-filter
     (when buf
       (ibuffer-jump-to-buffer (buffer-name buf)))))
 
-(defun ibuffer-push-filter (qualifier)
-  "Add QUALIFIER to `ibuffer-filtering-qualifiers'."
-  (push qualifier ibuffer-filtering-qualifiers))
+(defun ibuffer-push-filter (filter-specification)
+  "Add FILTER-SPECIFICATION to `ibuffer-filtering-qualifiers'."
+  (push filter-specification ibuffer-filtering-qualifiers))
 
 ;;;###autoload
 (defun ibuffer-decompose-filter ()
-  "Separate the top compound filter (OR, NOT, or SAVED) in this buffer.
+  "Separate this buffer's top compound filter (AND, OR, NOT, or SAVED).
 
 This means that the topmost filter on the filtering stack, which must
 be a complex filter like (OR [name: foo] [mode: bar-mode]), will be
-turned into two separate filters [name: foo] and [mode: bar-mode]."
+turned into separate filters, like [name: foo] and [mode: bar-mode]."
   (interactive)
   (when (null ibuffer-filtering-qualifiers)
     (error "No filters in effect"))
   (let ((lim (pop ibuffer-filtering-qualifiers)))
     (pcase (car lim)
-      (`or
-       (setq ibuffer-filtering-qualifiers (append
-					  (cdr lim)
-					  ibuffer-filtering-qualifiers)))
+      ((or 'or 'and)
+       (setq ibuffer-filtering-qualifiers
+             (nconc (cdr lim) ibuffer-filtering-qualifiers)))
       (`saved
-       (let ((data
-	      (assoc (cdr lim)
-		     ibuffer-saved-filters)))
-	 (unless data
-	   (ibuffer-filter-disable)
-	   (error "Unknown saved filter %s" (cdr lim)))
-	 (setq ibuffer-filtering-qualifiers (append
-					    (cadr data)
-					    ibuffer-filtering-qualifiers))))
+       (let ((data (assoc (cdr lim) ibuffer-saved-filters)))
+         (unless data
+           (ibuffer-filter-disable)
+           (error "Unknown saved filter %s" (cdr lim)))
+         (setq ibuffer-filtering-qualifiers
+               (append (cadr data) ibuffer-filtering-qualifiers))))
       (`not
-       (push (cdr lim)
-	     ibuffer-filtering-qualifiers))
+       (push (ibuffer-unary-operand lim) ibuffer-filtering-qualifiers))
       (_
        (error "Filter type %s is not compound" (car lim)))))
   (ibuffer-update nil t))
@@ -888,31 +991,36 @@ ibuffer-negate-filter
 	  ibuffer-filtering-qualifiers))
   (ibuffer-update nil t))
 
+(defun ibuffer--or-and-filter (op decompose)
+  (if decompose
+      (if (eq op (caar ibuffer-filtering-qualifiers))
+          (ibuffer-decompose-filter)
+        (error "Top filter is not an %s" (upcase (symbol-name op))))
+    (when (< (length ibuffer-filtering-qualifiers) 2)
+      (error "Need two filters to %s" (upcase (symbol-name op))))
+    ;; If either filter is an op, eliminate unnecessary nesting.
+    (let ((first (pop ibuffer-filtering-qualifiers))
+          (second (pop ibuffer-filtering-qualifiers)))
+      (push (nconc (if (eq op (car first)) first (list op first))
+                   (if (eq op (car second)) (cdr second) (list second)))
+            ibuffer-filtering-qualifiers)))
+  (ibuffer-update nil t))
+
 ;;;###autoload
-(defun ibuffer-or-filter (&optional reverse)
+(defun ibuffer-or-filter (&optional decompose)
   "Replace the top two filters in this buffer with their logical OR.
-If optional argument REVERSE is non-nil, instead break the top OR
+If optional argument DECOMPOSE is non-nil, instead break the top OR
 filter into parts."
   (interactive "P")
-  (if reverse
-      (progn
-	(when (or (null ibuffer-filtering-qualifiers)
-		  (not (eq 'or (caar ibuffer-filtering-qualifiers))))
-	  (error "Top filter is not an OR"))
-	(let ((lim (pop ibuffer-filtering-qualifiers)))
-	  (setq ibuffer-filtering-qualifiers
-		(nconc (cdr lim) ibuffer-filtering-qualifiers))))
-    (when (< (length ibuffer-filtering-qualifiers) 2)
-      (error "Need two filters to OR"))
-    ;; If the second filter is an OR, just add to it.
-    (let ((first (pop ibuffer-filtering-qualifiers))
-	  (second (pop ibuffer-filtering-qualifiers)))
-      (if (eq 'or (car second))
-	  (push (nconc (list 'or first) (cdr second))
-		ibuffer-filtering-qualifiers)
-	(push (list 'or first second)
-	      ibuffer-filtering-qualifiers))))
-  (ibuffer-update nil t))
+  (ibuffer--or-and-filter 'or decompose))
+
+;;;###autoload
+(defun ibuffer-and-filter (&optional decompose)
+  "Replace the top two filters in this buffer with their logical AND.
+If optional argument DECOMPOSE is non-nil, instead break the top AND
+filter into parts."
+  (interactive "P")
+  (ibuffer--or-and-filter 'and decompose))
 
 (defun ibuffer-maybe-save-stuff ()
   (when ibuffer-save-with-custom
@@ -986,7 +1094,9 @@ ibuffer-format-filter-group-data
 
 (defun ibuffer-format-qualifier (qualifier)
   (if (eq (car-safe qualifier) 'not)
-      (concat " [NOT" (ibuffer-format-qualifier-1 (cdr qualifier)) "]")
+      (concat " [NOT"
+              (ibuffer-format-qualifier-1 (ibuffer-unary-operand qualifier))
+              "]")
     (ibuffer-format-qualifier-1 qualifier)))
 
 (defun ibuffer-format-qualifier-1 (qualifier)
@@ -995,14 +1105,16 @@ ibuffer-format-qualifier-1
      (concat " [filter: " (cdr qualifier) "]"))
     (`or
      (concat " [OR" (mapconcat #'ibuffer-format-qualifier
-			       (cdr qualifier) "") "]"))
+                               (cdr qualifier) "") "]"))
+    (`and
+     (concat " [AND" (mapconcat #'ibuffer-format-qualifier
+                                (cdr qualifier) "") "]"))
     (_
      (let ((type (assq (car qualifier) ibuffer-filtering-alist)))
        (unless qualifier
-	 (error "Ibuffer: bad qualifier %s" qualifier))
+         (error "Ibuffer: bad qualifier %s" qualifier))
        (concat " [" (cadr type) ": " (format "%s]" (cdr qualifier)))))))
 
-
 (defun ibuffer-list-buffer-modes (&optional include-parents)
   "Create a completion table of buffer modes currently in use.
 If INCLUDE-PARENTS is non-nil then include parent modes."
@@ -1020,7 +1132,7 @@ ibuffer-list-buffer-modes
 
 ;;;###autoload (autoload 'ibuffer-filter-by-mode "ibuf-ext")
 (define-ibuffer-filter mode
-  "Toggle current view to buffers with major mode QUALIFIER."
+  "Limit current view to buffers with major mode QUALIFIER."
   (:description "major mode"
    :reader
    (let* ((buf (ibuffer-current-buffer))
@@ -1040,7 +1152,7 @@ mode
 
 ;;;###autoload (autoload 'ibuffer-filter-by-used-mode "ibuf-ext")
 (define-ibuffer-filter used-mode
-  "Toggle current view to buffers with major mode QUALIFIER.
+  "Limit current view to buffers with major mode QUALIFIER.
 Called interactively, this function allows selection of modes
 currently used by buffers."
   (:description "major mode in use"
@@ -1059,7 +1171,7 @@ used-mode
 
 ;;;###autoload (autoload 'ibuffer-filter-by-derived-mode "ibuf-ext")
 (define-ibuffer-filter derived-mode
-    "Toggle current view to buffers whose major mode inherits from QUALIFIER."
+    "Limit current view to buffers whose major mode inherits from QUALIFIER."
   (:description "derived mode"
 		:reader
 		(intern
@@ -1070,22 +1182,75 @@ derived-mode
 
 ;;;###autoload (autoload 'ibuffer-filter-by-name "ibuf-ext")
 (define-ibuffer-filter name
-  "Toggle current view to buffers with name matching QUALIFIER."
+  "Limit current view to buffers with name matching QUALIFIER."
   (:description "buffer name"
    :reader (read-from-minibuffer "Filter by name (regexp): "))
   (string-match qualifier (buffer-name buf)))
 
+;;;###autoload (autoload 'ibuffer-filter-by-starred-name "ibuf-ext")
+(define-ibuffer-filter starred-name
+    "Limit current view to buffers with name beginning and ending
+with *, along with an optional suffix of the form digits or
+<digits>."
+  (:description "starred buffer name"
+   :reader nil)
+  (ignore qualifier)
+  (string-match "\\`\\*[^*]+\\*\\(?:<[[:digit:]]+>\\)?\\'" (buffer-name buf)))
+
 ;;;###autoload (autoload 'ibuffer-filter-by-filename "ibuf-ext")
 (define-ibuffer-filter filename
-  "Toggle current view to buffers with filename matching QUALIFIER."
-  (:description "filename"
-   :reader (read-from-minibuffer "Filter by filename (regexp): "))
+    "Limit current view to buffers with full file pathname matching QUALIFIER.
+
+For example, for a buffer associated with file '/a/b/c.d', this
+matches against '/a/b/c.d'."
+  (:description "file pathname"
+   :reader (read-from-minibuffer "Filter by file pathname (regexp): "))
   (ibuffer-awhen (with-current-buffer buf (ibuffer-buffer-file-name))
     (string-match qualifier it)))
 
+;; If filename above were renamed to pathname, this could be called filename.
+;;;###autoload (autoload 'ibuffer-filter-by-basename "ibuf-ext")
+(define-ibuffer-filter basename
+    "Limit current view to buffers with file basename matching QUALIFIER.
+
+For example, for a buffer associated with file '/a/b/c.d', this
+matches against 'c.d'."
+  (:description "file basename"
+   :reader (read-from-minibuffer
+            "Filter by file name, without directory part (regex): "))
+  (ibuffer-awhen (with-current-buffer buf (ibuffer-buffer-file-name))
+    (string-match qualifier (file-name-nondirectory it))))
+
+;;;###autoload (autoload 'ibuffer-filter-by-file-extension "ibuf-ext")
+(define-ibuffer-filter file-extension
+    "Limit current view to buffers with filename extension matching QUALIFIER.
+
+The separator character (typically `.') is not part of the
+pattern. For example, for a buffer associated with file
+'/a/b/c.d', this matches against 'd'."
+  (:description "filename extension"
+   :reader (read-from-minibuffer
+            "Filter by filename extension without separator (regex): "))
+  (ibuffer-awhen (with-current-buffer buf (ibuffer-buffer-file-name))
+    (string-match qualifier (or (file-name-extension it) ""))))
+
+;;;###autoload (autoload 'ibuffer-filter-by-directory "ibuf-ext")
+(define-ibuffer-filter directory
+    "Limit current view to buffers with directory matching QUALIFIER.
+
+For a buffer associated with file '/a/b/c.d', this matches
+against '/a/b'. For a buffer not associated with a file, this
+matches against the value of `default-directory' in that buffer."
+  (:description "directory name"
+   :reader (read-from-minibuffer "Filter by directory name (regex): "))
+  (ibuffer-aif (with-current-buffer buf (ibuffer-buffer-file-name))
+      (let ((dirname (file-name-directory it)))
+        (when dirname (string-match qualifier dirname)))
+    (when default-directory (string-match qualifier default-directory))))
+
 ;;;###autoload (autoload 'ibuffer-filter-by-size-gt  "ibuf-ext")
 (define-ibuffer-filter size-gt
-  "Toggle current view to buffers with size greater than QUALIFIER."
+  "Limit current view to buffers with size greater than QUALIFIER."
   (:description "size greater than"
    :reader
    (string-to-number (read-from-minibuffer "Filter by size greater than: ")))
@@ -1094,16 +1259,32 @@ size-gt
 
 ;;;###autoload (autoload 'ibuffer-filter-by-size-lt  "ibuf-ext")
 (define-ibuffer-filter size-lt
-   "Toggle current view to buffers with size less than QUALIFIER."
+    "Limit current view to buffers with size less than QUALIFIER."
   (:description "size less than"
    :reader
    (string-to-number (read-from-minibuffer "Filter by size less than: ")))
   (< (with-current-buffer buf (buffer-size))
      qualifier))
 
+;;;###autoload (autoload 'ibuffer-filter-by-modified "ibuf-ext")
+(define-ibuffer-filter modified
+    "Limit current view to buffers that are marked as modified."
+  (:description "modified"
+   :reader nil)
+  (ignore qualifier)
+  (buffer-modified-p buf))
+
+;;;###autoload (autoload 'ibuffer-filter-by-visiting-file "ibuf-ext")
+(define-ibuffer-filter visiting-file
+    "Limit current view to buffers that are visiting a file."
+  (:description "visiting a file"
+   :reader nil)
+  (ignore qualifier)
+  (with-current-buffer buf (buffer-file-name)))
+
 ;;;###autoload (autoload 'ibuffer-filter-by-content "ibuf-ext")
 (define-ibuffer-filter content
-   "Toggle current view to buffers whose contents match QUALIFIER."
+   "Limit current view to buffers whose contents match QUALIFIER."
   (:description "content"
    :reader (read-from-minibuffer "Filter by content (regexp): "))
   (with-current-buffer buf
@@ -1113,12 +1294,33 @@ content
 
 ;;;###autoload (autoload 'ibuffer-filter-by-predicate "ibuf-ext")
 (define-ibuffer-filter predicate
-   "Toggle current view to buffers for which QUALIFIER returns non-nil."
+   "Limit current view to buffers for which QUALIFIER returns non-nil."
   (:description "predicate"
    :reader (read-minibuffer "Filter by predicate (form): "))
   (with-current-buffer buf
     (eval qualifier)))
 
+;;;###autoload (autoload 'ibuffer-filter-chosen-by-completion "ibuf-ext")
+(defun ibuffer-filter-chosen-by-completion ()
+  "Select and apply filter chosen by completion against available filters.
+Indicates corresponding key sequences in echo area after filtering.
+
+The completion matches against the filter description text of
+each filter in `ibuffer-filtering-alist'."
+  (interactive)
+  (let* ((filters (mapcar (lambda (x) (cons (cadr x) (car x)))
+                          ibuffer-filtering-alist))
+         (match (completing-read "Filter by: " filters nil t))
+         (filter (cdr (assoc match filters)))
+         (command (intern (concat "ibuffer-filter-by-" (symbol-name filter)))))
+    (call-interactively command)
+    (message "%s can be run with key sequences: %s"
+             command
+             (mapconcat #'key-description
+                        (where-is-internal command ibuffer-mode-map nil t)
+                        "or "))))
+
+
 ;;; Sorting
 
 ;;;###autoload
diff --git a/lisp/ibuffer.el b/lisp/ibuffer.el
index 51d7cb9..0205861 100644
--- a/lisp/ibuffer.el
+++ b/lisp/ibuffer.el
@@ -518,26 +518,37 @@ ibuffer-mode-map
     (define-key map (kbd "s f") 'ibuffer-do-sort-by-filename/process)
     (define-key map (kbd "s m") 'ibuffer-do-sort-by-major-mode)
 
+    (define-key map (kbd "/ RET") 'ibuffer-filter-by-mode)
     (define-key map (kbd "/ m") 'ibuffer-filter-by-used-mode)
     (define-key map (kbd "/ M") 'ibuffer-filter-by-derived-mode)
     (define-key map (kbd "/ n") 'ibuffer-filter-by-name)
-    (define-key map (kbd "/ c") 'ibuffer-filter-by-content)
-    (define-key map (kbd "/ e") 'ibuffer-filter-by-predicate)
+    (define-key map (kbd "/ *") 'ibuffer-filter-by-starred-name)
     (define-key map (kbd "/ f") 'ibuffer-filter-by-filename)
-    (define-key map (kbd "/ >") 'ibuffer-filter-by-size-gt)
+    (define-key map (kbd "/ b") 'ibuffer-filter-by-basename)
+    (define-key map (kbd "/ .") 'ibuffer-filter-by-file-extension)
     (define-key map (kbd "/ <") 'ibuffer-filter-by-size-lt)
+    (define-key map (kbd "/ >") 'ibuffer-filter-by-size-gt)
+    (define-key map (kbd "/ i") 'ibuffer-filter-by-modified)
+    (define-key map (kbd "/ v") 'ibuffer-filter-by-visiting-file)
+    (define-key map (kbd "/ c") 'ibuffer-filter-by-content)
+    (define-key map (kbd "/ e") 'ibuffer-filter-by-predicate)
+
     (define-key map (kbd "/ r") 'ibuffer-switch-to-saved-filters)
     (define-key map (kbd "/ a") 'ibuffer-add-saved-filters)
     (define-key map (kbd "/ x") 'ibuffer-delete-saved-filters)
     (define-key map (kbd "/ d") 'ibuffer-decompose-filter)
     (define-key map (kbd "/ s") 'ibuffer-save-filters)
     (define-key map (kbd "/ p") 'ibuffer-pop-filter)
+    (define-key map (kbd "/ <up>") 'ibuffer-pop-filter)
     (define-key map (kbd "/ !") 'ibuffer-negate-filter)
     (define-key map (kbd "/ t") 'ibuffer-exchange-filters)
     (define-key map (kbd "/ TAB") 'ibuffer-exchange-filters)
     (define-key map (kbd "/ o") 'ibuffer-or-filter)
+    (define-key map (kbd "/ |") 'ibuffer-or-filter)
+    (define-key map (kbd "/ &") 'ibuffer-and-filter)
     (define-key map (kbd "/ g") 'ibuffer-filters-to-filter-group)
     (define-key map (kbd "/ P") 'ibuffer-pop-filter-group)
+    (define-key map (kbd "/ S-<up>") 'ibuffer-pop-filter-group)
     (define-key map (kbd "/ D") 'ibuffer-decompose-filter-group)
     (define-key map (kbd "/ /") 'ibuffer-filter-disable)
 
@@ -647,6 +658,7 @@ ibuffer-mode-map
     (define-key-after map [menu-bar view filter filter-disable]
       '(menu-item "Disable all filtering" ibuffer-filter-disable
         :enable (and (featurep 'ibuf-ext) ibuffer-filtering-qualifiers)))
+
     (define-key-after map [menu-bar view filter filter-by-mode]
       '(menu-item "Add filter by any major mode..." ibuffer-filter-by-mode))
     (define-key-after map [menu-bar view filter filter-by-used-mode]
@@ -657,19 +669,50 @@ ibuffer-mode-map
                   ibuffer-filter-by-derived-mode))
     (define-key-after map [menu-bar view filter filter-by-name]
       '(menu-item "Add filter by buffer name..." ibuffer-filter-by-name))
+    (define-key-after map [menu-bar view filter filter-by-starred-name]
+      '(menu-item "Add filter by starred buffer name..."
+                  ibuffer-filter-by-starred-name
+                  :help "List buffers whose names begin with a star"))
     (define-key-after map [menu-bar view filter filter-by-filename]
-      '(menu-item "Add filter by filename..." ibuffer-filter-by-filename))
+      '(menu-item "Add filter by full filename..." ibuffer-filter-by-filename
+                  :help
+                  (concat "For a buffer associated with file '/a/b/c.d', "
+                          "list buffer if a given pattern matches '/a/b/c.d'")))
+    (define-key-after map [menu-bar view filter filter-by-basename]
+      '(menu-item "Add filter by file basename..."
+                  ibuffer-filter-by-basename
+                  :help (concat "For a buffer associated with file '/a/b/c.d', "
+                                "list buffer if a given pattern matches 'c.d'")))
+    (define-key-after map [menu-bar view filter filter-by-file-extension]
+      '(menu-item "Add filter by file name extension..."
+                  ibuffer-filter-by-file-extension
+                  :help (concat "For a buffer associated with file '/a/b/c.d', "
+                                "list buffer if a given pattern matches 'd'")))
+    (define-key-after map [menu-bar view filter filter-by-directory]
+      '(menu-item "Add filter by filename's directory..."
+                  ibuffer-filter-by-directory
+                  :help
+                  (concat "For a buffer associated with file '/a/b/c.d', "
+                          "list buffer if a given pattern matches '/a/b'")))
     (define-key-after map [menu-bar view filter filter-by-size-lt]
       '(menu-item "Add filter by size less than..." ibuffer-filter-by-size-lt))
     (define-key-after map [menu-bar view filter filter-by-size-gt]
       '(menu-item "Add filter by size greater than..."
         ibuffer-filter-by-size-gt))
+    (define-key-after map [menu-bar view filter filter-by-modified]
+      '(menu-item "Add filter by modified buffer" ibuffer-filter-by-modified
+                  :help "List buffers that are marked as modified"))
+    (define-key-after map [menu-bar view filter filter-by-visiting-file]
+      '(menu-item "Add filter by buffer visiting a file"
+                  ibuffer-filter-by-visiting-file
+                  :help "List buffers that are visiting files"))
     (define-key-after map [menu-bar view filter filter-by-content]
       '(menu-item "Add filter by content (regexp)..."
         ibuffer-filter-by-content))
     (define-key-after map [menu-bar view filter filter-by-predicate]
       '(menu-item "Add filter by Lisp predicate..."
         ibuffer-filter-by-predicate))
+
     (define-key-after map [menu-bar view filter pop-filter]
       '(menu-item "Remove top filter" ibuffer-pop-filter
         :enable (and (featurep 'ibuf-ext) ibuffer-filtering-qualifiers)))
@@ -682,6 +725,12 @@ ibuffer-mode-map
     (define-key-after map [menu-bar view filter negate-filter]
       '(menu-item "Negate top filter" ibuffer-negate-filter
         :enable (and (featurep 'ibuf-ext) ibuffer-filtering-qualifiers)))
+    (define-key-after map [menu-bar view filter and-filter]
+      '(menu-item "AND top two filters" ibuffer-and-filter
+        :enable (and (featurep 'ibuf-ext) ibuffer-filtering-qualifiers
+                     (cdr ibuffer-filtering-qualifiers))
+        :help
+        "Create a new filter which is the logical AND of the top two filters"))
     (define-key-after map [menu-bar view filter decompose-filter]
       '(menu-item "Decompose top filter" ibuffer-decompose-filter
         :enable (and (featurep 'ibuf-ext)
diff --git a/test/lisp/ibuffer-tests.el b/test/lisp/ibuffer-tests.el
index 3a4def3..2afd044 100644
--- a/test/lisp/ibuffer-tests.el
+++ b/test/lisp/ibuffer-tests.el
@@ -24,7 +24,8 @@
   (require 'ibuf-macs))
 
 (ert-deftest ibuffer-autoload ()
-  "Tests to see whether reftex-auc has been autoloaded"
+  "Tests to see whether ibuffer has been autoloaded"
+  (skip-unless (not (featurep 'ibuf-ext)))
   (should
    (fboundp 'ibuffer-mark-unsaved-buffers))
   (should
@@ -66,5 +67,669 @@
       (mapc (lambda (buf) (when (buffer-live-p buf)
                             (kill-buffer buf))) (list buf1 buf2)))))
 
+;; Test Filter Inclusion
+(let* (test-buffer-list  ; accumulated buffers to clean up
+       ;; Utility functions without polluting the environment
+       (set-buffer-mode
+        (lambda (buffer mode)
+          "Set BUFFER's major mode to MODE, a mode function, or fundamental."
+          (with-current-buffer buffer
+            (funcall (or mode #'fundamental-mode)))))
+       (set-buffer-contents
+        (lambda (buffer size include-content)
+          "Add exactly SIZE bytes to BUFFER, including INCLUDE-CONTENT."
+          (when (or size include-content)
+            (let* ((unit "\n")
+                   (chunk "ccccccccccccccccccccccccccccccc\n")
+                   (chunk-size (length chunk))
+                   (size (if (and size include-content (stringp include-content))
+                             (- size (length include-content))
+                           size)))
+              (unless (or (null size) (> size 0))
+                (error "size argument must be nil or positive"))
+              (with-current-buffer buffer
+                (when include-content
+                  (insert include-content))
+                (when size
+                  (dotimes (_ (floor size chunk-size))
+                    (insert chunk))
+                  (dotimes (_ (mod size chunk-size))
+                    (insert unit)))
+                ;; prevent query on cleanup
+                (set-buffer-modified-p nil))))))
+       (create-file-buffer
+        (lambda (prefix &rest args-plist)
+          "Create a file and buffer with designated properties.
+        PREFIX is a string giving the beginning of the name, and ARGS-PLIST
+        is a series of keyword-value pairs, with allowed keywords
+        :suffix STRING, :size NUMBER, :mode MODE-FUNC, :include-content STRING.
+        Returns the created buffer."
+          (let* ((suffix  (plist-get args-plist :suffix))
+                 (size    (plist-get args-plist :size))
+                 (include (plist-get args-plist :include-content))
+                 (mode    (plist-get args-plist :mode))
+                 (file    (make-temp-file prefix nil suffix))
+                 (buf     (find-file-noselect file t)))
+            (push buf test-buffer-list) ; record for cleanup
+            (funcall set-buffer-mode buf mode)
+            (funcall set-buffer-contents buf size include)
+            buf)))
+       (create-non-file-buffer
+        (lambda (prefix &rest args-plist)
+          "Create a non-file and buffer with designated properties.
+        PREFIX is a string giving the beginning of the name, and ARGS-PLIST
+        is a series of keyword-value pairs, with allowed keywords
+        :size NUMBER, :mode MODE-FUNC, :include-content STRING.
+        Returns the created buffer."
+          (let* ((size    (plist-get args-plist :size))
+                 (include (plist-get args-plist :include-content))
+                 (mode    (plist-get args-plist :mode))
+                 (buf     (generate-new-buffer prefix)))
+            (push buf test-buffer-list) ; record for cleanup
+            (funcall set-buffer-mode buf mode)
+            (funcall set-buffer-contents buf size include)
+            buf)))
+       (clean-up
+        (lambda ()
+          "Restore all emacs state modified during the tests"
+          (while test-buffer-list       ; created temporary buffers
+            (let ((buf (pop test-buffer-list)))
+              (with-current-buffer buf (bury-buffer)) ; ensure not selected
+              (kill-buffer buf))))))
+  ;; Tests
+  (ert-deftest ibuffer-filter-inclusion-1 ()
+    "Tests inclusion using basic filter combinators with a single buffer."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((buf
+               (funcall create-file-buffer "ibuf-test-1" :size 100
+                        :include-content "One ring to rule them all\n")))
+          (should (ibuffer-included-in-filters-p buf '((size-gt . 99))))
+          (should (ibuffer-included-in-filters-p buf '((size-lt . 101))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((mode . fundamental-mode))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((content . "ring to rule them all"))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (content . "ring to rule them all")))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (and (content . "ring to rule them all"))))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (and (and (content . "ring to rule them all")))))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((or (content . "ring to rule them all")))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not (not (content . "ring to rule them all"))))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (size-gt . 99)
+                              (content . "ring to rule them all")
+                              (mode . fundamental-mode)
+                              (basename . "\\`ibuf-test-1")))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not (or (not (size-gt . 99))
+                                  (not (content . "ring to rule them all"))
+                                  (not (mode . fundamental-mode))
+                                  (not (basename . "\\`ibuf-test-1")))))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (or (size-gt . 99) (size-lt . 10))
+                              (and (content . "ring.*all")
+                                   (content . "rule")
+                                   (content . "them all")
+                                   (content . "One"))
+                              (not (mode . text-mode))
+                              (basename . "\\`ibuf-test-1"))))))
+      (funcall clean-up)))
+
+  (ert-deftest ibuffer-filter-inclusion-2 ()
+    "Tests inclusion of basic filters in combination on a single buffer."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((buf
+               (funcall create-file-buffer "ibuf-test-2" :size 200
+                        :mode #'text-mode
+                        :include-content "and in the darkness find them\n")))
+          (should (ibuffer-included-in-filters-p buf '((size-gt . 199))))
+          (should (ibuffer-included-in-filters-p buf '((size-lt . 201))))
+          (should (ibuffer-included-in-filters-p buf '((not size-gt . 200))))
+          (should (ibuffer-included-in-filters-p buf '((not (size-gt . 200)))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (size-gt . 199) (size-lt . 201)))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((or (size-gt . 199) (size-gt . 201)))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((or (size-gt . 201) (size-gt . 199)))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((size-gt . 199) (mode . text-mode)
+                         (content . "darkness find them"))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (size-gt . 199) (mode . text-mode)
+                              (content . "darkness find them")))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not (or (not (size-gt . 199)) (not (mode . text-mode))
+                                  (not (content . "darkness find them")))))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((or (size-gt . 200) (content . "darkness find them")
+                             (derived-mode . emacs-lisp-mode)))))
+          (should-not (ibuffer-included-in-filters-p
+                       buf '((or (size-gt . 200) (content . "rule them all")
+                                 (derived-mode . emacs-lisp-mode))))))
+      (funcall clean-up)))
+
+  (ert-deftest ibuffer-filter-inclusion-3 ()
+    "Tests inclusion with filename filters on specified buffers."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let* ((bufA
+                (funcall create-file-buffer "ibuf-test-3.a" :size 50
+                         :mode #'text-mode
+                         :include-content "...but a multitude of drops?\n"))
+               (bufB
+                (funcall create-non-file-buffer "ibuf-test-3.b" :size 50
+                         :mode #'text-mode
+                         :include-content "...but a multitude of drops?\n"))
+               (dirA (with-current-buffer bufA default-directory))
+               (dirB (with-current-buffer bufB default-directory)))
+          (should (ibuffer-included-in-filters-p
+                   bufA '((basename . "ibuf-test-3"))))
+          (should (ibuffer-included-in-filters-p
+                   bufA '((basename . "test-3\\.a"))))
+          (should (ibuffer-included-in-filters-p
+                   bufA '((file-extension . "a"))))
+          (should (ibuffer-included-in-filters-p
+                   bufA (list (cons 'directory dirA))))
+          (should-not (ibuffer-included-in-filters-p
+                       bufB '((basename . "ibuf-test-3"))))
+          (should-not (ibuffer-included-in-filters-p
+                       bufB '((file-extension . "b"))))
+          (should (ibuffer-included-in-filters-p
+                   bufB (list (cons 'directory dirB))))
+          (should (ibuffer-included-in-filters-p
+                   bufA '((name . "ibuf-test-3"))))
+          (should (ibuffer-included-in-filters-p
+                   bufB '((name . "ibuf-test-3")))))
+      (funcall clean-up)))
+
+  (ert-deftest ibuffer-filter-inclusion-4 ()
+    "Tests inclusion with various filters on a single buffer."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((buf
+               (funcall create-file-buffer "ibuf-test-4"
+                        :mode #'emacs-lisp-mode :suffix ".el"
+                        :include-content "(message \"--%s--\" 'emacs-rocks)\n")))
+          (should (ibuffer-included-in-filters-p
+                   buf '((file-extension . "el"))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((derived-mode . prog-mode))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((used-mode . emacs-lisp-mode))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((mode . emacs-lisp-mode))))
+          (with-current-buffer buf (set-buffer-modified-p t))
+          (should (ibuffer-included-in-filters-p buf '((modified))))
+          (with-current-buffer buf (set-buffer-modified-p nil))
+          (should (ibuffer-included-in-filters-p buf '((not modified))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (file-extension . "el")
+                              (derived-mode . prog-mode)
+                              (not modified)))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((or (file-extension . "tex")
+                             (derived-mode . prog-mode)
+                             (modified)))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((file-extension . "el")
+                         (derived-mode . prog-mode)
+                         (not modified)))))
+      (funcall clean-up)))
+
+  (ert-deftest ibuffer-filter-inclusion-5 ()
+    "Tests inclusion with various filters on a single buffer."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((buf
+               (funcall create-non-file-buffer "ibuf-test-5.el"
+                        :mode #'emacs-lisp-mode
+                        :include-content
+                        "(message \"--%s--\" \"It really does!\")\n")))
+          (should-not (ibuffer-included-in-filters-p
+                       buf '((file-extension . "el"))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((size-gt . 18))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((predicate . (lambda ()
+                                        (> (- (point-max) (point-min)) 18))))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (mode . emacs-lisp-mode)
+                              (or (starred-name)
+                                  (size-gt . 18))
+                              (and (not (size-gt . 100))
+                                   (content . "[Ii]t  *really does!")
+                                   (or (name . "test-5")
+                                       (not (filename . "test-5")))))))))
+      (funcall clean-up)))
+
+  (ert-deftest ibuffer-filter-inclusion-6 ()
+    "Tests inclusion using saved filters and DeMorgan's laws."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((buf
+               (funcall create-non-file-buffer "*ibuf-test-6*" :size 65
+                        :mode #'text-mode))
+              (buf2
+               (funcall create-file-buffer "ibuf-test-6a" :suffix ".html"
+                        :mode #'html-mode
+                        :include-content
+                        "<HTML><BODY><H1>Hello, World!</H1></BODY></HTML>")))
+          (should (ibuffer-included-in-filters-p buf '((starred-name))))
+          (should-not (ibuffer-included-in-filters-p
+                       buf '((saved . "text document"))))
+          (should (ibuffer-included-in-filters-p buf2 '((saved . "web"))))
+          (should (ibuffer-included-in-filters-p
+                   buf2 '((not (and (not (derived-mode . sgml-mode))
+                                    (not (derived-mode . css-mode))
+                                    (not (mode         . javascript-mode))
+                                    (not (mode         . js2-mode))
+                                    (not (mode         . scss-mode))
+                                    (not (derived-mode . haml-mode))
+                                    (not (mode         . sass-mode)))))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((and (starred-name)
+                              (or (size-gt . 50) (filename . "foo"))))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not (or (not starred-name)
+                                  (and (size-lt . 51)
+                                       (not (filename . "foo")))))))))
+      (funcall clean-up)))
+
+  (ert-deftest ibuffer-filter-inclusion-7 ()
+    "Tests inclusion with various filters on a single buffer."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((buf
+               (funcall create-non-file-buffer "ibuf-test-7"
+                        :mode #'artist-mode)))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not (starred-name)))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not starred-name))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not (not (not starred-name))))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not (modified)))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not modified))))
+          (should (ibuffer-included-in-filters-p
+                   buf '((not (not (not modified)))))))
+      (funcall clean-up)))
+
+  (ert-deftest ibuffer-filter-inclusion-8 ()
+    "Tests inclusion with various filters."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((bufA
+               (funcall create-non-file-buffer "ibuf-test-8a"
+                        :mode #'artist-mode))
+              (bufB (funcall create-non-file-buffer "*ibuf-test-8b*" :size 32))
+              (bufC (funcall create-file-buffer "ibuf-test8c" :suffix "*"
+                             :size 64))
+              (bufD (funcall create-file-buffer "*ibuf-test8d" :size 128))
+              (bufE (funcall create-file-buffer "*ibuf-test8e" :suffix "*<2>"
+                             :size 16))
+              (bufF (and (funcall create-non-file-buffer "*ibuf-test8f*")
+                         (funcall create-non-file-buffer "*ibuf-test8f*"
+                                  :size 8))))
+          (with-current-buffer bufA (set-buffer-modified-p t))
+          (should (ibuffer-included-in-filters-p
+                   bufA '((and (not starred-name)
+                               (modified)
+                               (name . "test-8")
+                               (not (size-gt . 100))
+                               (mode . picture-mode)))))
+          (with-current-buffer bufA (set-buffer-modified-p nil))
+          (should-not (ibuffer-included-in-filters-p
+                       bufA '((or (starred-name) (visiting-file) (modified)))))
+          (should (ibuffer-included-in-filters-p
+                   bufB '((and (starred-name)
+                               (name . "test.*8b")
+                               (size-gt . 31)
+                               (not visiting-file)))))
+          (should (ibuffer-included-in-filters-p
+                   bufC '((and (not (starred-name))
+                               (visiting-file)
+                               (name . "8c[^*]*\\*")
+                               (size-lt . 65)))))
+          (should (ibuffer-included-in-filters-p
+                   bufD '((and (not (starred-name))
+                               (visiting-file)
+                               (name . "\\`\\*.*test8d")
+                               (size-lt . 129)
+                               (size-gt . 127)))))
+          (should (ibuffer-included-in-filters-p
+                   bufE '((and (starred-name)
+                               (visiting-file)
+                               (name . "8e.*?\\*<[[:digit:]]+>")
+                               (size-gt . 10)))))
+          (should (ibuffer-included-in-filters-p
+                   bufF '((and (starred-name)
+                               (not (visiting-file))
+                               (name . "8f\\*<[[:digit:]]>")
+                               (size-lt . 10))))))
+      (funcall clean-up))))
+
+;; Test Filter Combination and Decomposition
+(let* (ibuffer-to-kill       ; if non-nil, kill this buffer at cleanup
+       (ibuffer-already 'check) ; existing ibuffer buffer to use but not kill
+       ;; Utility functions without polluting the environment
+       (get-test-ibuffer
+        (lambda ()
+          "Returns a test ibuffer-mode buffer, creating one if necessary.
+        If a new buffer is created, it is named  \"*Test-Ibuffer*\" and is
+        saved to `ibuffer-to-kill' for later cleanup."
+          (when (eq ibuffer-already 'check)
+            (setq ibuffer-already
+                  (catch 'found-buf
+                    (dolist (buf (buffer-list) nil)
+                      (when (with-current-buffer buf
+                              (derived-mode-p 'ibuffer-mode))
+                        (throw 'found-buf buf))))))
+          (or ibuffer-already
+              ibuffer-to-kill
+              (let ((test-ibuf-name "*Test-Ibuffer*"))
+                (ibuffer nil test-ibuf-name nil t)
+                (setq ibuffer-to-kill (get-buffer test-ibuf-name))))))
+       (clean-up
+        (lambda ()
+          "Restore all emacs state modified during the tests"
+          (when ibuffer-to-kill         ; created ibuffer
+            (with-current-buffer ibuffer-to-kill
+              (set-buffer-modified-p nil)
+              (bury-buffer))
+            (kill-buffer ibuffer-to-kill)
+            (setq ibuffer-to-kill nil))
+          (when (and ibuffer-already (not (eq ibuffer-already 'check)))
+            ;; restore existing ibuffer state
+            (ibuffer-update nil t)))))
+  ;; Tests
+  (ert-deftest ibuffer-decompose-filter ()
+    "Tests `ibuffer-decompose-filter' for and, or, not, and saved."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((ibuf (funcall get-test-ibuffer)))
+          (with-current-buffer ibuf
+            (let ((ibuffer-filtering-qualifiers nil)
+                  (ibuffer-filter-groups nil)
+                  (filters '((size-gt . 100) (not (starred-name))
+                             (name . "foo"))))
+              (progn
+                (push (cons 'or filters) ibuffer-filtering-qualifiers)
+                (ibuffer-decompose-filter)
+                (should (equal filters ibuffer-filtering-qualifiers))
+                (setq ibuffer-filtering-qualifiers nil))
+              (progn
+                (push (cons 'and filters) ibuffer-filtering-qualifiers)
+                (ibuffer-decompose-filter)
+                (should (equal filters ibuffer-filtering-qualifiers))
+                (setq ibuffer-filtering-qualifiers nil))
+              (progn
+                (push (list 'not (car filters)) ibuffer-filtering-qualifiers)
+                (ibuffer-decompose-filter)
+                (should (equal (list (car filters))
+                               ibuffer-filtering-qualifiers))
+                (setq ibuffer-filtering-qualifiers nil))
+              (progn
+                (push (cons 'not (car filters)) ibuffer-filtering-qualifiers)
+                (ibuffer-decompose-filter)
+                (should (equal (list (car filters))
+                               ibuffer-filtering-qualifiers))
+                (setq ibuffer-filtering-qualifiers nil))
+              (let ((gnus (assoc "gnus" ibuffer-saved-filters)))
+                (push '(saved . "gnus") ibuffer-filtering-qualifiers)
+                (ibuffer-decompose-filter)
+                (should (equal (cdr gnus) ibuffer-filtering-qualifiers))
+                (ibuffer-decompose-filter)
+                (should (equal (cdr (cadr gnus)) ibuffer-filtering-qualifiers))
+                (setq ibuffer-filtering-qualifiers nil))
+              (when (not (assoc "__unknown__" ibuffer-saved-filters))
+                (push '(saved . "__uknown__") ibuffer-filtering-qualifiers)
+                (should-error (ibuffer-decompose-filter) :type 'error)
+                (setq ibuffer-filtering-qualifiers nil))
+              (progn
+                (push (car filters) ibuffer-filtering-qualifiers)
+                (should-error (ibuffer-decompose-filter) :type 'error)
+                (setq ibuffer-filtering-qualifiers nil)))))
+      (funcall clean-up)))
+
+  (ert-deftest ibuffer-and-filter ()
+    "Tests `ibuffer-and-filter' in an Ibuffer buffer."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((ibuf (funcall get-test-ibuffer)))
+          (with-current-buffer ibuf
+            (let ((ibuffer-filtering-qualifiers nil)
+                  (ibuffer-filter-groups nil)
+                  (filters [(size-gt . 100) (not (starred-name))
+                            (filename . "A") (mode . text-mode)]))
+              (should-error (ibuffer-and-filter) :type 'error)
+              (progn
+                (push (aref filters 1) ibuffer-filtering-qualifiers)
+                (should-error (ibuffer-and-filter) :type 'error))
+              (should (progn
+                        (push (aref filters 0) ibuffer-filtering-qualifiers)
+                        (ibuffer-and-filter)
+                        (and (equal (list 'and (aref filters 0) (aref filters 1))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers)))))
+              (should (progn
+                        (ibuffer-and-filter 'decompose)
+                        (and (equal (aref filters 0)
+                                    (pop ibuffer-filtering-qualifiers))
+                             (equal (aref filters 1)
+                                    (pop ibuffer-filtering-qualifiers))
+                             (null ibuffer-filtering-qualifiers))))
+              (should (progn
+                        (push (list 'and (aref filters 2) (aref filters 3))
+                              ibuffer-filtering-qualifiers)
+                        (push (list 'and (aref filters 0) (aref filters 1))
+                              ibuffer-filtering-qualifiers)
+                        (ibuffer-and-filter)
+                        (and (equal (list 'and (aref filters 0) (aref filters 1)
+                                          (aref filters 2) (aref filters 3))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers)))))
+              (pop ibuffer-filtering-qualifiers)
+              (should (progn
+                        (push (list 'or (aref filters 2) (aref filters 3))
+                              ibuffer-filtering-qualifiers)
+                        (push (list 'and (aref filters 0) (aref filters 1))
+                              ibuffer-filtering-qualifiers)
+                        (ibuffer-and-filter)
+                        (and (equal (list 'and (aref filters 0) (aref filters 1)
+                                          (list 'or (aref filters 2)
+                                                (aref filters 3)))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers)))))
+              (pop ibuffer-filtering-qualifiers)
+              (should (progn
+                        (push (list 'and (aref filters 2) (aref filters 3))
+                              ibuffer-filtering-qualifiers)
+                        (push (list 'or (aref filters 0) (aref filters 1))
+                              ibuffer-filtering-qualifiers)
+                        (ibuffer-and-filter)
+                        (and (equal (list 'and (list 'or (aref filters 0)
+                                                    (aref filters 1))
+                                          (aref filters 2) (aref filters 3))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers)))))
+              (pop ibuffer-filtering-qualifiers)
+              (should (progn
+                        (push (list 'or (aref filters 2) (aref filters 3))
+                              ibuffer-filtering-qualifiers)
+                        (push (list 'or (aref filters 0) (aref filters 1))
+                              ibuffer-filtering-qualifiers)
+                        (ibuffer-and-filter)
+                        (and (equal (list 'and
+                                          (list 'or (aref filters 0)
+                                                (aref filters 1))
+                                          (list 'or (aref filters 2)
+                                                (aref filters 3)))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers))))))))
+      (funcall clean-up)))
+
+  (ert-deftest ibuffer-or-filter ()
+    "Tests `ibuffer-or-filter' in an Ibuffer buffer."
+    (skip-unless (featurep 'ibuf-ext))
+    (unwind-protect
+        (let ((ibuf (funcall get-test-ibuffer)))
+          (with-current-buffer ibuf
+            (let ((ibuffer-filtering-qualifiers nil)
+                  (ibuffer-filter-groups nil)
+                  (filters [(size-gt . 100) (not (starred-name))
+                            (filename . "A") (mode . text-mode)]))
+              (should-error (ibuffer-or-filter) :type 'error)
+              (progn
+                (push (aref filters 1) ibuffer-filtering-qualifiers)
+                (should-error (ibuffer-or-filter) :type 'error))
+              (should (progn
+                        (push (aref filters 0) ibuffer-filtering-qualifiers)
+                        (ibuffer-or-filter)
+                        (and (equal (list 'or (aref filters 0) (aref filters 1))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers)))))
+              (should (progn
+                        (ibuffer-or-filter 'decompose)
+                        (and (equal (aref filters 0)
+                                    (pop ibuffer-filtering-qualifiers))
+                             (equal (aref filters 1)
+                                    (pop ibuffer-filtering-qualifiers))
+                             (null ibuffer-filtering-qualifiers))))
+              (should (progn
+                        (push (list 'or (aref filters 2) (aref filters 3))
+                              ibuffer-filtering-qualifiers)
+                        (push (list 'or (aref filters 0) (aref filters 1))
+                              ibuffer-filtering-qualifiers)
+                        (ibuffer-or-filter)
+                        (and (equal (list 'or (aref filters 0) (aref filters 1)
+                                          (aref filters 2) (aref filters 3))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers)))))
+              (pop ibuffer-filtering-qualifiers)
+              (should (progn
+                        (push (list 'and (aref filters 2) (aref filters 3))
+                              ibuffer-filtering-qualifiers)
+                        (push (list 'or (aref filters 0) (aref filters 1))
+                              ibuffer-filtering-qualifiers)
+                        (ibuffer-or-filter)
+                        (and (equal (list 'or (aref filters 0) (aref filters 1)
+                                          (list 'and (aref filters 2)
+                                                (aref filters 3)))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers)))))
+              (pop ibuffer-filtering-qualifiers)
+              (should (progn
+                        (push (list 'or (aref filters 2) (aref filters 3))
+                              ibuffer-filtering-qualifiers)
+                        (push (list 'and (aref filters 0) (aref filters 1))
+                              ibuffer-filtering-qualifiers)
+                        (ibuffer-or-filter)
+                        (and (equal (list 'or (list 'and (aref filters 0)
+                                                    (aref filters 1))
+                                          (aref filters 2) (aref filters 3))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers)))))
+              (pop ibuffer-filtering-qualifiers)
+              (should (progn
+                        (push (list 'and (aref filters 2) (aref filters 3))
+                              ibuffer-filtering-qualifiers)
+                        (push (list 'and (aref filters 0) (aref filters 1))
+                              ibuffer-filtering-qualifiers)
+                        (ibuffer-or-filter)
+                        (and (equal (list 'or
+                                          (list 'and (aref filters 0)
+                                                (aref filters 1))
+                                          (list 'and (aref filters 2)
+                                                (aref filters 3)))
+                                    (car ibuffer-filtering-qualifiers))
+                             (null (cdr ibuffer-filtering-qualifiers))))))))
+      (funcall clean-up))))
+
+(ert-deftest ibuffer-format-qualifier ()
+  "Tests string recommendation of filter from `ibuffer-format-qualifier'."
+  (skip-unless (featurep 'ibuf-ext))
+  (let ((test1 '(mode . org-mode))
+        (test2 '(size-lt . 100))
+        (test3 '(derived-mode . prog-mode))
+        (test4 '(or (size-gt . 10000)
+                    (and (not (starred-name))
+                         (directory . "\\<org\\>"))))
+        (test5 '(or (filename . "scratch")
+                    (filename . "bonz")
+                    (filename . "temp")))
+        (test6 '(or (mode . emacs-lisp-mode) (file-extension . "elc?")
+                    (and (starred-name) (name . "elisp"))
+                    (mode . lisp-interaction-mode)))
+        (description (lambda (q)
+                       (cadr (assq q ibuffer-filtering-alist))))
+        (tag (lambda (&rest args )
+               (concat " [" (apply #'concat args) "]"))))
+    (should (equal (ibuffer-format-qualifier test1)
+                   (funcall tag (funcall description 'mode)
+                            ": " "org-mode")))
+    (should (equal (ibuffer-format-qualifier test2)
+                   (funcall tag (funcall description 'size-lt)
+                            ": " "100")))
+    (should (equal (ibuffer-format-qualifier test3)
+                   (funcall tag (funcall description 'derived-mode)
+                            ": " "prog-mode")))
+    (should (equal (ibuffer-format-qualifier test4)
+                   (funcall tag "OR"
+                            (funcall tag (funcall description 'size-gt)
+                                     ": " (format "%s" 10000))
+                            (funcall tag "AND"
+                                     (funcall tag "NOT"
+                                              (funcall tag
+                                                       (funcall description
+                                                                'starred-name)
+                                                       ": " "nil"))
+                                     (funcall tag
+                                              (funcall description 'directory)
+                                              ": " "\\<org\\>")))))
+    (should (equal (ibuffer-format-qualifier test5)
+                   (funcall tag "OR"
+                            (funcall tag (funcall description 'filename)
+                                     ": "  "scratch")
+                            (funcall tag (funcall description 'filename)
+                                     ": " "bonz")
+                            (funcall tag (funcall description 'filename)
+                                     ": " "temp"))))
+    (should (equal (ibuffer-format-qualifier test6)
+                   (funcall tag "OR"
+                            (funcall tag (funcall description 'mode)
+                                     ": " "emacs-lisp-mode")
+                            (funcall tag (funcall description 'file-extension)
+                                     ": " "elc?")
+                            (funcall tag "AND"
+                                     (funcall tag
+                                              (funcall description 'starred-name)
+                                              ": " "nil")
+                                     (funcall tag
+                                              (funcall description 'name)
+                                              ": " "elisp"))
+                            (funcall tag (funcall description 'mode)
+                                     ": " "lisp-interaction-mode"))))))
+
+(ert-deftest ibuffer-unary-operand ()
+  "Tests `ibuffer-unary-operand': (not cell) or (not . cell) -> cell."
+  (skip-unless (featurep 'ibuf-ext))
+  (should (equal (ibuffer-unary-operand '(not . (mode "foo")))
+                 '(mode "foo")))
+  (should (equal (ibuffer-unary-operand '(not (mode "foo")))
+                 '(mode "foo")))
+  (should (equal (ibuffer-unary-operand '(not "cdr"))
+                 '("cdr")))
+  (should (equal (ibuffer-unary-operand '(not)) nil))
+  (should (equal (ibuffer-unary-operand '(not . a)) 'a)))
+
 (provide 'ibuffer-tests)
 ;; ibuffer-tests.el ends here
-- 
2.10.2

From 4f951f46bab2ae36d9a3cf94489a9e0978ee6fb2 Mon Sep 17 00:00:00 2001
From: Christopher Genovese <genovese@cmu.edu>
Date: Sat, 3 Dec 2016 00:07:25 +0900
Subject: [PATCH 2/2] ibuffer: Update key bindings

* lisp/ibuffer.el (ibuffer-mode-map): Bind 'ibuffer-filter-by-directory'
and 'ibuffer-filter-chosen-by-completion' to '//' and '/TAB' respectively.
Rebind 'ibuffer-filter-disable' to '/ DEL'.
; * etc/NEWS: Update NEWS entries.
---
 etc/NEWS        | 10 ++++++----
 lisp/ibuffer.el |  5 +++--
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 2dd6b5c..ec56ea0 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -312,12 +312,14 @@ to '/b', '/.', '//', '/*', '/i' and '/v'.
 
 ---
 *** Two new commands 'ibuffer-filter-chosen-by-completion'
-and `ibuffer-and-filter', the second bound to '/&'.
+and `ibuffer-and-filter'; bound to '/ TAB' and '/&'
+respectively.
 
 ---
-*** The commands `ibuffer-pop-filter', `ibuffer-pop-filter-group',
-`ibuffer-or-filter' and `ibuffer-filter-disable' have the alternative
-bindings '/<up>', '/S-<up>', '/|' and '/DEL', respectively.
+*** The key binding for `ibuffer-filter-disable' has being changed
+to '/DEL'; the commands `ibuffer-pop-filter', `ibuffer-pop-filter-group'
+and `ibuffer-or-filter' have the alternative bindings '/<up>', '/S-<up>'
+and '/|'.
 
 ---
 *** The data format specifying filters has been extended to allow
diff --git a/lisp/ibuffer.el b/lisp/ibuffer.el
index 0205861..72e37f5 100644
--- a/lisp/ibuffer.el
+++ b/lisp/ibuffer.el
@@ -526,12 +526,14 @@ ibuffer-mode-map
     (define-key map (kbd "/ f") 'ibuffer-filter-by-filename)
     (define-key map (kbd "/ b") 'ibuffer-filter-by-basename)
     (define-key map (kbd "/ .") 'ibuffer-filter-by-file-extension)
+    (define-key map (kbd "/ /") 'ibuffer-filter-by-directory)
     (define-key map (kbd "/ <") 'ibuffer-filter-by-size-lt)
     (define-key map (kbd "/ >") 'ibuffer-filter-by-size-gt)
     (define-key map (kbd "/ i") 'ibuffer-filter-by-modified)
     (define-key map (kbd "/ v") 'ibuffer-filter-by-visiting-file)
     (define-key map (kbd "/ c") 'ibuffer-filter-by-content)
     (define-key map (kbd "/ e") 'ibuffer-filter-by-predicate)
+    (define-key map (kbd "/ TAB") 'ibuffer-filter-chosen-by-completion)
 
     (define-key map (kbd "/ r") 'ibuffer-switch-to-saved-filters)
     (define-key map (kbd "/ a") 'ibuffer-add-saved-filters)
@@ -542,7 +544,6 @@ ibuffer-mode-map
     (define-key map (kbd "/ <up>") 'ibuffer-pop-filter)
     (define-key map (kbd "/ !") 'ibuffer-negate-filter)
     (define-key map (kbd "/ t") 'ibuffer-exchange-filters)
-    (define-key map (kbd "/ TAB") 'ibuffer-exchange-filters)
     (define-key map (kbd "/ o") 'ibuffer-or-filter)
     (define-key map (kbd "/ |") 'ibuffer-or-filter)
     (define-key map (kbd "/ &") 'ibuffer-and-filter)
@@ -550,7 +551,7 @@ ibuffer-mode-map
     (define-key map (kbd "/ P") 'ibuffer-pop-filter-group)
     (define-key map (kbd "/ S-<up>") 'ibuffer-pop-filter-group)
     (define-key map (kbd "/ D") 'ibuffer-decompose-filter-group)
-    (define-key map (kbd "/ /") 'ibuffer-filter-disable)
+    (define-key map (kbd "/ DEL") 'ibuffer-filter-disable)
 
     (define-key map (kbd "M-n") 'ibuffer-forward-filter-group)
     (define-key map "\t" 'ibuffer-forward-filter-group)
-- 
2.10.2

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
In GNU Emacs 26.0.50.1 (x86_64-pc-linux-gnu, GTK+ Version 3.22.4)
 of 2016-12-02
Repository revision: 66d6e7e9ecf5e481f8c2c3a4f88411f66c869a6e



  reply	other threads:[~2016-12-02 15:56 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-11-17 15:53 Ibuffer improvements: filtering, documentation, bug fix, tests Christopher Genovese
2016-11-18 13:08 ` Richard Stallman
2016-11-19 11:17 ` Tino Calancha
2016-11-22 23:45   ` Christopher Genovese
2016-11-26 10:53     ` Tino Calancha
2016-12-01  3:10       ` Christopher Genovese
2016-12-02 15:56         ` Tino Calancha [this message]
2016-12-09  1:00           ` Tino Calancha
2016-12-14 19:47             ` Christopher Genovese
2016-12-15  6:27               ` Tino Calancha

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87twamnx3f.fsf@gmail.com \
    --to=tino.calancha@gmail.com \
    --cc=emacs-devel@gnu.org \
    --cc=genovese@cmu.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).