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