* progmodes/ada-xref.el (ada-prj-find-prj-file):
[bpt/emacs.git] / lisp / msb.el
CommitLineData
55535639 1;;; msb.el --- customizable buffer-selection with multiple menus
b578f267 2
0d30b337 3;; Copyright (C) 1993, 1994, 1995, 1997, 1998, 1999, 2000, 2001, 2002,
d7a0267c 4;; 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
b578f267 5
17df99ea 6;; Author: Lars Lindberg <lars.lindberg@home.se>
eed30659 7;; Maintainer: FSF
b9a5a6af 8;; Created: 8 Oct 1993
3cfa0ee9 9;; Lindberg's last update version: 3.34
0eb3b336 10;; Keywords: mouse buffer menu
b578f267
EN
11
12;; This file is part of GNU Emacs.
13
14;; GNU Emacs is free software; you can redistribute it and/or modify
b9a5a6af 15;; it under the terms of the GNU General Public License as published by
b4aa6026 16;; the Free Software Foundation; either version 3, or (at your option)
b578f267
EN
17;; any later version.
18
19;; GNU Emacs is distributed in the hope that it will be useful,
b9a5a6af
RS
20;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22;; GNU General Public License for more details.
b578f267 23
b9a5a6af 24;; You should have received a copy of the GNU General Public License
b578f267 25;; along with GNU Emacs; see the file COPYING. If not, write to the
086add15
LK
26;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
27;; Boston, MA 02110-1301, USA.
b9a5a6af 28
b9a5a6af 29;;; Commentary:
b578f267 30
b9a5a6af
RS
31;; Purpose of this package:
32;; 1. Offer a function for letting the user choose buffer,
33;; not necessarily for switching to it.
aade135d
DL
34;; 2. Make a better mouse-buffer-menu. This is done as a global
35;; minor mode, msb-mode.
b9a5a6af
RS
36;;
37;; Customization:
2e6286be
RS
38;; Look at the variable `msb-menu-cond' for deciding what menus you
39;; want. It's not that hard to customize, despite my not-so-good
40;; doc-string. Feel free to send me a better doc-string.
b9a5a6af
RS
41;; There are some constants for you to try here:
42;; msb--few-menus
43;; msb--very-many-menus (default)
f1180544 44;;
2e6286be
RS
45;; Look at the variable `msb-item-handling-function' for customization
46;; of the appearance of every menu item. Try for instance setting
47;; it to `msb-alon-item-handler'.
f1180544 48;;
2e6286be
RS
49;; Look at the variable `msb-item-sort-function' for customization
50;; of sorting the menus. Set it to t for instance, which means no
b9a5a6af
RS
51;; sorting - you will get latest used buffer first.
52;;
2e6286be 53;; Also check out the variable `msb-display-invisible-buffers-p'.
b9a5a6af
RS
54
55;; Known bugs:
4aa4849b 56;; - Files-by-directory
fd46fd17 57;; + No possibility to show client/changed buffers separately.
3cfa0ee9 58;; + All file buffers only appear in a file sub-menu, they will
fd46fd17
RS
59;; for instance not appear in the Mail sub-menu.
60
b9a5a6af 61;; Future enhancements:
b9a5a6af 62
b9a5a6af 63;;; Thanks goes to
fd46fd17
RS
64;; Mark Brader <msb@sq.com>
65;; Jim Berry <m1jhb00@FRB.GOV>
66;; Hans Chalupsky <hans@cs.Buffalo.EDU>
67;; Larry Rosenberg <ljr@ictv.com>
68;; Will Henney <will@astroscu.unam.mx>
69;; Jari Aalto <jaalto@tre.tele.nokia.fi>
70;; Michael Kifer <kifer@sbkifer.cs.sunysb.edu>
71;; Gael Marziou <gael@gnlab030.grenoble.hp.com>
72;; Dave Gillespie <daveg@thymus.synaptics.com>
73;; Alon Albert <alon@milcse.rtsg.mot.com>
74;; Kevin Broadey, <KevinB@bartley.demon.co.uk>
75;; Ake Stenhof <ake@cadpoint.se>
5762abec 76;; Richard Stallman <rms@gnu.org>
fd46fd17 77;; Steve Fisk <fisk@medved.bowdoin.edu>
b9a5a6af 78
492bd758
DL
79;; This version turned into a global minor mode and subsequently
80;; hacked on by Dave Love.
b9a5a6af
RS
81;;; Code:
82
492bd758 83(eval-when-compile (require 'cl))
b9a5a6af
RS
84
85;;;
2e6286be
RS
86;;; Some example constants to be used for `msb-menu-cond'. See that
87;;; variable for more information. Please note that if the condition
88;;; returns `multi', then the buffer can appear in several menus.
b9a5a6af
RS
89;;;
90(defconst msb--few-menus
91 '(((and (boundp 'server-buffer-clients)
92 server-buffer-clients
93 'multi)
94 3030
95 "Clients (%d)")
96 ((and msb-display-invisible-buffers-p
97 (msb-invisible-buffer-p)
98 'multi)
99 3090
100 "Invisible buffers (%d)")
101 ((eq major-mode 'dired-mode)
102 2010
103 "Dired (%d)"
104 msb-dired-item-handler
105 msb-sort-by-directory)
106 ((eq major-mode 'Man-mode)
107 4090
108 "Manuals (%d)")
109 ((eq major-mode 'w3-mode)
110 4020
111 "WWW (%d)")
a4a49c21
DL
112 ((or (memq major-mode
113 '(rmail-mode rmail-edit-mode vm-summary-mode vm-mode mail-mode))
114 (memq major-mode '(mh-letter-mode mh-show-mode mh-folder-mode))
115 (memq major-mode
116 '(gnus-summary-mode message-mode gnus-group-mode
117 gnus-article-mode score-mode gnus-browse-killed-mode)))
b9a5a6af
RS
118 4010
119 "Mail (%d)")
120 ((not buffer-file-name)
121 4099
122 "Buffers (%d)")
123 ('no-multi
124 1099
125 "Files (%d)")))
126
127(defconst msb--very-many-menus
128 '(((and (boundp 'server-buffer-clients)
129 server-buffer-clients
130 'multi)
131 1010
132 "Clients (%d)")
133 ((and (boundp 'vc-mode) vc-mode 'multi)
134 1020
135 "Version Control (%d)")
136 ((and buffer-file-name
137 (buffer-modified-p)
138 'multi)
139 1030
140 "Changed files (%d)")
141 ((and (get-buffer-process (current-buffer))
142 'multi)
143 1040
144 "Processes (%d)")
145 ((and msb-display-invisible-buffers-p
146 (msb-invisible-buffer-p)
147 'multi)
148 1090
0eb3b336 149 "Invisible buffers (%d)")
b9a5a6af
RS
150 ((eq major-mode 'dired-mode)
151 2010
152 "Dired (%d)"
153 ;; Note this different menu-handler
154 msb-dired-item-handler
155 ;; Also note this item-sorter
156 msb-sort-by-directory)
157 ((eq major-mode 'Man-mode)
3cfa0ee9 158 5030
b9a5a6af
RS
159 "Manuals (%d)")
160 ((eq major-mode 'w3-mode)
3cfa0ee9 161 5020
b9a5a6af 162 "WWW (%d)")
a4a49c21
DL
163 ((or (memq major-mode
164 '(rmail-mode rmail-edit-mode vm-summary-mode vm-mode mail-mode))
165 (memq major-mode '(mh-letter-mode mh-show-mode mh-folder-mode))
166 (memq major-mode '(gnus-summary-mode message-mode gnus-group-mode
167 gnus-article-mode score-mode
b9a5a6af 168 gnus-browse-killed-mode)))
3cfa0ee9 169 5010
b9a5a6af
RS
170 "Mail (%d)")
171 ;; Catchup for all non-file buffers
172 ((and (not buffer-file-name)
173 'no-multi)
3cfa0ee9 174 5099
b9a5a6af
RS
175 "Other non-file buffers (%d)")
176 ((and (string-match "/\\.[^/]*$" buffer-file-name)
177 'multi)
178 3090
179 "Hidden Files (%d)")
180 ((memq major-mode '(c-mode c++-mode))
181 3010
182 "C/C++ Files (%d)")
183 ((eq major-mode 'emacs-lisp-mode)
184 3020
185 "Elisp Files (%d)")
186 ((eq major-mode 'latex-mode)
187 3030
8e2c8d3e 188 "LaTeX Files (%d)")
b9a5a6af
RS
189 ('no-multi
190 3099
191 "Other files (%d)")))
192
b9a5a6af
RS
193;;;
194;;; Customizable variables
195;;;
196
3cfa0ee9
SE
197(defgroup msb nil
198 "Customizable buffer-selection with multiple menus."
199 :prefix "msb-"
200 :group 'mouse)
201
202(defun msb-custom-set (symbol value)
203 "Set the value of custom variables for msb."
204 (set symbol value)
eed30659 205 (if (and (featurep 'msb) msb-mode)
3cfa0ee9
SE
206 ;; wait until package has been loaded before bothering to update
207 ;; the buffer lists.
eed30659 208 (msb-menu-bar-update-buffers t)))
3cfa0ee9
SE
209
210(defcustom msb-menu-cond msb--very-many-menus
211 "*List of criteria for splitting the mouse buffer menu.
212The elements in the list should be of this type:
213 (CONDITION MENU-SORT-KEY MENU-TITLE ITEM-HANDLING-FN ITEM-SORT-FN).
214
215When making the split, the buffers are tested one by one against the
aade135d 216CONDITION, just like a Lisp cond: When hitting a true condition, the
3cfa0ee9
SE
217other criteria are *not* tested and the buffer name will appear in the
218menu with the menu-title corresponding to the true condition.
219
220If the condition returns the symbol `multi', then the buffer will be
221added to this menu *and* tested for other menus too. If it returns
222`no-multi', then the buffer will only be added if it hasn't been added
223to any other menu.
224
225During this test, the buffer in question is the current buffer, and
226the test is surrounded by calls to `save-excursion' and
227`save-match-data'.
228
229The categories are sorted by MENU-SORT-KEY. Smaller keys are on top.
c11bf648 230A value of nil means don't display this menu.
3cfa0ee9
SE
231
232MENU-TITLE is really a format. If you add %d in it, the %d is
233replaced with the number of items in that menu.
234
20ba690c
JB
235ITEM-HANDLING-FN is optional. If it is supplied and is a function,
236then it is used for displaying the items in that particular buffer
3cfa0ee9
SE
237menu, otherwise the function pointed out by
238`msb-item-handling-function' is used.
239
20ba690c 240ITEM-SORT-FN is also optional.
3cfa0ee9
SE
241If it is not supplied, the function pointed out by
242`msb-item-sort-function' is used.
243If it is nil, then no sort takes place and the buffers are presented
244in least-recently-used order.
245If it is t, then no sort takes place and the buffers are presented in
246most-recently-used order.
247If it is supplied and non-nil and not t than it is used for sorting
248the items in that particular buffer menu.
249
250Note1: There should always be a `catch-all' as last element, in this
251list. That is an element like (t TITLE ITEM-HANDLING-FUNCTION).
252Note2: A buffer menu appears only if it has at least one buffer in it.
253Note3: If you have a CONDITION that can't be evaluated you will get an
254error every time you do \\[msb]."
255 :type `(choice (const :tag "long" :value ,msb--very-many-menus)
25bb0401
GM
256 (const :tag "short" :value ,msb--few-menus)
257 (sexp :tag "user"))
3cfa0ee9
SE
258 :set 'msb-custom-set
259 :group 'msb)
260
261(defcustom msb-modes-key 4000
262 "The sort key for files sorted by mode."
263 :type 'integer
264 :set 'msb-custom-set
f9e9ac1d
DN
265 :group 'msb
266 :version "20.3")
3cfa0ee9
SE
267
268(defcustom msb-separator-diff 100
b9a5a6af 269 "*Non-nil means use separators.
0eb3b336 270The separators will appear between all menus that have a sorting key
3cfa0ee9
SE
271that differs by this value or more."
272 :type '(choice integer (const nil))
273 :set 'msb-custom-set
274 :group 'msb)
b9a5a6af
RS
275
276(defvar msb-files-by-directory-sort-key 0
0eb3b336 277 "*The sort key for files sorted by directory.")
b9a5a6af 278
3cfa0ee9 279(defcustom msb-max-menu-items 15
b9a5a6af 280 "*The maximum number of items in a menu.
0eb3b336 281If this variable is set to 15 for instance, then the submenu will be
20ba690c 282split up in minor parts, 15 items each. A value of nil means no limit."
3cfa0ee9
SE
283 :type '(choice integer (const nil))
284 :set 'msb-custom-set
285 :group 'msb)
b9a5a6af 286
3cfa0ee9 287(defcustom msb-max-file-menu-items 10
b9a5a6af
RS
288 "*The maximum number of items from different directories.
289
2e6286be 290When the menu is of type `file by directory', this is the maximum
6331da4b 291number of buffers that are clumped together from different
b9a5a6af
RS
292directories.
293
4aa4849b
RS
294Set this to 1 if you want one menu per directory instead of clumping
295them together.
296
3cfa0ee9
SE
297If the value is not a number, then the value 10 is used."
298 :type 'integer
299 :set 'msb-custom-set
300 :group 'msb)
b9a5a6af 301
3cfa0ee9
SE
302(defcustom msb-most-recently-used-sort-key -1010
303 "*Where should the menu with the most recently used buffers be placed?"
304 :type 'integer
305 :set 'msb-custom-set
306 :group 'msb)
b9a5a6af 307
3cfa0ee9 308(defcustom msb-display-most-recently-used 15
b9a5a6af 309 "*How many buffers should be in the most-recently-used menu.
3cfa0ee9
SE
310No buffers at all if less than 1 or nil (or any non-number)."
311 :type 'integer
312 :set 'msb-custom-set
313 :group 'msb)
314
315(defcustom msb-most-recently-used-title "Most recently used (%d)"
316 "*The title for the most-recently-used menu."
317 :type 'string
318 :set 'msb-custom-set
319 :group 'msb)
f1180544 320
b9a5a6af 321(defvar msb-horizontal-shift-function '(lambda () 0)
0eb3b336 322 "*Function that specifies how many pixels to shift the top menu leftwards.")
b9a5a6af 323
3cfa0ee9 324(defcustom msb-display-invisible-buffers-p nil
b9a5a6af
RS
325 "*Show invisible buffers or not.
326Non-nil means that the buffer menu should include buffers that have
3cfa0ee9
SE
327names that starts with a space character."
328 :type 'boolean
329 :set 'msb-custom-set
330 :group 'msb)
b9a5a6af
RS
331
332(defvar msb-item-handling-function 'msb-item-handler
333 "*The appearance of a buffer menu.
334
335The default function to call for handling the appearance of a menu
20ba690c 336item. It should take two arguments, BUFFER and MAX-BUFFER-NAME-LENGTH,
b9a5a6af 337where the latter is the max length of all buffer names.
4aa4849b
RS
338
339The function should return the string to use in the menu.
340
0eb3b336
RS
341When the function is called, BUFFER is the current buffer. This
342function is called for items in the variable `msb-menu-cond' that have
343nil as ITEM-HANDLING-FUNCTION. See `msb-menu-cond' for more
b9a5a6af
RS
344information.")
345
3cfa0ee9 346(defcustom msb-item-sort-function 'msb-sort-by-name
b9a5a6af 347 "*The order of items in a buffer menu.
0eb3b336 348
b9a5a6af 349The default function to call for handling the order of items in a menu
0eb3b336
RS
350item. This function is called like a sort function. The items look
351like (ITEM-NAME . BUFFER).
352
b9a5a6af
RS
353ITEM-NAME is the name of the item that will appear in the menu.
354BUFFER is the buffer, this is not necessarily the current buffer.
355
3cfa0ee9
SE
356Set this to nil or t if you don't want any sorting (faster)."
357 :type '(choice (const msb-sort-by-name)
358 (const :tag "Newest first" t)
359 (const :tag "Oldest first" nil))
360 :set 'msb-custom-set
a4a49c21 361 :group 'msb)
f1180544 362
3cfa0ee9 363(defcustom msb-files-by-directory nil
aade135d
DL
364 "*Non-nil means that files should be sorted by directory.
365This is instead of the groups in `msb-menu-cond'."
3cfa0ee9
SE
366 :type 'boolean
367 :set 'msb-custom-set
368 :group 'msb)
b9a5a6af 369
3392cf05
SM
370(defcustom msb-after-load-hook nil
371 "Hook run after the msb package has been loaded."
3cfa0ee9
SE
372 :type 'hook
373 :set 'msb-custom-set
374 :group 'msb)
b9a5a6af
RS
375
376;;;
377;;; Internal variables
378;;;
379
380;; The last calculated menu.
381(defvar msb--last-buffer-menu nil)
382
383;; If this is non-nil, then it is a string that describes the error.
384(defvar msb--error nil)
385
386;;;
4aa4849b 387;;; Some example function to be used for `msb-item-handling-function'.
b9a5a6af
RS
388;;;
389(defun msb-item-handler (buffer &optional maxbuf)
390 "Create one string item, concerning BUFFER, for the buffer menu.
391The item looks like:
392*% <buffer-name>
2e6286be
RS
393The `*' appears only if the buffer is marked as modified.
394The `%' appears only if the buffer is read-only.
b9a5a6af
RS
395Optional second argument MAXBUF is completely ignored."
396 (let ((name (buffer-name))
397 (modified (if (buffer-modified-p) "*" " "))
398 (read-only (if buffer-read-only "%" " ")))
399 (format "%s%s %s" modified read-only name)))
400
401
402(eval-when-compile (require 'dired))
403
2e6286be
RS
404;; `dired' can be called with a list of the form (directory file1 file2 ...)
405;; which causes `dired-directory' to be in the same form.
b9a5a6af
RS
406(defun msb--dired-directory ()
407 (cond ((stringp dired-directory)
408 (abbreviate-file-name (expand-file-name dired-directory)))
409 ((consp dired-directory)
410 (abbreviate-file-name (expand-file-name (car dired-directory))))
411 (t
2e6286be 412 (error "Unknown type of `dired-directory' in buffer %s"
b9a5a6af
RS
413 (buffer-name)))))
414
415(defun msb-dired-item-handler (buffer &optional maxbuf)
416 "Create one string item, concerning a dired BUFFER, for the buffer menu.
417The item looks like:
418*% <buffer-name>
2e6286be
RS
419The `*' appears only if the buffer is marked as modified.
420The `%' appears only if the buffer is read-only.
b9a5a6af
RS
421Optional second argument MAXBUF is completely ignored."
422 (let ((name (msb--dired-directory))
423 (modified (if (buffer-modified-p) "*" " "))
424 (read-only (if buffer-read-only "%" " ")))
425 (format "%s%s %s" modified read-only name)))
426
427(defun msb-alon-item-handler (buffer maxbuf)
428 "Create one string item for the buffer menu.
429The item looks like:
430<buffer-name> *%# <file-name>
2e6286be
RS
431The `*' appears only if the buffer is marked as modified.
432The `%' appears only if the buffer is read-only.
433The `#' appears only version control file (SCCS/RCS)."
b9a5a6af
RS
434 (format (format "%%%ds %%s%%s%%s %%s" maxbuf)
435 (buffer-name buffer)
436 (if (buffer-modified-p) "*" " ")
437 (if buffer-read-only "%" " ")
438 (if (and (boundp 'vc-mode) vc-mode) "#" " ")
439 (or buffer-file-name "")))
440
441;;;
4aa4849b 442;;; Some example function to be used for `msb-item-sort-function'.
b9a5a6af
RS
443;;;
444(defun msb-sort-by-name (item1 item2)
aade135d
DL
445 "Sort the items ITEM1 and ITEM2 by their `buffer-name'.
446An item looks like (NAME . BUFFER)."
b9a5a6af
RS
447 (string-lessp (buffer-name (cdr item1))
448 (buffer-name (cdr item2))))
449
450
451(defun msb-sort-by-directory (item1 item2)
aade135d 452 "Sort the items ITEM1 and ITEM2 by directory name. Made for dired.
b9a5a6af 453An item look like (NAME . BUFFER)."
7ccc8f70
SM
454 (string-lessp (with-current-buffer (cdr item1)
455 (msb--dired-directory))
456 (with-current-buffer (cdr item2)
457 (msb--dired-directory))))
b9a5a6af
RS
458
459;;;
460;;; msb
461;;;
462;;; This function can be used instead of (mouse-buffer-menu EVENT)
463;;; function in "mouse.el".
0eb3b336 464;;;
b9a5a6af
RS
465(defun msb (event)
466 "Pop up several menus of buffers for selection with the mouse.
467This command switches buffers in the window that you clicked on, and
468selects that window.
469
2e6286be
RS
470See the function `mouse-select-buffer' and the variable
471`msb-menu-cond' for more information about how the menus are split."
b9a5a6af 472 (interactive "e")
fd46fd17 473 (let ((old-window (selected-window))
809b6e98
CY
474 (window (posn-window (event-start event)))
475 early-release)
fd46fd17 476 (unless (framep window) (select-window window))
809b6e98
CY
477 ;; This `sit-for' magically makes the menu stay up if the mouse
478 ;; button is released within 0.1 second.
479 (setq early-release (not (sit-for 0.1 t)))
fd46fd17
RS
480 (let ((buffer (mouse-select-buffer event)))
481 (if buffer
482 (switch-to-buffer buffer)
809b6e98
CY
483 (select-window old-window)))
484 ;; If the above `sit-for' was interrupted by a mouse-up, avoid
485 ;; generating a drag event.
486 (if (and early-release (memq 'down (event-modifiers last-input-event)))
487 (discard-input)))
b9a5a6af
RS
488 nil)
489
490;;;
491;;; Some supportive functions
492;;;
493(defun msb-invisible-buffer-p (&optional buffer)
494 "Return t if optional BUFFER is an \"invisible\" buffer.
495If the argument is left out or nil, then the current buffer is considered."
496 (and (> (length (buffer-name buffer)) 0)
e665a469 497 (eq ?\s (aref (buffer-name buffer) 0))))
b9a5a6af 498
7612d61a 499(defun msb--strip-dir (dir)
eed30659 500 "Strip one hierarchy level from the end of DIR."
862aacbf 501 (file-name-directory (directory-file-name dir)))
b9a5a6af
RS
502
503;; Create an alist with all buffers from LIST that lies under the same
965440e6
KS
504;; directory will be in the same item as the directory name.
505;; ((DIR1 . (BUFFER-1 BUFFER-2 ...)) (DIR2 . (BUFFER-K BUFFER-K+1...)) ...)
b9a5a6af
RS
506(defun msb--init-file-alist (list)
507 (let ((buffer-alist
0eb3b336 508 ;; Make alist that looks like
965440e6
KS
509 ;; ((DIR-1 BUFFER-1) (DIR-2 BUFFER-2) ...)
510 ;; sorted on DIR-x
a4a49c21
DL
511 (sort
512 (apply #'nconc
513 (mapcar
514 (lambda (buffer)
515 (let ((file-name (expand-file-name
516 (buffer-file-name buffer))))
517 (when file-name
518 (list (cons (msb--strip-dir file-name) buffer)))))
519 list))
520 (lambda (item1 item2)
521 (string< (car item1) (car item2))))))
965440e6 522 ;; Now clump buffers together that have the same directory name
b9a5a6af 523 ;; Make alist that looks like
965440e6
KS
524 ;; ((DIR1 . (BUFFER-1 BUFFER-2 ...)) (DIR2 . (BUFFER-K)) ...)
525 (let ((dir nil)
0eb3b336
RS
526 (buffers nil))
527 (nconc
a4a49c21
DL
528 (apply
529 #'nconc
530 (mapcar (lambda (item)
531 (cond
965440e6
KS
532 ((equal dir (car item))
533 ;; The same dir as earlier:
534 ;; Add to current list of buffers.
a4a49c21
DL
535 (push (cdr item) buffers)
536 ;; This item should not be added to list
537 nil)
538 (t
965440e6
KS
539 ;; New dir
540 (let ((result (and dir (cons dir buffers))))
541 (setq dir (car item))
a4a49c21
DL
542 (setq buffers (list (cdr item)))
543 ;; Add the last result the list.
544 (and result (list result))))))
545 buffer-alist))
0eb3b336 546 ;; Add the last result to the list
965440e6 547 (list (cons dir buffers))))))
b9a5a6af 548
965440e6 549(defun msb--format-title (top-found-p dir number-of-items)
eed30659 550 "Format a suitable title for the menu item."
492bd758 551 (format (if top-found-p "%s... (%d)" "%s (%d)")
965440e6 552 (abbreviate-file-name dir) number-of-items))
0eb3b336 553
c549c1bf
RS
554;; Variables for debugging.
555(defvar msb--choose-file-menu-list)
556(defvar msb--choose-file-menu-arg-list)
0eb3b336 557
b9a5a6af 558(defun msb--choose-file-menu (list)
eed30659 559 "Choose file-menu with respect to directory for every buffer in LIST."
c549c1bf 560 (setq msb--choose-file-menu-arg-list list)
b9a5a6af
RS
561 (let ((buffer-alist (msb--init-file-alist list))
562 (final-list nil)
563 (max-clumped-together (if (numberp msb-max-file-menu-items)
564 msb-max-file-menu-items
565 10))
566 (top-found-p nil)
965440e6
KS
567 (last-dir nil)
568 first rest dir buffers old-dir)
0eb3b336
RS
569 ;; Prepare for looping over all items in buffer-alist
570 (setq first (car buffer-alist)
571 rest (cdr buffer-alist)
965440e6 572 dir (car first)
0eb3b336 573 buffers (cdr first))
a4a49c21 574 (setq msb--choose-file-menu-list (copy-sequence rest))
0eb3b336
RS
575 ;; This big loop tries to clump buffers together that have a
576 ;; similar name. Remember that buffer-alist is sorted based on the
965440e6 577 ;; directory name of the buffers' visited files.
b9a5a6af
RS
578 (while rest
579 (let ((found-p nil)
580 (tmp-rest rest)
7ccc8f70 581 item)
b9a5a6af 582 (setq item (car tmp-rest))
965440e6
KS
583 ;; Clump together the "rest"-buffers that have a dir that is
584 ;; a subdir of the current one.
b9a5a6af
RS
585 (while (and tmp-rest
586 (<= (length buffers) max-clumped-together)
965440e6 587 (>= (length (car item)) (length dir))
b9b37d2b
DL
588 ;; `completion-ignore-case' seems to default to t
589 ;; on the systems with case-insensitive file names.
965440e6
KS
590 (eq t (compare-strings dir 0 nil
591 (car item) 0 (length dir)
b9b37d2b 592 completion-ignore-case)))
b9a5a6af 593 (setq found-p t)
0eb3b336
RS
594 (setq buffers (append buffers (cdr item))) ;nconc is faster than append
595 (setq tmp-rest (cdr tmp-rest)
596 item (car tmp-rest)))
b9a5a6af
RS
597 (cond
598 ((> (length buffers) max-clumped-together)
0eb3b336
RS
599 ;; Oh, we failed. Too many buffers clumped together.
600 ;; Just use the original ones for the result.
965440e6 601 (setq last-dir (car first))
0eb3b336
RS
602 (push (cons (msb--format-title top-found-p
603 (car first)
604 (length (cdr first)))
605 (cdr first))
606 final-list)
4aa4849b 607 (setq top-found-p nil)
b9a5a6af 608 (setq first (car rest)
0eb3b336 609 rest (cdr rest)
965440e6 610 dir (car first)
b9a5a6af
RS
611 buffers (cdr first)))
612 (t
0eb3b336
RS
613 ;; The first pass of clumping together worked out, go ahead
614 ;; with this result.
b9a5a6af
RS
615 (when found-p
616 (setq top-found-p t)
965440e6 617 (setq first (cons dir buffers)
b9a5a6af 618 rest tmp-rest))
0eb3b336
RS
619 ;; Now see if we can clump more buffers together if we go up
620 ;; one step in the file hierarchy.
965440e6 621 ;; If dir isn't changed by msb--strip-dir, we are looking
3cfa0ee9 622 ;; at the machine name component of an ange-ftp filename.
965440e6
KS
623 (setq old-dir dir)
624 (setq dir (msb--strip-dir dir)
b9a5a6af 625 buffers (cdr first))
965440e6
KS
626 (if (equal old-dir dir)
627 (setq last-dir dir))
628 (when (and last-dir
629 (or (and (>= (length dir) (length last-dir))
b9b37d2b 630 (eq t (compare-strings
965440e6
KS
631 last-dir 0 nil dir 0
632 (length last-dir)
b9b37d2b 633 completion-ignore-case)))
965440e6 634 (and (< (length dir) (length last-dir))
b9b37d2b 635 (eq t (compare-strings
965440e6 636 dir 0 nil last-dir 0 (length dir)
b9b37d2b 637 completion-ignore-case)))))
0eb3b336
RS
638 ;; We have reached the same place in the file hierarchy as
639 ;; the last result, so we should quit at this point and
640 ;; take what we have as result.
641 (push (cons (msb--format-title top-found-p
642 (car first)
643 (length (cdr first)))
644 (cdr first))
645 final-list)
4aa4849b 646 (setq top-found-p nil)
b9a5a6af 647 (setq first (car rest)
0eb3b336 648 rest (cdr rest)
965440e6 649 dir (car first)
0eb3b336
RS
650 buffers (cdr first)))))))
651 ;; Now take care of the last item.
3cfa0ee9
SE
652 (when first
653 (push (cons (msb--format-title top-found-p
654 (car first)
655 (length (cdr first)))
656 (cdr first))
657 final-list))
4aa4849b 658 (setq top-found-p nil)
b9a5a6af
RS
659 (nreverse final-list)))
660
b9a5a6af 661(defun msb--create-function-info (menu-cond-elt)
eed30659
DL
662 "Create a vector from an element MENU-COND-ELT of `msb-menu-cond'.
663This takes the form:
20ba690c 664\[BUFFER-LIST-VARIABLE CONDITION MENU-SORT-KEY MENU-TITLE ITEM-HANDLER SORTER]
eed30659 665See `msb-menu-cond' for a description of its elements."
b9a5a6af
RS
666 (let* ((list-symbol (make-symbol "-msb-buffer-list"))
667 (tmp-ih (and (> (length menu-cond-elt) 3)
668 (nth 3 menu-cond-elt)))
669 (item-handler (if (and tmp-ih (fboundp tmp-ih))
670 tmp-ih
671 msb-item-handling-function))
672 (tmp-s (if (> (length menu-cond-elt) 4)
673 (nth 4 menu-cond-elt)
674 msb-item-sort-function))
675 (sorter (if (or (fboundp tmp-s)
676 (null tmp-s)
2e6286be 677 (eq tmp-s t))
a4a49c21 678 tmp-s
b9a5a6af
RS
679 msb-item-sort-function)))
680 (when (< (length menu-cond-elt) 3)
aade135d 681 (error "Wrong format of msb-menu-cond"))
b9a5a6af
RS
682 (when (and (> (length menu-cond-elt) 3)
683 (not (fboundp tmp-ih)))
684 (signal 'invalid-function (list tmp-ih)))
685 (when (and (> (length menu-cond-elt) 4)
686 tmp-s
687 (not (fboundp tmp-s))
2e6286be 688 (not (eq tmp-s t)))
b9a5a6af 689 (signal 'invalid-function (list tmp-s)))
2e6286be 690 (set list-symbol ())
b9a5a6af
RS
691 (vector list-symbol ;BUFFER-LIST-VARIABLE
692 (nth 0 menu-cond-elt) ;CONDITION
693 (nth 1 menu-cond-elt) ;SORT-KEY
694 (nth 2 menu-cond-elt) ;MENU-TITLE
695 item-handler ;ITEM-HANDLER
696 sorter) ;SORTER
697 ))
698
699;; This defsubst is only used in `msb--choose-menu' below. It was
3cfa0ee9 700;; pulled out merely to make the code somewhat clearer. The indentation
b9a5a6af
RS
701;; level was too big.
702(defsubst msb--collect (function-info-vector)
703 (let ((result nil)
704 (multi-flag nil)
705 function-info-list)
706 (setq function-info-list
707 (loop for fi
708 across function-info-vector
709 if (and (setq result
710 (eval (aref fi 1))) ;Test CONDITION
711 (not (and (eq result 'no-multi)
712 multi-flag))
713 (progn (when (eq result 'multi)
714 (setq multi-flag t))
4aa4849b 715 t))
b9a5a6af
RS
716 collect fi
717 until (and result
718 (not (eq result 'multi)))))
719 (when (and (not function-info-list)
720 (not result))
721 (error "No catch-all in msb-menu-cond!"))
722 function-info-list))
723
b9a5a6af 724(defun msb--add-to-menu (buffer function-info max-buffer-name-length)
eed30659
DL
725 "Add BUFFER to the menu depicted by FUNCTION-INFO.
726All side-effects. Adds an element of form (BUFFER-TITLE . BUFFER)
20ba690c 727to the buffer-list variable in FUNCTION-INFO."
b9a5a6af
RS
728 (let ((list-symbol (aref function-info 0))) ;BUFFER-LIST-VARIABLE
729 ;; Here comes the hairy side-effect!
730 (set list-symbol
731 (cons (cons (funcall (aref function-info 4) ;ITEM-HANDLER
732 buffer
733 max-buffer-name-length)
734 buffer)
735 (eval list-symbol)))))
f1180544 736
b9a5a6af 737(defsubst msb--choose-menu (buffer function-info-vector max-buffer-name-length)
eed30659
DL
738 "Select the appropriate menu for BUFFER."
739 ;; This is all side-effects, folks!
740 ;; This should be optimized.
b9a5a6af
RS
741 (unless (and (not msb-display-invisible-buffers-p)
742 (msb-invisible-buffer-p buffer))
743 (condition-case nil
7ccc8f70 744 (with-current-buffer buffer
2e6286be 745 ;; Menu found. Add to this menu
b2eb3813
GM
746 (dolist (info (msb--collect function-info-vector))
747 (msb--add-to-menu buffer info max-buffer-name-length)))
b9a5a6af
RS
748 (error (unless msb--error
749 (setq msb--error
750 (format
2e6286be 751 "In msb-menu-cond, error for buffer `%s'."
b9a5a6af 752 (buffer-name buffer)))
76e4c0ba 753 (error "%s" msb--error))))))
b9a5a6af 754
b9a5a6af 755(defun msb--create-sort-item (function-info)
eed30659 756 "Return (SORT-KEY TITLE . BUFFER-LIST) or nil if the buffer-list is empty."
b9a5a6af
RS
757 (let ((buffer-list (eval (aref function-info 0))))
758 (when buffer-list
759 (let ((sorter (aref function-info 5)) ;SORTER
760 (sort-key (aref function-info 2))) ;MENU-SORT-KEY
761 (when sort-key
0eb3b336 762 (cons sort-key
b9a5a6af
RS
763 (cons (format (aref function-info 3) ;MENU-TITLE
764 (length buffer-list))
765 (cond
766 ((null sorter)
767 buffer-list)
2e6286be 768 ((eq sorter t)
b9a5a6af
RS
769 (nreverse buffer-list))
770 (t
771 (sort buffer-list sorter))))))))))
772
3cfa0ee9 773(defun msb--aggregate-alist (alist same-predicate sort-predicate)
eed30659
DL
774 "Return ALIST as a sorted, aggregated alist.
775
776In the result all items with the same car element (according to
777SAME-PREDICATE) are aggregated together. The alist is first sorted by
778SORT-PREDICATE.
779
780Example:
b2eb3813 781\(msb--aggregate-alist
eed30659
DL
782 '((a . a1) (a . a2) (b . b1) (c . c3) (a . a4) (a . a3) (b . b3) (b . b2))
783 (function string=)
784 (lambda (item1 item2)
785 (string< (symbol-name item1) (symbol-name item2))))
786results in
b2eb3813 787\((a a1 a2 a4 a3) (b b1 b3 b2) (c c3))"
3cfa0ee9 788 (when (not (null alist))
7ccc8f70 789 (let (same
3cfa0ee9
SE
790 tmp-old-car
791 tmp-same
792 (first-time-p t)
793 old-car)
794 (nconc
a4a49c21
DL
795 (apply #'nconc
796 (mapcar
797 (lambda (item)
3cfa0ee9
SE
798 (cond
799 (first-time-p
800 (push (cdr item) same)
801 (setq first-time-p nil)
802 (setq old-car (car item))
803 nil)
804 ((funcall same-predicate (car item) old-car)
805 (push (cdr item) same)
806 nil)
807 (t
808 (setq tmp-same same
809 tmp-old-car old-car)
810 (setq same (list (cdr item))
811 old-car (car item))
812 (list (cons tmp-old-car (nreverse tmp-same))))))
813 (sort alist (lambda (item1 item2)
7ccc8f70
SM
814 (funcall sort-predicate
815 (car item1) (car item2))))))
3cfa0ee9
SE
816 (list (cons old-car (nreverse same)))))))
817
818
819(defun msb--mode-menu-cond ()
820 (let ((key msb-modes-key))
821 (mapcar (lambda (item)
822 (incf key)
823 (list `( eq major-mode (quote ,(car item)))
824 key
825 (concat (cdr item) " (%d)")))
aade135d 826 (sort
3cfa0ee9 827 (let ((mode-list nil))
b2eb3813 828 (dolist (buffer (cdr (buffer-list)))
7ccc8f70 829 (with-current-buffer buffer
b2eb3813
GM
830 (when (and (not (msb-invisible-buffer-p))
831 (not (assq major-mode mode-list)))
832 (push (cons major-mode mode-name)
833 mode-list))))
3cfa0ee9
SE
834 mode-list)
835 (lambda (item1 item2)
836 (string< (cdr item1) (cdr item2)))))))
837
b9a5a6af 838(defun msb--most-recently-used-menu (max-buffer-name-length)
eed30659
DL
839 "Return a list for the most recently used buffers.
840It takes the form ((TITLE . BUFFER-LIST)...)."
4aa4849b
RS
841 (when (and (numberp msb-display-most-recently-used)
842 (> msb-display-most-recently-used 0))
fd46fd17
RS
843 (let* ((buffers (cdr (buffer-list)))
844 (most-recently-used
b9a5a6af 845 (loop with n = 0
fd46fd17 846 for buffer in buffers
7ccc8f70 847 if (with-current-buffer buffer
b9a5a6af
RS
848 (and (not (msb-invisible-buffer-p))
849 (not (eq major-mode 'dired-mode))))
7ccc8f70 850 collect (with-current-buffer buffer
b9a5a6af
RS
851 (cons (funcall msb-item-handling-function
852 buffer
853 max-buffer-name-length)
854 buffer))
855 and do (incf n)
4aa4849b 856 until (>= n msb-display-most-recently-used))))
b9a5a6af
RS
857 (cons (if (stringp msb-most-recently-used-title)
858 (format msb-most-recently-used-title
859 (length most-recently-used))
860 (signal 'wrong-type-argument (list msb-most-recently-used-title)))
861 most-recently-used))))
862
863(defun msb--create-buffer-menu-2 ()
864 (let ((max-buffer-name-length 0)
865 file-buffers
866 function-info-vector)
867 ;; Calculate the longest buffer name.
b2eb3813
GM
868 (dolist (buffer (buffer-list))
869 (when (or msb-display-invisible-buffers-p
870 (not (msb-invisible-buffer-p)))
871 (setq max-buffer-name-length
872 (max max-buffer-name-length (length (buffer-name buffer))))))
b9a5a6af
RS
873 ;; Make a list with elements of type
874 ;; (BUFFER-LIST-VARIABLE
875 ;; CONDITION
876 ;; MENU-SORT-KEY
877 ;; MENU-TITLE
878 ;; ITEM-HANDLER
879 ;; SORTER)
880 ;; Uses "function-global" variables:
881 ;; function-info-vector
882 (setq function-info-vector
883 (apply (function vector)
884 (mapcar (function msb--create-function-info)
3cfa0ee9 885 (append msb-menu-cond (msb--mode-menu-cond)))))
b9a5a6af 886 ;; Split the buffer-list into several lists; one list for each
2e6286be 887 ;; criteria. This is the most critical part with respect to time.
b2eb3813
GM
888 (dolist (buffer (buffer-list))
889 (cond ((and msb-files-by-directory
890 (buffer-file-name buffer)
891 ;; exclude ange-ftp buffers
892 ;;(not (string-match "\\/[^/:]+:"
893 ;; (buffer-file-name buffer)))
894 )
895 (push buffer file-buffers))
896 (t
897 (msb--choose-menu buffer
898 function-info-vector
899 max-buffer-name-length))))
b9a5a6af
RS
900 (when file-buffers
901 (setq file-buffers
3cfa0ee9 902 (mapcar (lambda (buffer-list)
7ccc8f70
SM
903 (list* msb-files-by-directory-sort-key
904 (car buffer-list)
905 (sort
906 (mapcar (lambda (buffer)
907 (cons (with-current-buffer buffer
908 (funcall
909 msb-item-handling-function
910 buffer
911 max-buffer-name-length))
912 buffer))
913 (cdr buffer-list))
914 (lambda (item1 item2)
915 (string< (car item1) (car item2))))))
916 (msb--choose-file-menu file-buffers))))
b9a5a6af 917 ;; Now make the menu - a list of (TITLE . BUFFER-LIST)
4aa4849b 918 (let* (menu
b9a5a6af
RS
919 (most-recently-used
920 (msb--most-recently-used-menu max-buffer-name-length))
0eb3b336 921 (others (nconc file-buffers
b9a5a6af 922 (loop for elt
4aa4849b
RS
923 across function-info-vector
924 for value = (msb--create-sort-item elt)
925 if value collect value))))
b9a5a6af
RS
926 (setq menu
927 (mapcar 'cdr ;Remove the SORT-KEY
928 ;; Sort the menus - not the items.
929 (msb--add-separators
930 (sort
931 ;; Get a list of (SORT-KEY TITLE . BUFFER-LIST)
932 ;; Also sorts the items within the menus.
933 (if (cdr most-recently-used)
934 (cons
935 ;; Add most recent used buffers
936 (cons msb-most-recently-used-sort-key
937 most-recently-used)
938 others)
939 others)
3cfa0ee9
SE
940 (lambda (elt1 elt2)
941 (< (car elt1) (car elt2)))))))
b9a5a6af
RS
942 ;; Now make it a keymap menu
943 (append
944 '(keymap "Select Buffer")
945 (msb--make-keymap-menu menu)
946 (when msb-separator-diff
0eb3b336
RS
947 (list (list 'separator "--")))
948 (list (cons 'toggle
b9a5a6af
RS
949 (cons
950 (if msb-files-by-directory
a4a49c21
DL
951 "*Files by type*"
952 "*Files by directory*")
953 'msb--toggle-menu-type)))))))
b9a5a6af 954
0b704e15 955(defun msb--create-buffer-menu ()
b9a5a6af
RS
956 (save-match-data
957 (save-excursion
958 (msb--create-buffer-menu-2))))
959
b9a5a6af 960(defun msb--toggle-menu-type ()
20ba690c 961 "Multi-purpose function for selecting a buffer with the mouse."
b9a5a6af
RS
962 (interactive)
963 (setq msb-files-by-directory (not msb-files-by-directory))
c549c1bf
RS
964 ;; This gets a warning, but it is correct,
965 ;; because this file redefines menu-bar-update-buffers.
eed30659 966 (msb-menu-bar-update-buffers t))
b9a5a6af
RS
967
968(defun mouse-select-buffer (event)
969 "Pop up several menus of buffers, for selection with the mouse.
970Returns the selected buffer or nil if no buffer is selected.
971
4aa4849b 972The way the buffers are split is conveniently handled with the
2e6286be 973variable `msb-menu-cond'."
b9a5a6af
RS
974 ;; Popup the menu and return the selected buffer.
975 (when (or msb--error
976 (not msb--last-buffer-menu)
977 (not (fboundp 'frame-or-buffer-changed-p))
978 (frame-or-buffer-changed-p))
979 (setq msb--error nil)
980 (setq msb--last-buffer-menu (msb--create-buffer-menu)))
4aa4849b
RS
981 (let ((position event)
982 choice)
b9a5a6af
RS
983 (when (and (fboundp 'posn-x-y)
984 (fboundp 'posn-window))
985 (let ((posX (car (posn-x-y (event-start event))))
986 (posY (cdr (posn-x-y (event-start event))))
4aa4849b 987 (posWind (posn-window (event-start event))))
b9a5a6af
RS
988 ;; adjust position
989 (setq posX (- posX (funcall msb-horizontal-shift-function))
990 position (list (list posX posY) posWind))))
1cc9a99e 991 ;; Popup the menu
4aa4849b 992 (setq choice (x-popup-menu position msb--last-buffer-menu))
b9a5a6af 993 (cond
4aa4849b
RS
994 ((eq (car choice) 'toggle)
995 ;; Bring up the menu again with type toggled.
996 (msb--toggle-menu-type)
997 (mouse-select-buffer event))
998 ((and (numberp (car choice))
999 (null (cdr choice)))
122e29de 1000 (let ((msb--last-buffer-menu (nthcdr 2 (assq (car choice)
a4a49c21 1001 msb--last-buffer-menu))))
b9a5a6af 1002 (mouse-select-buffer event)))
4aa4849b
RS
1003 ((while (numberp (car choice))
1004 (setq choice (cdr choice))))
1005 ((and (stringp (car choice))
1006 (null (cdr choice)))
1007 (car choice))
dc3247b3
RS
1008 ((null choice)
1009 choice)
4aa4849b
RS
1010 (t
1011 (error "Unknown form for buffer: %s" choice)))))
3cfa0ee9 1012
b9a5a6af
RS
1013;; Add separators
1014(defun msb--add-separators (sorted-list)
a4a49c21
DL
1015 (if (or (not msb-separator-diff)
1016 (not (numberp msb-separator-diff)))
1017 sorted-list
b9a5a6af 1018 (let ((last-key nil))
a4a49c21
DL
1019 (apply #'nconc
1020 (mapcar
1021 (lambda (item)
1022 (cond
1023 ((and msb-separator-diff
1024 last-key
1025 (> (- (car item) last-key)
1026 msb-separator-diff))
1027 (setq last-key (car item))
1028 (list (cons last-key 'separator)
1029 item))
1030 (t
1031 (setq last-key (car item))
1032 (list item))))
1033 sorted-list)))))
b9a5a6af 1034
4aa4849b
RS
1035(defun msb--split-menus-2 (list mcount result)
1036 (cond
1037 ((> (length list) msb-max-menu-items)
1038 (let ((count 0)
1039 sub-name
1040 (tmp-list nil))
1041 (while (< count msb-max-menu-items)
1042 (push (pop list) tmp-list)
1043 (incf count))
a4a49c21
DL
1044 (setq tmp-list (nreverse tmp-list))
1045 (setq sub-name (concat (car (car tmp-list)) "..."))
1046 (push (nconc (list mcount sub-name
1047 'keymap sub-name)
1048 tmp-list)
1049 result))
4aa4849b
RS
1050 (msb--split-menus-2 list (1+ mcount) result))
1051 ((null result)
1052 list)
1053 (t
1054 (let (sub-name)
1055 (setq sub-name (concat (car (car list)) "..."))
a4a49c21
DL
1056 (push (nconc (list mcount sub-name 'keymap sub-name)
1057 list)
1058 result))
4aa4849b 1059 (nreverse result))))
4aa4849b 1060
3cfa0ee9
SE
1061(defun msb--split-menus (list)
1062 (if (and (integerp msb-max-menu-items)
1063 (> msb-max-menu-items 0))
1064 (msb--split-menus-2 list 0 nil)
1065 list))
4aa4849b 1066
b9a5a6af
RS
1067(defun msb--make-keymap-menu (raw-menu)
1068 (let ((end (cons '(nil) 'menu-bar-select-buffer))
1069 (mcount 0))
1070 (mapcar
3cfa0ee9 1071 (lambda (sub-menu)
aade135d 1072 (cond
3cfa0ee9
SE
1073 ((eq 'separator sub-menu)
1074 (list 'separator "--"))
1075 (t
a4a49c21
DL
1076 (let ((buffers (mapcar (lambda (item)
1077 (cons (buffer-name (cdr item))
1078 (cons (car item) end)))
3cfa0ee9
SE
1079 (cdr sub-menu))))
1080 (nconc (list (incf mcount) (car sub-menu)
1081 'keymap (car sub-menu))
1082 (msb--split-menus buffers))))))
b9a5a6af
RS
1083 raw-menu)))
1084
eed30659
DL
1085(defun msb-menu-bar-update-buffers (&optional arg)
1086 "A re-written version of `menu-bar-update-buffers'."
b9a5a6af
RS
1087 ;; If user discards the Buffers item, play along.
1088 (when (and (lookup-key (current-global-map) [menu-bar buffer])
1089 (or (not (fboundp 'frame-or-buffer-changed-p))
1090 (frame-or-buffer-changed-p)
1091 arg))
fd46fd17 1092 (let ((frames (frame-list))
b9a5a6af 1093 buffers-menu frames-menu)
b9a5a6af
RS
1094 ;; Make the menu of buffers proper.
1095 (setq msb--last-buffer-menu (msb--create-buffer-menu))
7ccc8f70
SM
1096 ;; Skip the `keymap' symbol.
1097 (setq buffers-menu (cdr msb--last-buffer-menu))
b9a5a6af 1098 ;; Make a Frames menu if we have more than one frame.
fd46fd17
RS
1099 (when (cdr frames)
1100 (let* ((frame-length (length frames))
1101 (f-title (format "Frames (%d)" frame-length)))
1102 ;; List only the N most recently selected frames
1103 (when (and (integerp msb-max-menu-items)
0b704e15 1104 (> msb-max-menu-items 1)
fd46fd17
RS
1105 (> frame-length msb-max-menu-items))
1106 (setcdr (nthcdr msb-max-menu-items frames) nil))
b9a5a6af 1107 (setq frames-menu
fd46fd17
RS
1108 (nconc
1109 (list 'frame f-title '(nil) 'keymap f-title)
1110 (mapcar
3cfa0ee9
SE
1111 (lambda (frame)
1112 (nconc
10df5051
RS
1113 (list (frame-parameter frame 'name)
1114 (frame-parameter frame 'name)
3cfa0ee9
SE
1115 (cons nil nil))
1116 'menu-bar-select-frame))
fd46fd17 1117 frames)))))
7ccc8f70 1118 (setcdr global-buffers-menu-map
b9a5a6af 1119 (if (and buffers-menu frames-menu)
fd46fd17 1120 ;; Combine Frame and Buffers menus with separator between
7ccc8f70 1121 (nconc (list "Buffers and Frames" frames-menu
0eb3b336 1122 (and msb-separator-diff '(separator "--")))
7ccc8f70
SM
1123 (cdr buffers-menu))
1124 buffers-menu)))))
b9a5a6af 1125
aade135d
DL
1126;; Snarf current bindings of `mouse-buffer-menu' (normally
1127;; C-down-mouse-1).
1128(defvar msb-mode-map
a4a49c21 1129 (let ((map (make-sparse-keymap "Msb")))
9103eeef 1130 (define-key map [remap mouse-buffer-menu] 'msb)
aade135d
DL
1131 map))
1132
1133;;;###autoload
3bdb5fb8 1134(define-minor-mode msb-mode
aade135d
DL
1135 "Toggle Msb mode.
1136With arg, turn Msb mode on if and only if arg is positive.
1137This mode overrides the binding(s) of `mouse-buffer-menu' to provide a
1138different buffer menu using the function `msb'."
329ffac0 1139 :global t :group 'msb
aade135d 1140 (if msb-mode
eed30659
DL
1141 (progn
1142 (add-hook 'menu-bar-update-hook 'msb-menu-bar-update-buffers)
0f6d89c4
GM
1143 (remove-hook 'menu-bar-update-hook 'menu-bar-update-buffers)
1144 (msb-menu-bar-update-buffers t))
eed30659 1145 (remove-hook 'menu-bar-update-hook 'msb-menu-bar-update-buffers)
0f6d89c4
GM
1146 (add-hook 'menu-bar-update-hook 'menu-bar-update-buffers)
1147 (menu-bar-update-buffers t)))
aade135d 1148
0b704e15
JB
1149(defun msb-unload-function ()
1150 "Unload the Msb library."
1151 (msb-mode -1)
1152 ;; continue standard unloading
1153 nil)
a4a49c21 1154
b9a5a6af 1155(provide 'msb)
00b1e7a1 1156(eval-after-load "msb" '(run-hooks 'msb-after-load-hook 'msb-after-load-hooks))
be17d374 1157
7ccc8f70 1158;; arch-tag: 403f9e82-b92e-4e7a-a797-5d6d9b76da36
b9a5a6af 1159;;; msb.el ends here