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