1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162 |
- ;;; msb.el --- customizable buffer-selection with multiple menus
- ;; Copyright (C) 1993-1995, 1997-2012 Free Software Foundation, Inc.
- ;; Author: Lars Lindberg <lars.lindberg@home.se>
- ;; Maintainer: FSF
- ;; Created: 8 Oct 1993
- ;; Lindberg's last update version: 3.34
- ;; Keywords: mouse buffer menu
- ;; This file is part of GNU Emacs.
- ;; GNU Emacs 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 3 of the License, or
- ;; (at your option) any later version.
- ;; GNU Emacs 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. If not, see <http://www.gnu.org/licenses/>.
- ;;; 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. This is done as a global
- ;; minor mode, msb-mode.
- ;;
- ;; 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:
- ;; - Files-by-directory
- ;; + No possibility to show client/changed buffers separately.
- ;; + All file buffers only appear in a file sub-menu, they will
- ;; for instance not appear in the Mail sub-menu.
- ;; Future enhancements:
- ;;; Thanks goes to
- ;; Mark Brader <msb@sq.com>
- ;; Jim Berry <m1jhb00@FRB.GOV>
- ;; Hans Chalupsky <hans@cs.Buffalo.EDU>
- ;; Larry Rosenberg <ljr@ictv.com>
- ;; Will Henney <will@astroscu.unam.mx>
- ;; Jari Aalto <jaalto@tre.tele.nokia.fi>
- ;; Michael Kifer <kifer@sbkifer.cs.sunysb.edu>
- ;; Gael Marziou <gael@gnlab030.grenoble.hp.com>
- ;; Dave Gillespie <daveg@thymus.synaptics.com>
- ;; Alon Albert <alon@milcse.rtsg.mot.com>
- ;; Kevin Broadey, <KevinB@bartley.demon.co.uk>
- ;; Ake Stenhof <ake@cadpoint.se>
- ;; Richard Stallman <rms@gnu.org>
- ;; Steve Fisk <fisk@medved.bowdoin.edu>
- ;; This version turned into a global minor mode and subsequently
- ;; hacked on by Dave Love.
- ;;; Code:
- (eval-when-compile (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 rmail-edit-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 message-mode gnus-group-mode
- gnus-article-mode score-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)
- 5030
- "Manuals (%d)")
- ((eq major-mode 'w3-mode)
- 5020
- "WWW (%d)")
- ((or (memq major-mode
- '(rmail-mode rmail-edit-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 message-mode gnus-group-mode
- gnus-article-mode score-mode
- gnus-browse-killed-mode)))
- 5010
- "Mail (%d)")
- ;; Catchup for all non-file buffers
- ((and (not buffer-file-name)
- 'no-multi)
- 5099
- "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)")))
- ;;;
- ;;; Customizable variables
- ;;;
- (defgroup msb nil
- "Customizable buffer-selection with multiple menus."
- :prefix "msb-"
- :group 'mouse)
- (defun msb-custom-set (symbol value)
- "Set the value of custom variables for msb."
- (set symbol value)
- (if (and (featurep 'msb) msb-mode)
- ;; wait until package has been loaded before bothering to update
- ;; the buffer lists.
- (msb-menu-bar-update-buffers t)))
- (defcustom msb-menu-cond msb--very-many-menus
- "List of criteria 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 criteria 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.
- A value of 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,
- then 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]."
- :type `(choice (const :tag "long" :value ,msb--very-many-menus)
- (const :tag "short" :value ,msb--few-menus)
- (sexp :tag "user"))
- :set 'msb-custom-set
- :group 'msb)
- (defcustom msb-modes-key 4000
- "The sort key for files sorted by mode."
- :type 'integer
- :set 'msb-custom-set
- :group 'msb
- :version "20.3")
- (defcustom 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."
- :type '(choice integer (const nil))
- :set 'msb-custom-set
- :group 'msb)
- (defvar msb-files-by-directory-sort-key 0
- "*The sort key for files sorted by directory.")
- (defcustom msb-max-menu-items 15
- "The maximum number of items in a menu.
- If this variable is set to 15 for instance, then the submenu will be
- split up in minor parts, 15 items each. A value of nil means no limit."
- :type '(choice integer (const nil))
- :set 'msb-custom-set
- :group 'msb)
- (defcustom 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 together from different
- directories.
- Set this to 1 if you want one menu per directory instead of clumping
- them together.
- If the value is not a number, then the value 10 is used."
- :type 'integer
- :set 'msb-custom-set
- :group 'msb)
- (defcustom msb-most-recently-used-sort-key -1010
- "Where should the menu with the most recently used buffers be placed?"
- :type 'integer
- :set 'msb-custom-set
- :group 'msb)
- (defcustom msb-display-most-recently-used 15
- "How many buffers should be in the most-recently-used menu.
- No buffers at all if less than 1 or nil (or any non-number)."
- :type 'integer
- :set 'msb-custom-set
- :group 'msb)
- (defcustom msb-most-recently-used-title "Most recently used (%d)"
- "The title for the most-recently-used menu."
- :type 'string
- :set 'msb-custom-set
- :group 'msb)
- (defvar msb-horizontal-shift-function (lambda () 0)
- "*Function that specifies how many pixels to shift the top menu leftwards.")
- (defcustom 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."
- :type 'boolean
- :set 'msb-custom-set
- :group 'msb)
- (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 two arguments, BUFFER and MAX-BUFFER-NAME-LENGTH,
- where the latter is the max length of all buffer names.
- The function should return the string to use in the menu.
- 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.")
- (defcustom 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)."
- :type '(choice (const msb-sort-by-name)
- (const :tag "Newest first" t)
- (const :tag "Oldest first" nil))
- :set 'msb-custom-set
- :group 'msb)
- (defcustom msb-files-by-directory nil
- "Non-nil means that files should be sorted by directory.
- This is instead of the groups in `msb-menu-cond'."
- :type 'boolean
- :set 'msb-custom-set
- :group 'msb)
- (define-obsolete-variable-alias 'msb-after-load-hooks
- 'msb-after-load-hook "24.1")
- (defcustom msb-after-load-hook nil
- "Hook run after the msb package has been loaded."
- :type 'hook
- :set 'msb-custom-set
- :group 'msb)
- ;;;
- ;;; 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-handling-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)))
- ;; `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-sort-function'.
- ;;;
- (defun msb-sort-by-name (item1 item2)
- "Sort the items ITEM1 and ITEM2 by their `buffer-name'.
- An item looks like (NAME . BUFFER)."
- (string-lessp (buffer-name (cdr item1))
- (buffer-name (cdr item2))))
- (defun msb-sort-by-directory (item1 item2)
- "Sort the items ITEM1 and ITEM2 by directory name. Made for dired.
- An item look like (NAME . BUFFER)."
- (string-lessp (with-current-buffer (cdr item1)
- (msb--dired-directory))
- (with-current-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 ((old-window (selected-window))
- (window (posn-window (event-start event)))
- early-release)
- (unless (framep window) (select-window window))
- ;; This `sit-for' magically makes the menu stay up if the mouse
- ;; button is released within 0.1 second.
- (setq early-release (not (sit-for 0.1 t)))
- (let ((buffer (mouse-select-buffer event)))
- (if buffer
- (switch-to-buffer buffer)
- (select-window old-window)))
- ;; If the above `sit-for' was interrupted by a mouse-up, avoid
- ;; generating a drag event.
- (if (and early-release (memq 'down (event-modifiers last-input-event)))
- (discard-input)))
- 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 ?\s (aref (buffer-name buffer) 0))))
- (defun msb--strip-dir (dir)
- "Strip one hierarchy level from the end of DIR."
- (file-name-directory (directory-file-name dir)))
- ;; Create an alist with all buffers from LIST that lies under the same
- ;; directory will be in the same item as the directory name.
- ;; ((DIR1 . (BUFFER-1 BUFFER-2 ...)) (DIR2 . (BUFFER-K BUFFER-K+1...)) ...)
- (defun msb--init-file-alist (list)
- (let ((buffer-alist
- ;; Make alist that looks like
- ;; ((DIR-1 BUFFER-1) (DIR-2 BUFFER-2) ...)
- ;; sorted on DIR-x
- (sort
- (apply #'nconc
- (mapcar
- (lambda (buffer)
- (let ((file-name (expand-file-name
- (buffer-file-name buffer))))
- (when file-name
- (list (cons (msb--strip-dir file-name) buffer)))))
- list))
- (lambda (item1 item2)
- (string< (car item1) (car item2))))))
- ;; Now clump buffers together that have the same directory name
- ;; Make alist that looks like
- ;; ((DIR1 . (BUFFER-1 BUFFER-2 ...)) (DIR2 . (BUFFER-K)) ...)
- (let ((dir nil)
- (buffers nil))
- (nconc
- (apply
- #'nconc
- (mapcar (lambda (item)
- (cond
- ((equal dir (car item))
- ;; The same dir as earlier:
- ;; Add to current list of buffers.
- (push (cdr item) buffers)
- ;; This item should not be added to list
- nil)
- (t
- ;; New dir
- (let ((result (and dir (cons dir buffers))))
- (setq dir (car item))
- (setq buffers (list (cdr item)))
- ;; Add the last result the list.
- (and result (list result))))))
- buffer-alist))
- ;; Add the last result to the list
- (list (cons dir buffers))))))
- (defun msb--format-title (top-found-p dir number-of-items)
- "Format a suitable title for the menu item."
- (format (if top-found-p "%s... (%d)" "%s (%d)")
- (abbreviate-file-name dir) number-of-items))
- ;; Variables for debugging.
- (defvar msb--choose-file-menu-list)
- (defvar msb--choose-file-menu-arg-list)
- (defun msb--choose-file-menu (list)
- "Choose file-menu with respect to directory for every buffer in LIST."
- (setq msb--choose-file-menu-arg-list 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-dir nil)
- first rest dir buffers old-dir)
- ;; Prepare for looping over all items in buffer-alist
- (setq first (car buffer-alist)
- rest (cdr buffer-alist)
- dir (car first)
- buffers (cdr first))
- (setq msb--choose-file-menu-list (copy-sequence rest))
- ;; This big loop tries to clump buffers together that have a
- ;; similar name. Remember that buffer-alist is sorted based on the
- ;; directory name of the buffers' visited files.
- (while rest
- (let ((found-p nil)
- (tmp-rest rest)
- item)
- (setq item (car tmp-rest))
- ;; Clump together the "rest"-buffers that have a dir that is
- ;; a subdir of the current one.
- (while (and tmp-rest
- (<= (length buffers) max-clumped-together)
- (>= (length (car item)) (length dir))
- ;; `completion-ignore-case' seems to default to t
- ;; on the systems with case-insensitive file names.
- (eq t (compare-strings dir 0 nil
- (car item) 0 (length dir)
- completion-ignore-case)))
- (setq found-p t)
- (setq buffers (append buffers (cdr item))) ;nconc is faster than append
- (setq tmp-rest (cdr tmp-rest)
- item (car tmp-rest)))
- (cond
- ((> (length buffers) max-clumped-together)
- ;; Oh, we failed. Too many buffers clumped together.
- ;; Just use the original ones for the result.
- (setq last-dir (car first))
- (push (cons (msb--format-title top-found-p
- (car first)
- (length (cdr first)))
- (cdr first))
- final-list)
- (setq top-found-p nil)
- (setq first (car rest)
- rest (cdr rest)
- dir (car first)
- buffers (cdr first)))
- (t
- ;; The first pass of clumping together worked out, go ahead
- ;; with this result.
- (when found-p
- (setq top-found-p t)
- (setq first (cons dir buffers)
- rest tmp-rest))
- ;; Now see if we can clump more buffers together if we go up
- ;; one step in the file hierarchy.
- ;; If dir isn't changed by msb--strip-dir, we are looking
- ;; at the machine name component of an ange-ftp filename.
- (setq old-dir dir)
- (setq dir (msb--strip-dir dir)
- buffers (cdr first))
- (if (equal old-dir dir)
- (setq last-dir dir))
- (when (and last-dir
- (or (and (>= (length dir) (length last-dir))
- (eq t (compare-strings
- last-dir 0 nil dir 0
- (length last-dir)
- completion-ignore-case)))
- (and (< (length dir) (length last-dir))
- (eq t (compare-strings
- dir 0 nil last-dir 0 (length dir)
- completion-ignore-case)))))
- ;; We have reached the same place in the file hierarchy as
- ;; the last result, so we should quit at this point and
- ;; take what we have as result.
- (push (cons (msb--format-title top-found-p
- (car first)
- (length (cdr first)))
- (cdr first))
- final-list)
- (setq top-found-p nil)
- (setq first (car rest)
- rest (cdr rest)
- dir (car first)
- buffers (cdr first)))))))
- ;; Now take care of the last item.
- (when first
- (push (cons (msb--format-title top-found-p
- (car first)
- (length (cdr first)))
- (cdr first))
- final-list))
- (setq top-found-p nil)
- (nreverse final-list)))
- (defun msb--create-function-info (menu-cond-elt)
- "Create a vector from an element MENU-COND-ELT of `msb-menu-cond'.
- This takes the form:
- \[BUFFER-LIST-VARIABLE CONDITION MENU-SORT-KEY MENU-TITLE ITEM-HANDLER SORTER]
- See `msb-menu-cond' for a description of its elements."
- (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 indentation
- ;; 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))
- 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))
- (defun msb--add-to-menu (buffer function-info max-buffer-name-length)
- "Add BUFFER to the menu depicted by FUNCTION-INFO.
- All side-effects. Adds an element of form (BUFFER-TITLE . BUFFER)
- to the buffer-list variable in FUNCTION-INFO."
- (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)))))
- (defsubst msb--choose-menu (buffer function-info-vector max-buffer-name-length)
- "Select the appropriate menu for BUFFER."
- ;; This is all side-effects, folks!
- ;; This should be optimized.
- (unless (and (not msb-display-invisible-buffers-p)
- (msb-invisible-buffer-p buffer))
- (condition-case nil
- (with-current-buffer buffer
- ;; Menu found. Add to this menu
- (dolist (info (msb--collect function-info-vector))
- (msb--add-to-menu buffer info max-buffer-name-length)))
- (error (unless msb--error
- (setq msb--error
- (format
- "In msb-menu-cond, error for buffer `%s'."
- (buffer-name buffer)))
- (error "%s" msb--error))))))
- (defun msb--create-sort-item (function-info)
- "Return (SORT-KEY TITLE . BUFFER-LIST) or nil if the buffer-list is empty."
- (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))))))))))
- (defun msb--aggregate-alist (alist same-predicate sort-predicate)
- "Return ALIST as a sorted, aggregated alist.
- In the result all items with the same car element (according to
- SAME-PREDICATE) are aggregated together. The alist is first sorted by
- SORT-PREDICATE.
- Example:
- \(msb--aggregate-alist
- '((a . a1) (a . a2) (b . b1) (c . c3) (a . a4) (a . a3) (b . b3) (b . b2))
- (function string=)
- (lambda (item1 item2)
- (string< (symbol-name item1) (symbol-name item2))))
- results in
- \((a a1 a2 a4 a3) (b b1 b3 b2) (c c3))"
- (when (not (null alist))
- (let (same
- tmp-old-car
- tmp-same
- (first-time-p t)
- old-car)
- (nconc
- (apply #'nconc
- (mapcar
- (lambda (item)
- (cond
- (first-time-p
- (push (cdr item) same)
- (setq first-time-p nil)
- (setq old-car (car item))
- nil)
- ((funcall same-predicate (car item) old-car)
- (push (cdr item) same)
- nil)
- (t
- (setq tmp-same same
- tmp-old-car old-car)
- (setq same (list (cdr item))
- old-car (car item))
- (list (cons tmp-old-car (nreverse tmp-same))))))
- (sort alist (lambda (item1 item2)
- (funcall sort-predicate
- (car item1) (car item2))))))
- (list (cons old-car (nreverse same)))))))
- (defun msb--mode-menu-cond ()
- (let ((key msb-modes-key))
- (mapcar (lambda (item)
- (incf key)
- (list `( eq major-mode (quote ,(car item)))
- key
- (concat (cdr item) " (%d)")))
- (sort
- (let ((mode-list nil))
- (dolist (buffer (cdr (buffer-list)))
- (with-current-buffer buffer
- (when (and (not (msb-invisible-buffer-p))
- (not (assq major-mode mode-list)))
- (push (cons major-mode
- (format-mode-line mode-name nil nil buffer))
- mode-list))))
- mode-list)
- (lambda (item1 item2)
- (string< (cdr item1) (cdr item2)))))))
- (defun msb--most-recently-used-menu (max-buffer-name-length)
- "Return a list for the most recently used buffers.
- It takes the form ((TITLE . BUFFER-LIST)...)."
- (when (and (numberp msb-display-most-recently-used)
- (> msb-display-most-recently-used 0))
- (let* ((buffers (cdr (buffer-list)))
- (most-recently-used
- (loop with n = 0
- for buffer in buffers
- if (with-current-buffer buffer
- (and (not (msb-invisible-buffer-p))
- (not (eq major-mode 'dired-mode))))
- collect (with-current-buffer buffer
- (cons (funcall msb-item-handling-function
- buffer
- max-buffer-name-length)
- buffer))
- and do (incf n)
- until (>= n msb-display-most-recently-used))))
- (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.
- (dolist (buffer (buffer-list))
- (when (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))))))
- ;; 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)
- (append msb-menu-cond (msb--mode-menu-cond)))))
- ;; Split the buffer-list into several lists; one list for each
- ;; criteria. This is the most critical part with respect to time.
- (dolist (buffer (buffer-list))
- (cond ((and msb-files-by-directory
- (buffer-file-name buffer)
- ;; exclude ange-ftp buffers
- ;;(not (string-match "\\/[^/:]+:"
- ;; (buffer-file-name buffer)))
- )
- (push buffer file-buffers))
- (t
- (msb--choose-menu buffer
- function-info-vector
- max-buffer-name-length))))
- (when file-buffers
- (setq file-buffers
- (mapcar (lambda (buffer-list)
- (list* msb-files-by-directory-sort-key
- (car buffer-list)
- (sort
- (mapcar (lambda (buffer)
- (cons (with-current-buffer buffer
- (funcall
- msb-item-handling-function
- buffer
- max-buffer-name-length))
- buffer))
- (cdr buffer-list))
- (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* (menu
- (most-recently-used
- (msb--most-recently-used-menu max-buffer-name-length))
- (others (nconc 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)
- (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))))
- (defun msb--toggle-menu-type ()
- "Multi-purpose function for selecting a buffer with the mouse."
- (interactive)
- (setq msb-files-by-directory (not msb-files-by-directory))
- ;; This gets a warning, but it is correct,
- ;; because this file redefines menu-bar-update-buffers.
- (msb-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 split 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)
- choice)
- (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))))
- ;; adjust position
- (setq posX (- posX (funcall msb-horizontal-shift-function))
- position (list (list posX posY) posWind))))
- ;; Popup the menu
- (setq choice (x-popup-menu position msb--last-buffer-menu))
- (cond
- ((eq (car choice) 'toggle)
- ;; Bring up the menu again with type toggled.
- (msb--toggle-menu-type)
- (mouse-select-buffer event))
- ((and (numberp (car choice))
- (null (cdr choice)))
- (let ((msb--last-buffer-menu (nthcdr 2 (assq (car choice)
- msb--last-buffer-menu))))
- (mouse-select-buffer event)))
- ((while (numberp (car choice))
- (setq choice (cdr choice))))
- ((and (stringp (car choice))
- (null (cdr choice)))
- (car choice))
- ((null choice)
- choice)
- (t
- (error "Unknown form for buffer: %s" choice)))))
- ;; Add separators
- (defun msb--add-separators (sorted-list)
- (if (or (not msb-separator-diff)
- (not (numberp msb-separator-diff)))
- sorted-list
- (let ((last-key nil))
- (apply #'nconc
- (mapcar
- (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--split-menus-2 (list mcount result)
- (cond
- ((> (length list) msb-max-menu-items)
- (let ((count 0)
- sub-name
- (tmp-list nil))
- (while (< count msb-max-menu-items)
- (push (pop list) tmp-list)
- (incf count))
- (setq tmp-list (nreverse tmp-list))
- (setq sub-name (concat (car (car tmp-list)) "..."))
- (push (nconc (list mcount sub-name
- 'keymap sub-name)
- tmp-list)
- result))
- (msb--split-menus-2 list (1+ mcount) result))
- ((null result)
- list)
- (t
- (let (sub-name)
- (setq sub-name (concat (car (car list)) "..."))
- (push (nconc (list mcount sub-name 'keymap sub-name)
- list)
- result))
- (nreverse result))))
- (defun msb--split-menus (list)
- (if (and (integerp msb-max-menu-items)
- (> msb-max-menu-items 0))
- (msb--split-menus-2 list 0 nil)
- list))
- (defun msb--make-keymap-menu (raw-menu)
- (let ((end (cons '(nil) 'menu-bar-select-buffer))
- (mcount 0))
- (mapcar
- (lambda (sub-menu)
- (cond
- ((eq 'separator sub-menu)
- (list 'separator "--"))
- (t
- (let ((buffers (mapcar (lambda (item)
- (cons (buffer-name (cdr item))
- (cons (car item) end)))
- (cdr sub-menu))))
- (nconc (list (incf mcount) (car sub-menu)
- 'keymap (car sub-menu))
- (msb--split-menus buffers))))))
- raw-menu)))
- (defun msb-menu-bar-update-buffers (&optional arg)
- "A re-written version of `menu-bar-update-buffers'."
- ;; 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 ((frames (frame-list))
- buffers-menu frames-menu)
- ;; Make the menu of buffers proper.
- (setq msb--last-buffer-menu (msb--create-buffer-menu))
- ;; Skip the `keymap' symbol.
- (setq buffers-menu (cdr msb--last-buffer-menu))
- ;; Make a Frames menu if we have more than one frame.
- (when (cdr frames)
- (let* ((frame-length (length frames))
- (f-title (format "Frames (%d)" frame-length)))
- ;; List only the N most recently selected frames
- (when (and (integerp msb-max-menu-items)
- (> msb-max-menu-items 1)
- (> frame-length msb-max-menu-items))
- (setcdr (nthcdr msb-max-menu-items frames) nil))
- (setq frames-menu
- (nconc
- (list 'frame f-title '(nil) 'keymap f-title)
- (mapcar
- (lambda (frame)
- (nconc
- (list (frame-parameter frame 'name)
- (frame-parameter frame 'name)
- (cons nil nil))
- `(lambda ()
- (interactive) (menu-bar-select-frame ,frame))))
- frames)))))
- (setcdr global-buffers-menu-map
- (if (and buffers-menu frames-menu)
- ;; Combine Frame and Buffers menus with separator between
- (nconc (list "Buffers and Frames" frames-menu
- (and msb-separator-diff '(separator "--")))
- (cdr buffers-menu))
- buffers-menu)))))
- ;; Snarf current bindings of `mouse-buffer-menu' (normally
- ;; C-down-mouse-1).
- (defvar msb-mode-map
- (let ((map (make-sparse-keymap "Msb")))
- (define-key map [remap mouse-buffer-menu] 'msb)
- map))
- ;;;###autoload
- (define-minor-mode msb-mode
- "Toggle Msb mode.
- With a prefix argument ARG, enable Msb mode if ARG is positive,
- and disable it otherwise. If called from Lisp, enable the mode
- if ARG is omitted or nil.
- This mode overrides the binding(s) of `mouse-buffer-menu' to provide a
- different buffer menu using the function `msb'."
- :global t :group 'msb
- (if msb-mode
- (progn
- (add-hook 'menu-bar-update-hook 'msb-menu-bar-update-buffers)
- (remove-hook 'menu-bar-update-hook 'menu-bar-update-buffers)
- (msb-menu-bar-update-buffers t))
- (remove-hook 'menu-bar-update-hook 'msb-menu-bar-update-buffers)
- (add-hook 'menu-bar-update-hook 'menu-bar-update-buffers)
- (menu-bar-update-buffers t)))
- (defun msb-unload-function ()
- "Unload the Msb library."
- (msb-mode -1)
- ;; continue standard unloading
- nil)
- (provide 'msb)
- (run-hooks 'msb-after-load-hook)
- ;;; msb.el ends here
|