Rewrite Eshell tests using ERT
[bpt/emacs.git] / lisp / bs.el
CommitLineData
2fbc1934 1;;; bs.el --- menu for selecting and displaying buffers -*- lexical-binding: t -*-
6448a6b3 2
ab422c4d 3;; Copyright (C) 1998-2013 Free Software Foundation, Inc.
6448a6b3
GM
4;; Author: Olaf Sylvester <Olaf.Sylvester@netsurf.de>
5;; Maintainer: Olaf Sylvester <Olaf.Sylvester@netsurf.de>
6;; Keywords: convenience
7
8;; This file is part of GNU Emacs.
9
eb3fa2cf 10;; GNU Emacs is free software: you can redistribute it and/or modify
6448a6b3 11;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
6448a6b3
GM
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
eb3fa2cf 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
6448a6b3
GM
22
23;;; Commentary:
24
25;; Version: 1.17
91b69101 26;; X-URL: http://www.geekware.de/software/emacs
6448a6b3 27;;
0b381c7e 28;; The bs-package contains a main function bs-show for popping up a
6448a6b3
GM
29;; buffer in a way similar to `list-buffers' and `electric-buffer-list':
30;; The new buffer offers a Buffer Selection Menu for manipulating
31;; the buffer list and buffers.
32;;
33;; -----------------------------------------------------------------------
34;; | MR Buffer Size Mode File |
35;; | -- ------ ---- ---- ---- |
36;; |. bs.el 14690 Emacs-Lisp /home/sun/sylvester/el/bs.e$|
37;; | % executable.el 9429 Emacs-Lisp /usr/share/emacs/19.34/lisp$|
38;; | % vc.el 104893 Emacs-Lisp /usr/share/emacs/19.34/lisp$|
39;; | % test_vc.el 486 Emacs-Lisp /home/sun/sylvester/el/test$|
40;; | % vc-hooks.el 43605 Emacs-Lisp /usr/share/emacs/19.34/lisp$|
41;; -----------------------------------------------------------------------
42
135377f1 43;;; Quick Installation and Customization:
6448a6b3 44
aca6915a 45;; To display the bs menu, do
6448a6b3 46;; M-x bs-show
aca6915a
CY
47;; To customize its behavior, do
48;; M-x bs-customize
6448a6b3
GM
49
50;;; More Commentary:
51
52;; bs-show will generate a new buffer named *buffer-selection*, which shows
53;; all buffers or a subset of them, and has possibilities for deleting,
54;; saving and selecting buffers. For more details see docstring of
55;; function `bs-mode'. A current configuration describes which buffers appear
56;; in *buffer-selection*. See docstring of variable `bs-configurations' for
57;; more details.
58;;
59;; The package bs combines the advantages of the Emacs functions
60;; `list-buffers' and `electric-buffer-list'.
61;;
880ed9f1 62;; Additional features for Buffer Selection Menu:
6448a6b3
GM
63;; - configurable list of buffers (show only files etc.).
64;; - comfortable way to change displayed subset of all buffers.
65;; - show sorted list of buffers.
66;; - cyclic navigation:
67;; - goes to top of buffer list if you are on last line and press down.
68;; - goes to end of buffer list if you are on first line and press up.
69;; - Offer an alternative buffer list by prefix key C-u.
70
71;;; Cycling through buffers
72
73;; This package offers two functions for buffer cycling. If you want to cycle
74;; through buffer list you can use `bs-cycle-next' or `bs-cycle-previous'.
75;; Bind these function to a key like
76;; (global-set-key [(f9)] 'bs-cycle-previous)
77;; (global-set-key [(f10)] 'bs-cycle-next)
78;;
79;; Both functions use a special subset of all buffers for cycling to avoid
80;; to go through internal buffers like *Messages*.
81;;
82;; Cycling through buffers ignores sorting because sorting destroys
83;; the logical buffer list. If buffer list is sorted by size you
84;; won't be able to cycle to the smallest buffer.
85
86;;; Customization:
87
88;; There is a customization group called `bs' in group `convenience'.
89;; Start customization by M-x bs-customize
90;;
91;; Buffer list
92;; -----------
93;; You can define your own configurations by extending variable
94;; `bs-configurations' (see docstring for details).
95;;
96;; `bs-default-configuration' contains the name of default configuration.
97;; The default value is "files" which means to show only files.
98;;
99;; If you always want to see all buffers, customize variable
100;; `bs-default-configuration' in customization group `bs'.
101;;
102;; Configure sorting
103;; -----------------
104;; You can define functions for sorting the buffer list.
105;; When selecting buffers, you can step through available sorting
106;; methods with key 'S'.
107;; To define a new way of sorting, customize variable `bs-sort-functions'.
108;;
109;; There are four basic functions for sorting:
110;; by buffer name, by mode, by size, or by filename
111;;
112;; Configure buffer cycling
113;; ------------------------
114;; When cycling through buffer list the functions for cycling will use
115;; the current configuration of bs to calculate the buffer list.
116;; If you want to use a different configuration for cycling you have to set
117;; the variable `bs-cycle-configuration-name'. You can customize this variable.
118;;
119;; For example: If you use the configuration called "files-and-scratch" you
120;; can cycle through all file buffers and *scratch* although your current
121;; configuration perhaps is "files" which ignores buffer *scratch*.
122
123;;; History:
124
125;;; Code:
126
127;; ----------------------------------------------------------------------
128;; Globals for customization
129;; ----------------------------------------------------------------------
130
131(defgroup bs nil
132 "Buffer Selection: Maintaining buffers by buffer menu."
00a9f6a5 133 :version "21.1"
dc7904f5 134 :link '(emacs-commentary-link "bs")
91b69101 135 :link '(url-link "http://www.geekware.de/software/emacs")
6448a6b3
GM
136 :group 'convenience)
137
880ed9f1
GM
138(defgroup bs-appearance nil
139 "Buffer Selection appearance: Appearance of bs buffer menu."
6448a6b3
GM
140 :group 'bs)
141
142(defcustom bs-attributes-list
143 '(("" 1 1 left bs--get-marked-string)
144 ("M" 1 1 left bs--get-modified-string)
145 ("R" 2 2 left bs--get-readonly-string)
146 ("Buffer" bs--get-name-length 10 left bs--get-name)
147 ("" 1 1 left " ")
148 ("Size" 8 8 right bs--get-size-string)
149 ("" 1 1 left " ")
150 ("Mode" 12 12 right bs--get-mode-name)
151 ("" 2 2 left " ")
152 ("File" 12 12 left bs--get-file-name)
153 ("" 2 2 left " "))
e749f576 154 "List specifying the layout of a Buffer Selection Menu buffer.
6448a6b3 155Each entry specifies a column and is a list of the form of:
c0cb1027 156\(HEADER MINIMUM-LENGTH MAXIMUM-LENGTH ALIGNMENT FUN-OR-STRING)
91b69101
JB
157
158HEADER : String for header for first line or a function
159 which calculates column title.
160MINIMUM-LENGTH : Minimum width of column (number or name of function).
161 The function must return a positive integer.
162MAXIMUM-LENGTH : Maximum width of column (number or name of function)
163 (currently ignored).
164ALIGNMENT : Alignment of column (`left', `right', `middle').
165FUN-OR-STRING : Name of a function for calculating the value or a
166 string for a constant value.
167
880ed9f1 168The function gets as parameter the buffer where we have started
6448a6b3 169buffer selection and the list of all buffers to show. The function must
880ed9f1
GM
170return a string representing the column's value."
171 :group 'bs-appearance
6448a6b3
GM
172 :type '(repeat sexp))
173
6448a6b3
GM
174(defun bs--make-header-match-string ()
175 "Return a regexp matching the first line of a Buffer Selection Menu buffer."
e749f576 176 (concat "^\\(" (mapconcat #'car bs-attributes-list " *") " *$\\)"))
6448a6b3 177
d8754ce5 178;; Font-Lock-Settings
6448a6b3 179(defvar bs-mode-font-lock-keywords
880ed9f1 180 (list ;; header in font-lock-type-face
dc7904f5
DL
181 (list (bs--make-header-match-string)
182 '(1 font-lock-type-face append) '(1 'bold append))
183 ;; Buffername embedded by *
184 (list "^\\(.*\\*.*\\*.*\\)$"
a5b09d5a
DL
185 1
186 ;; problem in XEmacs with font-lock-constant-face
187 (if (facep 'font-lock-constant-face)
188 'font-lock-constant-face
189 'font-lock-comment-face))
dc7904f5 190 ;; Dired-Buffers
29115ca9 191 '("^..\\(.*Dired .*\\)$" 1 font-lock-function-name-face)
dc7904f5
DL
192 ;; the star for modified buffers
193 '("^.\\(\\*\\) +[^\\*]" 1 font-lock-comment-face))
6448a6b3
GM
194 "Default font lock expressions for Buffer Selection Menu.")
195
196(defcustom bs-max-window-height 20
e749f576 197 "Maximal window height of Buffer Selection Menu."
880ed9f1 198 :group 'bs-appearance
6448a6b3
GM
199 :type 'integer)
200
201(defvar bs-dont-show-regexp nil
202 "Regular expression specifying which buffers not to show.
203A buffer whose name matches this regular expression will not be
204included in the buffer list.")
205
206(defvar bs-must-show-regexp nil
207 "Regular expression for specifying buffers which must be shown.
208A buffer whose name matches this regular expression will be
209included in the buffer list.
210Note that this variable is temporary: if the configuration is changed
211it is reset to nil. Use `bs-must-always-show-regexp' to specify buffers
212that must always be shown regardless of the configuration.")
213
214(defcustom bs-must-always-show-regexp nil
e749f576 215 "Regular expression for specifying buffers to show always.
6448a6b3
GM
216A buffer whose name matches this regular expression will
217be shown regardless of current configuration of Buffer Selection Menu."
218 :group 'bs
219 :type '(choice (const :tag "Nothing at all" nil) regexp))
220
221(defvar bs-dont-show-function nil
222 "Function for specifying buffers not to show.
223The function gets one argument - the buffer to test. The function must
224return a value different from nil to ignore the buffer in
225Buffer Selection Menu.")
226
227(defvar bs-must-show-function nil
228 "Function for specifying buffers which must be shown.
229The function gets one argument - the buffer to test.")
230
231(defvar bs-buffer-sort-function nil
232 "Sort function to sort the buffers that appear in Buffer Selection Menu.
b75ccc22
JB
233The function gets two arguments - the buffers to compare.
234It must return non-nil if the first buffer should sort before the second.")
6448a6b3
GM
235
236(defcustom bs-maximal-buffer-name-column 45
e749f576 237 "Maximum column width for buffer names.
6448a6b3
GM
238The column for buffer names has dynamic width. The width depends on
239maximal and minimal length of names of buffers to show. The maximal
240width is bounded by `bs-maximal-buffer-name-column'.
241See also `bs-minimal-buffer-name-column'."
880ed9f1 242 :group 'bs-appearance
6448a6b3
GM
243 :type 'integer)
244
245(defcustom bs-minimal-buffer-name-column 15
e749f576 246 "Minimum column width for buffer names.
6448a6b3
GM
247The column for buffer names has dynamic width. The width depends on
248maximal and minimal length of names of buffers to show. The minimal
249width is bounded by `bs-minimal-buffer-name-column'.
250See also `bs-maximal-buffer-name-column'."
880ed9f1 251 :group 'bs-appearance
6448a6b3
GM
252 :type 'integer)
253
254(defconst bs-header-lines-length 2
255 "Number of lines for headers in Buffer Selection Menu.")
256
257(defcustom bs-configurations
258 '(("all" nil nil nil nil nil)
259 ("files" nil nil nil bs-visits-non-file bs-sort-buffer-interns-are-last)
880ed9f1 260 ("files-and-scratch" "^\\*scratch\\*$" nil nil bs-visits-non-file
6448a6b3
GM
261 bs-sort-buffer-interns-are-last)
262 ("all-intern-last" nil nil nil nil bs-sort-buffer-interns-are-last))
e749f576 263 "List of all configurations you can use in the Buffer Selection Menu.
6448a6b3 264A configuration describes which buffers appear in Buffer Selection Menu
880ed9f1 265and also the order of buffers. A configuration is a list with
6448a6b3
GM
266six elements. The first element is a string and describes the configuration.
267The following five elements represent the values for Buffer Selection Menu
880ed9f1
GM
268configuration variables `bs-must-show-regexp', `bs-must-show-function',
269`bs-dont-show-regexp', `bs-dont-show-function' and `bs-buffer-sort-function'.
6448a6b3 270By setting these variables you define a configuration."
880ed9f1 271 :group 'bs-appearance
6448a6b3
GM
272 :type '(repeat sexp))
273
274(defcustom bs-default-configuration "files"
e749f576 275 "Name of default configuration used by the Buffer Selection Menu.
6448a6b3
GM
276\\<bs-mode-map>
277Will be changed using key \\[bs-select-next-configuration].
278Must be a string used in `bs-configurations' for naming a configuration."
279 :group 'bs
280 :type 'string)
281
282(defcustom bs-alternative-configuration "all"
e749f576 283 "Name of configuration used when calling `bs-show' with \
6448a6b3
GM
284\\[universal-argument] as prefix key.
285Must be a string used in `bs-configurations' for naming a configuration."
286 :group 'bs
287 :type 'string)
288
289(defvar bs-current-configuration bs-default-configuration
290 "Name of current configuration.
880ed9f1 291Must be a string used in `bs-configurations' for naming a configuration.")
6448a6b3
GM
292
293(defcustom bs-cycle-configuration-name nil
e749f576 294 "Name of configuration used when cycling through the buffer list.
6448a6b3
GM
295A value of nil means to use current configuration `bs-default-configuration'.
296Must be a string used in `bs-configurations' for naming a configuration."
297 :group 'bs
298 :type '(choice (const :tag "like current configuration" nil)
299 string))
300
301(defcustom bs-string-show-always "+"
e749f576 302 "String added in column 1 indicating a buffer will always be shown."
880ed9f1 303 :group 'bs-appearance
6448a6b3
GM
304 :type 'string)
305
306(defcustom bs-string-show-never "-"
e749f576 307 "String added in column 1 indicating a buffer will never be shown."
880ed9f1 308 :group 'bs-appearance
6448a6b3
GM
309 :type 'string)
310
311(defcustom bs-string-current "."
e749f576 312 "String added in column 1 indicating the current buffer."
880ed9f1 313 :group 'bs-appearance
6448a6b3
GM
314 :type 'string)
315
316(defcustom bs-string-current-marked "#"
e749f576 317 "String added in column 1 indicating the current buffer when it is marked."
880ed9f1 318 :group 'bs-appearance
6448a6b3
GM
319 :type 'string)
320
321(defcustom bs-string-marked ">"
e749f576 322 "String added in column 1 indicating a marked buffer."
880ed9f1 323 :group 'bs-appearance
6448a6b3
GM
324 :type 'string)
325
326(defcustom bs-string-show-normally " "
e749f576 327 "String added in column 1 indicating an unmarked buffer."
880ed9f1 328 :group 'bs-appearance
6448a6b3
GM
329 :type 'string)
330
331(defvar bs--name-entry-length 20
332 "Maximum length of all displayed buffer names.
333Used internally, only.")
334
335;; ----------------------------------------------------------------------
880ed9f1 336;; Internal globals
6448a6b3
GM
337;; ----------------------------------------------------------------------
338
339(defvar bs-buffer-show-mark nil
340 "Flag for the current mode for showing this buffer.
880ed9f1
GM
341A value of nil means buffer will be shown depending on the current
342configuration.
6448a6b3
GM
343A value of `never' means to never show the buffer.
344A value of `always' means to show buffer regardless of the configuration.")
345
346(make-variable-buffer-local 'bs-buffer-show-mark)
347
348;; Make face named region (for XEmacs)
349(unless (facep 'region)
350 (make-face 'region)
351 (set-face-background 'region "gray75"))
352
6448a6b3
GM
353(defun bs--sort-by-name (b1 b2)
354 "Compare buffers B1 and B2 by buffer name."
355 (string< (buffer-name b1)
dc7904f5 356 (buffer-name b2)))
6448a6b3
GM
357
358(defun bs--sort-by-filename (b1 b2)
359 "Compare buffers B1 and B2 by file name."
360 (string< (or (buffer-file-name b1) "")
dc7904f5 361 (or (buffer-file-name b2) "")))
6448a6b3
GM
362
363(defun bs--sort-by-mode (b1 b2)
364 "Compare buffers B1 and B2 by mode name."
48d33090
SM
365 (save-current-buffer
366 (string< (progn (set-buffer b1) (format-mode-line mode-name nil nil b1))
367 (progn (set-buffer b2) (format-mode-line mode-name nil nil b2)))))
6448a6b3
GM
368
369(defun bs--sort-by-size (b1 b2)
370 "Compare buffers B1 and B2 by buffer size."
70ef8857 371 (< (buffer-size b1) (buffer-size b2)))
6448a6b3
GM
372
373(defcustom bs-sort-functions
374 '(("by name" bs--sort-by-name "Buffer" region)
375 ("by size" bs--sort-by-size "Size" region)
376 ("by mode" bs--sort-by-mode "Mode" region)
377 ("by filename" bs--sort-by-filename "File" region)
378 ("by nothing" nil nil nil))
e749f576 379 "List of all possible sorting aspects for Buffer Selection Menu.
6448a6b3 380You can add a new entry with a call to `bs-define-sort-function'.
b75ccc22 381Each element is a list of four elements (NAME FUNCTION REGEXP-FOR-SORTING FACE).
6448a6b3 382NAME specifies the sort order defined by function FUNCTION.
b75ccc22 383FUNCTION nil means don't sort the buffer list. Otherwise the function
6448a6b3
GM
384must have two parameters - the buffers to compare.
385REGEXP-FOR-SORTING is a regular expression which describes the
386column title to highlight.
387FACE is a face used to fontify the sorted column title. A value of nil means
388don't highlight."
389 :group 'bs
390 :type '(repeat sexp))
391
392(defun bs-define-sort-function (name fun &optional regexp-for-sorting face)
393 "Define a new function for buffer sorting in Buffer Selection Menu.
394NAME specifies the sort order defined by function FUN.
395A value of nil for FUN means don't sort the buffer list. Otherwise the
396functions must have two parameters - the buffers to compare.
397REGEXP-FOR-SORTING is a regular expression which describes the
398column title to highlight.
399FACE is a face used to fontify the sorted column title. A value of nil means
400don't highlight.
401The new sort aspect will be inserted into list `bs-sort-functions'."
402 (let ((tupel (assoc name bs-sort-functions)))
403 (if tupel
dc7904f5 404 (setcdr tupel (list fun regexp-for-sorting face))
6448a6b3 405 (setq bs-sort-functions
dc7904f5
DL
406 (cons (list name fun regexp-for-sorting face)
407 bs-sort-functions)))))
6448a6b3
GM
408
409(defvar bs--current-sort-function nil
410 "Description of the current function for sorting the buffer list.
411This is an element of `bs-sort-functions'.")
412
413(defcustom bs-default-sort-name "by nothing"
e749f576 414 "Name of default sort behavior.
6448a6b3
GM
415Must be \"by nothing\" or a string used in `bs-sort-functions' for
416naming a sort behavior. Default is \"by nothing\" which means no sorting."
417 :group 'bs
418 :type 'string
419 :set (lambda (var-name value)
dc7904f5
DL
420 (set var-name value)
421 (setq bs--current-sort-function
422 (assoc value bs-sort-functions))))
6448a6b3
GM
423
424(defvar bs--buffer-coming-from nil
425 "The buffer in which the user started the current Buffer Selection Menu.")
426
427(defvar bs--show-all nil
428 "Flag whether showing all buffers regardless of current configuration.
aaf08d6b 429Non-nil means to show all buffers. Otherwise show buffers
6448a6b3
GM
430defined by current configuration `bs-current-configuration'.")
431
432(defvar bs--window-config-coming-from nil
433 "Window configuration before starting Buffer Selection Menu.")
434
435(defvar bs--intern-show-never "^ \\|\\*buffer-selection\\*"
436 "Regular expression specifying which buffers never to show.
437A buffer whose name matches this regular expression will never be
438included in the buffer list.")
439
440(defvar bs-current-list nil
441 "List of buffers shown in Buffer Selection Menu.
442Used internally, only.")
443
444(defvar bs--marked-buffers nil
445 "Currently marked buffers in Buffer Selection Menu.")
446
e6e76838
AS
447(defvar bs-mode-map
448 (let ((map (make-sparse-keymap)))
449 (define-key map " " 'bs-select)
450 (define-key map "f" 'bs-select)
451 (define-key map "v" 'bs-view)
452 (define-key map "!" 'bs-select-in-one-window)
453 (define-key map [mouse-2] 'bs-mouse-select) ;; for GNU EMACS
454 (define-key map [button2] 'bs-mouse-select) ;; for XEmacs
455 (define-key map "F" 'bs-select-other-frame)
456 (let ((key ?1))
457 (while (<= key ?9)
458 (define-key map (char-to-string key) 'digit-argument)
459 (setq key (1+ key))))
460 (define-key map "-" 'negative-argument)
461 (define-key map "\e-" 'negative-argument)
462 (define-key map "o" 'bs-select-other-window)
463 (define-key map "\C-o" 'bs-tmp-select-other-window)
464 ;; for GNU EMACS
465 (define-key map [mouse-3] 'bs-mouse-select-other-frame)
466 ;; for XEmacs
467 (define-key map [button3] 'bs-mouse-select-other-frame)
468 (define-key map [up] 'bs-up)
469 (define-key map "n" 'bs-down)
470 (define-key map "p" 'bs-up)
471 (define-key map [down] 'bs-down)
472 (define-key map "\C-m" 'bs-select)
473 (define-key map "b" 'bs-bury-buffer)
474 (define-key map "s" 'bs-save)
475 (define-key map "S" 'bs-show-sorted)
476 (define-key map "a" 'bs-toggle-show-all)
477 (define-key map "d" 'bs-delete)
478 (define-key map "\C-d" 'bs-delete-backward)
479 (define-key map "k" 'bs-delete)
480 (define-key map "g" 'bs-refresh)
481 (define-key map "C" 'bs-set-configuration-and-refresh)
482 (define-key map "c" 'bs-select-next-configuration)
483 (define-key map "q" 'bs-kill)
484 ;; (define-key map "z" 'bs-kill)
485 (define-key map "\C-c\C-c" 'bs-kill)
486 (define-key map "\C-g" 'bs-abort)
487 (define-key map "\C-]" 'bs-abort)
488 (define-key map "%" 'bs-toggle-readonly)
489 (define-key map "~" 'bs-clear-modified)
490 (define-key map "M" 'bs-toggle-current-to-show)
491 (define-key map "+" 'bs-set-current-buffer-to-show-always)
492 ;;(define-key map "-" 'bs-set-current-buffer-to-show-never)
493 (define-key map "t" 'bs-visit-tags-table)
494 (define-key map "m" 'bs-mark-current)
495 (define-key map "u" 'bs-unmark-current)
496 (define-key map ">" 'scroll-right)
497 (define-key map "<" 'scroll-left)
498 (define-key map "?" 'bs-help)
499 map)
6448a6b3
GM
500 "Keymap of `bs-mode'.")
501
6448a6b3
GM
502;; ----------------------------------------------------------------------
503;; Functions
504;; ----------------------------------------------------------------------
505
506(defun bs-buffer-list (&optional list sort-description)
507 "Return a list of buffers to be shown.
880ed9f1 508LIST is a list of buffers to test for appearance in Buffer Selection Menu.
6448a6b3
GM
509The result list depends on the global variables `bs-dont-show-regexp',
510`bs-must-show-regexp', `bs-dont-show-function', `bs-must-show-function'
511and `bs-buffer-sort-function'.
512If SORT-DESCRIPTION isn't nil the list will be sorted by
513a special function. SORT-DESCRIPTION is an element of `bs-sort-functions'."
514 (setq sort-description (or sort-description bs--current-sort-function)
dc7904f5 515 list (or list (buffer-list)))
6448a6b3 516 (let ((result nil))
e749f576
JB
517 (dolist (buf list)
518 (let* ((buffername (buffer-name buf))
519 (int-show-never (string-match-p bs--intern-show-never buffername))
dc7904f5 520 (ext-show-never (and bs-dont-show-regexp
e749f576
JB
521 (string-match-p bs-dont-show-regexp
522 buffername)))
dc7904f5 523 (extern-must-show (or (and bs-must-always-show-regexp
e749f576 524 (string-match-p
dc7904f5
DL
525 bs-must-always-show-regexp
526 buffername))
527 (and bs-must-show-regexp
e749f576
JB
528 (string-match-p bs-must-show-regexp
529 buffername))))
dc7904f5
DL
530 (extern-show-never-from-fun (and bs-dont-show-function
531 (funcall bs-dont-show-function
e749f576 532 buf)))
dc7904f5
DL
533 (extern-must-show-from-fun (and bs-must-show-function
534 (funcall bs-must-show-function
e749f576
JB
535 buf)))
536 (show-flag (buffer-local-value 'bs-buffer-show-mark buf)))
537 (when (or (eq show-flag 'always)
538 (and (or bs--show-all (not (eq show-flag 'never)))
539 (not int-show-never)
540 (or bs--show-all
541 extern-must-show
542 extern-must-show-from-fun
543 (and (not ext-show-never)
544 (not extern-show-never-from-fun)))))
545 (setq result (cons buf result)))))
6448a6b3
GM
546 (setq result (reverse result))
547 ;; The current buffer which was the start point of bs should be an element
548 ;; of result list, so that we can leave with space and be back in the
549 ;; buffer we started bs-show.
e749f576
JB
550 (when (and bs--buffer-coming-from
551 (buffer-live-p bs--buffer-coming-from)
552 (not (memq bs--buffer-coming-from result)))
553 (setq result (cons bs--buffer-coming-from result)))
6448a6b3
GM
554 ;; sorting
555 (if (and sort-description
dc7904f5
DL
556 (nth 1 sort-description))
557 (setq result (sort result (nth 1 sort-description)))
6448a6b3
GM
558 ;; else standard sorting
559 (bs-buffer-sort result))))
560
561(defun bs-buffer-sort (buffer-list)
562 "Sort buffers in BUFFER-LIST according to `bs-buffer-sort-function'."
563 (if bs-buffer-sort-function
564 (sort buffer-list bs-buffer-sort-function)
565 buffer-list))
566
567(defun bs--redisplay (&optional keep-line-p sort-description)
568 "Redisplay whole Buffer Selection Menu.
aaf08d6b 569If KEEP-LINE-P is non-nil the point will stay on current line.
ce3ba12c 570SORT-DESCRIPTION is an element of `bs-sort-functions'."
e6ce8c42 571 (let ((line (count-lines 1 (point))))
6448a6b3 572 (bs-show-in-buffer (bs-buffer-list nil sort-description))
e749f576 573 (when keep-line-p
e6ce8c42
GM
574 (goto-char (point-min))
575 (forward-line line))
6448a6b3
GM
576 (beginning-of-line)))
577
578(defun bs--goto-current-buffer ()
579 "Goto line which represents the current buffer;
580actually the line which begins with character in `bs-string-current' or
581`bs-string-current-marked'."
dc7904f5
DL
582 (let ((regexp (concat "^"
583 (regexp-quote bs-string-current)
584 "\\|^"
585 (regexp-quote bs-string-current-marked)))
586 point)
6448a6b3
GM
587 (save-excursion
588 (goto-char (point-min))
e749f576
JB
589 (when (search-forward-regexp regexp nil t)
590 (setq point (1- (point)))))
591 (when point
592 (goto-char point))))
6448a6b3
GM
593
594(defun bs--current-config-message ()
595 "Return a string describing the current `bs-mode' configuration."
596 (if bs--show-all
597 "Show all buffers."
598 (format "Show buffer by configuration %S"
dc7904f5 599 bs-current-configuration)))
6448a6b3 600
e749f576
JB
601(defun bs--track-window-changes (frame)
602 "Track window changes to refresh the buffer list.
603Used from `window-size-change-functions'."
604 (let ((win (get-buffer-window "*buffer-selection*" frame)))
605 (when win
606 (with-selected-window win
e749f576
JB
607 (bs--set-window-height)))))
608
609(defun bs--remove-hooks ()
610 "Remove `bs--track-window-changes' and auxiliary hooks."
611 (remove-hook 'window-size-change-functions 'bs--track-window-changes)
612 ;; Remove itself
613 (remove-hook 'kill-buffer-hook 'bs--remove-hooks t)
614 (remove-hook 'change-major-mode-hook 'bs--remove-hooks t))
615
5c742460
JB
616(put 'bs-mode 'mode-class 'special)
617
e749f576 618(define-derived-mode bs-mode nil "Buffer-Selection-Menu"
44e97401 619 "Major mode for editing a subset of Emacs's buffers.
6448a6b3
GM
620\\<bs-mode-map>
621Aside from two header lines each line describes one buffer.
622Move to a line representing the buffer you want to edit and select
462db93c 623buffer by \\[bs-select] or SPC. Abort buffer list with \\[bs-kill].
6448a6b3
GM
624There are many key commands similar to `Buffer-menu-mode' for
625manipulating the buffer list and buffers.
626For faster navigation each digit key is a digit argument.
627
628\\[bs-select] or SPACE -- select current line's buffer and other marked buffers.
629\\[bs-toggle-show-all] -- toggle between all buffers and a special subset.
630\\[bs-select-other-window] -- select current line's buffer in other window.
631\\[bs-tmp-select-other-window] -- make another window display that buffer and
632 remain in Buffer Selection Menu.
633\\[bs-mouse-select] -- select current line's buffer and other marked buffers.
e148f726
TTN
634\\[bs-save] -- save current line's buffer immediately.
635\\[bs-delete] -- kill current line's buffer immediately.
6448a6b3
GM
636\\[bs-toggle-readonly] -- toggle read-only status of current line's buffer.
637\\[bs-clear-modified] -- clear modified-flag on that buffer.
638\\[bs-mark-current] -- mark current line's buffer to be displayed.
639\\[bs-unmark-current] -- unmark current line's buffer to be displayed.
640\\[bs-show-sorted] -- display buffer list sorted by next sort aspect.
641\\[bs-set-configuration-and-refresh] -- ask user for a configuration and \
642apply selected configuration.
643\\[bs-select-next-configuration] -- select and apply next \
644available Buffer Selection Menu configuration.
645\\[bs-kill] -- leave Buffer Selection Menu without a selection.
880ed9f1 646\\[bs-toggle-current-to-show] -- toggle status of appearance.
6448a6b3
GM
647\\[bs-set-current-buffer-to-show-always] -- mark current line's buffer \
648to show always.
880ed9f1 649\\[bs-visit-tags-table] -- call `visit-tags-table' on current line's buffer.
6448a6b3 650\\[bs-help] -- display this help text."
6448a6b3
GM
651 (make-local-variable 'font-lock-defaults)
652 (make-local-variable 'font-lock-verbose)
5a25a895 653 (make-local-variable 'font-lock-global-modes)
310a26be 654 (buffer-disable-undo)
e749f576 655 (setq buffer-read-only t
dc7904f5 656 truncate-lines t
e0d0133a 657 show-trailing-whitespace nil
5a25a895 658 font-lock-global-modes '(not bs-mode)
dc7904f5 659 font-lock-defaults '(bs-mode-font-lock-keywords t)
1e7a80f2
JB
660 font-lock-verbose nil)
661 (set (make-local-variable 'revert-buffer-function) 'bs-refresh)
e749f576
JB
662 (add-hook 'window-size-change-functions 'bs--track-window-changes)
663 (add-hook 'kill-buffer-hook 'bs--remove-hooks nil t)
664 (add-hook 'change-major-mode-hook 'bs--remove-hooks nil t))
6448a6b3 665
ce3ba12c
JB
666(defun bs--restore-window-config ()
667 "Restore window configuration on the current frame."
668 (when bs--window-config-coming-from
e749f576
JB
669 (let ((frame (selected-frame)))
670 (unwind-protect
671 (set-window-configuration bs--window-config-coming-from)
672 (select-frame frame)))
ce3ba12c
JB
673 (setq bs--window-config-coming-from nil)))
674
6448a6b3 675(defun bs-kill ()
6af00b67 676 "Let buffer disappear and reset window configuration."
6448a6b3
GM
677 (interactive)
678 (bury-buffer (current-buffer))
ce3ba12c 679 (bs--restore-window-config))
6448a6b3
GM
680
681(defun bs-abort ()
682 "Ding and leave Buffer Selection Menu without a selection."
dc7904f5 683 (interactive)
6448a6b3
GM
684 (ding)
685 (bs-kill))
686
687(defun bs-set-configuration-and-refresh ()
688 "Ask user for a configuration and apply selected configuration.
689Refresh whole Buffer Selection Menu."
690 (interactive)
691 (call-interactively 'bs-set-configuration)
692 (bs--redisplay t))
693
2fbc1934 694(defun bs-refresh (&rest _ignored)
fa72d075
JB
695 "Refresh whole Buffer Selection Menu.
696Arguments are IGNORED (for `revert-buffer')."
6448a6b3
GM
697 (interactive)
698 (bs--redisplay t))
699
6448a6b3
GM
700(defun bs--set-window-height ()
701 "Change the height of the selected window to suit the current buffer list."
702 (unless (one-window-p t)
ca6b8248 703 (fit-window-to-buffer (selected-window) bs-max-window-height)))
6448a6b3
GM
704
705(defun bs--current-buffer ()
706 "Return buffer on current line.
880ed9f1 707Raise an error if not on a buffer line."
6448a6b3
GM
708 (beginning-of-line)
709 (let ((line (+ (- bs-header-lines-length)
dc7904f5 710 (count-lines 1 (point)))))
e749f576
JB
711 (when (< line 0)
712 (error "You are on a header row"))
6448a6b3
GM
713 (nth line bs-current-list)))
714
715(defun bs--update-current-line ()
716 "Update the entry on current line for Buffer Selection Menu."
717 (let ((buffer (bs--current-buffer))
dc7904f5 718 (inhibit-read-only t))
6448a6b3
GM
719 (beginning-of-line)
720 (delete-region (point) (line-end-position))
721 (bs--insert-one-entry buffer)
722 (beginning-of-line)))
723
724(defun bs-view ()
725 "View current line's buffer in View mode.
726Leave Buffer Selection Menu."
727 (interactive)
728 (view-buffer (bs--current-buffer)))
729
730(defun bs-select ()
731 "Select current line's buffer and other marked buffers.
732If there are no marked buffers the window configuration before starting
de302d45 733Buffer Selection Menu will be restored.
6448a6b3
GM
734If there are marked buffers each marked buffer and the current line's buffer
735will be selected in a window.
736Leave Buffer Selection Menu."
737 (interactive)
738 (let ((buffer (bs--current-buffer)))
739 (bury-buffer (current-buffer))
ce3ba12c 740 (bs--restore-window-config)
6448a6b3 741 (switch-to-buffer buffer)
e749f576
JB
742 (when bs--marked-buffers
743 ;; Some marked buffers for selection
744 (let* ((all (delq buffer bs--marked-buffers))
745 (height (/ (1- (frame-height)) (1+ (length all)))))
746 (delete-other-windows)
747 (switch-to-buffer buffer)
748 (dolist (buf all)
749 (split-window nil height)
750 (other-window 1)
751 (switch-to-buffer buf))
752 ;; goto window we have started bs.
753 (other-window 1)))))
6448a6b3
GM
754
755(defun bs-select-other-window ()
756 "Select current line's buffer by `switch-to-buffer-other-window'.
de302d45 757The window configuration before starting Buffer Selection Menu will be restored
6448a6b3
GM
758unless there is no other window. In this case a new window will be created.
759Leave Buffer Selection Menu."
760 (interactive)
761 (let ((buffer (bs--current-buffer)))
762 (bury-buffer (current-buffer))
ce3ba12c 763 (bs--restore-window-config)
6448a6b3
GM
764 (switch-to-buffer-other-window buffer)))
765
766(defun bs-tmp-select-other-window ()
767 "Make the other window select this line's buffer.
768The current window remains selected."
769 (interactive)
770 (let ((buffer (bs--current-buffer)))
771 (display-buffer buffer t)))
772
773(defun bs-select-other-frame ()
774 "Select current line's buffer in new created frame.
775Leave Buffer Selection Menu."
776 (interactive)
777 (let ((buffer (bs--current-buffer)))
778 (bury-buffer (current-buffer))
ce3ba12c 779 (bs--restore-window-config)
6448a6b3
GM
780 (switch-to-buffer-other-frame buffer)))
781
782(defun bs-mouse-select-other-frame (event)
783 "Select selected line's buffer in new created frame.
784Leave Buffer Selection Menu.
b75ccc22 785EVENT: a mouse click event."
6448a6b3
GM
786 (interactive "e")
787 (mouse-set-point event)
788 (bs-select-other-frame))
789
790(defun bs-mouse-select (event)
791 "Select buffer on mouse click EVENT.
792Select buffer by `bs-select'."
793 (interactive "e")
794 (mouse-set-point event)
795 (bs-select))
796
797(defun bs-select-in-one-window ()
798 "Select current line's buffer in one window and delete other windows.
799Leave Buffer Selection Menu."
800 (interactive)
801 (bs-select)
802 (delete-other-windows))
803
804(defun bs-bury-buffer ()
805 "Bury buffer on current line."
806 (interactive)
807 (bury-buffer (bs--current-buffer))
808 (bs--redisplay t))
809
810(defun bs-save ()
811 "Save buffer on current line."
812 (interactive)
70ef8857 813 (with-current-buffer (bs--current-buffer)
3af36712
SM
814 (save-buffer))
815 (bs--update-current-line))
6448a6b3
GM
816
817(defun bs-visit-tags-table ()
818 "Visit the tags table in the buffer on this line.
819See `visit-tags-table'."
820 (interactive)
821 (let ((file (buffer-file-name (bs--current-buffer))))
822 (if file
dc7904f5 823 (visit-tags-table file)
6448a6b3
GM
824 (error "Specified buffer has no file"))))
825
826(defun bs-toggle-current-to-show ()
827 "Toggle status of showing flag for buffer in current line."
828 (interactive)
70ef8857
SM
829 (let ((res
830 (with-current-buffer (bs--current-buffer)
f58e0fd5
SM
831 (setq bs-buffer-show-mark (pcase bs-buffer-show-mark
832 (`nil 'never)
833 (`never 'always)
834 (_ nil))))))
6448a6b3
GM
835 (bs--update-current-line)
836 (bs--set-window-height)
837 (bs--show-config-message res)))
838
839(defun bs-set-current-buffer-to-show-always (&optional not-to-show-p)
840 "Toggle status of buffer on line to `always shown'.
841NOT-TO-SHOW-P: prefix argument.
842With no prefix argument the buffer on current line is marked to show
843always. Otherwise it is marked to show never."
844 (interactive "P")
845 (if not-to-show-p
846 (bs-set-current-buffer-to-show-never)
847 (bs--set-toggle-to-show (bs--current-buffer) 'always)))
848
849(defun bs-set-current-buffer-to-show-never ()
850 "Toggle status of buffer on line to `never shown'."
851 (interactive)
852 (bs--set-toggle-to-show (bs--current-buffer) 'never))
853
854(defun bs--set-toggle-to-show (buffer what)
855 "Set value `bs-buffer-show-mark' of buffer BUFFER to WHAT.
856Redisplay current line and display a message describing
857the status of buffer on current line."
e64dbd8b 858 (with-current-buffer buffer (setq bs-buffer-show-mark what))
6448a6b3
GM
859 (bs--update-current-line)
860 (bs--set-window-height)
861 (bs--show-config-message what))
862
3d5aef76
JB
863(defun bs--mark-unmark (count fun)
864 "Call FUN on COUNT consecutive buffers of *buffer-selection*."
865 (let ((dir (if (> count 0) 1 -1)))
b3e945d3 866 (dotimes (_i (abs count))
3d5aef76
JB
867 (let ((buffer (bs--current-buffer)))
868 (when buffer (funcall fun buffer))
869 (bs--update-current-line)
870 (bs-down dir)))))
871
6448a6b3
GM
872(defun bs-mark-current (count)
873 "Mark buffers.
874COUNT is the number of buffers to mark.
875Move cursor vertically down COUNT lines."
876 (interactive "p")
3d5aef76
JB
877 (bs--mark-unmark count
878 (lambda (buf)
879 (add-to-list 'bs--marked-buffers buf))))
6448a6b3
GM
880
881(defun bs-unmark-current (count)
882 "Unmark buffers.
883COUNT is the number of buffers to unmark.
884Move cursor vertically down COUNT lines."
885 (interactive "p")
3d5aef76
JB
886 (bs--mark-unmark count
887 (lambda (buf)
888 (setq bs--marked-buffers (delq buf bs--marked-buffers)))))
6448a6b3
GM
889
890(defun bs--show-config-message (what)
891 "Show message indicating the new showing status WHAT.
892WHAT is a value of nil, `never', or `always'."
893 (bs-message-without-log (cond ((null what)
dc7904f5
DL
894 "Buffer will be shown normally.")
895 ((eq what 'never)
896 "Mark buffer to never be shown.")
897 (t "Mark buffer to show always."))))
6448a6b3
GM
898
899(defun bs-delete ()
900 "Kill buffer on current line."
901 (interactive)
902 (let ((current (bs--current-buffer))
dc7904f5 903 (inhibit-read-only t))
706e6a52
EZ
904 (unless (kill-buffer current)
905 (error "Buffer was not deleted"))
6448a6b3 906 (setq bs-current-list (delq current bs-current-list))
6448a6b3
GM
907 (beginning-of-line)
908 (delete-region (point) (save-excursion
dc7904f5
DL
909 (end-of-line)
910 (if (eobp) (point) (1+ (point)))))
e749f576
JB
911 (when (eobp)
912 (backward-delete-char 1)
913 (beginning-of-line)
914 (recenter -1))
6448a6b3
GM
915 (bs--set-window-height)))
916
917(defun bs-delete-backward ()
918 "Like `bs-delete' but go to buffer in front of current."
919 (interactive)
920 (let ((on-last-line-p (save-excursion (end-of-line) (eobp))))
921 (bs-delete)
922 (unless on-last-line-p
dc7904f5 923 (bs-up 1))))
6448a6b3
GM
924
925(defun bs-show-sorted ()
5a25a895 926 "Show buffer list sorted by next sort aspect."
6448a6b3
GM
927 (interactive)
928 (setq bs--current-sort-function
dc7904f5
DL
929 (bs-next-config-aux (car bs--current-sort-function)
930 bs-sort-functions))
6448a6b3
GM
931 (bs--redisplay)
932 (bs--goto-current-buffer)
933 (bs-message-without-log "Sorted %s" (car bs--current-sort-function)))
934
935(defun bs-apply-sort-faces (&optional sort-description)
936 "Set text properties for the sort described by SORT-DESCRIPTION.
937SORT-DESCRIPTION is an element of `bs-sort-functions'.
938Default is `bs--current-sort-function'."
939 (let ((sort-description (or sort-description
dc7904f5 940 bs--current-sort-function)))
6448a6b3
GM
941 (save-excursion
942 (goto-char (point-min))
e749f576
JB
943 (when (and (nth 2 sort-description)
944 (search-forward-regexp (nth 2 sort-description) nil t))
945 (let ((inhibit-read-only t))
946 (put-text-property (match-beginning 0)
947 (match-end 0)
948 'face
949 (or (nth 3 sort-description)
950 'region)))))))
6448a6b3
GM
951
952(defun bs-toggle-show-all ()
953 "Toggle show all buffers / show buffers with current configuration."
954 (interactive)
955 (setq bs--show-all (not bs--show-all))
956 (bs--redisplay)
957 (bs--goto-current-buffer)
958 (bs-message-without-log "%s" (bs--current-config-message)))
959
960(defun bs-toggle-readonly ()
961 "Toggle read-only status for buffer on current line.
70ef8857 962Uses function `toggle-read-only'."
6448a6b3 963 (interactive)
70ef8857 964 (with-current-buffer (bs--current-buffer)
9a930676 965 (read-only-mode 'toggle))
70ef8857 966 (bs--update-current-line))
6448a6b3
GM
967
968(defun bs-clear-modified ()
969 "Set modified flag for buffer on current line to nil."
970 (interactive)
70ef8857
SM
971 (with-current-buffer (bs--current-buffer)
972 (set-buffer-modified-p nil))
6448a6b3
GM
973 (bs--update-current-line))
974
975(defun bs--nth-wrapper (count fun &rest args)
976 "Call COUNT times function FUN with arguments ARGS."
b3e945d3 977 (dotimes (_i (or count 1))
e749f576 978 (apply fun args)))
6448a6b3
GM
979
980(defun bs-up (arg)
981 "Move cursor vertically up ARG lines in Buffer Selection Menu."
982 (interactive "p")
983 (if (and arg (numberp arg) (< arg 0))
984 (bs--nth-wrapper (- arg) 'bs--down)
985 (bs--nth-wrapper arg 'bs--up)))
986
987(defun bs--up ()
988 "Move cursor vertically up one line.
989If on top of buffer list go to last line."
ca6b8248
JB
990 (if (> (count-lines 1 (point)) bs-header-lines-length)
991 (forward-line -1)
992 (goto-char (point-max))
993 (beginning-of-line)
994 (recenter -1)))
6448a6b3
GM
995
996(defun bs-down (arg)
997 "Move cursor vertically down ARG lines in Buffer Selection Menu."
998 (interactive "p")
999 (if (and arg (numberp arg) (< arg 0))
1000 (bs--nth-wrapper (- arg) 'bs--up)
1001 (bs--nth-wrapper arg 'bs--down)))
1002
1003(defun bs--down ()
1004 "Move cursor vertically down one line.
1005If at end of buffer list go to first line."
ca6b8248 1006 (if (eq (line-end-position) (point-max))
936934f5 1007 (progn
e600eb79 1008 (goto-char (point-min))
936934f5 1009 (forward-line bs-header-lines-length))
ca6b8248 1010 (forward-line 1)))
6448a6b3
GM
1011
1012(defun bs-visits-non-file (buffer)
b75ccc22 1013 "Return whether BUFFER visits no file.
6448a6b3
GM
1014A value of t means BUFFER belongs to no file.
1015A value of nil means BUFFER belongs to a file."
1016 (not (buffer-file-name buffer)))
1017
2fbc1934 1018(defun bs-sort-buffer-interns-are-last (_b1 b2)
b75ccc22 1019 "Function for sorting internal buffers at the end of all buffers."
e749f576 1020 (string-match-p "^\\*" (buffer-name b2)))
6448a6b3
GM
1021
1022;; ----------------------------------------------------------------------
1023;; Configurations:
1024;; ----------------------------------------------------------------------
1025
1026(defun bs-config-clear ()
e0d0133a 1027 "Reset all variables which specify a configuration.
6448a6b3
GM
1028These variables are `bs-dont-show-regexp', `bs-must-show-regexp',
1029`bs-dont-show-function', `bs-must-show-function' and
1030`bs-buffer-sort-function'."
1031 (setq bs-dont-show-regexp nil
dc7904f5
DL
1032 bs-must-show-regexp nil
1033 bs-dont-show-function nil
1034 bs-must-show-function nil
1035 bs-buffer-sort-function nil))
6448a6b3
GM
1036
1037(defun bs-config--only-files ()
1038 "Define a configuration for showing only buffers visiting a file."
1039 (bs-config-clear)
880ed9f1 1040 (setq ;; I want to see *-buffers at the end
dc7904f5
DL
1041 bs-buffer-sort-function 'bs-sort-buffer-interns-are-last
1042 ;; Don't show files who don't belong to a file
1043 bs-dont-show-function 'bs-visits-non-file))
6448a6b3
GM
1044
1045(defun bs-config--files-and-scratch ()
1046 "Define a configuration for showing buffer *scratch* and file buffers."
1047 (bs-config-clear)
880ed9f1 1048 (setq ;; I want to see *-buffers at the end
dc7904f5
DL
1049 bs-buffer-sort-function 'bs-sort-buffer-interns-are-last
1050 ;; Don't show files who don't belong to a file
1051 bs-dont-show-function 'bs-visits-non-file
1052 ;; Show *scratch* buffer.
880ed9f1 1053 bs-must-show-regexp "^\\*scratch\\*$"))
6448a6b3
GM
1054
1055(defun bs-config--all ()
1056 "Define a configuration for showing all buffers.
1057Reset all according variables by `bs-config-clear'."
1058 (bs-config-clear))
1059
1060(defun bs-config--all-intern-last ()
1061 "Define a configuration for showing all buffers.
880ed9f1 1062Internal buffers appear at end of all buffers."
6448a6b3
GM
1063 (bs-config-clear)
1064 ;; I want to see *-buffers at the end
1065 (setq bs-buffer-sort-function 'bs-sort-buffer-interns-are-last))
1066
1067(defun bs-set-configuration (name)
1068 "Set configuration to the one saved under string NAME in `bs-configurations'.
1069When called interactively ask user for a configuration and apply selected
1070configuration."
1071 (interactive (list (completing-read "Use configuration: "
dc7904f5
DL
1072 bs-configurations
1073 nil
1074 t)))
6448a6b3
GM
1075 (let ((list (assoc name bs-configurations)))
1076 (if list
dc7904f5
DL
1077 (if (listp list)
1078 (setq bs-current-configuration name
1079 bs-must-show-regexp (nth 1 list)
1080 bs-must-show-function (nth 2 list)
1081 bs-dont-show-regexp (nth 3 list)
1082 bs-dont-show-function (nth 4 list)
1083 bs-buffer-sort-function (nth 5 list))
135377f1 1084 ;; for backward compatibility
dc7904f5 1085 (funcall (cdr list)))
6448a6b3
GM
1086 ;; else
1087 (ding)
1088 (bs-message-without-log "No bs-configuration named %S." name))))
1089
1090(defun bs-help ()
1091 "Help for `bs-show'."
1092 (interactive)
1093 (describe-function 'bs-mode))
1094
1095(defun bs-next-config-aux (start-name list)
1096 "Get the next assoc after START-NAME in list LIST.
1097Will return the first if START-NAME is at end."
1098 (let ((assocs list)
dc7904f5
DL
1099 (length (length list))
1100 pos)
6448a6b3 1101 (while (and assocs (not pos))
e749f576
JB
1102 (when (string= (car (car assocs)) start-name)
1103 (setq pos (- length (length assocs))))
6448a6b3
GM
1104 (setq assocs (cdr assocs)))
1105 (setq pos (1+ pos))
1106 (if (eq pos length)
dc7904f5 1107 (car list)
6448a6b3
GM
1108 (nth pos list))))
1109
1110(defun bs-next-config (name)
1111 "Return next configuration with respect to configuration with name NAME."
1112 (bs-next-config-aux name bs-configurations))
1113
1114(defun bs-select-next-configuration (&optional start-name)
1115 "Apply next configuration START-NAME and refresh buffer list.
1116If START-NAME is nil the current configuration `bs-current-configuration'
1117will be used."
1118 (interactive)
1119 (let ((config (bs-next-config (or start-name bs-current-configuration))))
1120 (bs-set-configuration (car config))
1121 (setq bs-default-configuration bs-current-configuration)
1122 (bs--redisplay t)
1123 (bs--set-window-height)
880ed9f1 1124 (bs-message-without-log "Selected configuration: %s" (car config))))
6448a6b3
GM
1125
1126(defun bs-show-in-buffer (list)
1127 "Display buffer list LIST in buffer *buffer-selection*.
1128Select buffer *buffer-selection* and display buffers according to current
1129configuration `bs-current-configuration'. Set window height, fontify buffer
1130and move point to current buffer."
1131 (setq bs-current-list list)
1132 (switch-to-buffer (get-buffer-create "*buffer-selection*"))
1133 (bs-mode)
1134 (let* ((inhibit-read-only t)
dc7904f5
DL
1135 (map-fun (lambda (entry)
1136 (length (buffer-name entry))))
1137 (max-length-of-names (apply 'max
1138 (cons 0 (mapcar map-fun list))))
1139 (name-entry-length (min bs-maximal-buffer-name-column
1140 (max bs-minimal-buffer-name-column
1141 max-length-of-names))))
6448a6b3
GM
1142 (erase-buffer)
1143 (setq bs--name-entry-length name-entry-length)
1144 (bs--show-header)
e749f576
JB
1145 (dolist (buffer list)
1146 (bs--insert-one-entry buffer)
1147 (insert "\n"))
d355a0b7 1148 (delete-char -1)
6448a6b3
GM
1149 (bs--set-window-height)
1150 (bs--goto-current-buffer)
1151 (font-lock-fontify-buffer)
310a26be
JB
1152 (bs-apply-sort-faces)
1153 (set-buffer-modified-p nil)))
6448a6b3
GM
1154
1155(defun bs-next-buffer (&optional buffer-list sorting-p)
1156 "Return next buffer and buffer list for buffer cycling in BUFFER-LIST.
1157Ignore sorting when SORTING-P is nil.
1158If BUFFER-LIST is nil the result of `bs-buffer-list' will be used as
1159buffer list. The result is a cons of normally the second element of
1160BUFFER-LIST and the buffer list used for buffer cycling."
1161 (let* ((bs--current-sort-function (if sorting-p
dc7904f5
DL
1162 bs--current-sort-function))
1163 (bs-buffer-list (or buffer-list (bs-buffer-list))))
6448a6b3 1164 (cons (or (car (cdr bs-buffer-list))
dc7904f5
DL
1165 (car bs-buffer-list)
1166 (current-buffer))
1167 bs-buffer-list)))
6448a6b3
GM
1168
1169(defun bs-previous-buffer (&optional buffer-list sorting-p)
1170 "Return previous buffer and buffer list for buffer cycling in BUFFER-LIST.
1171Ignore sorting when SORTING-P is nil.
1172If BUFFER-LIST is nil the result of `bs-buffer-list' will be used as
1173buffer list. The result is a cons of last element of BUFFER-LIST and the
1174buffer list used for buffer cycling."
1175 (let* ((bs--current-sort-function (if sorting-p
dc7904f5
DL
1176 bs--current-sort-function))
1177 (bs-buffer-list (or buffer-list (bs-buffer-list))))
6448a6b3 1178 (cons (or (car (last bs-buffer-list))
dc7904f5
DL
1179 (current-buffer))
1180 bs-buffer-list)))
6448a6b3
GM
1181
1182(defun bs-message-without-log (&rest args)
1183 "Like `message' but don't log it on the message log.
a1d5a11b 1184All arguments ARGS are transferred to function `message'."
6448a6b3
GM
1185 (let ((message-log-max nil))
1186 (apply 'message args)))
1187
1188(defvar bs--cycle-list nil
880ed9f1 1189 "Current buffer list used for cycling.")
6448a6b3
GM
1190
1191;;;###autoload
1192(defun bs-cycle-next ()
1193 "Select next buffer defined by buffer cycling.
1194The buffers taking part in buffer cycling are defined
1195by buffer configuration `bs-cycle-configuration-name'."
1196 (interactive)
1197 (let ((bs--buffer-coming-from (current-buffer))
dc7904f5
DL
1198 (bs-dont-show-regexp bs-dont-show-regexp)
1199 (bs-must-show-regexp bs-must-show-regexp)
1200 (bs-dont-show-function bs-dont-show-function)
1201 (bs-must-show-function bs-must-show-function)
038e04cb
GM
1202 (bs--show-all nil))
1203 (bs-set-configuration (or bs-cycle-configuration-name bs-default-configuration))
6448a6b3 1204 (let ((bs-buffer-sort-function nil)
dc7904f5 1205 (bs--current-sort-function nil))
6448a6b3 1206 (let* ((tupel (bs-next-buffer (if (or (eq last-command
dc7904f5
DL
1207 'bs-cycle-next)
1208 (eq last-command
1209 'bs-cycle-previous))
1210 bs--cycle-list)))
1211 (next (car tupel))
1212 (cycle-list (cdr tupel)))
c9d29fb8
SM
1213 ;; We don't want the frame iconified if the only window in the frame
1214 ;; happens to be dedicated.
1215 (bury-buffer (current-buffer))
8bdfa064 1216 (switch-to-buffer next nil t)
dc7904f5
DL
1217 (setq bs--cycle-list (append (cdr cycle-list)
1218 (list (car cycle-list))))
dc7904f5
DL
1219 (bs-message-without-log "Next buffers: %s"
1220 (or (cdr bs--cycle-list)
1221 "this buffer"))))))
6448a6b3 1222
6448a6b3
GM
1223;;;###autoload
1224(defun bs-cycle-previous ()
1225 "Select previous buffer defined by buffer cycling.
1226The buffers taking part in buffer cycling are defined
1227by buffer configuration `bs-cycle-configuration-name'."
1228 (interactive)
1229 (let ((bs--buffer-coming-from (current-buffer))
dc7904f5
DL
1230 (bs-dont-show-regexp bs-dont-show-regexp)
1231 (bs-must-show-regexp bs-must-show-regexp)
1232 (bs-dont-show-function bs-dont-show-function)
1233 (bs-must-show-function bs-must-show-function)
038e04cb
GM
1234 (bs--show-all nil))
1235 (bs-set-configuration (or bs-cycle-configuration-name bs-default-configuration))
6448a6b3 1236 (let ((bs-buffer-sort-function nil)
dc7904f5 1237 (bs--current-sort-function nil))
6448a6b3 1238 (let* ((tupel (bs-previous-buffer (if (or (eq last-command
dc7904f5
DL
1239 'bs-cycle-next)
1240 (eq last-command
1241 'bs-cycle-previous))
1242 bs--cycle-list)))
1243 (prev-buffer (car tupel))
1244 (cycle-list (cdr tupel)))
8bdfa064 1245 (switch-to-buffer prev-buffer nil t)
dc7904f5
DL
1246 (setq bs--cycle-list (append (last cycle-list)
1247 (reverse (cdr (reverse cycle-list)))))
dc7904f5
DL
1248 (bs-message-without-log "Previous buffers: %s"
1249 (or (reverse (cdr bs--cycle-list))
1250 "this buffer"))))))
6448a6b3
GM
1251
1252(defun bs--get-value (fun &optional args)
1253 "Apply function FUN with arguments ARGS.
1254Return result of evaluation. Will return FUN if FUN is a number
1255or a string."
1256 (cond ((numberp fun)
dc7904f5
DL
1257 fun)
1258 ((stringp fun)
1259 fun)
1260 (t (apply fun args))))
6448a6b3 1261
2fbc1934 1262(defun bs--get-marked-string (start-buffer _all-buffers)
6448a6b3
GM
1263 "Return a string which describes whether current buffer is marked.
1264START-BUFFER is the buffer where we started buffer selection.
462db93c 1265ALL-BUFFERS is the list of buffers appearing in Buffer Selection Menu.
6448a6b3
GM
1266The result string is one of `bs-string-current', `bs-string-current-marked',
1267`bs-string-marked', `bs-string-show-normally', `bs-string-show-never', or
1268`bs-string-show-always'."
880ed9f1 1269 (cond ;; current buffer is the buffer we started buffer selection.
dc7904f5
DL
1270 ((eq (current-buffer) start-buffer)
1271 (if (memq (current-buffer) bs--marked-buffers)
1272 bs-string-current-marked ; buffer is marked
1273 bs-string-current))
1274 ;; current buffer is marked
1275 ((memq (current-buffer) bs--marked-buffers)
1276 bs-string-marked)
1277 ;; current buffer hasn't a special mark.
1278 ((null bs-buffer-show-mark)
1279 bs-string-show-normally)
1280 ;; current buffer has a mark not to show itself.
1281 ((eq bs-buffer-show-mark 'never)
1282 bs-string-show-never)
1283 ;; otherwise current buffer is marked to show always.
1284 (t
1285 bs-string-show-always)))
6448a6b3 1286
2fbc1934 1287(defun bs--get-modified-string (_start-buffer _all-buffers)
6448a6b3
GM
1288 "Return a string which describes whether current buffer is modified.
1289START-BUFFER is the buffer where we started buffer selection.
462db93c 1290ALL-BUFFERS is the list of buffers appearing in Buffer Selection Menu."
6448a6b3
GM
1291 (if (buffer-modified-p) "*" " "))
1292
2fbc1934 1293(defun bs--get-readonly-string (_start-buffer _all-buffers)
6448a6b3
GM
1294 "Return a string which describes whether current buffer is read only.
1295START-BUFFER is the buffer where we started buffer selection.
462db93c 1296ALL-BUFFERS is the list of buffers appearing in Buffer Selection Menu."
6448a6b3
GM
1297 (if buffer-read-only "%" " "))
1298
2fbc1934 1299(defun bs--get-size-string (_start-buffer _all-buffers)
6448a6b3
GM
1300 "Return a string which describes the size of current buffer.
1301START-BUFFER is the buffer where we started buffer selection.
462db93c 1302ALL-BUFFERS is the list of buffers appearing in Buffer Selection Menu."
6448a6b3
GM
1303 (int-to-string (buffer-size)))
1304
2fbc1934 1305(defun bs--get-name (_start-buffer _all-buffers)
6448a6b3
GM
1306 "Return name of current buffer for Buffer Selection Menu.
1307The name of current buffer gets additional text properties
1308for mouse highlighting.
1309START-BUFFER is the buffer where we started buffer selection.
462db93c 1310ALL-BUFFERS is the list of buffers appearing in Buffer Selection Menu."
635ed242
JB
1311 (propertize (buffer-name)
1312 'help-echo "mouse-2: select this buffer, mouse-3: select in other frame"
1313 'mouse-face 'highlight))
6448a6b3 1314
2fbc1934 1315(defun bs--get-mode-name (start-buffer _all-buffers)
6448a6b3
GM
1316 "Return the name of mode of current buffer for Buffer Selection Menu.
1317START-BUFFER is the buffer where we started buffer selection.
462db93c 1318ALL-BUFFERS is the list of buffers appearing in Buffer Selection Menu."
c2699583 1319 (format-mode-line mode-name nil nil start-buffer))
6448a6b3 1320
2fbc1934 1321(defun bs--get-file-name (_start-buffer _all-buffers)
6448a6b3
GM
1322 "Return string for column 'File' in Buffer Selection Menu.
1323This is the variable `buffer-file-name' of current buffer.
027b1942 1324If not visiting a file, `list-buffers-directory' is returned instead.
6448a6b3 1325START-BUFFER is the buffer where we started buffer selection.
b75ccc22 1326ALL-BUFFERS is the list of buffers appearing in Buffer Selection Menu."
027b1942
JB
1327 (propertize (or buffer-file-name
1328 (bound-and-true-p list-buffers-directory)
1329 "")
310a26be
JB
1330 'mouse-face 'highlight
1331 'help-echo "mouse-2: select this buffer, mouse-3: select in other frame"))
6448a6b3 1332
6448a6b3
GM
1333(defun bs--insert-one-entry (buffer)
1334 "Generate one entry for buffer BUFFER in Buffer Selection Menu.
1335It goes over all columns described in `bs-attributes-list'
1336and evaluates corresponding string. Inserts string in current buffer;
1337normally *buffer-selection*."
1338 (let ((string "")
dc7904f5 1339 (to-much 0)
6448a6b3 1340 (apply-args (append (list bs--buffer-coming-from bs-current-list))))
70ef8857 1341 (with-current-buffer buffer
e749f576
JB
1342 (dolist (column bs-attributes-list)
1343 (let* ((min (bs--get-value (nth 1 column)))
1344 (new-string (bs--format-aux (bs--get-value (nth 4 column) ; fun
1345 apply-args)
1346 (nth 3 column) ; align
1347 (- min to-much)))
1348 (len (length new-string)))
dc7904f5 1349 (setq string (concat string new-string))
e749f576
JB
1350 (when (> len min)
1351 (setq to-much (- len min))))))
1352 (insert string)))
6448a6b3
GM
1353
1354(defun bs--format-aux (string align len)
462db93c 1355 "Pad STRING to length LEN with alignment ALIGN.
6448a6b3 1356ALIGN is one of the symbols `left', `middle', or `right'."
635ed242
JB
1357 (let* ((width (length string))
1358 (len (max len width)))
1359 (format (format "%%%s%ds" (if (eq align 'right) "" "-") len)
1360 (if (eq align 'middle)
1361 (concat (make-string (/ (- len width) 2) ?\s) string)
1362 string))))
6448a6b3
GM
1363
1364(defun bs--show-header ()
1365 "Insert header for Buffer Selection Menu in current buffer."
e749f576
JB
1366 (insert (bs--create-header-line #'identity)
1367 "\n"
1368 (bs--create-header-line (lambda (title)
1369 (make-string (length title) ?-)))
1370 "\n"))
6448a6b3
GM
1371
1372(defun bs--get-name-length ()
1373 "Return value of `bs--name-entry-length'."
1374 bs--name-entry-length)
1375
e749f576
JB
1376(defun bs--create-header-line (col)
1377 "Generate a line for the header.
1378COL is called for each column in `bs-attributes-list' as a
1379function of one argument, the string heading for the column."
1380 (mapconcat (lambda (column)
1381 (bs--format-aux (funcall col (bs--get-value (car column)))
1382 (nth 3 column) ; align
1383 (bs--get-value (nth 1 column))))
1384 bs-attributes-list
1385 ""))
6448a6b3
GM
1386
1387(defun bs--show-with-configuration (name &optional arg)
880ed9f1 1388 "Display buffer list of configuration with name NAME.
6448a6b3
GM
1389Set configuration NAME and determine window for Buffer Selection Menu.
1390Unless current buffer is buffer *buffer-selection* we have to save
1391the buffer we started Buffer Selection Menu and the current window
1392configuration to restore buffer and window configuration after a
1393selection. If there is already a window displaying *buffer-selection*
1394select this window for Buffer Selection Menu. Otherwise open a new
1395window.
1396The optional argument ARG is the prefix argument when calling a function
1397for buffer selection."
1398 (bs-set-configuration name)
1399 (let ((bs--show-all (or bs--show-all arg)))
dc7904f5 1400 (unless (string= "*buffer-selection*" (buffer-name))
6448a6b3
GM
1401 ;; Only when not in buffer *buffer-selection*
1402 ;; we have to set the buffer we started the command
ce3ba12c 1403 (setq bs--buffer-coming-from (current-buffer)))
dc7904f5 1404 (let ((liste (bs-buffer-list))
edf6fbc5 1405 (active-window (get-window-with-predicate
e749f576
JB
1406 (lambda (w)
1407 (string= (buffer-name (window-buffer w))
1408 "*buffer-selection*"))
1409 nil (selected-frame))))
dc7904f5
DL
1410 (if active-window
1411 (select-window active-window)
e749f576
JB
1412 (bs--restore-window-config)
1413 (setq bs--window-config-coming-from (current-window-configuration))
ce3ba12c 1414 (when (> (window-height (selected-window)) 7)
ea8fb88d
JB
1415 ;; Errors would mess with the window configuration (bug#10882).
1416 (ignore-errors (select-window (split-window-below)))))
dc7904f5
DL
1417 (bs-show-in-buffer liste)
1418 (bs-message-without-log "%s" (bs--current-config-message)))))
6448a6b3 1419
2fbc1934
JB
1420(defun bs--configuration-name-for-prefix-arg (prefix)
1421 "Convert prefix argument PREFIX to a name of a buffer configuration.
1422If PREFIX is nil return `bs-default-configuration'.
1423If PREFIX is an integer return PREFIX element of `bs-configurations'.
6448a6b3 1424Otherwise return `bs-alternative-configuration'."
880ed9f1 1425 (cond ;; usually activation
2fbc1934 1426 ((null prefix)
dc7904f5
DL
1427 bs-default-configuration)
1428 ;; call with integer as prefix argument
2fbc1934
JB
1429 ((integerp prefix)
1430 (if (and (< 0 prefix) (<= prefix (length bs-configurations)))
1431 (car (nth (1- prefix) bs-configurations))
dc7904f5
DL
1432 bs-default-configuration))
1433 ;; call by prefix argument C-u
1434 (t bs-alternative-configuration)))
6448a6b3
GM
1435
1436;; ----------------------------------------------------------------------
1437;; Main function bs-customize and bs-show
1438;; ----------------------------------------------------------------------
1439
1440;;;###autoload
1441(defun bs-customize ()
1442 "Customization of group bs for Buffer Selection Menu."
1443 (interactive)
1444 (customize-group "bs"))
1445
1446;;;###autoload
1447(defun bs-show (arg)
dc7904f5 1448 "Make a menu of buffers so you can manipulate buffers or the buffer list.
6448a6b3
GM
1449\\<bs-mode-map>
1450There are many key commands similar to `Buffer-menu-mode' for
b75ccc22 1451manipulating the buffer list and the buffers themselves.
6448a6b3
GM
1452User can move with [up] or [down], select a buffer
1453by \\[bs-select] or [SPC]\n
1454Type \\[bs-kill] to leave Buffer Selection Menu without a selection.
1455Type \\[bs-help] after invocation to get help on commands available.
1456With prefix argument ARG show a different buffer list. Function
1457`bs--configuration-name-for-prefix-arg' determine accordingly
1458name of buffer configuration."
1459 (interactive "P")
1460 (setq bs--marked-buffers nil)
1461 (bs--show-with-configuration (bs--configuration-name-for-prefix-arg arg)))
1462
c4384ef1
JB
1463;; ----------------------------------------------------------------------
1464;; Cleanup
1465;; ----------------------------------------------------------------------
1466
1467(defun bs-unload-function ()
1468 "Unload the Buffer Selection library."
1469 (let ((bs-buf (get-buffer "*buffer-selection*")))
1470 (when bs-buf
1471 (with-current-buffer bs-buf
1472 (when (eq major-mode 'bs-mode)
1473 (bs-kill)
1474 (kill-buffer bs-buf)))))
1475 ;; continue standard unloading
1476 nil)
1477
d8754ce5 1478;; Now provide feature bs
6448a6b3
GM
1479(provide 'bs)
1480
1481;;; bs.el ends here