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