Subject: bug#22763: 25.1.50; Feature Request -- A faster method to obtain line number at position.

From: Keith David Bershatsky <[email-masked]>
Date: Sun, 21 Feb 2016 18:42:52 -0800

A while back, I posed a question on emacs.stackexchange.com for a faster method to obtain `line-number-at-pos`.  A user by the name of Constantine came up with `(format-mode-line "%l")`, which is indeed much faster.  I think the draft code below may be just a tad faster, with the added feature of the POS argument.

http://emacs.stackexchange.com/questions/3821/a-faster-method-to-obtain-line-number-at-pos-in-large-buffers

A user named wasamasa posted a comment: "Be aware that this method will give you "??" for lines exceeding line-number-display-limit-width which is set to a value of 200 per default as I found out here."

And Stefan posted a comment:  "IIRC the result may also be unreliable if there have been modifications in the buffer since the last redisplay."

The thread has received 555 hits in the past year, and several have star-ed it and up-voted the question and answer.

The following is a draft in C based on the existing function `decode_mode_spec` that lets the user input the POS as an argument without the necessity to use `goto-char`.  I'm not sure if it resolves either of the comments above.  The draft is not meant to be a patch per se, because I'm not a programmer and am just beginning my tinkering quest into the language of C.  I have been using it in my own setup for about a week and I haven't seen any ill effects.  It can of course use some TLC by someone more knowledgeable than myself.

static const char *
internal_line_number_at_position (struct window *w, register int c, int field_width, Lisp_Object *string)
{
  Lisp_Object obj;
  struct frame *f = XFRAME (WINDOW_FRAME (w));
  char *decode_mode_spec_buf = f->decode_mode_spec_buffer;
  /* We are going to use f->decode_mode_spec_buffer as the buffer to
     produce strings from numerical values, so limit preposterously
     large values of FIELD_WIDTH to avoid overrunning the buffer's
     end.  The size of the buffer is enough for FRAME_MESSAGE_BUF_SIZE
     bytes plus the terminating null.  */
  int width = min (field_width, FRAME_MESSAGE_BUF_SIZE (f));
  struct buffer *b = current_buffer;
  obj = Qnil;
  *string = Qnil;
  switch (c)
    {
    case 'l':
      {
  ptrdiff_t startpos, startpos_byte, line, linepos, linepos_byte;
  ptrdiff_t topline, nlines, height;
  ptrdiff_t junk;
  /* %c and %l are ignored in `frame-title-format'.  */
  if (mode_line_target == MODE_LINE_TITLE)
    return "";
  startpos = marker_position (w->start);
  startpos_byte = marker_byte_position (w->start);
  height = WINDOW_TOTAL_LINES (w);
  /* If we decided that this buffer isn't suitable for line numbers,
     don't forget that too fast.  */
  if (w->base_line_pos == -1)
    goto no_value;
  /* 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;
      goto no_value;
    }
  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;
    goto no_value;
        }
      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);
  /* Record that we did display the line number.  */
  line_number_displayed = true;
  /* Make the string to show.  */
  pint2str (decode_mode_spec_buf, width, topline + nlines);
  return decode_mode_spec_buf;
    no_value:
        {
    char *p = decode_mode_spec_buf;
    int pad = width - 2;
    while (pad-- > 0)
      *p++ = ' ';
    *p++ = '?';
    *p++ = '?';
    *p = '\0';
    return decode_mode_spec_buf;
  }
      }
      break;
    }
  if (STRINGP (obj))
    {
      *string = obj;
      return SSDATA (obj);
    }
  else
    return "";
}

DEFUN ("line-number-at-position", Fline_number_at_position, Sline_number_at_position, 1, 2, 0,
       doc: /* Return line number at position.  */)
  (Lisp_Object window, Lisp_Object pos)
{
  struct window *w = decode_live_window (window);
  Lisp_Object buf;
  struct buffer *b;
  buf = w->contents;
  CHECK_BUFFER (buf);
  b = XBUFFER (buf);
  struct buffer *old_buffer = NULL;
  Lisp_Object foo_string;
  const char *spec;
  EMACS_INT opoint;
  EMACS_INT posint;
  Lisp_Object value;
  if (b != current_buffer)
    {
      old_buffer = current_buffer;
      set_buffer_internal (b);
    }
  if (!NILP (pos))
    {
      opoint = PT;
      posint = XINT (pos);
      SET_PT (posint);
    }
  spec = internal_line_number_at_position (w, 'l', 0, &foo_string);
  if (!NILP (pos))
    SET_PT (opoint);
  if (old_buffer)
    set_buffer_internal (old_buffer);
  value = build_string (spec);
  value = Fstring_to_number (value, make_number (10));
  return value;
}

DEFSYM (Qline_number_at_position, "line-number-at-position");