*** empty log message ***
[bpt/emacs.git] / lisp / winner.el
CommitLineData
ff287a27 1;;; winner.el --- Restore old window configurations
a14fb2b1 2
4f850b26 3;; Copyright (C) 1997, 1998 Free Software Foundation. Inc.
a14fb2b1 4
e27e5e07 5;; Author: Ivar Rummelhoff <ivarru@math.uio.no>
a14fb2b1 6;; Created: 27 Feb 1997
ff287a27 7;; Time-stamp: <1998-08-21 19:51:02 ivarr>
63f6b2c4 8;; Keywords: convenience frames
a14fb2b1 9
21f3d1d3
RS
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or modify
a14fb2b1
RS
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation; either version 2, or (at your option)
15;; any later version.
16
21f3d1d3 17;; GNU Emacs is distributed in the hope that it will be useful,
a14fb2b1
RS
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs; see the file COPYING. If not, write to the
24;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25;; Boston, MA 02111-1307, USA.
26
27;;; Commentary:
1ffece3a 28
2a92dc25
RS
29;; Winner mode is a global minor mode that records the changes in the
30;; window configuration (i.e. how the frames are partitioned into
ff287a27 31;; windows) so that the changes can be "undone" using the command
2a92dc25
RS
32;; `winner-undo'. By default this one is bound to the key sequence
33;; ctrl-x left. If you change your mind (while undoing), you can
34;; press ctrl-x right (calling `winner-redo'). Even though it uses
35;; some features of Emacs20.3, winner.el should also work with
36;; Emacs19.34 and XEmacs20, provided that the installed version of
37;; custom is not obsolete.
38
ff287a27
RS
39;; Winner mode was improved august 1998.
40
41;;; Code:
42
43(eval-when-compile
63f6b2c4 44 (require 'cl))
ff287a27 45
63f6b2c4 46(eval-when-compile
ff287a27 47 (cond
70afcff3 48 ((eq (aref (emacs-version) 0) ?X)
ff287a27
RS
49 (defmacro winner-active-region ()
50 '(region-active-p))
51 (defsetf winner-active-region () (store)
52 `(if ,store (zmacs-activate-region)
53 (zmacs-deactivate-region))))
54 (t (defmacro winner-active-region ()
55 'mark-active)
56 (defsetf winner-active-region () (store)
57 `(setq mark-active ,store)))) )
a14fb2b1 58
1ffece3a 59(require 'ring)
a14fb2b1 60
2a92dc25 61(when (fboundp 'defgroup)
ff287a27 62 (defgroup winner nil
2a92dc25
RS
63 "Restoring window configurations."
64 :group 'windows))
65
66(unless (fboundp 'defcustom)
67 (defmacro defcustom (symbol &optional initvalue docs &rest rest)
68 (list 'defvar symbol initvalue docs)))
69
63f6b2c4
DL
70\f
71;;;###autoload
4f850b26
DL
72(defcustom winner-mode nil
73 "Toggle winner-mode.
e96394e9
DL
74Setting this variable directly does not take effect;
75use either \\[customize] or the function `winner-mode'."
2a92dc25
RS
76 :set #'(lambda (symbol value)
77 (winner-mode (or value 0)))
4f850b26 78 :initialize 'custom-initialize-default
2a92dc25
RS
79 :type 'boolean
80 :group 'winner
4f850b26
DL
81 :require 'winner)
82
83(defcustom winner-dont-bind-my-keys nil
84 "If non-nil: Do not use `winner-mode-map' in Winner mode."
2a92dc25 85 :type 'boolean
4f850b26 86 :group 'winner)
a14fb2b1 87
2a92dc25
RS
88(defcustom winner-ring-size 200
89 "Maximum number of stored window configurations per frame."
90 :type 'integer
4f850b26 91 :group 'winner)
a14fb2b1 92
a14fb2b1 93
a14fb2b1 94
2a92dc25 95
ff287a27
RS
96;;;; Saving old configurations (internal variables and subroutines)
97
98;; This variable is updated with the current window configuration
99;; after every command, so that when command make changes in the
100;; window configuration, the last configuration can be saved.
101(defvar winner-currents nil)
102
103;; The current configuration (+ the buffers involved).
104(defsubst winner-conf ()
105 (list (current-window-configuration)
106 (loop for w being the windows
107 unless (window-minibuffer-p w)
108 collect (window-buffer w)) ))
109;; (if winner-testvar (incf winner-testvar) ; For debugging purposes
110;; (setq winner-testvar 0))))
111
112;; Save current configuration.
113;; (Called by `winner-save-old-configurations' below).
114(defun winner-remember ()
115 (let ((entry (assq (selected-frame) winner-currents)))
116 (if entry (setcdr entry (winner-conf))
117 (push (cons (selected-frame) (winner-conf))
118 winner-currents))))
119
120;; Consult `winner-currents'.
121(defun winner-configuration (&optional frame)
122 (or (cdr (assq (or frame (selected-frame)) winner-currents))
123 (letf (((selected-frame) frame))
124 (winner-conf))))
125
2a92dc25
RS
126
127
128;; This variable contains the window cofiguration rings.
129;; The key in this alist is the frame.
1ffece3a 130(defvar winner-ring-alist nil)
a14fb2b1 131
2a92dc25 132;; Find the right ring. If it does not exist, create one.
1ffece3a
RS
133(defsubst winner-ring (frame)
134 (or (cdr (assq frame winner-ring-alist))
135 (progn
2a92dc25
RS
136 (let ((ring (make-ring winner-ring-size)))
137 (ring-insert ring (winner-configuration frame))
138 (push (cons frame ring) winner-ring-alist)
139 ring))))
140
ff287a27
RS
141\f;; If the same command is called several times in a row,
142;; we only save one window configuration.
143(defvar winner-last-command nil)
a14fb2b1 144
ff287a27
RS
145;; Frames affected by the previous command.
146(defvar winner-last-frames nil)
a14fb2b1 147
ff287a27
RS
148;; Save the current window configuration, if it has changed.
149;; Then return frame, else return nil.
150(defun winner-insert-if-new (frame)
151 (unless (or (memq frame winner-last-frames)
152 (eq this-command 'winner-redo))
153 (let ((conf (winner-configuration frame))
154 (ring (winner-ring frame)))
155 (when (and (not (ring-empty-p ring))
156 (winner-equal conf (ring-ref ring 0)))
157 (ring-remove ring 0))
158 (ring-insert ring conf)
159 (push frame winner-last-frames)
160 frame)))
161
162;; Frames affected by the current command.
163(defvar winner-modified-list nil)
164
165;; Called whenever the window configuration changes
166;; (a `window-configuration-change-hook').
1ffece3a 167(defun winner-change-fun ()
2a92dc25
RS
168 (unless (memq (selected-frame) winner-modified-list)
169 (push (selected-frame) winner-modified-list)))
a14fb2b1 170
2a92dc25 171
ff287a27
RS
172;; For Emacs20 (a `post-command-hook').
173(defun winner-save-old-configurations ()
174 (unless (eq this-command winner-last-command)
175 (setq winner-last-frames nil)
176 (setq winner-last-command this-command))
177 (dolist (frame winner-modified-list)
178 (winner-insert-if-new frame))
179 (setq winner-modified-list nil)
180 ;; (ir-trace ; For debugging purposes
181 ;; "%S"
182 ;; (loop with ring = (winner-ring (selected-frame))
183 ;; for i from 0 to (1- (ring-length ring))
184 ;; collect (caddr (ring-ref ring i))))
185 (winner-remember))
186
187;; For compatibility with other emacsen
188;; and called by `winner-undo' before "undoing".
2a92dc25 189(defun winner-save-unconditionally ()
ff287a27
RS
190 (unless (eq this-command winner-last-command)
191 (setq winner-last-frames nil)
192 (setq winner-last-command this-command))
193 (winner-insert-if-new (selected-frame))
194 (winner-remember))
2a92dc25 195
2a92dc25 196
2a92dc25 197
2a92dc25 198
ff287a27
RS
199\f;;;; Restoring configurations
200
201;; Works almost as `set-window-configuration',
202;; but doesn't change the contents or the size of the minibuffer.
203(defun winner-set-conf (winconf)
204 (let ((miniwin (minibuffer-window))
205 (minisel (window-minibuffer-p (selected-window))))
206 (let ((minibuf (window-buffer miniwin))
207 (minipoint (window-point miniwin))
208 (minisize (window-height miniwin)))
209 (set-window-configuration winconf)
210 (setf (window-buffer miniwin) minibuf
211 (window-point miniwin) minipoint)
212 (when (/= minisize (window-height miniwin))
213 (letf (((selected-window) miniwin) )
214 ;; Clumsy due to cl-macs-limitation
215 (setf (window-height) minisize)))
216 (cond
217 (minisel (select-window miniwin))
218 ((window-minibuffer-p (selected-window))
219 (other-window 1))))))
220
221
222(defvar winner-point-alist nil)
223;; `set-window-configuration' restores old points and marks. This is
224;; not what we want, so we make a list of the "real" (i.e. new) points
225;; and marks before undoing window configurations.
226;;
227;; Format of entries: (buffer (mark . mark-active) (window . point) ..)
228
229(defun winner-make-point-alist ()
230 (letf (((current-buffer)))
231 (loop with alist
232 with entry
233 for win being the windows
234 do (cond
235 ((window-minibuffer-p win))
236 ((setq entry (assq win alist))
237 ;; Update existing entry
238 (push (cons win (window-point win))
239 (cddr entry)))
240 (t;; Else create new entry
241 (push (list (set-buffer (window-buffer win))
242 (cons (mark t) (winner-active-region))
243 (cons win (window-point win)))
244 alist)))
245 finally return alist)))
246
247
248(defun winner-get-point (buf win)
249 ;; Consult (and possibly extend) `winner-point-alist'.
250 (when (buffer-name buf)
251 (let ((entry (assq buf winner-point-alist)))
252 (cond
253 (entry
254 (or (cdr (assq win (cddr entry)))
255 (cdr (assq nil (cddr entry)))
256 (letf (((current-buffer) buf))
257 (push (cons nil (point)) (cddr entry))
258 (point))))
259 (t (letf (((current-buffer) buf))
260 (push (list buf
261 (cons (mark t) (winner-active-region))
262 (cons nil (point)))
263 winner-point-alist)
264 (point)))))))
265
266\f;; Make sure point doesn't end up in the minibuffer and
2a92dc25
RS
267;; delete windows displaying dead buffers. Return nil
268;; if and only if all the windows should have been deleted.
ff287a27 269;; Do not move neither points nor marks.
1ffece3a 270(defun winner-set (conf)
ff287a27
RS
271 (let* ((buffers nil)
272 (origpoints
273 (loop for buf in (cadr conf)
274 for pos = (winner-get-point buf nil)
275 if (and pos (not (memq buf buffers)))
276 do (push buf buffers)
277 collect pos)))
2a92dc25 278 (winner-set-conf (car conf))
ff287a27
RS
279 (let (xwins) ; These windows should be deleted
280 (loop for win being the windows
281 unless (window-minibuffer-p win)
282 do (if (pop origpoints)
283 (setf (window-point win)
284 ;; Restore point
285 (winner-get-point
286 (window-buffer win)
287 win))
288 (push win xwins))) ; delete this window
289 ;; Restore mark
290 (letf (((current-buffer)))
291 (loop for buf in buffers
292 for entry = (cadr (assq buf winner-point-alist))
293 do (progn (set-buffer buf)
294 (set-mark (car entry))
295 (setf (winner-active-region) (cdr entry)))))
296 ;; Delete windows, whose buffers are dead.
297 ;; Return t if this is still a possible configuration.
298 (or (null xwins)
299 (progn (mapcar 'delete-window (cdr xwins))
300 (if (one-window-p t)
301 nil ; No windows left
302 (progn (delete-window (car xwins))
303 t)))))))
304
305
306
307;;;; Winner mode (a minor mode)
a14fb2b1 308
4f850b26
DL
309(defcustom winner-mode-hook nil
310 "Functions to run whenever Winner mode is turned on."
311 :type 'hook
78900bc3 312 :group 'winner)
a14fb2b1 313
78900bc3 314(defcustom winner-mode-leave-hook nil
4f850b26
DL
315 "Functions to run whenever Winner mode is turned off."
316 :type 'hook
78900bc3 317 :group 'winner)
1ffece3a 318
a14fb2b1 319(defvar winner-mode-map nil "Keymap for Winner mode.")
21f3d1d3 320
2a92dc25
RS
321;; Is `window-configuration-change-hook' working?
322(defun winner-hook-installed-p ()
323 (save-window-excursion
324 (let ((winner-var nil)
325 (window-configuration-change-hook
326 '((lambda () (setq winner-var t)))))
327 (split-window)
328 winner-var)))
329
63f6b2c4
DL
330\f
331;;;###autoload
a14fb2b1
RS
332(defun winner-mode (&optional arg)
333 "Toggle Winner mode.
334With arg, turn Winner mode on if and only if arg is positive."
335 (interactive "P")
336 (let ((on-p (if arg (> (prefix-numeric-value arg) 0)
337 (not winner-mode))))
338 (cond
1ffece3a
RS
339 ;; Turn mode on
340 (on-p
341 (setq winner-mode t)
2a92dc25
RS
342 (cond
343 ((winner-hook-installed-p)
344 (add-hook 'window-configuration-change-hook 'winner-change-fun)
ff287a27 345 (add-hook 'post-command-hook 'winner-save-old-configurations))
2a92dc25 346 (t (add-hook 'post-command-hook 'winner-save-unconditionally)))
1ffece3a 347 (setq winner-modified-list (frame-list))
ff287a27 348 (winner-save-old-configurations)
1ffece3a
RS
349 (run-hooks 'winner-mode-hook))
350 ;; Turn mode off
351 (winner-mode
352 (setq winner-mode nil)
2a92dc25 353 (remove-hook 'window-configuration-change-hook 'winner-change-fun)
ff287a27 354 (remove-hook 'post-command-hook 'winner-save-old-configurations)
2a92dc25 355 (remove-hook 'post-command-hook 'winner-save-unconditionally)
1ffece3a 356 (run-hooks 'winner-mode-leave-hook)))
a14fb2b1
RS
357 (force-mode-line-update)))
358
2a92dc25 359\f;; Inspired by undo (simple.el)
e0a81650 360
ff287a27
RS
361(defvar winner-undo-frame nil)
362
e0a81650
RS
363(defvar winner-pending-undo-ring nil
364 "The ring currently used by winner undo.")
365(defvar winner-undo-counter nil)
366(defvar winner-undone-data nil) ; There confs have been passed.
367
ff287a27 368(defun winner-undo ()
1ffece3a 369 "Switch back to an earlier window configuration saved by Winner mode.
ff287a27
RS
370In other words, \"undo\" changes in window configuration."
371 (interactive)
1ffece3a
RS
372 (cond
373 ((not winner-mode) (error "Winner mode is turned off"))
ff287a27
RS
374 (t (unless (and (eq last-command 'winner-undo)
375 (eq winner-undo-frame (selected-frame)))
376 (winner-save-unconditionally) ; current configuration->stack
377 (setq winner-undo-frame (selected-frame))
378 (setq winner-point-alist (winner-make-point-alist))
379 (setq winner-pending-undo-ring (winner-ring (selected-frame)))
380 (setq winner-undo-counter 0)
381 (setq winner-undone-data (list (winner-win-data))))
382 (incf winner-undo-counter) ; starting at 1
383 (when (and (winner-undo-this)
384 (not (window-minibuffer-p (selected-window))))
385 (message "Winner undo (%d / %d)"
386 winner-undo-counter
387 (1- (ring-length winner-pending-undo-ring)))))))
388
389(defun winner-win-data ()
390 ;; Essential properties of the windows in the selected frame.
391 (loop for win being the windows
392 unless (window-minibuffer-p win)
393 collect (list (window-buffer win)
394 (window-width win)
395 (window-height win))))
396
397\f
398(defun winner-undo-this () ; The heart of winner undo.
399 (loop
400 (cond
401 ((>= winner-undo-counter (ring-length winner-pending-undo-ring))
402 (message "No further window configuration undo information")
403 (return nil))
404
405 ((and ; If possible configuration
406 (winner-set (ring-ref winner-pending-undo-ring
407 winner-undo-counter))
408 ;; .. and new configuration
409 (let ((data (winner-win-data)))
410 (and (not (member data winner-undone-data))
411 (push data winner-undone-data))))
412 (return t)) ; .. then everything is all right.
413 (t ; Else; discharge it and try another one.
414 (ring-remove winner-pending-undo-ring winner-undo-counter)))))
415
416
417(defun winner-redo () ; If you change your mind.
1ffece3a
RS
418 "Restore a more recent window configuration saved by Winner mode."
419 (interactive)
420 (cond
421 ((eq last-command 'winner-undo)
1ffece3a
RS
422 (winner-set
423 (ring-remove winner-pending-undo-ring 0))
ff287a27
RS
424 (unless (eq (selected-window) (minibuffer-window))
425 (message "Winner undid undo")))
1ffece3a 426 (t (error "Previous command was not a winner-undo"))))
ff287a27
RS
427\f
428;;; To be evaluated when the package is loaded:
2a92dc25
RS
429
430(if (fboundp 'compare-window-configurations)
431 (defalias 'winner-equal 'compare-window-configurations)
432 (defalias 'winner-equal 'equal))
a14fb2b1
RS
433
434(unless winner-mode-map
435 (setq winner-mode-map (make-sparse-keymap))
2a92dc25
RS
436 (define-key winner-mode-map [(control x) left] 'winner-undo)
437 (define-key winner-mode-map [(control x) right] 'winner-redo))
a14fb2b1
RS
438
439(unless (or (assq 'winner-mode minor-mode-map-alist)
440 winner-dont-bind-my-keys)
441 (push (cons 'winner-mode winner-mode-map)
442 minor-mode-map-alist))
443
444(unless (assq 'winner-mode minor-mode-alist)
445 (push '(winner-mode " Win") minor-mode-alist))
446
447(provide 'winner)
448
21f3d1d3 449;;; winner.el ends here