*** empty log message ***
[bpt/emacs.git] / lisp / winner.el
CommitLineData
ff287a27 1;;; winner.el --- Restore old window configurations
a14fb2b1 2
59c2dcf4 3;; Copyright (C) 1997, 1998, 2001 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
59c2dcf4
GM
148
149(defun winner-equal (a b)
150 "Check two Winner configurations A and B for equality.
151Winner configurations are of the form (CONFIG BUFFERS),
152where CONFIG is a window configuration and BUFFERS is a list of
153buffers."
154 (and (compare-window-configurations (car a) (car b))
155 (equal (cdr a) (cdr b))))
156
157
ff287a27
RS
158;; Save the current window configuration, if it has changed.
159;; Then return frame, else return nil.
160(defun winner-insert-if-new (frame)
161 (unless (or (memq frame winner-last-frames)
162 (eq this-command 'winner-redo))
163 (let ((conf (winner-configuration frame))
164 (ring (winner-ring frame)))
165 (when (and (not (ring-empty-p ring))
166 (winner-equal conf (ring-ref ring 0)))
167 (ring-remove ring 0))
168 (ring-insert ring conf)
169 (push frame winner-last-frames)
170 frame)))
171
172;; Frames affected by the current command.
173(defvar winner-modified-list nil)
174
175;; Called whenever the window configuration changes
176;; (a `window-configuration-change-hook').
1ffece3a 177(defun winner-change-fun ()
2a92dc25
RS
178 (unless (memq (selected-frame) winner-modified-list)
179 (push (selected-frame) winner-modified-list)))
a14fb2b1 180
2a92dc25 181
ff287a27
RS
182;; For Emacs20 (a `post-command-hook').
183(defun winner-save-old-configurations ()
184 (unless (eq this-command winner-last-command)
185 (setq winner-last-frames nil)
186 (setq winner-last-command this-command))
187 (dolist (frame winner-modified-list)
188 (winner-insert-if-new frame))
189 (setq winner-modified-list nil)
190 ;; (ir-trace ; For debugging purposes
191 ;; "%S"
192 ;; (loop with ring = (winner-ring (selected-frame))
193 ;; for i from 0 to (1- (ring-length ring))
194 ;; collect (caddr (ring-ref ring i))))
195 (winner-remember))
196
197;; For compatibility with other emacsen
198;; and called by `winner-undo' before "undoing".
2a92dc25 199(defun winner-save-unconditionally ()
ff287a27
RS
200 (unless (eq this-command winner-last-command)
201 (setq winner-last-frames nil)
202 (setq winner-last-command this-command))
203 (winner-insert-if-new (selected-frame))
204 (winner-remember))
2a92dc25 205
2a92dc25 206
2a92dc25 207
2a92dc25 208
ff287a27
RS
209\f;;;; Restoring configurations
210
211;; Works almost as `set-window-configuration',
212;; but doesn't change the contents or the size of the minibuffer.
213(defun winner-set-conf (winconf)
214 (let ((miniwin (minibuffer-window))
215 (minisel (window-minibuffer-p (selected-window))))
216 (let ((minibuf (window-buffer miniwin))
217 (minipoint (window-point miniwin))
218 (minisize (window-height miniwin)))
219 (set-window-configuration winconf)
220 (setf (window-buffer miniwin) minibuf
221 (window-point miniwin) minipoint)
222 (when (/= minisize (window-height miniwin))
223 (letf (((selected-window) miniwin) )
224 ;; Clumsy due to cl-macs-limitation
225 (setf (window-height) minisize)))
226 (cond
227 (minisel (select-window miniwin))
228 ((window-minibuffer-p (selected-window))
229 (other-window 1))))))
230
231
232(defvar winner-point-alist nil)
233;; `set-window-configuration' restores old points and marks. This is
234;; not what we want, so we make a list of the "real" (i.e. new) points
235;; and marks before undoing window configurations.
236;;
237;; Format of entries: (buffer (mark . mark-active) (window . point) ..)
238
239(defun winner-make-point-alist ()
240 (letf (((current-buffer)))
241 (loop with alist
242 with entry
243 for win being the windows
244 do (cond
245 ((window-minibuffer-p win))
246 ((setq entry (assq win alist))
247 ;; Update existing entry
248 (push (cons win (window-point win))
249 (cddr entry)))
250 (t;; Else create new entry
251 (push (list (set-buffer (window-buffer win))
252 (cons (mark t) (winner-active-region))
253 (cons win (window-point win)))
254 alist)))
255 finally return alist)))
256
257
258(defun winner-get-point (buf win)
259 ;; Consult (and possibly extend) `winner-point-alist'.
260 (when (buffer-name buf)
261 (let ((entry (assq buf winner-point-alist)))
262 (cond
263 (entry
264 (or (cdr (assq win (cddr entry)))
265 (cdr (assq nil (cddr entry)))
266 (letf (((current-buffer) buf))
267 (push (cons nil (point)) (cddr entry))
268 (point))))
269 (t (letf (((current-buffer) buf))
270 (push (list buf
271 (cons (mark t) (winner-active-region))
272 (cons nil (point)))
273 winner-point-alist)
274 (point)))))))
275
276\f;; Make sure point doesn't end up in the minibuffer and
2a92dc25
RS
277;; delete windows displaying dead buffers. Return nil
278;; if and only if all the windows should have been deleted.
ff287a27 279;; Do not move neither points nor marks.
1ffece3a 280(defun winner-set (conf)
ff287a27
RS
281 (let* ((buffers nil)
282 (origpoints
283 (loop for buf in (cadr conf)
284 for pos = (winner-get-point buf nil)
285 if (and pos (not (memq buf buffers)))
286 do (push buf buffers)
287 collect pos)))
2a92dc25 288 (winner-set-conf (car conf))
ff287a27
RS
289 (let (xwins) ; These windows should be deleted
290 (loop for win being the windows
291 unless (window-minibuffer-p win)
292 do (if (pop origpoints)
293 (setf (window-point win)
294 ;; Restore point
295 (winner-get-point
296 (window-buffer win)
297 win))
298 (push win xwins))) ; delete this window
299 ;; Restore mark
300 (letf (((current-buffer)))
301 (loop for buf in buffers
302 for entry = (cadr (assq buf winner-point-alist))
303 do (progn (set-buffer buf)
304 (set-mark (car entry))
305 (setf (winner-active-region) (cdr entry)))))
306 ;; Delete windows, whose buffers are dead.
307 ;; Return t if this is still a possible configuration.
308 (or (null xwins)
309 (progn (mapcar 'delete-window (cdr xwins))
310 (if (one-window-p t)
311 nil ; No windows left
312 (progn (delete-window (car xwins))
313 t)))))))
314
315
316
317;;;; Winner mode (a minor mode)
a14fb2b1 318
4f850b26
DL
319(defcustom winner-mode-hook nil
320 "Functions to run whenever Winner mode is turned on."
321 :type 'hook
78900bc3 322 :group 'winner)
a14fb2b1 323
78900bc3 324(defcustom winner-mode-leave-hook nil
4f850b26
DL
325 "Functions to run whenever Winner mode is turned off."
326 :type 'hook
78900bc3 327 :group 'winner)
1ffece3a 328
a14fb2b1 329(defvar winner-mode-map nil "Keymap for Winner mode.")
21f3d1d3 330
2a92dc25
RS
331;; Is `window-configuration-change-hook' working?
332(defun winner-hook-installed-p ()
333 (save-window-excursion
334 (let ((winner-var nil)
335 (window-configuration-change-hook
336 '((lambda () (setq winner-var t)))))
337 (split-window)
338 winner-var)))
339
63f6b2c4
DL
340\f
341;;;###autoload
a14fb2b1
RS
342(defun winner-mode (&optional arg)
343 "Toggle Winner mode.
344With arg, turn Winner mode on if and only if arg is positive."
345 (interactive "P")
346 (let ((on-p (if arg (> (prefix-numeric-value arg) 0)
347 (not winner-mode))))
348 (cond
1ffece3a
RS
349 ;; Turn mode on
350 (on-p
351 (setq winner-mode t)
2a92dc25
RS
352 (cond
353 ((winner-hook-installed-p)
354 (add-hook 'window-configuration-change-hook 'winner-change-fun)
ff287a27 355 (add-hook 'post-command-hook 'winner-save-old-configurations))
2a92dc25 356 (t (add-hook 'post-command-hook 'winner-save-unconditionally)))
1ffece3a 357 (setq winner-modified-list (frame-list))
ff287a27 358 (winner-save-old-configurations)
1ffece3a
RS
359 (run-hooks 'winner-mode-hook))
360 ;; Turn mode off
361 (winner-mode
362 (setq winner-mode nil)
2a92dc25 363 (remove-hook 'window-configuration-change-hook 'winner-change-fun)
ff287a27 364 (remove-hook 'post-command-hook 'winner-save-old-configurations)
2a92dc25 365 (remove-hook 'post-command-hook 'winner-save-unconditionally)
1ffece3a 366 (run-hooks 'winner-mode-leave-hook)))
a14fb2b1
RS
367 (force-mode-line-update)))
368
2a92dc25 369\f;; Inspired by undo (simple.el)
e0a81650 370
ff287a27
RS
371(defvar winner-undo-frame nil)
372
e0a81650
RS
373(defvar winner-pending-undo-ring nil
374 "The ring currently used by winner undo.")
375(defvar winner-undo-counter nil)
376(defvar winner-undone-data nil) ; There confs have been passed.
377
ff287a27 378(defun winner-undo ()
1ffece3a 379 "Switch back to an earlier window configuration saved by Winner mode.
ff287a27
RS
380In other words, \"undo\" changes in window configuration."
381 (interactive)
1ffece3a
RS
382 (cond
383 ((not winner-mode) (error "Winner mode is turned off"))
ff287a27
RS
384 (t (unless (and (eq last-command 'winner-undo)
385 (eq winner-undo-frame (selected-frame)))
386 (winner-save-unconditionally) ; current configuration->stack
387 (setq winner-undo-frame (selected-frame))
388 (setq winner-point-alist (winner-make-point-alist))
389 (setq winner-pending-undo-ring (winner-ring (selected-frame)))
390 (setq winner-undo-counter 0)
391 (setq winner-undone-data (list (winner-win-data))))
392 (incf winner-undo-counter) ; starting at 1
393 (when (and (winner-undo-this)
394 (not (window-minibuffer-p (selected-window))))
395 (message "Winner undo (%d / %d)"
396 winner-undo-counter
397 (1- (ring-length winner-pending-undo-ring)))))))
398
399(defun winner-win-data ()
400 ;; Essential properties of the windows in the selected frame.
401 (loop for win being the windows
402 unless (window-minibuffer-p win)
403 collect (list (window-buffer win)
404 (window-width win)
405 (window-height win))))
406
407\f
408(defun winner-undo-this () ; The heart of winner undo.
409 (loop
410 (cond
411 ((>= winner-undo-counter (ring-length winner-pending-undo-ring))
412 (message "No further window configuration undo information")
413 (return nil))
414
415 ((and ; If possible configuration
416 (winner-set (ring-ref winner-pending-undo-ring
417 winner-undo-counter))
418 ;; .. and new configuration
419 (let ((data (winner-win-data)))
420 (and (not (member data winner-undone-data))
421 (push data winner-undone-data))))
422 (return t)) ; .. then everything is all right.
423 (t ; Else; discharge it and try another one.
424 (ring-remove winner-pending-undo-ring winner-undo-counter)))))
425
426
427(defun winner-redo () ; If you change your mind.
1ffece3a
RS
428 "Restore a more recent window configuration saved by Winner mode."
429 (interactive)
430 (cond
431 ((eq last-command 'winner-undo)
1ffece3a
RS
432 (winner-set
433 (ring-remove winner-pending-undo-ring 0))
ff287a27
RS
434 (unless (eq (selected-window) (minibuffer-window))
435 (message "Winner undid undo")))
1ffece3a 436 (t (error "Previous command was not a winner-undo"))))
ff287a27
RS
437\f
438;;; To be evaluated when the package is loaded:
2a92dc25 439
a14fb2b1
RS
440(unless winner-mode-map
441 (setq winner-mode-map (make-sparse-keymap))
2a92dc25
RS
442 (define-key winner-mode-map [(control x) left] 'winner-undo)
443 (define-key winner-mode-map [(control x) right] 'winner-redo))
a14fb2b1
RS
444
445(unless (or (assq 'winner-mode minor-mode-map-alist)
446 winner-dont-bind-my-keys)
447 (push (cons 'winner-mode winner-mode-map)
448 minor-mode-map-alist))
449
450(unless (assq 'winner-mode minor-mode-alist)
451 (push '(winner-mode " Win") minor-mode-alist))
452
453(provide 'winner)
454
21f3d1d3 455;;; winner.el ends here