Subject: bug#48095: 28.0.50; display-line-numbers-mode / display-line-numbers-width-start incorrect

From: Eli Zaretskii <[email-masked]>
Date: Thu, 29 Apr 2021 12:03:36 +0300

> From: Len Trigg <[email-masked]>
> Date: Thu, 29 Apr 2021 14:43:30 +1200
> 
> I am using display-line-numbers mode, and would like to avoid it
> changing the width assigned to the line numbers as the buffer is
> scrolled. I have (setq display-line-numbers-width-start t), which
> purports to check in advance how much width is required. It seems
> somehow incorrect though. To reproduce:
> 
> 1 Create a buffer with 90 lines.
> 
> 2 Ensure the window is approx 40 lines tall, displaying the first 40 lines
> of the buffer.
> 
> 3 Ensure display-line-numbers-width-start is t
> 
> 4 Turn on display-line-numbers-mode.
> 
> 5 Scroll down to near the bottom of the buffer. At some point, emacs
>   adds an extra character of width to the line numbers, even though no 
>   line number higher than 90 is ever shown. If emacs needed an extra
>   character for whatever reason, surely display-line-numbers-width-start
>   should have incorporated that up front (otherwise what's the point)?

This is not exactly as simple as it may sound.  When the line numbers
are produced at display time, Emacs accounts for the maximum possible
number of lines that could fit in the window, and enlarges the width
when the window could display more lines, so that the width doesn't
change in the middle of the window.  So if the window is, say, 35
lines high, and the last portion of the buffer is about to be
displayed, Emacs computes the width necessary to display up to 35 more
lines, even if those lines are not yet in the buffer.  There's also
the possibility that the text will have different faces, and that
could affect the actual number of lines (via the font height).

But this cannot be done up front, because when the mode is turned on
in a buffer, the dimensions of the window where it will be displayed
are not necessarily known, and neither are the fonts that will be used
to display the buffer.  And windows nowadays can be as small as a few
lines or as toll as the full screen.

What I can offer to make the situation slightly better is to allow
display-line-numbers-width-start to have a numeric value, which will
then be interpreted as the number of extra lines to account for when
computing the required width.  A user who wants this is supposed to
know how tall are the windows in his/her sessions, and should set the
value to the maximum height of windows he/she wants to support.

The patch for that is below; could you please try it and see if this
provides a satisfactory solution for your problem?

diff --git a/lisp/display-line-numbers.el b/lisp/display-line-numbers.el
index a6fa813..0a834d4 100644
--- a/lisp/display-line-numbers.el
+++ b/lisp/display-line-numbers.el
@@ -56,12 +56,15 @@ display-line-numbers-grow-only
 
 (defcustom display-line-numbers-width-start nil
   "If non-nil, count number of lines to use for line number width.
-When `display-line-numbers-mode' is turned on,
-`display-line-numbers-width' is set to the minimum width necessary
-to display all line numbers in the buffer."
+When `display-line-numbers-mode' is turned on, if this option is
+non-nil, `display-line-numbers-width' is set up front to a width
+necessary to display all line numbers in the buffer.  If the value
+is a positive number, it is interpreted as extra lines to account
+for when computing the required width."
   :group 'display-line-numbers
-  :type 'boolean
-  :version "26.1")
+  :type '(choice (boolean :tag "Minimum width for buffer's line count")
+                 (integer :tag "Number of extra lines to account for"))
+  :version "28.1")
 
 (defun display-line-numbers-update-width ()
   "Prevent the line number width from shrinking."
@@ -83,7 +86,11 @@ display-line-numbers-mode
         (when display-line-numbers-width-start
           (setq display-line-numbers-width
                 (length (number-to-string
-                         (count-lines (point-min) (point-max))))))
+                         (+ (count-lines (point-min) (point-max))
+                            (if (and (numberp display-line-numbers-width-start)
+                                     (> display-line-numbers-width-start 0))
+                                display-line-numbers-width-start
+                              0))))))
         (when display-line-numbers-grow-only
           (add-hook 'pre-command-hook #'display-line-numbers-update-width nil t))
         (setq display-line-numbers display-line-numbers-type))