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