9

There are several questions similar to this one. I have read them all and they don't provide an answer for my scenario. I want emacs to prefer vertical splits (splitting windows into left and right parts) over horizontal ones, even if both horizontal and vertical splitting is possible. This is what the manual says:

The split can be either vertical or horizontal, depending on the variables split-height-threshold and split-width-threshold. These variables should have integer values. If split-height-threshold is smaller than the chosen window's height, the split puts the new window below. Otherwise, if split-width-threshold is smaller than the window's width, the split puts the new window on the right.

So if both split-height-threshold and split-width-threshold are smaller than the window's width and height, emacs will perform a horizontal split. I want the opposite. If both thresholds are smaller, perform a vertical split.

One way to achieve that is to set split-height-threshold to nil, but I don't like that because it disables horizontal splitting completely.

I have looked at the split-window-sensibly function, but I'm not good enough at elisp to write my own my-split-window-sensibly function that works like I want.

  • There is a variable called split-window-preferred-function which can be set to use a custom function. Have a good look at the function split-window-sensibly and see if it can suit your needs by adjusting certain variables as you mentioned in your question, and also read the doc-string of that function ... if it cannot be made to suit your needs, then you can write another, or obtain help writing another function ... – lawlist Feb 23 '18 at 21:55
5

In my experience this is a harder problem that one might think, because one's intuitive idea of what is sensible is not always easy to put in precise terms. I'll just describe what I've ended up with, but you may have to fiddle around.

First: the existing split-window-sensibly function always prefers to end up with a horizontal stack of windows (which, rather confusingly, it calls a vertical "split", though the split is horizontal ...) over a side-by-side arrangement. It's easy enough to create a function which has the opposite preference, which is essentially just a copy of split-window-sensibly with the preferences reversed:

