1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-01-30 12:21:25 -08:00

File customized.

(msb-modes-key): New variable.
(msb--mode-menu-cond, msb--aggregate-alist): New functions.
(msb--split-menus): Check if msb-max-file-menu-items is nil.
(msb--format-title): Remove extra / after ~.
(msb--choose-file-menu): Prevent looping when examining ange-ftp directory
paths.
Redundant (function ...) forms around lambda functions removed.
Update copyright year.
This commit is contained in:
Stephen Eglen 1998-02-15 16:45:52 +00:00
parent 4d7ce99c2f
commit 3cfa0ee92f

View file

@ -1,10 +1,10 @@
;;; msb.el --- Customizable buffer-selection with multiple menus.
;; Copyright (C) 1993, 1994, 1995, 1997 Free Software Foundation, Inc.
;; Copyright (C) 1993, 1994, 1995, 1997, 1998 Free Software Foundation, Inc.
;; Author: Lars Lindberg <Lars.G.Lindberg@capgemini.se>
;; Created: 8 Oct 1993
;; Lindberg's last update version: 3.33
;; Lindberg's last update version: 3.34
;; Keywords: mouse buffer menu
;; This file is part of GNU Emacs.
@ -48,7 +48,7 @@
;; 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'.
@ -62,7 +62,7 @@
;; Known bugs:
;; - Files-by-directory
;; + No possibility to show client/changed buffers separately.
;; + All file buffers only appear in in a file sub-menu, they will
;; + All file buffers only appear in a file sub-menu, they will
;; for instance not appear in the Mail sub-menu.
;; Future enhancements:
@ -164,10 +164,10 @@
;; Also note this item-sorter
msb-sort-by-directory)
((eq major-mode 'Man-mode)
4030
5030
"Manuals (%d)")
((eq major-mode 'w3-mode)
4020
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
@ -179,12 +179,12 @@
gnus-article-mode
gnus-kill-file-mode
gnus-browse-killed-mode)))
4010
5010
"Mail (%d)")
;; Catchup for all non-file buffers
((and (not buffer-file-name)
'no-multi)
4099
5099
"Other non-file buffers (%d)")
((and (string-match "/\\.[^/]*$" buffer-file-name)
'multi)
@ -210,80 +210,21 @@
;;; 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.")
(defgroup msb nil
"Customizable buffer-selection with multiple menus."
:prefix "msb-"
:group 'mouse)
(defvar msb-files-by-directory-sort-key 0
"*The sort key for files sorted by directory.")
(defun msb-custom-set (symbol value)
"Set the value of custom variables for msb."
(set symbol value)
(if (featurep 'msb)
;; wait until package has been loaded before bothering to update
;; the buffer lists.
(menu-bar-update-buffers t))
)
(defvar 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. If nil, there is 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 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.")
(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 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).")
(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 how many pixels to shift the top menu 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.
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.")
(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
(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).
@ -327,17 +268,133 @@ 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].")
error every time you do \\[msb]."
:type `(choice (const :tag "long" :value ,msb--very-many-menus)
(const :tag "short" :value ,msb--few-menus))
:set 'msb-custom-set
:group 'msb)
(defvar msb-after-load-hooks nil
"Hooks to be run after the msb package has been loaded.")
(defcustom msb-modes-key 4000
"The sort key for files sorted by mode."
:type 'integer
:set 'msb-custom-set
:group 'msb)
(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. 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 to 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 instead of
the groups in msb-menu-cond."
:type 'boolean
:set 'msb-custom-set
:group 'msb)
(defcustom msb-after-load-hooks nil
"Hooks to be run after the msb package has been loaded."
:type 'hook
:set 'msb-custom-set
:group 'msb)
;;;
;;; Internal variables
;;;
;; Home directory for the current user
(defvar msb--home-dir
(defconst msb--home-dir
(condition-case nil
(substitute-in-file-name "$HOME")
;; If $HOME isn't defined, use nil
@ -467,37 +524,35 @@ If the argument is left out or nil, then the current buffer is considered."
;; ((PATH-1 BUFFER-1) (PATH-2 BUFFER-2) ...)
;; sorted on PATH-x
(sort (mapcan
(function
(lambda (buffer)
(let ((file-name (expand-file-name (buffer-file-name buffer))))
(when file-name
(list (cons (msb--strip-dir file-name) buffer))))))
(lambda (buffer)
(let ((file-name (expand-file-name (buffer-file-name buffer))))
(when file-name
(list (cons (msb--strip-dir file-name) buffer)))))
list)
(function (lambda (item1 item2)
(string< (car item1) (car item2)))))))
(lambda (item1 item2)
(string< (car item1) (car item2))))))
;; Now clump buffers together that have the same path
;; Make alist that looks like
;; ((PATH1 . (BUFFER-1 BUFFER-2 ...)) (PATH2 . (BUFFER-K)) ...)
(let ((path nil)
(buffers nil))
(nconc
(mapcan (function
(lambda (item)
(cond
((and path
(string= path (car item)))
;; The same path as earlier: Add to current list of
;; buffers.
(push (cdr item) buffers)
;; This item should not be added to list
nil)
(t
;; New path
(let ((result (and path (cons path buffers))))
(setq path (car item))
(setq buffers (list (cdr item)))
;; Add the last result the list.
(and result (list result)))))))
(mapcan (lambda (item)
(cond
((and path
(string= path (car item)))
;; The same path as earlier: Add to current list of
;; buffers.
(push (cdr item) buffers)
;; This item should not be added to list
nil)
(t
;; New path
(let ((result (and path (cons path buffers))))
(setq path (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 path buffers))))))
@ -507,7 +562,7 @@ If the argument is left out or nil, then the current buffer is considered."
(let ((new-path path))
(when (and msb--home-dir
(string-match (concat "^" msb--home-dir) path))
(setq new-path (concat "~/"
(setq new-path (concat "~"
(substring path (match-end 0)))))
(format (if top-found-p "%s... (%d)" "%s (%d)")
new-path number-of-items)))
@ -526,7 +581,7 @@ If the argument is left out or nil, then the current buffer is considered."
10))
(top-found-p nil)
(last-path nil)
first rest path buffers)
first rest path buffers old-path)
;; Prepare for looping over all items in buffer-alist
(setq first (car buffer-alist)
rest (cdr buffer-alist)
@ -576,8 +631,13 @@ If the argument is left out or nil, then the current buffer is considered."
rest tmp-rest))
;; Now see if we can clump more buffers together if we go up
;; one step in the file hierarchy.
;; If path isn't changed by msb--strip-dir, we are looking
;; at the machine name component of an ange-ftp filename.
(setq old-path path)
(setq path (msb--strip-dir path)
buffers (cdr first))
(if (equal old-path path)
(setq last-path path))
(when (and last-path
(or (and (>= (length path) (length last-path))
(string= last-path
@ -599,11 +659,12 @@ If the argument is left out or nil, then the current buffer is considered."
path (car first)
buffers (cdr first)))))))
;; Now take care of the last item.
(push (cons (msb--format-title top-found-p
(car first)
(length (cdr first)))
(cdr first))
final-list)
(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)))
@ -646,7 +707,7 @@ If the argument is left out or nil, then the current buffer is considered."
))
;; This defsubst is only used in `msb--choose-menu' below. It was
;; pulled out merely to make the code somewhat clearer. The indention
;; pulled out merely to make the code somewhat clearer. The indentation
;; level was too big.
(defsubst msb--collect (function-info-vector)
(let ((result nil)
@ -693,9 +754,8 @@ If the argument is left out or nil, then the current buffer is considered."
(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)))
(mapc (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
@ -723,6 +783,68 @@ If the argument is left out or nil, then the current buffer is considered."
(t
(sort buffer-list sorter))))))))))
;; Return ALIST as a sorted, aggregated alist, where 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))
(defun msb--aggregate-alist (alist same-predicate sort-predicate)
(when (not (null alist))
(let (result
same
tmp-old-car
tmp-same
(first-time-p t)
old-car)
(nconc
(mapcan (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))
(mapc (lambda (buffer)
(save-excursion
(set-buffer buffer)
(when (and (not (msb-invisible-buffer-p))
(not (assq major-mode mode-list))
(push (cons major-mode mode-name)
mode-list)))))
(cdr (buffer-list)))
mode-list)
(lambda (item1 item2)
(string< (cdr item1) (cdr item2)))))))
;; 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)
@ -756,13 +878,12 @@ If the argument is left out or nil, then the current buffer is considered."
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)))))))
(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
@ -776,37 +897,40 @@ If the argument is left out or nil, then the current buffer is considered."
(setq function-info-vector
(apply (function vector)
(mapcar (function msb--create-function-info)
msb-menu-cond)))
(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.
(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)))))
(mapc (lambda (buffer)
(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))))
(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 (save-excursion
(set-buffer buffer)
(funcall msb-item-handling-function
buffer
max-buffer-name-length))
buffer)))
(cdr buffer-list))
(function
(lambda (item1 item2)
(string< (car item1) (car item2)))))))))
(mapcar (lambda (buffer-list)
(cons msb-files-by-directory-sort-key
(cons (car buffer-list)
(sort
(mapcar (function
(lambda (buffer)
(cons (save-excursion
(set-buffer buffer)
(funcall msb-item-handling-function
buffer
max-buffer-name-length))
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* (menu
@ -831,8 +955,8 @@ If the argument is left out or nil, then the current buffer is considered."
most-recently-used)
others)
others)
(function (lambda (elt1 elt2)
(< (car elt1) (car elt2))))))))
(lambda (elt1 elt2)
(< (car elt1) (car elt2)))))))
;; Now make it a keymap menu
(append
'(keymap "Select Buffer")
@ -907,7 +1031,7 @@ variable `msb-menu-cond'."
choice)
(t
(error "Unknown form for buffer: %s" choice)))))
;; Add separators
(defun msb--add-separators (sorted-list)
(cond
@ -917,19 +1041,18 @@ variable `msb-menu-cond'."
(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)))))
(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)
@ -958,31 +1081,32 @@ variable `msb-menu-cond'."
list)
result))
(nreverse result))))
(defun msb--split-menus (list)
(msb--split-menus-2 list 0 nil))
(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
(function
(lambda (sub-menu)
(cond
((eq 'separator sub-menu)
(list 'separator "--"))
(t
(let ((buffers (mapcar (function
(lambda (item)
(let ((string (car item))
(buffer (cdr item)))
(cons (buffer-name buffer)
(cons string end)))))
(cdr sub-menu))))
(nconc (list (incf mcount) (car sub-menu)
'keymap (car sub-menu))
(msb--split-menus buffers)))))))
(lambda (sub-menu)
(cond
((eq 'separator sub-menu)
(list 'separator "--"))
(t
(let ((buffers (mapcar (function
(lambda (item)
(let ((string (car item))
(buffer (cdr item)))
(cons (buffer-name buffer)
(cons string end)))))
(cdr sub-menu))))
(nconc (list (incf mcount) (car sub-menu)
'keymap (car sub-menu))
(msb--split-menus buffers))))))
raw-menu)))
(defun menu-bar-update-buffers (&optional arg)
@ -1009,14 +1133,13 @@ variable `msb-menu-cond'."
(nconc
(list 'frame f-title '(nil) 'keymap f-title)
(mapcar
(function
(lambda (frame)
(nconc
(list frame
(cdr (assq 'name
(frame-parameters frame)))
(cons nil nil))
'menu-bar-select-frame)))
(lambda (frame)
(nconc
(list frame
(cdr (assq 'name
(frame-parameters frame)))
(cons nil nil))
'menu-bar-select-frame))
frames)))))
(define-key (current-global-map) [menu-bar buffer]
(cons "Buffers"