summaryrefslogtreecommitdiff
path: root/lisp/msb.el
diff options
context:
space:
mode:
authorRichard M. Stallman <rms@gnu.org>1994-12-23 17:58:46 +0000
committerRichard M. Stallman <rms@gnu.org>1994-12-23 17:58:46 +0000
commitfccee31231ec67d0fb40828b81d696d2d9f72113 (patch)
tree5c456e53c2abbfcb6b16cc6829a0a967ba9828f9 /lisp/msb.el
parentaf9e8df23852560fc2ded31c3ec22c8bfa891a77 (diff)
downloademacs-fccee31231ec67d0fb40828b81d696d2d9f72113.tar.gz
Initial revision
Diffstat (limited to 'lisp/msb.el')
-rw-r--r--lisp/msb.el1009
1 files changed, 1009 insertions, 0 deletions
diff --git a/lisp/msb.el b/lisp/msb.el
new file mode 100644
index 00000000000..8cee0b779bf
--- /dev/null
+++ b/lisp/msb.el
@@ -0,0 +1,1009 @@
+;;; msb.el --- Customizable buffer-selection with multiple menus.
+;; Copyright (C) 1993, 1994 Lars Lindberg <Lars.Lindberg@sypro.cap.se>
+;;
+;; Author: Lars Lindberg <Lars.Lindberg@sypro.cap.se>
+;; Created: 8 Oct 1993
+;; $Revision: 3.21 $
+;; $Date: 1994/12/22 07:58:27 $
+;; Keywords: mouse buffer menu
+;;
+;; This program 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 of the License, or
+;; (at your option) any later version.
+;;
+;; This program 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 this program; if not, write to the Free Software
+;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;; LCD Archive Entry:
+;; msb|Lars Lindberg|Lars.Lindberg@sypro.cap.se|
+;; Choose buffer with the mouse.
+;; $Date: 1994/12/22 07:58:27 $|$Revision: 3.21 $|~/packages/msb.el.Z|
+
+;;; Commentary:
+;;
+;; Purpose of this package:
+;; 1. Offer a function for letting the user choose buffer,
+;; not necessarily for switching to it.
+;; 2. Make a better mouse-buffer-menu.
+;;
+;; Installation:
+;; (require 'msb)
+;; Note! You now use msb instead of mouse-buffer-menu.
+;;
+;; Now try c-mouse-down-1 (Press <CTRL> and mouse button 1 at the
+;; same time).
+;;
+;; Customization:
+;; Look at the variable 'msb-menu-cond' for deciding what menus you
+;; want. It's not that hard to customize, despite my not-so-good
+;; doc-string. Feel free to send me a better doc-string.
+;; There are some constants for you to try here:
+;; msb--few-menus
+;; msb--very-many-menus (default)
+;;
+;; Look at the variable 'msb-item-handling-function' for customization
+;; of the appearance of every menu item. Try for instance setting
+;; it to 'msb-alon-item-handler.
+;;
+;; Look at the variable 'msb-item-sort-function' for customization
+;; of sorting the menus. Set it to t for instance, which means no
+;; sorting - you will get latest used buffer first.
+;;
+;; Also check out the variable 'msb-display-invisible-buffers-p'
+
+;; Known bugs:
+;; - `msb' does not work on a non-X-toolkit Emacs.
+;; Future enhancements:
+;; - [Mattes] had a suggestion about sorting files by extension.
+;; I (Lars Lindberg) think this case could be solved if msb.el was
+;; rewritten to handle more dynamic splitting. It's now completely
+;; static, depending on the menu-cond. If the splitting could also
+;; be done by a user-defined function a lot of cases would be
+;; solved.
+;; - [Jim] suggested that the Frame menu became a part of the buffer menu.
+
+;;; Change Log
+;; 3.21 22/12-94
+;; Fixed bug that occured in non X-toolkit versions of Emacs.
+;; [Chalupsky] pointed out that a global `save-match-data' is
+;; necessary.
+;; Bug found. Thanks [kifer].
+;; 3.16 20/12-94
+;; Added separators to the menu. New variable `msb-separator-diff'.
+;; New variable `msb-files-by-directory-sort-key'.
+;; Removed `msb--many-menus.'
+;; Fixed bugs.
+;; 3.13 20/12-94
+;; Menu fix for non-X-toolkit Emacsen and new "process"
+;; menu. Thanks [jim].
+;; Bug for 'files-by-type'.
+;; Restored the call to `msb-after-load-hooks'. Thanks [larry].
+;; Major fixes by [Ake].
+;; Menu-bar buffer-menu now has menu-toggle at top level.
+;; 3.6 16/12-94
+;; Added variable `msb-max-file-menu-items'.
+;; Removed a large part of the change log.
+;; Found bug.
+;; 3.3 16/12-94
+;; Found bugs.
+;; 3.1 16/12-94
+;; Now has two types of menus - "files by directory" and "files
+;; by type".
+;; Added variable `msb-files-by-directory'.
+;; Fixed a number of bugs for older versions.
+;; 2.11 16/12-94
+;; Added 'no-multi to msb-menu-cond.
+;; Added possibility to shift the menu leftwards. Thanks [kifer].
+;; 2.8 15/12-94
+;; Now aware of earlier versions of Emacs that doesn't have the
+;; function `frame-or-buffer-changed-p' or the variable
+;; `menu-bar-update-hook'. Thanks [will].
+;; 2.7 14/12-94
+;; Better installation.
+;; 2.6 14/12-94
+;; Now only makes up the menu when necessary.
+;; Added menu-bar support.
+;; Now handles errors in msb-menu-cond better. Thanks [jaalto].
+;; Added MH-awareness. Thanks [kifer].
+;; Added autoload statements.
+;; 2.3 8/12-94
+;; Now uses RCS version numbering for msb.el version number.
+;; Submitted this to LCD.
+
+;;; Thanks goes to
+;; [msb] - Mark Brader <msb@sq.com>
+;; [Chalupsky] - Hans Chalupsky <hans@cs.Buffalo.EDU>
+;; [jim] - Jim Berry <m1jhb00@FRB.GOV>
+;; [larry] - Larry Rosenberg <ljr@ictv.com>
+;; [will] - Will Henney <will@astroscu.unam.mx>
+;; [jaalto] - Jari Aalto <jaalto@tre.tele.nokia.fi>
+;; [kifer] - Michael Kifer <kifer@sbkifer.cs.sunysb.edu>
+;; [Gael] - Gael Marziou <gael@gnlab030.grenoble.hp.com>
+;; [Gillespie] - Dave Gillespie <daveg@thymus.synaptics.com>
+;; [Alon] - Alon Albert <alon@milcse.rtsg.mot.com>
+;; [KevinB] - Kevin Broadey, <KevinB@bartley.demon.co.uk>
+;; [Ake] - Ake Stenhof <ake@cadpoint.se>
+;; [RMS] - Richard Stallman <rms@gnu.ai.mit.edu>
+;; [Fisk] - Steve Fisk <fisk@medved.bowdoin.edu>
+
+;;; Code:
+
+(require 'cl)
+
+;;;
+;;; Some example constants to be used for 'msb-menu-cond'. See that
+;;; variable for more information. Please note that if the condition
+;;; returns 'multi, then the buffer can appear in several menus.
+;;;
+(defconst msb--few-menus
+ '(((and (boundp 'server-buffer-clients)
+ server-buffer-clients
+ 'multi)
+ 3030
+ "Clients (%d)")
+ ((and msb-display-invisible-buffers-p
+ (msb-invisible-buffer-p)
+ 'multi)
+ 3090
+ "Invisible buffers (%d)")
+ ((eq major-mode 'dired-mode)
+ 2010
+ "Dired (%d)"
+ msb-dired-item-handler
+ msb-sort-by-directory)
+ ((eq major-mode 'Man-mode)
+ 4090
+ "Manuals (%d)")
+ ((eq major-mode 'w3-mode)
+ 4020
+ "WWW (%d)")
+ ((or (memq major-mode '(rmail-mode vm-summary-mode vm-mode mail-mode))
+ (memq major-mode '(mh-letter-mode
+ mh-show-mode
+ mh-folder-mode))
+ (memq major-mode '(gnus-summary-mode
+ news-reply-mode
+ gnus-group-mode
+ gnus-article-mode
+ gnus-kill-file-mode
+ gnus-browse-killed-mode)))
+ 4010
+ "Mail (%d)")
+ ((not buffer-file-name)
+ 4099
+ "Buffers (%d)")
+ ('no-multi
+ 1099
+ "Files (%d)")))
+
+(defconst msb--very-many-menus
+ '(((and (boundp 'server-buffer-clients)
+ server-buffer-clients
+ 'multi)
+ 1010
+ "Clients (%d)")
+ ((and (boundp 'vc-mode) vc-mode 'multi)
+ 1020
+ "Version Control (%d)")
+ ((and buffer-file-name
+ (buffer-modified-p)
+ 'multi)
+ 1030
+ "Changed files (%d)")
+ ((and (get-buffer-process (current-buffer))
+ 'multi)
+ 1040
+ "Processes (%d)")
+ ((and msb-display-invisible-buffers-p
+ (msb-invisible-buffer-p)
+ 'multi)
+ 1090
+ "Invisible buffers (%d)")
+ ((eq major-mode 'dired-mode)
+ 2010
+ "Dired (%d)"
+ ;; Note this different menu-handler
+ msb-dired-item-handler
+ ;; Also note this item-sorter
+ msb-sort-by-directory)
+ ((eq major-mode 'Man-mode)
+ 4030
+ "Manuals (%d)")
+ ((eq major-mode 'w3-mode)
+ 4020
+ "WWW (%d)")
+ ((or (memq major-mode '(rmail-mode vm-summary-mode vm-mode mail-mode))
+ (memq major-mode '(mh-letter-mode
+ mh-show-mode
+ mh-folder-mode))
+ (memq major-mode '(gnus-summary-mode
+ news-reply-mode
+ gnus-group-mode
+ gnus-article-mode
+ gnus-kill-file-mode
+ gnus-browse-killed-mode)))
+ 4010
+ "Mail (%d)")
+ ;; Catchup for all non-file buffers
+ ((and (not buffer-file-name)
+ 'no-multi)
+ 4099
+ "Other non-file buffers (%d)")
+ ((and (string-match "/\\.[^/]*$" buffer-file-name)
+ 'multi)
+ 3090
+ "Hidden Files (%d)")
+ ((memq major-mode '(c-mode c++-mode))
+ 3010
+ "C/C++ Files (%d)")
+ ((eq major-mode 'emacs-lisp-mode)
+ 3020
+ "Elisp Files (%d)")
+ ((eq major-mode 'latex-mode)
+ 3030
+ "LaTex Files (%d)")
+ ('no-multi
+ 3099
+ "Other files (%d)")))
+
+;; msb--many-menus is obsolete
+(defvar msb--many-menus msb--very-many-menus)
+
+;;;
+;;; Customizable variables
+;;;
+
+(defvar msb-separator-diff 100
+ "*Non-nil means use separators.
+The separators will appear between all menus that have a sorting key that differs by this value or more.")
+
+(defvar msb-files-by-directory-sort-key 0
+ "*The sort key for files sorted by directory")
+
+(defvar msb-max-menu-items 25
+ "*The maximum number of items in a menu.
+If this variable is set to 15 for instance, then the 15 latest used
+buffer that fits in a certain submenu will appear in that submenu.
+Nil means no limit.")
+
+(defvar msb-max-file-menu-items 10
+ "*The maximum number of items from different directories.
+
+When the menu is of type 'file by directory', this is the maximum
+number of buffers that are clumped togehter from different
+directories.
+
+If the value is not a number, then the value 10 is used.")
+
+(defvar msb-most-recently-used-sort-key -1010
+ "*Where should the menu with the most recently used buffers be placed?")
+
+(defvar msb-display-most-recently-used t
+ "*How many buffers should be in the most-recently-used menu.
+No buffers at all if less than 1 or nil.
+T means use the value of `msb-max-menu-items' in the way it is defined.")
+
+(defvar msb-most-recently-used-title "Most recently used (%d)"
+ "*The title for the most-recently-used menu.")
+
+(defvar msb-horizontal-shift-function '(lambda () 0)
+ "*Function that specifies a number of pixels by which the top menu should
+be shifted leftwards.")
+
+(defvar msb-display-invisible-buffers-p nil
+ "*Show invisible buffers or not.
+Non-nil means that the buffer menu should include buffers that have
+names that starts with a space character.")
+
+(defvar msb-item-handling-function 'msb-item-handler
+ "*The appearance of a buffer menu.
+
+The default function to call for handling the appearance of a menu
+item. It should take to arguments, BUFFER and MAX-BUFFER-NAME-LENGTH,
+where the latter is the max length of all buffer names.
+When the function is called, BUFFER is the current buffer.
+This function is called for items in the variable 'msb-menu-cond' that
+have nil as ITEM-HANDLING-FUNCTION. See 'msb-menu-cond' for more
+information.")
+
+(defvar msb-item-sort-function 'msb-sort-by-name
+ "*The order of items in a buffer menu.
+The default function to call for handling the order of items in a menu
+item. This function is called like a sort function. The items
+look like (ITEM-NAME . BUFFER).
+ITEM-NAME is the name of the item that will appear in the menu.
+BUFFER is the buffer, this is not necessarily the current buffer.
+
+Set this to nil or t if you don't want any sorting (faster).")
+
+(defvar msb-files-by-directory nil
+ "*Non-nil means that files should be sorted by directory instead of
+the groups in msb-menu-cond.")
+
+(defvar msb-menu-cond msb--very-many-menus
+ "*List of criterias for splitting the mouse buffer menu.
+The elements in the list should be of this type:
+ (CONDITION MENU-SORT-KEY MENU-TITLE ITEM-HANDLING-FN ITEM-SORT-FN).
+
+When making the split, the buffers are tested one by one against the
+CONDITION, just like a lisp cond: When hitting a true condition, the
+other criterias are *not* tested and the buffer name will appear in
+the menu with the menu-title corresponding to the true condition.
+
+If the condition returns the symbol 'multi, then the buffer will be
+added to this menu *and* tested for other menus too. If it returns
+'no-multi, then the buffer will only be added if it hasn't been added
+to any other menu.
+
+During this test, the buffer in question is the current buffer, and
+the test is surrounded by calls to `save-excursion' and
+`save-match-data'
+
+The categories are sorted by MENU-SORT-KEY. Smaller keys are on
+top. nil means don't display this menu.
+
+MENU-TITLE is really a format. If you add %d in it, the %d is replaced
+with the number of items in that menu.
+
+ITEM-HANDLING-FN, is optional. If it is supplied and is a
+function, than it is used for displaying the items in that particular
+buffer menu, otherwise the function pointed out by
+'msb-item-handling-function' is used.
+
+ITEM-SORT-FN, is also optional.
+If it is not supplied, the function pointed out by
+'msb-item-sort-function' is used.
+If it is nil, then no sort takes place and the buffers are presented
+in least-recently-used order.
+If it is t, then no sort takes place and the buffers are presented in
+most-recently-used order.
+If it is supplied and non-nil and not t than it is used for sorting
+the items in that particular buffer menu.
+
+Note1: There should always be a 'catch-all' as last element,
+in this list. That is an element like (t TITLE ITEM-HANDLING-FUNCTION).
+Note2: A buffer menu appears only if it has at least one buffer in it.
+Note3: If you have a CONDITION that can't be evaluated you will get an
+error every time you do \\[msb].")
+
+(defvar msb-after-load-hooks nil
+ "Hooks to be run after the msb package has been loaded.")
+
+;;;
+;;; Internal variables
+;;;
+
+;; The last calculated menu.
+(defvar msb--last-buffer-menu nil)
+
+;; If this is non-nil, then it is a string that describes the error.
+(defvar msb--error nil)
+
+;;;
+;;; Some example function to be used for 'msb-item-sort-function'.
+;;;
+(defun msb-item-handler (buffer &optional maxbuf)
+ "Create one string item, concerning BUFFER, for the buffer menu.
+The item looks like:
+*% <buffer-name>
+The '*' appears only if the buffer is marked as modified.
+The '%' appears only if the buffer is read-only.
+Optional second argument MAXBUF is completely ignored."
+ (let ((name (buffer-name))
+ (modified (if (buffer-modified-p) "*" " "))
+ (read-only (if buffer-read-only "%" " ")))
+ (format "%s%s %s" modified read-only name)))
+
+
+(eval-when-compile (require 'dired))
+
+;; 'dired' can be called with a list of the form (directory file1 file2 ...)
+;; which causes 'dired-directory' to be in the same form.
+(defun msb--dired-directory ()
+ (cond ((stringp dired-directory)
+ (abbreviate-file-name (expand-file-name dired-directory)))
+ ((consp dired-directory)
+ (abbreviate-file-name (expand-file-name (car dired-directory))))
+ (t
+ (error "Unknown type of 'dired-directory' in buffer %s"
+ (buffer-name)))))
+
+(defun msb-dired-item-handler (buffer &optional maxbuf)
+ "Create one string item, concerning a dired BUFFER, for the buffer menu.
+The item looks like:
+*% <buffer-name>
+The '*' appears only if the buffer is marked as modified.
+The '%' appears only if the buffer is read-only.
+Optional second argument MAXBUF is completely ignored."
+ (let ((name (msb--dired-directory))
+ (modified (if (buffer-modified-p) "*" " "))
+ (read-only (if buffer-read-only "%" " ")))
+ (format "%s%s %s" modified read-only name)))
+
+(defun msb-alon-item-handler (buffer maxbuf)
+ "Create one string item for the buffer menu.
+The item looks like:
+<buffer-name> *%# <file-name>
+The '*' appears only if the buffer is marked as modified.
+The '%' appears only if the buffer is read-only.
+The '#' appears only version control file (SCCS/RCS)."
+ (format (format "%%%ds %%s%%s%%s %%s" maxbuf)
+ (buffer-name buffer)
+ (if (buffer-modified-p) "*" " ")
+ (if buffer-read-only "%" " ")
+ (if (and (boundp 'vc-mode) vc-mode) "#" " ")
+ (or buffer-file-name "")))
+
+;;;
+;;; Some example function to be used for 'msb-item-handling-function'.
+;;;
+(defun msb-sort-by-name (item1 item2)
+ "Sorts the items depending on their buffer-name
+An item look like (NAME . BUFFER)."
+ (string-lessp (buffer-name (cdr item1))
+ (buffer-name (cdr item2))))
+
+
+(defun msb-sort-by-directory (item1 item2)
+ "Sorts the items depending on their directory. Made for dired.
+An item look like (NAME . BUFFER)."
+ (string-lessp (save-excursion (set-buffer (cdr item1)) (msb--dired-directory))
+ (save-excursion (set-buffer (cdr item2)) (msb--dired-directory))))
+
+;;;
+;;; msb
+;;;
+;;; This function can be used instead of (mouse-buffer-menu EVENT)
+;;; function in "mouse.el".
+;;;
+(defun msb (event)
+ "Pop up several menus of buffers for selection with the mouse.
+This command switches buffers in the window that you clicked on, and
+selects that window.
+
+See the function 'mouse-select-buffer' and the variable
+'msb-menu-cond' for more information about how the menus are split."
+ (interactive "e")
+ (let ((buffer (mouse-select-buffer event))
+ (window (posn-window (event-start event))))
+ (cond
+ (buffer
+ (or (framep window) (select-window window))
+ (switch-to-buffer (car (cdr buffer))))))
+ nil)
+
+;;;
+;;; Some supportive functions
+;;;
+(defun msb-invisible-buffer-p (&optional buffer)
+ "Return t if optional BUFFER is an \"invisible\" buffer.
+If the argument is left out or nil, then the current buffer is considered."
+ (and (> (length (buffer-name buffer)) 0)
+ (eq ?\ (aref (buffer-name buffer) 0))))
+
+;; Strip one hierarcy level from the end of PATH.
+(defun msb--strip-path (path)
+ (save-match-data
+ (if (string-match "\\(.+\\)/[^/]+$" path)
+ (substring path (match-beginning 1) (match-end 1))
+ "/")))
+
+;; Create an alist with all buffers from LIST that lies under the same
+;; directory will be in the same item as the directory string as
+;;'((PATH1 . (BUFFER-1 BUFFER-2 ...)) (PATH2 . (BUFFER-K BUFFER-K+1...)) ...)
+(defun msb--init-file-alist (list)
+ (let ((buffer-alist
+ (sort (mapcan
+ (function
+ (lambda (buffer)
+ (let ((file-name (buffer-file-name buffer)))
+ (when file-name
+ (list (cons (msb--strip-path file-name) buffer))))))
+ list)
+ (function (lambda (item1 item2)
+ (string< (car item1) (car item2)))))))
+ ;; Make alist that looks like
+ ;;'((PATH1 . (BUFFER-1 BUFFER-2 ...)) (PATH2 . (BUFFER-K)) ...)
+ (let ((path nil)
+ (buffers nil)
+ (result nil))
+ (append
+ (mapcan (function
+ (lambda (item)
+ (cond
+ ((and path
+ msb-max-menu-items
+ (< (length buffers) msb-max-menu-items)
+ (string= path (car item)))
+ (push (cdr item) buffers)
+ nil)
+ (t
+ (when path
+ (setq result (cons path buffers)))
+ (setq path (car item))
+ (setq buffers (list (cdr item)))
+ (and result (list result))))))
+ buffer-alist)
+ (list (cons path buffers))))))
+
+;; Choose file-menu with respect to directory for every buffer in LIST.
+(defun msb--choose-file-menu (list)
+ (let ((buffer-alist (msb--init-file-alist list))
+ (final-list nil)
+ (max-clumped-together (if (numberp msb-max-file-menu-items)
+ msb-max-file-menu-items
+ 10))
+ (top-found-p nil)
+ (last-path nil)
+ first rest path buffers)
+ (setq first (car buffer-alist))
+ (setq rest (cdr buffer-alist))
+ (setq path (car first))
+ (setq buffers (cdr first))
+ (while rest
+ (let ((found-p nil)
+ (tmp-rest rest)
+ new-path item)
+ (setq item (car tmp-rest))
+ (while (and tmp-rest
+ (<= (length buffers) max-clumped-together)
+ (>= (length (car item)) (length path))
+ (string= path (substring (car item) 0 (length path))))
+ (setq found-p t)
+ (setq buffers (append buffers (cdr item)))
+ (setq tmp-rest (cdr tmp-rest))
+ (setq item (car tmp-rest)))
+ (cond
+ ((> (length buffers) max-clumped-together)
+ (setq last-path (car first))
+ (when top-found-p
+ (setq first (cons (concat (car first) "/...")
+ (cdr first)))
+ (setq top-found-p nil))
+ (push first final-list)
+ (setq first (car rest)
+ rest (cdr rest))
+ (setq path (car first)
+ buffers (cdr first)))
+ (t
+ (when found-p
+ (setq top-found-p t)
+ (setq first (cons path buffers)
+ rest tmp-rest))
+ (setq path (msb--strip-path path)
+ buffers (cdr first))
+ (when (and last-path
+ (or (and (>= (length path) (length last-path))
+ (string= last-path
+ (substring path 0 (length last-path))))
+ (and (< (length path) (length last-path))
+ (string= path
+ (substring last-path 0 (length path))))))
+
+ (when top-found-p
+ (setq first (cons (concat (car first) "/...")
+ (cdr first)))
+ (setq top-found-p nil))
+ (push first final-list)
+ (setq first (car rest)
+ rest (cdr rest))
+ (setq path (car first)
+ buffers (cdr first)))))))
+ (when top-found-p
+ (setq first (cons (concat (car first)
+ (if (string-match "/$" (car first))
+ "..."
+ "/..."))
+ (cdr first)))
+ (setq top-found-p nil))
+ (push first final-list)
+ (nreverse final-list)))
+
+;; Create a vector as:
+;; [BUFFER-LIST-VARIABLE CONDITION MENU-SORT-KEY MENU-TITLE ITEM-HANDLER SORTER)
+;; from an element in 'msb-menu-cond'. See that variable for a
+;; description of it's elements.
+(defun msb--create-function-info (menu-cond-elt)
+ (let* ((list-symbol (make-symbol "-msb-buffer-list"))
+ (tmp-ih (and (> (length menu-cond-elt) 3)
+ (nth 3 menu-cond-elt)))
+ (item-handler (if (and tmp-ih (fboundp tmp-ih))
+ tmp-ih
+ msb-item-handling-function))
+ (tmp-s (if (> (length menu-cond-elt) 4)
+ (nth 4 menu-cond-elt)
+ msb-item-sort-function))
+ (sorter (if (or (fboundp tmp-s)
+ (null tmp-s)
+ (eq tmp-s 't))
+ tmp-s
+ msb-item-sort-function)))
+ (when (< (length menu-cond-elt) 3)
+ (error "Wrong format of msb-menu-cond."))
+ (when (and (> (length menu-cond-elt) 3)
+ (not (fboundp tmp-ih)))
+ (signal 'invalid-function (list tmp-ih)))
+ (when (and (> (length menu-cond-elt) 4)
+ tmp-s
+ (not (fboundp tmp-s))
+ (not (eq tmp-s 't)))
+ (signal 'invalid-function (list tmp-s)))
+ (set list-symbol '())
+ (vector list-symbol ;BUFFER-LIST-VARIABLE
+ (nth 0 menu-cond-elt) ;CONDITION
+ (nth 1 menu-cond-elt) ;SORT-KEY
+ (nth 2 menu-cond-elt) ;MENU-TITLE
+ item-handler ;ITEM-HANDLER
+ sorter) ;SORTER
+ ))
+
+;; This defsubst is only used in `msb--choose-menu' below. It was
+;; pulled out merely to make the code somewhat clearer. The indention
+;; level was too big.
+(defsubst msb--collect (function-info-vector)
+ (let ((result nil)
+ (multi-flag nil)
+ function-info-list)
+ (setq function-info-list
+ (loop for fi
+ across function-info-vector
+ if (and (setq result
+ (eval (aref fi 1))) ;Test CONDITION
+ (not (and (eq result 'no-multi)
+ multi-flag))
+ (progn (when (eq result 'multi)
+ (setq multi-flag t))
+ t)
+ (or (not msb-max-menu-items)
+ (< (length (eval (aref fi 0)))
+ msb-max-menu-items)))
+ collect fi
+ until (and result
+ (not (eq result 'multi)))))
+ (when (and (not function-info-list)
+ (not result))
+ (error "No catch-all in msb-menu-cond!"))
+ function-info-list))
+
+;; Adds BUFFER to the menu depicted by FUNCTION-INFO
+;; All side-effects. Adds an element of type '(BUFFER-TITLE . BUFFER)
+;; to the buffer-list variable in function-info.
+(defun msb--add-to-menu (buffer function-info max-buffer-name-length)
+ (let ((list-symbol (aref function-info 0))) ;BUFFER-LIST-VARIABLE
+ ;; Here comes the hairy side-effect!
+ (set list-symbol
+ (cons (cons (funcall (aref function-info 4) ;ITEM-HANDLER
+ buffer
+ max-buffer-name-length)
+ buffer)
+ (eval list-symbol)))))
+
+;; Selects the appropriate menu for BUFFER.
+;; This is all side-effects, folks!
+;; This should be optimized.
+(defsubst msb--choose-menu (buffer function-info-vector max-buffer-name-length)
+ (unless (and (not msb-display-invisible-buffers-p)
+ (msb-invisible-buffer-p buffer))
+ (condition-case nil
+ (save-excursion
+ (set-buffer buffer)
+ ;; Menu found. Add to this menu
+ (mapc (function
+ (lambda (function-info)
+ (msb--add-to-menu buffer function-info max-buffer-name-length)))
+ (msb--collect function-info-vector)))
+ (error (unless msb--error
+ (setq msb--error
+ (format
+ "Variable `msb-menu-cond': Error for buffer \"%s\"."
+ (buffer-name buffer)))
+ (error msb--error))))))
+
+;; Return (SORT-KEY TITLE . BUFFER-LIST) or nil if the
+;; buffer-list is empty.
+(defun msb--create-sort-item (function-info)
+ (let ((buffer-list (eval (aref function-info 0))))
+ (when buffer-list
+ (let ((sorter (aref function-info 5)) ;SORTER
+ (sort-key (aref function-info 2))) ;MENU-SORT-KEY
+ (when sort-key
+ (cons sort-key
+ (cons (format (aref function-info 3) ;MENU-TITLE
+ (length buffer-list))
+ (cond
+ ((null sorter)
+ buffer-list)
+ ((eq sorter 't)
+ (nreverse buffer-list))
+ (t
+ (sort buffer-list sorter))))))))))
+
+;; Returns a list on the form ((TITLE . BUFFER-LIST)) for
+;; the most recently used buffers.
+(defun msb--most-recently-used-menu (max-buffer-name-length)
+ (when (and msb-display-most-recently-used
+ (or (not (numberp msb-display-most-recently-used))
+ (> msb-display-most-recently-used 0)))
+ (let* ((max-in-menu
+ (if (numberp msb-display-most-recently-used)
+ msb-display-most-recently-used
+ msb-max-menu-items))
+
+ (most-recently-used
+ (loop with n = 0
+ for buffer in (cdr (buffer-list))
+ if (save-excursion
+ (set-buffer buffer)
+ (and (not (msb-invisible-buffer-p))
+ (not (eq major-mode 'dired-mode))))
+ collect (save-excursion
+ (set-buffer buffer)
+ (cons (funcall msb-item-handling-function
+ buffer
+ max-buffer-name-length)
+ buffer))
+ and do (incf n)
+ until (and max-in-menu (>= n max-in-menu)))))
+ (cons (if (stringp msb-most-recently-used-title)
+ (format msb-most-recently-used-title
+ (length most-recently-used))
+ (signal 'wrong-type-argument (list msb-most-recently-used-title)))
+ most-recently-used))))
+
+(defun msb--create-buffer-menu-2 ()
+ (let ((max-buffer-name-length 0)
+ file-buffers
+ function-info-vector)
+ ;; Calculate the longest buffer name.
+ (mapc
+ (function
+ (lambda (buffer)
+ (if (or msb-display-invisible-buffers-p
+ (not (msb-invisible-buffer-p)))
+ (setq max-buffer-name-length
+ (max max-buffer-name-length
+ (length (buffer-name buffer)))))))
+ (buffer-list))
+ ;; Make a list with elements of type
+ ;; (BUFFER-LIST-VARIABLE
+ ;; CONDITION
+ ;; MENU-SORT-KEY
+ ;; MENU-TITLE
+ ;; ITEM-HANDLER
+ ;; SORTER)
+ ;; Uses "function-global" variables:
+ ;; function-info-vector
+ (setq function-info-vector
+ (apply (function vector)
+ (mapcar (function msb--create-function-info)
+ msb-menu-cond)))
+ ;; Split the buffer-list into several lists; one list for each
+ ;; criteria. This is the most critical part with respect to time.
+ (mapc (function (lambda (buffer)
+ (cond ((and msb-files-by-directory
+ (buffer-file-name buffer))
+ (push buffer file-buffers))
+ (t
+ (msb--choose-menu buffer
+ function-info-vector
+ max-buffer-name-length)))))
+ (buffer-list))
+ (when file-buffers
+ (setq file-buffers
+ (mapcar (function
+ (lambda (buffer-list)
+ (cons msb-files-by-directory-sort-key
+ (cons (car buffer-list)
+ (sort
+ (mapcar (function
+ (lambda (buffer)
+ (cons (buffer-name buffer)
+ buffer)))
+ (cdr buffer-list))
+ (function
+ (lambda (item1 item2)
+ (string< (car item1) (car item2)))))))))
+ (msb--choose-file-menu file-buffers))))
+ ;; Now make the menu - a list of (TITLE . BUFFER-LIST)
+ (let* ((buffers (buffer-list))
+ menu
+ (most-recently-used
+ (msb--most-recently-used-menu max-buffer-name-length))
+ (others (append file-buffers
+ (loop for elt
+ across function-info-vector
+ for value = (msb--create-sort-item elt)
+ if value collect value))))
+ (setq menu
+ (mapcar 'cdr ;Remove the SORT-KEY
+ ;; Sort the menus - not the items.
+ (msb--add-separators
+ (sort
+ ;; Get a list of (SORT-KEY TITLE . BUFFER-LIST)
+ ;; Also sorts the items within the menus.
+ (if (cdr most-recently-used)
+ (cons
+ ;; Add most recent used buffers
+ (cons msb-most-recently-used-sort-key
+ most-recently-used)
+ others)
+ others)
+ (function (lambda (elt1 elt2)
+ (< (car elt1) (car elt2))))))))
+ ;; Now make it a keymap menu
+ (append
+ '(keymap "Select Buffer")
+ (msb--make-keymap-menu menu)
+ (when msb-separator-diff
+ (list (list 'separator "---")))
+ (list (cons 'toggle
+ (cons
+ (if msb-files-by-directory
+ "*Files by type*"
+ "*Files by directory*")
+ 'msb--toggle-menu-type)))))))
+
+(defun msb--create-buffer-menu ()
+ (save-match-data
+ (save-excursion
+ (msb--create-buffer-menu-2))))
+
+;;;
+;;; Multi purpose function for selecting a buffer with the mouse.
+;;;
+(defun msb--toggle-menu-type ()
+ (interactive)
+ (setq msb-files-by-directory (not msb-files-by-directory))
+ (menu-bar-update-buffers t))
+
+(defun mouse-select-buffer (event)
+ "Pop up several menus of buffers, for selection with the mouse.
+Returns the selected buffer or nil if no buffer is selected.
+
+The way the buffers are splitted is conveniently handled with the
+variable 'msb-menu-cond'."
+ ;; Popup the menu and return the selected buffer.
+ (when (or msb--error
+ (not msb--last-buffer-menu)
+ (not (fboundp 'frame-or-buffer-changed-p))
+ (frame-or-buffer-changed-p))
+ (setq msb--error nil)
+ (setq msb--last-buffer-menu (msb--create-buffer-menu)))
+ (let ((position event))
+ (when (and (fboundp 'posn-x-y)
+ (fboundp 'posn-window))
+ (let ((posX (car (posn-x-y (event-start event))))
+ (posY (cdr (posn-x-y (event-start event))))
+ (posWind (posn-window (event-start event)))
+ name)
+ ;; adjust position
+ (setq posX (- posX (funcall msb-horizontal-shift-function))
+ position (list (list posX posY) posWind))))
+ (setq name (x-popup-menu position msb--last-buffer-menu))
+ ;; If toggle bring up the
+ (cond
+ ((eq (car name) 'toggle)
+ (msb--toggle-menu-type)
+ (mouse-select-buffer event))
+ ((and (numberp (car name))
+ (null (cdr name)))
+ (let ((msb--last-buffer-menu (nthcdr 3 (assq (car name) msb--last-buffer-menu))))
+ (mouse-select-buffer event)))
+ ((and (stringp (car name))
+ (null (cdr name)))
+ (cons nil name))
+ (t
+ name))))
+
+;; Add separators
+(defun msb--add-separators (sorted-list)
+ (cond
+ ((or (not msb-separator-diff)
+ (not (numberp msb-separator-diff)))
+ sorted-list)
+ (t
+ (let ((last-key nil))
+ (mapcan
+ (function
+ (lambda (item)
+ (cond
+ ((and msb-separator-diff
+ last-key
+ (> (- (car item) last-key)
+ msb-separator-diff))
+ (setq last-key (car item))
+ (list (cons last-key 'separator)
+ item))
+ (t
+ (setq last-key (car item))
+ (list item)))))
+ sorted-list)))))
+
+(defun msb--make-keymap-menu (raw-menu)
+ (let ((end (cons '(nil) 'menu-bar-select-buffer))
+ (mcount 0))
+ (mapcar
+ (function
+ (lambda (sub-menu)
+ (cond
+ ((eq 'separator sub-menu)
+ (list 'separator "---"))
+ (t
+ (append (list (incf mcount) (car sub-menu)
+ 'keymap (car sub-menu))
+ (mapcar (function
+ (lambda (item)
+ (let ((string (car item))
+ (buffer (cdr item)))
+ (cons (buffer-name buffer)
+ (cons string end)))))
+ (cdr sub-menu)))))))
+ raw-menu)))
+
+(defun menu-bar-update-buffers (&optional arg)
+ ;; If user discards the Buffers item, play along.
+ (when (and (lookup-key (current-global-map) [menu-bar buffer])
+ (or (not (fboundp 'frame-or-buffer-changed-p))
+ (frame-or-buffer-changed-p)
+ arg))
+ (let ((buffers (buffer-list))
+ (frames (frame-list))
+ buffers-menu frames-menu)
+ ;; If requested, list only the N most recently selected buffers.
+ (when (and (integerp buffers-menu-max-size)
+ (> buffers-menu-max-size 1)
+ (> (length buffers) buffers-menu-max-size))
+ (setcdr (nthcdr buffers-menu-max-size buffers) nil))
+ ;; Make the menu of buffers proper.
+ (setq msb--last-buffer-menu (msb--create-buffer-menu))
+ (setq buffers-menu msb--last-buffer-menu)
+ ;; Make a Frames menu if we have more than one frame.
+ (if (cdr frames)
+ (setq frames-menu
+ (cons "Select Frame"
+ (mapcar
+ (function
+ (lambda (frame)
+ (nconc
+ (list frame
+ (cdr (assq 'name
+ (frame-parameters frame)))
+ (cons nil nil))
+ 'menu-bar-select-frame)))
+ frames))))
+ (when frames-menu
+ (setq frames-menu (cons 'keymap frames-menu)))
+ (define-key (current-global-map) [menu-bar buffer]
+ (cons "Buffers"
+ (if (and buffers-menu frames-menu)
+ (list 'keymap "Buffers and Frames"
+ (cons 'buffers (cons "Buffers" buffers-menu))
+ (cons 'frames (cons "Frames" frames-menu)))
+ (or buffers-menu frames-menu 'undefined)))))))
+
+(when (and (boundp 'menu-bar-update-hook)
+ (not (fboundp 'frame-or-buffer-changed-p)))
+ (defvar msb--buffer-count 0)
+ (defun frame-or-buffer-changed-p ()
+ (let ((count (length (buffer-list))))
+ (when (/= count msb--buffer-count)
+ (setq msb--buffer-count count)
+ t))))
+
+(unless (or (not (boundp 'menu-bar-update-hook))
+ (memq 'menu-bar-update-buffers menu-bar-update-hook))
+ (add-hook 'menu-bar-update-hook 'menu-bar-update-buffers))
+
+(and (fboundp 'mouse-buffer-menu)
+ (substitute-key-definition 'mouse-buffer-menu 'msb (current-global-map)))
+
+(provide 'msb)
+(eval-after-load 'msb (run-hooks 'msb-after-load-hooks))
+;;; msb.el ends here