* lisp/gnus/mm-extern.el (message-goto-body): Update declaration.
[bpt/emacs.git] / lisp / ibuf-ext.el
CommitLineData
4e4a724c 1;;; ibuf-ext.el --- extensions for ibuffer
25d2f683 2
d9351bbd 3;; Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
114f9c96 4;; 2009, 2010 Free Software Foundation, Inc.
25d2f683
CW
5
6;; Author: Colin Walters <walters@verbum.org>
4e4a724c 7;; Maintainer: John Paul Wallington <jpw@gnu.org>
25d2f683 8;; Created: 2 Dec 2001
25d2f683 9;; Keywords: buffer, convenience
aad4679e 10;; Package: ibuffer
25d2f683 11
365e1cfb 12;; This file is part of GNU Emacs.
25d2f683 13
eb3fa2cf
GM
14;; GNU Emacs is free software: you can redistribute it and/or modify
15;; it under the terms of the GNU General Public License as published by
16;; the Free Software Foundation, either version 3 of the License, or
17;; (at your option) any later version.
25d2f683 18
eb3fa2cf
GM
19;; GNU Emacs is distributed in the hope that it will be useful,
20;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22;; GNU General Public License for more details.
25d2f683
CW
23
24;; You should have received a copy of the GNU General Public License
eb3fa2cf 25;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
25d2f683
CW
26
27;;; Commentary:
28
29;; These functions should be automatically loaded when called, but you
30;; can explicity (require 'ibuf-ext) in your ~/.emacs to have them
31;; preloaded.
32
33;;; Code:
34
35(require 'ibuffer)
36
37(eval-when-compile
25d2f683
CW
38 (require 'ibuf-macs)
39 (require 'cl))
40
41;;; Utility functions
42(defun ibuffer-delete-alist (key alist)
43 "Delete all entries in ALIST that have a key equal to KEY."
44 (let (entry)
45 (while (setq entry (assoc key alist))
46 (setq alist (delete entry alist)))
47 alist))
48
4ba16127
JPW
49;; borrowed from Gnus
50(defun ibuffer-remove-duplicates (list)
51 "Return a copy of LIST with duplicate elements removed."
52 (let ((new nil)
53 (tail list))
54 (while tail
55 (or (member (car tail) new)
56 (setq new (cons (car tail) new)))
57 (setq tail (cdr tail)))
58 (nreverse new)))
59
365e1cfb
CW
60(defun ibuffer-split-list (ibuffer-split-list-fn ibuffer-split-list-elts)
61 (let ((hip-crowd nil)
62 (lamers nil))
63 (dolist (ibuffer-split-list-elt ibuffer-split-list-elts)
4e4a724c 64 (if (funcall ibuffer-split-list-fn ibuffer-split-list-elt)
365e1cfb
CW
65 (push ibuffer-split-list-elt hip-crowd)
66 (push ibuffer-split-list-elt lamers)))
67 ;; Too bad Emacs Lisp doesn't have multiple values.
68 (list (nreverse hip-crowd) (nreverse lamers))))
69
25d2f683
CW
70(defcustom ibuffer-never-show-predicates nil
71 "A list of predicates (a regexp or function) for buffers not to display.
72If a regexp, then it will be matched against the buffer's name.
73If a function, it will be called with the buffer as an argument, and
74should return non-nil if this buffer should not be shown."
75 :type '(repeat (choice regexp function))
da63ece4 76 :require 'ibuf-ext
25d2f683
CW
77 :group 'ibuffer)
78
79(defcustom ibuffer-always-show-predicates nil
80 "A list of predicates (a regexp or function) for buffers to always display.
81If a regexp, then it will be matched against the buffer's name.
82If a function, it will be called with the buffer as an argument, and
83should return non-nil if this buffer should be shown.
84Note that buffers matching one of these predicates will be shown
85regardless of any active filters in this buffer."
86 :type '(repeat (choice regexp function))
87 :group 'ibuffer)
88
89(defvar ibuffer-tmp-hide-regexps nil
90 "A list of regexps which should match buffer names to not show.")
71296446 91
25d2f683
CW
92(defvar ibuffer-tmp-show-regexps nil
93 "A list of regexps which should match buffer names to always show.")
94
25d2f683
CW
95(defvar ibuffer-auto-buffers-changed nil)
96
25d2f683
CW
97(defcustom ibuffer-saved-filters '(("gnus"
98 ((or (mode . message-mode)
99 (mode . mail-mode)
100 (mode . gnus-group-mode)
4e4a724c 101 (mode . gnus-summary-mode)
25d2f683
CW
102 (mode . gnus-article-mode))))
103 ("programming"
104 ((or (mode . emacs-lisp-mode)
105 (mode . cperl-mode)
106 (mode . c-mode)
4e4a724c 107 (mode . java-mode)
25d2f683
CW
108 (mode . idl-mode)
109 (mode . lisp-mode)))))
71296446 110
25d2f683
CW
111 "An alist of filter qualifiers to switch between.
112
113This variable should look like ((\"STRING\" QUALIFIERS)
114 (\"STRING\" QUALIFIERS) ...), where
115QUALIFIERS is a list of the same form as
116`ibuffer-filtering-qualifiers'.
117See also the variables `ibuffer-filtering-qualifiers',
118`ibuffer-filtering-alist', and the functions
119`ibuffer-switch-to-saved-filters', `ibuffer-save-filters'."
120 :type '(repeat sexp)
121 :group 'ibuffer)
122
123(defvar ibuffer-filtering-qualifiers nil
124 "A list like (SYMBOL . QUALIFIER) which filters the current buffer list.
125See also `ibuffer-filtering-alist'.")
126
127;; This is now frobbed by `define-ibuffer-filter'.
128(defvar ibuffer-filtering-alist nil
129 "An alist of (SYMBOL DESCRIPTION FUNCTION) which describes a filter.
130
131You most likely do not want to modify this variable directly; see
132`define-ibuffer-filter'.
133
134SYMBOL is the symbolic name of the filter. DESCRIPTION is used when
135displaying information to the user. FUNCTION is given a buffer and
136the value of the qualifier, and returns non-nil if and only if the
137buffer should be displayed.")
138
d98be487
CW
139(defcustom ibuffer-filter-format-alist nil
140 "An alist which has special formats used when a filter is active.
141The contents of this variable should look like:
142 ((FILTER (FORMAT FORMAT ...)) (FILTER (FORMAT FORMAT ...)) ...)
143
144For example, suppose that when you add a filter for buffers whose
145major mode is `emacs-lisp-mode', you only want to see the mark and the
146name of the buffer. You could accomplish that by adding:
147 (mode ((mark \" \" name)))
6d63dcf5
CW
148to this variable."
149 :type '(repeat (list :tag "Association" (symbol :tag "Filter")
150 (list :tag "Formats" (repeat (sexp :tag "Format")))))
151 :group 'ibuffer)
d98be487
CW
152
153(defvar ibuffer-cached-filter-formats nil)
4e4a724c 154(defvar ibuffer-compiled-filter-formats nil)
d98be487 155
05276226 156(defvar ibuffer-filter-groups nil
365e1cfb 157 "A list like ((\"NAME\" ((SYMBOL . QUALIFIER) ...) ...) which groups buffers.
05276226
CW
158The SYMBOL should be one from `ibuffer-filtering-alist'.
159The QUALIFIER should be the same as QUALIFIER in
160`ibuffer-filtering-qualifiers'.")
161
162(defcustom ibuffer-show-empty-filter-groups t
163 "If non-nil, then show the names of filter groups which are empty."
164 :type 'boolean
165 :group 'ibuffer)
166
fece59b8 167(defcustom ibuffer-saved-filter-groups nil
05276226
CW
168 "An alist of filtering groups to switch between.
169
170This variable should look like ((\"STRING\" QUALIFIERS)
171 (\"STRING\" QUALIFIERS) ...), where
172QUALIFIERS is a list of the same form as
173`ibuffer-filtering-qualifiers'.
174
175See also the variables `ibuffer-filter-groups',
176`ibuffer-filtering-qualifiers', `ibuffer-filtering-alist', and the
ecacd8b4
GM
177functions `ibuffer-switch-to-saved-filter-groups',
178`ibuffer-save-filter-groups'."
05276226
CW
179 :type '(repeat sexp)
180 :group 'ibuffer)
365e1cfb 181
05276226 182(defvar ibuffer-hidden-filter-groups nil
365e1cfb
CW
183 "A list of filtering groups which are currently hidden.")
184
05276226
CW
185(defvar ibuffer-filter-group-kill-ring nil)
186
2c1bb3d3
CW
187(defcustom ibuffer-old-time 72
188 "The number of hours before a buffer is considered \"old\"."
189 :type '(choice (const :tag "72 hours (3 days)" 72)
190 (const :tag "48 hours (2 days)" 48)
191 (const :tag "24 hours (1 day)" 24)
192 (integer :tag "hours"))
25d2f683
CW
193 :group 'ibuffer)
194
195(defcustom ibuffer-save-with-custom t
196 "If non-nil, then use Custom to save interactively changed variables.
05276226 197Currently, this only applies to `ibuffer-saved-filters' and
36a579c1 198`ibuffer-saved-filter-groups'."
25d2f683
CW
199 :type 'boolean
200 :group 'ibuffer)
201
202(defun ibuffer-ext-visible-p (buf all &optional ibuffer-buf)
203 (or
204 (ibuffer-buf-matches-predicates buf ibuffer-tmp-show-regexps)
205 (and (not
206 (or
207 (ibuffer-buf-matches-predicates buf ibuffer-tmp-hide-regexps)
208 (ibuffer-buf-matches-predicates buf ibuffer-never-show-predicates)))
209 (or all
210 (not
211 (ibuffer-buf-matches-predicates buf ibuffer-maybe-show-predicates)))
212 (or ibuffer-view-ibuffer
4e4a724c 213 (and ibuffer-buf
25d2f683
CW
214 (not (eq ibuffer-buf buf))))
215 (or
216 (ibuffer-included-in-filters-p buf ibuffer-filtering-qualifiers)
217 (ibuffer-buf-matches-predicates buf ibuffer-always-show-predicates)))))
218
56eb0904
SM
219;;;###autoload
220(define-minor-mode ibuffer-auto-mode
221 "Toggle use of Ibuffer's auto-update facility.
222With numeric ARG, enable auto-update if and only if ARG is positive."
223 nil nil nil
224 (unless (derived-mode-p 'ibuffer-mode)
225 (error "This buffer is not in Ibuffer mode"))
226 (frame-or-buffer-changed-p 'ibuffer-auto-buffers-changed) ; Initialize state vector
227 (add-hook 'post-command-hook 'ibuffer-auto-update-changed))
228
25d2f683 229(defun ibuffer-auto-update-changed ()
f215a1b4 230 (when (frame-or-buffer-changed-p 'ibuffer-auto-buffers-changed)
368851a5
JB
231 (dolist (buf (buffer-list))
232 (ignore-errors
233 (with-current-buffer buf
234 (when (and ibuffer-auto-mode
b5c49962 235 (derived-mode-p 'ibuffer-mode))
368851a5 236 (ibuffer-update nil t)))))))
25d2f683 237
25d2f683
CW
238;;;###autoload
239(defun ibuffer-mouse-filter-by-mode (event)
240 "Enable or disable filtering by the major mode chosen via mouse."
241 (interactive "e")
242 (ibuffer-interactive-filter-by-mode event))
243
244;;;###autoload
245(defun ibuffer-interactive-filter-by-mode (event-or-point)
246 "Enable or disable filtering by the major mode at point."
247 (interactive "d")
248 (if (eventp event-or-point)
1617bc07 249 (posn-set-point (event-end event-or-point))
25d2f683
CW
250 (goto-char event-or-point))
251 (let ((buf (ibuffer-current-buffer)))
252 (if (assq 'mode ibuffer-filtering-qualifiers)
253 (setq ibuffer-filtering-qualifiers
254 (ibuffer-delete-alist 'mode ibuffer-filtering-qualifiers))
95e8ab35 255 (ibuffer-push-filter (cons 'mode (buffer-local-value 'major-mode buf)))))
25d2f683
CW
256 (ibuffer-update nil t))
257
365e1cfb
CW
258;;;###autoload
259(defun ibuffer-mouse-toggle-filter-group (event)
260 "Toggle the display status of the filter group chosen with the mouse."
261 (interactive "e")
262 (ibuffer-toggle-filter-group-1 (save-excursion
263 (mouse-set-point event)
264 (point))))
265
266;;;###autoload
267(defun ibuffer-toggle-filter-group ()
268 "Toggle the display status of the filter group on this line."
4e4a724c 269 (interactive)
365e1cfb
CW
270 (ibuffer-toggle-filter-group-1 (point)))
271
4e4a724c 272(defun ibuffer-toggle-filter-group-1 (posn)
365e1cfb
CW
273 (let ((name (get-text-property posn 'ibuffer-filter-group-name)))
274 (unless (stringp name)
275 (error "No filtering group name present"))
05276226
CW
276 (if (member name ibuffer-hidden-filter-groups)
277 (setq ibuffer-hidden-filter-groups
278 (delete name ibuffer-hidden-filter-groups))
279 (push name ibuffer-hidden-filter-groups))
365e1cfb
CW
280 (ibuffer-update nil t)))
281
282;;;###autoload
283(defun ibuffer-forward-filter-group (&optional count)
284 "Move point forwards by COUNT filtering groups."
285 (interactive "P")
286 (unless count
287 (setq count 1))
288 (when (> count 0)
289 (when (get-text-property (point) 'ibuffer-filter-group-name)
290 (goto-char (next-single-property-change
291 (point) 'ibuffer-filter-group-name
292 nil (point-max))))
293 (goto-char (next-single-property-change
294 (point) 'ibuffer-filter-group-name
295 nil (point-max)))
296 (ibuffer-forward-filter-group (1- count)))
297 (ibuffer-forward-line 0))
298
299;;;###autoload
300(defun ibuffer-backward-filter-group (&optional count)
301 "Move point backwards by COUNT filtering groups."
302 (interactive "P")
303 (unless count
304 (setq count 1))
305 (when (> count 0)
306 (when (get-text-property (point) 'ibuffer-filter-group-name)
307 (goto-char (previous-single-property-change
308 (point) 'ibuffer-filter-group-name
309 nil (point-min))))
310 (goto-char (previous-single-property-change
311 (point) 'ibuffer-filter-group-name
312 nil (point-min)))
313 (ibuffer-backward-filter-group (1- count)))
314 (when (= (point) (point-min))
315 (goto-char (point-max))
316 (ibuffer-backward-filter-group 1))
317 (ibuffer-forward-line 0))
318
4fe3f297 319;;;###autoload (autoload 'ibuffer-do-shell-command-pipe "ibuf-ext")
25d2f683
CW
320(define-ibuffer-op shell-command-pipe (command)
321 "Pipe the contents of each marked buffer to shell command COMMAND."
322 (:interactive "sPipe to shell command: "
323 :opstring "Shell command executed on"
324 :modifier-p nil)
325 (shell-command-on-region
326 (point-min) (point-max) command
327 (get-buffer-create "* ibuffer-shell-output*")))
328
4fe3f297 329;;;###autoload (autoload 'ibuffer-do-shell-command-pipe-replace "ibuf-ext")
25d2f683
CW
330(define-ibuffer-op shell-command-pipe-replace (command)
331 "Replace the contents of marked buffers with output of pipe to COMMAND."
332 (:interactive "sPipe to shell command (replace): "
333 :opstring "Buffer contents replaced in"
334 :active-opstring "replace buffer contents in"
335 :dangerous t
336 :modifier-p t)
337 (with-current-buffer buf
338 (shell-command-on-region (point-min) (point-max)
339 command nil t)))
340
4fe3f297 341;;;###autoload (autoload 'ibuffer-do-shell-command-file "ibuf-ext")
25d2f683
CW
342(define-ibuffer-op shell-command-file (command)
343 "Run shell command COMMAND separately on files of marked buffers."
344 (:interactive "sShell command on buffer's file: "
345 :opstring "Shell command executed on"
346 :modifier-p nil)
347 (shell-command (concat command " "
348 (shell-quote-argument
349 (if buffer-file-name
350 buffer-file-name
351 (make-temp-file
352 (substring (buffer-name) 0 (min 10 (length (buffer-name))))))))))
365e1cfb 353
4fe3f297 354;;;###autoload (autoload 'ibuffer-do-eval "ibuf-ext")
25d2f683
CW
355(define-ibuffer-op eval (form)
356 "Evaluate FORM in each of the buffers.
357Does not display the buffer during evaluation. See
358`ibuffer-do-view-and-eval' for that."
a0370ba4
JPW
359 (:interactive
360 (list
361 (read-from-minibuffer
362 "Eval in buffers (form): "
363 nil read-expression-map t 'read-expression-history))
25d2f683
CW
364 :opstring "evaluated in"
365 :modifier-p :maybe)
366 (eval form))
367
4fe3f297 368;;;###autoload (autoload 'ibuffer-do-view-and-eval "ibuf-ext")
25d2f683
CW
369(define-ibuffer-op view-and-eval (form)
370 "Evaluate FORM while displaying each of the marked buffers.
371To evaluate a form without viewing the buffer, see `ibuffer-do-eval'."
a0370ba4
JPW
372 (:interactive
373 (list
374 (read-from-minibuffer
375 "Eval viewing in buffers (form): "
376 nil read-expression-map t 'read-expression-history))
25d2f683
CW
377 :opstring "evaluated in"
378 :complex t
379 :modifier-p :maybe)
380 (let ((ibuffer-buf (current-buffer)))
381 (unwind-protect
382 (progn
383 (switch-to-buffer buf)
384 (eval form))
385 (switch-to-buffer ibuffer-buf))))
386
4fe3f297 387;;;###autoload (autoload 'ibuffer-do-rename-uniquely "ibuf-ext")
25d2f683
CW
388(define-ibuffer-op rename-uniquely ()
389 "Rename marked buffers as with `rename-uniquely'."
390 (:opstring "renamed"
391 :modifier-p t)
392 (rename-uniquely))
393
4fe3f297 394;;;###autoload (autoload 'ibuffer-do-revert "ibuf-ext")
25d2f683
CW
395(define-ibuffer-op revert ()
396 "Revert marked buffers as with `revert-buffer'."
397 (:dangerous t
398 :opstring "reverted"
399 :active-opstring "revert"
400 :modifier-p :maybe)
401 (revert-buffer t t))
402
4962c603
JL
403;;;###autoload (autoload 'ibuffer-do-isearch "ibuf-ext")
404(define-ibuffer-op ibuffer-do-isearch ()
405 "Perform a `isearch-forward' in marked buffers."
406 (:interactive ()
407 :opstring "searched in"
408 :complex t
409 :modifier-p :maybe)
410 (multi-isearch-buffers (ibuffer-get-marked-buffers)))
411
412;;;###autoload (autoload 'ibuffer-do-isearch-regexp "ibuf-ext")
413(define-ibuffer-op ibuffer-do-isearch-regexp ()
414 "Perform a `isearch-forward-regexp' in marked buffers."
415 (:interactive ()
416 :opstring "searched regexp in"
417 :complex t
418 :modifier-p :maybe)
419 (multi-isearch-buffers-regexp (ibuffer-get-marked-buffers)))
420
4fe3f297 421;;;###autoload (autoload 'ibuffer-do-replace-regexp "ibuf-ext")
25d2f683
CW
422(define-ibuffer-op replace-regexp (from-str to-str)
423 "Perform a `replace-regexp' in marked buffers."
424 (:interactive
425 (let* ((from-str (read-from-minibuffer "Replace regexp: "))
426 (to-str (read-from-minibuffer (concat "Replace " from-str
427 " with: "))))
428 (list from-str to-str))
429 :opstring "replaced in"
430 :complex t
431 :modifier-p :maybe)
432 (save-window-excursion
433 (switch-to-buffer buf)
434 (save-excursion
435 (goto-char (point-min))
436 (let ((case-fold-search ibuffer-case-fold-search))
437 (while (re-search-forward from-str nil t)
438 (replace-match to-str))))
439 t))
440
4fe3f297 441;;;###autoload (autoload 'ibuffer-do-query-replace "ibuf-ext")
25d2f683
CW
442(define-ibuffer-op query-replace (&rest args)
443 "Perform a `query-replace' in marked buffers."
444 (:interactive
193f8525 445 (query-replace-read-args "Query replace" t t)
25d2f683
CW
446 :opstring "replaced in"
447 :complex t
448 :modifier-p :maybe)
449 (save-window-excursion
450 (switch-to-buffer buf)
451 (save-excursion
452 (let ((case-fold-search ibuffer-case-fold-search))
453 (goto-char (point-min))
454 (apply #'query-replace args)))
455 t))
456
4fe3f297 457;;;###autoload (autoload 'ibuffer-do-query-replace-regexp "ibuf-ext")
25d2f683
CW
458(define-ibuffer-op query-replace-regexp (&rest args)
459 "Perform a `query-replace-regexp' in marked buffers."
460 (:interactive
193f8525 461 (query-replace-read-args "Query replace regexp" t t)
25d2f683
CW
462 :opstring "replaced in"
463 :complex t
464 :modifier-p :maybe)
465 (save-window-excursion
466 (switch-to-buffer buf)
467 (save-excursion
468 (let ((case-fold-search ibuffer-case-fold-search))
469 (goto-char (point-min))
470 (apply #'query-replace-regexp args)))
471 t))
472
4fe3f297 473;;;###autoload (autoload 'ibuffer-do-print "ibuf-ext")
25d2f683
CW
474(define-ibuffer-op print ()
475 "Print marked buffers as with `print-buffer'."
476 (:opstring "printed"
477 :modifier-p nil)
478 (print-buffer))
479
480;;;###autoload
481(defun ibuffer-included-in-filters-p (buf filters)
482 (not
483 (memq nil ;; a filter will return nil if it failed
484 (mapcar
485 ;; filter should be like (TYPE . QUALIFIER), or
486 ;; (or (TYPE . QUALIFIER) (TYPE . QUALIFIER) ...)
487 #'(lambda (qual)
488 (ibuffer-included-in-filter-p buf qual))
489 filters))))
490
491(defun ibuffer-included-in-filter-p (buf filter)
492 (if (eq (car filter) 'not)
493 (not (ibuffer-included-in-filter-p-1 buf (cdr filter)))
494 (ibuffer-included-in-filter-p-1 buf filter)))
495
496(defun ibuffer-included-in-filter-p-1 (buf filter)
497 (not
498 (not
499 (case (car filter)
500 (or
501 (memq t (mapcar #'(lambda (x)
502 (ibuffer-included-in-filter-p buf x))
503 (cdr filter))))
504 (saved
505 (let ((data
506 (assoc (cdr filter)
507 ibuffer-saved-filters)))
508 (unless data
509 (ibuffer-filter-disable)
510 (error "Unknown saved filter %s" (cdr filter)))
511 (ibuffer-included-in-filters-p buf (cadr data))))
512 (t
513 (let ((filterdat (assq (car filter)
514 ibuffer-filtering-alist)))
515 ;; filterdat should be like (TYPE DESCRIPTION FUNC)
516 ;; just a sanity check
517 (unless filterdat
518 (ibuffer-filter-disable)
519 (error "Undefined filter %s" (car filter)))
520 (not
521 (not
522 (funcall (caddr filterdat)
523 buf
524 (cdr filter))))))))))
525
fde057aa
RF
526(defun ibuffer-generate-filter-groups (bmarklist &optional noempty nodefault)
527 (let ((filter-group-alist (if nodefault
528 ibuffer-filter-groups
529 (append ibuffer-filter-groups
530 (list (cons "Default" nil))))))
05276226
CW
531;; (dolist (hidden ibuffer-hidden-filter-groups)
532;; (setq filter-group-alist (ibuffer-delete-alist
533;; hidden filter-group-alist)))
534 (let ((vec (make-vector (length filter-group-alist) nil))
365e1cfb 535 (i 0))
05276226 536 (dolist (filtergroup filter-group-alist)
365e1cfb
CW
537 (let ((filterset (cdr filtergroup)))
538 (multiple-value-bind (hip-crowd lamers)
d9351bbd 539 (values-list
f5fbd9ad
DG
540 (ibuffer-split-list (lambda (bufmark)
541 (ibuffer-included-in-filters-p (car bufmark)
542 filterset))
543 bmarklist))
365e1cfb
CW
544 (aset vec i hip-crowd)
545 (incf i)
546 (setq bmarklist lamers))))
fde057aa 547 (let (ret)
365e1cfb 548 (dotimes (j i ret)
fde057aa
RF
549 (let ((bufs (aref vec j)))
550 (unless (and noempty (null bufs))
551 (push (cons (car (nth j filter-group-alist))
552 bufs)
553 ret))))))))
365e1cfb
CW
554
555;;;###autoload
556(defun ibuffer-filters-to-filter-group (name)
557 "Make the current filters into a filtering group."
558 (interactive "sName for filtering group: ")
559 (when (null ibuffer-filtering-qualifiers)
560 (error "No filters in effect"))
05276226 561 (push (cons name ibuffer-filtering-qualifiers) ibuffer-filter-groups)
365e1cfb
CW
562 (ibuffer-filter-disable))
563
05276226
CW
564;;;###autoload
565(defun ibuffer-set-filter-groups-by-mode ()
566 "Set the current filter groups to filter by mode."
567 (interactive)
568 (setq ibuffer-filter-groups
b7f6c476
CW
569 (mapcar (lambda (mode)
570 (cons (format "%s" mode) `((mode . ,mode))))
571 (let ((modes
4ba16127 572 (ibuffer-remove-duplicates
20e192c0 573 (mapcar (lambda (buf)
95e8ab35 574 (buffer-local-value 'major-mode buf))
b7f6c476
CW
575 (buffer-list)))))
576 (if ibuffer-view-ibuffer
577 modes
578 (delq 'ibuffer-mode modes)))))
05276226
CW
579 (ibuffer-update nil t))
580
365e1cfb
CW
581;;;###autoload
582(defun ibuffer-pop-filter-group ()
f189891b 583 "Remove the first filter group."
365e1cfb 584 (interactive)
05276226 585 (when (null ibuffer-filter-groups)
f189891b 586 (error "No filter groups active"))
b7f6c476
CW
587 (setq ibuffer-hidden-filter-groups
588 (delete (pop ibuffer-filter-groups)
589 ibuffer-hidden-filter-groups))
05276226
CW
590 (ibuffer-update nil t))
591
f189891b
CW
592(defun ibuffer-read-filter-group-name (msg &optional nodefault noerror)
593 (when (and (not noerror) (null ibuffer-filter-groups))
594 (error "No filter groups active"))
fde057aa
RF
595 ;; `ibuffer-generate-filter-groups' returns all non-hidden filter
596 ;; groups, possibly excluding empty groups or Default.
597 ;; We add `ibuffer-hidden-filter-groups' to the list, excluding
598 ;; Default if necessary.
599 (completing-read msg (nconc
600 (ibuffer-generate-filter-groups
601 (ibuffer-current-state-list)
602 (not ibuffer-show-empty-filter-groups)
603 nodefault)
604 (if nodefault
605 (remove "Default" ibuffer-hidden-filter-groups)
606 ibuffer-hidden-filter-groups))
607 nil t))
f189891b
CW
608
609;;;###autoload
610(defun ibuffer-decompose-filter-group (group)
611 "Decompose the filter group GROUP into active filters."
20e192c0 612 (interactive
2bf1ab74 613 (list (ibuffer-read-filter-group-name "Decompose filter group: " t)))
f189891b
CW
614 (let ((data (cdr (assoc group ibuffer-filter-groups))))
615 (setq ibuffer-filter-groups (ibuffer-delete-alist
616 group ibuffer-filter-groups)
617 ibuffer-filtering-qualifiers data))
618 (ibuffer-update nil t))
619
05276226
CW
620;;;###autoload
621(defun ibuffer-clear-filter-groups ()
f189891b 622 "Remove all filter groups."
05276226 623 (interactive)
b7f6c476
CW
624 (setq ibuffer-filter-groups nil
625 ibuffer-hidden-filter-groups nil)
365e1cfb
CW
626 (ibuffer-update nil t))
627
05276226
CW
628(defun ibuffer-current-filter-groups-with-position ()
629 (save-excursion
630 (goto-char (point-min))
631 (let ((pos nil)
632 (result nil))
633 (while (and (not (eobp))
634 (setq pos (next-single-property-change
635 (point) 'ibuffer-filter-group-name)))
636 (goto-char pos)
637 (push (cons (get-text-property (point) 'ibuffer-filter-group-name)
638 pos)
639 result)
640 (goto-char (next-single-property-change
641 pos 'ibuffer-filter-group-name)))
642 (nreverse result))))
643
365e1cfb
CW
644;;;###autoload
645(defun ibuffer-jump-to-filter-group (name)
646 "Move point to the filter group whose name is NAME."
20e192c0 647 (interactive
2bf1ab74 648 (list (ibuffer-read-filter-group-name "Jump to filter group: ")))
f189891b
CW
649 (ibuffer-aif (assoc name (ibuffer-current-filter-groups-with-position))
650 (goto-char (cdr it))
651 (error "No filter group with name %s" name)))
365e1cfb 652
05276226
CW
653;;;###autoload
654(defun ibuffer-kill-filter-group (name)
f189891b 655 "Kill the filter group named NAME.
c56a4f1f 656The group will be added to `ibuffer-filter-group-kill-ring'."
f189891b 657 (interactive (list (ibuffer-read-filter-group-name "Kill filter group: " t)))
c56a4f1f 658 (when (equal name "Default")
f189891b 659 (error "Can't kill default filter group"))
05276226 660 (ibuffer-aif (assoc name ibuffer-filter-groups)
b7f6c476 661 (progn
c56a4f1f 662 (push (copy-tree it) ibuffer-filter-group-kill-ring)
b7f6c476
CW
663 (setq ibuffer-filter-groups (ibuffer-delete-alist
664 name ibuffer-filter-groups))
665 (setq ibuffer-hidden-filter-groups
b6cee494 666 (delete name ibuffer-hidden-filter-groups)))
05276226
CW
667 (error "No filter group with name \"%s\"" name))
668 (ibuffer-update nil t))
669
670;;;###autoload
818f3c45 671(defun ibuffer-kill-line (&optional arg interactive-p)
f189891b 672 "Kill the filter group at point.
c56a4f1f 673See also `ibuffer-kill-filter-group'."
818f3c45 674 (interactive "P\np")
05276226
CW
675 (ibuffer-aif (save-excursion
676 (ibuffer-forward-line 0)
677 (get-text-property (point) 'ibuffer-filter-group-name))
678 (progn
05276226 679 (ibuffer-kill-filter-group it))
818f3c45 680 (funcall (if interactive-p #'call-interactively #'funcall)
05276226
CW
681 #'kill-line arg)))
682
c56a4f1f 683(defun ibuffer-insert-filter-group-before (newgroup group)
4ba16127
JPW
684 (let* ((found nil)
685 (pos (let ((groups (mapcar #'car ibuffer-filter-groups))
686 (res 0))
687 (while groups
688 (if (equal (car groups) group)
689 (setq found t
690 groups nil)
691 (incf res)
692 (setq groups (cdr groups))))
693 res)))
694 (cond ((not found)
2bf1ab74
JPW
695 (setq ibuffer-filter-groups
696 (nconc ibuffer-filter-groups (list newgroup))))
4ba16127
JPW
697 ((zerop pos)
698 (push newgroup ibuffer-filter-groups))
c56a4f1f
CW
699 (t
700 (let ((cell (nthcdr pos ibuffer-filter-groups)))
701 (setf (cdr cell) (cons (car cell) (cdr cell)))
702 (setf (car cell) newgroup))))))
703
05276226 704;;;###autoload
c56a4f1f
CW
705(defun ibuffer-yank ()
706 "Yank the last killed filter group before group at point."
707 (interactive)
708 (ibuffer-yank-filter-group
709 (or (get-text-property (point) 'ibuffer-filter-group-name)
710 (get-text-property (point) 'ibuffer-filter-group)
711 (error "No filter group at point"))))
712
713;;;###autoload
714(defun ibuffer-yank-filter-group (name)
715 "Yank the last killed filter group before group named NAME."
36df86d8
JPW
716 (interactive (list (ibuffer-read-filter-group-name
717 "Yank filter group before group: ")))
718 (unless ibuffer-filter-group-kill-ring
719 (error "The Ibuffer filter group kill-ring is empty"))
05276226
CW
720 (save-excursion
721 (ibuffer-forward-line 0)
c56a4f1f
CW
722 (ibuffer-insert-filter-group-before (pop ibuffer-filter-group-kill-ring)
723 name))
05276226
CW
724 (ibuffer-update nil t))
725
726;;;###autoload
4e4a724c 727(defun ibuffer-save-filter-groups (name groups)
05276226
CW
728 "Save all active filter groups GROUPS as NAME.
729They are added to `ibuffer-saved-filter-groups'. Interactively,
730prompt for NAME, and use the current filters."
731 (interactive
732 (if (null ibuffer-filter-groups)
733 (error "No filter groups active")
734 (list
735 (read-from-minibuffer "Save current filter groups as: ")
736 ibuffer-filter-groups)))
737 (ibuffer-aif (assoc name ibuffer-saved-filter-groups)
738 (setcdr it groups)
fece59b8 739 (push (cons name groups) ibuffer-saved-filter-groups))
2f5ca70b 740 (ibuffer-maybe-save-stuff))
05276226
CW
741
742;;;###autoload
743(defun ibuffer-delete-saved-filter-groups (name)
744 "Delete saved filter groups with NAME.
745They are removed from `ibuffer-saved-filter-groups'."
746 (interactive
747 (list
748 (if (null ibuffer-saved-filter-groups)
b6cee494
CW
749 (error "No saved filter groups")
750 (completing-read "Delete saved filter group: "
05276226
CW
751 ibuffer-saved-filter-groups nil t))))
752 (setq ibuffer-saved-filter-groups
753 (ibuffer-delete-alist name ibuffer-saved-filter-groups))
754 (ibuffer-maybe-save-stuff)
755 (ibuffer-update nil t))
756
757;;;###autoload
758(defun ibuffer-switch-to-saved-filter-groups (name)
759 "Set this buffer's filter groups to saved version with NAME.
20e192c0 760The value from `ibuffer-saved-filter-groups' is used."
05276226
CW
761 (interactive
762 (list
763 (if (null ibuffer-saved-filter-groups)
764 (error "No saved filters")
765 (completing-read "Switch to saved filter group: "
766 ibuffer-saved-filter-groups nil t))))
b7f6c476
CW
767 (setq ibuffer-filter-groups (cdr (assoc name ibuffer-saved-filter-groups))
768 ibuffer-hidden-filter-groups nil)
05276226
CW
769 (ibuffer-update nil t))
770
25d2f683
CW
771;;;###autoload
772(defun ibuffer-filter-disable ()
773 "Disable all filters currently in effect in this buffer."
774 (interactive)
775 (setq ibuffer-filtering-qualifiers nil)
b8210c6e
JPW
776 (let ((buf (ibuffer-current-buffer)))
777 (ibuffer-update nil t)
778 (when buf
779 (ibuffer-jump-to-buffer (buffer-name buf)))))
25d2f683
CW
780
781;;;###autoload
782(defun ibuffer-pop-filter ()
783 "Remove the top filter in this buffer."
784 (interactive)
785 (when (null ibuffer-filtering-qualifiers)
786 (error "No filters in effect"))
787 (pop ibuffer-filtering-qualifiers)
b8210c6e
JPW
788 (let ((buf (ibuffer-current-buffer)))
789 (ibuffer-update nil t)
790 (when buf
791 (ibuffer-jump-to-buffer (buffer-name buf)))))
25d2f683
CW
792
793(defun ibuffer-push-filter (qualifier)
794 "Add QUALIFIER to `ibuffer-filtering-qualifiers'."
795 (push qualifier ibuffer-filtering-qualifiers))
796
797;;;###autoload
798(defun ibuffer-decompose-filter ()
799 "Separate the top compound filter (OR, NOT, or SAVED) in this buffer.
800
801This means that the topmost filter on the filtering stack, which must
802be a complex filter like (OR [name: foo] [mode: bar-mode]), will be
803turned into two separate filters [name: foo] and [mode: bar-mode]."
804 (interactive)
805 (when (null ibuffer-filtering-qualifiers)
4e4a724c 806 (error "No filters in effect"))
25d2f683
CW
807 (let ((lim (pop ibuffer-filtering-qualifiers)))
808 (case (car lim)
809 (or
810 (setq ibuffer-filtering-qualifiers (append
811 (cdr lim)
812 ibuffer-filtering-qualifiers)))
813 (saved
814 (let ((data
815 (assoc (cdr lim)
816 ibuffer-saved-filters)))
817 (unless data
818 (ibuffer-filter-disable)
819 (error "Unknown saved filter %s" (cdr lim)))
820 (setq ibuffer-filtering-qualifiers (append
821 (cadr data)
822 ibuffer-filtering-qualifiers))))
823 (not
824 (push (cdr lim)
825 ibuffer-filtering-qualifiers))
826 (t
827 (error "Filter type %s is not compound" (car lim)))))
828 (ibuffer-update nil t))
829
830;;;###autoload
831(defun ibuffer-exchange-filters ()
832 "Exchange the top two filters on the stack in this buffer."
833 (interactive)
834 (when (< (length ibuffer-filtering-qualifiers)
835 2)
836 (error "Need two filters to exchange"))
837 (let ((first (pop ibuffer-filtering-qualifiers))
838 (second (pop ibuffer-filtering-qualifiers)))
839 (push first ibuffer-filtering-qualifiers)
840 (push second ibuffer-filtering-qualifiers))
841 (ibuffer-update nil t))
842
843;;;###autoload
844(defun ibuffer-negate-filter ()
845 "Negate the sense of the top filter in the current buffer."
846 (interactive)
847 (when (null ibuffer-filtering-qualifiers)
848 (error "No filters in effect"))
849 (let ((lim (pop ibuffer-filtering-qualifiers)))
850 (push (if (eq (car lim) 'not)
851 (cdr lim)
852 (cons 'not lim))
853 ibuffer-filtering-qualifiers))
854 (ibuffer-update nil t))
855
856;;;###autoload
857(defun ibuffer-or-filter (&optional reverse)
858 "Replace the top two filters in this buffer with their logical OR.
859If optional argument REVERSE is non-nil, instead break the top OR
860filter into parts."
861 (interactive "P")
862 (if reverse
863 (progn
864 (when (or (null ibuffer-filtering-qualifiers)
865 (not (eq 'or (caar ibuffer-filtering-qualifiers))))
866 (error "Top filter is not an OR"))
867 (let ((lim (pop ibuffer-filtering-qualifiers)))
20e192c0 868 (setq ibuffer-filtering-qualifiers
2bf1ab74 869 (nconc (cdr lim) ibuffer-filtering-qualifiers))))
25d2f683
CW
870 (when (< (length ibuffer-filtering-qualifiers) 2)
871 (error "Need two filters to OR"))
872 ;; If the second filter is an OR, just add to it.
873 (let ((first (pop ibuffer-filtering-qualifiers))
874 (second (pop ibuffer-filtering-qualifiers)))
875 (if (eq 'or (car second))
2bf1ab74
JPW
876 (push (nconc (list 'or first) (cdr second))
877 ibuffer-filtering-qualifiers)
25d2f683
CW
878 (push (list 'or first second)
879 ibuffer-filtering-qualifiers))))
880 (ibuffer-update nil t))
881
05276226 882(defun ibuffer-maybe-save-stuff ()
25d2f683
CW
883 (when ibuffer-save-with-custom
884 (if (fboundp 'customize-save-variable)
885 (progn
886 (customize-save-variable 'ibuffer-saved-filters
05276226
CW
887 ibuffer-saved-filters)
888 (customize-save-variable 'ibuffer-saved-filter-groups
889 ibuffer-saved-filter-groups))
25d2f683
CW
890 (message "Not saved permanently: Customize not available"))))
891
892;;;###autoload
893(defun ibuffer-save-filters (name filters)
894 "Save FILTERS in this buffer with name NAME in `ibuffer-saved-filters'.
895Interactively, prompt for NAME, and use the current filters."
896 (interactive
897 (if (null ibuffer-filtering-qualifiers)
898 (error "No filters currently in effect")
899 (list
900 (read-from-minibuffer "Save current filters as: ")
901 ibuffer-filtering-qualifiers)))
902 (ibuffer-aif (assoc name ibuffer-saved-filters)
903 (setcdr it filters)
365e1cfb 904 (push (list name filters) ibuffer-saved-filters))
2f5ca70b 905 (ibuffer-maybe-save-stuff))
25d2f683
CW
906
907;;;###autoload
908(defun ibuffer-delete-saved-filters (name)
909 "Delete saved filters with NAME from `ibuffer-saved-filters'."
910 (interactive
911 (list
912 (if (null ibuffer-saved-filters)
913 (error "No saved filters")
914 (completing-read "Delete saved filters: "
915 ibuffer-saved-filters nil t))))
916 (setq ibuffer-saved-filters
917 (ibuffer-delete-alist name ibuffer-saved-filters))
05276226 918 (ibuffer-maybe-save-stuff)
25d2f683
CW
919 (ibuffer-update nil t))
920
921;;;###autoload
922(defun ibuffer-add-saved-filters (name)
923 "Add saved filters from `ibuffer-saved-filters' to this buffer's filters."
924 (interactive
925 (list
926 (if (null ibuffer-saved-filters)
927 (error "No saved filters")
928 (completing-read "Add saved filters: "
929 ibuffer-saved-filters nil t))))
930 (push (cons 'saved name) ibuffer-filtering-qualifiers)
931 (ibuffer-update nil t))
932
933;;;###autoload
934(defun ibuffer-switch-to-saved-filters (name)
20e192c0 935 "Set this buffer's filters to filters with NAME from `ibuffer-saved-filters'."
25d2f683
CW
936 (interactive
937 (list
938 (if (null ibuffer-saved-filters)
939 (error "No saved filters")
940 (completing-read "Switch to saved filters: "
941 ibuffer-saved-filters nil t))))
942 (setq ibuffer-filtering-qualifiers (list (cons 'saved name)))
943 (ibuffer-update nil t))
c1244745
CW
944
945(defun ibuffer-format-filter-group-data (filter)
946 (if (equal filter "Default")
947 ""
0aa1b02e
JPW
948 (concat "Filter:" (mapconcat #'ibuffer-format-qualifier
949 (cdr (assq filter ibuffer-filter-groups))
950 " "))))
71296446 951
25d2f683
CW
952(defun ibuffer-format-qualifier (qualifier)
953 (if (eq (car-safe qualifier) 'not)
954 (concat " [NOT" (ibuffer-format-qualifier-1 (cdr qualifier)) "]")
955 (ibuffer-format-qualifier-1 qualifier)))
956
957(defun ibuffer-format-qualifier-1 (qualifier)
958 (case (car qualifier)
959 (saved
960 (concat " [filter: " (cdr qualifier) "]"))
961 (or
962 (concat " [OR" (mapconcat #'ibuffer-format-qualifier
963 (cdr qualifier) "") "]"))
964 (t
965 (let ((type (assq (car qualifier) ibuffer-filtering-alist)))
966 (unless qualifier
967 (error "Ibuffer: bad qualifier %s" qualifier))
968 (concat " [" (cadr type) ": " (format "%s]" (cdr qualifier)))))))
71296446 969
0e1701c4
CW
970
971(defun ibuffer-list-buffer-modes ()
972 "Create an alist of buffer modes currently in use.
76f03778 973The list returned will be of the form (\"MODE-NAME\" . MODE-SYMBOL)."
0e1701c4
CW
974 (let ((bufs (buffer-list))
975 (modes)
976 (this-mode))
977 (while bufs
95e8ab35 978 (setq this-mode (buffer-local-value 'major-mode (car bufs))
0e1701c4 979 bufs (cdr bufs))
4e4a724c 980 (add-to-list
0e1701c4 981 'modes
4e4a724c 982 `(,(symbol-name this-mode) .
0e1701c4 983 ,this-mode)))
4e4a724c 984 modes))
0e1701c4
CW
985
986
25d2f683
CW
987;;; Extra operation definitions
988
4fe3f297 989;;;###autoload (autoload 'ibuffer-filter-by-mode "ibuf-ext")
4e4a724c 990(define-ibuffer-filter mode
25d2f683
CW
991 "Toggle current view to buffers with major mode QUALIFIER."
992 (:description "major mode"
993 :reader
994 (intern
995 (completing-read "Filter by major mode: " obarray
996 #'(lambda (e)
997 (string-match "-mode$"
998 (symbol-name e)))
999 t
1000 (let ((buf (ibuffer-current-buffer)))
1001 (if (and buf (buffer-live-p buf))
95e8ab35 1002 (symbol-name (buffer-local-value 'major-mode buf))
25d2f683 1003 "")))))
95e8ab35 1004 (eq qualifier (buffer-local-value 'major-mode buf)))
25d2f683 1005
4fe3f297 1006;;;###autoload (autoload 'ibuffer-filter-by-used-mode "ibuf-ext")
4e4a724c 1007(define-ibuffer-filter used-mode
0e1701c4
CW
1008 "Toggle current view to buffers with major mode QUALIFIER.
1009Called interactively, this function allows selection of modes
1010currently used by buffers."
1011 (:description "major mode in use"
1012 :reader
4e4a724c
JPW
1013 (intern
1014 (completing-read "Filter by major mode: "
0e1701c4
CW
1015 (ibuffer-list-buffer-modes)
1016 nil
1017 t
1018 (let ((buf (ibuffer-current-buffer)))
1019 (if (and buf (buffer-live-p buf))
95e8ab35
JPW
1020 (symbol-name (buffer-local-value
1021 'major-mode buf))
0e1701c4 1022 "")))))
95e8ab35 1023 (eq qualifier (buffer-local-value 'major-mode buf)))
0e1701c4 1024
4fe3f297 1025;;;###autoload (autoload 'ibuffer-filter-by-name "ibuf-ext")
4e4a724c 1026(define-ibuffer-filter name
25d2f683
CW
1027 "Toggle current view to buffers with name matching QUALIFIER."
1028 (:description "buffer name"
365e1cfb 1029 :reader (read-from-minibuffer "Filter by name (regexp): "))
25d2f683
CW
1030 (string-match qualifier (buffer-name buf)))
1031
4fe3f297 1032;;;###autoload (autoload 'ibuffer-filter-by-filename "ibuf-ext")
25d2f683
CW
1033(define-ibuffer-filter filename
1034 "Toggle current view to buffers with filename matching QUALIFIER."
1035 (:description "filename"
365e1cfb 1036 :reader (read-from-minibuffer "Filter by filename (regexp): "))
81f370da
JPW
1037 (ibuffer-awhen (buffer-local-value 'buffer-file-name buf)
1038 (string-match qualifier it)))
25d2f683 1039
4fe3f297 1040;;;###autoload (autoload 'ibuffer-filter-by-size-gt "ibuf-ext")
4e4a724c 1041(define-ibuffer-filter size-gt
25d2f683
CW
1042 "Toggle current view to buffers with size greater than QUALIFIER."
1043 (:description "size greater than"
1044 :reader
1045 (string-to-number (read-from-minibuffer "Filter by size greater than: ")))
1046 (> (with-current-buffer buf (buffer-size))
1047 qualifier))
1048
4fe3f297 1049;;;###autoload (autoload 'ibuffer-filter-by-size-lt "ibuf-ext")
4e4a724c 1050(define-ibuffer-filter size-lt
25d2f683
CW
1051 "Toggle current view to buffers with size less than QUALIFIER."
1052 (:description "size less than"
1053 :reader
1054 (string-to-number (read-from-minibuffer "Filter by size less than: ")))
1055 (< (with-current-buffer buf (buffer-size))
1056 qualifier))
365e1cfb 1057
4fe3f297 1058;;;###autoload (autoload 'ibuffer-filter-by-content "ibuf-ext")
25d2f683
CW
1059(define-ibuffer-filter content
1060 "Toggle current view to buffers whose contents match QUALIFIER."
1061 (:description "content"
365e1cfb 1062 :reader (read-from-minibuffer "Filter by content (regexp): "))
25d2f683
CW
1063 (with-current-buffer buf
1064 (save-excursion
1065 (goto-char (point-min))
1066 (re-search-forward qualifier nil t))))
1067
4fe3f297 1068;;;###autoload (autoload 'ibuffer-filter-by-predicate "ibuf-ext")
25d2f683
CW
1069(define-ibuffer-filter predicate
1070 "Toggle current view to buffers for which QUALIFIER returns non-nil."
1071 (:description "predicate"
365e1cfb 1072 :reader (read-minibuffer "Filter by predicate (form): "))
25d2f683
CW
1073 (with-current-buffer buf
1074 (eval qualifier)))
1075
1076;;; Sorting
1077
1078;;;###autoload
1079(defun ibuffer-toggle-sorting-mode ()
1080 "Toggle the current sorting mode.
13e14c51 1081Default sorting modes are:
25d2f683
CW
1082 Recency - the last time the buffer was viewed
1083 Name - the name of the buffer
1084 Major Mode - the name of the major mode of the buffer
1085 Size - the size of the buffer"
1086 (interactive)
13e14c51
CW
1087 (let ((modes (mapcar 'car ibuffer-sorting-functions-alist)))
1088 (add-to-list 'modes 'recency)
1089 (setq modes (sort modes 'string-lessp))
1915493b 1090 (let ((next (or (car-safe (cdr-safe (memq ibuffer-sorting-mode modes)))
13e14c51
CW
1091 (car modes))))
1092 (setq ibuffer-sorting-mode next)
1093 (message "Sorting by %s" next)))
25d2f683
CW
1094 (ibuffer-redisplay t))
1095
1096;;;###autoload
1097(defun ibuffer-invert-sorting ()
1098 "Toggle whether or not sorting is in reverse order."
1099 (interactive)
1100 (setq ibuffer-sorting-reversep (not ibuffer-sorting-reversep))
1101 (message "Sorting order %s"
1102 (if ibuffer-sorting-reversep
1103 "reversed"
1104 "normal"))
1105 (ibuffer-redisplay t))
1106
4fe3f297 1107;;;###autoload (autoload 'ibuffer-do-sort-by-major-mode "ibuf-ext")
25d2f683
CW
1108(define-ibuffer-sorter major-mode
1109 "Sort the buffers by major modes.
1110Ordering is lexicographic."
1111 (:description "major mode")
1112 (string-lessp (downcase
95e8ab35 1113 (symbol-name (buffer-local-value 'major-mode (car a))))
25d2f683 1114 (downcase
95e8ab35 1115 (symbol-name (buffer-local-value 'major-mode (car b))))))
25d2f683 1116
4fe3f297 1117;;;###autoload (autoload 'ibuffer-do-sort-by-mode-name "ibuf-ext")
10cf9a43
CW
1118(define-ibuffer-sorter mode-name
1119 "Sort the buffers by their mode name.
1120Ordering is lexicographic."
0fcbf8d6 1121 (:description "major mode name")
10cf9a43 1122 (string-lessp (downcase
95e8ab35
JPW
1123 (with-current-buffer
1124 (car a)
1125 (format-mode-line mode-name)))
10cf9a43 1126 (downcase
1915493b
CW
1127 (with-current-buffer
1128 (car b)
9dfee9c2 1129 (format-mode-line mode-name)))))
10cf9a43 1130
4fe3f297 1131;;;###autoload (autoload 'ibuffer-do-sort-by-alphabetic "ibuf-ext")
25d2f683
CW
1132(define-ibuffer-sorter alphabetic
1133 "Sort the buffers by their names.
1134Ordering is lexicographic."
1135 (:description "buffer name")
1136 (string-lessp
1137 (buffer-name (car a))
1138 (buffer-name (car b))))
1139
4fe3f297 1140;;;###autoload (autoload 'ibuffer-do-sort-by-size "ibuf-ext")
25d2f683
CW
1141(define-ibuffer-sorter size
1142 "Sort the buffers by their size."
1143 (:description "size")
1144 (< (with-current-buffer (car a)
1145 (buffer-size))
1146 (with-current-buffer (car b)
1147 (buffer-size))))
1148
d5794180
DN
1149;;;###autoload (autoload 'ibuffer-do-sort-by-filename/process "ibuf-ext")
1150(define-ibuffer-sorter filename/process
1151 "Sort the buffers by their file name/process name."
1152 (:description "file name")
1153 (string-lessp
1154 ;; FIXME: For now just compare the file name and the process name
1155 ;; (if it exists). Is there a better way to do this?
d9351bbd 1156 (or (buffer-file-name (car a))
d5794180
DN
1157 (let ((pr-a (get-buffer-process (car a))))
1158 (and (processp pr-a) (process-name pr-a))))
d9351bbd 1159 (or (buffer-file-name (car b))
d5794180
DN
1160 (let ((pr-b (get-buffer-process (car b))))
1161 (and (processp pr-b) (process-name pr-b))))))
1162
25d2f683
CW
1163;;; Functions to emulate bs.el
1164
1165;;;###autoload
1166(defun ibuffer-bs-show ()
1167 "Emulate `bs-show' from the bs.el package."
1168 (interactive)
1169 (ibuffer t "*Ibuffer-bs*" '((filename . ".*")) nil t)
1170 (define-key (current-local-map) "a" 'ibuffer-bs-toggle-all))
1171
1172(defun ibuffer-bs-toggle-all ()
1173 "Emulate `bs-toggle-show-all' from the bs.el package."
1174 (interactive)
1175 (if ibuffer-filtering-qualifiers
1176 (ibuffer-pop-filter)
1177 (progn (ibuffer-push-filter '(filename . ".*"))
1178 (ibuffer-update nil t))))
1179
1180;;; Handy functions
1181
1182;;;###autoload
1183(defun ibuffer-add-to-tmp-hide (regexp)
1184 "Add REGEXP to `ibuffer-tmp-hide-regexps'.
1185This means that buffers whose name matches REGEXP will not be shown
36a579c1 1186for this Ibuffer session."
25d2f683
CW
1187 (interactive
1188 (list
1189 (read-from-minibuffer "Never show buffers matching: "
1190 (regexp-quote (buffer-name (ibuffer-current-buffer t))))))
1191 (push regexp ibuffer-tmp-hide-regexps))
1192
1193;;;###autoload
1194(defun ibuffer-add-to-tmp-show (regexp)
1195 "Add REGEXP to `ibuffer-tmp-show-regexps'.
1196This means that buffers whose name matches REGEXP will always be shown
36a579c1 1197for this Ibuffer session."
25d2f683
CW
1198 (interactive
1199 (list
1200 (read-from-minibuffer "Always show buffers matching: "
1201 (regexp-quote (buffer-name (ibuffer-current-buffer t))))))
1202 (push regexp ibuffer-tmp-show-regexps))
1203
1204;;;###autoload
1205(defun ibuffer-forward-next-marked (&optional count mark direction)
1206 "Move forward by COUNT marked buffers (default 1).
1207
1208If MARK is non-nil, it should be a character denoting the type of mark
1209to move by. The default is `ibuffer-marked-char'.
1210
1211If DIRECTION is non-nil, it should be an integer; negative integers
1212mean move backwards, non-negative integers mean move forwards."
1213 (interactive "P")
1214 (unless count
1215 (setq count 1))
1216 (unless mark
1217 (setq mark ibuffer-marked-char))
1218 (unless direction
1219 (setq direction 1))
1220 ;; Skip the title
1221 (ibuffer-forward-line 0)
1222 (let ((opos (point))
1223 curmark)
1224 (ibuffer-forward-line direction)
1225 (while (not (or (= (point) opos)
1226 (eq (setq curmark (ibuffer-current-mark))
1227 mark)))
1228 (ibuffer-forward-line direction))
1229 (when (and (= (point) opos)
1230 (not (eq (ibuffer-current-mark) mark)))
1231 (error "No buffers with mark %c" mark))))
1232
1233;;;###autoload
1234(defun ibuffer-backwards-next-marked (&optional count mark)
1235 "Move backwards by COUNT marked buffers (default 1).
1236
1237If MARK is non-nil, it should be a character denoting the type of mark
1238to move by. The default is `ibuffer-marked-char'."
1239 (interactive "P")
1240 (ibuffer-forward-next-marked count mark -1))
1241
1242;;;###autoload
1243(defun ibuffer-do-kill-lines ()
1244 "Hide all of the currently marked lines."
1245 (interactive)
1246 (if (= (ibuffer-count-marked-lines) 0)
1247 (message "No buffers marked; use 'm' to mark a buffer")
1248 (let ((count
1249 (ibuffer-map-marked-lines
bde57911 1250 #'(lambda (buf mark)
25d2f683
CW
1251 'kill))))
1252 (message "Killed %s lines" count))))
1253
1254;;;###autoload
1255(defun ibuffer-jump-to-buffer (name)
0bdd7ae4
JPW
1256 "Move point to the buffer whose name is NAME.
1257
1258If called interactively, prompt for a buffer name and go to the
1259corresponding line in the Ibuffer buffer. If said buffer is in a
1260hidden group filter, open it.
1261
1262If `ibuffer-jump-offer-only-visible-buffers' is non-nil, only offer
1263visible buffers in the completion list. Calling the command with
1264a prefix argument reverses the meaning of that variable."
33a584e6
JPW
1265 (interactive (list
1266 (let ((only-visible ibuffer-jump-offer-only-visible-buffers))
1267 (when current-prefix-arg
1268 (setq only-visible (not only-visible)))
1269 (if only-visible
1270 (let ((table (mapcar #'(lambda (x)
1271 (buffer-name (car x)))
1272 (ibuffer-current-state-list))))
1273 (when (null table)
1274 (error "No buffers!"))
1275 (completing-read "Jump to buffer: "
1276 table nil t))
1277 (read-buffer "Jump to buffer: " nil t)))))
1278 (when (not (string= "" name))
1279 (let (buf-point)
1280 ;; Blindly search for our buffer: it is very likely that it is
1281 ;; not in a hidden filter group.
1282 (ibuffer-map-lines #'(lambda (buf marks)
1283 (when (string= (buffer-name buf) name)
1284 (setq buf-point (point))
1285 nil))
1286 t nil)
1287 (when (and
1288 (null buf-point)
1289 (not (null ibuffer-hidden-filter-groups)))
1290 ;; We did not find our buffer. It must be in a hidden filter
1291 ;; group, so go through all hidden filter groups to find it.
1292 (catch 'found
1293 (dolist (group ibuffer-hidden-filter-groups)
1294 (ibuffer-jump-to-filter-group group)
1295 (ibuffer-toggle-filter-group)
1296 (ibuffer-map-lines #'(lambda (buf marks)
1297 (when (string= (buffer-name buf) name)
1298 (setq buf-point (point))
1299 nil))
1300 t group)
1301 (if buf-point
1302 (throw 'found nil)
1303 (ibuffer-toggle-filter-group)))))
1304 (if (null buf-point)
1305 ;; Still not found even though we expanded all hidden filter
1306 ;; groups: that must be because it's hidden by predicate:
1307 ;; we won't bother trying to display it.
1308 (error "No buffer with name %s" name)
1309 (goto-char buf-point)))))
25d2f683 1310
cdc5b68f
JB
1311(declare-function diff-sentinel "diff" (code))
1312
c93addf5
JPW
1313(defun ibuffer-diff-buffer-with-file-1 (buffer)
1314 (let ((bufferfile (buffer-local-value 'buffer-file-name buffer))
1315 (tempfile (make-temp-file "buffer-content-")))
1316 (when bufferfile
1317 (unwind-protect
1318 (progn
1319 (with-current-buffer buffer
1320 (write-region nil nil tempfile nil 'nomessage))
1321 (let* ((old (expand-file-name bufferfile))
1322 (new (expand-file-name tempfile))
1323 (oldtmp (file-local-copy old))
1324 (newtmp (file-local-copy new))
1325 (switches diff-switches)
1326 (command
1327 (mapconcat
1328 'identity
1329 `(,diff-command
1330 ;; Use explicitly specified switches
1331 ,@(if (listp switches) switches (list switches))
1332 ,@(if (or old new)
2751961a 1333 (list "-L" (shell-quote-argument old)
c93addf5
JPW
1334 "-L" (shell-quote-argument
1335 (format "Buffer %s" (buffer-name buffer)))))
1336 ,(shell-quote-argument (or oldtmp old))
1337 ,(shell-quote-argument (or newtmp new)))
1338 " "))
1339 proc)
1340 (let ((inhibit-read-only t))
1341 (insert command "\n")
1342 (diff-sentinel
1343 (call-process shell-file-name nil
1344 (current-buffer) nil
1345 shell-command-switch command)))
1346 (insert "\n"))))
1347 (sit-for 0)
1348 (when (file-exists-p tempfile)
1349 (delete-file tempfile)))))
1350
25d2f683
CW
1351;;;###autoload
1352(defun ibuffer-diff-with-file ()
c93addf5
JPW
1353 "View the differences between marked buffers and their associated files.
1354If no buffers are marked, use buffer at point.
25d2f683
CW
1355This requires the external program \"diff\" to be in your `exec-path'."
1356 (interactive)
c93addf5
JPW
1357 (require 'diff)
1358 (let ((marked-bufs (ibuffer-get-marked-buffers)))
1359 (when (null marked-bufs)
1360 (setq marked-bufs (list (ibuffer-current-buffer t))))
1361 (with-current-buffer (get-buffer-create "*Ibuffer Diff*")
1362 (setq buffer-read-only nil)
1363 (buffer-disable-undo (current-buffer))
1364 (erase-buffer)
1365 (buffer-enable-undo (current-buffer))
1366 (diff-mode)
1367 (dolist (buf marked-bufs)
1368 (unless (buffer-live-p buf)
1369 (error "Buffer %s has been killed" buf))
1370 (ibuffer-diff-buffer-with-file-1 buf))
1371 (setq buffer-read-only t)))
1372 (switch-to-buffer "*Ibuffer Diff*"))
25d2f683
CW
1373
1374;;;###autoload
1375(defun ibuffer-copy-filename-as-kill (&optional arg)
1376 "Copy filenames of marked buffers into the kill ring.
4e4a724c 1377
25d2f683
CW
1378The names are separated by a space.
1379If a buffer has no filename, it is ignored.
25d2f683 1380
4e4a724c
JPW
1381With no prefix arg, use the filename sans its directory of each marked file.
1382With a zero prefix arg, use the complete filename of each marked file.
1383With \\[universal-argument], use the filename of each marked file relative
4837b516 1384to `ibuffer-default-directory' if non-nil, otherwise `default-directory'.
25d2f683 1385
4e4a724c
JPW
1386You can then feed the file name(s) to other commands with \\[yank]."
1387 (interactive "p")
1388 (if (zerop (ibuffer-count-marked-lines))
25d2f683
CW
1389 (message "No buffers marked; use 'm' to mark a buffer")
1390 (let ((ibuffer-copy-filename-as-kill-result "")
4e4a724c 1391 (type (cond ((zerop arg)
25d2f683 1392 'full)
4e4a724c
JPW
1393 ((= arg 4)
1394 'relative)
25d2f683
CW
1395 (t
1396 'name))))
1397 (ibuffer-map-marked-lines
bde57911 1398 #'(lambda (buf mark)
25d2f683
CW
1399 (setq ibuffer-copy-filename-as-kill-result
1400 (concat ibuffer-copy-filename-as-kill-result
1401 (let ((name (buffer-file-name buf)))
1402 (if name
1403 (case type
1404 (full
1405 name)
4e4a724c
JPW
1406 (relative
1407 (file-relative-name
1408 name (or ibuffer-default-directory
1409 default-directory)))
25d2f683
CW
1410 (t
1411 (file-name-nondirectory name)))
1412 ""))
1413 " "))))
4e4a724c 1414 (kill-new ibuffer-copy-filename-as-kill-result))))
25d2f683 1415
05276226 1416(defun ibuffer-mark-on-buffer (func &optional ibuffer-mark-on-buffer-mark group)
25d2f683
CW
1417 (let ((count
1418 (ibuffer-map-lines
bde57911 1419 #'(lambda (buf mark)
25d2f683 1420 (when (funcall func buf)
05276226
CW
1421 (ibuffer-set-mark-1 (or ibuffer-mark-on-buffer-mark
1422 ibuffer-marked-char))
1423 t))
1424 nil
1425 group)))
25d2f683 1426 (ibuffer-redisplay t)
c93addf5
JPW
1427 (unless (eq ibuffer-mark-on-buffer-mark ?\s)
1428 (message "Marked %s buffers" count))))
25d2f683
CW
1429
1430;;;###autoload
1431(defun ibuffer-mark-by-name-regexp (regexp)
1432 "Mark all buffers whose name matches REGEXP."
1433 (interactive "sMark by name (regexp): ")
1434 (ibuffer-mark-on-buffer
1435 #'(lambda (buf)
1436 (string-match regexp (buffer-name buf)))))
1437
1438;;;###autoload
1439(defun ibuffer-mark-by-mode-regexp (regexp)
1440 "Mark all buffers whose major mode matches REGEXP."
1441 (interactive "sMark by major mode (regexp): ")
1442 (ibuffer-mark-on-buffer
1443 #'(lambda (buf)
1444 (with-current-buffer buf
b5c49962 1445 (string-match regexp (format-mode-line mode-name nil nil buf))))))
25d2f683
CW
1446
1447;;;###autoload
1448(defun ibuffer-mark-by-file-name-regexp (regexp)
1449 "Mark all buffers whose file name matches REGEXP."
1450 (interactive "sMark by file name (regexp): ")
1451 (ibuffer-mark-on-buffer
1452 #'(lambda (buf)
1453 (let ((name (or (buffer-file-name buf)
1454 (with-current-buffer buf
1455 (and
1456 (boundp 'dired-directory)
1457 (stringp dired-directory)
1458 dired-directory)))))
1459 (when name
1460 (string-match regexp name))))))
1461
1462;;;###autoload
1463(defun ibuffer-mark-by-mode (mode)
1464 "Mark all buffers whose major mode equals MODE."
1465 (interactive
1466 (list (intern (completing-read "Mark by major mode: " obarray
1467 #'(lambda (e)
1468 ;; kind of a hack...
1469 (and (fboundp e)
1470 (string-match "-mode$"
1471 (symbol-name e))))
1472 t
1473 (let ((buf (ibuffer-current-buffer)))
1474 (if (and buf (buffer-live-p buf))
1475 (with-current-buffer buf
1476 (cons (symbol-name major-mode)
1477 0))
1478 ""))))))
1479 (ibuffer-mark-on-buffer
1480 #'(lambda (buf)
c93addf5 1481 (eq (buffer-local-value 'major-mode buf) mode))))
25d2f683
CW
1482
1483;;;###autoload
1484(defun ibuffer-mark-modified-buffers ()
1485 "Mark all modified buffers."
1486 (interactive)
1487 (ibuffer-mark-on-buffer
1488 #'(lambda (buf) (buffer-modified-p buf))))
1489
1490;;;###autoload
1491(defun ibuffer-mark-unsaved-buffers ()
1492 "Mark all modified buffers that have an associated file."
1493 (interactive)
1494 (ibuffer-mark-on-buffer
95e8ab35 1495 #'(lambda (buf) (and (buffer-local-value 'buffer-file-name buf)
25d2f683
CW
1496 (buffer-modified-p buf)))))
1497
1498;;;###autoload
1499(defun ibuffer-mark-dissociated-buffers ()
1500 "Mark all buffers whose associated file does not exist."
1501 (interactive)
1502 (ibuffer-mark-on-buffer
1503 #'(lambda (buf)
1504 (with-current-buffer buf
1505 (or
1506 (and buffer-file-name
1507 (not (file-exists-p buffer-file-name)))
1508 (and (eq major-mode 'dired-mode)
1509 (boundp 'dired-directory)
1510 (stringp dired-directory)
1511 (not (file-exists-p (file-name-directory dired-directory)))))))))
1512
1513;;;###autoload
1514(defun ibuffer-mark-help-buffers ()
1515 "Mark buffers like *Help*, *Apropos*, *Info*."
1516 (interactive)
1517 (ibuffer-mark-on-buffer
1518 #'(lambda (buf)
1519 (with-current-buffer buf
0fcbf8d6 1520 (memq major-mode ibuffer-help-buffer-modes)))))
25d2f683 1521
4b9ae390
JPW
1522;;;###autoload
1523(defun ibuffer-mark-compressed-file-buffers ()
1524 "Mark buffers whose associated file is compressed."
1525 (interactive)
1526 (ibuffer-mark-on-buffer
1527 #'(lambda (buf)
1528 (with-current-buffer buf
1529 (and buffer-file-name
1530 (string-match ibuffer-compressed-file-name-regexp
1531 buffer-file-name))))))
1532
25d2f683
CW
1533;;;###autoload
1534(defun ibuffer-mark-old-buffers ()
67de6223 1535 "Mark buffers which have not been viewed in `ibuffer-old-time' hours."
25d2f683
CW
1536 (interactive)
1537 (ibuffer-mark-on-buffer
1538 #'(lambda (buf)
1539 (with-current-buffer buf
1540 ;; hacked from midnight.el
1541 (when buffer-display-time
1542 (let* ((tm (current-time))
1543 (now (+ (* (float (ash 1 16)) (car tm))
1544 (float (cadr tm)) (* 0.0000001 (caddr tm))))
1545 (then (+ (* (float (ash 1 16))
1546 (car buffer-display-time))
1547 (float (cadr buffer-display-time))
1548 (* 0.0000001 (caddr buffer-display-time)))))
2c1bb3d3 1549 (> (- now then) (* 60 60 ibuffer-old-time))))))))
25d2f683
CW
1550
1551;;;###autoload
1552(defun ibuffer-mark-special-buffers ()
1553 "Mark all buffers whose name begins and ends with '*'."
1554 (interactive)
1555 (ibuffer-mark-on-buffer
1556 #'(lambda (buf) (string-match "^\\*.+\\*$"
1557 (buffer-name buf)))))
1558
1559;;;###autoload
1560(defun ibuffer-mark-read-only-buffers ()
1561 "Mark all read-only buffers."
1562 (interactive)
1563 (ibuffer-mark-on-buffer
95e8ab35 1564 #'(lambda (buf) (buffer-local-value 'buffer-read-only buf))))
25d2f683
CW
1565
1566;;;###autoload
1567(defun ibuffer-mark-dired-buffers ()
1568 "Mark all `dired' buffers."
1569 (interactive)
1570 (ibuffer-mark-on-buffer
95e8ab35 1571 #'(lambda (buf) (eq (buffer-local-value 'major-mode buf) 'dired-mode))))
25d2f683 1572
25d2f683
CW
1573;;;###autoload
1574(defun ibuffer-do-occur (regexp &optional nlines)
1575 "View lines which match REGEXP in all marked buffers.
1576Optional argument NLINES says how many lines of context to display: it
1577defaults to one."
365e1cfb 1578 (interactive (occur-read-primary-args))
25d2f683
CW
1579 (if (or (not (integerp nlines))
1580 (< nlines 0))
c33cdcc5 1581 (setq nlines 0))
25d2f683 1582 (when (zerop (ibuffer-count-marked-lines))
10cf9a43 1583 (ibuffer-set-mark ibuffer-marked-char))
25d2f683
CW
1584 (let ((ibuffer-do-occur-bufs nil))
1585 ;; Accumulate a list of marked buffers
1586 (ibuffer-map-marked-lines
bde57911 1587 #'(lambda (buf mark)
25d2f683 1588 (push buf ibuffer-do-occur-bufs)))
c06bd65e 1589 (occur-1 regexp nlines ibuffer-do-occur-bufs)))
25d2f683
CW
1590
1591(provide 'ibuf-ext)
1592
d9351bbd
GM
1593;; Local Variables:
1594;; generated-autoload-file: "ibuffer.el"
1595;; End:
1596
b5c49962 1597;; arch-tag: 9af21953-deda-4c30-b76d-f81d9128e76d
25d2f683 1598;;; ibuf-ext.el ends here