(outline-next-visible-heading):
[emacs.git] / lisp / ibuf-ext.el
blobc568f2c3fe729540df39395a81a4bc01b9a12320
1 ;;; ibuf-ext.el --- extensions for ibuffer
3 ;; Copyright (C) 2000, 2001, 2002 Free Software Foundation, Inc.
5 ;; Author: Colin Walters <walters@verbum.org>
6 ;; Maintainer: John Paul Wallington <jpw@shootybangbang.com>
7 ;; Created: 2 Dec 2001
8 ;; Keywords: buffer, convenience
10 ;; This file is part of GNU Emacs.
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.
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.
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.
27 ;;; Commentary:
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.
33 ;;; Code:
35 (require 'ibuffer)
37 (eval-when-compile
38 (require 'derived)
39 (require 'ibuf-macs)
40 (require 'cl))
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))
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)))
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)
65 (if (funcall ibuffer-split-list-fn ibuffer-split-list-elt)
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))))
71 (defcustom ibuffer-never-show-predicates nil
72 "A list of predicates (a regexp or function) for buffers not to display.
73 If a regexp, then it will be matched against the buffer's name.
74 If a function, it will be called with the buffer as an argument, and
75 should return non-nil if this buffer should not be shown."
76 :type '(repeat (choice regexp function))
77 :group 'ibuffer)
79 (defcustom ibuffer-always-show-predicates nil
80 "A list of predicates (a regexp or function) for buffers to always display.
81 If a regexp, then it will be matched against the buffer's name.
82 If a function, it will be called with the buffer as an argument, and
83 should return non-nil if this buffer should be shown.
84 Note that buffers matching one of these predicates will be shown
85 regardless of any active filters in this buffer."
86 :type '(repeat (choice regexp function))
87 :group 'ibuffer)
89 (defvar ibuffer-tmp-hide-regexps nil
90 "A list of regexps which should match buffer names to not show.")
92 (defvar ibuffer-tmp-show-regexps nil
93 "A list of regexps which should match buffer names to always show.")
95 (defvar ibuffer-auto-mode nil
96 "If non-nil, Ibuffer auto-mode should be enabled for this buffer.
97 Do not set this variable directly! Use the function
98 `ibuffer-auto-mode' instead.")
100 (defvar ibuffer-auto-buffers-changed nil)
102 (defcustom ibuffer-saved-filters '(("gnus"
103 ((or (mode . message-mode)
104 (mode . mail-mode)
105 (mode . gnus-group-mode)
106 (mode . gnus-summary-mode)
107 (mode . gnus-article-mode))))
108 ("programming"
109 ((or (mode . emacs-lisp-mode)
110 (mode . cperl-mode)
111 (mode . c-mode)
112 (mode . java-mode)
113 (mode . idl-mode)
114 (mode . lisp-mode)))))
116 "An alist of filter qualifiers to switch between.
118 This variable should look like ((\"STRING\" QUALIFIERS)
119 (\"STRING\" QUALIFIERS) ...), where
120 QUALIFIERS is a list of the same form as
121 `ibuffer-filtering-qualifiers'.
122 See 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)
128 (defvar ibuffer-filtering-qualifiers nil
129 "A list like (SYMBOL . QUALIFIER) which filters the current buffer list.
130 See also `ibuffer-filtering-alist'.")
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.
136 You most likely do not want to modify this variable directly; see
137 `define-ibuffer-filter'.
139 SYMBOL is the symbolic name of the filter. DESCRIPTION is used when
140 displaying information to the user. FUNCTION is given a buffer and
141 the value of the qualifier, and returns non-nil if and only if the
142 buffer should be displayed.")
144 (defcustom ibuffer-filter-format-alist nil
145 "An alist which has special formats used when a filter is active.
146 The contents of this variable should look like:
147 ((FILTER (FORMAT FORMAT ...)) (FILTER (FORMAT FORMAT ...)) ...)
149 For example, suppose that when you add a filter for buffers whose
150 major mode is `emacs-lisp-mode', you only want to see the mark and the
151 name of the buffer. You could accomplish that by adding:
152 (mode ((mark \" \" name)))
153 to this variable."
154 :type '(repeat (list :tag "Association" (symbol :tag "Filter")
155 (list :tag "Formats" (repeat (sexp :tag "Format")))))
156 :group 'ibuffer)
158 (defvar ibuffer-cached-filter-formats nil)
159 (defvar ibuffer-compiled-filter-formats nil)
161 (defvar ibuffer-filter-groups nil
162 "A list like ((\"NAME\" ((SYMBOL . QUALIFIER) ...) ...) which groups buffers.
163 The SYMBOL should be one from `ibuffer-filtering-alist'.
164 The QUALIFIER should be the same as QUALIFIER in
165 `ibuffer-filtering-qualifiers'.")
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)
172 (defcustom ibuffer-saved-filter-groups nil
174 "An alist of filtering groups to switch between.
176 This variable should look like ((\"STRING\" QUALIFIERS)
177 (\"STRING\" QUALIFIERS) ...), where
178 QUALIFIERS is a list of the same form as
179 `ibuffer-filtering-qualifiers'.
181 See also the variables `ibuffer-filter-groups',
182 `ibuffer-filtering-qualifiers', `ibuffer-filtering-alist', and the
183 functions `ibuffer-switch-to-saved-filter-group',
184 `ibuffer-save-filter-group'."
185 :type '(repeat sexp)
186 :group 'ibuffer)
188 (defvar ibuffer-hidden-filter-groups nil
189 "A list of filtering groups which are currently hidden.")
191 (defvar ibuffer-filter-group-kill-ring nil)
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"))
199 :group 'ibuffer)
201 (defcustom ibuffer-save-with-custom t
202 "If non-nil, then use Custom to save interactively changed variables.
203 Currently, this only applies to `ibuffer-saved-filters' and
204 `ibuffer-saved-filter-groups."
205 :type 'boolean
206 :group 'ibuffer)
208 (defun ibuffer-ext-visible-p (buf all &optional ibuffer-buf)
210 (ibuffer-buf-matches-predicates buf ibuffer-tmp-show-regexps)
211 (and (not
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
219 (and ibuffer-buf
220 (not (eq ibuffer-buf buf))))
222 (ibuffer-included-in-filters-p buf ibuffer-filtering-qualifiers)
223 (ibuffer-buf-matches-predicates buf ibuffer-always-show-predicates)))))
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))))
236 ;;;###autoload
237 (defun ibuffer-auto-mode (&optional arg)
238 "Toggle use of Ibuffer's auto-update facility.
239 With 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))
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))
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))
271 (ibuffer-push-filter (cons 'mode
272 (with-current-buffer buf
273 major-mode)))))
274 (ibuffer-update nil t))
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))))
284 ;;;###autoload
285 (defun ibuffer-toggle-filter-group ()
286 "Toggle the display status of the filter group on this line."
287 (interactive)
288 (ibuffer-toggle-filter-group-1 (point)))
290 (defun ibuffer-toggle-filter-group-1 (posn)
291 (let ((name (get-text-property posn 'ibuffer-filter-group-name)))
292 (unless (stringp name)
293 (error "No filtering group name present"))
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))
298 (ibuffer-update nil t)))
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))
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))
337 ;;;###autoload (autoload 'ibuffer-do-shell-command-pipe "ibuf-ext.el")
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*")))
347 ;;;###autoload (autoload 'ibuffer-do-shell-command-pipe-replace "ibuf-ext.el")
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)))
359 ;;;###autoload (autoload 'ibuffer-do-shell-command-file "ibuf-ext.el")
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))))))))))
372 ;;;###autoload (autoload 'ibuffer-do-eval "ibuf-ext.el")
373 (define-ibuffer-op eval (form)
374 "Evaluate FORM in each of the buffers.
375 Does 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))
382 ;;;###autoload (autoload 'ibuffer-do-view-and-eval "ibuf-ext.el")
383 (define-ibuffer-op view-and-eval (form)
384 "Evaluate FORM while displaying each of the marked buffers.
385 To 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))))
397 ;;;###autoload (autoload 'ibuffer-do-rename-uniquely "ibuf-ext.el")
398 (define-ibuffer-op rename-uniquely ()
399 "Rename marked buffers as with `rename-uniquely'."
400 (:opstring "renamed"
401 :modifier-p t)
402 (rename-uniquely))
404 ;;;###autoload (autoload 'ibuffer-do-revert "ibuf-ext.el")
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))
413 ;;;###autoload (autoload 'ibuffer-do-replace-regexp "ibuf-ext.el")
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))))
433 ;;;###autoload (autoload 'ibuffer-do-query-replace "ibuf-ext.el")
434 (define-ibuffer-op query-replace (&rest args)
435 "Perform a `query-replace' in marked buffers."
436 (:interactive
437 (query-replace-read-args "Query replace" t t)
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)))
449 ;;;###autoload (autoload 'ibuffer-do-query-replace-regexp "ibuf-ext.el")
450 (define-ibuffer-op query-replace-regexp (&rest args)
451 "Perform a `query-replace-regexp' in marked buffers."
452 (:interactive
453 (query-replace-read-args "Query replace regexp" t t)
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)))
465 ;;;###autoload (autoload 'ibuffer-do-print "ibuf-ext.el")
466 (define-ibuffer-op print ()
467 "Print marked buffers as with `print-buffer'."
468 (:opstring "printed"
469 :modifier-p nil)
470 (print-buffer))
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))))
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)))
488 (defun ibuffer-included-in-filter-p-1 (buf filter)
489 (not
490 (not
491 (case (car filter)
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))))
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)
516 (cdr filter))))))))))
518 (defun ibuffer-generate-filter-groups (bmarklist)
519 (let ((filter-group-alist (append ibuffer-filter-groups
520 (list (cons "Default" nil)))))
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))
525 (i 0))
526 (dolist (filtergroup filter-group-alist)
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)
538 (push (cons (car (nth j filter-group-alist))
539 (aref vec j))
540 ret))))))
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"))
548 (push (cons name ibuffer-filtering-qualifiers) ibuffer-filter-groups)
549 (ibuffer-filter-disable))
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
556 (mapcar (lambda (mode)
557 (cons (format "%s" mode) `((mode . ,mode))))
558 (let ((modes
559 (ibuffer-remove-duplicates
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)))))
565 (ibuffer-update nil t))
567 ;;;###autoload
568 (defun ibuffer-pop-filter-group ()
569 "Remove the first filter group."
570 (interactive)
571 (when (null ibuffer-filter-groups)
572 (error "No filter groups active"))
573 (setq ibuffer-hidden-filter-groups
574 (delete (pop ibuffer-filter-groups)
575 ibuffer-hidden-filter-groups))
576 (ibuffer-update nil t))
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)))
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))
597 ;;;###autoload
598 (defun ibuffer-clear-filter-groups ()
599 "Remove all filter groups."
600 (interactive)
601 (setq ibuffer-filter-groups nil
602 ibuffer-hidden-filter-groups nil)
603 (ibuffer-update nil t))
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))))
621 ;;;###autoload
622 (defun ibuffer-jump-to-filter-group (name)
623 "Move point to the filter group whose name is NAME."
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)))
629 ;;;###autoload
630 (defun ibuffer-kill-filter-group (name)
631 "Kill the filter group named NAME.
632 The group will be added to `ibuffer-filter-group-kill-ring'."
633 (interactive (list (ibuffer-read-filter-group-name "Kill filter group: " t)))
634 (when (equal name "Default")
635 (error "Can't kill default filter group"))
636 (ibuffer-aif (assoc name ibuffer-filter-groups)
637 (progn
638 (push (copy-tree it) ibuffer-filter-group-kill-ring)
639 (setq ibuffer-filter-groups (ibuffer-delete-alist
640 name ibuffer-filter-groups))
641 (setq ibuffer-hidden-filter-groups
642 (delete name ibuffer-hidden-filter-groups)))
643 (error "No filter group with name \"%s\"" name))
644 (ibuffer-update nil t))
646 ;;;###autoload
647 (defun ibuffer-kill-line (&optional arg)
648 "Kill the filter group at point.
649 See also `ibuffer-kill-filter-group'."
650 (interactive "P")
651 (ibuffer-aif (save-excursion
652 (ibuffer-forward-line 0)
653 (get-text-property (point) 'ibuffer-filter-group-name))
654 (progn
655 (ibuffer-kill-filter-group it))
656 (funcall (if (interactive-p) #'call-interactively #'funcall)
657 #'kill-line arg)))
659 (defun ibuffer-insert-filter-group-before (newgroup group)
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)
671 (setq ibuffer-filter-groups (nconc ibuffer-filter-groups (list newgroup))))
672 ((zerop pos)
673 (push newgroup ibuffer-filter-groups))
675 (let ((cell (nthcdr pos ibuffer-filter-groups)))
676 (setf (cdr cell) (cons (car cell) (cdr cell)))
677 (setf (car cell) newgroup))))))
679 ;;;###autoload
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"))))
688 ;;;###autoload
689 (defun ibuffer-yank-filter-group (name)
690 "Yank the last killed filter group before group named NAME."
691 (interactive (list (progn
692 (unless ibuffer-filter-group-kill-ring
693 (error "ibuffer-filter-group-kill-ring is empty"))
694 (ibuffer-read-filter-group-name
695 "Yank filter group before group: "))))
696 (save-excursion
697 (ibuffer-forward-line 0)
698 (ibuffer-insert-filter-group-before (pop ibuffer-filter-group-kill-ring)
699 name))
700 (ibuffer-update nil t))
702 ;;;###autoload
703 (defun ibuffer-save-filter-groups (name groups)
704 "Save all active filter groups GROUPS as NAME.
705 They are added to `ibuffer-saved-filter-groups'. Interactively,
706 prompt for NAME, and use the current filters."
707 (interactive
708 (if (null ibuffer-filter-groups)
709 (error "No filter groups active")
710 (list
711 (read-from-minibuffer "Save current filter groups as: ")
712 ibuffer-filter-groups)))
713 (ibuffer-aif (assoc name ibuffer-saved-filter-groups)
714 (setcdr it groups)
715 (push (cons name groups) ibuffer-saved-filter-groups))
716 (ibuffer-maybe-save-stuff)
717 (ibuffer-update-mode-name))
719 ;;;###autoload
720 (defun ibuffer-delete-saved-filter-groups (name)
721 "Delete saved filter groups with NAME.
722 They are removed from `ibuffer-saved-filter-groups'."
723 (interactive
724 (list
725 (if (null ibuffer-saved-filter-groups)
726 (error "No saved filter groups")
727 (completing-read "Delete saved filter group: "
728 ibuffer-saved-filter-groups nil t))))
729 (setq ibuffer-saved-filter-groups
730 (ibuffer-delete-alist name ibuffer-saved-filter-groups))
731 (ibuffer-maybe-save-stuff)
732 (ibuffer-update nil t))
734 ;;;###autoload
735 (defun ibuffer-switch-to-saved-filter-groups (name)
736 "Set this buffer's filter groups to saved version with NAME.
737 The value from `ibuffer-saved-filters' is used.
738 If prefix argument ADD is non-nil, then add the saved filters instead
739 of replacing the current filters."
740 (interactive
741 (list
742 (if (null ibuffer-saved-filter-groups)
743 (error "No saved filters")
744 (completing-read "Switch to saved filter group: "
745 ibuffer-saved-filter-groups nil t))))
746 (setq ibuffer-filter-groups (cdr (assoc name ibuffer-saved-filter-groups))
747 ibuffer-hidden-filter-groups nil)
748 (ibuffer-update nil t))
750 ;;;###autoload
751 (defun ibuffer-filter-disable ()
752 "Disable all filters currently in effect in this buffer."
753 (interactive)
754 (setq ibuffer-filtering-qualifiers nil)
755 (ibuffer-update nil t))
757 ;;;###autoload
758 (defun ibuffer-pop-filter ()
759 "Remove the top filter in this buffer."
760 (interactive)
761 (when (null ibuffer-filtering-qualifiers)
762 (error "No filters in effect"))
763 (pop ibuffer-filtering-qualifiers)
764 (ibuffer-update nil t))
766 (defun ibuffer-push-filter (qualifier)
767 "Add QUALIFIER to `ibuffer-filtering-qualifiers'."
768 (push qualifier ibuffer-filtering-qualifiers))
770 ;;;###autoload
771 (defun ibuffer-decompose-filter ()
772 "Separate the top compound filter (OR, NOT, or SAVED) in this buffer.
774 This means that the topmost filter on the filtering stack, which must
775 be a complex filter like (OR [name: foo] [mode: bar-mode]), will be
776 turned into two separate filters [name: foo] and [mode: bar-mode]."
777 (interactive)
778 (when (null ibuffer-filtering-qualifiers)
779 (error "No filters in effect"))
780 (let ((lim (pop ibuffer-filtering-qualifiers)))
781 (case (car lim)
783 (setq ibuffer-filtering-qualifiers (append
784 (cdr lim)
785 ibuffer-filtering-qualifiers)))
786 (saved
787 (let ((data
788 (assoc (cdr lim)
789 ibuffer-saved-filters)))
790 (unless data
791 (ibuffer-filter-disable)
792 (error "Unknown saved filter %s" (cdr lim)))
793 (setq ibuffer-filtering-qualifiers (append
794 (cadr data)
795 ibuffer-filtering-qualifiers))))
796 (not
797 (push (cdr lim)
798 ibuffer-filtering-qualifiers))
800 (error "Filter type %s is not compound" (car lim)))))
801 (ibuffer-update nil t))
803 ;;;###autoload
804 (defun ibuffer-exchange-filters ()
805 "Exchange the top two filters on the stack in this buffer."
806 (interactive)
807 (when (< (length ibuffer-filtering-qualifiers)
809 (error "Need two filters to exchange"))
810 (let ((first (pop ibuffer-filtering-qualifiers))
811 (second (pop ibuffer-filtering-qualifiers)))
812 (push first ibuffer-filtering-qualifiers)
813 (push second ibuffer-filtering-qualifiers))
814 (ibuffer-update nil t))
816 ;;;###autoload
817 (defun ibuffer-negate-filter ()
818 "Negate the sense of the top filter in the current buffer."
819 (interactive)
820 (when (null ibuffer-filtering-qualifiers)
821 (error "No filters in effect"))
822 (let ((lim (pop ibuffer-filtering-qualifiers)))
823 (push (if (eq (car lim) 'not)
824 (cdr lim)
825 (cons 'not lim))
826 ibuffer-filtering-qualifiers))
827 (ibuffer-update nil t))
829 ;;;###autoload
830 (defun ibuffer-or-filter (&optional reverse)
831 "Replace the top two filters in this buffer with their logical OR.
832 If optional argument REVERSE is non-nil, instead break the top OR
833 filter into parts."
834 (interactive "P")
835 (if reverse
836 (progn
837 (when (or (null ibuffer-filtering-qualifiers)
838 (not (eq 'or (caar ibuffer-filtering-qualifiers))))
839 (error "Top filter is not an OR"))
840 (let ((lim (pop ibuffer-filtering-qualifiers)))
841 (setq ibuffer-filtering-qualifiers (nconc (cdr lim) ibuffer-filtering-qualifiers))))
842 (when (< (length ibuffer-filtering-qualifiers) 2)
843 (error "Need two filters to OR"))
844 ;; If the second filter is an OR, just add to it.
845 (let ((first (pop ibuffer-filtering-qualifiers))
846 (second (pop ibuffer-filtering-qualifiers)))
847 (if (eq 'or (car second))
848 (push (nconc (list 'or first) (cdr second)) ibuffer-filtering-qualifiers)
849 (push (list 'or first second)
850 ibuffer-filtering-qualifiers))))
851 (ibuffer-update nil t))
853 (defun ibuffer-maybe-save-stuff ()
854 (when ibuffer-save-with-custom
855 (if (fboundp 'customize-save-variable)
856 (progn
857 (customize-save-variable 'ibuffer-saved-filters
858 ibuffer-saved-filters)
859 (customize-save-variable 'ibuffer-saved-filter-groups
860 ibuffer-saved-filter-groups))
861 (message "Not saved permanently: Customize not available"))))
863 ;;;###autoload
864 (defun ibuffer-save-filters (name filters)
865 "Save FILTERS in this buffer with name NAME in `ibuffer-saved-filters'.
866 Interactively, prompt for NAME, and use the current filters."
867 (interactive
868 (if (null ibuffer-filtering-qualifiers)
869 (error "No filters currently in effect")
870 (list
871 (read-from-minibuffer "Save current filters as: ")
872 ibuffer-filtering-qualifiers)))
873 (ibuffer-aif (assoc name ibuffer-saved-filters)
874 (setcdr it filters)
875 (push (list name filters) ibuffer-saved-filters))
876 (ibuffer-maybe-save-stuff)
877 (ibuffer-update-mode-name))
879 ;;;###autoload
880 (defun ibuffer-delete-saved-filters (name)
881 "Delete saved filters with NAME from `ibuffer-saved-filters'."
882 (interactive
883 (list
884 (if (null ibuffer-saved-filters)
885 (error "No saved filters")
886 (completing-read "Delete saved filters: "
887 ibuffer-saved-filters nil t))))
888 (setq ibuffer-saved-filters
889 (ibuffer-delete-alist name ibuffer-saved-filters))
890 (ibuffer-maybe-save-stuff)
891 (ibuffer-update nil t))
893 ;;;###autoload
894 (defun ibuffer-add-saved-filters (name)
895 "Add saved filters from `ibuffer-saved-filters' to this buffer's filters."
896 (interactive
897 (list
898 (if (null ibuffer-saved-filters)
899 (error "No saved filters")
900 (completing-read "Add saved filters: "
901 ibuffer-saved-filters nil t))))
902 (push (cons 'saved name) ibuffer-filtering-qualifiers)
903 (ibuffer-update nil t))
905 ;;;###autoload
906 (defun ibuffer-switch-to-saved-filters (name)
907 "Set this buffer's filters to filters with NAME from `ibuffer-saved-filters'.
908 If prefix argument ADD is non-nil, then add the saved filters instead
909 of replacing the current filters."
910 (interactive
911 (list
912 (if (null ibuffer-saved-filters)
913 (error "No saved filters")
914 (completing-read "Switch to saved filters: "
915 ibuffer-saved-filters nil t))))
916 (setq ibuffer-filtering-qualifiers (list (cons 'saved name)))
917 (ibuffer-update nil t))
919 (defun ibuffer-format-filter-group-data (filter)
920 (if (equal filter "Default")
922 (concat "Filter: " (mapconcat #'ibuffer-format-qualifier
923 (cdr (assq filter ibuffer-filter-groups))
924 " ") "\n")))
926 (defun ibuffer-format-qualifier (qualifier)
927 (if (eq (car-safe qualifier) 'not)
928 (concat " [NOT" (ibuffer-format-qualifier-1 (cdr qualifier)) "]")
929 (ibuffer-format-qualifier-1 qualifier)))
931 (defun ibuffer-format-qualifier-1 (qualifier)
932 (case (car qualifier)
933 (saved
934 (concat " [filter: " (cdr qualifier) "]"))
936 (concat " [OR" (mapconcat #'ibuffer-format-qualifier
937 (cdr qualifier) "") "]"))
939 (let ((type (assq (car qualifier) ibuffer-filtering-alist)))
940 (unless qualifier
941 (error "Ibuffer: bad qualifier %s" qualifier))
942 (concat " [" (cadr type) ": " (format "%s]" (cdr qualifier)))))))
945 (defun ibuffer-list-buffer-modes ()
946 "Create an alist of buffer modes currently in use.
947 The list returned will be of the form (\"MODE-NAME\" . MODE-SYMBOL)."
948 (let ((bufs (buffer-list))
949 (modes)
950 (this-mode))
951 (while bufs
952 (setq this-mode
953 (with-current-buffer
954 (car bufs)
955 major-mode)
956 bufs (cdr bufs))
957 (add-to-list
958 'modes
959 `(,(symbol-name this-mode) .
960 ,this-mode)))
961 modes))
964 ;;; Extra operation definitions
966 ;;;###autoload (autoload 'ibuffer-filter-by-mode "ibuf-ext.el")
967 (define-ibuffer-filter mode
968 "Toggle current view to buffers with major mode QUALIFIER."
969 (:description "major mode"
970 :reader
971 (intern
972 (completing-read "Filter by major mode: " obarray
973 #'(lambda (e)
974 (string-match "-mode$"
975 (symbol-name e)))
977 (let ((buf (ibuffer-current-buffer)))
978 (if (and buf (buffer-live-p buf))
979 (with-current-buffer buf
980 (symbol-name major-mode))
981 "")))))
982 (eq qualifier (with-current-buffer buf major-mode)))
984 ;;;###autoload (autoload 'ibuffer-filter-by-used-mode "ibuf-ext.el")
985 (define-ibuffer-filter used-mode
986 "Toggle current view to buffers with major mode QUALIFIER.
987 Called interactively, this function allows selection of modes
988 currently used by buffers."
989 (:description "major mode in use"
990 :reader
991 (intern
992 (completing-read "Filter by major mode: "
993 (ibuffer-list-buffer-modes)
996 (let ((buf (ibuffer-current-buffer)))
997 (if (and buf (buffer-live-p buf))
998 (with-current-buffer buf
999 (symbol-name major-mode))
1000 "")))))
1001 (eq qualifier (with-current-buffer buf major-mode)))
1003 ;;;###autoload (autoload 'ibuffer-filter-by-name "ibuf-ext.el")
1004 (define-ibuffer-filter name
1005 "Toggle current view to buffers with name matching QUALIFIER."
1006 (:description "buffer name"
1007 :reader (read-from-minibuffer "Filter by name (regexp): "))
1008 (string-match qualifier (buffer-name buf)))
1010 ;;;###autoload (autoload 'ibuffer-filter-by-filename "ibuf-ext.el")
1011 (define-ibuffer-filter filename
1012 "Toggle current view to buffers with filename matching QUALIFIER."
1013 (:description "filename"
1014 :reader (read-from-minibuffer "Filter by filename (regexp): "))
1015 (ibuffer-awhen (buffer-file-name buf)
1016 (string-match qualifier it)))
1018 ;;;###autoload (autoload 'ibuffer-filter-by-size-gt "ibuf-ext.el")
1019 (define-ibuffer-filter size-gt
1020 "Toggle current view to buffers with size greater than QUALIFIER."
1021 (:description "size greater than"
1022 :reader
1023 (string-to-number (read-from-minibuffer "Filter by size greater than: ")))
1024 (> (with-current-buffer buf (buffer-size))
1025 qualifier))
1027 ;;;###autoload (autoload 'ibuffer-filter-by-size-lt "ibuf-ext.el")
1028 (define-ibuffer-filter size-lt
1029 "Toggle current view to buffers with size less than QUALIFIER."
1030 (:description "size less than"
1031 :reader
1032 (string-to-number (read-from-minibuffer "Filter by size less than: ")))
1033 (< (with-current-buffer buf (buffer-size))
1034 qualifier))
1036 ;;;###autoload (autoload 'ibuffer-filter-by-content "ibuf-ext.el")
1037 (define-ibuffer-filter content
1038 "Toggle current view to buffers whose contents match QUALIFIER."
1039 (:description "content"
1040 :reader (read-from-minibuffer "Filter by content (regexp): "))
1041 (with-current-buffer buf
1042 (save-excursion
1043 (goto-char (point-min))
1044 (re-search-forward qualifier nil t))))
1046 ;;;###autoload (autoload 'ibuffer-filter-by-predicate "ibuf-ext.el")
1047 (define-ibuffer-filter predicate
1048 "Toggle current view to buffers for which QUALIFIER returns non-nil."
1049 (:description "predicate"
1050 :reader (read-minibuffer "Filter by predicate (form): "))
1051 (with-current-buffer buf
1052 (eval qualifier)))
1054 ;;; Sorting
1056 ;;;###autoload
1057 (defun ibuffer-toggle-sorting-mode ()
1058 "Toggle the current sorting mode.
1059 Default sorting modes are:
1060 Recency - the last time the buffer was viewed
1061 Name - the name of the buffer
1062 Major Mode - the name of the major mode of the buffer
1063 Size - the size of the buffer"
1064 (interactive)
1065 (let ((modes (mapcar 'car ibuffer-sorting-functions-alist)))
1066 (add-to-list 'modes 'recency)
1067 (setq modes (sort modes 'string-lessp))
1068 (let ((next (or (car-safe (cdr-safe (memq ibuffer-sorting-mode modes)))
1069 (car modes))))
1070 (setq ibuffer-sorting-mode next)
1071 (message "Sorting by %s" next)))
1072 (ibuffer-redisplay t))
1074 ;;;###autoload
1075 (defun ibuffer-invert-sorting ()
1076 "Toggle whether or not sorting is in reverse order."
1077 (interactive)
1078 (setq ibuffer-sorting-reversep (not ibuffer-sorting-reversep))
1079 (message "Sorting order %s"
1080 (if ibuffer-sorting-reversep
1081 "reversed"
1082 "normal"))
1083 (ibuffer-redisplay t))
1085 ;;;###autoload (autoload 'ibuffer-do-sort-by-major-mode "ibuf-ext.el")
1086 (define-ibuffer-sorter major-mode
1087 "Sort the buffers by major modes.
1088 Ordering is lexicographic."
1089 (:description "major mode")
1090 (string-lessp (downcase
1091 (symbol-name (with-current-buffer
1092 (car a)
1093 major-mode)))
1094 (downcase
1095 (symbol-name (with-current-buffer
1096 (car b)
1097 major-mode)))))
1099 ;;;###autoload (autoload 'ibuffer-do-sort-by-mode-name "ibuf-ext.el")
1100 (define-ibuffer-sorter mode-name
1101 "Sort the buffers by their mode name.
1102 Ordering is lexicographic."
1103 (:description "major mode name")
1104 (string-lessp (downcase
1105 (with-current-buffer
1106 (car a)
1107 mode-name))
1108 (downcase
1109 (with-current-buffer
1110 (car b)
1111 mode-name))))
1113 ;;;###autoload (autoload 'ibuffer-do-sort-by-alphabetic "ibuf-ext.el")
1114 (define-ibuffer-sorter alphabetic
1115 "Sort the buffers by their names.
1116 Ordering is lexicographic."
1117 (:description "buffer name")
1118 (string-lessp
1119 (buffer-name (car a))
1120 (buffer-name (car b))))
1122 ;;;###autoload (autoload 'ibuffer-do-sort-by-size "ibuf-ext.el")
1123 (define-ibuffer-sorter size
1124 "Sort the buffers by their size."
1125 (:description "size")
1126 (< (with-current-buffer (car a)
1127 (buffer-size))
1128 (with-current-buffer (car b)
1129 (buffer-size))))
1131 ;;; Functions to emulate bs.el
1133 ;;;###autoload
1134 (defun ibuffer-bs-show ()
1135 "Emulate `bs-show' from the bs.el package."
1136 (interactive)
1137 (ibuffer t "*Ibuffer-bs*" '((filename . ".*")) nil t)
1138 (define-key (current-local-map) "a" 'ibuffer-bs-toggle-all))
1140 (defun ibuffer-bs-toggle-all ()
1141 "Emulate `bs-toggle-show-all' from the bs.el package."
1142 (interactive)
1143 (if ibuffer-filtering-qualifiers
1144 (ibuffer-pop-filter)
1145 (progn (ibuffer-push-filter '(filename . ".*"))
1146 (ibuffer-update nil t))))
1148 ;;; Handy functions
1150 ;;;###autoload
1151 (defun ibuffer-add-to-tmp-hide (regexp)
1152 "Add REGEXP to `ibuffer-tmp-hide-regexps'.
1153 This means that buffers whose name matches REGEXP will not be shown
1154 for this ibuffer session."
1155 (interactive
1156 (list
1157 (read-from-minibuffer "Never show buffers matching: "
1158 (regexp-quote (buffer-name (ibuffer-current-buffer t))))))
1159 (push regexp ibuffer-tmp-hide-regexps))
1161 ;;;###autoload
1162 (defun ibuffer-add-to-tmp-show (regexp)
1163 "Add REGEXP to `ibuffer-tmp-show-regexps'.
1164 This means that buffers whose name matches REGEXP will always be shown
1165 for this ibuffer session."
1166 (interactive
1167 (list
1168 (read-from-minibuffer "Always show buffers matching: "
1169 (regexp-quote (buffer-name (ibuffer-current-buffer t))))))
1170 (push regexp ibuffer-tmp-show-regexps))
1172 ;;;###autoload
1173 (defun ibuffer-forward-next-marked (&optional count mark direction)
1174 "Move forward by COUNT marked buffers (default 1).
1176 If MARK is non-nil, it should be a character denoting the type of mark
1177 to move by. The default is `ibuffer-marked-char'.
1179 If DIRECTION is non-nil, it should be an integer; negative integers
1180 mean move backwards, non-negative integers mean move forwards."
1181 (interactive "P")
1182 (unless count
1183 (setq count 1))
1184 (unless mark
1185 (setq mark ibuffer-marked-char))
1186 (unless direction
1187 (setq direction 1))
1188 ;; Skip the title
1189 (ibuffer-forward-line 0)
1190 (let ((opos (point))
1191 curmark)
1192 (ibuffer-forward-line direction)
1193 (while (not (or (= (point) opos)
1194 (eq (setq curmark (ibuffer-current-mark))
1195 mark)))
1196 (ibuffer-forward-line direction))
1197 (when (and (= (point) opos)
1198 (not (eq (ibuffer-current-mark) mark)))
1199 (error "No buffers with mark %c" mark))))
1201 ;;;###autoload
1202 (defun ibuffer-backwards-next-marked (&optional count mark)
1203 "Move backwards by COUNT marked buffers (default 1).
1205 If MARK is non-nil, it should be a character denoting the type of mark
1206 to move by. The default is `ibuffer-marked-char'."
1207 (interactive "P")
1208 (ibuffer-forward-next-marked count mark -1))
1210 ;;;###autoload
1211 (defun ibuffer-do-kill-lines ()
1212 "Hide all of the currently marked lines."
1213 (interactive)
1214 (if (= (ibuffer-count-marked-lines) 0)
1215 (message "No buffers marked; use 'm' to mark a buffer")
1216 (let ((count
1217 (ibuffer-map-marked-lines
1218 #'(lambda (buf mark)
1219 'kill))))
1220 (message "Killed %s lines" count))))
1222 ;;;###autoload
1223 (defun ibuffer-jump-to-buffer (name)
1224 "Move point to the buffer whose name is NAME."
1225 (interactive (list nil))
1226 (let ((table (mapcar #'(lambda (x)
1227 (cons (buffer-name (car x))
1228 (caddr x)))
1229 (ibuffer-current-state-list t))))
1230 (when (null table)
1231 (error "No buffers!"))
1232 (when (interactive-p)
1233 (setq name (completing-read "Jump to buffer: " table nil t)))
1234 (ibuffer-aif (assoc name table)
1235 (goto-char (cdr it))
1236 (error "No buffer with name %s" name))))
1238 ;;;###autoload
1239 (defun ibuffer-diff-with-file ()
1240 "View the differences between this buffer and its associated file.
1241 This requires the external program \"diff\" to be in your `exec-path'."
1242 (interactive)
1243 (let ((buf (ibuffer-current-buffer)))
1244 (unless (buffer-live-p buf)
1245 (error "Buffer %s has been killed" buf))
1246 (diff-buffer-with-file buf)))
1248 ;;;###autoload
1249 (defun ibuffer-copy-filename-as-kill (&optional arg)
1250 "Copy filenames of marked buffers into the kill ring.
1251 The names are separated by a space.
1252 If a buffer has no filename, it is ignored.
1253 With a zero prefix arg, use the complete pathname of each marked file.
1255 You can then feed the file name(s) to other commands with C-y.
1257 [ This docstring shamelessly stolen from the
1258 `dired-copy-filename-as-kill' in \"dired-x\". ]"
1259 ;; Add to docstring later:
1260 ;; With C-u, use the relative pathname of each marked file.
1261 (interactive "P")
1262 (if (= (ibuffer-count-marked-lines) 0)
1263 (message "No buffers marked; use 'm' to mark a buffer")
1264 (let ((ibuffer-copy-filename-as-kill-result "")
1265 (type (cond ((eql arg 0)
1266 'full)
1267 ;; ((eql arg 4)
1268 ;; 'relative)
1270 'name))))
1271 (ibuffer-map-marked-lines
1272 #'(lambda (buf mark)
1273 (setq ibuffer-copy-filename-as-kill-result
1274 (concat ibuffer-copy-filename-as-kill-result
1275 (let ((name (buffer-file-name buf)))
1276 (if name
1277 (case type
1278 (full
1279 name)
1281 (file-name-nondirectory name)))
1282 ""))
1283 " "))))
1284 (push ibuffer-copy-filename-as-kill-result kill-ring))))
1286 (defun ibuffer-mark-on-buffer (func &optional ibuffer-mark-on-buffer-mark group)
1287 (let ((count
1288 (ibuffer-map-lines
1289 #'(lambda (buf mark)
1290 (when (funcall func buf)
1291 (ibuffer-set-mark-1 (or ibuffer-mark-on-buffer-mark
1292 ibuffer-marked-char))
1295 group)))
1296 (ibuffer-redisplay t)
1297 (message "Marked %s buffers" count)))
1299 ;;;###autoload
1300 (defun ibuffer-mark-by-name-regexp (regexp)
1301 "Mark all buffers whose name matches REGEXP."
1302 (interactive "sMark by name (regexp): ")
1303 (ibuffer-mark-on-buffer
1304 #'(lambda (buf)
1305 (string-match regexp (buffer-name buf)))))
1307 ;;;###autoload
1308 (defun ibuffer-mark-by-mode-regexp (regexp)
1309 "Mark all buffers whose major mode matches REGEXP."
1310 (interactive "sMark by major mode (regexp): ")
1311 (ibuffer-mark-on-buffer
1312 #'(lambda (buf)
1313 (with-current-buffer buf
1314 (string-match regexp mode-name)))))
1316 ;;;###autoload
1317 (defun ibuffer-mark-by-file-name-regexp (regexp)
1318 "Mark all buffers whose file name matches REGEXP."
1319 (interactive "sMark by file name (regexp): ")
1320 (ibuffer-mark-on-buffer
1321 #'(lambda (buf)
1322 (let ((name (or (buffer-file-name buf)
1323 (with-current-buffer buf
1324 (and
1325 (boundp 'dired-directory)
1326 (stringp dired-directory)
1327 dired-directory)))))
1328 (when name
1329 (string-match regexp name))))))
1331 ;;;###autoload
1332 (defun ibuffer-mark-by-mode (mode)
1333 "Mark all buffers whose major mode equals MODE."
1334 (interactive
1335 (list (intern (completing-read "Mark by major mode: " obarray
1336 #'(lambda (e)
1337 ;; kind of a hack...
1338 (and (fboundp e)
1339 (string-match "-mode$"
1340 (symbol-name e))))
1342 (let ((buf (ibuffer-current-buffer)))
1343 (if (and buf (buffer-live-p buf))
1344 (with-current-buffer buf
1345 (cons (symbol-name major-mode)
1347 ""))))))
1348 (ibuffer-mark-on-buffer
1349 #'(lambda (buf)
1350 (with-current-buffer buf
1351 (eq major-mode mode)))))
1353 ;;;###autoload
1354 (defun ibuffer-mark-modified-buffers ()
1355 "Mark all modified buffers."
1356 (interactive)
1357 (ibuffer-mark-on-buffer
1358 #'(lambda (buf) (buffer-modified-p buf))))
1360 ;;;###autoload
1361 (defun ibuffer-mark-unsaved-buffers ()
1362 "Mark all modified buffers that have an associated file."
1363 (interactive)
1364 (ibuffer-mark-on-buffer
1365 #'(lambda (buf) (and (with-current-buffer buf buffer-file-name)
1366 (buffer-modified-p buf)))))
1368 ;;;###autoload
1369 (defun ibuffer-mark-dissociated-buffers ()
1370 "Mark all buffers whose associated file does not exist."
1371 (interactive)
1372 (ibuffer-mark-on-buffer
1373 #'(lambda (buf)
1374 (with-current-buffer buf
1376 (and buffer-file-name
1377 (not (file-exists-p buffer-file-name)))
1378 (and (eq major-mode 'dired-mode)
1379 (boundp 'dired-directory)
1380 (stringp dired-directory)
1381 (not (file-exists-p (file-name-directory dired-directory)))))))))
1383 ;;;###autoload
1384 (defun ibuffer-mark-help-buffers ()
1385 "Mark buffers like *Help*, *Apropos*, *Info*."
1386 (interactive)
1387 (ibuffer-mark-on-buffer
1388 #'(lambda (buf)
1389 (with-current-buffer buf
1390 (memq major-mode ibuffer-help-buffer-modes)))))
1392 ;;;###autoload
1393 (defun ibuffer-mark-old-buffers ()
1394 "Mark buffers which have not been viewed in `ibuffer-old-time' days."
1395 (interactive)
1396 (ibuffer-mark-on-buffer
1397 #'(lambda (buf)
1398 (with-current-buffer buf
1399 ;; hacked from midnight.el
1400 (when buffer-display-time
1401 (let* ((tm (current-time))
1402 (now (+ (* (float (ash 1 16)) (car tm))
1403 (float (cadr tm)) (* 0.0000001 (caddr tm))))
1404 (then (+ (* (float (ash 1 16))
1405 (car buffer-display-time))
1406 (float (cadr buffer-display-time))
1407 (* 0.0000001 (caddr buffer-display-time)))))
1408 (> (- now then) (* 60 60 ibuffer-old-time))))))))
1410 ;;;###autoload
1411 (defun ibuffer-mark-special-buffers ()
1412 "Mark all buffers whose name begins and ends with '*'."
1413 (interactive)
1414 (ibuffer-mark-on-buffer
1415 #'(lambda (buf) (string-match "^\\*.+\\*$"
1416 (buffer-name buf)))))
1418 ;;;###autoload
1419 (defun ibuffer-mark-read-only-buffers ()
1420 "Mark all read-only buffers."
1421 (interactive)
1422 (ibuffer-mark-on-buffer
1423 #'(lambda (buf)
1424 (with-current-buffer buf
1425 buffer-read-only))))
1427 ;;;###autoload
1428 (defun ibuffer-mark-dired-buffers ()
1429 "Mark all `dired' buffers."
1430 (interactive)
1431 (ibuffer-mark-on-buffer
1432 #'(lambda (buf)
1433 (with-current-buffer buf
1434 (eq major-mode 'dired-mode)))))
1436 ;;;###autoload
1437 (defun ibuffer-do-occur (regexp &optional nlines)
1438 "View lines which match REGEXP in all marked buffers.
1439 Optional argument NLINES says how many lines of context to display: it
1440 defaults to one."
1441 (interactive (occur-read-primary-args))
1442 (if (or (not (integerp nlines))
1443 (< nlines 0))
1444 (setq nlines 0))
1445 (when (zerop (ibuffer-count-marked-lines))
1446 (ibuffer-set-mark ibuffer-marked-char))
1447 (let ((ibuffer-do-occur-bufs nil))
1448 ;; Accumulate a list of marked buffers
1449 (ibuffer-map-marked-lines
1450 #'(lambda (buf mark)
1451 (push buf ibuffer-do-occur-bufs)))
1452 (occur-1 regexp nlines ibuffer-do-occur-bufs)))
1454 (provide 'ibuf-ext)
1456 ;;; ibuf-ext.el ends here