;; -*- emacs-lisp -*-
;;; mst-iswitchb-narrow.el --- narrow the iswitch buffer to particular buffers

;; Author: Mark Triggs <mst@dishevelled.net>

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; This code allows quick navigation between buffers by 'narrowing' the iswitch
;; list based on a set of predefined predicates and/or regular expressions. For
;; example, hitting tab once might show only buffers whose major mode matches
;; the current buffer's major mode. Hitting again might show all .el files
;; currently open, again might show all the .lisp files currently open and so
;; on.

;; To use it, add something like the following to your ~/.emacs
;; (define-key global-map (kbd "C-x b") 'iswitchb-narrow)

;;; Code:

(defvar interesting-buffers
  '((lambda (b) (eq (buffer-mode (current-buffer))
                    (buffer-mode b)))
    "\\.lisp$" "\\(^dot\\.\\|\\.el$\\)" "\\.jl$" "\\.py$"
    "\\.p[lm]$" "\\.c$" "\\.hs$" "\\.java" "\\.e$" "\\.\\(tex\\|bib\\)$"
    "\\.htm\\(l\\)?$" "\\.txt$")
  "A list of strings and/or predicates that denote groups of buffers
that should be shown when hitting tab in iswitch. Strings are regexps
which are matched against buffer names using STRING-MATCH. Predicates
should be functions which take a buffer as a single argument, and
return a boolean.")


(defun iswitchb-buffer-narrow (predicate)
  (let ((matcher (if (stringp predicate)
                     (lambda (b) (string-match predicate (buffer-name b)))
                   predicate)))
    (if predicate
        (let ((iswitchb-make-buflist-hook
               (cons (lambda ()
                       (setq iswitchb-temp-buflist
                             (remove-if-not matcher
                                            (buffer-list))))
                     iswitchb-make-buflist-hook)))
          (iswitchb-buffer))
      (iswitchb-buffer))))


(defun iswitchb-narrow ()
  (interactive)
  ;; the first call should show all buffers
  (next-interesting-buffer-identifier 'reset)
  (let ((iswitchb-minibuffer-setup-hook
         (list (lambda ()
                 (define-key iswitchb-mode-map "\t"
                   (lambda ()
                     (interactive)
                     (throw 'done 'next-set)))))))
    (while (eql 'next-set
                (catch 'done (iswitchb-buffer-narrow
                              (next-interesting-buffer-identifier)))))))


(defvar next-interesting-buffer-identifier 0)

(defun next-interesting-buffer-identifier (&optional reset)
  "Return the next interesting file extension"
  (cond (reset (setq next-interesting-buffer-identifier
                     (length interesting-buffers)) ; a hack, I know
               nil)
        (t (or (dolist (ext (nthcdr next-interesting-buffer-identifier
                                    interesting-buffers))
                 (incf next-interesting-buffer-identifier)
                 (when (some
                        (if (stringp ext)
                            (lambda (b) (string-match ext (buffer-name b)))
                          ext)
                        (buffer-list))
                   (return ext)))
               (progn (setq next-interesting-buffer-identifier 0)
                      nil)))))


(provide 'mst-iswitchb-narrow)
;;; mst-iswitchb-narrow.el ends here
