;;; winring.el --- Window configuration rings
;; Copyright (C) 1998 Free Software Foundation, Inc.
;; Author: 1997-1998 Barry A. Warsaw
;; Maintainer: bwarsaw@python.org
;; Created: March 1997
;; Keywords: frames tools
(defconst winring-version "3.5"
"winring version number.")
;; This file is part of GNU Emacs.
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;;
;; This package provides lightweight support for circular rings of
;; window configurations. A window configuration is the layout of
;; windows and associated buffers within a frame. There is always at
;; least one configuration on the ring, the current configuration.
;; You can create new configurations and cycle through the layouts in
;; either direction. You can also delete configurations from the ring
;; (except the last one of course!). Window configurations are named,
;; and you can jump to and delete named configurations. Display of
;; the current window configuration name in the mode line is only
;; supported as of Emacs 20.3 and XEmacs 21.0.
;;
;; Window configuration rings are frame specific. That is, each frame
;; has its own ring which can be cycled through independently of other
;; frames. This is the way I like it.
;;
;; You are always looking at the current window configuration for each
;; frame, which consists of the windows in the frame, the buffers in
;; those windows, and point in the current buffer. As you run
;; commands such as "C-x 4 b", "C-x 2", and "C-x 0" you are modifying
;; the current window configuration. When you jump to a new
;; configuration, the layout that existed before the jump is captured,
;; and the ring is rotated to the selected configuration. Window
;; configurations are captured with `current-window-configuration',
;; however winring also saves point for the current buffer.
;; To use, make sure this file is on your `load-path' and put the
;; following in your .emacs file:
;;
;; (require 'winring)
;; (winring-initialize)
;;
;; Note that by default, this binds the winring keymap to the C-x 7
;; prefix, but you can change this by setting the value of
;; `winring-keymap-prefix', before you call `winring-initialize'.
;; Note that this is a change from previous versions of winring; the
;; prefix used to be M-o but was changed on the suggestion of RMS.
;; The following commands are defined:
;;
;; C-x 7 n -- Create a new window configuration. The new
;; configuration will contain a single buffer, the one
;; named in the variable `winring-new-config-buffer-name'
;;
;; With C-u, winring prompts for the name of the new
;; configuration. If you don't use C-u the function in
;; `winring-name-generator' will be called to get the
;; new configuration's name.
;;
;; C-x 7 2 -- Create a duplicate of the current window
;; configuration. C-u has the same semantics as with
;; "C-x 7 c".
;;
;; C-x 7 j -- Jump to a named configuration (prompts for the name).
;;
;; C-x 7 0 -- Kill the current window configuration and rotate to the
;; previous layout on the ring. You cannot delete the
;; last configuration in the ring. With C-u, prompts
;; for the name of the configuration to kill.
;;
;; C-x 7 o -- Go to the next configuration on the ring.
;;
;; C-x 7 p -- Go to the previous configuration on the ring.
;;
;; Note that the sequence `C-x 7 o C-x 7 p' is a no-op;
;; it leaves you in the same configuration you were in
;; before the sequence.
;;
;; C-x 7 r -- Rename the current window configuration.
;;
;; C-x 7 b -- Submit a bug report on winring.
;;
;; C-x 7 v -- Echo the winring version.
;; As mentioned, window configuration names can be displayed in the
;; modeline, but this feature only works with Emacs 20.3 and XEmacs
;; 21.0. A patch for XEmacs 20.4 to support this feature is available
;; at the URL below. Note that the default value of
;; `winring-show-names' is currently nil by default because if your
;; X/Emacs doesn't have the necessary support, ugly things can happen
;; (no you won't crash your X/Emacs -- it just won't do what you
;; want).
;;
;; If your X/Emacs has the necessary support, you can turn on display
;; of window configuration names by setting `winring-show-names' to
;; t. If you don't like the position in the modeline where winring
;; names are shown, you can change this by passing in your own
;; modeline hacker function to `winring-initialize'.
;;; Winring on the Web:
;;
;; The winring Web page (including the aforementioned XEmacs 20.4
;; patch) is
;;
;; http://www.python.org/emacs/winring/
;;; History:
;;
;; A long long time ago there was a package called `wicos' written by
;; Heikki Suopanki, which was based on yet another earlier package
;; called `screens' also written by Suopanki. This in turn was based
;; on the Unix tty session manager `screen' (unrelated to Emacs) by
;; Oliver Laumann, Juergen Weigert, and Michael Schroeder.
;;
;; Wicos essentially provided fancy handling for window
;; configurations. I liked the basic ideas, but wicos broke with
;; later versions of Emacs and XEmacs. I re-implemented just the
;; functionality I wanted, simplifying things in the process, and
;; porting the code to run with XEmacs 19 and 20, and Emacs 20 (I
;; don't know if winring works in Emacs 19.34).
;;
;; Wicos used the M-o prefix which I've recently changed to C-x 7 as
;; the default, by suggestion of RMS. Wicos also had some support for
;; multiple frames, and saving configurations on all visible frames,
;; but it didn't work too well, and I like frame independent rings
;; better.
;;
;; I know of a few other related packages:
;;
;; - `escreen' by Noah Friedman. A much more ambitious package
;; that does Emacs window session management. Very cool, but I
;; wanted something more lightweight.
;;
;; - `wconfig' by Bob Weiner as part of Hyperbole. I think wconfig
;; is similar in spirit to winring; it seems to have also have
;; named window configurations, but not frame-specific window
;; rings.
;;
;; - `winner' by Ivar Rummelhoff. This package comes with Emacs
;; 20, and appears to differ from winring by providing undo/redo
;; semantics to window configuration changes. winner is a minor
;; mode and does seem to support frame-specific window rings.
;;
;; - `window-xemacs' by the XEmacs Development Team. It appears
;; that this package, which is specific to XEmacs (and perhaps
;; just XEmacs 20) implements stacks of window configurations
;; which are frame independent.
;; Please feel free to email me if my rendition of history, or my
;; explanation of the related packages, is inaccurate.
;;; Code:
(require 'ring)
(defgroup winring nil
"Window configuration rings"
:prefix "winring-"
:group 'frames)
(defcustom winring-ring-size 7
"*Size of the window configuration ring."
:type 'integer
:group 'winring)
(defcustom winring-prompt-on-create 'usually
"*When true, prompt for new configuration name on creation.
If not t and not nil, prompt for configuration name on creation,
except when creating the initial configuration on a new frame."
:type '(radio
(const :tag "Never prompt for configuration name" nil)
(const :tag "Always prompt for configuration name" t)
(const :tag "Prompt for all but initial configuration name"
usually)
)
:group 'winring)
(defcustom winring-new-config-buffer-name "*scratch*"
"*Name of the buffer to switch to when a new configuration is created."
:type 'string
:group 'winring)
(defcustom winring-show-names nil
"*If non-nil, window configuration names are shown in the modeline.
If nil, the name is echoed in the minibuffer when switching window
configurations."
:type 'boolean
:group 'winring)
(defcustom winring-name-generator 'winring-next-name
"*Function that generates new automatic window configuration names.
When a new window configuration is created with `winring-new-configuration',
and the user did not specify an explicit name, this function is called with
no arguments to get the new name. It must return a string."
:type 'function
:group 'winring)
;; Not yet customized
(defvar winring-keymap-prefix "\C-x7"
"*Prefix key that the `winring-map' is placed on in the global keymap.
If you change this, you must do it before calling `winring-initialize'.")
;; Set up keymap
(defvar winring-map nil
"Keymap used for winring, window configuration rings.")
(if winring-map
nil
(setq winring-map (make-sparse-keymap))
(define-key winring-map "b" 'winring-submit-bug-report)
(define-key winring-map "n" 'winring-new-configuration)
(define-key winring-map "2" 'winring-duplicate-configuration)
(define-key winring-map "j" 'winring-jump-to-configuration)
(define-key winring-map "0" 'winring-delete-configuration)
(define-key winring-map "o" 'winring-next-configuration)
(define-key winring-map "p" 'winring-prev-configuration)
(define-key winring-map "r" 'winring-rename-configuration)
(define-key winring-map "v" 'winring-version)
)
;; Winring names
(defvar winring-name nil
"The name of the currently displayed window configuration.")
(defvar winring-name-index 1
"Index used as a sequence number for new unnamed window configurations.")
(defvar winring-name-history nil
"History variable for window configuration name prompts.")
(defun winring-next-name ()
(let ((name (format "%03d" winring-name-index)))
(setq winring-name-index (1+ winring-name-index))
name))
;; Compatibility
(defun winring-set-frame-ring (frame ring)
(cond
;; XEmacs
((fboundp 'set-frame-property)
(set-frame-property frame 'winring-ring ring))
;; Emacs
((fboundp 'modify-frame-parameters)
(modify-frame-parameters frame (list (cons 'winring-ring ring))))
;; Not supported
(t (error "This version of Emacs is not supported by winring"))))
(defun winring-get-frame-ring (frame)
(cond
;; XEmacs
((fboundp 'frame-property)
(frame-property frame 'winring-ring))
;; Emacs 20
((fboundp 'frame-parameter)
(frame-parameter frame 'winring-ring))
;; Emacs 19.34
((fboundp 'frame-parameters)
(cdr (assq 'winring-ring (frame-parameters frame))))
;; Unsupported
(t (error "This version of Emacs is not supported by winring"))))
(defun winring-create-frame-hook (frame)
;; generate the name, but specify the newly created frame
(winring-set-name (and (eq winring-prompt-on-create t)
(read-string "Initial window configuration name? "
nil 'winring-name-history))
frame))
;; Utilities
(defun winring-set-name (&optional name frame)
"Set the window configuration name.
Optional NAME is the name to use; if not given, then
`winring-name-generator' is `funcall'd with no arguments to get the
generated name. Optional FRAME is the frame to set the name for; if
not given then the currently selected frame is used."
(let ((name (or name (funcall winring-name-generator)))
(frame (or frame (selected-frame))))
(if (fboundp 'add-spec-to-specifier)
;; The XEmacs way. Only supported in hacked 20.4 or 21.0
(add-spec-to-specifier winring-name name frame)
;; the Emacs way. Only supported in Emacs 20.3
(setq winring-name name)
(modify-frame-parameters frame (list (cons 'winring-name name)))
))
(if (not winring-show-names)
(message "Switching to window configuration: %s" name)))
(defun winring-get-ring ()
(let* ((frame (selected-frame))
(ring (winring-get-frame-ring frame)))
(when (not ring)
(setq ring (make-ring winring-ring-size))
(winring-set-frame-ring frame ring))
ring))
(defsubst winring-name-of (config)
(car config))
(defsubst winring-conf-of (config)
(car (cdr config)))
(defsubst winring-point-of (config)
(nth 2 config))
(defsubst winring-name-of-current ()
(if (fboundp 'specifier-instance)
;; In XEmacs, this variable holds a specifier which
;; must be instanced to get the current
;; configuration name.
(specifier-instance winring-name)
;; In Emacs, just use the variable's string value
;; directly, since the `displayed' value is kept as a
;; frame parameter
winring-name))
(defun winring-save-current-configuration (&optional at-front)
(let* ((ring (winring-get-ring))
(name (winring-name-of-current))
(here (point))
(conf (list name (current-window-configuration) here)))
(if at-front
(ring-insert-at-beginning ring conf)
(ring-insert ring conf))))
(defun winring-restore-configuration (item)
(let ((conf (winring-conf-of item))
(name (winring-name-of item))
(here (winring-point-of item)))
(set-window-configuration conf)
;; current-window-configuration does not save point in current
;; window. That sucks!
(goto-char here)
(winring-set-name name))
(force-mode-line-update))
(defun winring-complete-name ()
(let* ((ring (winring-get-ring))
(n (1- (ring-length ring)))
(current (winring-name-of-current))
(table (list (cons current -1)))
name)
;; populate the completion table
(while (<= 0 n)
(setq table (cons (cons (winring-name-of (ring-ref ring n)) n) table)
n (1- n)))
(setq name (completing-read
(format "Window configuration name (%s): " current)
table nil 'must nil 'winring-name-history))
(if (string-equal name "")
(setq name current))
(cdr (assoc name table))))
(defun winring-read-name (prompt)
(let* ((ring (winring-get-ring))
(n (1- (ring-length ring)))
(table (list (winring-name-of-current)))
name)
;; get the list of all the names in the ring
(while (<= 0 n)
(setq table (cons (winring-name-of (ring-ref ring n)) table)
n (1- n)))
(setq name (read-string prompt nil 'winring-name-history))
(if (member name table)
(error "Window configuration name already in use: %s" name))
name))
;; Commands
;;;###autoload
(defun winring-new-configuration (&optional arg)
"Save the current window configuration and create an empty new one.
The buffer shown in the new empty configuration is defined by
`winring-new-config-buffer-name'.
With \\[universal-argument] prompt for the new configuration's name.
Otherwise, the function in `winring-name-generator' will be called to
get the new configuration's name."
(interactive "P")
(let ((name (and (or arg winring-prompt-on-create)
(winring-read-name "New window configuration name? "))))
;; Empty string is not allowed
(if (string-equal name "")
(setq name (funcall winring-name-generator)))
(winring-save-current-configuration)
(delete-other-windows)
(switch-to-buffer winring-new-config-buffer-name)
(winring-set-name name)))
;;;###autoload
(defun winring-duplicate-configuration (&optional arg)
"Push the current window configuration on the ring, and duplicate it.
With \\[universal-argument] prompt for the new configuration's name.
Otherwise, the function in `winring-name-generator' will be called to
get the new configuration's name."
(interactive "P")
(let ((name (and (or arg winring-prompt-on-create)
(winring-read-name "New window configuration name? "))))
;; Empty string is not allowed
(if (string-equal name "")
(setq name (funcall winring-name-generator)))
(winring-save-current-configuration)
(winring-set-name name)))
;;;###autoload
(defun winring-next-configuration ()
"Switch to the next window configuration for this frame."
(interactive)
(let ((next (ring-remove (winring-get-ring))))
(winring-save-current-configuration)
(winring-restore-configuration next)))
;;;###autoload
(defun winring-prev-configuration ()
"Switch to the previous window configuration for this frame."
(interactive)
(let ((prev (ring-remove (winring-get-ring) 0)))
(winring-save-current-configuration 'at-front)
(winring-restore-configuration prev)))
;;;###autoload
(defun winring-jump-to-configuration ()
"Go to the named window configuration."
(interactive)
(let* ((ring (winring-get-ring))
(index (winring-complete-name))
item)
;; if the current configuration was chosen, winring-complete-name
;; returns -1
(when (<= 0 index)
(setq item (ring-remove ring index))
(winring-save-current-configuration)
(winring-restore-configuration item))
))
;;;###autoload
(defun winring-delete-configuration (&optional arg)
"Delete the current configuration and switch to the next one.
With \\[universal-argument] prompt for named configuration to delete."
(interactive "P")
(let ((ring (winring-get-ring))
index)
(if (or (not arg)
(> 0 (setq index (winring-complete-name))))
;; remove the current one, so install the next one
(winring-restore-configuration (ring-remove ring))
;; otherwise, remove the named one but don't change the current config
(ring-remove ring index)
)))
;;;###autoload
(defun winring-rename-configuration (name)
"Rename the current configuration to NAME."
(interactive "sNew window configuration name? ")
(winring-set-name name))
(defconst winring-help-address "bwarsaw@python.org"
"Address accepting bug report submissions.")
(defun winring-version ()
"Echo the current version of winring in the minibuffer."
(interactive)
(message "Using winring version %s" winring-version)
;;(setq zmacs-region-stays t)
)
(defun winring-submit-bug-report (comment-p)
"Submit via mail a bug report on winring.
With \\[universal-argument] just send any type of comment."
(interactive
(list (not (y-or-n-p
"Is this a bug report? (hit `n' to send other comments) "))))
(let ((reporter-prompt-for-summary-p (if comment-p
"(Very) brief summary: "
t)))
(require 'reporter)
(reporter-submit-bug-report
winring-help-address ;address
(concat "winring " winring-version) ;pkgname
;; varlist
(if comment-p nil
'(winring-ring-size
winring-new-config-buffer-name
winring-show-names
winring-name-generator
winring-keymap-prefix))
nil ;pre-hooks
nil ;post-hooks
"Dear Barry,") ;salutation
(if comment-p nil
(set-mark (point))
(insert
"Please replace this text with a description of your problem.\n\
The more accurately and succinctly you can describe the\n\
problem you are encountering, the more likely I can fix it\n\
in a timely way.\n\n")
(exchange-point-and-mark)
;;(setq zmacs-region-stays t)
)))
;; Initialization. This is completely different b/w Emacs and XEmacs.
;; The Emacs 20.3 way is to create a frame-local variable (this is a
;; new feature with Emacs 20.3), and save the config name as a frame
;; property.
;;
;; In XEmacs 21.0 (a.k.a. 20.5), you create a generic specifier, and
;; save the config name as an instantiator over the current frame
;; locale.
;; Be sure to do this only once
(defvar winring-initialized nil)
(defun winring-initialize (&optional hack-modeline-function)
(unless winring-initialized
;;
;; Create the variable that holds the window configuration name
;;
(cond
;; The Emacs 20.3 way: frame-local variables
((fboundp 'make-variable-frame-local)
(make-variable-frame-local 'winring-name))
;; The XEmacs 21 way: specifiers
((fboundp 'make-specifier)
(setq winring-name (make-specifier 'generic)))
;; Not supported in older X/Emacsen
(t nil))
;;
;; Glom the configuration name into the mode-line. I've
;; experimented with a couple of different locations, including
;; for Emacs 20.3 mode-line-frame-identification, and for XEmacs,
;; just splicing it before the modeline-buffer-identification.
;; Sticking it on the very left side of the modeline, even before
;; mode-line-modified seems like the most useful and
;; cross-compatible place.
;;
;; Note that you can override the default hacking of the modeline
;; by passing in your own `hack-modeline-function'.
;;
(if hack-modeline-function
(funcall hack-modeline-function)
;; Else, default insertion hackery
(let ((format (list 'winring-show-names
'("<" winring-name "> ")))
(splice (cdr mode-line-format)))
(setcar splice (list format (car splice)))))
;;
;; We need to add a hook so that all newly created frames get
;; initialized properly. Again, different for Emacs and XEmacs.
;;
(if (boundp 'create-frame-hook)
;; XEmacs
(add-hook 'create-frame-hook 'winring-create-frame-hook)
;; better be Emacs!
(add-hook 'after-make-frame-functions 'winring-create-frame-hook))
;;
;; Now set the initial configuration name on the initial frame...
(winring-create-frame-hook (selected-frame))
;; ...the keymap...
(global-set-key winring-keymap-prefix winring-map)
;; ...and the init fence
(setq winring-initialized t)))
(provide 'winring)
;;; winring.el ends here