--- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -1038,6 +1038,7 @@ Top a specific window. * Quitting Windows:: How to restore the state prior to displaying a buffer. +* Side Windows:: Special windows on a frame's sides. * Window Point:: Each window has its own location of point. * Window Start and End:: Buffer positions indicating which text is on-screen in a window. @@ -1051,6 +1052,14 @@ Top redisplay going past a certain point, or window configuration changes. +Side Windows + +* Displaying Buffers in Side Windows:: An action function for displaying + buffers in side windows. +* Side Window Options and Functions:: Further tuning of side windows. +* Frame Layouts with Side Windows:: Setting up frame layouts with side + windows. + Frames * Creating Frames:: Creating additional frames. diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi index 3c9df0b..c326b7c 100644 --- a/doc/lispref/windows.texi +++ b/doc/lispref/windows.texi @@ -33,6 +33,7 @@ Windows a specific window. * Quitting Windows:: How to restore the state prior to displaying a buffer. +* Side Windows:: Special windows on a frame's sides. * Window Point:: Each window has its own location of point. * Window Start and End:: Buffer positions indicating which text is on-screen in a window. @@ -1275,9 +1276,12 @@ Deleting Windows @deffn Command delete-window &optional window This function removes @var{window} from display and returns @code{nil}. If @var{window} is omitted or @code{nil}, it defaults to -the selected window. If deleting the window would leave no more -windows in the window tree (e.g., if it is the only live window in the -frame), an error is signaled. +the selected window. + +If deleting the window would leave no more windows in the window tree +(e.g., if it is the only live window in the frame) or all remaining +windows on @var{window}'s frame are side windows (@pxref{Side Windows}), +an error is signaled. By default, the space taken up by @var{window} is given to one of its adjacent sibling windows, if any. However, if the variable @@ -1285,33 +1289,34 @@ Deleting Windows proportionally distributed among any remaining windows in the same window combination. @xref{Recombining Windows}. -The behavior of this function may be altered by the window parameters -of @var{window}, so long as the variable address@hidden is @code{nil}. If the value of -the @code{delete-window} window parameter is @code{t}, this function -ignores all other window parameters. Otherwise, if the value of the address@hidden window parameter is a function, that function is -called with the argument @var{window}, in lieu of the usual action of address@hidden Otherwise, this function obeys the address@hidden or @code{window-side} window parameter, if any. address@hidden Parameters}. +The behavior of this function may be altered by the window parameters of address@hidden, so long as the variable @code{ignore-window-parameters} is address@hidden If the value of the @code{delete-window} window parameter +is @code{t}, this function ignores all other window parameters. +Otherwise, if the value of the @code{delete-window} window parameter is +a function, that function is called with the argument @var{window}, in +lieu of the usual action of @code{delete-window}. @xref{Window +Parameters}. @end deffn @deffn Command delete-other-windows &optional window -This function makes @var{window} fill its frame, by deleting other -windows as necessary. If @var{window} is omitted or @code{nil}, it -defaults to the selected window. The return value is @code{nil}. - -The behavior of this function may be altered by the window parameters -of @var{window}, so long as the variable address@hidden is @code{nil}. If the value of -the @code{delete-other-windows} window parameter is @code{t}, this -function ignores all other window parameters. Otherwise, if the value -of the @code{delete-other-windows} window parameter is a function, -that function is called with the argument @var{window}, in lieu of the -usual action of @code{delete-other-windows}. Otherwise, this function -obeys the @code{window-atom} or @code{window-side} window parameter, -if any. @xref{Window Parameters}. +This function makes @var{window} fill its frame, deleting other windows +as necessary. If @var{window} is omitted or @code{nil}, it defaults to +the selected window. An error is signaled if @var{window} is a side +window (@pxref{Side Windows}). The return value is @code{nil}. + +The behavior of this function may be altered by the window parameters of address@hidden, so long as the variable @code{ignore-window-parameters} is address@hidden If the value of the @code{delete-other-windows} window +parameter is @code{t}, this function ignores all other window +parameters. Otherwise, if the value of the @code{delete-other-windows} +window parameter is a function, that function is called with the +argument @var{window}, in lieu of the usual action of address@hidden @xref{Window Parameters}. + +Also, if @code{ignore-window-parameters} is @code{nil}, this function +does not delete any window whose @code{no-delete-other-window} parameter +is address@hidden @end deffn @deffn Command delete-windows-on &optional buffer-or-name frame @@ -2574,7 +2579,12 @@ Display Action Functions from @code{display-buffer} in this case. @end defun -To illustrate the use of action functions, consider the following +If the @var{alist} argument of any of these functions contains a address@hidden entry, @code{display-buffer} assigns the +elements of the associated value as window parameters of the chosen +window. + + To illustrate the use of action functions, consider the following example. @example @@ -3068,6 +3078,278 @@ Quitting Windows @end defopt address@hidden Side Windows address@hidden Side Windows address@hidden side windows address@hidden main window address@hidden main window of a frame + +Side windows are special windows positioned at any of the four sides of +a frame's root window (@pxref{Windows and Frames}). In practice, this +means that the area of the frame's root window is subdivided into a main +window and a number of side windows surrounding that main window. The +main window is either a ``normal'' live window or specifies the area +containing all the normal windows. + + In their most simple form of use, side windows allow to display +specific buffers always in the same area of a frame. Hence they can be +regarded as a generalization of the concept provided by address@hidden (@pxref{Display Action Functions}) to +the remaining sides of a frame. With suitable customizations, however, +side windows can be also used to provide frame layouts similar to those +found in so-called integrated development environments (IDEs). + address@hidden +* Displaying Buffers in Side Windows:: An action function for displaying + buffers in side windows. +* Side Window Options and Functions:: Further tuning of side windows. +* Frame Layouts with Side Windows:: Setting up frame layouts with side + windows. address@hidden menu + + address@hidden Displaying Buffers in Side Windows address@hidden Displaying Buffers in Side Windows + +The following action function for @code{display-buffer} (@pxref{Display +Action Functions}) creates or reuses a side window for displaying the +specified buffer. + address@hidden display-buffer-in-side-window buffer alist +This function displays @var{buffer} in a side window of the selected +frame. @var{alist} is an association list of symbols and values as for address@hidden The following symbols in @var{alist} are special +for this function: + address@hidden @code address@hidden side +Denotes the side of the frame where the window shall be located. Valid +values are @code{left}, @code{top}, @code{right} and @code{bottom}. If +unspecified, the window is located at the bottom of the frame. + address@hidden slot +Denotes a slot at the specified side where to locate the window. A +value of zero means to preferably position the window in the middle of +the specified side. A negative value means to use a slot preceding +(that is, above or on the left of) the middle slot. A positive value +means to use a slot following (that is, below or on the right of) the +middle slot. Hence, all windows on a specific side are ordered by their address@hidden value. If unspecified, the window is located in the middle +of the specified side. address@hidden table + +If you specify the same slot on the same side for two or more different +buffers, the buffer displayed last is shown in the corresponding window. +Hence, slots can be used for sharing the same side window between +buffers. + +This function installs the @code{window-side} and @code{window-slot} +parameters (@pxref{Window Parameters}) and makes them persistent. It +does not install any other window parameters unless they have been +explicitly provided via a @code{window-parameters} entry in @var{alist}. address@hidden defun + +By default, side windows cannot be split via @code{split-window} +(@pxref{Splitting Windows}). Also, a side window is not reused or split +by any buffer display action (@pxref{Display Action Functions}) unless +it is explicitly specified as target of that action. Note also that address@hidden cannot make a side window the only window on +its frame (@pxref{Deleting Windows}). + + Once set up, side windows also change the behavior of the commands address@hidden and @code{switch-to-next-buffer} +(@pxref{Window History}). In particular, these commands will refrain +from showing, in a side window, buffers that have not been displayed in +that window before. And, they will refrain from having a normal, +non-side window show a buffer that has been already displayed in a side +window. A notable exception to the latter rule occurs when an +application, after displaying a buffer, resets that buffer's local +variables. + + address@hidden Side Window Options and Functions address@hidden Side Window Options and Functions + +The following two options provide additional control over the +placement of side windows. + address@hidden window-sides-vertical +If address@hidden, this means that the side windows on the left and +right of a frame occupy the frame's full height. Otherwise, the side +windows on the top and bottom of the frame occupy the frame's full +width. Note that changing the value of this variable does not affect +the layout of frames already displaying side windows on at least two +orthogonal sides. address@hidden defopt + address@hidden window-sides-slots +This option specifies the maximum number of side windows on each side of +a frame. The value is a list of four elements specifying the number of +side window slots on (in this order) the left, top, right and bottom of +each frame. If an element is a number, this means to display at most +that many windows on the corresponding side. If an element is address@hidden, this means there's no bound on the number of slots on that +side. + +If any of the specified values is zero, no window can be created on the +corresponding side. @code{display-buffer-in-side-window} will not +signal an error in that case but return @code{nil}. If a specified +value just forbids the creation of an additional side window, the most +suitable window on that side is reused and may have its address@hidden parameter changed accordingly. address@hidden defopt + +When a frame has side windows, the following function returns the main +window of that frame. + address@hidden window-main-window &optional frame +This function returns the main window of the specified @var{frame}. The +optional argument @var{frame} must be a live frame and defaults to the +selected one. + +If @var{frame} has no side windows, it returns @var{frame}'s root +window. Otherwise, it returns either an internal non-side window such +that all other non-side windows on @var{frame} descend from it, or the +single live non-side window of @var{frame}. Note that the main window +of a frame cannot be deleted via @code{delete-window}. address@hidden defun + +The following command is handy to toggle the appearance of all side +windows on a specified frame. + address@hidden Command window-toggle-side-windows &optional frame +This command toggles side windows on the specified @var{frame}. The +optional argument @var{frame} must be a live frame and defaults to the +selected one. + +If @var{frame} has at least one side window, this command saves the +state of @var{frame}'s root window in the @var{frame}'s address@hidden frame parameter and deletes all side windows on address@hidden afterwards. + +If @var{frame} has no side window but a @code{window-state} parameter, +it uses that parameter's value to restore the side windows on address@hidden leaving @var{frame}'s main window alone. + +An error is signaled if @var{frame} has no side window and no saved +state is found. address@hidden deffn + + address@hidden Frame Layouts with Side Windows address@hidden Frame Layouts with Side Windows + +Side windows can be used to create more complex frame layouts like those +provided by integrated development environments (IDEs). In such +layouts, the area of the main window is where the normal editing +activities take place. Side windows are not conceived for editing in +the usual sense. Rather, they are supposed to display information +complementary to the current editing activity, like lists of files, tags +or buffers, help information, search or grep results or shell output. + + The layout of such a frame might appear as follows: + address@hidden address@hidden + ___________________________________ + | *Buffer List* | + |___________________________________| + | | | | + | * | | * | + | d | | T | + | i | | a | + | r | Main Window Area | g | + | e | | s | + | d | | * | + | * | | | + |_____|_______________________|_____| + | *help*/*grep*/ | *shell*/ | + | *Completions* | *compilation* | + |_________________|_________________| + | Echo Area | + |___________________________________| + + address@hidden group address@hidden smallexample + +The following example illustrates how window parameters (@pxref{Window +Parameters}) can be used with @code{display-buffer-in-side-window} +(@pxref{Displaying Buffers in Side Windows}) to set up code for +producing the frame sketched above. + address@hidden address@hidden +(defvar parameters + '(window-parameters . ((no-other-window . t) (no-delete-other-window . t)))) + +(setq fit-window-to-buffer-horizontally t) +(setq window-resize-pixelwise t) + +(setq + display-buffer-alist + `(("\\*Buffer List\\*" display-buffer-in-side-window + (side . top) (slot . 0) (window-height . fit-window-to-buffer) + (preserve-size . (nil . t)) ,parameters) + ("\\*Tags List\\*" display-buffer-in-side-window + (side . right) (slot . 0) (window-width . fit-window-to-buffer) + (preserve-size . (t . nil)) ,parameters) + ("\\*\\(?:help\\|grep\\|Completions\\)\\*" display-buffer-in-side-window + (side . bottom) (slot . -1) (preserve-size . (nil . t)) ,parameters) + ("\\*\\(?:shell\\|compilation\\)\\*" display-buffer-in-side-window + (side . bottom) (slot . 1) (preserve-size . (nil . t)) ,parameters))) address@hidden group address@hidden example + +This specifies @code{display-buffer-alist} entries (@pxref{Choosing +Window}) for buffers with fixed names. In particular, it asks for +showing @file{*Buffer List*} with adjustable height at the top of the +frame and @file{*Tags List*} with adjustable width on the frame's right. +It also asks for having the @file{*help*}, @file{*grep*} and address@hidden buffers share a window on the bottom left side of +the frame and the @file{*shell*} and @file{*compilation*} buffers appear +in a window on the bottom right side of the frame. + + Note that the option @code{fit-window-to-buffer-horizontally} must +have a address@hidden value in order to allow horizontal adjustment of +windows. We also added entries that ask for preserving the height of +side windows at the top and bottom of the frame and the width of side +windows at the left or right of the frame. To assure that side windows +retain their respective sizes when maximizing the associated frame, we +have also set the variable @code{window-resize-pixelwise}. address@hidden Windows}. + + The last form also makes sure that none of the side windows it +creates are accessible via @kbd{C-x o} by installing a address@hidden parameter for each of these windows. In addition +it also makes sure that side windows are not deleted via @kbd{C-x 1} by +installing a @code{no-delete-other-window} parameter on each of these +windows. + + Since @code{dired} buffers have no fixed names, we use a special +function @code{dired-default-directory-on-left} in order to display a +lean directory buffer on the left side of the frame. + address@hidden address@hidden +(defun dired-default-directory-on-left () + "Display `default-directory' in side window on left, hiding details." + (interactive) + (let ((buffer (dired-noselect default-directory))) + (with-current-buffer buffer (dired-hide-details-mode t)) + (display-buffer-in-side-window + buffer `((side . left) (slot . 0) + (window-width . fit-window-to-buffer) + (preserve-size . (t . nil)) ,parameters)))) address@hidden group address@hidden example + +Evaluating the preceding forms and typing, in any order, @kbd{M-x +list-buffers}, @kbd{C-h f}, @kbd{M-x shell}, @kbd{M-x list-tags} and address@hidden dired-default-directory-on-left} should now reproduce the frame +layout sketched above. + + @node Window Point @section Windows and Point @cindex window position @@ -4279,6 +4561,7 @@ Window Parameters parameters reset to @code{nil}. The following variable allows you to override the standard behavior: address@hidden persistent window parameters @defvar window-persistent-parameters This variable is an alist specifying which parameters get saved by @code{current-window-configuration} and @code{window-state-get}, and @@ -4308,10 +4591,10 @@ Window Parameters @end defvar Some functions (notably @code{delete-window}, address@hidden and @code{split-window}), may behave specially -when their @var{window} argument has a parameter set. You can override -such special behavior by binding the following variable to a address@hidden value: address@hidden and @code{split-window}), may behave +specially when the window specified by their @var{window} argument has +the homonymous parameter set. You can override such special behavior by +binding the following variable to a address@hidden value: @defvar ignore-window-parameters If this variable is address@hidden, some standard functions do not @@ -4337,6 +4620,10 @@ Window Parameters This parameter affects the execution of @code{delete-other-windows} (@pxref{Deleting Windows}). address@hidden @code{no-delete-other-window} +This parameter marks the window as not deletable by address@hidden (@pxref{Deleting Windows}). + @item @code{split-window} This parameter affects the execution of @code{split-window} (@pxref{Splitting Windows}). @@ -4387,6 +4674,10 @@ Window Parameters this parameter. @code{quit-restore-window} deletes the specified window only if it still shows that buffer. address@hidden @code{window-side} @code{window-slot} +These parameters are used for implementing side windows (@pxref{Side +Windows}). + @item @code{min-margins} The value of this parameter is a cons cell whose @sc{car} and @sc{cdr}, if address@hidden, specify the minimum values (in columns) for the left @@ -4409,8 +4700,7 @@ Window Parameters versions of Emacs. @end table -There are additional parameters @code{window-atom} and @code{window-side}; -these are reserved and should not be used by applications. +The @code{window-atom} parameter is used for implemeting atomic windows. @node Window Hooks diff --git a/lisp/window.el b/lisp/window.el index 6728ea3..73c48be 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -714,19 +714,23 @@ window-sides (defcustom window-sides-vertical nil "If non-nil, left and right side windows are full height. -Otherwise, top and bottom side windows are full width." +Otherwise, top and bottom side windows are full width. + +Setting this variable does not affect the layout of frames +already displaying side windows on at least two orthogonal +sides." :type 'boolean :group 'windows :version "24.1") (defcustom window-sides-slots '(nil nil nil nil) "Maximum number of side window slots. -The value is a list of four elements specifying the number of -side window slots on (in this order) the left, top, right and -bottom side of each frame. If an element is a number, this means -to display at most that many side windows on the corresponding -side. If an element is nil, this means there's no bound on the -number of slots on that side." +The value is a list of four elements specifying the maximum +number of side windows on the left, top, right and bottom side of +any frame. If an element is a number, this means to display at +most that many side windows on the corresponding side. If an +element is nil, this means there's no bound on the number of +slots on that side." :version "24.1" :risky t :type @@ -734,34 +738,44 @@ window-sides-slots :value (nil nil nil nil) (choice :tag "Left" - :help-echo "Maximum slots of left side window." + :help-echo "Maximum number of left side windows." :value nil :format "%[Left%] %v\n" (const :tag "Unlimited" :format "%t" nil) (integer :tag "Number" :value 2 :size 5)) (choice :tag "Top" - :help-echo "Maximum slots of top side window." + :help-echo "Maximum number of top side windows." :value nil :format "%[Top%] %v\n" (const :tag "Unlimited" :format "%t" nil) (integer :tag "Number" :value 3 :size 5)) (choice :tag "Right" - :help-echo "Maximum slots of right side window." + :help-echo "Maximum number of right side windows." :value nil :format "%[Right%] %v\n" (const :tag "Unlimited" :format "%t" nil) (integer :tag "Number" :value 2 :size 5)) (choice :tag "Bottom" - :help-echo "Maximum slots of bottom side window." + :help-echo "Maximum number of bottom side windows." :value nil :format "%[Bottom%] %v\n" (const :tag "Unlimited" :format "%t" nil) (integer :tag "Number" :value 3 :size 5))) :group 'windows) +(defvar-local window-sides-shown nil + "Non-nil if this buffer was shown in a side window once. +If this variable is non-nil in a buffer, the +`switch-to-prev-buffer' and `switch-to-next-buffer' functions +will refrain to show this buffer within the main window area. +`display-buffer-in-side-window' sets this variable automatically. + +Killing buffer local variables after showing the buffer in a side +window annihilates the effect provided by setting this.") + (defun window--side-window-p (window) "Return non-nil if WINDOW is a side window or the parent of one." (or (window-parameter window 'window-side) @@ -771,19 +785,18 @@ window--side-window-p (window-parameter (window-last-child window) 'window-side))))) -(defun window--major-non-side-window (&optional frame) - "Return the major non-side window of frame FRAME. +(defun window-main-window (&optional frame) + "Return the main window of specified FRAME. The optional argument FRAME must be a live frame and defaults to the selected one. -If FRAME has at least one side window, the major non-side window -is either an internal non-side window such that all other -non-side windows on FRAME descend from it, or the single live -non-side window of FRAME. If FRAME has no side windows, return -its root window." +If FRAME has no side windows, return FRAME's root window. +Otherwise, return either an internal non-side window such that +all other non-side windows on FRAME descend from it, or the +single live non-side window of FRAME." (let ((frame (window-normalize-frame frame)) - major sibling) - ;; Set major to the _last_ window found by `walk-window-tree' that + main sibling) + ;; Set main to the _last_ window found by `walk-window-tree' that ;; is not a side window but has a side window as its sibling. (walk-window-tree (lambda (window) @@ -792,14 +805,17 @@ window--major-non-side-window (window-parameter sibling 'window-side)) (and (setq sibling (window-next-sibling window)) (window-parameter sibling 'window-side))) - (setq major window))) + (setq main window))) frame t 'nomini) - (or major (frame-root-window frame)))) + (or main (frame-root-window frame)))) -(defun window--major-side-window (side) - "Return major side window on SIDE. +(defun window--make-major-side-window-next-to (side) + "Return window to split for making a major side window. SIDE must be one of the symbols `left', `top', `right' or -`bottom'. Return nil if no such window exists." +`bottom'. + +This is an auxiliary function of `window--make-major-side-window' +and must not be called when a window on SIDE exists already." (let ((root (frame-root-window)) window) ;; (1) If a window on the opposite side exists, return that window's @@ -839,35 +855,36 @@ window--major-side-window (window-prev-sibling window)) (t root)))))) -(defun display-buffer-in-major-side-window (buffer side slot &optional alist) - "Display BUFFER in a new window on SIDE of the selected frame. +(defun window--make-major-side-window (buffer side slot &optional alist) + "Display BUFFER in a new major side window on the selected frame. SIDE must be one of `left', `top', `right' or `bottom'. SLOT specifies the slot to use. ALIST is an association list of symbols and values as passed to `display-buffer-in-side-window'. -This function may be called only if no window on SIDE exists yet. -The new window automatically becomes the \"major\" side window on -SIDE. Return the new window, nil if its creation window failed." +Return the new window, nil if its creation failed. + +This is an auxiliary function of `display-buffer-in-side-window' +and may be called only if no window on SIDE exists yet." (let* ((left-or-right (memq side '(left right))) - (major (window--major-side-window side)) + (next-to (window--make-major-side-window-next-to side)) (on-side (cond ((eq side 'top) 'above) ((eq side 'bottom) 'below) (t side))) ;; The following two bindings will tell `split-window' to take - ;; the space for the new window from `major' and not make a new - ;; parent window unless needed. + ;; the space for the new window from the selected frame's main + ;; window and not make a new parent window unless needed. (window-combination-resize 'side) (window-combination-limit nil) - (new (split-window major nil on-side))) - (when new - ;; Initialize `window-side' parameter of new window to SIDE. - (set-window-parameter new 'window-side side) - ;; Install `window-slot' parameter of new window. - (set-window-parameter new 'window-slot slot) - ;; Install `delete-window' parameter thus making sure that when - ;; the new window is deleted, a side window on the opposite side - ;; does not get resized. - (set-window-parameter new 'delete-window 'delete-side-window) + (window (split-window next-to nil on-side))) + (when window + ;; Initialize `window-side' parameter of new window to SIDE and + ;; make that parameter persistent. + (set-window-parameter window 'window-side side) + (add-to-list 'window-persistent-parameters '(window-side . writable)) + ;; Install `window-slot' parameter of new window and make that + ;; parameter persistent. + (set-window-parameter window 'window-slot slot) + (add-to-list 'window-persistent-parameters '(window-slot . writable)) ;; Auto-adjust height/width of new window unless a size has been ;; explicitly requested. (unless (if left-or-right @@ -882,15 +899,10 @@ display-buffer-in-major-side-window ;; root window. 4)) alist))) - ;; Install BUFFER in new window and return NEW. - (window--display-buffer buffer new 'window alist 'side)))) - -(defun delete-side-window (window) - "Delete side window WINDOW." - (let ((window-combination-resize - (window-parameter (window-parent window) 'window-side)) - (ignore-window-parameters t)) - (delete-window window))) + (with-current-buffer buffer + (setq window-sides-shown t)) + ;; Install BUFFER in new window and return WINDOW. + (window--display-buffer buffer window 'window alist 'side)))) (defun display-buffer-in-side-window (buffer alist) "Display BUFFER in a side window of the selected frame. @@ -906,9 +918,27 @@ display-buffer-in-side-window the specified side. A negative value means use a slot preceding (that is, above or on the left of) the middle slot. A positive value means use a slot following (that is, below or - on the right of) the middle slot. The default is zero." - (let ((side (or (cdr (assq 'side alist)) 'bottom)) - (slot (or (cdr (assq 'slot alist)) 0))) + on the right of) the middle slot. The default is zero. + +If the current frame size or the settings of `window-sides-slots' +do not permit making a new window, a suitable existing window may +be reused and have its `window-slot' parameter value accordingly +modified. + +Unless `display-buffer-mark-dedicated' is non-nil, softly +dedicate the side window used to BUFFER. Return nil if no +suitable window is found. + +This function installs the `window-side' and `window-slot' +parameters and makes them persistent. It neither modifies ALIST +nor installs any other window parameters unless they have been +explicitly provided via a `window-parameter' entry in ALIST." + (let* ((side (or (cdr (assq 'side alist)) 'bottom)) + (slot (or (cdr (assq 'slot alist)) 0)) + (left-or-right (memq side '(left right))) + ;; Softly dedicate window to BUFFER unless + ;; `display-buffer-mark-dedicated' already asks for it. + (dedicated (or display-buffer-mark-dedicated 'side))) (cond ((not (memq side '(top bottom left right))) (error "Invalid side %s specified" side)) @@ -940,12 +970,12 @@ display-buffer-in-side-window (cond ((and (numberp max-slots) (<= max-slots 0)) - ;; No side-slots available on this side. Don't create an error, + ;; No side-slots available on this side. Don't raise an error, ;; just return nil. nil) ((not windows) - ;; No major window exists on this side, make one. - (display-buffer-in-major-side-window buffer side slot alist)) + ;; No major side window exists on this side, make one. + (window--make-major-side-window buffer side slot alist)) (t ;; Scan windows on SIDE. (catch 'found @@ -983,46 +1013,69 @@ display-buffer-in-side-window ;; window will be created before it. ;; `best-window' is the window with the smallest absolute difference ;; of its slot and SLOT. - - ;; Note: We dedicate the window used softly to its buffer to - ;; avoid that "other" (non-side) buffer display functions steal - ;; it from us. This must eventually become customizable via - ;; ALIST (or, better, avoided in the "other" functions). (or (and this-window ;; Reuse `this-window'. - (window--display-buffer buffer this-window 'reuse alist 'side)) + (with-current-buffer buffer + (setq window-sides-shown t)) + (window--display-buffer + buffer this-window 'reuse alist dedicated)) (and (or (not max-slots) (< slots max-slots)) (or (and next-window ;; Make new window before `next-window'. - (let ((next-side - (if (memq side '(left right)) 'above 'left)) + (let ((next-side (if left-or-right 'above 'left)) (window-combination-resize 'side)) - (setq window (split-window next-window nil next-side)) - ;; When the new window is deleted, its space - ;; is returned to other side windows. - (set-window-parameter - window 'delete-window 'delete-side-window) - window)) + (setq window + (split-window next-window nil next-side)))) (and prev-window ;; Make new window after `prev-window'. - (let ((prev-side - (if (memq side '(left right)) 'below 'right)) + (let ((prev-side (if left-or-right 'below 'right)) (window-combination-resize 'side)) - (setq window (split-window prev-window nil prev-side)) - ;; When the new window is deleted, its space - ;; is returned to other side windows. - (set-window-parameter - window 'delete-window 'delete-side-window) - window))) + (setq window + (split-window prev-window nil prev-side))))) (set-window-parameter window 'window-slot slot) - (window--display-buffer buffer window 'window alist 'side)) + (with-current-buffer buffer + (setq window-sides-shown t)) + (window--display-buffer + buffer window 'window alist dedicated)) (and best-window ;; Reuse `best-window'. (progn ;; Give best-window the new slot value. (set-window-parameter best-window 'window-slot slot) - (window--display-buffer - buffer best-window 'reuse alist 'side))))))))) + (with-current-buffer buffer + (setq window-sides-shown t)) + (window--display-buffer + buffer best-window 'reuse alist dedicated))))))))) + +(defun window-toggle-side-windows (&optional frame) + "Toggle side windows on specified FRAME. +FRAME must be a live frame and defaults to the selected one. + +If FRAME has at least one side window, save FRAME's state in the +FRAME's `window-state' frame parameter and delete all side +windows on FRAME afterwards. Otherwise, if FRAME has a +`window-state' parameter, use that to restore any side windows on +FRAME leaving FRAME's main window alone. Signal an error if +FRAME has no side window and no saved state is found." + (interactive) + (let* ((frame (window-normalize-frame frame)) + state) + (cond + ((window-with-parameter 'window-side nil frame) + ;; At least one side window exists. Remove all side windows after + ;; saving FRAME's state in its `window-state' parameter. + (set-frame-parameter + frame 'window-state (window-state-get (frame-root-window frame))) + (let ((ignore-window-parameters t)) + (delete-other-windows (window-main-window frame)))) + ((setq state (frame-parameter frame 'window-state)) + ;; A window state was saved for FRAME. Restore it and put the + ;; current root window into its main window. + (let ((main-state (window-state-get (frame-root-window frame)))) + (window-state-put state (frame-root-window frame) t) + (window-state-put main-state (window-main-window frame)))) + (t + (error "No side windows state found"))))) (defun window--side-check (&optional frame) "Check the side window configuration of FRAME. @@ -3224,8 +3277,10 @@ adjust-window-trailing-edge (setq left first-left) (while (and left (or (window-size-fixed-p left horizontal 'preserved) - (<= (window-size left horizontal t) - (window-min-size left horizontal 'preserved t)))) + (and (< delta 0) + (<= (window-size left horizontal t) + (window-min-size + left horizontal 'preserved t))))) (setq left (or (window-left left) (progn @@ -3245,7 +3300,8 @@ adjust-window-trailing-edge (or (window-size-fixed-p right horizontal) (and (> delta 0) (<= (window-size right horizontal t) - (window-min-size right horizontal 'preserved t))))) + (window-min-size + right horizontal 'preserved t))))) (setq right (or (window-right right) (progn @@ -3259,8 +3315,10 @@ adjust-window-trailing-edge (setq right first-right) (while (and right (or (window-size-fixed-p right horizontal 'preserved) - (<= (window-size right horizontal t) - (window-min-size right horizontal 'preserved t)))) + (and (> delta 0) + (<= (window-size right horizontal t) + (window-min-size + right horizontal 'preserved t))))) (setq right (or (window-right right) (progn @@ -3289,8 +3347,9 @@ adjust-window-trailing-edge ;; Start resizing. (window--resize-reset frame horizontal) ;; Try to enlarge LEFT first. - (setq this-delta (window--resizable - left delta horizontal ignore 'after nil nil pixelwise)) + (setq this-delta + (window--resizable + left delta horizontal ignore 'after nil nil pixelwise)) (unless (zerop this-delta) (window--resize-this-window left this-delta horizontal ignore t 'before @@ -3740,7 +3799,9 @@ one-window-p (defun window-deletable-p (&optional window) "Return t if WINDOW can be safely deleted from its frame. WINDOW must be a valid window and defaults to the selected one. -Return `frame' if deleting WINDOW should also delete its frame." + +Return `frame' if WINDOW is the root window of its frame and that +frame can be safely deleted." (setq window (window-normalize-window window)) (unless (or ignore-window-parameters @@ -3767,10 +3828,14 @@ window-deletable-p (let ((minibuf (active-minibuffer-window))) (and minibuf (eq frame (window-frame minibuf))))) 'frame)) + ((window-minibuffer-p window) + ;; If WINDOW is the minibuffer window of a non-minibuffer-only + ;; frame, it cannot be deleted separately. + nil) ((or ignore-window-parameters - (not (eq window (window--major-non-side-window frame)))) - ;; WINDOW can be deleted unless it is the major non-side window of - ;; its frame. + (not (eq window (window-main-window frame)))) + ;; Otherwise, WINDOW can be deleted unless it is the main window + ;; of its frame. t)))) (defun window--in-subtree-p (window root) @@ -3826,11 +3891,14 @@ delete-window (throw 'done (delete-window atom-root)))) ((not parent) (error "Attempt to delete minibuffer or sole ordinary window")) - ((eq window (window--major-non-side-window frame)) - (error "Attempt to delete last non-side window"))) + ((eq window (window-main-window frame)) + (error "Attempt to delete main window of frame %s" frame))) (let* ((horizontal (window-left-child parent)) (size (window-size window horizontal t)) + (window-combination-resize + (or window-combination-resize + (window-parameter parent 'window-side))) (frame-selected (window--in-subtree-p (frame-selected-window frame) window)) ;; Emacs 23 preferably gives WINDOW's space to its left @@ -3886,8 +3954,7 @@ delete-other-windows (setq window (window-normalize-window window)) (let* ((frame (window-frame window)) (function (window-parameter window 'delete-other-windows)) - (window-side (window-parameter window 'window-side)) - atom-root side-main) + atom-root main) (window--check frame) (catch 'done (cond @@ -3905,18 +3972,48 @@ delete-other-windows (if (eq atom-root (frame-root-window frame)) (error "Root of atomic window is root window of its frame") (throw 'done (delete-other-windows atom-root)))) - ((memq window-side window-sides) + ((window-parameter window 'window-side) (error "Cannot make side window the only window")) ((and (window-minibuffer-p window) (not (eq window (frame-root-window window)))) (error "Can't expand minibuffer to full frame"))) - ;; If WINDOW is the major non-side window, do nothing. - (if (window-with-parameter 'window-side) - (setq side-main (window--major-non-side-window frame)) - (setq side-main (frame-root-window frame))) - (unless (eq window side-main) - (delete-other-windows-internal window side-main) + (cond + ((or ignore-window-parameters + (not (window-with-parameter 'no-delete-other-window nil frame))) + (setq main (frame-root-window frame))) + ((catch 'tag + (walk-window-tree + (lambda (other) + (when (or (and (window-parameter other 'window-side) + (not (window-parameter + other 'no-delete-other-window))) + (and (not (window-parameter other 'window-side)) + (window-parameter + other 'no-delete-other-window))) + (throw 'tag nil)))) + t) + (setq main (window-main-window frame))) + (t + ;; Delete other windows via `delete-window' because either a + ;; side window is or a non-side-window is not deletable. + (dolist (other (window-list frame)) + (when (and (window-live-p other) + (not (eq other window)) + (not (window-parameter + other 'no-delete-other-window)) + ;; When WINDOW and the other window are part of the + ;; same atomic window, don't delete the other. + (or (not atom-root) + (not (eq (window-atom-root other) atom-root)))) + (condition-case nil + (delete-window other) + (error nil)))) + (throw 'done nil))) + + ;; If WINDOW is the main window of its frame do nothing. + (unless (eq window main) + (delete-other-windows-internal window main) (run-window-configuration-change-hook frame) (window--check frame)) ;; Always return nil. @@ -4066,6 +4163,7 @@ switch-to-prev-buffer (interactive) (let* ((window (window-normalize-window window t)) (frame (window-frame window)) + (window-side (window-parameter window 'window-side)) (old-buffer (window-buffer window)) ;; Save this since it's destroyed by `set-window-buffer'. (next-buffers (window-next-buffers window)) @@ -4076,7 +4174,7 @@ switch-to-prev-buffer (unless (setq window (minibuffer-selected-window)) (error "Window %s is a minibuffer window" window))) - (when (window-dedicated-p window) + (unless (memq (window-dedicated-p window) '(nil side)) ;; Don't switch in dedicated window. (error "Window %s is dedicated to buffer %s" window old-buffer)) @@ -4106,23 +4204,27 @@ switch-to-prev-buffer ;; buffer we don't reverse the global buffer list to avoid showing ;; a buried buffer instead. Otherwise, we must reverse the global ;; buffer list in order to make sure that switching to the - ;; previous/next buffer traverse it in opposite directions. - (dolist (buffer (if bury-or-kill - (buffer-list frame) - (nreverse (buffer-list frame)))) - (when (and (buffer-live-p buffer) - (not (eq buffer old-buffer)) - (or (null pred) (funcall pred buffer)) - (not (eq (aref (buffer-name buffer) 0) ?\s)) - (or bury-or-kill (not (memq buffer next-buffers)))) - (if (and (not switch-to-visible-buffer) - (get-buffer-window buffer frame)) - ;; Try to avoid showing a buffer visible in some other window. - (unless visible - (setq visible buffer)) - (setq new-buffer buffer) - (set-window-buffer-start-and-point window new-buffer) - (throw 'found t)))) + ;; previous/next buffer traverse it in opposite directions. Skip + ;; this step for side windows. + (unless window-side + (dolist (buffer (if bury-or-kill + (buffer-list frame) + (nreverse (buffer-list frame)))) + (when (and (buffer-live-p buffer) + (not (eq buffer old-buffer)) + (or (null pred) (funcall pred buffer)) + (not (eq (aref (buffer-name buffer) 0) ?\s)) + ;; Don't show a buffer shown in a side window before. + (not (buffer-local-value 'window-sides-shown buffer)) + (or bury-or-kill (not (memq buffer next-buffers)))) + (if (and (not switch-to-visible-buffer) + (get-buffer-window buffer frame)) + ;; Try to avoid showing a buffer visible in some other window. + (unless visible + (setq visible buffer)) + (setq new-buffer buffer) + (set-window-buffer-start-and-point window new-buffer) + (throw 'found t))))) (unless bury-or-kill ;; Scan reverted next buffers last (must not use nreverse ;; here!). @@ -4184,6 +4286,7 @@ switch-to-next-buffer (interactive) (let* ((window (window-normalize-window window t)) (frame (window-frame window)) + (window-side (window-parameter window 'window-side)) (old-buffer (window-buffer window)) (next-buffers (window-next-buffers window)) (pred (frame-parameter frame 'buffer-predicate)) @@ -4193,7 +4296,7 @@ switch-to-next-buffer (unless (setq window (minibuffer-selected-window)) (error "Window %s is a minibuffer window" window))) - (when (window-dedicated-p window) + (unless (memq (window-dedicated-p window) '(nil side)) ;; Don't switch in dedicated window. (error "Window %s is dedicated to buffer %s" window old-buffer)) @@ -4211,20 +4314,23 @@ switch-to-next-buffer window new-buffer (nth 1 entry) (nth 2 entry)) (throw 'found t))) ;; Scan the buffer list of WINDOW's frame next, skipping previous - ;; buffers entries. - (dolist (buffer (buffer-list frame)) - (when (and (buffer-live-p buffer) - (not (eq buffer old-buffer)) - (or (null pred) (funcall pred buffer)) - (not (eq (aref (buffer-name buffer) 0) ?\s)) - (not (assq buffer (window-prev-buffers window)))) - (if (and (not switch-to-visible-buffer) - (get-buffer-window buffer frame)) - ;; Try to avoid showing a buffer visible in some other window. - (setq visible buffer) - (setq new-buffer buffer) - (set-window-buffer-start-and-point window new-buffer) - (throw 'found t)))) + ;; buffers entries. Skip this step for side windows. + (unless window-side + (dolist (buffer (buffer-list frame)) + (when (and (buffer-live-p buffer) + (not (eq buffer old-buffer)) + (or (null pred) (funcall pred buffer)) + (not (eq (aref (buffer-name buffer) 0) ?\s)) + ;; Don't show a buffer shown in a side window before. + (not (buffer-local-value 'window-sides-shown buffer)) + (not (assq buffer (window-prev-buffers window)))) + (if (and (not switch-to-visible-buffer) + (get-buffer-window buffer frame)) + ;; Try to avoid showing a buffer visible in some other window. + (setq visible buffer) + (setq new-buffer buffer) + (set-window-buffer-start-and-point window new-buffer) + (throw 'found t))))) ;; Scan WINDOW's reverted previous buffers last (must not use ;; nreverse here!) (dolist (entry (reverse (window-prev-buffers window))) @@ -5286,12 +5392,17 @@ window--state-get-1 (scroll-bars . ,(window-scroll-bars window)) (vscroll . ,(window-vscroll window)) (dedicated . ,(window-dedicated-p window)) - (point . ,(if writable point - (copy-marker point - (buffer-local-value - 'window-point-insertion-type - buffer)))) - (start . ,(if writable start (copy-marker start))))))))) + (point . ,(if writable + point + (with-current-buffer buffer + (copy-marker point + (buffer-local-value + 'window-point-insertion-type + buffer))))) + (start . ,(if writable + start + (with-current-buffer buffer + (copy-marker start)))))))))) (tail (when (memq type '(vc hc)) (let (list) @@ -5363,7 +5474,8 @@ window--state-put-1 ((memq type '(vc hc)) (let* ((horizontal (eq type 'hc)) (total (window-size window horizontal pixelwise)) - (first t) + (first t) + (window-combination-limit (cdr (assq 'combination-limit state))) size new) (dolist (item state) ;; Find the next child window. WINDOW always points to the @@ -5406,12 +5518,9 @@ window--state-put-1 (frame-char-height (window-frame window)) 1))))) (if (window-sizable-p window (- size) horizontal 'safe pixelwise) - (let* ((window-combination-limit - (assq 'combination-limit item))) - ;; We must inherit the combination limit, otherwise - ;; we might mess up handling of atomic and side - ;; window. - (setq new (split-window window size horizontal pixelwise))) + (progn + (setq new (split-window window size horizontal pixelwise)) + (setq window-combination-limit nil)) ;; Give up if we can't resize window down to safe sizes. (error "Cannot resize window %s" window)) @@ -5462,7 +5571,8 @@ window--state-put-2 (nth 3 scroll-bars) (nth 5 scroll-bars))) (set-window-vscroll window (cdr (assq 'vscroll state))) ;; Adjust vertically. - (if (memq window-size-fixed '(t height)) + (if (or (memq window-size-fixed '(t height)) + (window-preserved-size window)) ;; A fixed height window, try to restore the ;; original size. (let ((delta @@ -5484,7 +5594,8 @@ window--state-put-2 window delta nil ignore nil nil nil pixelwise)) (window-resize window delta nil ignore pixelwise)))) ;; Adjust horizontally. - (if (memq window-size-fixed '(t width)) + (if (or (memq window-size-fixed '(t width)) + (window-preserved-size window t)) ;; A fixed width window, try to restore the original ;; size. (let ((delta @@ -5494,8 +5605,8 @@ window--state-put-2 (window-size window t pixelwise))) window-size-fixed) (when (window--resizable-p - window delta nil nil nil nil nil pixelwise) - (window-resize window delta nil nil pixelwise))) + window delta t nil nil nil nil pixelwise) + (window-resize window delta t nil pixelwise))) ;; Else check whether the window is not wide enough. (let* ((min-size (window-min-size window t ignore pixelwise)) (delta (- min-size (window-size window t pixelwise)))) @@ -5540,16 +5651,14 @@ window-state-put ;; When WINDOW is internal, reduce it to a live one to put STATE into, ;; see Bug#16793. (unless (window-live-p window) - (let ((root (frame-root-window window))) - (if (eq window root) - (setq window (frame-first-window root)) - (setq root window) - (setq window (catch 'live - (walk-window-subtree - (lambda (window) - (when (window-live-p window) - (throw 'live window))) - root)))) + (let ((root window)) + (setq window (catch 'live + (walk-window-subtree + (lambda (window) + (when (and (window-live-p window) + (not (window-parameter window 'window-side))) + (throw 'live window))) + root))) (delete-other-windows-internal window root))) (set-window-dedicated-p window nil) @@ -6314,15 +6423,15 @@ window--display-buffer (set-window-dedicated-p window dedicated)) (when (memq type '(window frame)) (set-window-prev-buffers window nil))) - (let ((parameter (window-parameter window 'quit-restore)) + (let ((quit-restore (window-parameter window 'quit-restore)) (height (cdr (assq 'window-height alist))) (width (cdr (assq 'window-width alist))) (size (cdr (assq 'window-size alist))) (preserve-size (cdr (assq 'preserve-size alist)))) (cond ((or (eq type 'frame) - (and (eq (car parameter) 'same) - (eq (nth 1 parameter) 'frame))) + (and (eq (car quit-restore) 'same) + (eq (nth 1 quit-restore) 'frame))) ;; Adjust size of frame if asked for. (cond ((not size)) @@ -6340,8 +6449,8 @@ window--display-buffer ((functionp size) (ignore-errors (funcall size window))))) ((or (eq type 'window) - (and (eq (car parameter) 'same) - (eq (nth 1 parameter) 'window))) + (and (eq (car quit-restore) 'same) + (eq (nth 1 quit-restore) 'window))) ;; Adjust height of window if asked for. (cond ((not height)) @@ -6377,8 +6486,12 @@ window--display-buffer ;; Preserve window size if asked for. (when (consp preserve-size) (window-preserve-size window t (car preserve-size)) - (window-preserve-size window nil (cdr preserve-size)))))) - + (window-preserve-size window nil (cdr preserve-size))))) + ;; Assign any window parameters specified. + (let ((parameters (cdr (assq 'window-parameters alist)))) + (dolist (parameter parameters) + (set-window-parameter + window (car parameter) (cdr parameter))))) window)) (defun window--maybe-raise-frame (frame) @@ -6602,6 +6715,9 @@ display-buffer preserve the width of the window, (nil . t) to preserve its height or (t . t) to preserve both. + `window-parameters' -- Value specifies an alist of window + parameters to give the chosen window. + The ACTION argument to `display-buffer' can also have a non-nil and non-list value. This means to display the buffer in a window other than the selected one, even if it is already displayed in @@ -6954,7 +7070,7 @@ display-buffer-at-bottom (and (not (frame-parameter nil 'unsplittable)) (setq window (condition-case nil - (split-window (window--major-non-side-window)) + (split-window (window-main-window)) (error nil))) (window--display-buffer buffer window 'window alist display-buffer-mark-dedicated))