]> code.delx.au - gnu-emacs/blob - lisp/winner.el
; Revert "Use eldoc-documentation-functions"
[gnu-emacs] / lisp / winner.el
1 ;;; winner.el --- Restore old window configurations
2
3 ;; Copyright (C) 1997-1998, 2001-2016 Free Software Foundation, Inc.
4
5 ;; Author: Ivar Rummelhoff <ivarru@math.uio.no>
6 ;; Created: 27 Feb 1997
7 ;; Keywords: convenience frames
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
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
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25
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
28 ;; windows) so that the changes can be "undone" using the command
29 ;; `winner-undo'. By default this one is bound to the key sequence
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
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
36 ;; Winner mode was improved August 1998.
37 ;; Further improvements February 2002.
38
39 ;;; Code:
40
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 `(if ,store (activate-mark) (deactivate-mark))))))
49 (region-active-p))
50
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))))
57
58 (require 'ring)
59
60 (defgroup winner nil
61 "Restoring window configurations."
62 :group 'windows)
63
64 (defcustom winner-dont-bind-my-keys nil
65 "Non-nil means do not bind keys in Winner mode."
66 :type 'boolean
67 :group 'winner)
68
69 (defcustom winner-ring-size 200
70 "Maximum number of stored window configurations per frame."
71 :type 'integer
72 :group 'winner)
73
74 (defcustom winner-boring-buffers '("*Completions*")
75 "List of buffer names whose windows `winner-undo' will not restore.
76 You 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
82 \f
83 ;;;; Saving old configurations (internal variables and subroutines)
84
85
86 ;;; Current configuration
87
88 ;; List the windows according to their edges.
89 (defun winner-sorted-window-list ()
90 (sort (winner-window-list)
91 (lambda (x y)
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)))))
96
97 (defun winner-win-data ()
98 ;; Essential properties of the windows in the selected frame.
99 (cl-loop for win in (winner-sorted-window-list)
100 collect (cons (winner-edges win) (window-buffer win))))
101
102 ;; This variable is updated with the current window configuration
103 ;; every time it changes.
104 (defvar winner-currents nil)
105
106 ;; The current configuration (+ the buffers involved).
107 (defsubst winner-conf ()
108 (cons (current-window-configuration)
109 (winner-win-data)))
110
111
112 ;; Save current configuration.
113 ;; (Called below by `winner-save-old-configurations').
114 (defun winner-remember ()
115 (setf (alist-get (selected-frame) winner-currents) (winner-conf)))
116
117 ;; Consult `winner-currents'.
118 (defun winner-configuration (&optional frame)
119 (or (cdr (assq (or frame (selected-frame)) winner-currents))
120 (with-selected-frame frame
121 (winner-conf))))
122
123
124
125 ;;; Saved configurations
126
127 ;; This variable contains the window configuration rings.
128 ;; The key in this alist is the frame.
129 (defvar winner-ring-alist nil)
130
131 ;; Find the right ring. If it does not exist, create one.
132 (defsubst winner-ring (frame)
133 (or (cdr (assq frame winner-ring-alist))
134 (let ((ring (make-ring winner-ring-size)))
135 (ring-insert ring (winner-configuration frame))
136 (push (cons frame ring) winner-ring-alist)
137 ring)))
138
139 \f
140 ;; If the same command is called several times in a row,
141 ;; we only save one window configuration.
142 (defvar winner-last-command nil)
143
144 ;; Frames affected by the previous command.
145 (defvar winner-last-frames nil)
146
147
148 (defsubst winner-equal (a b)
149 "Check whether two Winner configurations (as produced by
150 `winner-conf') are equal."
151 (equal (cdr a) (cdr b)))
152
153
154 ;; Save the current window configuration, if it has changed.
155 ;; If so return frame, otherwise return nil.
156 (defun winner-insert-if-new (frame)
157 (unless (or (memq frame winner-last-frames)
158 (eq this-command 'winner-redo))
159 (let ((conf (winner-configuration frame))
160 (ring (winner-ring frame)))
161 (when (and (not (ring-empty-p ring))
162 (winner-equal conf (ring-ref ring 0)))
163 ;; When the previous configuration was very similar,
164 ;; keep only the latest.
165 (ring-remove ring 0))
166 (ring-insert ring conf)
167 (push frame winner-last-frames)
168 frame)))
169
170
171
172 ;;; Hooks
173
174 ;; Frames affected by the current command.
175 (defvar winner-modified-list nil)
176
177 ;; Called whenever the window configuration changes
178 ;; (a `window-configuration-change-hook').
179 (defun winner-change-fun ()
180
181 ;; Cull dead frames.
182 (setq winner-modified-list
183 (cl-loop for frame in winner-modified-list
184 if (frame-live-p frame) collect frame))
185
186 (unless (or (memq (selected-frame) winner-modified-list)
187 (/= 0 (minibuffer-depth)))
188 (push (selected-frame) winner-modified-list)))
189
190 ;; A `post-command-hook' for emacsen with
191 ;; `window-configuration-change-hook'.
192 (defun winner-save-old-configurations ()
193 (when (zerop (minibuffer-depth))
194 (unless (eq this-command winner-last-command)
195 (setq winner-last-frames nil)
196 (setq winner-last-command this-command))
197 (dolist (frame winner-modified-list)
198 (winner-insert-if-new frame))
199 (setq winner-modified-list nil)
200 (winner-remember)))
201
202 ;; A `minibuffer-setup-hook'.
203 (defun winner-save-unconditionally ()
204 (unless (eq this-command winner-last-command)
205 (setq winner-last-frames nil)
206 (setq winner-last-command this-command))
207 (winner-insert-if-new (selected-frame))
208 (winner-remember))
209
210 ;; A `post-command-hook' for other emacsen.
211 ;; Also called by `winner-undo' before "undoing".
212 (defun winner-save-conditionally ()
213 (when (zerop (minibuffer-depth))
214 (winner-save-unconditionally)))
215
216
217
218 \f
219 ;;;; Restoring configurations
220
221 ;; Works almost as `set-window-configuration',
222 ;; but does not change the contents or the size of the minibuffer,
223 ;; and tries to preserve the selected window.
224 (defun winner-set-conf (winconf)
225 (let* ((miniwin (minibuffer-window))
226 (chosen (selected-window))
227 (minisize (window-height miniwin)))
228 (cl-letf (((window-buffer miniwin))
229 ((window-point miniwin)))
230 (set-window-configuration winconf))
231 (cond
232 ((window-live-p chosen) (select-window chosen))
233 ((window-minibuffer-p) (other-window 1)))
234 (when (/= minisize (window-height miniwin))
235 (with-selected-window miniwin
236 (setf (window-height) minisize)))))
237
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 ()
248 (save-current-buffer
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)))
259
260 (defun winner-get-point (buf win)
261 ;; Consult (and possibly extend) `winner-point-alist'.
262 ;; Returns nil if buf no longer exists.
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)))
269 (with-current-buffer buf
270 (push (cons nil (point)) (cddr entry))
271 (point))))
272 (t (with-current-buffer buf
273 (push (list buf
274 (cons (mark t) (winner-active-region))
275 (cons nil (point)))
276 winner-point-alist)
277 (point)))))))
278
279 \f
280 ;; Make sure point does not end up in the minibuffer and delete
281 ;; windows displaying dead or boring buffers
282 ;; (c.f. `winner-boring-buffers'). Return nil if all the windows
283 ;; should be deleted. Preserve correct points and marks.
284 (defun winner-set (conf)
285 ;; For the format of `conf', see `winner-conf'.
286 (let* ((buffers nil)
287 (alive
288 ;; Possibly update `winner-point-alist'
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)))
294 (winner-set-conf (car conf))
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
307 (save-current-buffer
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)))))
313 ;; Delete windows, whose buffers are dead or boring.
314 ;; Return t if this is still a possible configuration.
315 (or (null xwins)
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))))))
321
322
323
324 ;;;; Winner mode (a minor mode)
325
326 (defcustom winner-mode-hook nil
327 "Functions to run whenever Winner mode is turned on or off."
328 :type 'hook
329 :group 'winner)
330
331 (define-obsolete-variable-alias 'winner-mode-leave-hook
332 'winner-mode-off-hook "24.3")
333
334 (defcustom winner-mode-off-hook nil
335 "Functions to run whenever Winner mode is turned off."
336 :type 'hook
337 :group 'winner)
338
339 (defvar winner-mode-map
340 (let ((map (make-sparse-keymap)))
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))
344 map)
345 "Keymap for Winner mode.")
346
347 \f
348 ;;;###autoload
349 (define-minor-mode winner-mode
350 "Toggle Winner mode on or off.
351 With a prefix argument ARG, enable Winner mode if ARG is
352 positive, and disable it otherwise. If called from Lisp, enable
353 the mode if ARG is omitted or nil, and toggle it if ARG is ‘toggle’.
354
355 Winner mode is a global minor mode that records the changes in
356 the window configuration (i.e. how the frames are partitioned
357 into windows) so that the changes can be \"undone\" using the
358 command `winner-undo'. By default this one is bound to the key
359 sequence `C-c <left>'. If you change your mind (while undoing),
360 you can press `C-c <right>' (calling `winner-redo')."
361 :global t
362 (if winner-mode
363 (progn
364 (add-hook 'window-configuration-change-hook 'winner-change-fun)
365 (add-hook 'post-command-hook 'winner-save-old-configurations)
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 'minibuffer-setup-hook 'winner-save-unconditionally)))
372
373 ;; Inspired by undo (simple.el)
374
375 (defvar winner-undo-frame nil)
376
377 (defvar winner-pending-undo-ring nil
378 "The ring currently used by `winner-undo'.")
379 (defvar winner-undo-counter nil)
380 (defvar winner-undone-data nil) ; There confs have been passed.
381
382 (defun winner-undo ()
383 "Switch back to an earlier window configuration saved by Winner mode.
384 In other words, \"undo\" changes in window configuration."
385 (interactive)
386 (cond
387 ((not winner-mode) (error "Winner mode is turned off"))
388 (t (unless (and (eq last-command 'winner-undo)
389 (eq winner-undo-frame (selected-frame)))
390 (winner-save-conditionally) ; current configuration->stack
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))))
396 (cl-incf winner-undo-counter) ; starting at 1
397 (when (and (winner-undo-this)
398 (not (window-minibuffer-p)))
399 (message "Winner undo (%d / %d)"
400 winner-undo-counter
401 (1- (ring-length winner-pending-undo-ring)))))))
402
403
404
405 \f
406 (defun winner-undo-this () ; The heart of winner undo.
407 (cl-loop
408 (cond
409 ((>= winner-undo-counter (ring-length winner-pending-undo-ring))
410 (message "No further window configuration undo information")
411 (cl-return nil))
412
413 ((and ; If possible configuration
414 (winner-set (ring-ref winner-pending-undo-ring
415 winner-undo-counter))
416 ; .. and new configuration
417 (let ((data (winner-win-data)))
418 (and (not (member data winner-undone-data))
419 (push data winner-undone-data))))
420 (cl-return t)) ; .. then everything is fine.
421 (t ;; Otherwise, discharge it (and try the next one).
422 (ring-remove winner-pending-undo-ring winner-undo-counter)))))
423
424
425 (defun winner-redo () ; If you change your mind.
426 "Restore a more recent window configuration saved by Winner mode."
427 (interactive)
428 (cond
429 ((eq last-command 'winner-undo)
430 (winner-set
431 (if (zerop (minibuffer-depth))
432 (ring-remove winner-pending-undo-ring 0)
433 (ring-ref winner-pending-undo-ring 0)))
434 (unless (eq (selected-window) (minibuffer-window))
435 (message "Winner undid undo")))
436 (t (user-error "Previous command was not a `winner-undo'"))))
437
438 (provide 'winner)
439 ;;; winner.el ends here