diff options
Diffstat (limited to 'lisp/window.el')
-rw-r--r-- | lisp/window.el | 1100 |
1 files changed, 744 insertions, 356 deletions
diff --git a/lisp/window.el b/lisp/window.el index 1c845f4ee99..358d7bc58f0 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -1,4 +1,4 @@ -;;; window.el --- GNU Emacs window commands aside from those written in C +;;; window.el --- GNU Emacs window commands aside from those written in C -*- lexical-binding:t -*- ;; Copyright (C) 1985, 1989, 1992-1994, 2000-2017 Free Software ;; Foundation, Inc. @@ -651,13 +651,13 @@ failed." (window-combination-limit t) (window-combination-resize 'atom) (window (cdr (assq 'window alist))) - (side (cdr (assq 'side alist))) + (side (or (cdr (assq 'side alist)) 'below)) (atom (when window (window-parameter window 'window-atom))) root new) (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 the window used +for displaying BUFFER, nil if no suitable window can be 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. @@ -1333,10 +1532,8 @@ return the minimum pixel-size of WINDOW." (window--min-size-1 (window-normalize-window window) horizontal ignore pixelwise)) -(defun window--min-size-ignore-p (window horizontal ignore) - "Return non-nil if IGNORE says to ignore height restrictions for WINDOW. -HORIZONTAL non-nil means to return non-nil if IGNORE says to -ignore width restrictions for WINDOW." +(defun window--min-size-ignore-p (window ignore) + "Return non-nil if IGNORE says to ignore height restrictions for WINDOW." (if (window-valid-p ignore) (eq window ignore) (not (memq ignore '(nil preserved))))) @@ -1407,12 +1604,12 @@ ignore width restrictions for WINDOW." pixel-width ;; Round up to next integral of columns. (* (ceiling pixel-width char-size) char-size)) - (if (window--min-size-ignore-p window horizontal ignore) + (if (window--min-size-ignore-p window ignore) 0 (window-min-pixel-width window))) (max (ceiling pixel-width char-size) - (if (window--min-size-ignore-p window horizontal ignore) + (if (window--min-size-ignore-p window ignore) 0 window-min-width))))) ((let ((char-size (frame-char-size window)) @@ -1428,11 +1625,11 @@ ignore width restrictions for WINDOW." pixel-height ;; Round up to next integral of lines. (* (ceiling pixel-height char-size) char-size)) - (if (window--min-size-ignore-p window horizontal ignore) + (if (window--min-size-ignore-p window ignore) 0 (window-min-pixel-height window))) (max (ceiling pixel-height char-size) - (if (window--min-size-ignore-p window horizontal ignore) + (if (window--min-size-ignore-p window ignore) 0 window-min-height)))))))))) @@ -2600,7 +2797,7 @@ instead." window delta horizontal ignore nil nil nil t))) (window--resize-reset frame horizontal) (window--resize-this-window window delta horizontal ignore t) - (if (and (not window-combination-resize) + (if (and (not (eq window-combination-resize t)) (window-combined-p window horizontal) (setq sibling (or (window-right window) (window-left window))) (window-sizable-p @@ -2633,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))) @@ -3140,8 +3334,8 @@ routines." pixel-delta (/ pixel-delta (frame-char-height frame))))) -(defun window--sanitize-window-sizes (frame horizontal) - "Assert that all windows on FRAME are large enough. +(defun window--sanitize-window-sizes (horizontal) + "Assert that all windows on selected frame are large enough. If necessary and possible, make sure that every window on frame FRAME has its minimum height. Optional argument HORIZONTAL non-nil means to make sure that every window on frame FRAME has @@ -3226,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 @@ -3247,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 @@ -3261,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 @@ -3291,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 @@ -3519,8 +3719,7 @@ ABSOLUTE is non-nil, PIXELWISE is implicitly non-nil too." (bottom (+ top (if pixelwise (window-pixel-height window) (window-total-height window)))) - (bottom-body (and body (+ top-body (window-body-height window t)))) - left-off right-off) + (bottom-body (and body (+ top-body (window-body-height window t))))) (if absolute (let* ((native-edges (frame-edges frame 'native-edges)) (left-off (nth 0 native-edges)) @@ -3743,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 @@ -3770,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) @@ -3829,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 @@ -3841,7 +4049,7 @@ that is its frame's root window." (sibling (or (window-left window) (window-right window)))) (window--resize-reset frame horizontal) (cond - ((and (not window-combination-resize) + ((and (not (eq window-combination-resize t)) sibling (window-sizable-p sibling size horizontal nil t)) ;; Resize WINDOW's sibling. (window--resize-this-window sibling size horizontal nil t) @@ -3889,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 @@ -3908,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. @@ -4069,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)) @@ -4079,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)) @@ -4109,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!). @@ -4187,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)) @@ -4196,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)) @@ -4214,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))) @@ -4703,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. @@ -4889,7 +5135,7 @@ frame. The selected window is not changed by this function." ;; Sanitize sizes unless SIZE was specified. (unless size - (window--sanitize-window-sizes frame horizontal)) + (window--sanitize-window-sizes horizontal)) (run-window-configuration-change-hook frame) (run-window-scroll-functions new) @@ -4897,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. @@ -5289,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) @@ -5366,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 @@ -5409,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)) @@ -5465,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 @@ -5487,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 @@ -5497,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)))) @@ -5511,7 +5774,9 @@ value can be also stored on disk and read back in a new session." ;; Install positions (maybe we should do this after all ;; windows have been created and sized). (ignore-errors - (set-window-start window (cdr (assq 'start state))) + ;; Set 'noforce argument to avoid that window start + ;; overrides window point set below (Bug#24240). + (set-window-start window (cdr (assq 'start state)) 'noforce) (set-window-point window (cdr (assq 'point state)))) ;; Select window if it's the selected one. (when (cdr (assq 'selected state)) @@ -5541,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) @@ -5635,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'. @@ -6140,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 @@ -6315,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)) @@ -6341,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)) @@ -6378,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) @@ -6603,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 @@ -6693,8 +7033,7 @@ that allows the selected frame)." (window--display-buffer buffer window 'frame alist display-buffer-mark-dedicated) (unless (cdr (assq 'inhibit-switch-frame alist)) - (window--maybe-raise-frame frame)))) - )) + (window--maybe-raise-frame frame)))))) (defun display-buffer-same-window (buffer alist) "Display BUFFER in the selected window. @@ -6757,6 +7096,70 @@ that frame." (unless (cdr (assq 'inhibit-switch-frame alist)) (window--maybe-raise-frame (window-frame window))))))) +(defun display-buffer-reuse-mode-window (buffer alist) + "Return a window based on the mode of the buffer it displays. +Display BUFFER in the returned window. Return nil if no usable +window is found. + +If ALIST contains a `mode' entry, its value is a major mode (a +symbol) or a list of modes. A window is a candidate if it +displays a buffer that derives from one of the given modes. When +ALIST contains no `mode' entry, the current major mode of BUFFER +is used. + +The behavior is also controlled by entries for +`inhibit-same-window', `reusable-frames' and +`inhibit-switch-frame' as is done in the function +`display-buffer-reuse-window'." + (let* ((alist-entry (assq 'reusable-frames alist)) + (alist-mode-entry (assq 'mode alist)) + (frames (cond (alist-entry (cdr alist-entry)) + ((if (eq pop-up-frames 'graphic-only) + (display-graphic-p) + pop-up-frames) + 0) + (display-buffer-reuse-frames 0) + (t (last-nonminibuffer-frame)))) + (inhibit-same-window-p (cdr (assq 'inhibit-same-window alist))) + (windows (window-list-1 nil 'nomini frames)) + (buffer-mode (with-current-buffer buffer major-mode)) + (allowed-modes (if alist-mode-entry + (cdr alist-mode-entry) + buffer-mode)) + (curwin (selected-window)) + (curframe (selected-frame))) + (unless (listp allowed-modes) + (setq allowed-modes (list allowed-modes))) + (let (same-mode-same-frame + same-mode-other-frame + derived-mode-same-frame + derived-mode-other-frame) + (dolist (window windows) + (let ((mode? + (with-current-buffer (window-buffer window) + (cond ((memq major-mode allowed-modes) + 'same) + ((derived-mode-p allowed-modes) + 'derived))))) + (when (and mode? + (not (and inhibit-same-window-p + (eq window curwin)))) + (push window (if (eq curframe (window-frame window)) + (if (eq mode? 'same) + same-mode-same-frame + derived-mode-same-frame) + (if (eq mode? 'same) + same-mode-other-frame + derived-mode-other-frame)))))) + (let ((window (car (nconc same-mode-same-frame + same-mode-other-frame + derived-mode-same-frame + derived-mode-other-frame)))) + (when (window-live-p window) + (prog1 (window--display-buffer buffer window 'reuse alist) + (unless (cdr (assq 'inhibit-switch-frame alist)) + (window--maybe-raise-frame (window-frame window))))))))) + (defun display-buffer--special-action (buffer) "Return special display action for BUFFER, if any. If `special-display-p' returns non-nil for BUFFER, return an @@ -6829,7 +7232,6 @@ raising the frame." (defun display-buffer--maybe-pop-up-frame-or-window (buffer alist) "Try displaying BUFFER based on `pop-up-frames' or `pop-up-windows'. - If `pop-up-frames' is non-nil (and not `graphic-only' on a text-only terminal), try with `display-buffer-pop-up-frame'. @@ -6844,8 +7246,11 @@ again with `display-buffer-pop-up-window'." (defun display-buffer-below-selected (buffer alist) "Try displaying BUFFER in a window below the selected window. -This either splits the selected window or reuses the window below -the selected one." +If there is a window below the selected one and that window +already displays BUFFER, use that window. Otherwise, try to +create a new window below the selected one and show BUFFER there. +If that attempt fails as well and there is a non-dedicated window +below the selected one, use that window." (let (window) (or (and (setq window (window-in-direction 'below)) (eq buffer (window-buffer window)) @@ -6888,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) @@ -7009,12 +7411,12 @@ returned from `display-buffer' in this case." 'fail)) ;;; Display + selection commands: -(defun pop-to-buffer (buffer &optional action norecord) - "Select buffer BUFFER in some window, preferably a different one. -BUFFER may be a buffer, a string (a buffer name), or nil. If it -is a string not naming an existent buffer, create a buffer with -that name. If BUFFER is nil, choose some other buffer. Return -the buffer. +(defun pop-to-buffer (buffer-or-name &optional action norecord) + "Display buffer specified by BUFFER-OR-NAME and select its window. +BUFFER-OR-NAME may be a buffer, a string (a buffer name), or nil. +If it is a string not naming an existent buffer, create a buffer +with that name. If BUFFER-OR-NAME is nil, choose some other +buffer. In either case, make that buffer current and return it. This uses `display-buffer' as a subroutine. The optional ACTION argument is passed to `display-buffer' as its ACTION argument. @@ -7023,24 +7425,30 @@ interactively with a prefix argument, which means to pop to a window other than the selected one even if the buffer is already displayed in the selected window. -If the window to show BUFFER is not on the selected -frame, raise that window's frame and give it input focus. +If a suitable window is found, select that window. If it is not +on the selected frame, raise that window's frame and give it +input focus. Optional third arg NORECORD non-nil means do not put this buffer at the front of the list of recently selected ones." (interactive (list (read-buffer "Pop to buffer: " (other-buffer)) (if current-prefix-arg t))) - (setq buffer (window-normalize-buffer-to-switch-to buffer)) - ;; This should be done by `select-window' below. - ;; (set-buffer buffer) - (let* ((old-frame (selected-frame)) - (window (display-buffer buffer action)) - (frame (window-frame window))) - ;; If we chose another frame, make sure it gets input focus. - (unless (eq frame old-frame) - (select-frame-set-input-focus frame norecord)) - ;; Make sure new window is selected (Bug#8615), (Bug#6954). - (select-window window norecord) + (let* ((buffer (window-normalize-buffer-to-switch-to buffer-or-name)) + (old-frame (selected-frame)) + (window (display-buffer buffer action))) + ;; Don't assume that `display-buffer' has supplied us with a window + ;; (Bug#24332). + (if window + (let ((frame (window-frame window))) + ;; If we chose another frame, make sure it gets input focus. + (unless (eq frame old-frame) + (select-frame-set-input-focus frame norecord)) + ;; Make sure the window is selected (Bug#8615), (Bug#6954) + (select-window window norecord)) + ;; If `display-buffer' failed to supply a window, just make the + ;; buffer current. + (set-buffer buffer)) + ;; Return BUFFER even when we got no window. buffer)) (defun pop-to-buffer-same-window (buffer &optional norecord) @@ -7093,7 +7501,7 @@ buffer with the name BUFFER-OR-NAME and return that buffer." buffer)) (other-buffer))) -(defcustom switch-to-buffer-preserve-window-point nil +(defcustom switch-to-buffer-preserve-window-point t "If non-nil, `switch-to-buffer' tries to preserve `window-point'. If this is nil, `switch-to-buffer' displays the buffer at that buffer's `point'. If this is `already-displayed', it tries to @@ -7111,7 +7519,7 @@ the selected window or never appeared in it before, or if (const :tag "If already displayed elsewhere" already-displayed) (const :tag "Always" t)) :group 'windows - :version "24.3") + :version "26.1") (defcustom switch-to-buffer-in-dedicated-window nil "Allow switching to buffer in strongly dedicated windows. @@ -7494,8 +7902,7 @@ FRAME." (setq frame (window-normalize-frame frame)) (when (window-live-p (frame-root-window frame)) (with-selected-window (frame-root-window frame) - (let* ((window (frame-root-window frame)) - (char-width (frame-char-width)) + (let* ((char-width (frame-char-width)) (char-height (frame-char-height)) (monitor-attributes (car (display-monitor-attributes-list (frame-parameter frame 'display)))) @@ -7542,8 +7949,6 @@ FRAME." ;; and the window's body width. This is the space we can't ;; use for fitting. (extra-width (- frame-width window-body-width)) - ;; The maximum width we can use for fitting. - (fit-width (- workarea-width extra-width)) ;; The pixel position of FRAME's left border. We usually ;; try to leave this alone. (left @@ -7562,23 +7967,6 @@ FRAME." ;; The difference in pixels between the frame's pixel ;; height and the window's height. (extra-height (- frame-height window-height)) - ;; When tool-bar-mode is enabled and we just created a new - ;; frame, reserve lines for toolbar resizing. Needed - ;; because for reasons unknown to me Emacs (1) reserves one - ;; line for the toolbar when making the initial frame and - ;; toolbars are enabled, and (2) later adds the remaining - ;; lines needed. Our code runs IN BETWEEN (1) and (2). - ;; YMMV when you're on a system that behaves differently. - (toolbar-extra-height - (let ((quit-restore (window-parameter window 'quit-restore)) - ;; This may have to change when we allow arbitrary - ;; pixel height toolbars. - (lines (tool-bar-height))) - (* char-height - (if (and quit-restore (eq (car quit-restore) 'frame) - (not (zerop lines))) - (1- lines) - 0)))) ;; The pixel position of FRAME's top border. (top (let ((top (frame-parameter nil 'top))) @@ -8500,9 +8888,9 @@ overrides the global or buffer-local value of :group 'windows :version "25.1") -(defun window-adjust-process-window-size (reducer process windows) - "Adjust the process window size of PROCESS. -WINDOWS is a list of windows associated with PROCESS. REDUCER is +(defun window-adjust-process-window-size (reducer windows) + "Adjust the window sizes of a process. +WINDOWS is a list of windows associated with that process. REDUCER is a two-argument function used to combine the widths and heights of the given windows." (when windows @@ -8513,17 +8901,17 @@ the given windows." (setf height (funcall reducer height (window-body-height window)))) (cons width height)))) -(defun window-adjust-process-window-size-smallest (process windows) +(defun window-adjust-process-window-size-smallest (_process windows) "Adjust the process window size of PROCESS. WINDOWS is a list of windows associated with PROCESS. Choose the smallest area available for displaying PROCESS's output." - (window-adjust-process-window-size #'min process windows)) + (window-adjust-process-window-size #'min windows)) -(defun window-adjust-process-window-size-largest (process windows) +(defun window-adjust-process-window-size-largest (_process windows) "Adjust the process window size of PROCESS. WINDOWS is a list of windows associated with PROCESS. Choose the largest area available for displaying PROCESS's output." - (window-adjust-process-window-size #'max process windows)) + (window-adjust-process-window-size #'max windows)) (defun window--process-window-list () "Return an alist mapping processes to associated windows. |