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