(display_string, display_text_line):
[bpt/emacs.git] / lisp / msb.el
CommitLineData
b9a5a6af
RS
1;;; msb.el --- Customizable buffer-selection with multiple menus.
2;; Copyright (C) 1993, 1994 Lars Lindberg <Lars.Lindberg@sypro.cap.se>
3;;
4;; Author: Lars Lindberg <Lars.Lindberg@sypro.cap.se>
5;; Created: 8 Oct 1993
fd46fd17 6;; Lindberg's last update version: 3.31
b9a5a6af
RS
7;; Keywords: mouse buffer menu
8;;
9;; This program is free software; you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation; either version 2 of the License, or
12;; (at your option) any later version.
13;;
14;; This program is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18;;
19;; You should have received a copy of the GNU General Public License
20;; along with this program; if not, write to the Free Software
21;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
b9a5a6af
RS
23;;; Commentary:
24;;
25;; Purpose of this package:
26;; 1. Offer a function for letting the user choose buffer,
27;; not necessarily for switching to it.
28;; 2. Make a better mouse-buffer-menu.
29;;
30;; Installation:
31;; (require 'msb)
32;; Note! You now use msb instead of mouse-buffer-menu.
33;;
2e6286be 34;; Now try the menu bar Buffers menu.
b9a5a6af
RS
35;;
36;; Customization:
2e6286be
RS
37;; Look at the variable `msb-menu-cond' for deciding what menus you
38;; want. It's not that hard to customize, despite my not-so-good
39;; doc-string. Feel free to send me a better doc-string.
b9a5a6af
RS
40;; There are some constants for you to try here:
41;; msb--few-menus
42;; msb--very-many-menus (default)
43;;
2e6286be
RS
44;; Look at the variable `msb-item-handling-function' for customization
45;; of the appearance of every menu item. Try for instance setting
46;; it to `msb-alon-item-handler'.
b9a5a6af 47;;
2e6286be
RS
48;; Look at the variable `msb-item-sort-function' for customization
49;; of sorting the menus. Set it to t for instance, which means no
b9a5a6af
RS
50;; sorting - you will get latest used buffer first.
51;;
2e6286be 52;; Also check out the variable `msb-display-invisible-buffers-p'.
b9a5a6af
RS
53
54;; Known bugs:
4aa4849b 55;; - Files-by-directory
fd46fd17
RS
56;; + No possibility to show client/changed buffers separately.
57;; + All file buffers only appear in in a file sub-menu, they will
58;; for instance not appear in the Mail sub-menu.
59
b9a5a6af 60;; Future enhancements:
b9a5a6af 61
b9a5a6af 62;;; Thanks goes to
fd46fd17
RS
63;; Mark Brader <msb@sq.com>
64;; Jim Berry <m1jhb00@FRB.GOV>
65;; Hans Chalupsky <hans@cs.Buffalo.EDU>
66;; Larry Rosenberg <ljr@ictv.com>
67;; Will Henney <will@astroscu.unam.mx>
68;; Jari Aalto <jaalto@tre.tele.nokia.fi>
69;; Michael Kifer <kifer@sbkifer.cs.sunysb.edu>
70;; Gael Marziou <gael@gnlab030.grenoble.hp.com>
71;; Dave Gillespie <daveg@thymus.synaptics.com>
72;; Alon Albert <alon@milcse.rtsg.mot.com>
73;; Kevin Broadey, <KevinB@bartley.demon.co.uk>
74;; Ake Stenhof <ake@cadpoint.se>
75;; Richard Stallman <rms@gnu.ai.mit.edu>
76;; Steve Fisk <fisk@medved.bowdoin.edu>
b9a5a6af
RS
77
78;;; Code:
79
80(require 'cl)
81
82;;;
2e6286be
RS
83;;; Some example constants to be used for `msb-menu-cond'. See that
84;;; variable for more information. Please note that if the condition
85;;; returns `multi', then the buffer can appear in several menus.
b9a5a6af
RS
86;;;
87(defconst msb--few-menus
88 '(((and (boundp 'server-buffer-clients)
89 server-buffer-clients
90 'multi)
91 3030
92 "Clients (%d)")
93 ((and msb-display-invisible-buffers-p
94 (msb-invisible-buffer-p)
95 'multi)
96 3090
97 "Invisible buffers (%d)")
98 ((eq major-mode 'dired-mode)
99 2010
100 "Dired (%d)"
101 msb-dired-item-handler
102 msb-sort-by-directory)
103 ((eq major-mode 'Man-mode)
104 4090
105 "Manuals (%d)")
106 ((eq major-mode 'w3-mode)
107 4020
108 "WWW (%d)")
fd46fd17 109 ((or (memq major-mode '(rmail-mode rmail-edit-mode vm-summary-mode vm-mode mail-mode))
b9a5a6af
RS
110 (memq major-mode '(mh-letter-mode
111 mh-show-mode
112 mh-folder-mode))
113 (memq major-mode '(gnus-summary-mode
114 news-reply-mode
115 gnus-group-mode
116 gnus-article-mode
117 gnus-kill-file-mode
118 gnus-browse-killed-mode)))
119 4010
120 "Mail (%d)")
121 ((not buffer-file-name)
122 4099
123 "Buffers (%d)")
124 ('no-multi
125 1099
126 "Files (%d)")))
127
128(defconst msb--very-many-menus
129 '(((and (boundp 'server-buffer-clients)
130 server-buffer-clients
131 'multi)
132 1010
133 "Clients (%d)")
134 ((and (boundp 'vc-mode) vc-mode 'multi)
135 1020
136 "Version Control (%d)")
137 ((and buffer-file-name
138 (buffer-modified-p)
139 'multi)
140 1030
141 "Changed files (%d)")
142 ((and (get-buffer-process (current-buffer))
143 'multi)
144 1040
145 "Processes (%d)")
146 ((and msb-display-invisible-buffers-p
147 (msb-invisible-buffer-p)
148 'multi)
149 1090
150 "Invisible buffers (%d)")
151 ((eq major-mode 'dired-mode)
152 2010
153 "Dired (%d)"
154 ;; Note this different menu-handler
155 msb-dired-item-handler
156 ;; Also note this item-sorter
157 msb-sort-by-directory)
158 ((eq major-mode 'Man-mode)
159 4030
160 "Manuals (%d)")
161 ((eq major-mode 'w3-mode)
162 4020
163 "WWW (%d)")
fd46fd17 164 ((or (memq major-mode '(rmail-mode rmail-edit-mode vm-summary-mode vm-mode mail-mode))
b9a5a6af
RS
165 (memq major-mode '(mh-letter-mode
166 mh-show-mode
167 mh-folder-mode))
168 (memq major-mode '(gnus-summary-mode
169 news-reply-mode
170 gnus-group-mode
171 gnus-article-mode
172 gnus-kill-file-mode
173 gnus-browse-killed-mode)))
174 4010
175 "Mail (%d)")
176 ;; Catchup for all non-file buffers
177 ((and (not buffer-file-name)
178 'no-multi)
179 4099
180 "Other non-file buffers (%d)")
181 ((and (string-match "/\\.[^/]*$" buffer-file-name)
182 'multi)
183 3090
184 "Hidden Files (%d)")
185 ((memq major-mode '(c-mode c++-mode))
186 3010
187 "C/C++ Files (%d)")
188 ((eq major-mode 'emacs-lisp-mode)
189 3020
190 "Elisp Files (%d)")
191 ((eq major-mode 'latex-mode)
192 3030
193 "LaTex Files (%d)")
194 ('no-multi
195 3099
196 "Other files (%d)")))
197
198;; msb--many-menus is obsolete
199(defvar msb--many-menus msb--very-many-menus)
200
201;;;
202;;; Customizable variables
203;;;
204
205(defvar msb-separator-diff 100
206 "*Non-nil means use separators.
207The separators will appear between all menus that have a sorting key that differs by this value or more.")
208
209(defvar msb-files-by-directory-sort-key 0
210 "*The sort key for files sorted by directory")
211
4aa4849b 212(defvar msb-max-menu-items 15
b9a5a6af 213 "*The maximum number of items in a menu.
4aa4849b 214If this variable is set to 15 for instance, then the submenu will be split up in minor parts, 15 items each.
b9a5a6af
RS
215Nil means no limit.")
216
217(defvar msb-max-file-menu-items 10
218 "*The maximum number of items from different directories.
219
2e6286be 220When the menu is of type `file by directory', this is the maximum
b9a5a6af
RS
221number of buffers that are clumped togehter from different
222directories.
223
4aa4849b
RS
224Set this to 1 if you want one menu per directory instead of clumping
225them together.
226
b9a5a6af
RS
227If the value is not a number, then the value 10 is used.")
228
229(defvar msb-most-recently-used-sort-key -1010
230 "*Where should the menu with the most recently used buffers be placed?")
231
4aa4849b 232(defvar msb-display-most-recently-used 15
b9a5a6af 233 "*How many buffers should be in the most-recently-used menu.
4aa4849b 234 No buffers at all if less than 1 or nil (or any non-number).")
b9a5a6af
RS
235
236(defvar msb-most-recently-used-title "Most recently used (%d)"
237 "*The title for the most-recently-used menu.")
238
239(defvar msb-horizontal-shift-function '(lambda () 0)
240 "*Function that specifies a number of pixels by which the top menu should
241be shifted leftwards.")
242
243(defvar msb-display-invisible-buffers-p nil
244 "*Show invisible buffers or not.
245Non-nil means that the buffer menu should include buffers that have
246names that starts with a space character.")
247
248(defvar msb-item-handling-function 'msb-item-handler
249 "*The appearance of a buffer menu.
250
251The default function to call for handling the appearance of a menu
2e6286be 252item. It should take to arguments, BUFFER and MAX-BUFFER-NAME-LENGTH,
b9a5a6af 253where the latter is the max length of all buffer names.
4aa4849b
RS
254
255The function should return the string to use in the menu.
256
b9a5a6af 257When the function is called, BUFFER is the current buffer.
2e6286be
RS
258This function is called for items in the variable `msb-menu-cond' that
259have nil as ITEM-HANDLING-FUNCTION. See `msb-menu-cond' for more
b9a5a6af
RS
260information.")
261
262(defvar msb-item-sort-function 'msb-sort-by-name
263 "*The order of items in a buffer menu.
264The default function to call for handling the order of items in a menu
2e6286be 265item. This function is called like a sort function. The items
b9a5a6af
RS
266look like (ITEM-NAME . BUFFER).
267ITEM-NAME is the name of the item that will appear in the menu.
268BUFFER is the buffer, this is not necessarily the current buffer.
269
270Set this to nil or t if you don't want any sorting (faster).")
271
272(defvar msb-files-by-directory nil
273 "*Non-nil means that files should be sorted by directory instead of
274the groups in msb-menu-cond.")
275
276(defvar msb-menu-cond msb--very-many-menus
277 "*List of criterias for splitting the mouse buffer menu.
278The elements in the list should be of this type:
279 (CONDITION MENU-SORT-KEY MENU-TITLE ITEM-HANDLING-FN ITEM-SORT-FN).
280
281When making the split, the buffers are tested one by one against the
282CONDITION, just like a lisp cond: When hitting a true condition, the
283other criterias are *not* tested and the buffer name will appear in
284the menu with the menu-title corresponding to the true condition.
285
2e6286be 286If the condition returns the symbol `multi', then the buffer will be
b9a5a6af 287added to this menu *and* tested for other menus too. If it returns
2e6286be 288`no-multi', then the buffer will only be added if it hasn't been added
b9a5a6af
RS
289to any other menu.
290
291During this test, the buffer in question is the current buffer, and
292the test is surrounded by calls to `save-excursion' and
2e6286be 293`save-match-data'.
b9a5a6af 294
2e6286be
RS
295The categories are sorted by MENU-SORT-KEY. Smaller keys are on
296top. nil means don't display this menu.
b9a5a6af 297
2e6286be 298MENU-TITLE is really a format. If you add %d in it, the %d is replaced
b9a5a6af
RS
299with the number of items in that menu.
300
2e6286be 301ITEM-HANDLING-FN, is optional. If it is supplied and is a
b9a5a6af
RS
302function, than it is used for displaying the items in that particular
303buffer menu, otherwise the function pointed out by
2e6286be 304`msb-item-handling-function' is used.
b9a5a6af
RS
305
306ITEM-SORT-FN, is also optional.
307If it is not supplied, the function pointed out by
2e6286be 308`msb-item-sort-function' is used.
b9a5a6af
RS
309If it is nil, then no sort takes place and the buffers are presented
310in least-recently-used order.
311If it is t, then no sort takes place and the buffers are presented in
312most-recently-used order.
313If it is supplied and non-nil and not t than it is used for sorting
314the items in that particular buffer menu.
315
2e6286be
RS
316Note1: There should always be a `catch-all' as last element,
317in this list. That is an element like (t TITLE ITEM-HANDLING-FUNCTION).
b9a5a6af
RS
318Note2: A buffer menu appears only if it has at least one buffer in it.
319Note3: If you have a CONDITION that can't be evaluated you will get an
320error every time you do \\[msb].")
321
322(defvar msb-after-load-hooks nil
323 "Hooks to be run after the msb package has been loaded.")
324
325;;;
326;;; Internal variables
327;;;
328
329;; The last calculated menu.
330(defvar msb--last-buffer-menu nil)
331
332;; If this is non-nil, then it is a string that describes the error.
333(defvar msb--error nil)
334
335;;;
4aa4849b 336;;; Some example function to be used for `msb-item-handling-function'.
b9a5a6af
RS
337;;;
338(defun msb-item-handler (buffer &optional maxbuf)
339 "Create one string item, concerning BUFFER, for the buffer menu.
340The item looks like:
341*% <buffer-name>
2e6286be
RS
342The `*' appears only if the buffer is marked as modified.
343The `%' appears only if the buffer is read-only.
b9a5a6af
RS
344Optional second argument MAXBUF is completely ignored."
345 (let ((name (buffer-name))
346 (modified (if (buffer-modified-p) "*" " "))
347 (read-only (if buffer-read-only "%" " ")))
348 (format "%s%s %s" modified read-only name)))
349
350
351(eval-when-compile (require 'dired))
352
2e6286be
RS
353;; `dired' can be called with a list of the form (directory file1 file2 ...)
354;; which causes `dired-directory' to be in the same form.
b9a5a6af
RS
355(defun msb--dired-directory ()
356 (cond ((stringp dired-directory)
357 (abbreviate-file-name (expand-file-name dired-directory)))
358 ((consp dired-directory)
359 (abbreviate-file-name (expand-file-name (car dired-directory))))
360 (t
2e6286be 361 (error "Unknown type of `dired-directory' in buffer %s"
b9a5a6af
RS
362 (buffer-name)))))
363
364(defun msb-dired-item-handler (buffer &optional maxbuf)
365 "Create one string item, concerning a dired BUFFER, for the buffer menu.
366The item looks like:
367*% <buffer-name>
2e6286be
RS
368The `*' appears only if the buffer is marked as modified.
369The `%' appears only if the buffer is read-only.
b9a5a6af
RS
370Optional second argument MAXBUF is completely ignored."
371 (let ((name (msb--dired-directory))
372 (modified (if (buffer-modified-p) "*" " "))
373 (read-only (if buffer-read-only "%" " ")))
374 (format "%s%s %s" modified read-only name)))
375
376(defun msb-alon-item-handler (buffer maxbuf)
377 "Create one string item for the buffer menu.
378The item looks like:
379<buffer-name> *%# <file-name>
2e6286be
RS
380The `*' appears only if the buffer is marked as modified.
381The `%' appears only if the buffer is read-only.
382The `#' appears only version control file (SCCS/RCS)."
b9a5a6af
RS
383 (format (format "%%%ds %%s%%s%%s %%s" maxbuf)
384 (buffer-name buffer)
385 (if (buffer-modified-p) "*" " ")
386 (if buffer-read-only "%" " ")
387 (if (and (boundp 'vc-mode) vc-mode) "#" " ")
388 (or buffer-file-name "")))
389
390;;;
4aa4849b 391;;; Some example function to be used for `msb-item-sort-function'.
b9a5a6af
RS
392;;;
393(defun msb-sort-by-name (item1 item2)
394 "Sorts the items depending on their buffer-name
395An item look like (NAME . BUFFER)."
396 (string-lessp (buffer-name (cdr item1))
397 (buffer-name (cdr item2))))
398
399
400(defun msb-sort-by-directory (item1 item2)
2e6286be 401 "Sorts the items depending on their directory. Made for dired.
b9a5a6af
RS
402An item look like (NAME . BUFFER)."
403 (string-lessp (save-excursion (set-buffer (cdr item1)) (msb--dired-directory))
404 (save-excursion (set-buffer (cdr item2)) (msb--dired-directory))))
405
406;;;
407;;; msb
408;;;
409;;; This function can be used instead of (mouse-buffer-menu EVENT)
410;;; function in "mouse.el".
411;;;
412(defun msb (event)
413 "Pop up several menus of buffers for selection with the mouse.
414This command switches buffers in the window that you clicked on, and
415selects that window.
416
2e6286be
RS
417See the function `mouse-select-buffer' and the variable
418`msb-menu-cond' for more information about how the menus are split."
b9a5a6af 419 (interactive "e")
fd46fd17 420 (let ((old-window (selected-window))
b9a5a6af 421 (window (posn-window (event-start event))))
fd46fd17
RS
422 (unless (framep window) (select-window window))
423 (let ((buffer (mouse-select-buffer event)))
424 (if buffer
425 (switch-to-buffer buffer)
426 (select-window old-window))))
b9a5a6af
RS
427 nil)
428
429;;;
430;;; Some supportive functions
431;;;
432(defun msb-invisible-buffer-p (&optional buffer)
433 "Return t if optional BUFFER is an \"invisible\" buffer.
434If the argument is left out or nil, then the current buffer is considered."
435 (and (> (length (buffer-name buffer)) 0)
436 (eq ?\ (aref (buffer-name buffer) 0))))
437
438;; Strip one hierarcy level from the end of PATH.
439(defun msb--strip-path (path)
440 (save-match-data
441 (if (string-match "\\(.+\\)/[^/]+$" path)
442 (substring path (match-beginning 1) (match-end 1))
443 "/")))
444
445;; Create an alist with all buffers from LIST that lies under the same
446;; directory will be in the same item as the directory string as
2e6286be 447;; ((PATH1 . (BUFFER-1 BUFFER-2 ...)) (PATH2 . (BUFFER-K BUFFER-K+1...)) ...)
b9a5a6af
RS
448(defun msb--init-file-alist (list)
449 (let ((buffer-alist
450 (sort (mapcan
451 (function
452 (lambda (buffer)
453 (let ((file-name (buffer-file-name buffer)))
454 (when file-name
455 (list (cons (msb--strip-path file-name) buffer))))))
456 list)
457 (function (lambda (item1 item2)
458 (string< (car item1) (car item2)))))))
459 ;; Make alist that looks like
2e6286be 460 ;; ((PATH1 . (BUFFER-1 BUFFER-2 ...)) (PATH2 . (BUFFER-K)) ...)
b9a5a6af
RS
461 (let ((path nil)
462 (buffers nil)
463 (result nil))
464 (append
465 (mapcan (function
466 (lambda (item)
467 (cond
468 ((and path
b9a5a6af
RS
469 (string= path (car item)))
470 (push (cdr item) buffers)
471 nil)
472 (t
473 (when path
474 (setq result (cons path buffers)))
475 (setq path (car item))
476 (setq buffers (list (cdr item)))
477 (and result (list result))))))
478 buffer-alist)
479 (list (cons path buffers))))))
480
481;; Choose file-menu with respect to directory for every buffer in LIST.
482(defun msb--choose-file-menu (list)
483 (let ((buffer-alist (msb--init-file-alist list))
484 (final-list nil)
485 (max-clumped-together (if (numberp msb-max-file-menu-items)
486 msb-max-file-menu-items
487 10))
488 (top-found-p nil)
489 (last-path nil)
490 first rest path buffers)
491 (setq first (car buffer-alist))
492 (setq rest (cdr buffer-alist))
493 (setq path (car first))
494 (setq buffers (cdr first))
495 (while rest
496 (let ((found-p nil)
497 (tmp-rest rest)
498 new-path item)
499 (setq item (car tmp-rest))
500 (while (and tmp-rest
501 (<= (length buffers) max-clumped-together)
502 (>= (length (car item)) (length path))
503 (string= path (substring (car item) 0 (length path))))
504 (setq found-p t)
505 (setq buffers (append buffers (cdr item)))
506 (setq tmp-rest (cdr tmp-rest))
507 (setq item (car tmp-rest)))
508 (cond
509 ((> (length buffers) max-clumped-together)
510 (setq last-path (car first))
4aa4849b
RS
511 (setq first
512 (cons (format (if top-found-p
513 "%s/... (%d)"
514 "%s (%d)")
515 (car first)
516 (length (cdr first)))
517 (cdr first)))
518 (setq top-found-p nil)
b9a5a6af
RS
519 (push first final-list)
520 (setq first (car rest)
521 rest (cdr rest))
522 (setq path (car first)
523 buffers (cdr first)))
524 (t
525 (when found-p
526 (setq top-found-p t)
527 (setq first (cons path buffers)
528 rest tmp-rest))
529 (setq path (msb--strip-path path)
530 buffers (cdr first))
531 (when (and last-path
532 (or (and (>= (length path) (length last-path))
533 (string= last-path
534 (substring path 0 (length last-path))))
535 (and (< (length path) (length last-path))
536 (string= path
537 (substring last-path 0 (length path))))))
538
4aa4849b
RS
539 (setq first
540 (cons (format (if top-found-p
541 "%s/... (%d)"
542 "%s (%d)")
543 (car first)
544 (length (cdr first)))
545 (cdr first)))
546 (setq top-found-p nil)
b9a5a6af
RS
547 (push first final-list)
548 (setq first (car rest)
549 rest (cdr rest))
550 (setq path (car first)
551 buffers (cdr first)))))))
4aa4849b
RS
552 (setq first
553 (cons (format (if top-found-p
554 "%s/... (%d)"
555 "%s (%d)")
556 (car first)
557 (length (cdr first)))
558 (cdr first)))
559 (setq top-found-p nil)
b9a5a6af
RS
560 (push first final-list)
561 (nreverse final-list)))
562
563;; Create a vector as:
564;; [BUFFER-LIST-VARIABLE CONDITION MENU-SORT-KEY MENU-TITLE ITEM-HANDLER SORTER)
2e6286be
RS
565;; from an element in `msb-menu-cond'. See that variable for a
566;; description of its elements.
b9a5a6af
RS
567(defun msb--create-function-info (menu-cond-elt)
568 (let* ((list-symbol (make-symbol "-msb-buffer-list"))
569 (tmp-ih (and (> (length menu-cond-elt) 3)
570 (nth 3 menu-cond-elt)))
571 (item-handler (if (and tmp-ih (fboundp tmp-ih))
572 tmp-ih
573 msb-item-handling-function))
574 (tmp-s (if (> (length menu-cond-elt) 4)
575 (nth 4 menu-cond-elt)
576 msb-item-sort-function))
577 (sorter (if (or (fboundp tmp-s)
578 (null tmp-s)
2e6286be 579 (eq tmp-s t))
b9a5a6af
RS
580 tmp-s
581 msb-item-sort-function)))
582 (when (< (length menu-cond-elt) 3)
583 (error "Wrong format of msb-menu-cond."))
584 (when (and (> (length menu-cond-elt) 3)
585 (not (fboundp tmp-ih)))
586 (signal 'invalid-function (list tmp-ih)))
587 (when (and (> (length menu-cond-elt) 4)
588 tmp-s
589 (not (fboundp tmp-s))
2e6286be 590 (not (eq tmp-s t)))
b9a5a6af 591 (signal 'invalid-function (list tmp-s)))
2e6286be 592 (set list-symbol ())
b9a5a6af
RS
593 (vector list-symbol ;BUFFER-LIST-VARIABLE
594 (nth 0 menu-cond-elt) ;CONDITION
595 (nth 1 menu-cond-elt) ;SORT-KEY
596 (nth 2 menu-cond-elt) ;MENU-TITLE
597 item-handler ;ITEM-HANDLER
598 sorter) ;SORTER
599 ))
600
601;; This defsubst is only used in `msb--choose-menu' below. It was
2e6286be 602;; pulled out merely to make the code somewhat clearer. The indention
b9a5a6af
RS
603;; level was too big.
604(defsubst msb--collect (function-info-vector)
605 (let ((result nil)
606 (multi-flag nil)
607 function-info-list)
608 (setq function-info-list
609 (loop for fi
610 across function-info-vector
611 if (and (setq result
612 (eval (aref fi 1))) ;Test CONDITION
613 (not (and (eq result 'no-multi)
614 multi-flag))
615 (progn (when (eq result 'multi)
616 (setq multi-flag t))
4aa4849b 617 t))
b9a5a6af
RS
618 collect fi
619 until (and result
620 (not (eq result 'multi)))))
621 (when (and (not function-info-list)
622 (not result))
623 (error "No catch-all in msb-menu-cond!"))
624 function-info-list))
625
626;; Adds BUFFER to the menu depicted by FUNCTION-INFO
2e6286be 627;; All side-effects. Adds an element of form (BUFFER-TITLE . BUFFER)
b9a5a6af
RS
628;; to the buffer-list variable in function-info.
629(defun msb--add-to-menu (buffer function-info max-buffer-name-length)
630 (let ((list-symbol (aref function-info 0))) ;BUFFER-LIST-VARIABLE
631 ;; Here comes the hairy side-effect!
632 (set list-symbol
633 (cons (cons (funcall (aref function-info 4) ;ITEM-HANDLER
634 buffer
635 max-buffer-name-length)
636 buffer)
637 (eval list-symbol)))))
638
639;; Selects the appropriate menu for BUFFER.
640;; This is all side-effects, folks!
641;; This should be optimized.
642(defsubst msb--choose-menu (buffer function-info-vector max-buffer-name-length)
643 (unless (and (not msb-display-invisible-buffers-p)
644 (msb-invisible-buffer-p buffer))
645 (condition-case nil
646 (save-excursion
647 (set-buffer buffer)
2e6286be 648 ;; Menu found. Add to this menu
b9a5a6af
RS
649 (mapc (function
650 (lambda (function-info)
651 (msb--add-to-menu buffer function-info max-buffer-name-length)))
652 (msb--collect function-info-vector)))
653 (error (unless msb--error
654 (setq msb--error
655 (format
2e6286be 656 "In msb-menu-cond, error for buffer `%s'."
b9a5a6af
RS
657 (buffer-name buffer)))
658 (error msb--error))))))
659
660;; Return (SORT-KEY TITLE . BUFFER-LIST) or nil if the
661;; buffer-list is empty.
662(defun msb--create-sort-item (function-info)
663 (let ((buffer-list (eval (aref function-info 0))))
664 (when buffer-list
665 (let ((sorter (aref function-info 5)) ;SORTER
666 (sort-key (aref function-info 2))) ;MENU-SORT-KEY
667 (when sort-key
668 (cons sort-key
669 (cons (format (aref function-info 3) ;MENU-TITLE
670 (length buffer-list))
671 (cond
672 ((null sorter)
673 buffer-list)
2e6286be 674 ((eq sorter t)
b9a5a6af
RS
675 (nreverse buffer-list))
676 (t
677 (sort buffer-list sorter))))))))))
678
679;; Returns a list on the form ((TITLE . BUFFER-LIST)) for
680;; the most recently used buffers.
681(defun msb--most-recently-used-menu (max-buffer-name-length)
4aa4849b
RS
682 (when (and (numberp msb-display-most-recently-used)
683 (> msb-display-most-recently-used 0))
fd46fd17
RS
684 (let* ((buffers (cdr (buffer-list)))
685 (most-recently-used
b9a5a6af 686 (loop with n = 0
fd46fd17 687 for buffer in buffers
b9a5a6af
RS
688 if (save-excursion
689 (set-buffer buffer)
690 (and (not (msb-invisible-buffer-p))
691 (not (eq major-mode 'dired-mode))))
692 collect (save-excursion
693 (set-buffer buffer)
694 (cons (funcall msb-item-handling-function
695 buffer
696 max-buffer-name-length)
697 buffer))
698 and do (incf n)
4aa4849b 699 until (>= n msb-display-most-recently-used))))
b9a5a6af
RS
700 (cons (if (stringp msb-most-recently-used-title)
701 (format msb-most-recently-used-title
702 (length most-recently-used))
703 (signal 'wrong-type-argument (list msb-most-recently-used-title)))
704 most-recently-used))))
705
706(defun msb--create-buffer-menu-2 ()
707 (let ((max-buffer-name-length 0)
708 file-buffers
709 function-info-vector)
710 ;; Calculate the longest buffer name.
711 (mapc
712 (function
713 (lambda (buffer)
714 (if (or msb-display-invisible-buffers-p
715 (not (msb-invisible-buffer-p)))
716 (setq max-buffer-name-length
717 (max max-buffer-name-length
718 (length (buffer-name buffer)))))))
719 (buffer-list))
720 ;; Make a list with elements of type
721 ;; (BUFFER-LIST-VARIABLE
722 ;; CONDITION
723 ;; MENU-SORT-KEY
724 ;; MENU-TITLE
725 ;; ITEM-HANDLER
726 ;; SORTER)
727 ;; Uses "function-global" variables:
728 ;; function-info-vector
729 (setq function-info-vector
730 (apply (function vector)
731 (mapcar (function msb--create-function-info)
732 msb-menu-cond)))
733 ;; Split the buffer-list into several lists; one list for each
2e6286be 734 ;; criteria. This is the most critical part with respect to time.
b9a5a6af
RS
735 (mapc (function (lambda (buffer)
736 (cond ((and msb-files-by-directory
737 (buffer-file-name buffer))
738 (push buffer file-buffers))
739 (t
740 (msb--choose-menu buffer
741 function-info-vector
742 max-buffer-name-length)))))
743 (buffer-list))
744 (when file-buffers
745 (setq file-buffers
746 (mapcar (function
747 (lambda (buffer-list)
748 (cons msb-files-by-directory-sort-key
749 (cons (car buffer-list)
750 (sort
751 (mapcar (function
752 (lambda (buffer)
4aa4849b
RS
753 (cons (save-excursion
754 (set-buffer buffer)
755 (funcall msb-item-handling-function
756 buffer
757 max-buffer-name-length))
b9a5a6af
RS
758 buffer)))
759 (cdr buffer-list))
760 (function
761 (lambda (item1 item2)
762 (string< (car item1) (car item2)))))))))
763 (msb--choose-file-menu file-buffers))))
764 ;; Now make the menu - a list of (TITLE . BUFFER-LIST)
4aa4849b 765 (let* (menu
b9a5a6af
RS
766 (most-recently-used
767 (msb--most-recently-used-menu max-buffer-name-length))
768 (others (append file-buffers
769 (loop for elt
4aa4849b
RS
770 across function-info-vector
771 for value = (msb--create-sort-item elt)
772 if value collect value))))
b9a5a6af
RS
773 (setq menu
774 (mapcar 'cdr ;Remove the SORT-KEY
775 ;; Sort the menus - not the items.
776 (msb--add-separators
777 (sort
778 ;; Get a list of (SORT-KEY TITLE . BUFFER-LIST)
779 ;; Also sorts the items within the menus.
780 (if (cdr most-recently-used)
781 (cons
782 ;; Add most recent used buffers
783 (cons msb-most-recently-used-sort-key
784 most-recently-used)
785 others)
786 others)
787 (function (lambda (elt1 elt2)
788 (< (car elt1) (car elt2))))))))
789 ;; Now make it a keymap menu
790 (append
791 '(keymap "Select Buffer")
792 (msb--make-keymap-menu menu)
793 (when msb-separator-diff
794 (list (list 'separator "---")))
795 (list (cons 'toggle
796 (cons
797 (if msb-files-by-directory
798 "*Files by type*"
799 "*Files by directory*")
800 'msb--toggle-menu-type)))))))
801
802(defun msb--create-buffer-menu ()
803 (save-match-data
804 (save-excursion
805 (msb--create-buffer-menu-2))))
806
807;;;
808;;; Multi purpose function for selecting a buffer with the mouse.
809;;;
810(defun msb--toggle-menu-type ()
811 (interactive)
812 (setq msb-files-by-directory (not msb-files-by-directory))
813 (menu-bar-update-buffers t))
814
815(defun mouse-select-buffer (event)
816 "Pop up several menus of buffers, for selection with the mouse.
817Returns the selected buffer or nil if no buffer is selected.
818
4aa4849b 819The way the buffers are split is conveniently handled with the
2e6286be 820variable `msb-menu-cond'."
b9a5a6af
RS
821 ;; Popup the menu and return the selected buffer.
822 (when (or msb--error
823 (not msb--last-buffer-menu)
824 (not (fboundp 'frame-or-buffer-changed-p))
825 (frame-or-buffer-changed-p))
826 (setq msb--error nil)
827 (setq msb--last-buffer-menu (msb--create-buffer-menu)))
4aa4849b
RS
828 (let ((position event)
829 choice)
b9a5a6af
RS
830 (when (and (fboundp 'posn-x-y)
831 (fboundp 'posn-window))
832 (let ((posX (car (posn-x-y (event-start event))))
833 (posY (cdr (posn-x-y (event-start event))))
4aa4849b 834 (posWind (posn-window (event-start event))))
b9a5a6af
RS
835 ;; adjust position
836 (setq posX (- posX (funcall msb-horizontal-shift-function))
837 position (list (list posX posY) posWind))))
4aa4849b 838 (setq choice (x-popup-menu position msb--last-buffer-menu))
b9a5a6af 839 (cond
4aa4849b
RS
840 ((eq (car choice) 'toggle)
841 ;; Bring up the menu again with type toggled.
842 (msb--toggle-menu-type)
843 (mouse-select-buffer event))
844 ((and (numberp (car choice))
845 (null (cdr choice)))
846 (let ((msb--last-buffer-menu (nthcdr 3 (assq (car choice) msb--last-buffer-menu))))
b9a5a6af 847 (mouse-select-buffer event)))
4aa4849b
RS
848 ((while (numberp (car choice))
849 (setq choice (cdr choice))))
850 ((and (stringp (car choice))
851 (null (cdr choice)))
852 (car choice))
dc3247b3
RS
853 ((null choice)
854 choice)
4aa4849b
RS
855 (t
856 (error "Unknown form for buffer: %s" choice)))))
b9a5a6af
RS
857
858;; Add separators
859(defun msb--add-separators (sorted-list)
860 (cond
861 ((or (not msb-separator-diff)
862 (not (numberp msb-separator-diff)))
863 sorted-list)
864 (t
865 (let ((last-key nil))
866 (mapcan
867 (function
868 (lambda (item)
869 (cond
870 ((and msb-separator-diff
871 last-key
872 (> (- (car item) last-key)
873 msb-separator-diff))
874 (setq last-key (car item))
875 (list (cons last-key 'separator)
876 item))
877 (t
878 (setq last-key (car item))
879 (list item)))))
880 sorted-list)))))
881
4aa4849b
RS
882(defun msb--split-menus-2 (list mcount result)
883 (cond
884 ((> (length list) msb-max-menu-items)
885 (let ((count 0)
886 sub-name
887 (tmp-list nil))
888 (while (< count msb-max-menu-items)
889 (push (pop list) tmp-list)
890 (incf count))
891 (setq tmp-list (nreverse tmp-list))
892 (setq sub-name (concat (car (car tmp-list)) "..."))
893 (push (append (list mcount sub-name
894 'keymap sub-name)
895 tmp-list)
896 result))
897 (msb--split-menus-2 list (1+ mcount) result))
898 ((null result)
899 list)
900 (t
901 (let (sub-name)
902 (setq sub-name (concat (car (car list)) "..."))
903 (push (append (list mcount sub-name
904 'keymap sub-name)
905 list)
906 result))
907 (nreverse result))))
908
909(defun msb--split-menus (list)
910 (msb--split-menus-2 list 0 nil))
911
912
b9a5a6af
RS
913(defun msb--make-keymap-menu (raw-menu)
914 (let ((end (cons '(nil) 'menu-bar-select-buffer))
915 (mcount 0))
916 (mapcar
917 (function
918 (lambda (sub-menu)
919 (cond
920 ((eq 'separator sub-menu)
921 (list 'separator "---"))
922 (t
4aa4849b
RS
923 (let ((buffers (mapcar (function
924 (lambda (item)
925 (let ((string (car item))
926 (buffer (cdr item)))
927 (cons (buffer-name buffer)
928 (cons string end)))))
929 (cdr sub-menu))))
930 (append (list (incf mcount) (car sub-menu)
931 'keymap (car sub-menu))
932 (msb--split-menus buffers)))))))
b9a5a6af
RS
933 raw-menu)))
934
935(defun menu-bar-update-buffers (&optional arg)
936 ;; If user discards the Buffers item, play along.
937 (when (and (lookup-key (current-global-map) [menu-bar buffer])
938 (or (not (fboundp 'frame-or-buffer-changed-p))
939 (frame-or-buffer-changed-p)
940 arg))
fd46fd17 941 (let ((frames (frame-list))
b9a5a6af 942 buffers-menu frames-menu)
b9a5a6af
RS
943 ;; Make the menu of buffers proper.
944 (setq msb--last-buffer-menu (msb--create-buffer-menu))
945 (setq buffers-menu msb--last-buffer-menu)
946 ;; Make a Frames menu if we have more than one frame.
fd46fd17
RS
947 (when (cdr frames)
948 (let* ((frame-length (length frames))
949 (f-title (format "Frames (%d)" frame-length)))
950 ;; List only the N most recently selected frames
951 (when (and (integerp msb-max-menu-items)
952 (> msb-max-menu-items 1)
953 (> frame-length msb-max-menu-items))
954 (setcdr (nthcdr msb-max-menu-items frames) nil))
b9a5a6af 955 (setq frames-menu
fd46fd17
RS
956 (nconc
957 (list 'frame f-title '(nil) 'keymap f-title)
958 (mapcar
959 (function
960 (lambda (frame)
961 (nconc
962 (list frame
963 (cdr (assq 'name
964 (frame-parameters frame)))
965 (cons nil nil))
966 'menu-bar-select-frame)))
967 frames)))))
b9a5a6af
RS
968 (define-key (current-global-map) [menu-bar buffer]
969 (cons "Buffers"
970 (if (and buffers-menu frames-menu)
fd46fd17
RS
971 ;; Combine Frame and Buffers menus with separator between
972 (nconc (list 'keymap "Buffers and Frames" frames-menu
973 (and msb-separator-diff '(separator "---")))
974 (cddr buffers-menu))
975 (or buffers-menu 'undefined)))))))
b9a5a6af
RS
976
977(when (and (boundp 'menu-bar-update-hook)
978 (not (fboundp 'frame-or-buffer-changed-p)))
979 (defvar msb--buffer-count 0)
980 (defun frame-or-buffer-changed-p ()
981 (let ((count (length (buffer-list))))
982 (when (/= count msb--buffer-count)
983 (setq msb--buffer-count count)
984 t))))
985
986(unless (or (not (boundp 'menu-bar-update-hook))
987 (memq 'menu-bar-update-buffers menu-bar-update-hook))
988 (add-hook 'menu-bar-update-hook 'menu-bar-update-buffers))
989
990(and (fboundp 'mouse-buffer-menu)
991 (substitute-key-definition 'mouse-buffer-menu 'msb (current-global-map)))
992
993(provide 'msb)
994(eval-after-load 'msb (run-hooks 'msb-after-load-hooks))
995;;; msb.el ends here