Add comments on coding-category-utf-8,
[bpt/emacs.git] / lisp / winner.el
... / ...
CommitLineData
1;;; winner.el --- Restore old window configurations
2
3;; Copyright (C) 1997, 1998 Free Software Foundation. Inc.
4
5;; Author: Ivar Rummelhoff <ivarr@ifi.uio.no>
6;; Maintainer: Ivar Rummelhoff <ivarr@ifi.uio.no>
7;; Created: 27 Feb 1997
8;; Time-stamp: <1998-08-21 19:51:02 ivarr>
9;; Keywords: windows
10
11;; This file is part of GNU Emacs.
12
13;; GNU Emacs is free software; you can redistribute it and/or modify
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
18;; GNU Emacs is distributed in the hope that it will be useful,
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:
29
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
32;; windows) so that the changes can be "undone" using the command
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
40;; Winner mode was improved august 1998.
41
42;;; Code:
43
44(eval-when-compile
45 (require 'cl)
46
47 (cond
48 ((eq (aref (emacs-version) 0) ?X)
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)))) )
58
59(require 'ring)
60
61(when (fboundp 'defgroup)
62 (defgroup winner nil
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
70\f;;;###autoload
71(defcustom winner-mode nil
72 "Toggle winner-mode.
73Setting this variable directly does not take effect;
74use either \\[customize] or the function `winner-mode'."
75 :set #'(lambda (symbol value)
76 (winner-mode (or value 0)))
77 :initialize 'custom-initialize-default
78 :type 'boolean
79 :group 'winner
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."
84 :type 'boolean
85 :group 'winner)
86
87(defcustom winner-ring-size 200
88 "Maximum number of stored window configurations per frame."
89 :type 'integer
90 :group 'winner)
91
92
93
94
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
125
126
127;; This variable contains the window cofiguration 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 (progn
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
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)
143
144;; Frames affected by the previous command.
145(defvar winner-last-frames nil)
146
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').
166(defun winner-change-fun ()
167 (unless (memq (selected-frame) winner-modified-list)
168 (push (selected-frame) winner-modified-list)))
169
170
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".
188(defun winner-save-unconditionally ()
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))
194
195
196
197
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
266;; delete windows displaying dead buffers. Return nil
267;; if and only if all the windows should have been deleted.
268;; Do not move neither points nor marks.
269(defun winner-set (conf)
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)))
277 (winner-set-conf (car conf))
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)
307
308(defcustom winner-mode-hook nil
309 "Functions to run whenever Winner mode is turned on."
310 :type 'hook
311 :group 'winner)
312
313(defcustom winner-mode-leave-hook nil
314 "Functions to run whenever Winner mode is turned off."
315 :type 'hook
316 :group 'winner)
317
318(defvar winner-mode-map nil "Keymap for Winner mode.")
319
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
329\f;;;###autoload
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
337 ;; Turn mode on
338 (on-p
339 (setq winner-mode t)
340 (cond
341 ((winner-hook-installed-p)
342 (add-hook 'window-configuration-change-hook 'winner-change-fun)
343 (add-hook 'post-command-hook 'winner-save-old-configurations))
344 (t (add-hook 'post-command-hook 'winner-save-unconditionally)))
345 (setq winner-modified-list (frame-list))
346 (winner-save-old-configurations)
347 (run-hooks 'winner-mode-hook))
348 ;; Turn mode off
349 (winner-mode
350 (setq winner-mode nil)
351 (remove-hook 'window-configuration-change-hook 'winner-change-fun)
352 (remove-hook 'post-command-hook 'winner-save-old-configurations)
353 (remove-hook 'post-command-hook 'winner-save-unconditionally)
354 (run-hooks 'winner-mode-leave-hook)))
355 (force-mode-line-update)))
356
357\f;; Inspired by undo (simple.el)
358
359(defvar winner-undo-frame nil)
360
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
366(defun winner-undo ()
367 "Switch back to an earlier window configuration saved by Winner mode.
368In other words, \"undo\" changes in window configuration."
369 (interactive)
370 (cond
371 ((not winner-mode) (error "Winner mode is turned off"))
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.
416 "Restore a more recent window configuration saved by Winner mode."
417 (interactive)
418 (cond
419 ((eq last-command 'winner-undo)
420 (winner-set
421 (ring-remove winner-pending-undo-ring 0))
422 (unless (eq (selected-window) (minibuffer-window))
423 (message "Winner undid undo")))
424 (t (error "Previous command was not a winner-undo"))))
425\f
426;;; To be evaluated when the package is loaded:
427
428(if (fboundp 'compare-window-configurations)
429 (defalias 'winner-equal 'compare-window-configurations)
430 (defalias 'winner-equal 'equal))
431
432(unless winner-mode-map
433 (setq winner-mode-map (make-sparse-keymap))
434 (define-key winner-mode-map [(control x) left] 'winner-undo)
435 (define-key winner-mode-map [(control x) right] 'winner-redo))
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
447;;; winner.el ends here