* net/tramp-sh.el (tramp-remote-process-environment): Add "TMOUT=0".
[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))
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
34cf6f39 67 "Non-nil means do not bind keys 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)))
34cf6f39
SM
341 (unless winner-dont-bind-my-keys
342 (define-key map [(control c) left] 'winner-undo)
343 (define-key map [(control c) right] 'winner-redo))
6babdce9
SM
344 map)
345 "Keymap for Winner mode.")
21f3d1d3 346
4a81d892 347;; Check if `window-configuration-change-hook' is working.
2a92dc25
RS
348(defun winner-hook-installed-p ()
349 (save-window-excursion
350 (let ((winner-var nil)
351 (window-configuration-change-hook
352 '((lambda () (setq winner-var t)))))
353 (split-window)
354 winner-var)))
355
63f6b2c4
DL
356\f
357;;;###autoload
5cc2e639
GM
358(define-minor-mode winner-mode nil :global t ; let d-m-m make the doc
359 (if winner-mode
360 (progn
361 (if (winner-hook-installed-p)
362 (progn
363 (add-hook 'window-configuration-change-hook 'winner-change-fun)
364 (add-hook 'post-command-hook 'winner-save-old-configurations))
365 (add-hook 'post-command-hook 'winner-save-conditionally))
366 (add-hook 'minibuffer-setup-hook 'winner-save-unconditionally)
367 (setq winner-modified-list (frame-list))
368 (winner-save-old-configurations))
369 (remove-hook 'window-configuration-change-hook 'winner-change-fun)
370 (remove-hook 'post-command-hook 'winner-save-old-configurations)
371 (remove-hook 'post-command-hook 'winner-save-conditionally)
372 (remove-hook 'minibuffer-setup-hook 'winner-save-unconditionally)))
a14fb2b1 373
4a81d892 374;; Inspired by undo (simple.el)
e0a81650 375
ff287a27
RS
376(defvar winner-undo-frame nil)
377
e0a81650 378(defvar winner-pending-undo-ring nil
f7285f0f 379 "The ring currently used by `winner-undo'.")
e0a81650
RS
380(defvar winner-undo-counter nil)
381(defvar winner-undone-data nil) ; There confs have been passed.
382
ff287a27 383(defun winner-undo ()
1ffece3a 384 "Switch back to an earlier window configuration saved by Winner mode.
ff287a27
RS
385In other words, \"undo\" changes in window configuration."
386 (interactive)
1ffece3a
RS
387 (cond
388 ((not winner-mode) (error "Winner mode is turned off"))
ff287a27
RS
389 (t (unless (and (eq last-command 'winner-undo)
390 (eq winner-undo-frame (selected-frame)))
4a81d892 391 (winner-save-conditionally) ; current configuration->stack
ff287a27
RS
392 (setq winner-undo-frame (selected-frame))
393 (setq winner-point-alist (winner-make-point-alist))
394 (setq winner-pending-undo-ring (winner-ring (selected-frame)))
395 (setq winner-undo-counter 0)
396 (setq winner-undone-data (list (winner-win-data))))
a464a6c7 397 (cl-incf winner-undo-counter) ; starting at 1
ff287a27
RS
398 (when (and (winner-undo-this)
399 (not (window-minibuffer-p (selected-window))))
400 (message "Winner undo (%d / %d)"
401 winner-undo-counter
402 (1- (ring-length winner-pending-undo-ring)))))))
f1180544
JB
403
404
405
f7285f0f
JB
406\f
407(defun winner-undo-this () ; The heart of winner undo.
a464a6c7 408 (cl-loop
ff287a27
RS
409 (cond
410 ((>= winner-undo-counter (ring-length winner-pending-undo-ring))
411 (message "No further window configuration undo information")
a464a6c7 412 (cl-return nil))
f1180544 413
ff287a27
RS
414 ((and ; If possible configuration
415 (winner-set (ring-ref winner-pending-undo-ring
416 winner-undo-counter))
4a81d892 417 ; .. and new configuration
ff287a27
RS
418 (let ((data (winner-win-data)))
419 (and (not (member data winner-undone-data))
420 (push data winner-undone-data))))
a464a6c7 421 (cl-return t)) ; .. then everything is fine.
4a81d892 422 (t ;; Otherwise, discharge it (and try the next one).
ff287a27 423 (ring-remove winner-pending-undo-ring winner-undo-counter)))))
f1180544 424
ff287a27
RS
425
426(defun winner-redo () ; If you change your mind.
1ffece3a
RS
427 "Restore a more recent window configuration saved by Winner mode."
428 (interactive)
429 (cond
430 ((eq last-command 'winner-undo)
1ffece3a 431 (winner-set
4a81d892
RS
432 (if (zerop (minibuffer-depth))
433 (ring-remove winner-pending-undo-ring 0)
434 (ring-ref winner-pending-undo-ring 0)))
ff287a27
RS
435 (unless (eq (selected-window) (minibuffer-window))
436 (message "Winner undid undo")))
f7285f0f 437 (t (error "Previous command was not a `winner-undo'"))))
4a81d892 438
a14fb2b1 439(provide 'winner)
21f3d1d3 440;;; winner.el ends here