Subject: bug#3142: 23.0.92; split-window-prefered-function should be able to use split-window-horizontally/split-window-vertically

From: martin rudalics <[email-masked]>
Date: Tue, 05 May 2009 09:02:31 +0200

>> Remains the question whether `split-window-preferred-function' should
 >> always operate on the selected window or have a window argument.  AFAICT
 >> nothing speaks for the window argument but the fact that people may have
 >> customized this already.  Omitting the window argument would allow users
 >> to put `split-window-vertically' directly as value of
 >> `split-window-preferred-function' without having to delve any deeper.
 >
 > I agree it would make sense to operate on the selected-window rather
 > than pass an explicit window argument.

Unfortunately, this will create the following problem: Suppose a user
wants to

(1) always split the selected window instead of the largest or LRU one,

(2) never split the selected window (as Drew points out in another
thread), or

(3) base her decision on whether a window shall be split on which window
was selected at the time `display-buffer' was called.

In any of these cases calling `split-window-preferred-function' with the
window to split selected will obscure the identity of the selected
window and make the desired customization impossible.  So we should
probably leave the current behavior alone.  The attached patch just
makes sure that `split-window-preferred-function' gets always called and
provides the standard options requested earlier.

martin
*** window.el.~1.179.~	2009-04-29 07:50:42.078125000 +0200
--- window.el	2009-05-05 08:46:08.390625000 +0200
***************
*** 794,832 ****
    :type 'boolean
    :group 'windows)
  
  (defcustom split-height-threshold 80
    "Minimum height of window to be split vertically.
! If the value is a number, `display-buffer' can split a window
! only if it has at least as many lines.  If the value is nil,
! `display-buffer' cannot split a window vertically.
! 
! If the window is the only window on its frame, `display-buffer'
! can split it regardless of this value."
    :type '(choice (const nil) (number :tag "lines"))
    :version "23.1"
    :group 'windows)
  
  (defcustom split-width-threshold 160
    "Minimum width of window to be split horizontally.
! If the value is a number, `display-buffer' can split a window
! only if it has at least as many columns.  If the value is nil,
! `display-buffer' cannot split a window horizontally."
    :type '(choice (const nil) (number :tag "columns"))
    :version "23.1"
    :group 'windows)
  
- (defcustom split-window-preferred-function nil
-   "Function used by `display-buffer' to split windows.
- If non-nil, a function called with a window as single argument
- supposed to split that window and return the new window.  If the
- function returns nil the window is not split.
- 
- If nil, `display-buffer' will split the window respecting the
- values of `split-height-threshold' and `split-width-threshold'."
-   :type '(choice (const nil) (function :tag "Function"))
-   :version "23.1"
-   :group 'windows)
- 
  (defun window--splittable-p (window &optional horizontal)
    "Return non-nil if WINDOW can be split evenly.
  Optional argument HORIZONTAL non-nil means check whether WINDOW
--- 794,865 ----
    :type 'boolean
    :group 'windows)
  
+ (defcustom split-window-preferred-function 'split-window-sensibly
+   "Function called by `display-buffer' to split a window.
+ The function is called with a window as single argument and is
+ supposed to either split that window and return the new window or
+ return nil if the window can (or shall) not be split.
+ 
+ The default is to split the window \"sensibly\".  This calls the
+ function `split-window-sensibly' which tries to split the window
+ in a way which seems most suitable.  A second standard option is
+ \"vertically\" to split the window into two windows one above the
+ other.  The third standard option is \"horizontally\" which tries
+ to split the window into two windows side by side.  All three
+ standard options examine the values of `split-height-threshold'
+ and/or `split-width-threshold' before they apply the split.
+ 
+ If you set this to any other function, bear in mind that it may
+ be called two times: The argument of the first call is the
+ largest window on its frame.  If that call fails to provide a
+ suitable window, it gets called again with the least recently
+ used window as argument.  If neither of these calls produces a
+ suitable window, `display-buffer' will use an existing one to
+ display its buffer.
+ 
+ The window selected at the time `display-buffer' was invoked is
+ selected when `split-window-preferred-function' gets called.
+ Hence you can compare WINDOW with the value of `selected-window'
+ if you always want to split the selected window instead of WINDOW
+ or if you never want to split the currently selected window."
+   :type '(choice
+ 	  (const :tag "sensibly" split-window-sensibly)
+ 	  (const :tag "vertically"
+ 		 (lambda (window)
+ 		   (when (window--splittable-p window)
+ 		     (with-selected-window window
+ 		       (split-window-vertically)))))
+ 	  (const :tag "horizontally"
+ 		 (lambda (window)
+ 		   (when (window--splittable-p window t)
+ 		     (with-selected-window window
+ 		       (split-window-horizontally)))))
+ 	  (function :tag "Function"))
+   :version "23.1"
+   :group 'windows)
+ 
  (defcustom split-height-threshold 80
    "Minimum height of window to be split vertically.
! If the value is a number, the standard functions specified for
! `split-window-preferred-function' are allowed to split a window
! vertically only if it has at least this many lines.  If the value
! is nil, they do not split a window vertically.  If a window is
! the only window on its frame, they can split it disregarding the
! value of this variable."
    :type '(choice (const nil) (number :tag "lines"))
    :version "23.1"
    :group 'windows)
  
  (defcustom split-width-threshold 160
    "Minimum width of window to be split horizontally.
! If the value is a number, the standard functions specified for
! `split-window-preferred-function' are allowed to split a window
! horizontally only if it has at least this many columns.  If the
! value is nil, they do not split a window horizontally."
    :type '(choice (const nil) (number :tag "columns"))
    :version "23.1"
    :group 'windows)
  
  (defun window--splittable-p (window &optional horizontal)
    "Return non-nil if WINDOW can be split evenly.
  Optional argument HORIZONTAL non-nil means check whether WINDOW
***************
*** 882,911 ****
  		      (* 2 (max window-min-height
  				(if mode-line-format 2 1))))))))))
  
  (defun window--try-to-split-window (window)
!   "Split WINDOW if it is splittable.
! See `window--splittable-p' for how to determine whether a window
! is splittable.  If WINDOW can be split, return the value returned
! by `split-window' (or `split-window-preferred-function')."
!   (when (and (window-live-p window)
! 	     (not (frame-parameter (window-frame window) 'unsplittable)))
!     (if (functionp split-window-preferred-function)
! 	;; `split-window-preferred-function' is specified, so use it.
! 	(funcall split-window-preferred-function window)
!       (or (and (window--splittable-p window)
! 	       ;; Split window vertically.
! 	       (split-window window))
! 	  (and (window--splittable-p window t)
! 	       ;; Split window horizontally.
! 	       (split-window window nil t))
! 	  (and (eq window (frame-root-window (window-frame window)))
! 	       (not (window-minibuffer-p window))
! 	       ;; If WINDOW is the only window on its frame and not the
! 	       ;; minibuffer window, attempt to split it vertically
! 	       ;; disregarding the value of `split-height-threshold'.
! 	       (let ((split-height-threshold 0))
! 		 (and (window--splittable-p window)
! 		      (split-window window))))))))
  
  (defun window--frame-usable-p (frame)
    "Return FRAME if it can be used to display a buffer."
--- 915,964 ----
  		      (* 2 (max window-min-height
  				(if mode-line-format 2 1))))))))))
  
