commit b8fd71d5709650c1aced92c772f70595c51881d2 (HEAD, refs/remotes/origin/master) Author: Martin Rudalics Date: Wed Oct 5 10:28:36 2016 +0200 Document and fix some bugs with side windows Add a documentation for side windows and fix some bugs found when testing their behavior. Also add a new window parameter `no-delete-other-window', a new `display-buffer' alist member called `window-parameters', and functions to toggle and reverse side windows on a frame. Add new function `window-swap-states' to exchange states of two live windows. * lisp/window.el (display-buffer-in-atom-window): Use `split-window-no-error'. (window-sides-vertical): Maybe change layouts when setting this variable. (window-sides-reversed): New option. (window-sides-slots): Rewrite doc-string and help echoes. (window-sides-shown): New buffer-local variable set when showing a buffer in a side window. (window--sides-inhibit-check): New variable. (window--sides-reverse-on-frame-p, window-toggle-side-windows) (window--sides-reverse-all, window--sides-reverse-frame) (window--sides-reverse-side, window--sides-reverse) (window--sides-verticalize-frame, window--sides-verticalize) (window--sides-check-failed): New functions. (window--side-window-p): Remove function. (window--major-non-side-window): Rename to `window-main-window', adjust callers, rewrite doc-string. (window--major-side-window): Rename to `window--make-major-side-window-next-to', adjust caller, fix doc-string. (display-buffer-in-major-side-window): Rename to `window--make-major-side-window', adjust caller, rewrite doc-string. Make `window-side' and `window-slot' parameters persistent (Bug#23858). Don't set `delete-window' parameter. Add `preserve-size' entry to ALIST. (delete-side-window): Remove function. (display-buffer-in-side-window): Fix doc-string. Don't set `delete-window' parameter. Add `preserve-size' entry to ALIST. (window--side-check): Rename to window--sides-check. Rewrite completely. Adjust caller. (window-resize-no-error): Don't describe PIXELWISE argument. (adjust-window-trailing-edge): Fix bug that disallowed re-enlarging windows that were too small. (window-deletable-p): Don't tell that a minibuffer window on a non-minibuffer-only frame can be deleted. Fix doc-string. (delete-window): Handle deleting a side window here (the `delete-window' parameter is no more set for side windows). (delete-other-windows): Handle ‘no-delete-other-window' parameter. Don't treat side windows separately (see discussion of Bug#24368) but keep optimization that makes the main window the root window of its frame. (switch-to-prev-buffer, switch-to-next-buffer): Handle side windows and buffers shown in side windows separately. (split-window-no-error): New function. (window--state-get-1): Use right buffer when storing window point and start positions and WRITABLE is nil (Bug#24368). (window--state-put-1): Fix handling of `window-combination-limit'. Use `split-window-no-error'. (window--state-put-2): Try to restore windows with preserved size to their original size. Fix bug where a fixed window's width was not preserved. (window-state-put): When reducing an internal window to a live one, don't choose a side window. (window-swap-states): New function. (window-splittable-p): Don't call `window--side-window-p'. (window--display-buffer): Handle `window-parameters' ALIST entry. Minor rewrite. (display-buffer): Mention `window-parameters' entry in doc-string. (display-buffer-at-bottom): Call `split-window-no-error'. * doc/lispref/elisp.texi (Top): New section "Side Windows". * doc/lispref/windows.texi (Deleting Windows): Fix descriptions of `delete-window' and `delete-other-windows' wrt window parameters and side windows. (Display Action Functions): Mention `window-parameters' ALIST entry. (Side Windows): New section (Bug#18170). (Window Configurations): Describe new function `window-swap-states'. (Window Parameters): Say that functions may behave specially when their homonymous window parameter has been set. Mention new parameter `no-delete-other-window'. Add cross reference for `window-side' and `window-slot' parameters. diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index 3297e53..1c6e059 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -1038,6 +1038,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. @@ -1051,6 +1052,14 @@ Windows 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..3ab8c95 100644 --- a/doc/lispref/windows.texi +++ b/doc/lispref/windows.texi @@ -33,6 +33,7 @@ is displayed in 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 @@ Configurations}). @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 @@ adjacent sibling windows, if any. However, if the variable 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 -@code{ignore-window-parameters} 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 -@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}. Otherwise, this function obeys the -@code{window-atom} or @code{window-side} window parameter, if any. -@xref{Window Parameters}. +The behavior of this function may be altered by the window parameters of +@var{window}, so long as the variable @code{ignore-window-parameters} 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 @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 -@code{ignore-window-parameters} 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 +@var{window}, so long as the variable @code{ignore-window-parameters} 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}. @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 non-@code{nil}. @end deffn @deffn Command delete-windows-on &optional buffer-or-name frame @@ -2574,7 +2579,12 @@ assumed that when the caller specifies a non-@code{nil} 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 +@code{window-parameters} 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,295 @@ other frame on the same terminal. @end defopt +@node Side Windows +@section Side Windows +@cindex side windows +@cindex main window +@cindex 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 +@code{display-buffer-at-bottom} (@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). + +@menu +* 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. +@end menu + + +@node Displaying Buffers in Side Windows +@subsection 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. + +@defun 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 +@code{display-buffer}. The following symbols in @var{alist} are special +for this function: + +@table @code +@item 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. + +@item 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 +@code{slot} value. If unspecified, the window is located in the middle +of the specified side. +@end 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}. +@end 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 +@code{delete-other-windows} 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 +@code{switch-to-prev-buffer} 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. + + +@node Side Window Options and Functions +@subsection Side Window Options and Functions + +The following options provide additional control over the placement of +side windows. + +@defopt window-sides-vertical +If non-@code{nil}, 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. +@end defopt + +@defopt 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 +@code{nil}, 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 +@code{window-slot} parameter changed accordingly. +@end defopt + +@defopt window-sides-reversed +This option specifies whether top/bottom side windows should appear in +reverse order. When this is @code{nil}, side windows on the top and +bottom of a frame are always drawn from left to right with increasing +slot values. When this is @code{t}, the drawing order is reversed and +side windows on the top and bottom of a frame are drawn from right to +left with increasing slot values. + +When this is @code{bidi}, the drawing order is reversed if and only if +the value of @code{bidi-paragraph-direction} is @code{right-to-left} in +the buffer displayed in the window most recently selected within the +main window area of this frame. Sometimes that window may be hard to +find, so heuristics are used to avoid that the drawing order changes +inadvertently when another window gets selected. + +The layout of side windows on the left or right of a frame is not +affected by the value of this variable. +@end defopt + +When a frame has side windows, the following function returns the main +window of that frame. + +@defun 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}. +@end defun + +The following command is handy to toggle the appearance of all side +windows on a specified frame. + +@deffn 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 +@code{window-state} frame parameter and deletes all side windows on +@var{frame} 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 +@var{frame} leaving @var{frame}'s main window alone. + +An error is signaled if @var{frame} has no side window and no saved +state is found. +@end deffn + + +@node Frame Layouts with Side Windows +@subsection 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: + +@smallexample +@group + ___________________________________ + | *Buffer List* | + |___________________________________| + | | | | + | * | | * | + | d | | T | + | i | | a | + | r | Main Window Area | g | + | e | | s | + | d | | * | + | * | | | + |_____|_______________________|_____| + | *help*/*grep*/ | *shell*/ | + | *Completions* | *compilation* | + |_________________|_________________| + | Echo Area | + |___________________________________| + + +@end group +@end 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. + +@example +@group +(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))) +@end group +@end 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 +@file{*Completions*} 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 non-@code{nil} 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}. +@xref{Resizing 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 +@code{no-other-window} 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. + +@example +@group +(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)))) +@end group +@end 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 +@kbd{M-x dired-default-directory-on-left} should now reproduce the frame +layout sketched above. + + @node Window Point @section Windows and Point @cindex window position @@ -4242,6 +4541,25 @@ is @code{safe}, this means windows can get as small as one line and/or two columns. @end defun +The functions @code{window-state-get} and @code{window-state-put} also +allow to exchange the contents of two live windows. The following +function does precisely that: + +@deffn Command window-swap-states &optional window-1 window-2 size +This command swaps the states of the two live windows @var{window-1} and +@var{window-2}. @var{window-1} must specify a live window and defaults +to the selected one. @var{window-2} must specify a live window and +defaults to the window following @var{window-1} in the cyclic ordering +of windows, excluding minibuffer windows and including live windows on +all visible frames. + +Optional argument @var{size} non-@code{nil} means to try swapping the +sizes of @var{window-1} and @var{window-2} as well. A value of +@code{height} means to swap heights only, a value of @code{width} +means to swap widths only, while @code{t} means to swap both widths +and heights, if possible. Frames are not resized by this function. +@end deffn + @node Window Parameters @section Window Parameters @@ -4279,6 +4597,7 @@ earlier by @code{window-state-get}, all cloned windows have their parameters reset to @code{nil}. The following variable allows you to override the standard behavior: +@cindex 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 +4627,10 @@ may fail with an @code{invalid-read-syntax} error. @end defvar Some functions (notably @code{delete-window}, -@code{delete-other-windows} 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 -non-@code{nil} value: +@code{delete-other-windows} 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 non-@code{nil} value: @defvar ignore-window-parameters If this variable is non-@code{nil}, some standard functions do not @@ -4337,6 +4656,10 @@ This parameter affects the execution of @code{delete-window} This parameter affects the execution of @code{delete-other-windows} (@pxref{Deleting Windows}). +@item @code{no-delete-other-window} +This parameter marks the window as not deletable by +@code{delete-other-windows} (@pxref{Deleting Windows}). + @item @code{split-window} This parameter affects the execution of @code{split-window} (@pxref{Splitting Windows}). @@ -4387,6 +4710,10 @@ The fourth element is the buffer whose display caused the creation of this parameter. @code{quit-restore-window} deletes the specified window only if it still shows that buffer. +@item @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 non-@code{nil}, specify the minimum values (in columns) for the left @@ -4409,8 +4736,7 @@ applications. It might be replaced by an improved solution in future 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/etc/NEWS b/etc/NEWS index bd94c94..4268a40 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -611,6 +611,27 @@ collection). ** The new functions 'make-nearby-temp-file' and 'temporary-file-directory' can be used for creation of temporary files of remote or mounted directories. +** Changes in Frame- and Window- Handling + ++++ +*** Support for side windows is now official. The display action +function `display-buffer-in-side-window' will display its buffer in a +side window. Functions for toggling all side windows on a frame, +changing and reversing the layout of side windows and returning the main +(major non-side) window of a frame are provided. For details consult +the section "Side Windows" in the Elisp manual. + ++++ +*** New `display-buffer' alist entry `window-parameters' allows to +assign window parameters to the window used for displaying the buffer. + ++++ +*** New window parameter `no-delete-other-window' prevents that +its window gets deleted by `delete-other-windows'. + ++++ +*** New command `window-swap-states' swaps the states of two live +windows. * Changes in Emacs 26.1 on Non-Free Operating Systems diff --git a/lisp/window.el b/lisp/window.el index 6728ea3..4511267 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -657,7 +657,7 @@ failed." (setq window (window-normalize-window window)) (setq root (window-atom-root window)) ;; Split off new window. - (when (setq new (split-window window nil side)) + (when (setq new (split-window-no-error window nil side)) (window-make-atom (if (and root (not (eq root window))) ;; When WINDOW was part of an atomic window and we did not @@ -709,24 +709,50 @@ no child windows or one of its child windows is not atomic." (window--atom-check-1 (frame-root-window frame))) ;; Side windows. -(defvar window-sides '(left top right bottom) - "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." + "If non-nil, left and right side windows occupy full frame height. +If nil, top and bottom side windows occupy full frame width." :type 'boolean + :initialize 'custom-initialize-default + :set 'window--sides-verticalize :group 'windows - :version "24.1") + :version "26.1") + +(defcustom window-sides-reversed nil + "Whether top/bottom side windows appear in reverse order. +When this is nil, side windows on the top and bottom of a frame +are always drawn from left to right with increasing slot values. +When this is t, side windows on the top and bottom of a frame are +always drawn from right to left with increasing slot values. + +When this is `bidi', the drawing order is like that for the value +t if the value of `bidi-paragraph-direction' is `right-to-left' +in the buffer most recently shown in the window selected within +the main window area of this frame. + +The layout of side windows on the left or right of a frame is not +affected by the value of this variable." + :type + '(choice (const :tag "Never" nil) + (const :tag "Bidi" bidi) + (const :tag "Always" t)) + :initialize 'custom-initialize-default + :set 'window--sides-reverse + :group 'windows + :version "26.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." + "Number of available side window slots on each side of a frame. +The value is a list of four elements specifying the maximum +number of side windows that may be created on the left, top, +right and bottom side of any frame. + +If an element is a number, `display-buffer-in-side-window' will +refrain from making a new side window if the number of windows on +that side is equal to or exceeds that number. Rather, it will +reuse the window whose `window-slot' value is nearest to the slot +specified via its ALIST argument. If an element is nil, this +means there's no bound on the number of windows on that side." :version "24.1" :risky t :type @@ -734,56 +760,94 @@ number of slots on that side." :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) -(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) - (and (window-child window) - (or (window-parameter - (window-child window) 'window-side) - (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. +(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, `switch-to-prev-buffer' +and `switch-to-next-buffer' will refrain from showing 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 any effect provided by this variable.") + +(defvar window--sides-inhibit-check nil + "Non-nil means inhibit any checks on side windows.") + +(defun window--sides-reverse-on-frame-p (frame) + "Return non-nil when side windows should appear reversed on FRAME. +This uses some heuristics to guess the user's intentions when the +selected window of FRAME is a side window ." + (cond + ;; Reverse when `window-sides-reversed' is t. Do not reverse when + ;; `window-sides-reversed' is nil. + ((memq window-sides-reversed '(nil t)) + window-sides-reversed) + ;; Reverse when FRAME's selected window shows a right-to-left buffer. + ((let ((window (frame-selected-window frame))) + (when (and (not (window-parameter window 'window-side)) + (or (not (window-minibuffer-p window)) + (setq window (minibuffer-selected-window)))) + (with-current-buffer (window-buffer window) + (eq bidi-paragraph-direction 'right-to-left))))) + ;; Reverse when FRAME's `window-sides-main-selected-window' parameter + ;; specifies a live window showing a right-to-left buffer. + ((let ((window (frame-parameter + frame 'window-sides-main-selected-window))) + (when (window-live-p window) + (with-current-buffer (window-buffer window) + (eq bidi-paragraph-direction 'right-to-left))))) + ;; Reverse when all windows in FRAME's main window show right-to-left + ;; buffers. + (t + (catch 'found + (walk-window-subtree + (lambda (window) + (with-current-buffer (window-buffer window) + (when (eq bidi-paragraph-direction 'left-to-right) + (throw 'found nil)))) + (window-main-window frame)) + t)))) + +(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,16 +856,20 @@ its root 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) + (window--sides-inhibit-check t) + window) ;; (1) If a window on the opposite side exists, return that window's ;; sibling. ;; (2) If the new window shall span the entire side, return the @@ -839,35 +907,37 @@ SIDE must be one of the symbols `left', `top', `right' or (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))) + (window--sides-inhibit-check t) ;; 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-no-error 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 +952,10 @@ SIDE. Return the new window, nil if its creation window failed." ;; 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 +971,27 @@ following special symbols can be used in ALIST. 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)) @@ -918,15 +1001,20 @@ following special symbols can be used in ALIST. (let* ((major (window-with-parameter 'window-side side nil t)) ;; `major' is the major window on SIDE, `windows' the list of ;; life windows on SIDE. - (windows - (when major - (let (windows) - (walk-window-tree - (lambda (window) - (when (eq (window-parameter window 'window-side) side) - (setq windows (cons window windows)))) - nil nil 'nomini) - (nreverse windows)))) + (reversed (window--sides-reverse-on-frame-p (selected-frame))) + (windows + (cond + ((window-live-p major) + (list major)) + ((window-valid-p major) + (let* ((first (window-child major)) + (next (window-next-sibling first)) + (windows (list next first))) + (setq reversed (> (window-parameter first 'window-slot) + (window-parameter next 'window-slot))) + (while (setq next (window-next-sibling next)) + (setq windows (cons next windows))) + (if reversed windows (nreverse windows)))))) (slots (when major (max 1 (window-child-count major)))) (max-slots (nth (cond @@ -935,17 +1023,18 @@ following special symbols can be used in ALIST. ((eq side 'right) 2) ((eq side 'bottom) 3)) window-sides-slots)) + (window--sides-inhibit-check t) window this-window this-slot prev-window next-window best-window best-slot abs-slot) (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 @@ -953,7 +1042,7 @@ following special symbols can be used in ALIST. (setq this-slot (window-parameter window 'window-slot)) (cond ;; The following should not happen and probably be checked - ;; by window--side-check. + ;; by window--sides-check. ((not (numberp this-slot))) ((= this-slot slot) ;; A window with a matching slot has been found. @@ -970,131 +1059,241 @@ following special symbols can be used in ALIST. (unless (and best-slot (<= best-slot abs-slot)) (setq best-window window) (setq best-slot abs-slot)) - (cond - ((<= this-slot slot) - (setq prev-window window)) - ((not next-window) - (setq next-window window))))))) - - ;; `this-window' is the first window with the same SLOT. + (if reversed + (cond + ((<= this-slot slot) + (setq next-window window)) + ((not prev-window) + (setq prev-window window))) + (cond + ((<= this-slot slot) + (setq prev-window window)) + ((not next-window) + (setq next-window window)))))))) + + ;; `this-window' is the first window with the same SLOT. ;; `prev-window' is the window with the largest slot < SLOT. A new ;; window will be created after it. ;; `next-window' is the window with the smallest slot > SLOT. A new ;; 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-no-error + 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-no-error + 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))))))))) - -(defun window--side-check (&optional frame) - "Check the side window configuration of FRAME. -FRAME defaults to the selected frame. - -A valid side window configuration preserves the following two -invariants: - -- If there exists a window whose window-side parameter is - non-nil, there must exist at least one live window whose - window-side parameter is nil. - -- If a window W has a non-nil window-side parameter (i) it must - have a parent window and that parent's window-side parameter - must be either nil or the same as for W, and (ii) any child - window of W must have the same window-side parameter as W. - -If the configuration is invalid, reset the window-side parameters -of all windows on FRAME to nil." - (let (left top right bottom none side parent parent-side) - (when (or (catch 'reset - (walk-window-tree - (lambda (window) - (setq side (window-parameter window 'window-side)) - (setq parent (window-parent window)) - (setq parent-side - (and parent (window-parameter parent 'window-side))) - ;; The following `cond' seems a bit tedious, but I'd - ;; rather stick to using just the stack. - (cond - (parent-side - (when (not (eq parent-side side)) - ;; A parent whose window-side is non-nil must - ;; have a child with the same window-side. - (throw 'reset t))) - ((not side) - (when (window-buffer window) - ;; Record that we have at least one non-side, - ;; live window. - (setq none t))) - ((if (memq side '(left top)) - (window-prev-sibling window) - (window-next-sibling window)) - ;; Left and top major side windows must not have a - ;; previous sibling, right and bottom major side - ;; windows must not have a next sibling. - (throw 'reset t)) - ;; Now check that there's no more than one major - ;; window for any of left, top, right and bottom. - ((eq side 'left) - (if left (throw 'reset t) (setq left t))) - ((eq side 'top) - (if top (throw 'reset t) (setq top t))) - ((eq side 'right) - (if right (throw 'reset t) (setq right t))) - ((eq side 'bottom) - (if bottom (throw 'reset t) (setq bottom t))) - (t - (throw 'reset t)))) - frame t 'nomini)) - ;; If there's a side window, there must be at least one - ;; non-side window. - (and (or left top right bottom) (not none))) - (walk-window-tree - (lambda (window) - (set-window-parameter window 'window-side nil)) - frame t 'nomini)))) + (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)) + (window--sides-inhibit-check t) + 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))) + (window--sides-reverse-frame frame)) + (t + (error "No side windows state found"))))) + +(defun window--sides-reverse-all () + "Maybe reverse side windows on all frames." + (unless window--sides-inhibit-check + (dolist (frame (frame-list)) + (window--sides-reverse-frame frame)))) + +(defun window--sides-reverse-frame (frame) + "Maybe reverse side windows on FRAME." + (when (eq window-sides-reversed 'bidi) + (let ((window (frame-selected-window frame))) + (unless (or (window-parameter window 'window-side) + (window-minibuffer-p window)) + (set-frame-parameter + frame 'window-sides-main-selected-window window)))) + (window--sides-reverse-side frame 'top) + (window--sides-reverse-side frame 'bottom)) + +(defun window--sides-reverse-side (frame side) + "Maybe reverse windows on SIDE of FRAME." + (let ((major (window-with-parameter 'window-side side frame t)) + (window--sides-inhibit-check t)) + (when (and major (not (window-live-p major))) + (let* ((first (window-child major)) + (reversed (> (window-parameter first 'window-slot) + (window-parameter + (window-next-sibling first) 'window-slot))) + (reverse (window--sides-reverse-on-frame-p frame))) + (unless (eq reversed reverse) + ;; We have to reverse. + (let ((last (window-last-child major))) + (while (and (not (eq first last)) + (not (eq first (window-next-sibling last)))) + (window-swap-states first last t) + (setq first (window-next-sibling first)) + (setq last (window-prev-sibling last))))))))) + +(defun window--sides-reverse (symbol value) + "Helper function for customizing `window-sides-reversed'." + (set-default symbol value) + (remove-hook 'buffer-list-update-hook 'window--sides-reverse-all) + (remove-hook 'window-configuration-change-hook 'window--sides-reverse-all) + (dolist (frame (frame-list)) + (set-frame-parameter frame 'window-sides-main-selected-window nil)) + (when (eq value 'bidi) + (add-hook 'buffer-list-update-hook 'window--sides-reverse-all) + (add-hook 'window-configuration-change-hook 'window--sides-reverse-all)) + (window--sides-reverse-all)) + +(defun window--sides-verticalize-frame (&optional frame) + "Maybe change side windows layout on specified FRAME." + (setq frame (window-normalize-frame frame)) + (let ((window--sides-inhibit-check t) + (root (frame-root-window frame)) + (main (window-main-window frame))) + (when (and (not (eq main root)) + (not (eq (window-parent main) root)) + (window-combined-p main window-sides-vertical)) + (let* ((window--sides-inhibit-check t) + (ignore-window-parameters t) + (first (window-child root)) + (first-state + (and first (window-parameter first 'window-side) + (window-state-get first))) + (last (window-last-child root)) + (last-state + (and last (window-parameter last 'window-side) + (window-state-get last))) + (dummy (get-buffer-create " *dummy*")) + major) + (unwind-protect + (progn + (when first-state (delete-window first)) + (when last-state (delete-window last)) + (when first-state + (setq major (window--make-major-side-window + dummy (if window-sides-vertical 'top 'left) 0)) + (window-state-put first-state major t)) + (when last-state + (setq major (window--make-major-side-window + dummy (if window-sides-vertical 'bottom 'right) 0)) + (window-state-put last-state major t))) + (kill-buffer " *dummy*")))))) + +(defun window--sides-verticalize (symbol value) + "Helper function for customizing `window-sides-vertical'." + (set-default symbol value) + (dolist (frame (frame-list)) + (window--sides-verticalize-frame frame))) + +(defun window--sides-check-failed (frame) + "Helper function for `window--sides-check'." + (catch 'failed + ;; FRAME must have a main window. + (unless (window-main-window frame) + (error "Frame %s has no main window" frame) + (throw 'failed t)) + ;; Now check the side windows. + (dolist (side '(left top right bottom)) + (let ((window (window-with-parameter 'window-side side frame t))) + (when window + ;; If WINDOW is live there must be no other window on this frame + ;; with the same `window-side' parameter. + (if (window-live-p window) + (walk-window-tree + (lambda (this) + (when (and (eq (window-parameter this 'window-side) side) + (not (eq this window))) + (error "Window %s has same side %s as window %s but no common parent" + this side window) + (throw 'failed t))) + frame t 'nomini) + (walk-window-tree + (lambda (this) + (if (eq (window-parent this) window) + (unless (eq (window-parameter this 'window-side) side) + (error "Window %s has not same side %s as its parent %s" + this side window) + (throw 'failed t)) + (when (and (eq (window-parameter this 'window-side) side) + (not (eq this window))) + (error "Window %s has same side %s as major side window %s but its parent is %s" + this side window (window-parent this)) + (throw 'failed t)))) + frame t 'nomini))))))) + +(defun window--sides-check (frame) + "Check side windows configuration of FRAME. +In a valid side windows configuration there can be at most one +internal side window on each side and all its children must be +live and have the same `window-side' parameter and no other +window with the same `window-side' parameter exists on FRAME. If +there is no such internal window, there may be at most one window +with this side's `window-side' parameter on FRAME. + +If the configuration is invalid, reset the `window-side' +parameters of all windows on FRAME." + (when (and (not window--sides-inhibit-check) + (window-with-parameter 'window-side nil frame t) + (window--sides-check-failed frame)) + ;; Reset all `window-side' parameters. + (walk-window-tree + (lambda (window) + (set-window-parameter window 'window-side nil)) + frame t 'nomini) + (message "Side windows configuration reset for frame %s" frame))) (defun window--check (&optional frame) "Check atomic and side windows on FRAME. FRAME defaults to the selected frame." - (window--side-check frame) + (window--sides-check frame) (window--atom-check frame)) ;; Dumping frame/window contents. @@ -2631,10 +2830,7 @@ instead." "Resize WINDOW vertically if it is resizable by DELTA lines. This function is like `window-resize' but does not signal an error when WINDOW cannot be resized. For the meaning of the -optional arguments see the documentation of `window-resize'. - -Optional argument PIXELWISE non-nil means interpret DELTA as -pixels." +optional arguments see the documentation of `window-resize'." (when (window--resizable-p window delta horizontal ignore nil nil nil pixelwise) (window-resize window delta horizontal ignore pixelwise))) @@ -3224,8 +3420,10 @@ move it as far as possible in the desired direction." (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 +3443,8 @@ move it as far as possible in the desired direction." (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 +3458,10 @@ move it as far as possible in the desired direction." (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 +3490,9 @@ move it as far as possible in the desired direction." ;; 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 +3942,9 @@ and no others." (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 +3971,14 @@ Return `frame' if deleting WINDOW should also delete its frame." (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 +4034,14 @@ that is its frame's root 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 +4097,7 @@ window signal an error." (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 +4115,48 @@ window signal an error." (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 +4306,7 @@ to it." (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 +4317,7 @@ to it." (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 +4347,27 @@ to it." ;; 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 +4429,7 @@ found." (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 +4439,7 @@ found." (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 +4457,23 @@ found." 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))) @@ -4700,7 +4949,7 @@ frame. The selected window is not changed by this function." ;; side window, throw an error unless `window-combination-resize' ;; equals 'side. ((and (not (eq window-combination-resize 'side)) - (window--side-window-p window)) + (window-parameter window 'window-side)) (error "Cannot split side window or parent of side window")) ;; If `window-combination-resize' is 'side and window has a side ;; window sibling, bind `window-combination-limit' to t. @@ -4894,6 +5143,17 @@ frame. The selected window is not changed by this function." ;; Always return the new window. new))))) +(defun split-window-no-error (&optional window size side pixelwise) + "Make a new window adjacent to WINDOW. +This function is like `split-window' but does not signal an error +when WINDOW cannot be split. + +For the meaning of all arguments see the documentation of +`split-window'." + (condition-case nil + (split-window window size side pixelwise) + (error nil))) + ;; I think this should be the default; I think people will prefer it--rms. (defcustom split-window-keep-point t "If non-nil, \\[split-window-below] preserves point in the new window. @@ -5286,12 +5546,17 @@ specific buffers." (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 +5628,8 @@ value can be also stored on disk and read back in a new session." ((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 +5672,10 @@ value can be also stored on disk and read back in a new session." (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-no-error + 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 +5726,8 @@ value can be also stored on disk and read back in a new session." (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 +5749,8 @@ value can be also stored on disk and read back in a new session." 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 +5760,8 @@ value can be also stored on disk and read back in a new session." (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 +5806,14 @@ windows can get as small as `window-safe-min-height' and ;; 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) @@ -5634,6 +5898,75 @@ windows can get as small as `window-safe-min-height' and (when (eq (window-deletable-p window) t) (delete-window window)))) (window--check frame)))) + +(defun window-swap-states (&optional window-1 window-2 size) + "Swap the states of live windows WINDOW-1 and WINDOW-2. +WINDOW-1 must specify a live window and defaults to the selected +one. WINDOW-2 must specify a live window and defaults to the +window following WINDOW-1 in the cyclic ordering of windows, +excluding minibuffer windows and including live windows on all +visible frames. + +Optional argument SIZE non-nil means to try swapping the sizes of +WINDOW-1 and WINDOW-2 as well. A value of `height' means to swap +heights only, a value of `width' means to swap widths only, while +t means to swap both widths and heights, if possible. Frames are +not resized by this function." + (interactive) + (setq window-1 (window-normalize-window window-1 t)) + (if window-2 + (unless (window-live-p window-2) + (error "%s is not a live window" window-2)) + (setq window-2 (next-window window-1 'nomini 'visible))) + (unless (eq window-1 window-2) + (let* ((height (memq size '(t height))) + (width (memq size '(t width))) + (state-1 (window-state-get window-1)) + (width-1 (and width (window-text-width window-1 t))) + (height-1 (and height (window-text-height window-1 t))) + (state-2 (window-state-get window-2)) + (width-2 (and width (window-text-width window-2 t))) + (height-2 (and height (window-text-height window-2 t))) + old preserved) + ;; Swap basic states. + (window-state-put state-1 window-2 t) + (window-state-put state-2 window-1 t) + ;; Swap overlays with `window' property. + (with-current-buffer (window-buffer window-1) + (dolist (overlay (overlays-in (point-min) (point-max))) + (let ((window (overlay-get overlay 'window))) + (cond + ((not window)) + ((eq window window-1) + (overlay-put overlay 'window window-2)) + ((eq window window-2) + (overlay-put overlay 'window window-1)))))) + (unless (eq (window-buffer window-1) (window-buffer window-2)) + (with-current-buffer (window-buffer window-2) + (dolist (overlay (overlays-in (point-min) (point-max))) + (let ((window (overlay-get overlay 'window))) + (cond + ((not window)) + ((eq window window-1) + (overlay-put overlay 'window window-2)) + ((eq window window-2) + (overlay-put overlay 'window window-1))))))) + ;; Try to swap window sizes. + (when size + (unless (= (setq old (window-text-width window-1 t)) width-2) + (window-resize-no-error window-1 (- width-2 old) t t t)) + (unless (= (setq old (window-text-width window-2 t)) width-1) + (setq preserved (window-preserved-size window-1 t)) + (window-preserve-size window-1 t t) + (window-resize-no-error window-2 (- width-1 old) t t t) + (window-preserve-size window-1 t preserved)) + (unless (= (setq old (window-text-height window-1 t)) height-2) + (window-resize-no-error window-1 (- height-2 old) nil t t)) + (unless (= (setq old (window-text-height window-2 t)) height-1) + (setq preserved (window-preserved-size window-1)) + (window-preserve-size window-1 nil t) + (window-resize-no-error window-2 (- height-1 old) nil t t) + (window-preserve-size window-1 nil preserved)))))) (defun display-buffer-record-window (type window buffer) "Record information for window used by `display-buffer'. @@ -6139,7 +6472,8 @@ hold: wide as `split-width-threshold'. - When WINDOW is split evenly, the emanating windows are at least `window-min-width' or two (whichever is larger) columns wide." - (when (and (window-live-p window) (not (window--side-window-p window))) + (when (and (window-live-p window) + (not (window-parameter window 'window-side))) (with-current-buffer (window-buffer window) (if horizontal ;; A window can be split horizontally when its width is not @@ -6314,15 +6648,15 @@ live." (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 +6674,8 @@ live." ((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 +6711,12 @@ live." ;; 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 +6940,9 @@ Recognized alist entries include: 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 @@ -6952,10 +7293,7 @@ selected frame." (window--display-buffer buffer window 'window alist display-buffer-mark-dedicated)) (and (not (frame-parameter nil 'unsplittable)) - (setq window - (condition-case nil - (split-window (window--major-non-side-window)) - (error nil))) + (setq window (split-window-no-error (window-main-window))) (window--display-buffer buffer window 'window alist display-buffer-mark-dedicated)) (and (setq window bottom-window) commit 13ba5af7427bdbd022a9d449dc2987d6a96591eb Author: John Wiegley Date: Tue Oct 4 14:47:43 2016 -0700 Add documentation note from Alex diff --git a/doc/misc/cc-mode.texi b/doc/misc/cc-mode.texi index f311ec8..6916169 100644 --- a/doc/misc/cc-mode.texi +++ b/doc/misc/cc-mode.texi @@ -2554,7 +2554,9 @@ for C code in GNU programs. @item k&r @cindex K&R style -The classic Kernighan and Ritchie style for C code. +The classic Kernighan and Ritchie style for C code. If you're looking +for the style used in the 2nd edition of their book ``The C +Programming Language'', then check out the @code{stroustrup} style. @item bsd @cindex BSD style commit 6d6c93f4cc02d5c03b2f0ec9e565d61a50677e14 Author: Mark Oteiza Date: Tue Oct 4 10:17:53 2016 -0400 Avoid dynamic binding on a symbol Instead, bind history in the default minibuffer-history. Fixes bug#24580. * lisp/replace.el (query-replace-read-from): Let-bind minibuffer-history. Change read-regexp and read-from-minibuffer's HISTORY arguments to nil so that they use minibuffer-history. diff --git a/lisp/replace.el b/lisp/replace.el index 4256751..4fc48d4 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -174,7 +174,7 @@ wants to replace FROM with TO." (propertize "\0" 'display query-replace-from-to-separator 'separator t))) - (query-replace-from-to-history + (minibuffer-history (append (when separator (mapcar (lambda (from-to) @@ -186,7 +186,7 @@ wants to replace FROM with TO." (minibuffer-allow-text-properties t) ; separator uses text-properties (prompt (if (and query-replace-defaults separator) - (format "%s (default %s): " prompt (car query-replace-from-to-history)) + (format "%s (default %s): " prompt (car minibuffer-history)) (format "%s: " prompt))) (from ;; The save-excursion here is in case the user marks and copies @@ -198,9 +198,9 @@ wants to replace FROM with TO." (setq-local text-property-default-nonsticky (cons '(separator . t) text-property-default-nonsticky))) (if regexp-flag - (read-regexp prompt nil 'query-replace-from-to-history) + (read-regexp prompt) (read-from-minibuffer - prompt nil nil nil 'query-replace-from-to-history + prompt nil nil nil nil (car (if regexp-flag regexp-search-ring search-ring)) t))))) (to)) (if (and (zerop (length from)) query-replace-defaults)