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