+ (defun split-window-sensibly (window)
+   "Split WINDOW \"sensibly\".
+ If WINDOW has at least `split-height-threshold' lines, split it
+ into two windows one above the other and return the lower one.
+ Otherwise, if WINDOW is at least `split-width-threshold' columns
+ wide, split it into two windows side by side and return the one
+ on the right.  If this does not produce a suitable window either
+ and WINDOW is the only window on its frame, try to split WINDOW
+ vertically and return the lower one.  Return nil if that fails as
+ well.
+ 
+ The default value of `split-window-preferred-function' is set to
+ this function."
+   (or (and (window--splittable-p window)
+ 	   ;; Split window vertically.
+ 	   (with-selected-window window
+ 	     (split-window-vertically)))
+       (and (window--splittable-p window t)
+ 	   ;; Split window horizontally.
+ 	   (with-selected-window window
+ 	     (split-window-horizontally)))
+       (and (eq window (frame-root-window (window-frame window)))
+ 	   (not (window-minibuffer-p window))
+ 	   ;; If WINDOW is the only window on its frame and is not the
+ 	   ;; minibuffer window, try to split it vertically disregarding
+ 	   ;; the value of `split-height-threshold'.
+ 	   (let ((split-height-threshold 0))
+ 	     (when (window--splittable-p window)
+ 	       (with-selected-window window
+ 		 (split-window-vertically)))))))
+ 
  (defun window--try-to-split-window (window)
!   "Split WINDOW if it can be split.
! Return value returned by `split-window-preferred-function' if it
! represents a live window, nil otherwise."
!       (and (window-live-p window)
! 	   (not (frame-parameter (window-frame window) 'unsplittable))
! 	   (let ((new-window
! 		  ;; Since `split-window-preferred-function' might
! 		  ;; throw an error use `condition-case'.
! 		  (condition-case nil
! 		      (funcall split-window-preferred-function window)
! 		    (error nil))))
! 	     (and (window-live-p new-window) new-window))))
  
  (defun window--frame-usable-p (frame)
    "Return FRAME if it can be used to display a buffer."