(defun split-window-sensibly-prefer-horizontal (&optional window)
"Based on split-window-sensibly, but designed to prefer a horizontal split,
i.e. windows tiled side-by-side."
  (let ((window (or window (selected-window))))
    (or (and (window-splittable-p window t)
         ;; Split window horizontally
         (with-selected-window window
           (split-window-right)))
    (and (window-splittable-p window)
         ;; Split window vertically
         (with-selected-window window
           (split-window-below)))
    (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
         ;; ones are dedicated) and is not the minibuffer window, try
         ;; to split it horizontally disregarding the value of
         ;; `split-height-threshold'.
         (let ((frame (window-frame window)))
           (or
            (eq window (frame-root-window frame))
            (catch 'done
              (walk-window-tree (lambda (w)
                                  (unless (or (eq w window)
                                              (window-dedicated-p w))
                                    (throw 'done nil)))
                                frame)
              t)))
     (not (window-minibuffer-p window))
     (let ((split-width-threshold 0))
       (when (window-splittable-p window t)
         (with-selected-window window
               (split-window-right))))))))

So now we have two functions: the original which "prefers" a vertical stack, and the new one which "prefers" a horizontal stack.

Next we need a function which tends to prefer the one we'd rather use.

(defun split-window-really-sensibly (&optional window)
  (let ((window (or window (selected-window))))
    (if (> (window-total-width window) (* 2 (window-total-height window)))
        (with-selected-window window (split-window-sensibly-prefer-horizontal window))
      (with-selected-window window (split-window-sensibly window)))))

You need to fiddle around with the values here, but the basic idea is that we prefer a vertical arrangement whenever there is at least twice as much width as height. You might think you wanted it wherever the existing window is wider than it's tall, but in my experience that's not right, and lets you end up with windows which are far too skinny.

Finally, we also need to have some sane minimums. I set a split-height-threshold of 4 (i.e. I don't want, unless it's unavoidable, to have less than 2 lines in a window) and a split-width-threshold of 40 (i.e. I don't want, unless it's unavoidable, to have less than 20 characters across in a window) -- at least I think that's what these mean.

Then one just binds split-window-preferred-function to split-window-really-sensibly

(setq
   split-height-threshold 4
   split-width-threshold 40 
   split-window-preferred-function 'split-window-really-sensibly)

Another idea (which you might prefer) would just be to substitute the "preference for side-by-side" arrangement, and set split-width-threshold to 80: then you would get side-by-side windows whenever there was space for them, I think.

  • 1
    It works! But I had to keep the split-height/width-threshold values at their default or else the pop-to-buffer function would create new splits rather than reusing old ones. I prefer to have a single right/left split and don't want emacs functions to mess with that. – Björn Lindqvist Apr 17 '18 at 11:51
1

You can use my package el-patch to implement the function split-window-sensibly-prefer-horizontal in a way that makes it obvious what has changed from the original split-window-sensibly, and also allows you to detect if the original definition changes in a future Emacs release:

(el-patch-defun (el-patch-swap
                  split-window-sensibly
                  split-window-sensibly-prefer-horizontal)
  (&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.

By default `display-buffer' routines call this function to split
the largest or least recently used window.  To change the default
customize the option `split-window-preferred-function'.

You can enforce this function to not split WINDOW horizontally,
by setting (or binding) the variable `split-width-threshold' to
nil.  If, in addition, you set `split-height-threshold' to zero,
chances increase that this function does split WINDOW vertically.

In order to not split WINDOW vertically, set (or bind) the
variable `split-height-threshold' to nil.  Additionally, you can
set `split-width-threshold' to zero to make a horizontal split
more likely to occur.

Have a look at the function `window-splittable-p' if you want to
know how `split-window-sensibly' determines whether WINDOW can be
split."
  (let ((window (or window (selected-window))))
    (or (el-patch-let
            (($fst (and (window-splittable-p window)
                        ;; Split window vertically.
                        (with-selected-window window
                          (split-window-below))))
             ($snd (and (window-splittable-p window t)
                        ;; Split window horizontally.
                        (with-selected-window window
                          (split-window-right)))))
          (el-patch-swap $fst $snd)
          (el-patch-swap $snd $fst))
        (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 ones are dedicated) and is not the minibuffer
         ;; window, try to split it s/vertically/horizontally
         ;; disregarding the value of `split-height-threshold'.
         (let ((frame (window-frame window)))
           (or
            (eq window (frame-root-window frame))
            (catch 'done
              (walk-window-tree (lambda (w)
                                  (unless (or (eq w window)
                                              (window-dedicated-p w))
                                    (throw 'done nil)))
                                frame)
              t)))
         (not (window-minibuffer-p window))
         (let (((el-patch-swap split-height-threshold
                               split-width-threshold)
                0))
           (when (window-splittable-p window)
             (with-selected-window window
               ((el-patch-swap split-window-below split-window-right)))))))))
1

I found this solution in the Emacs mailing list, and it works wonders:

;; Fix annoying vertical window splitting.
;; https://lists.gnu.org/archive/html/help-gnu-emacs/2015-08/msg00339.html
(with-eval-after-load "window"
  (defcustom split-window-below nil
    "If non-nil, vertical splits produce new windows below."
    :group 'windows
    :type 'boolean)

  (defcustom split-window-right nil
    "If non-nil, horizontal splits produce new windows to the right."
    :group 'windows
    :type 'boolean)

  (fmakunbound #'split-window-sensibly)

  (defun split-window-sensibly
      (&optional window)
    (setq window (or window (selected-window)))
    (or (and (window-splittable-p window t)
             ;; Split window horizontally.
             (split-window window nil (if split-window-right 'left  'right)))
        (and (window-splittable-p window)
             ;; Split window vertically.
             (split-window window nil (if split-window-below 'above 'below)))
        (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 horizontally disregarding the
             ;; value of `split-width-threshold'.
             (let ((split-width-threshold 0))
               (when (window-splittable-p window t)
                 (split-window window nil (if split-window-right
                                              'left
                                            'right))))))))

(setq-default split-height-threshold  4
              split-width-threshold   160) ; the reasonable limit for horizontal splits

Kudos to Alexander, the original author.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.