Revision: miles@gnu.org--gnu-2005/emacs--cvs-trunk--0--patch-419
[bpt/emacs.git] / lisp / winner.el
CommitLineData
ff287a27 1;;; winner.el --- Restore old window configurations
a14fb2b1 2
8eae13fd 3;; Copyright (C) 1997, 1998, 2001, 2004 Free Software Foundation. Inc.
a14fb2b1 4
e27e5e07 5;; Author: Ivar Rummelhoff <ivarru@math.uio.no>
a14fb2b1 6;; Created: 27 Feb 1997
4a81d892 7;; Time-stamp: <2002-02-20 22:06:58 ivarru>
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 32;; `winner-undo'. By default this one is bound to the key sequence
8eae13fd
JB
33;; ctrl-c left. If you change your mind (while undoing), you can
34;; press ctrl-c right (calling `winner-redo'). Even though it uses
2a92dc25
RS
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
4a81d892
RS
39;; Winner mode was improved August 1998.
40;; Further improvements February 2002.
ff287a27
RS
41
42;;; Code:
43
44(eval-when-compile
63f6b2c4 45 (require 'cl))
ff287a27 46
6babdce9
SM
47
48(defmacro winner-active-region ()
49 (if (fboundp 'region-active-p)
50 '(region-active-p)
51 'mark-active))
52
53(defsetf winner-active-region () (store)
54 (if (fboundp 'zmacs-activate-region)
ff287a27 55 `(if ,store (zmacs-activate-region)
6babdce9
SM
56 (zmacs-deactivate-region))
57 `(setq mark-active ,store)))
a14fb2b1 58
6babdce9
SM
59(defalias 'winner-edges
60 (if (featurep 'xemacs) 'window-pixel-edges 'window-edges))
61(defalias 'winner-window-list
62 (if (featurep 'xemacs)
63 (lambda () (delq (minibuffer-window) (window-list nil 0)))
64 (lambda () (window-list nil 0))))
4a81d892 65
1ffece3a 66(require 'ring)
a14fb2b1 67
ed0d58c9
MR
68(unless (fboundp 'defgroup)
69 (defmacro defgroup (&rest rest)))
70
71(defgroup winner nil
72 "Restoring window configurations."
73 :group 'windows)
2a92dc25
RS
74
75(unless (fboundp 'defcustom)
76 (defmacro defcustom (symbol &optional initvalue docs &rest rest)
77 (list 'defvar symbol initvalue docs)))
78
63f6b2c4 79;;;###autoload
4f850b26
DL
80(defcustom winner-mode nil
81 "Toggle winner-mode.
e96394e9
DL
82Setting this variable directly does not take effect;
83use either \\[customize] or the function `winner-mode'."
6babdce9 84 :set #'(lambda (symbol value) (funcall symbol (or value 0)))
4f850b26 85 :initialize 'custom-initialize-default
2a92dc25
RS
86 :type 'boolean
87 :group 'winner
4f850b26
DL
88 :require 'winner)
89
90(defcustom winner-dont-bind-my-keys nil
91 "If non-nil: Do not use `winner-mode-map' in Winner mode."
2a92dc25 92 :type 'boolean
4f850b26 93 :group 'winner)
a14fb2b1 94
2a92dc25
RS
95(defcustom winner-ring-size 200
96 "Maximum number of stored window configurations per frame."
97 :type 'integer
4f850b26 98 :group 'winner)
a14fb2b1 99
4a81d892
RS
100(defcustom winner-boring-buffers '("*Completions*")
101 "`winner-undo' will not restore windows displaying any of these \
102buffers.
103You may want to include buffer names such as *Help*, *Apropos*,
104*Buffer List*, *info* and *Compile-Log*."
105 :type '(repeat string)
106 :group 'winner)
107
108
109
a14fb2b1 110
4a81d892 111\f;;;; Saving old configurations (internal variables and subroutines)
a14fb2b1 112
2a92dc25 113
4a81d892 114;;; Current configuration
ff287a27 115
4a81d892
RS
116;; List the windows according to their edges.
117(defun winner-sorted-window-list ()
118 (sort (winner-window-list)
119 (lambda (x y)
120 (loop for a in (winner-edges x)
121 for b in (winner-edges y)
122 while (= a b)
123 finally return (< a b)))))
124
f1180544 125(defun winner-win-data ()
4a81d892
RS
126 ;; Essential properties of the windows in the selected frame.
127 (loop for win in (winner-sorted-window-list)
128 collect (cons (winner-edges win) (window-buffer win))))
f1180544 129
ff287a27 130;; This variable is updated with the current window configuration
4a81d892 131;; every time it changes.
ff287a27
RS
132(defvar winner-currents nil)
133
134;; The current configuration (+ the buffers involved).
135(defsubst winner-conf ()
4a81d892
RS
136 (cons (current-window-configuration)
137 (winner-win-data)))
138
ff287a27
RS
139
140;; Save current configuration.
4a81d892 141;; (Called below by `winner-save-old-configurations').
ff287a27
RS
142(defun winner-remember ()
143 (let ((entry (assq (selected-frame) winner-currents)))
144 (if entry (setcdr entry (winner-conf))
145 (push (cons (selected-frame) (winner-conf))
146 winner-currents))))
147
148;; Consult `winner-currents'.
149(defun winner-configuration (&optional frame)
150 (or (cdr (assq (or frame (selected-frame)) winner-currents))
151 (letf (((selected-frame) frame))
152 (winner-conf))))
153
2a92dc25
RS
154
155
4a81d892
RS
156;;; Saved configurations
157
2a92dc25
RS
158;; This variable contains the window cofiguration rings.
159;; The key in this alist is the frame.
1ffece3a 160(defvar winner-ring-alist nil)
a14fb2b1 161
2a92dc25 162;; Find the right ring. If it does not exist, create one.
1ffece3a
RS
163(defsubst winner-ring (frame)
164 (or (cdr (assq frame winner-ring-alist))
165 (progn
2a92dc25
RS
166 (let ((ring (make-ring winner-ring-size)))
167 (ring-insert ring (winner-configuration frame))
168 (push (cons frame ring) winner-ring-alist)
169 ring))))
170
ff287a27
RS
171\f;; If the same command is called several times in a row,
172;; we only save one window configuration.
173(defvar winner-last-command nil)
a14fb2b1 174
ff287a27
RS
175;; Frames affected by the previous command.
176(defvar winner-last-frames nil)
a14fb2b1 177
59c2dcf4
GM
178
179(defun winner-equal (a b)
4a81d892
RS
180 "Check whether two Winner configurations (as produced by
181`winner-conf') are equal."
182 (equal (cdr a) (cdr b)))
59c2dcf4
GM
183
184
ff287a27 185;; Save the current window configuration, if it has changed.
4a81d892 186;; If so return frame, otherwise return nil.
ff287a27
RS
187(defun winner-insert-if-new (frame)
188 (unless (or (memq frame winner-last-frames)
189 (eq this-command 'winner-redo))
190 (let ((conf (winner-configuration frame))
191 (ring (winner-ring frame)))
192 (when (and (not (ring-empty-p ring))
193 (winner-equal conf (ring-ref ring 0)))
4a81d892
RS
194 ;; When the previous configuration was very similar,
195 ;; keep only the latest.
ff287a27
RS
196 (ring-remove ring 0))
197 (ring-insert ring conf)
198 (push frame winner-last-frames)
199 frame)))
200
4a81d892
RS
201
202
203;;; Hooks
204
ff287a27
RS
205;; Frames affected by the current command.
206(defvar winner-modified-list nil)
207
208;; Called whenever the window configuration changes
209;; (a `window-configuration-change-hook').
1ffece3a 210(defun winner-change-fun ()
4a81d892
RS
211 (unless (or (memq (selected-frame) winner-modified-list)
212 (/= 0 (minibuffer-depth)))
2a92dc25 213 (push (selected-frame) winner-modified-list)))
a14fb2b1 214
4a81d892
RS
215;; A `post-command-hook' for emacsen with
216;; `window-configuration-change-hook'.
ff287a27 217(defun winner-save-old-configurations ()
4a81d892
RS
218 (when (zerop (minibuffer-depth))
219 (unless (eq this-command winner-last-command)
220 (setq winner-last-frames nil)
221 (setq winner-last-command this-command))
222 (dolist (frame winner-modified-list)
223 (winner-insert-if-new frame))
224 (setq winner-modified-list nil)
225 (winner-remember)))
226
227;; A `minibuffer-setup-hook'.
2a92dc25 228(defun winner-save-unconditionally ()
ff287a27
RS
229 (unless (eq this-command winner-last-command)
230 (setq winner-last-frames nil)
231 (setq winner-last-command this-command))
232 (winner-insert-if-new (selected-frame))
233 (winner-remember))
2a92dc25 234
4a81d892
RS
235;; A `post-command-hook' for other emacsen.
236;; Also called by `winner-undo' before "undoing".
237(defun winner-save-conditionally ()
238 (when (zerop (minibuffer-depth))
239 (winner-save-unconditionally)))
2a92dc25 240
2a92dc25 241
2a92dc25 242
ff287a27
RS
243\f;;;; Restoring configurations
244
245;; Works almost as `set-window-configuration',
4a81d892
RS
246;; but does not change the contents or the size of the minibuffer,
247;; and tries to preserve the selected window.
ff287a27 248(defun winner-set-conf (winconf)
4a81d892
RS
249 (let* ((miniwin (minibuffer-window))
250 (chosen (selected-window))
251 (minisize (window-height miniwin)))
252 (letf (((window-buffer miniwin))
253 ((window-point miniwin)))
254 (set-window-configuration winconf))
255 (cond
256 ((window-live-p chosen) (select-window chosen))
257 ((window-minibuffer-p (selected-window))
258 (other-window 1)))
f1180544 259 (when (/= minisize (window-height miniwin))
4a81d892
RS
260 (letf (((selected-window) miniwin) )
261 (setf (window-height) minisize)))))
262
ff287a27
RS
263
264
265(defvar winner-point-alist nil)
266;; `set-window-configuration' restores old points and marks. This is
267;; not what we want, so we make a list of the "real" (i.e. new) points
268;; and marks before undoing window configurations.
269;;
270;; Format of entries: (buffer (mark . mark-active) (window . point) ..)
271
272(defun winner-make-point-alist ()
273 (letf (((current-buffer)))
274 (loop with alist
4a81d892 275 for win in (winner-window-list)
f1180544 276 for entry =
4a81d892
RS
277 (or (assq (window-buffer win) alist)
278 (car (push (list (set-buffer (window-buffer win))
279 (cons (mark t) (winner-active-region)))
280 alist)))
281 do (push (cons win (window-point win))
282 (cddr entry))
ff287a27
RS
283 finally return alist)))
284
ff287a27
RS
285(defun winner-get-point (buf win)
286 ;; Consult (and possibly extend) `winner-point-alist'.
4a81d892 287 ;; Returns nil iff buf no longer exists.
ff287a27
RS
288 (when (buffer-name buf)
289 (let ((entry (assq buf winner-point-alist)))
290 (cond
291 (entry
292 (or (cdr (assq win (cddr entry)))
293 (cdr (assq nil (cddr entry)))
294 (letf (((current-buffer) buf))
295 (push (cons nil (point)) (cddr entry))
296 (point))))
297 (t (letf (((current-buffer) buf))
298 (push (list buf
299 (cons (mark t) (winner-active-region))
300 (cons nil (point)))
301 winner-point-alist)
302 (point)))))))
303
4a81d892
RS
304\f;; Make sure point does not end up in the minibuffer and delete
305;; windows displaying dead or boring buffers
306;; (c.f. `winner-boring-buffers'). Return nil iff all the windows
307;; should be deleted. Preserve correct points and marks.
1ffece3a 308(defun winner-set (conf)
4a81d892 309 ;; For the format of `conf', see `winner-conf'.
ff287a27 310 (let* ((buffers nil)
4a81d892
RS
311 (alive
312 ;; Possibly update `winner-point-alist'
313 (loop for buf in (mapcar 'cdr (cdr conf))
ff287a27
RS
314 for pos = (winner-get-point buf nil)
315 if (and pos (not (memq buf buffers)))
316 do (push buf buffers)
317 collect pos)))
2a92dc25 318 (winner-set-conf (car conf))
4a81d892
RS
319 (let (xwins) ; to be deleted
320
321 ;; Restore points
322 (dolist (win (winner-sorted-window-list))
323 (unless (and (pop alive)
324 (setf (window-point win)
325 (winner-get-point (window-buffer win) win))
326 (not (member (buffer-name (window-buffer win))
327 winner-boring-buffers)))
328 (push win xwins))) ; delete this window
329
330 ;; Restore marks
ff287a27 331 (letf (((current-buffer)))
f1180544 332 (loop for buf in buffers
ff287a27
RS
333 for entry = (cadr (assq buf winner-point-alist))
334 do (progn (set-buffer buf)
335 (set-mark (car entry))
336 (setf (winner-active-region) (cdr entry)))))
4a81d892 337 ;; Delete windows, whose buffers are dead or boring.
ff287a27
RS
338 ;; Return t if this is still a possible configuration.
339 (or (null xwins)
4a81d892
RS
340 (progn
341 (mapc 'delete-window (cdr xwins)) ; delete all but one
342 (unless (one-window-p t)
343 (delete-window (car xwins))
344 t))))))
ff287a27
RS
345
346
347
348;;;; Winner mode (a minor mode)
a14fb2b1 349
4f850b26
DL
350(defcustom winner-mode-hook nil
351 "Functions to run whenever Winner mode is turned on."
352 :type 'hook
78900bc3 353 :group 'winner)
a14fb2b1 354
78900bc3 355(defcustom winner-mode-leave-hook nil
4f850b26
DL
356 "Functions to run whenever Winner mode is turned off."
357 :type 'hook
78900bc3 358 :group 'winner)
1ffece3a 359
6babdce9
SM
360(defvar winner-mode-map
361 (let ((map (make-sparse-keymap)))
362 (define-key map [(control c) left] 'winner-undo)
363 (define-key map [(control c) right] 'winner-redo)
364 map)
365 "Keymap for Winner mode.")
21f3d1d3 366
4a81d892 367;; Check if `window-configuration-change-hook' is working.
2a92dc25
RS
368(defun winner-hook-installed-p ()
369 (save-window-excursion
370 (let ((winner-var nil)
371 (window-configuration-change-hook
372 '((lambda () (setq winner-var t)))))
373 (split-window)
374 winner-var)))
375
63f6b2c4
DL
376\f
377;;;###autoload
a14fb2b1
RS
378(defun winner-mode (&optional arg)
379 "Toggle Winner mode.
380With arg, turn Winner mode on if and only if arg is positive."
381 (interactive "P")
382 (let ((on-p (if arg (> (prefix-numeric-value arg) 0)
383 (not winner-mode))))
384 (cond
1ffece3a 385 ;; Turn mode on
f1180544 386 (on-p
1ffece3a 387 (setq winner-mode t)
2a92dc25
RS
388 (cond
389 ((winner-hook-installed-p)
390 (add-hook 'window-configuration-change-hook 'winner-change-fun)
6babdce9 391 (add-hook 'post-command-hook 'winner-save-old-configurations))
4a81d892
RS
392 (t (add-hook 'post-command-hook 'winner-save-conditionally)))
393 (add-hook 'minibuffer-setup-hook 'winner-save-unconditionally)
1ffece3a 394 (setq winner-modified-list (frame-list))
ff287a27 395 (winner-save-old-configurations)
4a81d892
RS
396 (run-hooks 'winner-mode-hook)
397 (when (interactive-p) (message "Winner mode enabled")))
1ffece3a
RS
398 ;; Turn mode off
399 (winner-mode
400 (setq winner-mode nil)
2a92dc25 401 (remove-hook 'window-configuration-change-hook 'winner-change-fun)
ff287a27 402 (remove-hook 'post-command-hook 'winner-save-old-configurations)
4a81d892
RS
403 (remove-hook 'post-command-hook 'winner-save-conditionally)
404 (remove-hook 'minibuffer-setup-hook 'winner-save-unconditionally)
405 (run-hooks 'winner-mode-leave-hook)
406 (when (interactive-p) (message "Winner mode disabled"))))))
a14fb2b1 407
4a81d892 408;; Inspired by undo (simple.el)
e0a81650 409
ff287a27
RS
410(defvar winner-undo-frame nil)
411
e0a81650
RS
412(defvar winner-pending-undo-ring nil
413 "The ring currently used by winner undo.")
414(defvar winner-undo-counter nil)
415(defvar winner-undone-data nil) ; There confs have been passed.
416
ff287a27 417(defun winner-undo ()
1ffece3a 418 "Switch back to an earlier window configuration saved by Winner mode.
ff287a27
RS
419In other words, \"undo\" changes in window configuration."
420 (interactive)
1ffece3a
RS
421 (cond
422 ((not winner-mode) (error "Winner mode is turned off"))
ff287a27
RS
423 (t (unless (and (eq last-command 'winner-undo)
424 (eq winner-undo-frame (selected-frame)))
4a81d892 425 (winner-save-conditionally) ; current configuration->stack
ff287a27
RS
426 (setq winner-undo-frame (selected-frame))
427 (setq winner-point-alist (winner-make-point-alist))
428 (setq winner-pending-undo-ring (winner-ring (selected-frame)))
429 (setq winner-undo-counter 0)
430 (setq winner-undone-data (list (winner-win-data))))
431 (incf winner-undo-counter) ; starting at 1
432 (when (and (winner-undo-this)
433 (not (window-minibuffer-p (selected-window))))
434 (message "Winner undo (%d / %d)"
435 winner-undo-counter
436 (1- (ring-length winner-pending-undo-ring)))))))
f1180544
JB
437
438
439
4a81d892 440\f(defun winner-undo-this () ; The heart of winner undo.
f1180544 441 (loop
ff287a27
RS
442 (cond
443 ((>= winner-undo-counter (ring-length winner-pending-undo-ring))
444 (message "No further window configuration undo information")
445 (return nil))
f1180544 446
ff287a27
RS
447 ((and ; If possible configuration
448 (winner-set (ring-ref winner-pending-undo-ring
449 winner-undo-counter))
4a81d892 450 ; .. and new configuration
ff287a27
RS
451 (let ((data (winner-win-data)))
452 (and (not (member data winner-undone-data))
453 (push data winner-undone-data))))
4a81d892
RS
454 (return t)) ; .. then everything is fine.
455 (t ;; Otherwise, discharge it (and try the next one).
ff287a27 456 (ring-remove winner-pending-undo-ring winner-undo-counter)))))
f1180544 457
ff287a27
RS
458
459(defun winner-redo () ; If you change your mind.
1ffece3a
RS
460 "Restore a more recent window configuration saved by Winner mode."
461 (interactive)
462 (cond
463 ((eq last-command 'winner-undo)
1ffece3a 464 (winner-set
4a81d892
RS
465 (if (zerop (minibuffer-depth))
466 (ring-remove winner-pending-undo-ring 0)
467 (ring-ref winner-pending-undo-ring 0)))
ff287a27
RS
468 (unless (eq (selected-window) (minibuffer-window))
469 (message "Winner undid undo")))
1ffece3a 470 (t (error "Previous command was not a winner-undo"))))
4a81d892 471
ff287a27 472;;; To be evaluated when the package is loaded:
2a92dc25 473
a14fb2b1
RS
474(unless (or (assq 'winner-mode minor-mode-map-alist)
475 winner-dont-bind-my-keys)
476 (push (cons 'winner-mode winner-mode-map)
477 minor-mode-map-alist))
478
a14fb2b1 479(provide 'winner)
6babdce9 480;; arch-tag: 686d1c1b-010e-42ca-a192-b5685112418f
21f3d1d3 481;;; winner.el ends here