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