1 ;;; winring.el --- Window configuration rings
3 ;; Copyright (C) 1998, 1999 Free Software Foundation, Inc.
5 ;; Author: 1997-2004 Barry A. Warsaw
6 ;; Contact: gnu@wooz.org (Barry A. Warsaw)
7 ;; Homepage: http://barry.warsaw.us/elisp
9 ;; Keywords: frames tools
11 (defconst winring-version "$Revision: 1.2 $"
12 "winring version number.")
14 ;; This file is part of GNU Emacs.
16 ;; GNU Emacs is free software; you can redistribute it and/or modify
17 ;; it under the terms of the GNU General Public License as published by
18 ;; the Free Software Foundation; either version 2, or (at your option)
21 ;; GNU Emacs is distributed in the hope that it will be useful,
22 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ;; GNU General Public License for more details.
26 ;; You should have received a copy of the GNU General Public License
27 ;; along with GNU Emacs; see the file COPYING. If not, write to the
28 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
29 ;; Boston, MA 02111-1307, USA.
33 ;; This package provides lightweight support for circular rings of
34 ;; window configurations. A window configuration is the layout of
35 ;; windows and associated buffers within a frame. There is always at
36 ;; least one configuration on the ring, the current configuration.
37 ;; You can create new configurations and cycle through the layouts in
38 ;; either direction. You can also delete configurations from the ring
39 ;; (except the last one of course!). Window configurations are named,
40 ;; and you can jump to and delete named configurations. Display of
41 ;; the current window configuration name in the mode line is only
42 ;; supported as of Emacs 20.3 and XEmacs 21.0.
44 ;; Window configuration rings are frame specific. That is, each frame
45 ;; has its own ring which can be cycled through independently of other
46 ;; frames. This is the way I like it.
48 ;; You are always looking at the current window configuration for each
49 ;; frame, which consists of the windows in the frame, the buffers in
50 ;; those windows, and point in the current buffer. As you run
51 ;; commands such as "C-x 4 b", "C-x 2", and "C-x 0" you are modifying
52 ;; the current window configuration. When you jump to a new
53 ;; configuration, the layout that existed before the jump is captured,
54 ;; and the ring is rotated to the selected configuration. Window
55 ;; configurations are captured with `current-window-configuration',
56 ;; however winring also saves point for the current buffer.
58 ;; To use, make sure this file is on your `load-path' and put the
59 ;; following in your .emacs file:
62 ;; (winring-initialize)
64 ;; Note that by default, this binds the winring keymap to the C-x 7
65 ;; prefix, but you can change this by setting the value of
66 ;; `winring-keymap-prefix', before you call `winring-initialize'.
67 ;; Note that this is a change from previous versions of winring; the
68 ;; prefix used to be M-o but was changed on the suggestion of RMS.
70 ;; The following commands are defined:
72 ;; C-x 7 n -- Create a new window configuration. The new
73 ;; configuration will contain a single buffer, the one
74 ;; named in the variable `winring-new-config-buffer-name'
76 ;; With C-u, winring prompts for the name of the new
77 ;; configuration. If you don't use C-u the function in
78 ;; `winring-name-generator' will be called to get the
79 ;; new configuration's name.
81 ;; C-x 7 2 -- Create a duplicate of the current window
82 ;; configuration. C-u has the same semantics as with
85 ;; C-x 7 j -- Jump to a named configuration (prompts for the name).
87 ;; C-x 7 0 -- Kill the current window configuration and rotate to the
88 ;; previous layout on the ring. You cannot delete the
89 ;; last configuration in the ring. With C-u, prompts
90 ;; for the name of the configuration to kill.
92 ;; C-x 7 o -- Go to the next configuration on the ring.
94 ;; C-x 7 p -- Go to the previous configuration on the ring.
96 ;; Note that the sequence `C-x 7 o C-x 7 p' is a no-op;
97 ;; it leaves you in the same configuration you were in
98 ;; before the sequence.
100 ;; C-x 7 r -- Rename the current window configuration.
102 ;; C-x 7 b -- Submit a bug report on winring.
104 ;; C-x 7 v -- Echo the winring version.
106 ;; As mentioned, window configuration names can be displayed in the
107 ;; modeline, but this feature only works with Emacs 20.3 and XEmacs
108 ;; 21.0. A patch for XEmacs 20.4 to support this feature is available
109 ;; at the URL below. Note that the default value of
110 ;; `winring-show-names' is currently nil by default because if your
111 ;; X/Emacs doesn't have the necessary support, ugly things can happen
112 ;; (no you won't crash your X/Emacs -- it just won't do what you
115 ;; If your X/Emacs has the necessary support, you can turn on display
116 ;; of window configuration names by setting `winring-show-names' to
117 ;; t. If you don't like the position in the modeline where winring
118 ;; names are shown, you can change this by passing in your own
119 ;; modeline hacker function to `winring-initialize'.
123 ;; A long long time ago there was a package called `wicos' written by
124 ;; Heikki Suopanki, which was based on yet another earlier package
125 ;; called `screens' also written by Suopanki. This in turn was based
126 ;; on the Unix tty session manager `screen' (unrelated to Emacs) by
127 ;; Oliver Laumann, Juergen Weigert, and Michael Schroeder.
129 ;; Wicos essentially provided fancy handling for window
130 ;; configurations. I liked the basic ideas, but wicos broke with
131 ;; later versions of Emacs and XEmacs. I re-implemented just the
132 ;; functionality I wanted, simplifying things in the process, and
133 ;; porting the code to run with XEmacs 19 and 20, and Emacs 20 (I
134 ;; don't know if winring works in Emacs 19.34).
136 ;; Wicos used the M-o prefix which I've recently changed to C-x 7 as
137 ;; the default, by suggestion of RMS. Wicos also had some support for
138 ;; multiple frames, and saving configurations on all visible frames,
139 ;; but it didn't work too well, and I like frame independent rings
142 ;; I know of a few other related packages:
144 ;; - `escreen' by Noah Friedman. A much more ambitious package
145 ;; that does Emacs window session management. Very cool, but I
146 ;; wanted something more lightweight.
148 ;; - `wconfig' by Bob Weiner as part of Hyperbole. I think wconfig
149 ;; is similar in spirit to winring; it seems to have also have
150 ;; named window configurations, but not frame-specific window
153 ;; - `winner' by Ivar Rummelhoff. This package comes with Emacs
154 ;; 20, and appears to differ from winring by providing undo/redo
155 ;; semantics to window configuration changes. winner is a minor
156 ;; mode and does seem to support frame-specific window rings.
158 ;; - `tapestry' by Kyle Jones and distributed with the VM package.
159 ;; A much more featured package, but there is a lot of overlap
160 ;; and it seems more designed for programmatic rather than
163 ;; - XEmacs has some built-in support for window configuration
164 ;; stacks, but I wanted to use a ring structure for managing
167 ;; Please feel free to email me if my rendition of history, or my
168 ;; explanation of the related packages, is inaccurate.
175 (defgroup winring nil
176 "Window configuration rings"
180 (defcustom winring-ring-size 7
181 "*Size of the window configuration ring."
185 (defcustom winring-prompt-on-create 'usually
186 "*When true, prompt for new configuration name on creation.
187 If not t and not nil, prompt for configuration name on creation,
188 except when creating the initial configuration on a new frame."
190 (const :tag "Never prompt for configuration name" nil)
191 (const :tag "Always prompt for configuration name" t)
192 (const :tag "Prompt for all but initial configuration name"
197 (defcustom winring-new-config-buffer-name "*scratch*"
198 "*Name of the buffer to switch to when a new configuration is created."
202 (defcustom winring-show-names nil
203 "*If non-nil, window configuration names are shown in the modeline.
204 If nil, the name is echoed in the minibuffer when switching window
209 (defcustom winring-name-generator 'winring-next-name
210 "*Function that generates new automatic window configuration names.
211 When a new window configuration is created with `winring-new-configuration',
212 and the user did not specify an explicit name, this function is called with
213 no arguments to get the new name. It must return a string."
217 ;; Not yet customized
218 (defvar winring-keymap-prefix "\C-x7"
219 "*Prefix key that the `winring-map' is placed on in the global keymap.
220 If you change this, you must do it before calling `winring-initialize'.")
224 (defvar winring-map nil
225 "Keymap used for winring, window configuration rings.")
228 (setq winring-map (make-sparse-keymap))
229 (define-key winring-map "b" 'winring-submit-bug-report)
230 (define-key winring-map "n" 'winring-new-configuration)
231 (define-key winring-map "2" 'winring-duplicate-configuration)
232 (define-key winring-map "j" 'winring-jump-to-configuration)
233 (define-key winring-map "0" 'winring-delete-configuration)
234 (define-key winring-map "o" 'winring-next-configuration)
235 (define-key winring-map "p" 'winring-prev-configuration)
236 (define-key winring-map "r" 'winring-rename-configuration)
237 (define-key winring-map "v" 'winring-version)
243 (defvar winring-name nil
244 "The name of the currently displayed window configuration.")
246 (defvar winring-name-index 1
247 "Index used as a sequence number for new unnamed window configurations.")
249 (defvar winring-name-history nil
250 "History variable for window configuration name prompts.")
252 (defun winring-next-name ()
253 (let ((name (format "%03d" winring-name-index)))
254 (setq winring-name-index (1+ winring-name-index))
260 (defun winring-set-frame-ring (frame ring)
263 ((fboundp 'set-frame-property)
264 (set-frame-property frame 'winring-ring ring))
266 ((fboundp 'modify-frame-parameters)
267 (modify-frame-parameters frame (list (cons 'winring-ring ring))))
269 (t (error "This version of Emacs is not supported by winring"))))
271 (defun winring-get-frame-ring (frame)
274 ((fboundp 'frame-property)
275 (frame-property frame 'winring-ring))
277 ((fboundp 'frame-parameter)
278 (frame-parameter frame 'winring-ring))
280 ((fboundp 'frame-parameters)
281 (cdr (assq 'winring-ring (frame-parameters frame))))
283 (t (error "This version of Emacs is not supported by winring"))))
285 (defun winring-create-frame-hook (frame)
286 ;; generate the name, but specify the newly created frame
287 (winring-set-name (and (eq winring-prompt-on-create t)
288 (read-string "Initial window configuration name? "
289 nil 'winring-name-history))
294 (defun winring-set-name (&optional name frame)
295 "Set the window configuration name.
296 Optional NAME is the name to use; if not given, then
297 `winring-name-generator' is `funcall'd with no arguments to get the
298 generated name. Optional FRAME is the frame to set the name for; if
299 not given then the currently selected frame is used."
300 (let ((name (or name (funcall winring-name-generator)))
301 (frame (or frame (selected-frame))))
302 (if (fboundp 'add-spec-to-specifier)
303 ;; The XEmacs way. Only supported in hacked 20.4 or 21.0
304 (add-spec-to-specifier winring-name name frame)
305 ;; the Emacs way. Only supported in Emacs 20.3
306 (modify-frame-parameters frame (list (cons 'winring-name name)))
308 (if (not winring-show-names)
309 (message "Switching to window configuration: %s" name)))
311 (defun winring-get-ring ()
312 (let* ((frame (selected-frame))
313 (ring (winring-get-frame-ring frame)))
315 (setq ring (make-ring winring-ring-size))
316 (winring-set-frame-ring frame ring))
319 (defsubst winring-name-of (config)
322 (defsubst winring-conf-of (config)
325 (defsubst winring-point-of (config)
328 (defsubst winring-name-of-current ()
329 (if (fboundp 'specifier-instance)
330 ;; In XEmacs, this variable holds a specifier which
331 ;; must be instanced to get the current
332 ;; configuration name.
333 (specifier-instance winring-name)
334 ;; In Emacs, just use the variable's string value
335 ;; directly, since the `displayed' value is kept as a
339 (defun winring-save-current-configuration (&optional at-front)
340 (let* ((ring (winring-get-ring))
341 (name (winring-name-of-current))
343 (conf (list name (current-window-configuration) here)))
345 (ring-insert-at-beginning ring conf)
346 (ring-insert ring conf))))
348 (defun winring-restore-configuration (item)
349 (let ((conf (winring-conf-of item))
350 (name (winring-name-of item))
351 (here (winring-point-of item)))
352 (set-window-configuration conf)
353 ;; current-window-configuration does not save point in current
354 ;; window. That sucks!
356 (winring-set-name name))
357 (force-mode-line-update))
359 (defun winring-complete-name ()
360 (let* ((ring (winring-get-ring))
361 (n (1- (ring-length ring)))
362 (current (winring-name-of-current))
363 (table (list (cons current -1)))
365 ;; populate the completion table
367 (setq table (cons (cons (winring-name-of (ring-ref ring n)) n) table)
369 (setq name (completing-read
370 (format "Window configuration name (%s): " current)
371 table nil 'must nil 'winring-name-history))
372 (if (string-equal name "")
374 (cdr (assoc name table))))
376 (defun winring-read-name (prompt)
377 (let* ((ring (winring-get-ring))
378 (n (1- (ring-length ring)))
379 (table (list (winring-name-of-current)))
381 ;; get the list of all the names in the ring
383 (setq table (cons (winring-name-of (ring-ref ring n)) table)
385 (setq name (read-string prompt nil 'winring-name-history))
386 (if (member name table)
387 (error "Window configuration name already in use: %s" name))
394 (defun winring-new-configuration (&optional arg)
395 "Save the current window configuration and create an empty new one.
396 The buffer shown in the new empty configuration is defined by
397 `winring-new-config-buffer-name'.
399 With \\[universal-argument] prompt for the new configuration's name.
400 Otherwise, the function in `winring-name-generator' will be called to
401 get the new configuration's name."
403 (let ((name (and (or arg winring-prompt-on-create)
404 (winring-read-name "New window configuration name? "))))
405 ;; Empty string is not allowed
406 (if (string-equal name "")
407 (setq name (funcall winring-name-generator)))
408 (winring-save-current-configuration)
409 (delete-other-windows)
410 (switch-to-buffer winring-new-config-buffer-name)
411 (winring-set-name name)))
414 (defun winring-duplicate-configuration (&optional arg)
415 "Push the current window configuration on the ring, and duplicate it.
417 With \\[universal-argument] prompt for the new configuration's name.
418 Otherwise, the function in `winring-name-generator' will be called to
419 get the new configuration's name."
421 (let ((name (and (or arg winring-prompt-on-create)
422 (winring-read-name "New window configuration name? "))))
423 ;; Empty string is not allowed
424 (if (string-equal name "")
425 (setq name (funcall winring-name-generator)))
426 (winring-save-current-configuration)
427 (winring-set-name name)))
430 (defun winring-next-configuration ()
431 "Switch to the next window configuration for this frame."
433 (let ((next (ring-remove (winring-get-ring))))
434 (winring-save-current-configuration)
435 (winring-restore-configuration next)))
438 (defun winring-prev-configuration ()
439 "Switch to the previous window configuration for this frame."
441 (let ((prev (ring-remove (winring-get-ring) 0)))
442 (winring-save-current-configuration 'at-front)
443 (winring-restore-configuration prev)))
446 (defun winring-jump-to-configuration ()
447 "Go to the named window configuration."
449 (let* ((ring (winring-get-ring))
450 (index (winring-complete-name))
452 ;; if the current configuration was chosen, winring-complete-name
455 (setq item (ring-remove ring index))
456 (winring-save-current-configuration)
457 (winring-restore-configuration item))
461 (defun winring-delete-configuration (&optional arg)
462 "Delete the current configuration and switch to the next one.
463 With \\[universal-argument] prompt for named configuration to delete."
465 (let ((ring (winring-get-ring))
468 (> 0 (setq index (winring-complete-name))))
469 ;; remove the current one, so install the next one
470 (winring-restore-configuration (ring-remove ring))
471 ;; otherwise, remove the named one but don't change the current config
472 (ring-remove ring index)
476 (defun winring-rename-configuration ()
477 "Rename the current configuration to NAME."
479 (winring-set-name (winring-read-name "New window configuration name? ")))
483 (defconst winring-help-address "bwarsaw@python.org"
484 "Address accepting bug report submissions.")
486 (defun winring-version ()
487 "Echo the current version of winring in the minibuffer."
489 (message "Using winring version %s" winring-version)
490 ;;(setq zmacs-region-stays t)
493 (defun winring-submit-bug-report (comment-p)
494 "Submit via mail a bug report on winring.
495 With \\[universal-argument] just send any type of comment."
498 "Is this a bug report? (hit `n' to send other comments) "))))
499 (let ((reporter-prompt-for-summary-p (if comment-p
500 "(Very) brief summary: "
503 (reporter-submit-bug-report
504 winring-help-address ;address
505 (concat "winring " winring-version) ;pkgname
509 winring-new-config-buffer-name
511 winring-name-generator
512 winring-keymap-prefix))
515 "Dear Barry,") ;salutation
519 "Please replace this text with a description of your problem.\n\
520 The more accurately and succinctly you can describe the\n\
521 problem you are encountering, the more likely I can fix it\n\
522 in a timely way.\n\n")
523 (exchange-point-and-mark)
524 ;;(setq zmacs-region-stays t)
529 ;; Initialization. This is completely different b/w Emacs and XEmacs.
530 ;; The Emacs 20.3 way is to create a frame-local variable (this is a
531 ;; new feature with Emacs 20.3), and save the config name as a frame
534 ;; In XEmacs 21.0 (a.k.a. 20.5), you create a generic specifier, and
535 ;; save the config name as an instantiator over the current frame
538 ;; Be sure to do this only once
539 (defvar winring-initialized nil)
541 (defun winring-initialize (&optional hack-modeline-function)
542 (unless winring-initialized
544 ;; Create the variable that holds the window configuration name
547 ;; The Emacs 20.3 way: frame-local variables
548 ((fboundp 'make-variable-frame-local)
549 (make-variable-frame-local 'winring-name))
550 ;; The XEmacs 21 way: specifiers
551 ((fboundp 'make-specifier)
552 (setq winring-name (make-specifier 'generic)))
553 ;; Not supported in older X/Emacsen
556 ;; Glom the configuration name into the mode-line. I've
557 ;; experimented with a couple of different locations, including
558 ;; for Emacs 20.3 mode-line-frame-identification, and for XEmacs,
559 ;; just splicing it before the modeline-buffer-identification.
560 ;; Sticking it on the very left side of the modeline, even before
561 ;; mode-line-modified seems like the most useful and
562 ;; cross-compatible place.
564 ;; Note that you can override the default hacking of the modeline
565 ;; by passing in your own `hack-modeline-function'.
567 (if hack-modeline-function
568 (funcall hack-modeline-function)
569 ;; Else, default insertion hackery
570 (let ((format (list 'winring-show-names
571 '("<" winring-name "> ")))
572 (splice (cdr mode-line-format)))
573 (setcar splice (list format (car splice)))))
575 ;; We need to add a hook so that all newly created frames get
576 ;; initialized properly. Again, different for Emacs and XEmacs.
578 (if (boundp 'create-frame-hook)
580 (add-hook 'create-frame-hook 'winring-create-frame-hook)
582 (add-hook 'after-make-frame-functions 'winring-create-frame-hook))
584 ;; Now set the initial configuration name on the initial frame...
585 (winring-create-frame-hook (selected-frame))
587 (global-set-key winring-keymap-prefix winring-map)
588 ;; ...and the init fence
589 (setq winring-initialized t)))
594 ;;; winring.el ends here