summaryrefslogtreecommitdiff
path: root/lisp/ibuf-ext.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/ibuf-ext.el')
-rw-r--r--lisp/ibuf-ext.el716
1 files changed, 541 insertions, 175 deletions
diff --git a/lisp/ibuf-ext.el b/lisp/ibuf-ext.el
index f5375612c34..7ebfecd3749 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,15 +42,15 @@
(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)
- (while (setq entry (assoc key alist))
- (setq alist (delete entry alist)))
- alist))
+(defun ibuffer-remove-alist (key alist)
+ "Remove all entries in ALIST that have a key equal to KEY."
+ (while (ibuffer-awhen (assoc key alist)
+ (setq alist (remove it alist)) it))
+ alist)
;; borrowed from Gnus
(defun ibuffer-remove-duplicates (list)
@@ -85,6 +92,32 @@ regardless of any active filters in this buffer."
:type '(repeat (choice regexp function))
:group 'ibuffer)
+(defcustom ibuffer-never-search-content-name
+ (let* ((names '("Completions" "Help" "Messages" "Pp Eval Output"
+ "CompileLog" "Info" "Buffer List" "Ibuffer" "Apropos"))
+ (partial '("Customize Option: " "Async Shell Command\\*"
+ "Shell Command Output\\*" "ediff "))
+ (beg "\\`\\*")
+ (end "\\*\\'")
+ (excluded (mapcar (lambda (x)
+ (format "%s%s" beg x)) partial)))
+ (dolist (str names (nreverse excluded))
+ (push (format "%s%s%s" beg str end) excluded)))
+ "A list of regexps for buffers ignored by `ibuffer-mark-by-content-regexp'.
+Buffers whose name matches a regexp in this list, are not searched."
+ :version "26.1"
+ :type '(repeat regexp)
+ :require 'ibuf-ext
+ :group 'ibuffer)
+
+(defcustom ibuffer-never-search-content-mode '(dired-mode)
+ "A list of major modes ignored by `ibuffer-mark-by-content-regexp'.
+Buffers whose major mode is in this list, are not searched."
+ :version "26.1"
+ :type '(repeat regexp)
+ :require 'ibuf-ext
+ :group 'ibuffer)
+
(defvar ibuffer-tmp-hide-regexps nil
"A list of regexps which should match buffer names to not show.")
@@ -93,35 +126,157 @@ regardless of any active filters in this buffer."
(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)
+(defun ibuffer-update-saved-filters-format (filters)
+ "Transforms alist from old to new `ibuffer-saved-filters' format.
+
+Specifically, converts old-format alist with values of the
+form (STRING (FILTER-SPECS...)) to alist with values of the
+form (STRING FILTER-SPECS...), where each filter spec should be a
+cons cell with a symbol in the car. Any elements in the latter
+form are kept as is.
+
+Returns (OLD-FORMAT-DETECTED . UPDATED-SAVED-FILTERS-LIST)."
+ (when filters
+ (let* ((old-format-detected nil)
+ (fix-filter (lambda (filter-spec)
+ (if (symbolp (car (cadr filter-spec)))
+ filter-spec
+ (setq old-format-detected t) ; side-effect
+ (cons (car filter-spec) (cadr filter-spec)))))
+ (fixed (mapcar fix-filter filters)))
+ (cons old-format-detected fixed))))
+
+(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 (repeat :tag "Filter specification" sexp))
+ :set (lambda (symbol value)
+ ;; Just set-default but update legacy old-style format
+ (set-default symbol (cdr (ibuffer-update-saved-filters-format value))))
:group 'ibuffer)
+(defvar ibuffer-old-saved-filters-warning
+ (concat "Deprecated format detected for variable `ibuffer-saved-filters'.
+
+The format has been repaired and the variable modified accordingly.
+You can save the current value through the customize system by
+either clicking or hitting return "
+ (make-text-button
+ "here" nil
+ 'face '(:weight bold :inherit button)
+ 'mouse-face '(:weight normal :background "gray50" :inherit button)
+ 'follow-link t
+ 'help-echo "Click or RET: save new value in customize"
+ 'action (lambda (_)
+ (if (not (fboundp 'customize-save-variable))
+ (message "Customize not available; value not saved")
+ (customize-save-variable 'ibuffer-saved-filters
+ ibuffer-saved-filters)
+ (message "Saved updated ibuffer-saved-filters."))))
+ ". See below for
+an explanation and alternative ways to save the repaired value.
+
+Explanation: For the list variable `ibuffer-saved-filters',
+elements of the form (STRING (FILTER-SPECS...)) are deprecated
+and should instead have the form (STRING FILTER-SPECS...), where
+each filter spec is a cons cell with a symbol in the car. See
+`ibuffer-saved-filters' for details. The repaired value fixes
+this format without changing the meaning of the saved filters.
+
+Alternative ways to save the repaired value:
+
+ 1. Do M-x customize-variable and entering `ibuffer-saved-filters'
+ when prompted.
+
+ 2. Set the updated value manually by copying the
+ following emacs-lisp form to your emacs init file.
+
+%s
+"))
+
(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
@@ -153,10 +308,18 @@ to this variable."
(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."
@@ -166,20 +329,21 @@ The QUALIFIER should be the same as QUALIFIER in
(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)
@@ -198,6 +362,28 @@ Currently, this only applies to `ibuffer-saved-filters' and
:type 'boolean
:group 'ibuffer)
+(defun ibuffer-repair-saved-filters ()
+ "Updates `ibuffer-saved-filters' to its new-style format, if needed.
+
+If this list has any elements of the old-style format, a
+deprecation warning is raised, with a button allowing persistent
+update. Any updated filters retain their meaning in the new
+format. See `ibuffer-update-saved-filters-format' and
+`ibuffer-saved-filters' for details of the old and new formats."
+ (interactive)
+ (when (and (boundp 'ibuffer-saved-filters) ibuffer-saved-filters)
+ (let ((fixed (ibuffer-update-saved-filters-format ibuffer-saved-filters)))
+ (prog1
+ (setq ibuffer-saved-filters (cdr fixed))
+ (when-let (old-format-detected (car fixed))
+ (let ((warning-series t)
+ (updated-form
+ (with-output-to-string
+ (pp `(setq ibuffer-saved-filters ',ibuffer-saved-filters)))))
+ (display-warning
+ 'ibuffer
+ (format ibuffer-old-saved-filters-warning updated-form))))))))
+
(defun ibuffer-ext-visible-p (buf all &optional ibuffer-buf)
(or
(ibuffer-buf-matches-predicates buf ibuffer-tmp-show-regexps)
@@ -224,8 +410,11 @@ the mode if ARG is omitted or nil."
nil nil nil
(unless (derived-mode-p 'ibuffer-mode)
(error "This buffer is not in Ibuffer mode"))
- (frame-or-buffer-changed-p 'ibuffer-auto-buffers-changed) ; Initialize state vector
- (add-hook 'post-command-hook 'ibuffer-auto-update-changed))
+ (cond (ibuffer-auto-mode
+ (frame-or-buffer-changed-p 'ibuffer-auto-buffers-changed) ; Initialize state vector
+ (add-hook 'post-command-hook 'ibuffer-auto-update-changed))
+ (t
+ (remove-hook 'post-command-hook 'ibuffer-auto-update-changed))))
(defun ibuffer-auto-update-changed ()
(when (frame-or-buffer-changed-p 'ibuffer-auto-buffers-changed)
@@ -252,7 +441,7 @@ the mode if ARG is omitted or nil."
(let ((buf (ibuffer-current-buffer)))
(if (assq 'mode ibuffer-filtering-qualifiers)
(setq ibuffer-filtering-qualifiers
- (ibuffer-delete-alist 'mode ibuffer-filtering-qualifiers))
+ (ibuffer-remove-alist 'mode ibuffer-filtering-qualifiers))
(ibuffer-push-filter (cons 'mode (buffer-local-value 'major-mode buf)))))
(ibuffer-update nil t))
@@ -324,8 +513,7 @@ the mode if ARG is omitted or nil."
:opstring "Shell command executed on"
:modifier-p nil)
(shell-command-on-region
- (point-min) (point-max) command
- (get-buffer-create "* ibuffer-shell-output*")))
+ (point-min) (point-max) command))
;;;###autoload (autoload 'ibuffer-do-shell-command-pipe-replace "ibuf-ext")
(define-ibuffer-op shell-command-pipe-replace (command)
@@ -347,10 +535,14 @@ the mode if ARG is omitted or nil."
:modifier-p nil)
(shell-command (concat command " "
(shell-quote-argument
- (if buffer-file-name
- buffer-file-name
- (make-temp-file
- (substring (buffer-name) 0 (min 10 (length (buffer-name))))))))))
+ (or buffer-file-name
+ (let ((file
+ (make-temp-file
+ (substring
+ (buffer-name) 0
+ (min 10 (length (buffer-name)))))))
+ (write-region nil nil file nil 0)
+ file))))))
;;;###autoload (autoload 'ibuffer-do-eval "ibuf-ext")
(define-ibuffer-op eval (form)
@@ -480,18 +672,38 @@ To evaluate a form without viewing the buffer, see `ibuffer-do-eval'."
;;;###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)
@@ -499,17 +711,25 @@ To evaluate a form without viewing the buffer, see `ibuffer-do-eval'."
(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)))
+ (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))))
+ (ibuffer-included-in-filters-p buf (cdr data))))
(_
(pcase-let ((`(,_type ,_desc ,func)
(assq (car filter) ibuffer-filtering-alist)))
@@ -524,7 +744,7 @@ To evaluate a form without viewing the buffer, see `ibuffer-do-eval'."
(append ibuffer-filter-groups
(list (cons "Default" nil))))))
;; (dolist (hidden ibuffer-hidden-filter-groups)
- ;; (setq filter-group-alist (ibuffer-delete-alist
+ ;; (setq filter-group-alist (ibuffer-remove-alist
;; hidden filter-group-alist)))
(let ((vec (make-vector (length filter-group-alist) nil))
(i 0))
@@ -608,7 +828,7 @@ To evaluate a form without viewing the buffer, see `ibuffer-do-eval'."
(interactive
(list (ibuffer-read-filter-group-name "Decompose filter group: " t)))
(let ((data (cdr (assoc group ibuffer-filter-groups))))
- (setq ibuffer-filter-groups (ibuffer-delete-alist
+ (setq ibuffer-filter-groups (ibuffer-remove-alist
group ibuffer-filter-groups)
ibuffer-filtering-qualifiers data))
(ibuffer-update nil t))
@@ -656,7 +876,7 @@ The group will be added to `ibuffer-filter-group-kill-ring'."
(ibuffer-aif (assoc name ibuffer-filter-groups)
(progn
(push (copy-tree it) ibuffer-filter-group-kill-ring)
- (setq ibuffer-filter-groups (ibuffer-delete-alist
+ (setq ibuffer-filter-groups (ibuffer-remove-alist
name ibuffer-filter-groups))
(setq ibuffer-hidden-filter-groups
(delete name ibuffer-hidden-filter-groups)))
@@ -746,7 +966,7 @@ They are removed from `ibuffer-saved-filter-groups'."
(completing-read "Delete saved filter group: "
ibuffer-saved-filter-groups nil t))))
(setq ibuffer-saved-filter-groups
- (ibuffer-delete-alist name ibuffer-saved-filter-groups))
+ (ibuffer-remove-alist name ibuffer-saved-filter-groups))
(ibuffer-maybe-save-stuff)
(ibuffer-update nil t))
@@ -796,55 +1016,47 @@ group definitions by setting `ibuffer-filter-groups' to nil."
(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)
+ (unless 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)))
- (`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))))
- (`not
- (push (cdr lim)
- ibuffer-filtering-qualifiers))
- (_
- (error "Filter type %s is not compound" (car lim)))))
+ (let* ((filters ibuffer-filtering-qualifiers)
+ (head (cdar filters))
+ (tail (cdr filters))
+ (value
+ (pcase (caar filters)
+ ((or `or 'and) (nconc head tail))
+ (`saved
+ (let ((data (assoc head ibuffer-saved-filters)))
+ (unless data
+ (ibuffer-filter-disable)
+ (error "Unknown saved filter %s" head))
+ (append (cdr data) tail)))
+ (`not (cons (ibuffer-unary-operand (car filters)) tail))
+ (_
+ (error "Filter type %s is not compound" (caar filters))))))
+ (setq ibuffer-filtering-qualifiers value))
(ibuffer-update nil t))
;;;###autoload
(defun ibuffer-exchange-filters ()
"Exchange the top two filters on the stack in this buffer."
(interactive)
- (when (< (length ibuffer-filtering-qualifiers)
- 2)
- (error "Need two filters to exchange"))
- (let ((first (pop ibuffer-filtering-qualifiers))
- (second (pop ibuffer-filtering-qualifiers)))
- (push first ibuffer-filtering-qualifiers)
- (push second ibuffer-filtering-qualifiers))
- (ibuffer-update nil t))
+ (let ((filters ibuffer-filtering-qualifiers))
+ (when (< (length filters) 2)
+ (error "Need two filters to exchange"))
+ (cl-rotatef (car filters) (cadr filters))
+ (ibuffer-update nil t)))
;;;###autoload
(defun ibuffer-negate-filter ()
@@ -859,31 +1071,36 @@ turned into two separate filters [name: foo] and [mode: bar-mode]."
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
@@ -907,7 +1124,7 @@ Interactively, prompt for NAME, and use the current filters."
ibuffer-filtering-qualifiers)))
(ibuffer-aif (assoc name ibuffer-saved-filters)
(setcdr it filters)
- (push (list name filters) ibuffer-saved-filters))
+ (push (cons name filters) ibuffer-saved-filters))
(ibuffer-maybe-save-stuff))
;;;###autoload
@@ -920,7 +1137,7 @@ Interactively, prompt for NAME, and use the current filters."
(completing-read "Delete saved filters: "
ibuffer-saved-filters nil t))))
(setq ibuffer-saved-filters
- (ibuffer-delete-alist name ibuffer-saved-filters))
+ (ibuffer-remove-alist name ibuffer-saved-filters))
(ibuffer-maybe-save-stuff)
(ibuffer-update nil t))
@@ -957,7 +1174,9 @@ Interactively, prompt for NAME, and use the current filters."
(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)
@@ -966,14 +1185,16 @@ Interactively, prompt for NAME, and use the current filters."
(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."
@@ -991,7 +1212,7 @@ If INCLUDE-PARENTS is non-nil then include parent 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))
@@ -1011,7 +1232,7 @@ If INCLUDE-PARENTS is non-nil then include parent modes."
;;;###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"
@@ -1030,7 +1251,7 @@ currently used by buffers."
;;;###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
@@ -1041,22 +1262,73 @@ currently used by buffers."
;;;###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)
+ (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 name matching QUALIFIER.
+
+For example, for a buffer associated with file '/a/b/c.d', this
+matches against '/a/b/c.d'."
+ (:description "full file name"
+ :reader (read-from-minibuffer "Filter by full file name (regexp): "))
(ibuffer-awhen (with-current-buffer buf (ibuffer-buffer-file-name))
(string-match qualifier it)))
+;;;###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: ")))
@@ -1065,16 +1337,30 @@ currently used by buffers."
;;;###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)
+ (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)
+ (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
@@ -1084,12 +1370,33 @@ currently used by buffers."
;;;###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
@@ -1388,7 +1695,7 @@ This requires the external program \"diff\" to be in your `exec-path'."
;;;###autoload
(defun ibuffer-copy-filename-as-kill (&optional arg)
- "Copy filenames of marked buffers into the kill ring.
+ "Copy filenames of marked (or next ARG) buffers into the kill ring.
The names are separated by a space.
If a buffer has no filename, it is ignored.
@@ -1399,34 +1706,51 @@ With \\[universal-argument], use the filename of each marked file relative
to `ibuffer-default-directory' if non-nil, otherwise `default-directory'.
You can then feed the file name(s) to other commands with \\[yank]."
- (interactive "p")
- (if (zerop (ibuffer-count-marked-lines))
- (message "No buffers marked; use 'm' to mark a buffer")
- (let ((ibuffer-copy-filename-as-kill-result "")
- (type (cond ((zerop arg)
- 'full)
- ((= arg 4)
- 'relative)
- (t
- 'name))))
- (ibuffer-map-marked-lines
- #'(lambda (buf _mark)
- (setq ibuffer-copy-filename-as-kill-result
- (concat ibuffer-copy-filename-as-kill-result
- (let ((name (buffer-file-name buf)))
- (if name
- (pcase type
- (`full
- name)
- (`relative
- (file-relative-name
- name (or ibuffer-default-directory
- default-directory)))
- (_
- (file-name-nondirectory name)))
- ""))
- " "))))
- (kill-new ibuffer-copy-filename-as-kill-result))))
+ (interactive "P")
+ (let* ((buffers (cond ((and (integerp arg) (not (zerop arg)))
+ (ibuffer--near-buffers arg))
+ (t
+ (or (ibuffer-get-marked-buffers)
+ (list (ibuffer-current-buffer))))))
+ (file-names
+ (mapcar
+ (lambda (buf)
+ (let ((name (with-current-buffer buf
+ (ibuffer-buffer-file-name))))
+ (if (null name)
+ ""
+ (cond ((and (integerp arg) (zerop arg)) name)
+ ((consp arg)
+ (file-relative-name
+ name (or ibuffer-default-directory
+ default-directory)))
+ (t (file-name-nondirectory name))))))
+ buffers))
+ (string
+ (mapconcat 'identity (delete "" file-names) " ")))
+ (unless (string= string "")
+ (if (eq last-command 'kill-region)
+ (kill-append string nil)
+ (kill-new string))
+ (message "%s" string))))
+
+;;;###autoload
+(defun ibuffer-copy-buffername-as-kill (&optional arg)
+ "Copy buffer names of marked (or next ARG) buffers into the kill ring.
+The names are separated by a space.
+You can then feed the file name(s) to other commands with \\[yank]."
+ (interactive "P")
+ (let* ((buffers (cond ((and (integerp arg) (not (zerop arg)))
+ (ibuffer--near-buffers arg))
+ (t
+ (or (ibuffer-get-marked-buffers)
+ (list (ibuffer-current-buffer))))))
+ (string (mapconcat #'buffer-name buffers " ")))
+ (unless (string= string "")
+ (if (eq last-command 'kill-region)
+ (kill-append string nil)
+ (kill-new string))
+ (message "%s" string))))
(defun ibuffer-mark-on-buffer (func &optional ibuffer-mark-on-buffer-mark group)
(let ((count
@@ -1450,6 +1774,23 @@ You can then feed the file name(s) to other commands with \\[yank]."
#'(lambda (buf)
(string-match regexp (buffer-name buf)))))
+(defun ibuffer-locked-buffer-p (&optional buf)
+ "Return non-nil if BUF is locked.
+When BUF nil, default to the buffer at current line."
+ (let ((cbuffer (or buf (ibuffer-current-buffer))))
+ (when cbuffer
+ (with-current-buffer cbuffer
+ (and (boundp 'emacs-lock-mode) emacs-lock-mode)))))
+
+;;;###autoload
+(defun ibuffer-mark-by-locked ()
+ "Mark all locked buffers."
+ (interactive)
+ (when (featurep 'emacs-lock)
+ (ibuffer-mark-on-buffer
+ (lambda (buf)
+ (ibuffer-locked-buffer-p buf)))))
+
;;;###autoload
(defun ibuffer-mark-by-mode-regexp (regexp)
"Mark all buffers whose major mode matches REGEXP."
@@ -1475,6 +1816,31 @@ You can then feed the file name(s) to other commands with \\[yank]."
(string-match regexp name))))))
;;;###autoload
+(defun ibuffer-mark-by-content-regexp (regexp &optional all-buffers)
+ "Mark all buffers whose content matches REGEXP.
+Optional arg ALL-BUFFERS, if non-nil, then search in all buffers.
+Otherwise buffers whose name matches an element of
+`ibuffer-never-search-content-name' or whose major mode is on
+`ibuffer-never-search-content-mode' are excluded."
+ (interactive (let ((reg (read-string "Mark by content (regexp): ")))
+ (list reg current-prefix-arg)))
+ (ibuffer-mark-on-buffer
+ #'(lambda (buf)
+ (let ((mode (with-current-buffer buf major-mode))
+ res)
+ (cond ((and (not all-buffers)
+ (or
+ (memq mode ibuffer-never-search-content-mode)
+ (cl-some (lambda (x) (string-match x (buffer-name buf)))
+ ibuffer-never-search-content-name)))
+ (setq res nil))
+ (t
+ (with-current-buffer buf
+ (save-mark-and-excursion
+ (goto-char (point-min))
+ (setq res (re-search-forward regexp nil t)))))) res))))
+
+;;;###autoload
(defun ibuffer-mark-by-mode (mode)
"Mark all buffers whose major mode equals MODE."
(interactive
@@ -1598,7 +1964,7 @@ defaults to one."
(provide 'ibuf-ext)
;; Local Variables:
-;; generated-autoload-file: "ibuffer.el"
+;; generated-autoload-file: "ibuffer-loaddefs.el"
;; End:
;;; ibuf-ext.el ends here