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