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