From 6e1ebcbb7768f73b5d6498e8baccb899cebb1c0b Mon Sep 17 00:00:00 2001
From: Nicolas Despres <nicolas.despres@gmail.com>
Date: Tue, 17 Dec 2024 06:58:33 +0100
Subject: [PATCH] Prioritize split along the longest edge by default.

Currently, `split-window-sensibly' prefer to try to split vertically
first disregarding the actual shape of the frame or the users
preferences.  This is a good default when Emacs is taller than wider.
However, when Emacs is in full-screen (landscape screen layout) trying to
split vertically may not be what the user expected since there is plenty
of space available on the right.

Typical scenario: Emacs is in landscape layout, one buffer is open in a
window covering the entire frame.  Another buffer is opened in a second
window (C-x 4 f). Both split are feasible but users may prefer the
horizontal one.

This patch preserves the behavior of the `split-height-threshold' and
`split-width-threshold' variables. Splitting continue not be permitted
if the edge length is below the threshold.

* lisp/window.el (split-window-sensibly): Always prefer to try first the
longest edge instead of blindly trying to split vertically.  * etc/NEWS:
Add an entry for new variable `preferred-split-edge'.  *
doc/emacs/windows.texi: Document new variable `preferred-split-edge'.
---
 doc/emacs/windows.texi |  5 ++-
 etc/NEWS               |  6 ++++
 lisp/window.el         | 73 ++++++++++++++++++++++++++++--------------
 3 files changed, 59 insertions(+), 25 deletions(-)

diff --git a/doc/emacs/windows.texi b/doc/emacs/windows.texi
index 69f24ec192f..9e67cb1e07d 100644
--- a/doc/emacs/windows.texi
+++ b/doc/emacs/windows.texi
@@ -511,6 +511,7 @@ Window Choice
 
 @vindex split-height-threshold
 @vindex split-width-threshold
+@vindex preferred-split-edge
 The split can be either vertical or horizontal, depending on the
 variables @code{split-height-threshold} and
 @code{split-width-threshold}.  These variables should have integer
@@ -519,7 +520,9 @@ Window Choice
 @code{split-width-threshold} is smaller than the window's width, the
 split puts the new window on the right.  If neither condition holds,
 Emacs tries to split so that the new window is below---but only if the
-window was not split before (to avoid excessive splitting).
+window was not split before (to avoid excessive splitting). Whether
+Emacs tries to split the vertical or horizontal edge first, is
+determined by the value of @code{preferred-split-edge}.
 
 @item
 Otherwise, display the buffer in a window previously showing it.
diff --git a/etc/NEWS b/etc/NEWS
index 4f74795043e..3338e95bcaf 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -174,6 +174,12 @@ It has been obsolete since Emacs 30.1.  Use '(category . comint)' instead.
 Another user option 'display-tex-shell-buffer-action' has been removed too
 for which you can use '(category . tex-shell)'.
 
++++
+*** New user option 'preferred-split-edge'.
+Users can now choose which edge (vertical or horizontal) is tried to be
+split first.  With this new setting, when the frame is in landscape shape
+for instance, Emacs could split horizontally before to split vertically.
+
 ** Frames
 
 +++
diff --git a/lisp/window.el b/lisp/window.el
index e9d57652ec6..db6affaf1dd 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -7347,20 +7347,47 @@ window-splittable-p
 		      (* 2 (max window-min-height
 				(if mode-line-format 2 1))))))))))
 
+(defcustom preferred-split-edge 'vertical
+  "The edge that `split-window-sensibly' will try to split first.
+This order matter when both edges can be split according to
+`split-width-threshold' and `split-height-threshold'."
+  :type '(choice
+          (const :tag "Try to split vertically first (legacy behavior)"
+                 vertical)
+          (const :tag "Try to split horizontally first"
+                 horizontal)
+          (const :tag "Try to split along the longest edge first"
+                 longest))
+  :version "30.1"
+  :group 'windows)
+
+(defun window--try-vertical-split (window)
+  "Helper function for `split-window-sensibly'"
+  (when (window-splittable-p window)
+    (with-selected-window window
+      (split-window-below))))
+
+(defun window--try-horizontal-split (window)
+  "Helper function for `split-window-sensibly'"
+  (when (window-splittable-p window t)
+    (with-selected-window window
+      (split-window-right))))
+
 (defun split-window-sensibly (&optional window)
   "Split WINDOW in a way suitable for `display-buffer'.
-WINDOW defaults to the currently selected window.
-If `split-height-threshold' specifies an integer, WINDOW is at
-least `split-height-threshold' lines tall and can be split
-vertically, split WINDOW into two windows one above the other and
-return the lower window.  Otherwise, if `split-width-threshold'
-specifies an integer, WINDOW is at least `split-width-threshold'
-columns wide and can be split horizontally, split WINDOW into two
-windows side by side and return the window on the right.  If this
-can't be done either and WINDOW is the only window on its frame,
-try to split WINDOW vertically disregarding any value specified
-by `split-height-threshold'.  If that succeeds, return the lower
-window.  Return nil otherwise.
+WINDOW defaults to the currently selected window.  If the threshold
+(`split-height-threshold' respectively `split-width-threshold')
+specifies an integer, WINDOW is at least that threshold lines tall
+(respectively wide) and can be split (vertically respectively
+horizontally).  If that fails, try to split along the other edge.  If
+this can't be done either and WINDOW is the only window on its frame,
+try to split WINDOW along the vertical edge disregarding any value
+specified by `split-height-threshold' respectively
+`split-width-threshold'. If that succeeds, return the lower window.
+Return nil otherwise.
+
+The edge that is tried first can be configured by setting
+`preferred-split-edge'.
 
 By default `display-buffer' routines call this function to split
 the largest or least recently used window.  To change the default
@@ -7380,14 +7407,14 @@ split-window-sensibly
 know how `split-window-sensibly' determines whether WINDOW can be
 split."
   (let ((window (or window (selected-window))))
-    (or (and (window-splittable-p window)
-	     ;; Split window vertically.
-	     (with-selected-window window
-	       (split-window-below)))
-	(and (window-splittable-p window t)
-	     ;; Split window horizontally.
-	     (with-selected-window window
-	       (split-window-right)))
+    (or (if (or
+             (eql preferred-split-edge 'horizontal)
+             (and (eql preferred-split-edge 'longest)
+                  (> (frame-width) (frame-height))))
+            (or (window--try-horizontal-split window)
+                (window--try-vertical-split window))
+          (or (window--try-vertical-split window)
+              (window--try-horizontal-split window)))
 	(and
          ;; If WINDOW is the only usable window on its frame (it is
          ;; the only one or, not being the only one, all the other
@@ -7405,10 +7432,8 @@ split-window-sensibly
                                 frame nil 'nomini)
               t)))
 	 (not (window-minibuffer-p window))
-	 (let ((split-height-threshold 0))
-	   (when (window-splittable-p window)
-	     (with-selected-window window
-	       (split-window-below))))))))
+         (let ((split-height-threshold 0))
+           (window--try-vertical-split window))))))
 
 (defun window--try-to-split-window (window &optional alist)
   "Try to split WINDOW.
-- 
2.47.1

