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