remove `declare' macro
[bpt/emacs.git] / lisp / winner.el
CommitLineData
ff287a27 1;;; winner.el --- Restore old window configurations
a14fb2b1 2
ba318903 3;; Copyright (C) 1997-1998, 2001-2014 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))
290d5b58 230 ((window-minibuffer-p) (other-window 1)))
f1180544 231 (when (/= minisize (window-height miniwin))
2ee3d7f0 232 (with-selected-window miniwin
4a81d892
RS
233 (setf (window-height) minisize)))))
234
ff287a27
RS
235
236
237(defvar winner-point-alist nil)
238;; `set-window-configuration' restores old points and marks. This is
239;; not what we want, so we make a list of the "real" (i.e. new) points
240;; and marks before undoing window configurations.
241;;
242;; Format of entries: (buffer (mark . mark-active) (window . point) ..)
243
244(defun winner-make-point-alist ()
2ee3d7f0 245 (save-current-buffer
a464a6c7
SM
246 (cl-loop with alist
247 for win in (winner-window-list)
248 for entry =
249 (or (assq (window-buffer win) alist)
250 (car (push (list (set-buffer (window-buffer win))
251 (cons (mark t) (winner-active-region)))
252 alist)))
253 do (push (cons win (window-point win))
254 (cddr entry))
255 finally return alist)))
ff287a27 256
ff287a27
RS
257(defun winner-get-point (buf win)
258 ;; Consult (and possibly extend) `winner-point-alist'.
5453fa41 259 ;; Returns nil if buf no longer exists.
ff287a27
RS
260 (when (buffer-name buf)
261 (let ((entry (assq buf winner-point-alist)))
262 (cond
263 (entry
264 (or (cdr (assq win (cddr entry)))
265 (cdr (assq nil (cddr entry)))
2ee3d7f0 266 (with-current-buffer buf
ff287a27
RS
267 (push (cons nil (point)) (cddr entry))
268 (point))))
2ee3d7f0 269 (t (with-current-buffer buf
ff287a27
RS
270 (push (list buf
271 (cons (mark t) (winner-active-region))
272 (cons nil (point)))
273 winner-point-alist)
274 (point)))))))
275
f7285f0f
JB
276\f
277;; Make sure point does not end up in the minibuffer and delete
4a81d892 278;; windows displaying dead or boring buffers
5453fa41 279;; (c.f. `winner-boring-buffers'). Return nil if all the windows
4a81d892 280;; should be deleted. Preserve correct points and marks.
1ffece3a 281(defun winner-set (conf)
4a81d892 282 ;; For the format of `conf', see `winner-conf'.
ff287a27 283 (let* ((buffers nil)
4a81d892
RS
284 (alive
285 ;; Possibly update `winner-point-alist'
a464a6c7
SM
286 (cl-loop for buf in (mapcar 'cdr (cdr conf))
287 for pos = (winner-get-point buf nil)
288 if (and pos (not (memq buf buffers)))
289 do (push buf buffers)
290 collect pos)))
2a92dc25 291 (winner-set-conf (car conf))
4a81d892
RS
292 (let (xwins) ; to be deleted
293
294 ;; Restore points
295 (dolist (win (winner-sorted-window-list))
296 (unless (and (pop alive)
297 (setf (window-point win)
298 (winner-get-point (window-buffer win) win))
299 (not (member (buffer-name (window-buffer win))
300 winner-boring-buffers)))
301 (push win xwins))) ; delete this window
302
303 ;; Restore marks
2ee3d7f0 304 (save-current-buffer
a464a6c7
SM
305 (cl-loop for buf in buffers
306 for entry = (cadr (assq buf winner-point-alist))
307 do (progn (set-buffer buf)
308 (set-mark (car entry))
309 (setf (winner-active-region) (cdr entry)))))
4a81d892 310 ;; Delete windows, whose buffers are dead or boring.
ff287a27
RS
311 ;; Return t if this is still a possible configuration.
312 (or (null xwins)
4a81d892
RS
313 (progn
314 (mapc 'delete-window (cdr xwins)) ; delete all but one
315 (unless (one-window-p t)
316 (delete-window (car xwins))
317 t))))))
ff287a27
RS
318
319
320
321;;;; Winner mode (a minor mode)
a14fb2b1 322
4f850b26 323(defcustom winner-mode-hook nil
5cc2e639 324 "Functions to run whenever Winner mode is turned on or off."
4f850b26 325 :type 'hook
78900bc3 326 :group 'winner)
a14fb2b1 327
5cc2e639
GM
328(define-obsolete-variable-alias 'winner-mode-leave-hook
329 'winner-mode-off-hook "24.3")
330
331(defcustom winner-mode-off-hook nil
4f850b26
DL
332 "Functions to run whenever Winner mode is turned off."
333 :type 'hook
78900bc3 334 :group 'winner)
1ffece3a 335
6babdce9
SM
336(defvar winner-mode-map
337 (let ((map (make-sparse-keymap)))
34cf6f39
SM
338 (unless winner-dont-bind-my-keys
339 (define-key map [(control c) left] 'winner-undo)
340 (define-key map [(control c) right] 'winner-redo))
6babdce9
SM
341 map)
342 "Keymap for Winner mode.")
21f3d1d3 343
63f6b2c4
DL
344\f
345;;;###autoload
5cc2e639
GM
346(define-minor-mode winner-mode nil :global t ; let d-m-m make the doc
347 (if winner-mode
348 (progn
9b2607e8
SM
349 (add-hook 'window-configuration-change-hook 'winner-change-fun)
350 (add-hook 'post-command-hook 'winner-save-old-configurations)
5cc2e639
GM
351 (add-hook 'minibuffer-setup-hook 'winner-save-unconditionally)
352 (setq winner-modified-list (frame-list))
353 (winner-save-old-configurations))
354 (remove-hook 'window-configuration-change-hook 'winner-change-fun)
355 (remove-hook 'post-command-hook 'winner-save-old-configurations)
5cc2e639 356 (remove-hook 'minibuffer-setup-hook 'winner-save-unconditionally)))
a14fb2b1 357
4a81d892 358;; Inspired by undo (simple.el)
e0a81650 359
ff287a27
RS
360(defvar winner-undo-frame nil)
361
e0a81650 362(defvar winner-pending-undo-ring nil
f7285f0f 363 "The ring currently used by `winner-undo'.")
e0a81650
RS
364(defvar winner-undo-counter nil)
365(defvar winner-undone-data nil) ; There confs have been passed.
366
ff287a27 367(defun winner-undo ()
1ffece3a 368 "Switch back to an earlier window configuration saved by Winner mode.
ff287a27
RS
369In other words, \"undo\" changes in window configuration."
370 (interactive)
1ffece3a
RS
371 (cond
372 ((not winner-mode) (error "Winner mode is turned off"))
ff287a27
RS
373 (t (unless (and (eq last-command 'winner-undo)
374 (eq winner-undo-frame (selected-frame)))
4a81d892 375 (winner-save-conditionally) ; current configuration->stack
ff287a27
RS
376 (setq winner-undo-frame (selected-frame))
377 (setq winner-point-alist (winner-make-point-alist))
378 (setq winner-pending-undo-ring (winner-ring (selected-frame)))
379 (setq winner-undo-counter 0)
380 (setq winner-undone-data (list (winner-win-data))))
a464a6c7 381 (cl-incf winner-undo-counter) ; starting at 1
ff287a27 382 (when (and (winner-undo-this)
290d5b58 383 (not (window-minibuffer-p)))
ff287a27
RS
384 (message "Winner undo (%d / %d)"
385 winner-undo-counter
386 (1- (ring-length winner-pending-undo-ring)))))))
f1180544
JB
387
388
389
f7285f0f
JB
390\f
391(defun winner-undo-this () ; The heart of winner undo.
a464a6c7 392 (cl-loop
ff287a27
RS
393 (cond
394 ((>= winner-undo-counter (ring-length winner-pending-undo-ring))
395 (message "No further window configuration undo information")
a464a6c7 396 (cl-return nil))
f1180544 397
ff287a27
RS
398 ((and ; If possible configuration
399 (winner-set (ring-ref winner-pending-undo-ring
400 winner-undo-counter))
4a81d892 401 ; .. and new configuration
ff287a27
RS
402 (let ((data (winner-win-data)))
403 (and (not (member data winner-undone-data))
404 (push data winner-undone-data))))
a464a6c7 405 (cl-return t)) ; .. then everything is fine.
4a81d892 406 (t ;; Otherwise, discharge it (and try the next one).
ff287a27 407 (ring-remove winner-pending-undo-ring winner-undo-counter)))))
f1180544 408
ff287a27
RS
409
410(defun winner-redo () ; If you change your mind.
1ffece3a
RS
411 "Restore a more recent window configuration saved by Winner mode."
412 (interactive)
413 (cond
414 ((eq last-command 'winner-undo)
1ffece3a 415 (winner-set
4a81d892
RS
416 (if (zerop (minibuffer-depth))
417 (ring-remove winner-pending-undo-ring 0)
418 (ring-ref winner-pending-undo-ring 0)))
ff287a27
RS
419 (unless (eq (selected-window) (minibuffer-window))
420 (message "Winner undid undo")))
f7285f0f 421 (t (error "Previous command was not a `winner-undo'"))))
4a81d892 422
a14fb2b1 423(provide 'winner)
21f3d1d3 424;;; winner.el ends here