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