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