viper -- Update and prettify package-info.in provides.
[packages] / xemacs-packages / edit-utils / winring.el
1 ;;; winring.el --- Window configuration rings
2
3 ;; Copyright (C) 1998, 1999 Free Software Foundation, Inc.
4
5 ;; Author:   1997-2004 Barry A. Warsaw
6 ;; Contact:  gnu@wooz.org (Barry A. Warsaw)
7 ;; Homepage: http://barry.warsaw.us/elisp
8 ;; Created:  March 1997
9 ;; Keywords: frames tools
10
11 (defconst winring-version "$Revision: 1.2 $"
12   "winring version number.")
13
14 ;; This file is part of GNU Emacs.
15
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)
19 ;; any later version.
20
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.
25
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.
30
31 ;;; Commentary:
32 ;;
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.
43 ;;
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.
47 ;;
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.
57
58 ;; To use, make sure this file is on your `load-path' and put the
59 ;; following in your .emacs file:
60 ;;
61 ;; (require 'winring)
62 ;; (winring-initialize)
63 ;;
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.
69
70 ;; The following commands are defined:
71 ;;
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'
75 ;;
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.
80 ;;
81 ;;    C-x 7 2 -- Create a duplicate of the current window
82 ;;               configuration.  C-u has the same semantics as with
83 ;;               "C-x 7 c".
84 ;;
85 ;;    C-x 7 j -- Jump to a named configuration (prompts for the name).
86 ;;
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.
91 ;;
92 ;;    C-x 7 o -- Go to the next configuration on the ring.
93 ;;
94 ;;    C-x 7 p -- Go to the previous configuration on the ring.
95 ;;
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.
99 ;;
100 ;;    C-x 7 r -- Rename the current window configuration.
101 ;;
102 ;;    C-x 7 b -- Submit a bug report on winring.
103 ;;
104 ;;    C-x 7 v -- Echo the winring version.
105
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
113 ;; want).
114 ;;
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'.
120
121 ;;; History:
122 ;;
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.
128 ;;
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).
135 ;;
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
140 ;; better.
141 ;;
142 ;; I know of a few other related packages:
143 ;;
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.
147 ;;
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
151 ;;      rings.
152 ;;
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.
157 ;;
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
161 ;;      interactive use.
162
163 ;;    - XEmacs has some built-in support for window configuration
164 ;;      stacks, but I wanted to use a ring structure for managing
165 ;;      configurations.
166
167 ;; Please feel free to email me if my rendition of history, or my
168 ;; explanation of the related packages, is inaccurate.
169
170 ;;; Code:
171
172 (require 'ring)
173
174 \f
175 (defgroup winring nil
176   "Window configuration rings"
177   :prefix "winring-"
178   :group 'frames)
179
180 (defcustom winring-ring-size 7
181   "*Size of the window configuration ring."
182   :type 'integer
183   :group 'winring)
184
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."
189   :type '(radio
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"
193                  usually)
194           )
195   :group 'winring)
196
197 (defcustom winring-new-config-buffer-name "*scratch*"
198   "*Name of the buffer to switch to when a new configuration is created."
199   :type 'string
200   :group 'winring)
201
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
205 configurations."
206   :type 'boolean
207   :group 'winring)
208
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."
214   :type 'function
215   :group 'winring)
216
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'.")
221
222 \f
223 ;; Set up keymap
224 (defvar winring-map nil
225   "Keymap used for winring, window configuration rings.")
226 (if winring-map
227     nil
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)
238   )
239
240
241 \f
242 ;; Winring names
243 (defvar winring-name nil
244   "The name of the currently displayed window configuration.")
245
246 (defvar winring-name-index 1
247   "Index used as a sequence number for new unnamed window configurations.")
248
249 (defvar winring-name-history nil
250   "History variable for window configuration name prompts.")
251
252 (defun winring-next-name ()
253   (let ((name (format "%03d" winring-name-index)))
254     (setq winring-name-index (1+ winring-name-index))
255     name))
256
257
258 \f
259 ;; Compatibility
260 (defun winring-set-frame-ring (frame ring)
261   (cond
262    ;; XEmacs
263    ((fboundp 'set-frame-property)
264     (set-frame-property frame 'winring-ring ring))
265    ;; Emacs
266    ((fboundp 'modify-frame-parameters)
267     (modify-frame-parameters frame (list (cons 'winring-ring ring))))
268    ;; Not supported
269    (t (error "This version of Emacs is not supported by winring"))))
270
271 (defun winring-get-frame-ring (frame)
272   (cond
273    ;; XEmacs
274    ((fboundp 'frame-property)
275     (frame-property frame 'winring-ring))
276    ;; Emacs 20
277    ((fboundp 'frame-parameter)
278     (frame-parameter frame 'winring-ring))
279    ;; Emacs 19.34
280    ((fboundp 'frame-parameters)
281     (cdr (assq 'winring-ring (frame-parameters frame))))
282    ;; Unsupported
283    (t (error "This version of Emacs is not supported by winring"))))
284
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))
290                     frame))
291
292 \f
293 ;; Utilities
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)))
307       ))
308   (if (not winring-show-names)
309       (message "Switching to window configuration: %s" name)))
310
311 (defun winring-get-ring ()
312   (let* ((frame (selected-frame))
313          (ring (winring-get-frame-ring frame)))
314     (when (not ring)
315       (setq ring (make-ring winring-ring-size))
316       (winring-set-frame-ring frame ring))
317     ring))
318
319 (defsubst winring-name-of (config)
320   (car config))
321
322 (defsubst winring-conf-of (config)
323   (car (cdr config)))
324
325 (defsubst winring-point-of (config)
326   (nth 2 config))
327
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
336     ;; frame parameter
337     winring-name))
338
339 (defun winring-save-current-configuration (&optional at-front)
340   (let* ((ring (winring-get-ring))
341          (name (winring-name-of-current))
342          (here (point))
343          (conf (list name (current-window-configuration) here)))
344     (if at-front
345         (ring-insert-at-beginning ring conf)
346       (ring-insert ring conf))))
347
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!
355     (goto-char here)
356     (winring-set-name name))
357   (force-mode-line-update))
358
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)))
364          name)
365     ;; populate the completion table
366     (while (<= 0 n)
367       (setq table (cons (cons (winring-name-of (ring-ref ring n)) n) table)
368             n (1- n)))
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 "")
373         (setq name current))
374     (cdr (assoc name table))))
375
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)))
380          name)
381     ;; get the list of all the names in the ring
382     (while (<= 0 n)
383       (setq table (cons (winring-name-of (ring-ref ring n)) table)
384             n (1- n)))
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))
388     name))
389
390 \f
391 ;; Commands
392
393 ;;;###autoload
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'.
398
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."
402   (interactive "P")
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)))
412
413 ;;;###autoload
414 (defun winring-duplicate-configuration (&optional arg)
415   "Push the current window configuration on the ring, and duplicate it.
416
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."
420   (interactive "P")
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)))
428
429 ;;;###autoload
430 (defun winring-next-configuration ()
431   "Switch to the next window configuration for this frame."
432   (interactive)
433   (let ((next (ring-remove (winring-get-ring))))
434     (winring-save-current-configuration)
435     (winring-restore-configuration next)))
436
437 ;;;###autoload
438 (defun winring-prev-configuration ()
439   "Switch to the previous window configuration for this frame."
440   (interactive)
441   (let ((prev (ring-remove (winring-get-ring) 0)))
442     (winring-save-current-configuration 'at-front)
443     (winring-restore-configuration prev)))
444
445 ;;;###autoload
446 (defun winring-jump-to-configuration ()
447   "Go to the named window configuration."
448   (interactive)
449   (let* ((ring (winring-get-ring))
450          (index (winring-complete-name))
451          item)
452     ;; if the current configuration was chosen, winring-complete-name
453     ;; returns -1
454     (when (<= 0 index)
455       (setq item (ring-remove ring index))
456       (winring-save-current-configuration)
457       (winring-restore-configuration item))
458     ))
459
460 ;;;###autoload
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."
464   (interactive "P")
465   (let ((ring (winring-get-ring))
466         index)
467     (if (or (not arg)
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)
473       )))
474
475 ;;;###autoload
476 (defun winring-rename-configuration ()
477   "Rename the current configuration to NAME."
478   (interactive)
479   (winring-set-name (winring-read-name "New window configuration name? ")))
480
481
482 \f
483 (defconst winring-help-address "bwarsaw@python.org"
484   "Address accepting bug report submissions.")
485
486 (defun winring-version ()
487   "Echo the current version of winring in the minibuffer."
488   (interactive)
489   (message "Using winring version %s" winring-version)
490   ;;(setq zmacs-region-stays t)
491   )
492
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."
496   (interactive
497    (list (not (y-or-n-p
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: "
501                                          t)))
502     (require 'reporter)
503     (reporter-submit-bug-report
504      winring-help-address                ;address
505      (concat "winring " winring-version) ;pkgname
506      ;; varlist
507      (if comment-p nil
508        '(winring-ring-size
509          winring-new-config-buffer-name
510          winring-show-names
511          winring-name-generator
512          winring-keymap-prefix))
513      nil                                ;pre-hooks
514      nil                                ;post-hooks
515      "Dear Barry,")                     ;salutation
516     (if comment-p nil
517       (set-mark (point))
518       (insert
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)
525       )))
526
527
528 \f
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
532 ;; property.
533 ;;
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
536 ;; locale.
537
538 ;; Be sure to do this only once
539 (defvar winring-initialized nil)
540
541 (defun winring-initialize (&optional hack-modeline-function)
542   (unless winring-initialized
543     ;;
544     ;; Create the variable that holds the window configuration name
545     ;;
546     (cond
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
554      (t nil))
555     ;;
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.
563     ;;
564     ;; Note that you can override the default hacking of the modeline
565     ;; by passing in your own `hack-modeline-function'.
566     ;;
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)))))
574     ;;
575     ;; We need to add a hook so that all newly created frames get
576     ;; initialized properly.  Again, different for Emacs and XEmacs.
577     ;;
578     (if (boundp 'create-frame-hook)
579         ;; XEmacs
580         (add-hook 'create-frame-hook 'winring-create-frame-hook)
581       ;; better be Emacs!
582       (add-hook 'after-make-frame-functions 'winring-create-frame-hook))
583     ;;
584     ;; Now set the initial configuration name on the initial frame...
585     (winring-create-frame-hook (selected-frame))
586     ;; ...the keymap...
587     (global-set-key winring-keymap-prefix winring-map)
588     ;; ...and the init fence
589     (setq winring-initialized t)))
590
591
592 \f
593 (provide 'winring)
594 ;;; winring.el ends here