diff options
| author | Martin Rudalics | 2011-06-19 12:17:56 +0200 |
|---|---|---|
| committer | Martin Rudalics | 2011-06-19 12:17:56 +0200 |
| commit | 9d89fec7458c978272a716c33df41f9958f7fe7f (patch) | |
| tree | bd12fefafbf8461912c1d2d793247388baa213c9 | |
| parent | fbf5b3ce9d95a61c06ebf09ee58c809469d71387 (diff) | |
| download | emacs-9d89fec7458c978272a716c33df41f9958f7fe7f.tar.gz emacs-9d89fec7458c978272a716c33df41f9958f7fe7f.zip | |
Provide functions for saving window configurations as Lisp objects.
* window.el (window-list-no-nils, window-state-ignored-parameters)
(window-state-get-1, window-state-get, window-state-put-list)
(window-state-put-1, window-state-put-2, window-state-put): New
functions.
| -rw-r--r-- | lisp/ChangeLog | 4 | ||||
| -rw-r--r-- | lisp/window.el | 305 |
2 files changed, 309 insertions, 0 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 281c73528b2..8f3c0ea0572 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -19,6 +19,10 @@ | |||
| 19 | display-buffer-normalize-options-inhibit is non-nil. | 19 | display-buffer-normalize-options-inhibit is non-nil. |
| 20 | (frame-auto-delete): New option. | 20 | (frame-auto-delete): New option. |
| 21 | (window-deletable-p): Use frame-auto-delete. | 21 | (window-deletable-p): Use frame-auto-delete. |
| 22 | (window-list-no-nils, window-state-ignored-parameters) | ||
| 23 | (window-state-get-1, window-state-get, window-state-put-list) | ||
| 24 | (window-state-put-1, window-state-put-2, window-state-put): New | ||
| 25 | functions. | ||
| 22 | 26 | ||
| 23 | 2011-06-18 Chong Yidong <cyd@stupidchicken.com> | 27 | 2011-06-18 Chong Yidong <cyd@stupidchicken.com> |
| 24 | 28 | ||
diff --git a/lisp/window.el b/lisp/window.el index 454aa6e2941..e79489e40b3 100644 --- a/lisp/window.el +++ b/lisp/window.el | |||
| @@ -3500,6 +3500,311 @@ specific buffers." | |||
| 3500 | ;; (bw-finetune wins) | 3500 | ;; (bw-finetune wins) |
| 3501 | ;; (message "Done in %d rounds" round) | 3501 | ;; (message "Done in %d rounds" round) |
| 3502 | )) | 3502 | )) |
| 3503 | |||
| 3504 | ;;; Window states, how to get them and how to put them in a window. | ||
| 3505 | (defsubst window-list-no-nils (&rest args) | ||
| 3506 | "Like LIST but do not add nil elements of ARGS." | ||
| 3507 | (delq nil (apply 'list args))) | ||
| 3508 | |||
| 3509 | (defvar window-state-ignored-parameters '(quit-restore) | ||
| 3510 | "List of window parameters ignored by `window-state-get'.") | ||
| 3511 | |||
| 3512 | (defun window-state-get-1 (window &optional markers) | ||
| 3513 | "Helper function for `window-state-get'." | ||
| 3514 | (let* ((type | ||
| 3515 | (cond | ||
| 3516 | ((window-vchild window) 'vc) | ||
| 3517 | ((window-hchild window) 'hc) | ||
| 3518 | (t 'leaf))) | ||
| 3519 | (buffer (window-buffer window)) | ||
| 3520 | (selected (eq window (selected-window))) | ||
| 3521 | (head | ||
| 3522 | (window-list-no-nils | ||
| 3523 | type | ||
| 3524 | (unless (window-next window) (cons 'last t)) | ||
| 3525 | (cons 'clone-number (window-clone-number window)) | ||
| 3526 | (cons 'total-height (window-total-size window)) | ||
| 3527 | (cons 'total-width (window-total-size window t)) | ||
| 3528 | (cons 'normal-height (window-normal-size window)) | ||
| 3529 | (cons 'normal-width (window-normal-size window t)) | ||
| 3530 | (cons 'splits (window-splits window)) | ||
| 3531 | (cons 'nest (window-nest window)) | ||
| 3532 | (let (list) | ||
| 3533 | (dolist (parameter (window-parameters window)) | ||
| 3534 | (unless (memq (car parameter) | ||
| 3535 | window-state-ignored-parameters) | ||
| 3536 | (setq list (cons parameter list)))) | ||
| 3537 | (when list | ||
| 3538 | (cons 'parameters list))) | ||
| 3539 | (when buffer | ||
| 3540 | ;; All buffer related things go in here - make the buffer | ||
| 3541 | ;; current when retrieving `point' and `mark'. | ||
| 3542 | (with-current-buffer (window-buffer window) | ||
| 3543 | (let ((point (if selected (point) (window-point window))) | ||
| 3544 | (start (window-start window)) | ||
| 3545 | (mark (mark))) | ||
| 3546 | (window-list-no-nils | ||
| 3547 | 'buffer (buffer-name buffer) | ||
| 3548 | (cons 'selected selected) | ||
| 3549 | (when window-size-fixed (cons 'size-fixed window-size-fixed)) | ||
| 3550 | (cons 'hscroll (window-hscroll window)) | ||
| 3551 | (cons 'fringes (window-fringes window)) | ||
| 3552 | (cons 'margins (window-margins window)) | ||
| 3553 | (cons 'scroll-bars (window-scroll-bars window)) | ||
| 3554 | (cons 'vscroll (window-vscroll window)) | ||
| 3555 | (cons 'dedicated (window-dedicated-p window)) | ||
| 3556 | (cons 'point (if markers (copy-marker point) point)) | ||
| 3557 | (cons 'start (if markers (copy-marker start) start)) | ||
| 3558 | (when mark | ||
| 3559 | (cons 'mark (if markers (copy-marker mark) mark))))))))) | ||
| 3560 | (tail | ||
| 3561 | (when (memq type '(vc hc)) | ||
| 3562 | (let (list) | ||
| 3563 | (setq window (window-child window)) | ||
| 3564 | (while window | ||
| 3565 | (setq list (cons (window-state-get-1 window markers) list)) | ||
| 3566 | (setq window (window-right window))) | ||
| 3567 | (nreverse list))))) | ||
| 3568 | (append head tail))) | ||
| 3569 | |||
| 3570 | (defun window-state-get (&optional window markers) | ||
| 3571 | "Return state of WINDOW as a Lisp object. | ||
| 3572 | WINDOW can be any window and defaults to the root window of the | ||
| 3573 | selected frame. | ||
| 3574 | |||
| 3575 | Optional argument MARKERS non-nil means use markers for sampling | ||
| 3576 | positions like `window-point' or `window-start'. MARKERS should | ||
| 3577 | be non-nil only if the value is used for putting the state back | ||
| 3578 | in the same session (note that markers slow down processing). | ||
| 3579 | |||
| 3580 | The return value can be used as argument for `window-state-put' | ||
| 3581 | to put the state recorded here into an arbitrary window. The | ||
| 3582 | value can be also stored on disk and read back in a new session." | ||
| 3583 | (setq window | ||
| 3584 | (if window | ||
| 3585 | (if (window-any-p window) | ||
| 3586 | window | ||
| 3587 | (error "%s is not a live or internal window" window)) | ||
| 3588 | (frame-root-window))) | ||
| 3589 | ;; The return value is a cons whose car specifies some constraints on | ||
| 3590 | ;; the size of WINDOW. The cdr lists the states of the subwindows of | ||
| 3591 | ;; WINDOW. | ||
| 3592 | (cons | ||
| 3593 | ;; Frame related things would go into a function, say `frame-state', | ||
| 3594 | ;; calling `window-state-get' to insert the frame's root window. | ||
| 3595 | (window-list-no-nils | ||
| 3596 | (cons 'min-height (window-min-size window)) | ||
| 3597 | (cons 'min-width (window-min-size window t)) | ||
| 3598 | (cons 'min-height-ignore (window-min-size window nil t)) | ||
| 3599 | (cons 'min-width-ignore (window-min-size window t t)) | ||
| 3600 | (cons 'min-height-safe (window-min-size window nil 'safe)) | ||
| 3601 | (cons 'min-width-safe (window-min-size window t 'safe)) | ||
| 3602 | ;; These are probably not needed. | ||
| 3603 | (when (window-size-fixed-p window) (cons 'fixed-height t)) | ||
| 3604 | (when (window-size-fixed-p window t) (cons 'fixed-width t))) | ||
| 3605 | (window-state-get-1 window markers))) | ||
| 3606 | |||
| 3607 | (defvar window-state-put-list nil | ||
| 3608 | "Helper variable for `window-state-put'.") | ||
| 3609 | |||
| 3610 | (defun window-state-put-1 (state &optional window ignore totals) | ||
| 3611 | "Helper function for `window-state-put'." | ||
| 3612 | (let ((type (car state))) | ||
| 3613 | (setq state (cdr state)) | ||
| 3614 | (cond | ||
| 3615 | ((eq type 'leaf) | ||
| 3616 | ;; For a leaf window just add unprocessed entries to | ||
| 3617 | ;; `window-state-put-list'. | ||
| 3618 | (setq window-state-put-list | ||
| 3619 | (cons (cons window state) window-state-put-list))) | ||
| 3620 | ((memq type '(vc hc)) | ||
| 3621 | (let* ((horizontal (eq type 'hc)) | ||
| 3622 | (total (window-total-size window horizontal)) | ||
| 3623 | (first t) | ||
| 3624 | size new) | ||
| 3625 | (dolist (item state) | ||
| 3626 | ;; Find the next child window. WINDOW always points to the | ||
| 3627 | ;; real window that we want to fill with what we find here. | ||
| 3628 | (when (memq (car item) '(leaf vc hc)) | ||
| 3629 | (if (assq 'last item) | ||
| 3630 | ;; The last child window. Below `window-state-put-1' | ||
| 3631 | ;; will put into it whatever ITEM has in store. | ||
| 3632 | (setq new nil) | ||
| 3633 | ;; Not the last child window, prepare for splitting | ||
| 3634 | ;; WINDOW. SIZE is the new (and final) size of the old | ||
| 3635 | ;; window. | ||
| 3636 | (setq size | ||
| 3637 | (if totals | ||
| 3638 | ;; Use total size. | ||
| 3639 | (cdr (assq (if horizontal 'total-width 'total-height) item)) | ||
| 3640 | ;; Use normalized size and round. | ||
| 3641 | (round (* total | ||
| 3642 | (cdr (assq | ||
| 3643 | (if horizontal 'normal-width 'normal-height) | ||
| 3644 | item)))))) | ||
| 3645 | |||
| 3646 | ;; Use safe sizes, we try to resize later. | ||
| 3647 | (setq size (max size (if horizontal | ||
| 3648 | window-safe-min-height | ||
| 3649 | window-safe-min-width))) | ||
| 3650 | |||
| 3651 | (if (window-sizable-p window (- size) horizontal 'safe) | ||
| 3652 | (let* ((window-nest (assq 'nest item))) | ||
| 3653 | ;; We must inherit the nesting, otherwise we might mess | ||
| 3654 | ;; up handling of atomic and side window. | ||
| 3655 | (setq new (split-window window size horizontal))) | ||
| 3656 | ;; Give up if we can't resize window down to safe sizes. | ||
| 3657 | (error "Cannot resize window %s" window)) | ||
| 3658 | |||
| 3659 | (when first | ||
| 3660 | (setq first nil) | ||
| 3661 | ;; When creating the first child window add for parent | ||
| 3662 | ;; unprocessed entries to `window-state-put-list'. | ||
| 3663 | (setq window-state-put-list | ||
| 3664 | (cons (cons (window-parent window) state) | ||
| 3665 | window-state-put-list)))) | ||
| 3666 | |||
| 3667 | ;; Now process the current window (either the one we've just | ||
| 3668 | ;; split or the last child of its parent). | ||
| 3669 | (window-state-put-1 item window ignore totals) | ||
| 3670 | ;; Continue with the last window split off. | ||
| 3671 | (setq window new)))))))) | ||
| 3672 | |||
| 3673 | (defun window-state-put-2 (ignore) | ||
| 3674 | "Helper function for `window-state-put'." | ||
| 3675 | (dolist (item window-state-put-list) | ||
| 3676 | (let ((window (car item)) | ||
| 3677 | (clone-number (cdr (assq 'clone-number item))) | ||
| 3678 | (splits (cdr (assq 'splits item))) | ||
| 3679 | (nest (cdr (assq 'nest item))) | ||
| 3680 | (parameters (cdr (assq 'parameters item))) | ||
| 3681 | (state (cdr (assq 'buffer item)))) | ||
| 3682 | ;; Put in clone-number. | ||
| 3683 | (when clone-number (set-window-clone-number window clone-number)) | ||
| 3684 | (when splits (set-window-splits window splits)) | ||
| 3685 | (when nest (set-window-nest window nest)) | ||
| 3686 | ;; Process parameters. | ||
| 3687 | (when parameters | ||
| 3688 | (dolist (parameter parameters) | ||
| 3689 | (set-window-parameter window (car parameter) (cdr parameter)))) | ||
| 3690 | ;; Process buffer related state. | ||
| 3691 | (when state | ||
| 3692 | ;; We don't want to raise an error here so we create a buffer if | ||
| 3693 | ;; there's none. | ||
| 3694 | (set-window-buffer window (get-buffer-create (car state))) | ||
| 3695 | (with-current-buffer (window-buffer window) | ||
| 3696 | (set-window-hscroll window (cdr (assq 'hscroll state))) | ||
| 3697 | (apply 'set-window-fringes | ||
| 3698 | (cons window (cdr (assq 'fringes state)))) | ||
| 3699 | (let ((margins (cdr (assq 'margins state)))) | ||
| 3700 | (set-window-margins window (car margins) (cdr margins))) | ||
| 3701 | (let ((scroll-bars (cdr (assq 'scroll-bars state)))) | ||
| 3702 | (set-window-scroll-bars | ||
| 3703 | window (car scroll-bars) (nth 2 scroll-bars) (nth 3 scroll-bars))) | ||
| 3704 | (set-window-vscroll window (cdr (assq 'vscroll state))) | ||
| 3705 | ;; Adjust vertically. | ||
| 3706 | (if (memq window-size-fixed '(t height)) | ||
| 3707 | ;; A fixed height window, try to restore the original size. | ||
| 3708 | (let ((delta (- (cdr (assq 'total-height item)) | ||
| 3709 | (window-total-height window))) | ||
| 3710 | window-size-fixed) | ||
| 3711 | (when (window-resizable-p window delta) | ||
| 3712 | (resize-window window delta))) | ||
| 3713 | ;; Else check whether the window is not high enough. | ||
| 3714 | (let* ((min-size (window-min-size window nil ignore)) | ||
| 3715 | (delta (- min-size (window-total-size window)))) | ||
| 3716 | (when (and (> delta 0) | ||
| 3717 | (window-resizable-p window delta nil ignore)) | ||
| 3718 | (resize-window window delta nil ignore)))) | ||
| 3719 | ;; Adjust horizontally. | ||
| 3720 | (if (memq window-size-fixed '(t width)) | ||
| 3721 | ;; A fixed width window, try to restore the original size. | ||
| 3722 | (let ((delta (- (cdr (assq 'total-width item)) | ||
| 3723 | (window-total-width window))) | ||
| 3724 | window-size-fixed) | ||
| 3725 | (when (window-resizable-p window delta) | ||
| 3726 | (resize-window window delta))) | ||
| 3727 | ;; Else check whether the window is not wide enough. | ||
| 3728 | (let* ((min-size (window-min-size window t ignore)) | ||
| 3729 | (delta (- min-size (window-total-size window t)))) | ||
| 3730 | (when (and (> delta 0) | ||
| 3731 | (window-resizable-p window delta t ignore)) | ||
| 3732 | (resize-window window delta t ignore)))) | ||
| 3733 | ;; Set dedicated status. | ||
| 3734 | (set-window-dedicated-p window (cdr (assq 'dedicated state))) | ||
| 3735 | ;; Install positions (maybe we should do this after all windows | ||
| 3736 | ;; have been created and sized). | ||
| 3737 | (ignore-errors | ||
| 3738 | (set-window-start window (cdr (assq 'start state))) | ||
| 3739 | (set-window-point window (cdr (assq 'point state))) | ||
| 3740 | ;; I'm not sure whether we should set the mark here, but maybe | ||
| 3741 | ;; it can be used. | ||
| 3742 | (let ((mark (cdr (assq 'mark state)))) | ||
| 3743 | (when mark (set-mark mark)))) | ||
| 3744 | ;; Select window if it's the selected one. | ||
| 3745 | (when (cdr (assq 'selected state)) | ||
| 3746 | (select-window window))))))) | ||
| 3747 | |||
| 3748 | (defun window-state-put (state &optional window ignore) | ||
| 3749 | "Put window state STATE into WINDOW. | ||
| 3750 | STATE should be the state of a window returned by an earlier | ||
| 3751 | invocation of `window-state-get'. Optional argument WINDOW must | ||
| 3752 | specify a live window and defaults to the selected one. | ||
| 3753 | |||
| 3754 | Optional argument IGNORE non-nil means ignore minimum window | ||
| 3755 | sizes and fixed size restrictions. IGNORE equal `safe' means | ||
| 3756 | subwindows can get as small as `window-safe-min-height' and | ||
| 3757 | `window-safe-min-width'." | ||
| 3758 | (setq window (normalize-live-window window)) | ||
| 3759 | (let* ((frame (window-frame window)) | ||
| 3760 | (head (car state)) | ||
| 3761 | ;; We check here (1) whether the total sizes of root window of | ||
| 3762 | ;; STATE and that of WINDOW are equal so we can avoid | ||
| 3763 | ;; calculating new sizes, and (2) if we do have to resize | ||
| 3764 | ;; whether we can do so without violating size restrictions. | ||
| 3765 | (totals | ||
| 3766 | (and (= (window-total-size window) | ||
| 3767 | (cdr (assq 'total-height state))) | ||
| 3768 | (= (window-total-size window t) | ||
| 3769 | (cdr (assq 'total-width state))))) | ||
| 3770 | (min-height (cdr (assq 'min-height head))) | ||
| 3771 | (min-width (cdr (assq 'min-width head))) | ||
| 3772 | window-splits selected) | ||
| 3773 | (if (and (not totals) | ||
| 3774 | (or (> min-height (window-total-size window)) | ||
| 3775 | (> min-width (window-total-size window t))) | ||
| 3776 | (or (not ignore) | ||
| 3777 | (and (setq min-height | ||
| 3778 | (cdr (assq 'min-height-ignore head))) | ||
| 3779 | (setq min-width | ||
| 3780 | (cdr (assq 'min-width-ignore head))) | ||
| 3781 | (or (> min-height (window-total-size window)) | ||
| 3782 | (> min-width (window-total-size window t))) | ||
| 3783 | (or (not (eq ignore 'safe)) | ||
| 3784 | (and (setq min-height | ||
| 3785 | (cdr (assq 'min-height-safe head))) | ||
| 3786 | (setq min-width | ||
| 3787 | (cdr (assq 'min-width-safe head))) | ||
| 3788 | (or (> min-height | ||
| 3789 | (window-total-size window)) | ||
| 3790 | (> min-width | ||
| 3791 | (window-total-size window t)))))))) | ||
| 3792 | ;; The check above might not catch all errors due to rounding | ||
| 3793 | ;; issues - so IGNORE equal 'safe might not always produce the | ||
| 3794 | ;; minimum possible state. But such configurations hardly make | ||
| 3795 | ;; sense anyway. | ||
| 3796 | (error "Window %s too small to accomodate state" window) | ||
| 3797 | (setq state (cdr state)) | ||
| 3798 | (setq window-state-put-list nil) | ||
| 3799 | ;; Work on the windows of a temporary buffer to make sure that | ||
| 3800 | ;; splitting proceeds regardless of any buffer local values of | ||
| 3801 | ;; `window-size-fixed'. Release that buffer after the buffers of | ||
| 3802 | ;; all live windows have been set by `window-state-put-2'. | ||
| 3803 | (with-temp-buffer | ||
| 3804 | (set-window-buffer window (current-buffer)) | ||
| 3805 | (window-state-put-1 state window nil totals) | ||
| 3806 | (window-state-put-2 ignore)) | ||
| 3807 | (window-check frame)))) | ||
| 3503 | 3808 | ||
| 3504 | ;;; Displaying buffers. | 3809 | ;;; Displaying buffers. |
| 3505 | (defconst display-buffer-default-specifiers | 3810 | (defconst display-buffer-default-specifiers |