unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: Keith David Bershatsky <esq@lawlist.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: 28246@debbugs.gnu.org
Subject: bug#28246: display line number width != length of line number at eob
Date: Sun, 27 Aug 2017 18:46:42 -0700	[thread overview]
Message-ID: <m2inh8sbul.wl%esq@lawlist.com> (raw)
In-Reply-To: <m27exq56vv.wl%esq@lawlist.com>

Thank you, Eli, for reviewing #28246 and for explaining that automatic adjustment of the width for line-number display is *estimated* based upon certain criteria.

I do not know whether any other Emacs users would appreciate *precision* width based upon the length of the last line in the buffer.

Inasmuch as I was hoping to achieve *precision* width every command loop, display-line-numbers-width-start and/or display-line-numbers-grow-only would not suffice in that regard.  I am uncertain why it->lnum_width is *not* always equal to

  (save-excursion
    (goto-char (point-max))
    (length (format-mode-line "%l"))

The difference between it->lnum_width and the Lisp code above can be seen with messages in the echo area after evaluating the code in the opening bug report for #28246, and thereafter manually adding/removing lines from the buffer.

linum.el is slow in large buffers primarily because it uses count-lines.  In an emacs.stackexchange.com thread entitled "A faster method to obtain `line-number-at-pos` in large buffers" -- https://emacs.stackexchange.com/q/3821/2287 -- I learned that format-mode-line with the "%l" argument can be used to greatly enhance speed versus using count-lines.  However, the window must be visible and some other limitations apply as discussed in the comments of the SE thread.

After reading your comments (a few times to let it all sink in), I understand your concern about unnecessarily calculating the width each time that maybe_produce_line_number is called.  In the example that was used at the outset of #28246, the width is calculated only once per command loop and stored in display_line_numbers_width.

The following example function is a condensed version of what the mode-line code uses to determine the line number, which will be substantially faster than count-lines and/or line-number-at-pos.  In addition, I found a little snippet on stackoverflow.com to get the length of an integer.  Inasmuch as maybe_produce_line_number may be called more than one time each command loop, a different location needs to be found for internal_line_number_at_position -- so that it gets called just for each window per command loop -- and that value would need to be stored and subsequently used.  I understand that internal_line_number_at_position may not be well written, but it is a working proof concept to demonstrate where I am coming from.

/* Length of an Integer:  https://stackoverflow.com/a/3068420/2112489
The log10, abs, and floor functions are provided by math.h */
TARGET_WIDTH = floor (log10 (abs (internal_line_number_at_position (XWINDOW (selected_window), ZV)))) + 1;

static ptrdiff_t
internal_line_number_at_position (struct window *w, ptrdiff_t pos)
{
  Lisp_Object buf, value;
  struct buffer *b;
  struct buffer *old_buffer = NULL;
  buf = w->contents;
  CHECK_BUFFER (buf);
  b = XBUFFER (buf);
  ptrdiff_t line_number;
  if (b != current_buffer)
    {
      old_buffer = current_buffer;
      set_buffer_internal (b);
    }
  ptrdiff_t startpos, startpos_byte, line, linepos, linepos_byte,
            topline, nlines, height, junk, opoint;
  opoint = PT;
  if (opoint != pos)
    SET_PT (pos);
  startpos = marker_position (w->start);
  startpos_byte = marker_byte_position (w->start);
  /* If we decided that this buffer isn't suitable for line numbers, don't forget that too fast. */
  if (MINI_WINDOW_P (w))
    {
      line_number = 0;
      goto done;
    }
  if (w->base_line_pos == -1)
    {
      line_number = 0;
      goto done;
    }
  /* If the buffer is very big, don't waste time. */
  if (INTEGERP (Vline_number_display_limit)
      && BUF_ZV (b) - BUF_BEGV (b) > XINT (Vline_number_display_limit))
    {
      w->base_line_pos = 0;
      w->base_line_number = 0;
      line_number = 0;
      goto done;
    }
  if (w->base_line_number > 0
      && w->base_line_pos > 0
      && w->base_line_pos <= startpos)
    {
      line = w->base_line_number;
      linepos = w->base_line_pos;
      linepos_byte = buf_charpos_to_bytepos (b, linepos);
    }
    else
      {
        line = 1;
        linepos = BUF_BEGV (b);
        linepos_byte = BUF_BEGV_BYTE (b);
      }
  /* Count lines from base line to window start position. */
  nlines = display_count_lines (linepos_byte, startpos_byte, startpos, &junk);
  topline = nlines + line;
  /* Determine a new base line, if the old one is too close
     or too far away, or if we did not have one.
     "Too close" means it's plausible a scroll-down would go back past it. */
  if (startpos == BUF_BEGV (b))
    {
      w->base_line_number = topline;
      w->base_line_pos = BUF_BEGV (b);
    }
    else if (nlines < height + 25 || nlines > height * 3 + 50 || linepos == BUF_BEGV (b))
      {
        ptrdiff_t limit = BUF_BEGV (b);
        ptrdiff_t limit_byte = BUF_BEGV_BYTE (b);
        ptrdiff_t position;
        ptrdiff_t distance = (height * 2 + 30) * line_number_display_limit_width;
        if (startpos - distance > limit)
          {
            limit = startpos - distance;
            limit_byte = CHAR_TO_BYTE (limit);
          }
        nlines = display_count_lines (startpos_byte, limit_byte, - (height * 2 + 30), &position);
        /* If we couldn't find the lines we wanted within
           line_number_display_limit_width chars per line,
           give up on line numbers for this window. */
        if (position == limit_byte && limit == startpos - distance)
          {
            w->base_line_pos = -1;
            w->base_line_number = 0;
            line_number = 0;
            goto done;
          }
        w->base_line_number = topline - nlines;
        w->base_line_pos = BYTE_TO_CHAR (position);
      }
  /* Now count lines from the start pos to point. */
  nlines = display_count_lines (startpos_byte, PT_BYTE, PT, &junk);
  if (opoint != pos)
    SET_PT (opoint);
  line_number = topline + nlines;
  done:
  if (old_buffer)
    set_buffer_internal (old_buffer);
  return line_number;
}





  parent reply	other threads:[~2017-08-28  1:46 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-08-26 21:57 bug#28246: display line number width != length of line number at eob Keith David Bershatsky
2017-08-27 14:23 ` Eli Zaretskii
2017-08-28  1:46 ` Keith David Bershatsky [this message]
2017-08-28 17:27   ` Eli Zaretskii
2017-08-28 18:50 ` Keith David Bershatsky
2017-08-29 15:00   ` Eli Zaretskii

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=m2inh8sbul.wl%esq@lawlist.com \
    --to=esq@lawlist.com \
    --cc=28246@debbugs.gnu.org \
    --cc=eliz@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).