diff options
| author | Christopher Genovese | 2016-12-20 17:41:56 +0900 |
|---|---|---|
| committer | Tino Calancha | 2016-12-20 17:41:56 +0900 |
| commit | 35aaa6b6aa9a2e7b42465603fb32355a009c510f (patch) | |
| tree | 59e390dc4ab4b10398da125ce1d1e4f2c03b9570 | |
| parent | f8072cd5c16f855505f6a0ce6a6b30309735705d (diff) | |
| download | emacs-35aaa6b6aa9a2e7b42465603fb32355a009c510f.tar.gz emacs-35aaa6b6aa9a2e7b42465603fb32355a009c510f.zip | |
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.
| -rw-r--r-- | etc/NEWS | 21 | ||||
| -rw-r--r-- | lisp/ibuf-ext.el | 347 | ||||
| -rw-r--r-- | lisp/ibuffer.el | 55 | ||||
| -rw-r--r-- | test/lisp/ibuffer-tests.el | 667 |
4 files changed, 1009 insertions, 81 deletions
| @@ -333,6 +333,27 @@ bound to 'Buffer-menu-unmark-all-buffers'. | |||
| 333 | ** Ibuffer | 333 | ** Ibuffer |
| 334 | 334 | ||
| 335 | --- | 335 | --- |
| 336 | *** New filter commands `ibuffer-filter-by-basename', | ||
| 337 | `ibuffer-filter-by-file-extension', `ibuffer-filter-by-directory', | ||
| 338 | `ibuffer-filter-by-starred-name', `ibuffer-filter-by-modified' | ||
| 339 | and `ibuffer-filter-by-visiting-file'; bound respectively | ||
| 340 | to '/b', '/.', '//', '/*', '/i' and '/v'. | ||
| 341 | |||
| 342 | --- | ||
| 343 | *** Two new commands 'ibuffer-filter-chosen-by-completion' | ||
| 344 | and `ibuffer-and-filter', the second bound to '/&'. | ||
| 345 | |||
| 346 | --- | ||
| 347 | *** The commands `ibuffer-pop-filter', `ibuffer-pop-filter-group', | ||
| 348 | `ibuffer-or-filter' and `ibuffer-filter-disable' have the alternative | ||
| 349 | bindings '/<up>', '/S-<up>', '/|' and '/DEL', respectively. | ||
| 350 | |||
| 351 | --- | ||
| 352 | *** The data format specifying filters has been extended to allow | ||
| 353 | explicit logical 'and', and a more flexible form for logical 'not'. | ||
| 354 | See 'ibuffer-filtering-qualifiers' doc string for full details. | ||
| 355 | |||
| 356 | --- | ||
| 336 | *** A new command 'ibuffer-copy-buffername-as-kill'; bound | 357 | *** A new command 'ibuffer-copy-buffername-as-kill'; bound |
| 337 | to 'B'. | 358 | to 'B'. |
| 338 | 359 | ||
diff --git a/lisp/ibuf-ext.el b/lisp/ibuf-ext.el index 9ce7b5a4846..7ebfecd3749 100644 --- a/lisp/ibuf-ext.el +++ b/lisp/ibuf-ext.el | |||
| @@ -28,6 +28,13 @@ | |||
| 28 | ;; These functions should be automatically loaded when called, but you | 28 | ;; These functions should be automatically loaded when called, but you |
| 29 | ;; can explicitly (require 'ibuf-ext) in your ~/.emacs to have them | 29 | ;; can explicitly (require 'ibuf-ext) in your ~/.emacs to have them |
| 30 | ;; preloaded. | 30 | ;; preloaded. |
| 31 | ;; | ||
| 32 | ;; For details on the structure of ibuffer filters and filter groups, | ||
| 33 | ;; see the documentation for variables `ibuffer-filtering-qualifiers', | ||
| 34 | ;; `ibuffer-filter-groups', and `ibuffer-saved-filters' in that order. | ||
| 35 | ;; The variable `ibuffer-filtering-alist' contains names and | ||
| 36 | ;; descriptions of the currently defined filters; also see the macro | ||
| 37 | ;; `define-ibuffer-filter'. | ||
| 31 | 38 | ||
| 32 | ;;; Code: | 39 | ;;; Code: |
| 33 | 40 | ||
| @@ -139,19 +146,33 @@ Returns (OLD-FORMAT-DETECTED . UPDATED-SAVED-FILTERS-LIST)." | |||
| 139 | (fixed (mapcar fix-filter filters))) | 146 | (fixed (mapcar fix-filter filters))) |
| 140 | (cons old-format-detected fixed)))) | 147 | (cons old-format-detected fixed)))) |
| 141 | 148 | ||
| 142 | (defcustom ibuffer-saved-filters '(("gnus" | 149 | (defcustom ibuffer-saved-filters '(("programming" |
| 150 | (or (derived-mode . prog-mode) | ||
| 151 | (mode . ess-mode) | ||
| 152 | (mode . compilation-mode))) | ||
| 153 | ("text document" | ||
| 154 | (and (derived-mode . text-mode) | ||
| 155 | (not (starred-name)))) | ||
| 156 | ("TeX" | ||
| 157 | (or (derived-mode . tex-mode) | ||
| 158 | (mode . latex-mode) | ||
| 159 | (mode . context-mode) | ||
| 160 | (mode . ams-tex-mode) | ||
| 161 | (mode . bibtex-mode))) | ||
| 162 | ("web" | ||
| 163 | (or (derived-mode . sgml-mode) | ||
| 164 | (derived-mode . css-mode) | ||
| 165 | (mode . javascript-mode) | ||
| 166 | (mode . js2-mode) | ||
| 167 | (mode . scss-mode) | ||
| 168 | (derived-mode . haml-mode) | ||
| 169 | (mode . sass-mode))) | ||
| 170 | ("gnus" | ||
| 143 | (or (mode . message-mode) | 171 | (or (mode . message-mode) |
| 144 | (mode . mail-mode) | 172 | (mode . mail-mode) |
| 145 | (mode . gnus-group-mode) | 173 | (mode . gnus-group-mode) |
| 146 | (mode . gnus-summary-mode) | 174 | (mode . gnus-summary-mode) |
| 147 | (mode . gnus-article-mode))) | 175 | (mode . gnus-article-mode)))) |
| 148 | ("programming" | ||
| 149 | (or (mode . emacs-lisp-mode) | ||
| 150 | (mode . cperl-mode) | ||
| 151 | (mode . c-mode) | ||
| 152 | (mode . java-mode) | ||
| 153 | (mode . idl-mode) | ||
| 154 | (mode . lisp-mode)))) | ||
| 155 | 176 | ||
| 156 | "An alist mapping saved filter names to filter specifications. | 177 | "An alist mapping saved filter names to filter specifications. |
| 157 | 178 | ||
| @@ -214,8 +235,48 @@ Alternative ways to save the repaired value: | |||
| 214 | ")) | 235 | ")) |
| 215 | 236 | ||
| 216 | (defvar ibuffer-filtering-qualifiers nil | 237 | (defvar ibuffer-filtering-qualifiers nil |
| 217 | "A list like (SYMBOL . QUALIFIER) which filters the current buffer list. | 238 | "A list specifying the filters currently acting on the buffer list. |
| 218 | See also `ibuffer-filtering-alist'.") | 239 | |
| 240 | If this list is nil, then no filters are currently in | ||
| 241 | effect. Otherwise, each element of this list specifies a single | ||
| 242 | filter, and all of the specified filters in the list are applied | ||
| 243 | successively to the buffer list. | ||
| 244 | |||
| 245 | Each filter specification can be of two types: simple or compound. | ||
| 246 | |||
| 247 | A simple filter specification has the form (SYMBOL . QUALIFIER), | ||
| 248 | where SYMBOL is a key in the alist `ibuffer-filtering-alist' that | ||
| 249 | determines the filter function to use and QUALIFIER is the data | ||
| 250 | passed to that function (along with the buffer being considered). | ||
| 251 | |||
| 252 | A compound filter specification can have one of four forms: | ||
| 253 | |||
| 254 | -- (not FILTER-SPEC) | ||
| 255 | |||
| 256 | Represents the logical complement of FILTER-SPEC, which | ||
| 257 | is any single filter specification, simple or compound. | ||
| 258 | The form (not . FILTER-SPEC) is also accepted here. | ||
| 259 | |||
| 260 | -- (and FILTER-SPECS...) | ||
| 261 | |||
| 262 | Represents the logical-and of the filters defined by one or | ||
| 263 | more filter specifications FILTER-SPECS..., where each | ||
| 264 | specification can be simple or compound. Note that and is | ||
| 265 | implicitly applied to the filters in the top-level list. | ||
| 266 | |||
| 267 | -- (or FILTER-SPECS...) | ||
| 268 | |||
| 269 | Represents the logical-or of the filters defined by one or | ||
| 270 | more filter specifications FILTER-SPECS..., where each | ||
| 271 | specification can be simple or compound. | ||
| 272 | |||
| 273 | -- (saved . \"NAME\") | ||
| 274 | |||
| 275 | Represents the filter saved under the string NAME | ||
| 276 | in the alist `ibuffer-saved-filters'. It is an | ||
| 277 | error to name a filter that has not been saved. | ||
| 278 | |||
| 279 | This variable is local to each ibuffer buffer.") | ||
| 219 | 280 | ||
| 220 | ;; This is now frobbed by `define-ibuffer-filter'. | 281 | ;; This is now frobbed by `define-ibuffer-filter'. |
| 221 | (defvar ibuffer-filtering-alist nil | 282 | (defvar ibuffer-filtering-alist nil |
| @@ -247,10 +308,18 @@ to this variable." | |||
| 247 | (defvar ibuffer-compiled-filter-formats nil) | 308 | (defvar ibuffer-compiled-filter-formats nil) |
| 248 | 309 | ||
| 249 | (defvar ibuffer-filter-groups nil | 310 | (defvar ibuffer-filter-groups nil |
| 250 | "A list like ((\"NAME\" ((SYMBOL . QUALIFIER) ...) ...) which groups buffers. | 311 | "An alist giving this buffer's active filter groups, or nil if none. |
| 251 | The SYMBOL should be one from `ibuffer-filtering-alist'. | 312 | |
| 252 | The QUALIFIER should be the same as QUALIFIER in | 313 | This alist maps filter group labels to filter specification |
| 253 | `ibuffer-filtering-qualifiers'.") | 314 | lists. Each element has the form (\"LABEL\" FILTER-SPECS...), |
| 315 | where FILTER-SPECS... represents one or more filter | ||
| 316 | specifications of the same form as allowed as elements of | ||
| 317 | `ibuffer-filtering-qualifiers'. | ||
| 318 | |||
| 319 | Each filter group is displayed as a separate section in the | ||
| 320 | ibuffer list, headed by LABEL and displaying only the buffers | ||
| 321 | that pass through all the filters associated with NAME in this | ||
| 322 | list.") | ||
| 254 | 323 | ||
| 255 | (defcustom ibuffer-show-empty-filter-groups t | 324 | (defcustom ibuffer-show-empty-filter-groups t |
| 256 | "If non-nil, then show the names of filter groups which are empty." | 325 | "If non-nil, then show the names of filter groups which are empty." |
| @@ -260,20 +329,21 @@ The QUALIFIER should be the same as QUALIFIER in | |||
| 260 | (defcustom ibuffer-saved-filter-groups nil | 329 | (defcustom ibuffer-saved-filter-groups nil |
| 261 | "An alist of filtering groups to switch between. | 330 | "An alist of filtering groups to switch between. |
| 262 | 331 | ||
| 263 | This variable should look like ((\"STRING\" QUALIFIERS) | 332 | Each element is of the form (\"NAME\" . FILTER-GROUP-LIST), |
| 264 | (\"STRING\" QUALIFIERS) ...), where | 333 | where NAME is a unique but arbitrary name and FILTER-GROUP-LIST |
| 265 | QUALIFIERS is a list of the same form as | 334 | is a list of filter groups with the same structure as |
| 266 | `ibuffer-filtering-qualifiers'. | 335 | allowed for `ibuffer-filter-groups'. |
| 267 | 336 | ||
| 268 | See also the variables `ibuffer-filter-groups', | 337 | See also the functions `ibuffer-save-filter-groups' and |
| 269 | `ibuffer-filtering-qualifiers', `ibuffer-filtering-alist', and the | 338 | `ibuffer-switch-to-saved-filter-groups' for saving and switching |
| 270 | functions `ibuffer-switch-to-saved-filter-groups', | 339 | between sets of filter groups, and the variable |
| 271 | `ibuffer-save-filter-groups'." | 340 | `ibuffer-save-with-custom' that affects how this information is |
| 341 | saved." | ||
| 272 | :type '(repeat sexp) | 342 | :type '(repeat sexp) |
| 273 | :group 'ibuffer) | 343 | :group 'ibuffer) |
| 274 | 344 | ||
| 275 | (defvar ibuffer-hidden-filter-groups nil | 345 | (defvar ibuffer-hidden-filter-groups nil |
| 276 | "A list of filtering groups which are currently hidden.") | 346 | "The list of filter groups that are currently hidden.") |
| 277 | 347 | ||
| 278 | (defvar ibuffer-filter-group-kill-ring nil) | 348 | (defvar ibuffer-filter-group-kill-ring nil) |
| 279 | 349 | ||
| @@ -602,18 +672,38 @@ To evaluate a form without viewing the buffer, see `ibuffer-do-eval'." | |||
| 602 | 672 | ||
| 603 | ;;;###autoload | 673 | ;;;###autoload |
| 604 | (defun ibuffer-included-in-filters-p (buf filters) | 674 | (defun ibuffer-included-in-filters-p (buf filters) |
| 675 | "Return non-nil if BUF passes all FILTERS. | ||
| 676 | |||
| 677 | BUF is a lisp buffer object, and FILTERS is a list of filter | ||
| 678 | specifications with the same structure as | ||
| 679 | `ibuffer-filtering-qualifiers'." | ||
| 605 | (not | 680 | (not |
| 606 | (memq nil ;; a filter will return nil if it failed | 681 | (memq nil ;; a filter will return nil if it failed |
| 607 | (mapcar | 682 | (mapcar #'(lambda (filter) |
| 608 | ;; filter should be like (TYPE . QUALIFIER), or | 683 | (ibuffer-included-in-filter-p buf filter)) |
| 609 | ;; (or (TYPE . QUALIFIER) (TYPE . QUALIFIER) ...) | 684 | filters)))) |
| 610 | #'(lambda (qual) | 685 | |
| 611 | (ibuffer-included-in-filter-p buf qual)) | 686 | (defun ibuffer-unary-operand (filter) |
| 612 | filters)))) | 687 | "Extracts operand from a unary compound FILTER specification. |
| 688 | |||
| 689 | FILTER should be a cons cell of either form (f . d) or (f d), | ||
| 690 | where operand d is itself a cons cell, or nil. Returns d." | ||
| 691 | (let* ((tail (cdr filter)) | ||
| 692 | (maybe-q (car-safe tail))) | ||
| 693 | (if (consp maybe-q) maybe-q tail))) | ||
| 613 | 694 | ||
| 614 | (defun ibuffer-included-in-filter-p (buf filter) | 695 | (defun ibuffer-included-in-filter-p (buf filter) |
| 696 | "Return non-nil if BUF pass FILTER. | ||
| 697 | |||
| 698 | BUF is a lisp buffer object, and FILTER is a filter | ||
| 699 | specification, with the same structure as an element of the list | ||
| 700 | `ibuffer-filtering-qualifiers'." | ||
| 615 | (if (eq (car filter) 'not) | 701 | (if (eq (car filter) 'not) |
| 616 | (not (ibuffer-included-in-filter-p-1 buf (cdr filter))) | 702 | (let ((inner (ibuffer-unary-operand filter))) |
| 703 | ;; Allows (not (not ...)) etc, which may be overkill | ||
| 704 | (if (eq (car inner) 'not) | ||
| 705 | (ibuffer-included-in-filter-p buf (ibuffer-unary-operand inner)) | ||
| 706 | (not (ibuffer-included-in-filter-p-1 buf inner)))) | ||
| 617 | (ibuffer-included-in-filter-p-1 buf filter))) | 707 | (ibuffer-included-in-filter-p-1 buf filter))) |
| 618 | 708 | ||
| 619 | (defun ibuffer-included-in-filter-p-1 (buf filter) | 709 | (defun ibuffer-included-in-filter-p-1 (buf filter) |
| @@ -621,9 +711,19 @@ To evaluate a form without viewing the buffer, see `ibuffer-do-eval'." | |||
| 621 | (not | 711 | (not |
| 622 | (pcase (car filter) | 712 | (pcase (car filter) |
| 623 | (`or | 713 | (`or |
| 714 | ;;; ATTN: Short-circuiting alternative with parallel structure w/`and | ||
| 715 | ;;(catch 'has-match | ||
| 716 | ;; (dolist (filter-spec (cdr filter) nil) | ||
| 717 | ;; (when (ibuffer-included-in-filter-p buf filter-spec) | ||
| 718 | ;; (throw 'has-match t)))) | ||
| 624 | (memq t (mapcar #'(lambda (x) | 719 | (memq t (mapcar #'(lambda (x) |
| 625 | (ibuffer-included-in-filter-p buf x)) | 720 | (ibuffer-included-in-filter-p buf x)) |
| 626 | (cdr filter)))) | 721 | (cdr filter)))) |
| 722 | (`and | ||
| 723 | (catch 'no-match | ||
| 724 | (dolist (filter-spec (cdr filter) t) | ||
| 725 | (unless (ibuffer-included-in-filter-p buf filter-spec) | ||
| 726 | (throw 'no-match nil))))) | ||
| 627 | (`saved | 727 | (`saved |
| 628 | (let ((data (assoc (cdr filter) ibuffer-saved-filters))) | 728 | (let ((data (assoc (cdr filter) ibuffer-saved-filters))) |
| 629 | (unless data | 729 | (unless data |
| @@ -916,17 +1016,17 @@ group definitions by setting `ibuffer-filter-groups' to nil." | |||
| 916 | (when buf | 1016 | (when buf |
| 917 | (ibuffer-jump-to-buffer (buffer-name buf))))) | 1017 | (ibuffer-jump-to-buffer (buffer-name buf))))) |
| 918 | 1018 | ||
| 919 | (defun ibuffer-push-filter (qualifier) | 1019 | (defun ibuffer-push-filter (filter-specification) |
| 920 | "Add QUALIFIER to `ibuffer-filtering-qualifiers'." | 1020 | "Add FILTER-SPECIFICATION to `ibuffer-filtering-qualifiers'." |
| 921 | (push qualifier ibuffer-filtering-qualifiers)) | 1021 | (push filter-specification ibuffer-filtering-qualifiers)) |
| 922 | 1022 | ||
| 923 | ;;;###autoload | 1023 | ;;;###autoload |
| 924 | (defun ibuffer-decompose-filter () | 1024 | (defun ibuffer-decompose-filter () |
| 925 | "Separate the top compound filter (OR, NOT, or SAVED) in this buffer. | 1025 | "Separate this buffer's top compound filter (AND, OR, NOT, or SAVED). |
| 926 | 1026 | ||
| 927 | This means that the topmost filter on the filtering stack, which must | 1027 | This means that the topmost filter on the filtering stack, which must |
| 928 | be a complex filter like (OR [name: foo] [mode: bar-mode]), will be | 1028 | be a complex filter like (OR [name: foo] [mode: bar-mode]), will be |
| 929 | turned into two separate filters [name: foo] and [mode: bar-mode]." | 1029 | turned into separate filters, like [name: foo] and [mode: bar-mode]." |
| 930 | (interactive) | 1030 | (interactive) |
| 931 | (unless ibuffer-filtering-qualifiers | 1031 | (unless ibuffer-filtering-qualifiers |
| 932 | (error "No filters in effect")) | 1032 | (error "No filters in effect")) |
| @@ -935,14 +1035,14 @@ turned into two separate filters [name: foo] and [mode: bar-mode]." | |||
| 935 | (tail (cdr filters)) | 1035 | (tail (cdr filters)) |
| 936 | (value | 1036 | (value |
| 937 | (pcase (caar filters) | 1037 | (pcase (caar filters) |
| 938 | (`or (nconc head tail)) | 1038 | ((or `or 'and) (nconc head tail)) |
| 939 | (`saved | 1039 | (`saved |
| 940 | (let ((data (assoc head ibuffer-saved-filters))) | 1040 | (let ((data (assoc head ibuffer-saved-filters))) |
| 941 | (unless data | 1041 | (unless data |
| 942 | (ibuffer-filter-disable) | 1042 | (ibuffer-filter-disable) |
| 943 | (error "Unknown saved filter %s" head)) | 1043 | (error "Unknown saved filter %s" head)) |
| 944 | (append (cdr data) tail))) | 1044 | (append (cdr data) tail))) |
| 945 | (`not (cons head tail)) | 1045 | (`not (cons (ibuffer-unary-operand (car filters)) tail)) |
| 946 | (_ | 1046 | (_ |
| 947 | (error "Filter type %s is not compound" (caar filters)))))) | 1047 | (error "Filter type %s is not compound" (caar filters)))))) |
| 948 | (setq ibuffer-filtering-qualifiers value)) | 1048 | (setq ibuffer-filtering-qualifiers value)) |
| @@ -971,31 +1071,36 @@ turned into two separate filters [name: foo] and [mode: bar-mode]." | |||
| 971 | ibuffer-filtering-qualifiers)) | 1071 | ibuffer-filtering-qualifiers)) |
| 972 | (ibuffer-update nil t)) | 1072 | (ibuffer-update nil t)) |
| 973 | 1073 | ||
| 1074 | (defun ibuffer--or-and-filter (op decompose) | ||
| 1075 | (if decompose | ||
| 1076 | (if (eq op (caar ibuffer-filtering-qualifiers)) | ||
| 1077 | (ibuffer-decompose-filter) | ||
| 1078 | (error "Top filter is not an %s" (upcase (symbol-name op)))) | ||
| 1079 | (when (< (length ibuffer-filtering-qualifiers) 2) | ||
| 1080 | (error "Need two filters to %s" (upcase (symbol-name op)))) | ||
| 1081 | ;; If either filter is an op, eliminate unnecessary nesting. | ||
| 1082 | (let ((first (pop ibuffer-filtering-qualifiers)) | ||
| 1083 | (second (pop ibuffer-filtering-qualifiers))) | ||
| 1084 | (push (nconc (if (eq op (car first)) first (list op first)) | ||
| 1085 | (if (eq op (car second)) (cdr second) (list second))) | ||
| 1086 | ibuffer-filtering-qualifiers))) | ||
| 1087 | (ibuffer-update nil t)) | ||
| 1088 | |||
| 974 | ;;;###autoload | 1089 | ;;;###autoload |
| 975 | (defun ibuffer-or-filter (&optional reverse) | 1090 | (defun ibuffer-or-filter (&optional decompose) |
| 976 | "Replace the top two filters in this buffer with their logical OR. | 1091 | "Replace the top two filters in this buffer with their logical OR. |
| 977 | If optional argument REVERSE is non-nil, instead break the top OR | 1092 | If optional argument DECOMPOSE is non-nil, instead break the top OR |
| 978 | filter into parts." | 1093 | filter into parts." |
| 979 | (interactive "P") | 1094 | (interactive "P") |
| 980 | (if reverse | 1095 | (ibuffer--or-and-filter 'or decompose)) |
| 981 | (progn | 1096 | |
| 982 | (when (or (null ibuffer-filtering-qualifiers) | 1097 | ;;;###autoload |
| 983 | (not (eq 'or (caar ibuffer-filtering-qualifiers)))) | 1098 | (defun ibuffer-and-filter (&optional decompose) |
| 984 | (error "Top filter is not an OR")) | 1099 | "Replace the top two filters in this buffer with their logical AND. |
| 985 | (let ((lim (pop ibuffer-filtering-qualifiers))) | 1100 | If optional argument DECOMPOSE is non-nil, instead break the top AND |
| 986 | (setq ibuffer-filtering-qualifiers | 1101 | filter into parts." |
| 987 | (nconc (cdr lim) ibuffer-filtering-qualifiers)))) | 1102 | (interactive "P") |
| 988 | (when (< (length ibuffer-filtering-qualifiers) 2) | 1103 | (ibuffer--or-and-filter 'and decompose)) |
| 989 | (error "Need two filters to OR")) | ||
| 990 | ;; If the second filter is an OR, just add to it. | ||
| 991 | (let ((first (pop ibuffer-filtering-qualifiers)) | ||
| 992 | (second (pop ibuffer-filtering-qualifiers))) | ||
| 993 | (if (eq 'or (car second)) | ||
| 994 | (push (nconc (list 'or first) (cdr second)) | ||
| 995 | ibuffer-filtering-qualifiers) | ||
| 996 | (push (list 'or first second) | ||
| 997 | ibuffer-filtering-qualifiers)))) | ||
| 998 | (ibuffer-update nil t)) | ||
| 999 | 1104 | ||
| 1000 | (defun ibuffer-maybe-save-stuff () | 1105 | (defun ibuffer-maybe-save-stuff () |
| 1001 | (when ibuffer-save-with-custom | 1106 | (when ibuffer-save-with-custom |
| @@ -1069,7 +1174,9 @@ Interactively, prompt for NAME, and use the current filters." | |||
| 1069 | 1174 | ||
| 1070 | (defun ibuffer-format-qualifier (qualifier) | 1175 | (defun ibuffer-format-qualifier (qualifier) |
| 1071 | (if (eq (car-safe qualifier) 'not) | 1176 | (if (eq (car-safe qualifier) 'not) |
| 1072 | (concat " [NOT" (ibuffer-format-qualifier-1 (cdr qualifier)) "]") | 1177 | (concat " [NOT" |
| 1178 | (ibuffer-format-qualifier-1 (ibuffer-unary-operand qualifier)) | ||
| 1179 | "]") | ||
| 1073 | (ibuffer-format-qualifier-1 qualifier))) | 1180 | (ibuffer-format-qualifier-1 qualifier))) |
| 1074 | 1181 | ||
| 1075 | (defun ibuffer-format-qualifier-1 (qualifier) | 1182 | (defun ibuffer-format-qualifier-1 (qualifier) |
| @@ -1078,14 +1185,16 @@ Interactively, prompt for NAME, and use the current filters." | |||
| 1078 | (concat " [filter: " (cdr qualifier) "]")) | 1185 | (concat " [filter: " (cdr qualifier) "]")) |
| 1079 | (`or | 1186 | (`or |
| 1080 | (concat " [OR" (mapconcat #'ibuffer-format-qualifier | 1187 | (concat " [OR" (mapconcat #'ibuffer-format-qualifier |
| 1081 | (cdr qualifier) "") "]")) | 1188 | (cdr qualifier) "") "]")) |
| 1189 | (`and | ||
| 1190 | (concat " [AND" (mapconcat #'ibuffer-format-qualifier | ||
| 1191 | (cdr qualifier) "") "]")) | ||
| 1082 | (_ | 1192 | (_ |
| 1083 | (let ((type (assq (car qualifier) ibuffer-filtering-alist))) | 1193 | (let ((type (assq (car qualifier) ibuffer-filtering-alist))) |
| 1084 | (unless qualifier | 1194 | (unless qualifier |
| 1085 | (error "Ibuffer: bad qualifier %s" qualifier)) | 1195 | (error "Ibuffer: bad qualifier %s" qualifier)) |
| 1086 | (concat " [" (cadr type) ": " (format "%s]" (cdr qualifier))))))) | 1196 | (concat " [" (cadr type) ": " (format "%s]" (cdr qualifier))))))) |
| 1087 | 1197 | ||
| 1088 | |||
| 1089 | (defun ibuffer-list-buffer-modes (&optional include-parents) | 1198 | (defun ibuffer-list-buffer-modes (&optional include-parents) |
| 1090 | "Create a completion table of buffer modes currently in use. | 1199 | "Create a completion table of buffer modes currently in use. |
| 1091 | If INCLUDE-PARENTS is non-nil then include parent modes." | 1200 | If INCLUDE-PARENTS is non-nil then include parent modes." |
| @@ -1103,7 +1212,7 @@ If INCLUDE-PARENTS is non-nil then include parent modes." | |||
| 1103 | 1212 | ||
| 1104 | ;;;###autoload (autoload 'ibuffer-filter-by-mode "ibuf-ext") | 1213 | ;;;###autoload (autoload 'ibuffer-filter-by-mode "ibuf-ext") |
| 1105 | (define-ibuffer-filter mode | 1214 | (define-ibuffer-filter mode |
| 1106 | "Toggle current view to buffers with major mode QUALIFIER." | 1215 | "Limit current view to buffers with major mode QUALIFIER." |
| 1107 | (:description "major mode" | 1216 | (:description "major mode" |
| 1108 | :reader | 1217 | :reader |
| 1109 | (let* ((buf (ibuffer-current-buffer)) | 1218 | (let* ((buf (ibuffer-current-buffer)) |
| @@ -1123,7 +1232,7 @@ If INCLUDE-PARENTS is non-nil then include parent modes." | |||
| 1123 | 1232 | ||
| 1124 | ;;;###autoload (autoload 'ibuffer-filter-by-used-mode "ibuf-ext") | 1233 | ;;;###autoload (autoload 'ibuffer-filter-by-used-mode "ibuf-ext") |
| 1125 | (define-ibuffer-filter used-mode | 1234 | (define-ibuffer-filter used-mode |
| 1126 | "Toggle current view to buffers with major mode QUALIFIER. | 1235 | "Limit current view to buffers with major mode QUALIFIER. |
| 1127 | Called interactively, this function allows selection of modes | 1236 | Called interactively, this function allows selection of modes |
| 1128 | currently used by buffers." | 1237 | currently used by buffers." |
| 1129 | (:description "major mode in use" | 1238 | (:description "major mode in use" |
| @@ -1142,7 +1251,7 @@ currently used by buffers." | |||
| 1142 | 1251 | ||
| 1143 | ;;;###autoload (autoload 'ibuffer-filter-by-derived-mode "ibuf-ext") | 1252 | ;;;###autoload (autoload 'ibuffer-filter-by-derived-mode "ibuf-ext") |
| 1144 | (define-ibuffer-filter derived-mode | 1253 | (define-ibuffer-filter derived-mode |
| 1145 | "Toggle current view to buffers whose major mode inherits from QUALIFIER." | 1254 | "Limit current view to buffers whose major mode inherits from QUALIFIER." |
| 1146 | (:description "derived mode" | 1255 | (:description "derived mode" |
| 1147 | :reader | 1256 | :reader |
| 1148 | (intern | 1257 | (intern |
| @@ -1153,22 +1262,73 @@ currently used by buffers." | |||
| 1153 | 1262 | ||
| 1154 | ;;;###autoload (autoload 'ibuffer-filter-by-name "ibuf-ext") | 1263 | ;;;###autoload (autoload 'ibuffer-filter-by-name "ibuf-ext") |
| 1155 | (define-ibuffer-filter name | 1264 | (define-ibuffer-filter name |
| 1156 | "Toggle current view to buffers with name matching QUALIFIER." | 1265 | "Limit current view to buffers with name matching QUALIFIER." |
| 1157 | (:description "buffer name" | 1266 | (:description "buffer name" |
| 1158 | :reader (read-from-minibuffer "Filter by name (regexp): ")) | 1267 | :reader (read-from-minibuffer "Filter by name (regexp): ")) |
| 1159 | (string-match qualifier (buffer-name buf))) | 1268 | (string-match qualifier (buffer-name buf))) |
| 1160 | 1269 | ||
| 1270 | ;;;###autoload (autoload 'ibuffer-filter-by-starred-name "ibuf-ext") | ||
| 1271 | (define-ibuffer-filter starred-name | ||
| 1272 | "Limit current view to buffers with name beginning and ending | ||
| 1273 | with *, along with an optional suffix of the form digits or | ||
| 1274 | <digits>." | ||
| 1275 | (:description "starred buffer name" | ||
| 1276 | :reader nil) | ||
| 1277 | (string-match "\\`\\*[^*]+\\*\\(?:<[[:digit:]]+>\\)?\\'" (buffer-name buf))) | ||
| 1278 | |||
| 1161 | ;;;###autoload (autoload 'ibuffer-filter-by-filename "ibuf-ext") | 1279 | ;;;###autoload (autoload 'ibuffer-filter-by-filename "ibuf-ext") |
| 1162 | (define-ibuffer-filter filename | 1280 | (define-ibuffer-filter filename |
| 1163 | "Toggle current view to buffers with filename matching QUALIFIER." | 1281 | "Limit current view to buffers with full file name matching QUALIFIER. |
| 1164 | (:description "filename" | 1282 | |
| 1165 | :reader (read-from-minibuffer "Filter by filename (regexp): ")) | 1283 | For example, for a buffer associated with file '/a/b/c.d', this |
| 1284 | matches against '/a/b/c.d'." | ||
| 1285 | (:description "full file name" | ||
| 1286 | :reader (read-from-minibuffer "Filter by full file name (regexp): ")) | ||
| 1166 | (ibuffer-awhen (with-current-buffer buf (ibuffer-buffer-file-name)) | 1287 | (ibuffer-awhen (with-current-buffer buf (ibuffer-buffer-file-name)) |
| 1167 | (string-match qualifier it))) | 1288 | (string-match qualifier it))) |
| 1168 | 1289 | ||
| 1290 | ;;;###autoload (autoload 'ibuffer-filter-by-basename "ibuf-ext") | ||
| 1291 | (define-ibuffer-filter basename | ||
| 1292 | "Limit current view to buffers with file basename matching QUALIFIER. | ||
| 1293 | |||
| 1294 | For example, for a buffer associated with file '/a/b/c.d', this | ||
| 1295 | matches against 'c.d'." | ||
| 1296 | (:description "file basename" | ||
| 1297 | :reader (read-from-minibuffer | ||
| 1298 | "Filter by file name, without directory part (regex): ")) | ||
| 1299 | (ibuffer-awhen (with-current-buffer buf (ibuffer-buffer-file-name)) | ||
| 1300 | (string-match qualifier (file-name-nondirectory it)))) | ||
| 1301 | |||
| 1302 | ;;;###autoload (autoload 'ibuffer-filter-by-file-extension "ibuf-ext") | ||
| 1303 | (define-ibuffer-filter file-extension | ||
| 1304 | "Limit current view to buffers with filename extension matching QUALIFIER. | ||
| 1305 | |||
| 1306 | The separator character (typically `.') is not part of the | ||
| 1307 | pattern. For example, for a buffer associated with file | ||
| 1308 | '/a/b/c.d', this matches against 'd'." | ||
| 1309 | (:description "filename extension" | ||
| 1310 | :reader (read-from-minibuffer | ||
| 1311 | "Filter by filename extension without separator (regex): ")) | ||
| 1312 | (ibuffer-awhen (with-current-buffer buf (ibuffer-buffer-file-name)) | ||
| 1313 | (string-match qualifier (or (file-name-extension it) "")))) | ||
| 1314 | |||
| 1315 | ;;;###autoload (autoload 'ibuffer-filter-by-directory "ibuf-ext") | ||
| 1316 | (define-ibuffer-filter directory | ||
| 1317 | "Limit current view to buffers with directory matching QUALIFIER. | ||
| 1318 | |||
| 1319 | For a buffer associated with file '/a/b/c.d', this matches | ||
| 1320 | against '/a/b'. For a buffer not associated with a file, this | ||
| 1321 | matches against the value of `default-directory' in that buffer." | ||
| 1322 | (:description "directory name" | ||
| 1323 | :reader (read-from-minibuffer "Filter by directory name (regex): ")) | ||
| 1324 | (ibuffer-aif (with-current-buffer buf (ibuffer-buffer-file-name)) | ||
| 1325 | (let ((dirname (file-name-directory it))) | ||
| 1326 | (when dirname (string-match qualifier dirname))) | ||
| 1327 | (when default-directory (string-match qualifier default-directory)))) | ||
| 1328 | |||
| 1169 | ;;;###autoload (autoload 'ibuffer-filter-by-size-gt "ibuf-ext") | 1329 | ;;;###autoload (autoload 'ibuffer-filter-by-size-gt "ibuf-ext") |
| 1170 | (define-ibuffer-filter size-gt | 1330 | (define-ibuffer-filter size-gt |
| 1171 | "Toggle current view to buffers with size greater than QUALIFIER." | 1331 | "Limit current view to buffers with size greater than QUALIFIER." |
| 1172 | (:description "size greater than" | 1332 | (:description "size greater than" |
| 1173 | :reader | 1333 | :reader |
| 1174 | (string-to-number (read-from-minibuffer "Filter by size greater than: "))) | 1334 | (string-to-number (read-from-minibuffer "Filter by size greater than: "))) |
| @@ -1177,16 +1337,30 @@ currently used by buffers." | |||
| 1177 | 1337 | ||
| 1178 | ;;;###autoload (autoload 'ibuffer-filter-by-size-lt "ibuf-ext") | 1338 | ;;;###autoload (autoload 'ibuffer-filter-by-size-lt "ibuf-ext") |
| 1179 | (define-ibuffer-filter size-lt | 1339 | (define-ibuffer-filter size-lt |
| 1180 | "Toggle current view to buffers with size less than QUALIFIER." | 1340 | "Limit current view to buffers with size less than QUALIFIER." |
| 1181 | (:description "size less than" | 1341 | (:description "size less than" |
| 1182 | :reader | 1342 | :reader |
| 1183 | (string-to-number (read-from-minibuffer "Filter by size less than: "))) | 1343 | (string-to-number (read-from-minibuffer "Filter by size less than: "))) |
| 1184 | (< (with-current-buffer buf (buffer-size)) | 1344 | (< (with-current-buffer buf (buffer-size)) |
| 1185 | qualifier)) | 1345 | qualifier)) |
| 1186 | 1346 | ||
| 1347 | ;;;###autoload (autoload 'ibuffer-filter-by-modified "ibuf-ext") | ||
| 1348 | (define-ibuffer-filter modified | ||
| 1349 | "Limit current view to buffers that are marked as modified." | ||
| 1350 | (:description "modified" | ||
| 1351 | :reader nil) | ||
| 1352 | (buffer-modified-p buf)) | ||
| 1353 | |||
| 1354 | ;;;###autoload (autoload 'ibuffer-filter-by-visiting-file "ibuf-ext") | ||
| 1355 | (define-ibuffer-filter visiting-file | ||
| 1356 | "Limit current view to buffers that are visiting a file." | ||
| 1357 | (:description "visiting a file" | ||
| 1358 | :reader nil) | ||
| 1359 | (with-current-buffer buf (buffer-file-name))) | ||
| 1360 | |||
| 1187 | ;;;###autoload (autoload 'ibuffer-filter-by-content "ibuf-ext") | 1361 | ;;;###autoload (autoload 'ibuffer-filter-by-content "ibuf-ext") |
| 1188 | (define-ibuffer-filter content | 1362 | (define-ibuffer-filter content |
| 1189 | "Toggle current view to buffers whose contents match QUALIFIER." | 1363 | "Limit current view to buffers whose contents match QUALIFIER." |
| 1190 | (:description "content" | 1364 | (:description "content" |
| 1191 | :reader (read-from-minibuffer "Filter by content (regexp): ")) | 1365 | :reader (read-from-minibuffer "Filter by content (regexp): ")) |
| 1192 | (with-current-buffer buf | 1366 | (with-current-buffer buf |
| @@ -1196,12 +1370,33 @@ currently used by buffers." | |||
| 1196 | 1370 | ||
| 1197 | ;;;###autoload (autoload 'ibuffer-filter-by-predicate "ibuf-ext") | 1371 | ;;;###autoload (autoload 'ibuffer-filter-by-predicate "ibuf-ext") |
| 1198 | (define-ibuffer-filter predicate | 1372 | (define-ibuffer-filter predicate |
| 1199 | "Toggle current view to buffers for which QUALIFIER returns non-nil." | 1373 | "Limit current view to buffers for which QUALIFIER returns non-nil." |
| 1200 | (:description "predicate" | 1374 | (:description "predicate" |
| 1201 | :reader (read-minibuffer "Filter by predicate (form): ")) | 1375 | :reader (read-minibuffer "Filter by predicate (form): ")) |
| 1202 | (with-current-buffer buf | 1376 | (with-current-buffer buf |
| 1203 | (eval qualifier))) | 1377 | (eval qualifier))) |
| 1204 | 1378 | ||
| 1379 | ;;;###autoload (autoload 'ibuffer-filter-chosen-by-completion "ibuf-ext") | ||
| 1380 | (defun ibuffer-filter-chosen-by-completion () | ||
| 1381 | "Select and apply filter chosen by completion against available filters. | ||
| 1382 | Indicates corresponding key sequences in echo area after filtering. | ||
| 1383 | |||
| 1384 | The completion matches against the filter description text of | ||
| 1385 | each filter in `ibuffer-filtering-alist'." | ||
| 1386 | (interactive) | ||
| 1387 | (let* ((filters (mapcar (lambda (x) (cons (cadr x) (car x))) | ||
| 1388 | ibuffer-filtering-alist)) | ||
| 1389 | (match (completing-read "Filter by: " filters nil t)) | ||
| 1390 | (filter (cdr (assoc match filters))) | ||
| 1391 | (command (intern (concat "ibuffer-filter-by-" (symbol-name filter))))) | ||
| 1392 | (call-interactively command) | ||
| 1393 | (message "%s can be run with key sequences: %s" | ||
| 1394 | command | ||
| 1395 | (mapconcat #'key-description | ||
| 1396 | (where-is-internal command ibuffer-mode-map nil t) | ||
| 1397 | "or ")))) | ||
| 1398 | |||
| 1399 | |||
| 1205 | ;;; Sorting | 1400 | ;;; Sorting |
| 1206 | 1401 | ||
| 1207 | ;;;###autoload | 1402 | ;;;###autoload |
diff --git a/lisp/ibuffer.el b/lisp/ibuffer.el index 94cee329d5a..5a740845bdf 100644 --- a/lisp/ibuffer.el +++ b/lisp/ibuffer.el | |||
| @@ -518,26 +518,37 @@ directory, like `default-directory'." | |||
| 518 | (define-key map (kbd "s f") 'ibuffer-do-sort-by-filename/process) | 518 | (define-key map (kbd "s f") 'ibuffer-do-sort-by-filename/process) |
| 519 | (define-key map (kbd "s m") 'ibuffer-do-sort-by-major-mode) | 519 | (define-key map (kbd "s m") 'ibuffer-do-sort-by-major-mode) |
| 520 | 520 | ||
| 521 | (define-key map (kbd "/ RET") 'ibuffer-filter-by-mode) | ||
| 521 | (define-key map (kbd "/ m") 'ibuffer-filter-by-used-mode) | 522 | (define-key map (kbd "/ m") 'ibuffer-filter-by-used-mode) |
| 522 | (define-key map (kbd "/ M") 'ibuffer-filter-by-derived-mode) | 523 | (define-key map (kbd "/ M") 'ibuffer-filter-by-derived-mode) |
| 523 | (define-key map (kbd "/ n") 'ibuffer-filter-by-name) | 524 | (define-key map (kbd "/ n") 'ibuffer-filter-by-name) |
| 524 | (define-key map (kbd "/ c") 'ibuffer-filter-by-content) | 525 | (define-key map (kbd "/ *") 'ibuffer-filter-by-starred-name) |
| 525 | (define-key map (kbd "/ e") 'ibuffer-filter-by-predicate) | ||
| 526 | (define-key map (kbd "/ f") 'ibuffer-filter-by-filename) | 526 | (define-key map (kbd "/ f") 'ibuffer-filter-by-filename) |
| 527 | (define-key map (kbd "/ >") 'ibuffer-filter-by-size-gt) | 527 | (define-key map (kbd "/ b") 'ibuffer-filter-by-basename) |
| 528 | (define-key map (kbd "/ .") 'ibuffer-filter-by-file-extension) | ||
| 528 | (define-key map (kbd "/ <") 'ibuffer-filter-by-size-lt) | 529 | (define-key map (kbd "/ <") 'ibuffer-filter-by-size-lt) |
| 530 | (define-key map (kbd "/ >") 'ibuffer-filter-by-size-gt) | ||
| 531 | (define-key map (kbd "/ i") 'ibuffer-filter-by-modified) | ||
| 532 | (define-key map (kbd "/ v") 'ibuffer-filter-by-visiting-file) | ||
| 533 | (define-key map (kbd "/ c") 'ibuffer-filter-by-content) | ||
| 534 | (define-key map (kbd "/ e") 'ibuffer-filter-by-predicate) | ||
| 535 | |||
| 529 | (define-key map (kbd "/ r") 'ibuffer-switch-to-saved-filters) | 536 | (define-key map (kbd "/ r") 'ibuffer-switch-to-saved-filters) |
| 530 | (define-key map (kbd "/ a") 'ibuffer-add-saved-filters) | 537 | (define-key map (kbd "/ a") 'ibuffer-add-saved-filters) |
| 531 | (define-key map (kbd "/ x") 'ibuffer-delete-saved-filters) | 538 | (define-key map (kbd "/ x") 'ibuffer-delete-saved-filters) |
| 532 | (define-key map (kbd "/ d") 'ibuffer-decompose-filter) | 539 | (define-key map (kbd "/ d") 'ibuffer-decompose-filter) |
| 533 | (define-key map (kbd "/ s") 'ibuffer-save-filters) | 540 | (define-key map (kbd "/ s") 'ibuffer-save-filters) |
| 534 | (define-key map (kbd "/ p") 'ibuffer-pop-filter) | 541 | (define-key map (kbd "/ p") 'ibuffer-pop-filter) |
| 542 | (define-key map (kbd "/ <up>") 'ibuffer-pop-filter) | ||
| 535 | (define-key map (kbd "/ !") 'ibuffer-negate-filter) | 543 | (define-key map (kbd "/ !") 'ibuffer-negate-filter) |
| 536 | (define-key map (kbd "/ t") 'ibuffer-exchange-filters) | 544 | (define-key map (kbd "/ t") 'ibuffer-exchange-filters) |
| 537 | (define-key map (kbd "/ TAB") 'ibuffer-exchange-filters) | 545 | (define-key map (kbd "/ TAB") 'ibuffer-exchange-filters) |
| 538 | (define-key map (kbd "/ o") 'ibuffer-or-filter) | 546 | (define-key map (kbd "/ o") 'ibuffer-or-filter) |
| 547 | (define-key map (kbd "/ |") 'ibuffer-or-filter) | ||
| 548 | (define-key map (kbd "/ &") 'ibuffer-and-filter) | ||
| 539 | (define-key map (kbd "/ g") 'ibuffer-filters-to-filter-group) | 549 | (define-key map (kbd "/ g") 'ibuffer-filters-to-filter-group) |
| 540 | (define-key map (kbd "/ P") 'ibuffer-pop-filter-group) | 550 | (define-key map (kbd "/ P") 'ibuffer-pop-filter-group) |
| 551 | (define-key map (kbd "/ S-<up>") 'ibuffer-pop-filter-group) | ||
| 541 | (define-key map (kbd "/ D") 'ibuffer-decompose-filter-group) | 552 | (define-key map (kbd "/ D") 'ibuffer-decompose-filter-group) |
| 542 | (define-key map (kbd "/ /") 'ibuffer-filter-disable) | 553 | (define-key map (kbd "/ /") 'ibuffer-filter-disable) |
| 543 | 554 | ||
| @@ -657,13 +668,43 @@ directory, like `default-directory'." | |||
| 657 | ibuffer-filter-by-derived-mode)) | 668 | ibuffer-filter-by-derived-mode)) |
| 658 | (define-key-after map [menu-bar view filter filter-by-name] | 669 | (define-key-after map [menu-bar view filter filter-by-name] |
| 659 | '(menu-item "Add filter by buffer name..." ibuffer-filter-by-name)) | 670 | '(menu-item "Add filter by buffer name..." ibuffer-filter-by-name)) |
| 671 | (define-key-after map [menu-bar view filter filter-by-starred-name] | ||
| 672 | '(menu-item "Add filter by starred buffer name..." | ||
| 673 | ibuffer-filter-by-starred-name | ||
| 674 | :help "List buffers whose names begin with a star")) | ||
| 660 | (define-key-after map [menu-bar view filter filter-by-filename] | 675 | (define-key-after map [menu-bar view filter filter-by-filename] |
| 661 | '(menu-item "Add filter by filename..." ibuffer-filter-by-filename)) | 676 | '(menu-item "Add filter by full filename..." ibuffer-filter-by-filename |
| 677 | :help | ||
| 678 | (concat "For a buffer associated with file '/a/b/c.d', " | ||
| 679 | "list buffer if a given pattern matches '/a/b/c.d'"))) | ||
| 680 | (define-key-after map [menu-bar view filter filter-by-basename] | ||
| 681 | '(menu-item "Add filter by file basename..." | ||
| 682 | ibuffer-filter-by-basename | ||
| 683 | :help (concat "For a buffer associated with file '/a/b/c.d', " | ||
| 684 | "list buffer if a given pattern matches 'c.d'"))) | ||
| 685 | (define-key-after map [menu-bar view filter filter-by-file-extension] | ||
| 686 | '(menu-item "Add filter by file name extension..." | ||
| 687 | ibuffer-filter-by-file-extension | ||
| 688 | :help (concat "For a buffer associated with file '/a/b/c.d', " | ||
| 689 | "list buffer if a given pattern matches 'd'"))) | ||
| 690 | (define-key-after map [menu-bar view filter filter-by-directory] | ||
| 691 | '(menu-item "Add filter by filename's directory..." | ||
| 692 | ibuffer-filter-by-directory | ||
| 693 | :help | ||
| 694 | (concat "For a buffer associated with file '/a/b/c.d', " | ||
| 695 | "list buffer if a given pattern matches '/a/b'"))) | ||
| 662 | (define-key-after map [menu-bar view filter filter-by-size-lt] | 696 | (define-key-after map [menu-bar view filter filter-by-size-lt] |
| 663 | '(menu-item "Add filter by size less than..." ibuffer-filter-by-size-lt)) | 697 | '(menu-item "Add filter by size less than..." ibuffer-filter-by-size-lt)) |
| 664 | (define-key-after map [menu-bar view filter filter-by-size-gt] | 698 | (define-key-after map [menu-bar view filter filter-by-size-gt] |
| 665 | '(menu-item "Add filter by size greater than..." | 699 | '(menu-item "Add filter by size greater than..." |
| 666 | ibuffer-filter-by-size-gt)) | 700 | ibuffer-filter-by-size-gt)) |
| 701 | (define-key-after map [menu-bar view filter filter-by-modified] | ||
| 702 | '(menu-item "Add filter by modified buffer" ibuffer-filter-by-modified | ||
| 703 | :help "List buffers that are marked as modified")) | ||
| 704 | (define-key-after map [menu-bar view filter filter-by-visiting-file] | ||
| 705 | '(menu-item "Add filter by buffer visiting a file" | ||
| 706 | ibuffer-filter-by-visiting-file | ||
| 707 | :help "List buffers that are visiting files")) | ||
| 667 | (define-key-after map [menu-bar view filter filter-by-content] | 708 | (define-key-after map [menu-bar view filter filter-by-content] |
| 668 | '(menu-item "Add filter by content (regexp)..." | 709 | '(menu-item "Add filter by content (regexp)..." |
| 669 | ibuffer-filter-by-content)) | 710 | ibuffer-filter-by-content)) |
| @@ -673,6 +714,12 @@ directory, like `default-directory'." | |||
| 673 | (define-key-after map [menu-bar view filter pop-filter] | 714 | (define-key-after map [menu-bar view filter pop-filter] |
| 674 | '(menu-item "Remove top filter" ibuffer-pop-filter | 715 | '(menu-item "Remove top filter" ibuffer-pop-filter |
| 675 | :enable (and (featurep 'ibuf-ext) ibuffer-filtering-qualifiers))) | 716 | :enable (and (featurep 'ibuf-ext) ibuffer-filtering-qualifiers))) |
| 717 | (define-key-after map [menu-bar view filter and-filter] | ||
| 718 | '(menu-item "AND top two filters" ibuffer-and-filter | ||
| 719 | :enable (and (featurep 'ibuf-ext) ibuffer-filtering-qualifiers | ||
| 720 | (cdr ibuffer-filtering-qualifiers)) | ||
| 721 | :help | ||
| 722 | "Create a new filter which is the logical AND of the top two filters")) | ||
| 676 | (define-key-after map [menu-bar view filter or-filter] | 723 | (define-key-after map [menu-bar view filter or-filter] |
| 677 | '(menu-item "OR top two filters" ibuffer-or-filter | 724 | '(menu-item "OR top two filters" ibuffer-or-filter |
| 678 | :enable (and (featurep 'ibuf-ext) ibuffer-filtering-qualifiers | 725 | :enable (and (featurep 'ibuf-ext) ibuffer-filtering-qualifiers |
diff --git a/test/lisp/ibuffer-tests.el b/test/lisp/ibuffer-tests.el index 92ed101e6bf..40760abd96a 100644 --- a/test/lisp/ibuffer-tests.el +++ b/test/lisp/ibuffer-tests.el | |||
| @@ -24,7 +24,8 @@ | |||
| 24 | (require 'ibuf-macs)) | 24 | (require 'ibuf-macs)) |
| 25 | 25 | ||
| 26 | (ert-deftest ibuffer-autoload () | 26 | (ert-deftest ibuffer-autoload () |
| 27 | "Tests to see whether reftex-auc has been autoloaded" | 27 | "Tests to see whether ibuffer has been autoloaded" |
| 28 | (skip-unless (not (featurep 'ibuf-ext))) | ||
| 28 | (should | 29 | (should |
| 29 | (fboundp 'ibuffer-mark-unsaved-buffers)) | 30 | (fboundp 'ibuffer-mark-unsaved-buffers)) |
| 30 | (should | 31 | (should |
| @@ -138,5 +139,669 @@ | |||
| 138 | (should-not ibuffer-filtering-qualifiers)) | 139 | (should-not ibuffer-filtering-qualifiers)) |
| 139 | (setq ibuffer-filtering-qualifiers filters)))) | 140 | (setq ibuffer-filtering-qualifiers filters)))) |
| 140 | 141 | ||
| 142 | ;; Test Filter Inclusion | ||
| 143 | (let* (test-buffer-list ; accumulated buffers to clean up | ||
| 144 | ;; Utility functions without polluting the environment | ||
| 145 | (set-buffer-mode | ||
| 146 | (lambda (buffer mode) | ||
| 147 | "Set BUFFER's major mode to MODE, a mode function, or fundamental." | ||
| 148 | (with-current-buffer buffer | ||
| 149 | (funcall (or mode #'fundamental-mode))))) | ||
| 150 | (set-buffer-contents | ||
| 151 | (lambda (buffer size include-content) | ||
| 152 | "Add exactly SIZE bytes to BUFFER, including INCLUDE-CONTENT." | ||
| 153 | (when (or size include-content) | ||
| 154 | (let* ((unit "\n") | ||
| 155 | (chunk "ccccccccccccccccccccccccccccccc\n") | ||
| 156 | (chunk-size (length chunk)) | ||
| 157 | (size (if (and size include-content (stringp include-content)) | ||
| 158 | (- size (length include-content)) | ||
| 159 | size))) | ||
| 160 | (unless (or (null size) (> size 0)) | ||
| 161 | (error "size argument must be nil or positive")) | ||
| 162 | (with-current-buffer buffer | ||
| 163 | (when include-content | ||
| 164 | (insert include-content)) | ||
| 165 | (when size | ||
| 166 | (dotimes (_ (floor size chunk-size)) | ||
| 167 | (insert chunk)) | ||
| 168 | (dotimes (_ (mod size chunk-size)) | ||
| 169 | (insert unit))) | ||
| 170 | ;; prevent query on cleanup | ||
| 171 | (set-buffer-modified-p nil)))))) | ||
| 172 | (create-file-buffer | ||
| 173 | (lambda (prefix &rest args-plist) | ||
| 174 | "Create a file and buffer with designated properties. | ||
| 175 | PREFIX is a string giving the beginning of the name, and ARGS-PLIST | ||
| 176 | is a series of keyword-value pairs, with allowed keywords | ||
| 177 | :suffix STRING, :size NUMBER, :mode MODE-FUNC, :include-content STRING. | ||
| 178 | Returns the created buffer." | ||
| 179 | (let* ((suffix (plist-get args-plist :suffix)) | ||
| 180 | (size (plist-get args-plist :size)) | ||
| 181 | (include (plist-get args-plist :include-content)) | ||
| 182 | (mode (plist-get args-plist :mode)) | ||
| 183 | (file (make-temp-file prefix nil suffix)) | ||
| 184 | (buf (find-file-noselect file t))) | ||
| 185 | (push buf test-buffer-list) ; record for cleanup | ||
| 186 | (funcall set-buffer-mode buf mode) | ||
| 187 | (funcall set-buffer-contents buf size include) | ||
| 188 | buf))) | ||
| 189 | (create-non-file-buffer | ||
| 190 | (lambda (prefix &rest args-plist) | ||
| 191 | "Create a non-file and buffer with designated properties. | ||
| 192 | PREFIX is a string giving the beginning of the name, and ARGS-PLIST | ||
| 193 | is a series of keyword-value pairs, with allowed keywords | ||
| 194 | :size NUMBER, :mode MODE-FUNC, :include-content STRING. | ||
| 195 | Returns the created buffer." | ||
| 196 | (let* ((size (plist-get args-plist :size)) | ||
| 197 | (include (plist-get args-plist :include-content)) | ||
| 198 | (mode (plist-get args-plist :mode)) | ||
| 199 | (buf (generate-new-buffer prefix))) | ||
| 200 | (push buf test-buffer-list) ; record for cleanup | ||
| 201 | (funcall set-buffer-mode buf mode) | ||
| 202 | (funcall set-buffer-contents buf size include) | ||
| 203 | buf))) | ||
| 204 | (clean-up | ||
| 205 | (lambda () | ||
| 206 | "Restore all emacs state modified during the tests" | ||
| 207 | (while test-buffer-list ; created temporary buffers | ||
| 208 | (let ((buf (pop test-buffer-list))) | ||
| 209 | (with-current-buffer buf (bury-buffer)) ; ensure not selected | ||
| 210 | (kill-buffer buf)))))) | ||
| 211 | ;; Tests | ||
| 212 | (ert-deftest ibuffer-filter-inclusion-1 () | ||
| 213 | "Tests inclusion using basic filter combinators with a single buffer." | ||
| 214 | (skip-unless (featurep 'ibuf-ext)) | ||
| 215 | (unwind-protect | ||
| 216 | (let ((buf | ||
| 217 | (funcall create-file-buffer "ibuf-test-1" :size 100 | ||
| 218 | :include-content "One ring to rule them all\n"))) | ||
| 219 | (should (ibuffer-included-in-filters-p buf '((size-gt . 99)))) | ||
| 220 | (should (ibuffer-included-in-filters-p buf '((size-lt . 101)))) | ||
| 221 | (should (ibuffer-included-in-filters-p | ||
| 222 | buf '((mode . fundamental-mode)))) | ||
| 223 | (should (ibuffer-included-in-filters-p | ||
| 224 | buf '((content . "ring to rule them all")))) | ||
| 225 | (should (ibuffer-included-in-filters-p | ||
| 226 | buf '((and (content . "ring to rule them all"))))) | ||
| 227 | (should (ibuffer-included-in-filters-p | ||
| 228 | buf '((and (and (content . "ring to rule them all")))))) | ||
| 229 | (should (ibuffer-included-in-filters-p | ||
| 230 | buf '((and (and (and (content . "ring to rule them all"))))))) | ||
| 231 | (should (ibuffer-included-in-filters-p | ||
| 232 | buf '((or (content . "ring to rule them all"))))) | ||
| 233 | (should (ibuffer-included-in-filters-p | ||
| 234 | buf '((not (not (content . "ring to rule them all")))))) | ||
| 235 | (should (ibuffer-included-in-filters-p | ||
| 236 | buf '((and (size-gt . 99) | ||
| 237 | (content . "ring to rule them all") | ||
| 238 | (mode . fundamental-mode) | ||
| 239 | (basename . "\\`ibuf-test-1"))))) | ||
| 240 | (should (ibuffer-included-in-filters-p | ||
| 241 | buf '((not (or (not (size-gt . 99)) | ||
| 242 | (not (content . "ring to rule them all")) | ||
| 243 | (not (mode . fundamental-mode)) | ||
| 244 | (not (basename . "\\`ibuf-test-1"))))))) | ||
| 245 | (should (ibuffer-included-in-filters-p | ||
| 246 | buf '((and (or (size-gt . 99) (size-lt . 10)) | ||
| 247 | (and (content . "ring.*all") | ||
| 248 | (content . "rule") | ||
| 249 | (content . "them all") | ||
| 250 | (content . "One")) | ||
| 251 | (not (mode . text-mode)) | ||
| 252 | (basename . "\\`ibuf-test-1")))))) | ||
| 253 | (funcall clean-up))) | ||
| 254 | |||
| 255 | (ert-deftest ibuffer-filter-inclusion-2 () | ||
| 256 | "Tests inclusion of basic filters in combination on a single buffer." | ||
| 257 | (skip-unless (featurep 'ibuf-ext)) | ||
| 258 | (unwind-protect | ||
| 259 | (let ((buf | ||
| 260 | (funcall create-file-buffer "ibuf-test-2" :size 200 | ||
| 261 | :mode #'text-mode | ||
| 262 | :include-content "and in the darkness find them\n"))) | ||
| 263 | (should (ibuffer-included-in-filters-p buf '((size-gt . 199)))) | ||
| 264 | (should (ibuffer-included-in-filters-p buf '((size-lt . 201)))) | ||
| 265 | (should (ibuffer-included-in-filters-p buf '((not size-gt . 200)))) | ||
| 266 | (should (ibuffer-included-in-filters-p buf '((not (size-gt . 200))))) | ||
| 267 | (should (ibuffer-included-in-filters-p | ||
| 268 | buf '((and (size-gt . 199) (size-lt . 201))))) | ||
| 269 | (should (ibuffer-included-in-filters-p | ||
| 270 | buf '((or (size-gt . 199) (size-gt . 201))))) | ||
| 271 | (should (ibuffer-included-in-filters-p | ||
| 272 | buf '((or (size-gt . 201) (size-gt . 199))))) | ||
| 273 | (should (ibuffer-included-in-filters-p | ||
| 274 | buf '((size-gt . 199) (mode . text-mode) | ||
| 275 | (content . "darkness find them")))) | ||
| 276 | (should (ibuffer-included-in-filters-p | ||
| 277 | buf '((and (size-gt . 199) (mode . text-mode) | ||
| 278 | (content . "darkness find them"))))) | ||
| 279 | (should (ibuffer-included-in-filters-p | ||
| 280 | buf '((not (or (not (size-gt . 199)) (not (mode . text-mode)) | ||
| 281 | (not (content . "darkness find them"))))))) | ||
| 282 | (should (ibuffer-included-in-filters-p | ||
| 283 | buf '((or (size-gt . 200) (content . "darkness find them") | ||
| 284 | (derived-mode . emacs-lisp-mode))))) | ||
| 285 | (should-not (ibuffer-included-in-filters-p | ||
| 286 | buf '((or (size-gt . 200) (content . "rule them all") | ||
| 287 | (derived-mode . emacs-lisp-mode)))))) | ||
| 288 | (funcall clean-up))) | ||
| 289 | |||
| 290 | (ert-deftest ibuffer-filter-inclusion-3 () | ||
| 291 | "Tests inclusion with filename filters on specified buffers." | ||
| 292 | (skip-unless (featurep 'ibuf-ext)) | ||
| 293 | (unwind-protect | ||
| 294 | (let* ((bufA | ||
| 295 | (funcall create-file-buffer "ibuf-test-3.a" :size 50 | ||
| 296 | :mode #'text-mode | ||
| 297 | :include-content "...but a multitude of drops?\n")) | ||
| 298 | (bufB | ||
| 299 | (funcall create-non-file-buffer "ibuf-test-3.b" :size 50 | ||
| 300 | :mode #'text-mode | ||
| 301 | :include-content "...but a multitude of drops?\n")) | ||
| 302 | (dirA (with-current-buffer bufA default-directory)) | ||
| 303 | (dirB (with-current-buffer bufB default-directory))) | ||
| 304 | (should (ibuffer-included-in-filters-p | ||
| 305 | bufA '((basename . "ibuf-test-3")))) | ||
| 306 | (should (ibuffer-included-in-filters-p | ||
| 307 | bufA '((basename . "test-3\\.a")))) | ||
| 308 | (should (ibuffer-included-in-filters-p | ||
| 309 | bufA '((file-extension . "a")))) | ||
| 310 | (should (ibuffer-included-in-filters-p | ||
| 311 | bufA (list (cons 'directory dirA)))) | ||
| 312 | (should-not (ibuffer-included-in-filters-p | ||
| 313 | bufB '((basename . "ibuf-test-3")))) | ||
| 314 | (should-not (ibuffer-included-in-filters-p | ||
| 315 | bufB '((file-extension . "b")))) | ||
| 316 | (should (ibuffer-included-in-filters-p | ||
| 317 | bufB (list (cons 'directory dirB)))) | ||
| 318 | (should (ibuffer-included-in-filters-p | ||
| 319 | bufA '((name . "ibuf-test-3")))) | ||
| 320 | (should (ibuffer-included-in-filters-p | ||
| 321 | bufB '((name . "ibuf-test-3"))))) | ||
| 322 | (funcall clean-up))) | ||
| 323 | |||
| 324 | (ert-deftest ibuffer-filter-inclusion-4 () | ||
| 325 | "Tests inclusion with various filters on a single buffer." | ||
| 326 | (skip-unless (featurep 'ibuf-ext)) | ||
| 327 | (unwind-protect | ||
| 328 | (let ((buf | ||
| 329 | (funcall create-file-buffer "ibuf-test-4" | ||
| 330 | :mode #'emacs-lisp-mode :suffix ".el" | ||
| 331 | :include-content "(message \"--%s--\" 'emacs-rocks)\n"))) | ||
| 332 | (should (ibuffer-included-in-filters-p | ||
| 333 | buf '((file-extension . "el")))) | ||
| 334 | (should (ibuffer-included-in-filters-p | ||
| 335 | buf '((derived-mode . prog-mode)))) | ||
| 336 | (should (ibuffer-included-in-filters-p | ||
| 337 | buf '((used-mode . emacs-lisp-mode)))) | ||
| 338 | (should (ibuffer-included-in-filters-p | ||
| 339 | buf '((mode . emacs-lisp-mode)))) | ||
| 340 | (with-current-buffer buf (set-buffer-modified-p t)) | ||
| 341 | (should (ibuffer-included-in-filters-p buf '((modified)))) | ||
| 342 | (with-current-buffer buf (set-buffer-modified-p nil)) | ||
| 343 | (should (ibuffer-included-in-filters-p buf '((not modified)))) | ||
| 344 | (should (ibuffer-included-in-filters-p | ||
| 345 | buf '((and (file-extension . "el") | ||
| 346 | (derived-mode . prog-mode) | ||
| 347 | (not modified))))) | ||
| 348 | (should (ibuffer-included-in-filters-p | ||
| 349 | buf '((or (file-extension . "tex") | ||
| 350 | (derived-mode . prog-mode) | ||
| 351 | (modified))))) | ||
| 352 | (should (ibuffer-included-in-filters-p | ||
| 353 | buf '((file-extension . "el") | ||
| 354 | (derived-mode . prog-mode) | ||
| 355 | (not modified))))) | ||
| 356 | (funcall clean-up))) | ||
| 357 | |||
| 358 | (ert-deftest ibuffer-filter-inclusion-5 () | ||
| 359 | "Tests inclusion with various filters on a single buffer." | ||
| 360 | (skip-unless (featurep 'ibuf-ext)) | ||
| 361 | (unwind-protect | ||
| 362 | (let ((buf | ||
| 363 | (funcall create-non-file-buffer "ibuf-test-5.el" | ||
| 364 | :mode #'emacs-lisp-mode | ||
| 365 | :include-content | ||
| 366 | "(message \"--%s--\" \"It really does!\")\n"))) | ||
| 367 | (should-not (ibuffer-included-in-filters-p | ||
| 368 | buf '((file-extension . "el")))) | ||
| 369 | (should (ibuffer-included-in-filters-p | ||
| 370 | buf '((size-gt . 18)))) | ||
| 371 | (should (ibuffer-included-in-filters-p | ||
| 372 | buf '((predicate . (lambda () | ||
| 373 | (> (- (point-max) (point-min)) 18)))))) | ||
| 374 | (should (ibuffer-included-in-filters-p | ||
| 375 | buf '((and (mode . emacs-lisp-mode) | ||
| 376 | (or (starred-name) | ||
| 377 | (size-gt . 18)) | ||
| 378 | (and (not (size-gt . 100)) | ||
| 379 | (content . "[Ii]t *really does!") | ||
| 380 | (or (name . "test-5") | ||
| 381 | (not (filename . "test-5"))))))))) | ||
| 382 | (funcall clean-up))) | ||
| 383 | |||
| 384 | (ert-deftest ibuffer-filter-inclusion-6 () | ||
| 385 | "Tests inclusion using saved filters and DeMorgan's laws." | ||
| 386 | (skip-unless (featurep 'ibuf-ext)) | ||
| 387 | (unwind-protect | ||
| 388 | (let ((buf | ||
| 389 | (funcall create-non-file-buffer "*ibuf-test-6*" :size 65 | ||
| 390 | :mode #'text-mode)) | ||
| 391 | (buf2 | ||
| 392 | (funcall create-file-buffer "ibuf-test-6a" :suffix ".html" | ||
| 393 | :mode #'html-mode | ||
| 394 | :include-content | ||
| 395 | "<HTML><BODY><H1>Hello, World!</H1></BODY></HTML>"))) | ||
| 396 | (should (ibuffer-included-in-filters-p buf '((starred-name)))) | ||
| 397 | (should-not (ibuffer-included-in-filters-p | ||
| 398 | buf '((saved . "text document")))) | ||
| 399 | (should (ibuffer-included-in-filters-p buf2 '((saved . "web")))) | ||
| 400 | (should (ibuffer-included-in-filters-p | ||
| 401 | buf2 '((not (and (not (derived-mode . sgml-mode)) | ||
| 402 | (not (derived-mode . css-mode)) | ||
| 403 | (not (mode . javascript-mode)) | ||
| 404 | (not (mode . js2-mode)) | ||
| 405 | (not (mode . scss-mode)) | ||
| 406 | (not (derived-mode . haml-mode)) | ||
| 407 | (not (mode . sass-mode))))))) | ||
| 408 | (should (ibuffer-included-in-filters-p | ||
| 409 | buf '((and (starred-name) | ||
| 410 | (or (size-gt . 50) (filename . "foo")))))) | ||
| 411 | (should (ibuffer-included-in-filters-p | ||
| 412 | buf '((not (or (not starred-name) | ||
| 413 | (and (size-lt . 51) | ||
| 414 | (not (filename . "foo"))))))))) | ||
| 415 | (funcall clean-up))) | ||
| 416 | |||
| 417 | (ert-deftest ibuffer-filter-inclusion-7 () | ||
| 418 | "Tests inclusion with various filters on a single buffer." | ||
| 419 | (skip-unless (featurep 'ibuf-ext)) | ||
| 420 | (unwind-protect | ||
| 421 | (let ((buf | ||
| 422 | (funcall create-non-file-buffer "ibuf-test-7" | ||
| 423 | :mode #'artist-mode))) | ||
| 424 | (should (ibuffer-included-in-filters-p | ||
| 425 | buf '((not (starred-name))))) | ||
| 426 | (should (ibuffer-included-in-filters-p | ||
| 427 | buf '((not starred-name)))) | ||
| 428 | (should (ibuffer-included-in-filters-p | ||
| 429 | buf '((not (not (not starred-name)))))) | ||
| 430 | (should (ibuffer-included-in-filters-p | ||
| 431 | buf '((not (modified))))) | ||
| 432 | (should (ibuffer-included-in-filters-p | ||
| 433 | buf '((not modified)))) | ||
| 434 | (should (ibuffer-included-in-filters-p | ||
| 435 | buf '((not (not (not modified))))))) | ||
| 436 | (funcall clean-up))) | ||
| 437 | |||
| 438 | (ert-deftest ibuffer-filter-inclusion-8 () | ||
| 439 | "Tests inclusion with various filters." | ||
| 440 | (skip-unless (featurep 'ibuf-ext)) | ||
| 441 | (unwind-protect | ||
| 442 | (let ((bufA | ||
| 443 | (funcall create-non-file-buffer "ibuf-test-8a" | ||
| 444 | :mode #'artist-mode)) | ||
| 445 | (bufB (funcall create-non-file-buffer "*ibuf-test-8b*" :size 32)) | ||
| 446 | (bufC (funcall create-file-buffer "ibuf-test8c" :suffix "*" | ||
| 447 | :size 64)) | ||
| 448 | (bufD (funcall create-file-buffer "*ibuf-test8d" :size 128)) | ||
| 449 | (bufE (funcall create-file-buffer "*ibuf-test8e" :suffix "*<2>" | ||
| 450 | :size 16)) | ||
| 451 | (bufF (and (funcall create-non-file-buffer "*ibuf-test8f*") | ||
| 452 | (funcall create-non-file-buffer "*ibuf-test8f*" | ||
| 453 | :size 8)))) | ||
| 454 | (with-current-buffer bufA (set-buffer-modified-p t)) | ||
| 455 | (should (ibuffer-included-in-filters-p | ||
| 456 | bufA '((and (not starred-name) | ||
| 457 | (modified) | ||
| 458 | (name . "test-8") | ||
| 459 | (not (size-gt . 100)) | ||
| 460 | (mode . picture-mode))))) | ||
| 461 | (with-current-buffer bufA (set-buffer-modified-p nil)) | ||
| 462 | (should-not (ibuffer-included-in-filters-p | ||
| 463 | bufA '((or (starred-name) (visiting-file) (modified))))) | ||
| 464 | (should (ibuffer-included-in-filters-p | ||
| 465 | bufB '((and (starred-name) | ||
| 466 | (name . "test.*8b") | ||
| 467 | (size-gt . 31) | ||
| 468 | (not visiting-file))))) | ||
| 469 | (should (ibuffer-included-in-filters-p | ||
| 470 | bufC '((and (not (starred-name)) | ||
| 471 | (visiting-file) | ||
| 472 | (name . "8c[^*]*\\*") | ||
| 473 | (size-lt . 65))))) | ||
| 474 | (should (ibuffer-included-in-filters-p | ||
| 475 | bufD '((and (not (starred-name)) | ||
| 476 | (visiting-file) | ||
| 477 | (name . "\\`\\*.*test8d") | ||
| 478 | (size-lt . 129) | ||
| 479 | (size-gt . 127))))) | ||
| 480 | (should (ibuffer-included-in-filters-p | ||
| 481 | bufE '((and (starred-name) | ||
| 482 | (visiting-file) | ||
| 483 | (name . "8e.*?\\*<[[:digit:]]+>") | ||
| 484 | (size-gt . 10))))) | ||
| 485 | (should (ibuffer-included-in-filters-p | ||
| 486 | bufF '((and (starred-name) | ||
| 487 | (not (visiting-file)) | ||
| 488 | (name . "8f\\*<[[:digit:]]>") | ||
| 489 | (size-lt . 10)))))) | ||
| 490 | (funcall clean-up)))) | ||
| 491 | |||
| 492 | ;; Test Filter Combination and Decomposition | ||
| 493 | (let* (ibuffer-to-kill ; if non-nil, kill this buffer at cleanup | ||
| 494 | (ibuffer-already 'check) ; existing ibuffer buffer to use but not kill | ||
| 495 | ;; Utility functions without polluting the environment | ||
| 496 | (get-test-ibuffer | ||
| 497 | (lambda () | ||
| 498 | "Returns a test ibuffer-mode buffer, creating one if necessary. | ||
| 499 | If a new buffer is created, it is named \"*Test-Ibuffer*\" and is | ||
| 500 | saved to `ibuffer-to-kill' for later cleanup." | ||
| 501 | (when (eq ibuffer-already 'check) | ||
| 502 | (setq ibuffer-already | ||
| 503 | (catch 'found-buf | ||
| 504 | (dolist (buf (buffer-list) nil) | ||
| 505 | (when (with-current-buffer buf | ||
| 506 | (derived-mode-p 'ibuffer-mode)) | ||
| 507 | (throw 'found-buf buf)))))) | ||
| 508 | (or ibuffer-already | ||
| 509 | ibuffer-to-kill | ||
| 510 | (let ((test-ibuf-name "*Test-Ibuffer*")) | ||
| 511 | (ibuffer nil test-ibuf-name nil t) | ||
| 512 | (setq ibuffer-to-kill (get-buffer test-ibuf-name)))))) | ||
| 513 | (clean-up | ||
| 514 | (lambda () | ||
| 515 | "Restore all emacs state modified during the tests" | ||
| 516 | (when ibuffer-to-kill ; created ibuffer | ||
| 517 | (with-current-buffer ibuffer-to-kill | ||
| 518 | (set-buffer-modified-p nil) | ||
| 519 | (bury-buffer)) | ||
| 520 | (kill-buffer ibuffer-to-kill) | ||
| 521 | (setq ibuffer-to-kill nil)) | ||
| 522 | (when (and ibuffer-already (not (eq ibuffer-already 'check))) | ||
| 523 | ;; restore existing ibuffer state | ||
| 524 | (ibuffer-update nil t))))) | ||
| 525 | ;; Tests | ||
| 526 | (ert-deftest ibuffer-decompose-filter () | ||
| 527 | "Tests `ibuffer-decompose-filter' for and, or, not, and saved." | ||
| 528 | (skip-unless (featurep 'ibuf-ext)) | ||
| 529 | (unwind-protect | ||
| 530 | (let ((ibuf (funcall get-test-ibuffer))) | ||
| 531 | (with-current-buffer ibuf | ||
| 532 | (let ((ibuffer-filtering-qualifiers nil) | ||
| 533 | (ibuffer-filter-groups nil) | ||
| 534 | (filters '((size-gt . 100) (not (starred-name)) | ||
| 535 | (name . "foo")))) | ||
| 536 | (progn | ||
| 537 | (push (cons 'or filters) ibuffer-filtering-qualifiers) | ||
| 538 | (ibuffer-decompose-filter) | ||
| 539 | (should (equal filters ibuffer-filtering-qualifiers)) | ||
| 540 | (setq ibuffer-filtering-qualifiers nil)) | ||
| 541 | (progn | ||
| 542 | (push (cons 'and filters) ibuffer-filtering-qualifiers) | ||
| 543 | (ibuffer-decompose-filter) | ||
| 544 | (should (equal filters ibuffer-filtering-qualifiers)) | ||
| 545 | (setq ibuffer-filtering-qualifiers nil)) | ||
| 546 | (progn | ||
| 547 | (push (list 'not (car filters)) ibuffer-filtering-qualifiers) | ||
| 548 | (ibuffer-decompose-filter) | ||
| 549 | (should (equal (list (car filters)) | ||
| 550 | ibuffer-filtering-qualifiers)) | ||
| 551 | (setq ibuffer-filtering-qualifiers nil)) | ||
| 552 | (progn | ||
| 553 | (push (cons 'not (car filters)) ibuffer-filtering-qualifiers) | ||
| 554 | (ibuffer-decompose-filter) | ||
| 555 | (should (equal (list (car filters)) | ||
| 556 | ibuffer-filtering-qualifiers)) | ||
| 557 | (setq ibuffer-filtering-qualifiers nil)) | ||
| 558 | (let ((gnus (assoc "gnus" ibuffer-saved-filters))) | ||
| 559 | (push '(saved . "gnus") ibuffer-filtering-qualifiers) | ||
| 560 | (ibuffer-decompose-filter) | ||
| 561 | (should (equal (cdr gnus) ibuffer-filtering-qualifiers)) | ||
| 562 | (ibuffer-decompose-filter) | ||
| 563 | (should (equal (cdr (cadr gnus)) ibuffer-filtering-qualifiers)) | ||
| 564 | (setq ibuffer-filtering-qualifiers nil)) | ||
| 565 | (when (not (assoc "__unknown__" ibuffer-saved-filters)) | ||
| 566 | (push '(saved . "__uknown__") ibuffer-filtering-qualifiers) | ||
| 567 | (should-error (ibuffer-decompose-filter) :type 'error) | ||
| 568 | (setq ibuffer-filtering-qualifiers nil)) | ||
| 569 | (progn | ||
| 570 | (push (car filters) ibuffer-filtering-qualifiers) | ||
| 571 | (should-error (ibuffer-decompose-filter) :type 'error) | ||
| 572 | (setq ibuffer-filtering-qualifiers nil))))) | ||
| 573 | (funcall clean-up))) | ||
| 574 | |||
| 575 | (ert-deftest ibuffer-and-filter () | ||
| 576 | "Tests `ibuffer-and-filter' in an Ibuffer buffer." | ||
| 577 | (skip-unless (featurep 'ibuf-ext)) | ||
| 578 | (unwind-protect | ||
| 579 | (let ((ibuf (funcall get-test-ibuffer))) | ||
| 580 | (with-current-buffer ibuf | ||
| 581 | (let ((ibuffer-filtering-qualifiers nil) | ||
| 582 | (ibuffer-filter-groups nil) | ||
| 583 | (filters [(size-gt . 100) (not (starred-name)) | ||
| 584 | (filename . "A") (mode . text-mode)])) | ||
| 585 | (should-error (ibuffer-and-filter) :type 'error) | ||
| 586 | (progn | ||
| 587 | (push (aref filters 1) ibuffer-filtering-qualifiers) | ||
| 588 | (should-error (ibuffer-and-filter) :type 'error)) | ||
| 589 | (should (progn | ||
| 590 | (push (aref filters 0) ibuffer-filtering-qualifiers) | ||
| 591 | (ibuffer-and-filter) | ||
| 592 | (and (equal (list 'and (aref filters 0) (aref filters 1)) | ||
| 593 | (car ibuffer-filtering-qualifiers)) | ||
| 594 | (null (cdr ibuffer-filtering-qualifiers))))) | ||
| 595 | (should (progn | ||
| 596 | (ibuffer-and-filter 'decompose) | ||
| 597 | (and (equal (aref filters 0) | ||
| 598 | (pop ibuffer-filtering-qualifiers)) | ||
| 599 | (equal (aref filters 1) | ||
| 600 | (pop ibuffer-filtering-qualifiers)) | ||
| 601 | (null ibuffer-filtering-qualifiers)))) | ||
| 602 | (should (progn | ||
| 603 | (push (list 'and (aref filters 2) (aref filters 3)) | ||
| 604 | ibuffer-filtering-qualifiers) | ||
| 605 | (push (list 'and (aref filters 0) (aref filters 1)) | ||
| 606 | ibuffer-filtering-qualifiers) | ||
| 607 | (ibuffer-and-filter) | ||
| 608 | (and (equal (list 'and (aref filters 0) (aref filters 1) | ||
| 609 | (aref filters 2) (aref filters 3)) | ||
| 610 | (car ibuffer-filtering-qualifiers)) | ||
| 611 | (null (cdr ibuffer-filtering-qualifiers))))) | ||
| 612 | (pop ibuffer-filtering-qualifiers) | ||
| 613 | (should (progn | ||
| 614 | (push (list 'or (aref filters 2) (aref filters 3)) | ||
| 615 | ibuffer-filtering-qualifiers) | ||
| 616 | (push (list 'and (aref filters 0) (aref filters 1)) | ||
| 617 | ibuffer-filtering-qualifiers) | ||
| 618 | (ibuffer-and-filter) | ||
| 619 | (and (equal (list 'and (aref filters 0) (aref filters 1) | ||
| 620 | (list 'or (aref filters 2) | ||
| 621 | (aref filters 3))) | ||
| 622 | (car ibuffer-filtering-qualifiers)) | ||
| 623 | (null (cdr ibuffer-filtering-qualifiers))))) | ||
| 624 | (pop ibuffer-filtering-qualifiers) | ||
| 625 | (should (progn | ||
| 626 | (push (list 'and (aref filters 2) (aref filters 3)) | ||
| 627 | ibuffer-filtering-qualifiers) | ||
| 628 | (push (list 'or (aref filters 0) (aref filters 1)) | ||
| 629 | ibuffer-filtering-qualifiers) | ||
| 630 | (ibuffer-and-filter) | ||
| 631 | (and (equal (list 'and (list 'or (aref filters 0) | ||
| 632 | (aref filters 1)) | ||
| 633 | (aref filters 2) (aref filters 3)) | ||
| 634 | (car ibuffer-filtering-qualifiers)) | ||
| 635 | (null (cdr ibuffer-filtering-qualifiers))))) | ||
| 636 | (pop ibuffer-filtering-qualifiers) | ||
| 637 | (should (progn | ||
| 638 | (push (list 'or (aref filters 2) (aref filters 3)) | ||
| 639 | ibuffer-filtering-qualifiers) | ||
| 640 | (push (list 'or (aref filters 0) (aref filters 1)) | ||
| 641 | ibuffer-filtering-qualifiers) | ||
| 642 | (ibuffer-and-filter) | ||
| 643 | (and (equal (list 'and | ||
| 644 | (list 'or (aref filters 0) | ||
| 645 | (aref filters 1)) | ||
| 646 | (list 'or (aref filters 2) | ||
| 647 | (aref filters 3))) | ||
| 648 | (car ibuffer-filtering-qualifiers)) | ||
| 649 | (null (cdr ibuffer-filtering-qualifiers)))))))) | ||
| 650 | (funcall clean-up))) | ||
| 651 | |||
| 652 | (ert-deftest ibuffer-or-filter () | ||
| 653 | "Tests `ibuffer-or-filter' in an Ibuffer buffer." | ||
| 654 | (skip-unless (featurep 'ibuf-ext)) | ||
| 655 | (unwind-protect | ||
| 656 | (let ((ibuf (funcall get-test-ibuffer))) | ||
| 657 | (with-current-buffer ibuf | ||
| 658 | (let ((ibuffer-filtering-qualifiers nil) | ||
| 659 | (ibuffer-filter-groups nil) | ||
| 660 | (filters [(size-gt . 100) (not (starred-name)) | ||
| 661 | (filename . "A") (mode . text-mode)])) | ||
| 662 | (should-error (ibuffer-or-filter) :type 'error) | ||
| 663 | (progn | ||
| 664 | (push (aref filters 1) ibuffer-filtering-qualifiers) | ||
| 665 | (should-error (ibuffer-or-filter) :type 'error)) | ||
| 666 | (should (progn | ||
| 667 | (push (aref filters 0) ibuffer-filtering-qualifiers) | ||
| 668 | (ibuffer-or-filter) | ||
| 669 | (and (equal (list 'or (aref filters 0) (aref filters 1)) | ||
| 670 | (car ibuffer-filtering-qualifiers)) | ||
| 671 | (null (cdr ibuffer-filtering-qualifiers))))) | ||
| 672 | (should (progn | ||
| 673 | (ibuffer-or-filter 'decompose) | ||
| 674 | (and (equal (aref filters 0) | ||
| 675 | (pop ibuffer-filtering-qualifiers)) | ||
| 676 | (equal (aref filters 1) | ||
| 677 | (pop ibuffer-filtering-qualifiers)) | ||
| 678 | (null ibuffer-filtering-qualifiers)))) | ||
| 679 | (should (progn | ||
| 680 | (push (list 'or (aref filters 2) (aref filters 3)) | ||
| 681 | ibuffer-filtering-qualifiers) | ||
| 682 | (push (list 'or (aref filters 0) (aref filters 1)) | ||
| 683 | ibuffer-filtering-qualifiers) | ||
| 684 | (ibuffer-or-filter) | ||
| 685 | (and (equal (list 'or (aref filters 0) (aref filters 1) | ||
| 686 | (aref filters 2) (aref filters 3)) | ||
| 687 | (car ibuffer-filtering-qualifiers)) | ||
| 688 | (null (cdr ibuffer-filtering-qualifiers))))) | ||
| 689 | (pop ibuffer-filtering-qualifiers) | ||
| 690 | (should (progn | ||
| 691 | (push (list 'and (aref filters 2) (aref filters 3)) | ||
| 692 | ibuffer-filtering-qualifiers) | ||
| 693 | (push (list 'or (aref filters 0) (aref filters 1)) | ||
| 694 | ibuffer-filtering-qualifiers) | ||
| 695 | (ibuffer-or-filter) | ||
| 696 | (and (equal (list 'or (aref filters 0) (aref filters 1) | ||
| 697 | (list 'and (aref filters 2) | ||
| 698 | (aref filters 3))) | ||
| 699 | (car ibuffer-filtering-qualifiers)) | ||
| 700 | (null (cdr ibuffer-filtering-qualifiers))))) | ||
| 701 | (pop ibuffer-filtering-qualifiers) | ||
| 702 | (should (progn | ||
| 703 | (push (list 'or (aref filters 2) (aref filters 3)) | ||
| 704 | ibuffer-filtering-qualifiers) | ||
| 705 | (push (list 'and (aref filters 0) (aref filters 1)) | ||
| 706 | ibuffer-filtering-qualifiers) | ||
| 707 | (ibuffer-or-filter) | ||
| 708 | (and (equal (list 'or (list 'and (aref filters 0) | ||
| 709 | (aref filters 1)) | ||
| 710 | (aref filters 2) (aref filters 3)) | ||
| 711 | (car ibuffer-filtering-qualifiers)) | ||
| 712 | (null (cdr ibuffer-filtering-qualifiers))))) | ||
| 713 | (pop ibuffer-filtering-qualifiers) | ||
| 714 | (should (progn | ||
| 715 | (push (list 'and (aref filters 2) (aref filters 3)) | ||
| 716 | ibuffer-filtering-qualifiers) | ||
| 717 | (push (list 'and (aref filters 0) (aref filters 1)) | ||
| 718 | ibuffer-filtering-qualifiers) | ||
| 719 | (ibuffer-or-filter) | ||
| 720 | (and (equal (list 'or | ||
| 721 | (list 'and (aref filters 0) | ||
| 722 | (aref filters 1)) | ||
| 723 | (list 'and (aref filters 2) | ||
| 724 | (aref filters 3))) | ||
| 725 | (car ibuffer-filtering-qualifiers)) | ||
| 726 | (null (cdr ibuffer-filtering-qualifiers)))))))) | ||
| 727 | (funcall clean-up)))) | ||
| 728 | |||
| 729 | (ert-deftest ibuffer-format-qualifier () | ||
| 730 | "Tests string recommendation of filter from `ibuffer-format-qualifier'." | ||
| 731 | (skip-unless (featurep 'ibuf-ext)) | ||
| 732 | (let ((test1 '(mode . org-mode)) | ||
| 733 | (test2 '(size-lt . 100)) | ||
| 734 | (test3 '(derived-mode . prog-mode)) | ||
| 735 | (test4 '(or (size-gt . 10000) | ||
| 736 | (and (not (starred-name)) | ||
| 737 | (directory . "\\<org\\>")))) | ||
| 738 | (test5 '(or (filename . "scratch") | ||
| 739 | (filename . "bonz") | ||
| 740 | (filename . "temp"))) | ||
| 741 | (test6 '(or (mode . emacs-lisp-mode) (file-extension . "elc?") | ||
| 742 | (and (starred-name) (name . "elisp")) | ||
| 743 | (mode . lisp-interaction-mode))) | ||
| 744 | (description (lambda (q) | ||
| 745 | (cadr (assq q ibuffer-filtering-alist)))) | ||
| 746 | (tag (lambda (&rest args ) | ||
| 747 | (concat " [" (apply #'concat args) "]")))) | ||
| 748 | (should (equal (ibuffer-format-qualifier test1) | ||
| 749 | (funcall tag (funcall description 'mode) | ||
| 750 | ": " "org-mode"))) | ||
| 751 | (should (equal (ibuffer-format-qualifier test2) | ||
| 752 | (funcall tag (funcall description 'size-lt) | ||
| 753 | ": " "100"))) | ||
| 754 | (should (equal (ibuffer-format-qualifier test3) | ||
| 755 | (funcall tag (funcall description 'derived-mode) | ||
| 756 | ": " "prog-mode"))) | ||
| 757 | (should (equal (ibuffer-format-qualifier test4) | ||
| 758 | (funcall tag "OR" | ||
| 759 | (funcall tag (funcall description 'size-gt) | ||
| 760 | ": " (format "%s" 10000)) | ||
| 761 | (funcall tag "AND" | ||
| 762 | (funcall tag "NOT" | ||
| 763 | (funcall tag | ||
| 764 | (funcall description | ||
| 765 | 'starred-name) | ||
| 766 | ": " "nil")) | ||
| 767 | (funcall tag | ||
| 768 | (funcall description 'directory) | ||
| 769 | ": " "\\<org\\>"))))) | ||
| 770 | (should (equal (ibuffer-format-qualifier test5) | ||
| 771 | (funcall tag "OR" | ||
| 772 | (funcall tag (funcall description 'filename) | ||
| 773 | ": " "scratch") | ||
| 774 | (funcall tag (funcall description 'filename) | ||
| 775 | ": " "bonz") | ||
| 776 | (funcall tag (funcall description 'filename) | ||
| 777 | ": " "temp")))) | ||
| 778 | (should (equal (ibuffer-format-qualifier test6) | ||
| 779 | (funcall tag "OR" | ||
| 780 | (funcall tag (funcall description 'mode) | ||
| 781 | ": " "emacs-lisp-mode") | ||
| 782 | (funcall tag (funcall description 'file-extension) | ||
| 783 | ": " "elc?") | ||
| 784 | (funcall tag "AND" | ||
| 785 | (funcall tag | ||
| 786 | (funcall description 'starred-name) | ||
| 787 | ": " "nil") | ||
| 788 | (funcall tag | ||
| 789 | (funcall description 'name) | ||
| 790 | ": " "elisp")) | ||
| 791 | (funcall tag (funcall description 'mode) | ||
| 792 | ": " "lisp-interaction-mode")))))) | ||
| 793 | |||
| 794 | (ert-deftest ibuffer-unary-operand () | ||
| 795 | "Tests `ibuffer-unary-operand': (not cell) or (not . cell) -> cell." | ||
| 796 | (skip-unless (featurep 'ibuf-ext)) | ||
| 797 | (should (equal (ibuffer-unary-operand '(not . (mode "foo"))) | ||
| 798 | '(mode "foo"))) | ||
| 799 | (should (equal (ibuffer-unary-operand '(not (mode "foo"))) | ||
| 800 | '(mode "foo"))) | ||
| 801 | (should (equal (ibuffer-unary-operand '(not "cdr")) | ||
| 802 | '("cdr"))) | ||
| 803 | (should (equal (ibuffer-unary-operand '(not)) nil)) | ||
| 804 | (should (equal (ibuffer-unary-operand '(not . a)) 'a))) | ||
| 805 | |||
| 141 | (provide 'ibuffer-tests) | 806 | (provide 'ibuffer-tests) |
| 142 | ;; ibuffer-tests.el ends here | 807 | ;; ibuffer-tests.el ends here |