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