Subject: bug#30226: Fixing it->pixel_width / it->current_x when tabs and line numbers.

From: Keith David Bershatsky <[email-masked]>
Date: Sat, 27 Jan 2018 13:20:55 -0800

Thank you, Eli, for looking into #30226.

> Your problem description talks about it->pixel_width, not about
> glyph->pixel_width, but the latter should be equal to the former . . .,
> 
* * *
> 
> I don't understand why you are saying that it->current_x is wrong.
> What is that assertion based on?  Maybe there's some misunderstanding
> of what current_x is and relative to what position does it measure the
> X coordinate.  Can you tell what you expected current_x to be in some
> specific situation (i.e., specific horizontal scroll of the display
> in the above scenario), and what you really found?

dump-glyph-row is _correct_, assuming that a redisplay has occurred prior to calling that function.

Feature request 17684 (i.e., crosshairs) uses move_it_in_display_line_to -- by increments of it.pixel_width -- to dump various values for each character in the current line.  I reproduce issue #30226 by placing IT at the beginning of the current line (i.e., it.current_x == 0) and iterating over each character in that line:

  move_it_in_display_line_to (&it, ZV, it.current_x + it.pixel_width,
                              MOVE_TO_POS | MOVE_TO_X);

Your outline of the steps needed to reproduce this issue requires a few additional steps:

Step 0.5) Build a new Emacs 26 with the reduced/simplified function named bug-30226 (written below); and, add `defsubr (&Sbug_30226);` to syms_of_xdisp.

Step 1) No changes.

Step 2) No changes.

Step 3) No changes.

Step 4) Instead of typing "M-: (scroll-left 1) RET", type "M-: (bug-30226) RET".  w->hscroll should now equal 1.  In step 4, there is a _hiccup_ with the initial value of it->pixel_width being wrong as to the tab STRETCH, and the subsequent value of it->pixel_width is correct as to the tab STRETCH.

1.  NOTHING
    it.c (0)
    w->hscroll (1)
    it.current_x (0)
    it.pixel_width (0)

2.  TAB CHARACTER
    it.c (187)
    w->hscroll (1)
    it.current_x (0)
    it.pixel_width (7)

3.  TAB STRETCH
    it.c (9)
    w->hscroll (1)
    it.current_x (7)
    it.pixel_width (49)

4.  TAB STRETCH
    it.c (9)
    w->hscroll (1)
    it.current_x (35)
    it.pixel_width (42)

5.  TEXT
    it.c (72)
    w->hscroll (1)
    it.current_x (77)
    it.pixel_width (7)

* * *

Row     Start       End Used oE><\CTZFesm     X    Y    W    H    V    A    P
==============================================================================
 12       455       467   17 111000110000     0  192  154   16   16   12   12
           -1        -1	    0
           -1        -1
           -1        -1
 Glyph#  Type       Pos   O   W     Code      C Face LR
      0     C        -1   0   7 0x000020          29 00
      1     C        -1   0   7 0x000031      1   29 00
      2     C        -1   0   7 0x000033      3   29 00
      3     C        -1   0   7 0x000020          29 00
      4     S       455   B  42 0x000000          31 00
      5     C       456   B   7 0x000048      H    0 00
      6     C       457   B   7 0x000065      e    0 00
      7     C       458   B   7 0x00006c      l    0 00
      8     C       459   B   7 0x00006c      l    0 00
      9     C       460   B   7 0x00006f      o    0 00
     10     C       461   B   7 0x00002d      -    0 00
     11     C       462   B   7 0x000077      w    0 00
     12     C       463   B   7 0x00006f      o    0 00
     13     C       464   B   7 0x000072      r    0 00
     14     C       465   B   7 0x00006c      l    0 00
     15     C       466   B   7 0x000064      d    0 00
     16     C         0   0   7 0x000020           0 00

Step 5) Type "M-: (bug-30226) RET".  w->hscroll should now equal 2.  In step 5, the value for it->pixel_width is wrong as to the tab STRETCH and it->current_x is wrong as to the characters that follow the tab STRETCH -- probably because it->pixel_width of the tab STRETCH was wrong.

1.  NOTHING
    it.c (0)
    w->hscroll (2)
    it.current_x (0)
    it.pixel_width (0)

2.  TAB CHARACTER
    it.c (187)
    w->hscroll (2)
    it.current_x (0)
    it.pixel_width (7)

3.  TAB STRETCH
    it.c (9)
    w->hscroll (2)
    it.current_x (7)
    it.pixel_width (49)

4.  TEXT
    it.c (72)
    w->hscroll (2)
    it.current_x (84)
    it.pixel_width (7)

* * *

Row     Start       End Used oE><\CTZFesm     X    Y    W    H    V    A    P
==============================================================================
 12       455       467   17 111000110000     0  192  147   16   16   12   12
           -1        -1	    0
           -1        -1
           -1        -1
 Glyph#  Type       Pos   O   W     Code      C Face LR
      0     C        -1   0   7 0x000020          29 00
      1     C        -1   0   7 0x000031      1   29 00
      2     C        -1   0   7 0x000033      3   29 00
      3     C        -1   0   7 0x000020          29 00
      4     S       455   B  35 0x000000          31 00
      5     C       456   B   7 0x000048      H    0 00
      6     C       457   B   7 0x000065      e    0 00
      7     C       458   B   7 0x00006c      l    0 00
      8     C       459   B   7 0x00006c      l    0 00
      9     C       460   B   7 0x00006f      o    0 00
     10     C       461   B   7 0x00002d      -    0 00
     11     C       462   B   7 0x000077      w    0 00
     12     C       463   B   7 0x00006f      o    0 00
     13     C       464   B   7 0x000072      r    0 00
     14     C       465   B   7 0x00006c      l    0 00
     15     C       466   B   7 0x000064      d    0 00
     16     C         0   0   7 0x000020           0 00

I have further reduced/simplified our test function (below) that uses move_it_in_display_line_to to iterate over each character in the current line, and print the relevant values to STDERR as the iteration is occurring.  At the very end of the test function, dump_glyph_row is called (following a forced redisplay) to help us compare its values to the previous values returned subsequent to each call of move_it_in_display_line_to.

DEFUN ("bug-30226", Fbug_30226, Sbug_30226, 0, 0, 0,
       doc: /* Debug the pixel-width of a stretch tab. */)
  (void)
{
  Fscroll_left (make_number (1), Qnil);
  struct window *w = decode_live_window (selected_window);
  struct frame *f = XFRAME (w->frame);
  struct it it;
  void *itdata = bidi_shelve_cache ();
  enum move_it_result rc = MOVE_X_REACHED;
  struct text_pos start_text_position;
  int count = 1;
/* ******************************************************************************
                      START DISPLAY -- w->start
****************************************************************************** */
  /* Begin the journey at w->start. */
  SET_TEXT_POS_FROM_MARKER (start_text_position, w->start);
  start_display (&it, w, start_text_position);
  struct face *face = FACE_FROM_ID (it.f, it.face_id);
  struct font *font = face->font;
/* ******************************************************************************
                GO TO THE BEGINNING OF THE CURRENT LINE.
****************************************************************************** */
  /* Place the IT on the current line containing PT. */
  int voffset = (WINDOW_HEADER_LINE_HEIGHT (w) > 0
                 && w->output_cursor.vpos > 0)
                  ? w->output_cursor.vpos - 1
                  : w->output_cursor.vpos;
  if (voffset > 0)
    move_it_by_lines (&it, voffset);
/* ******************************************************************************
             MOVE IT OVER EACH CHARACTER ON THE CURRENT LINE.
****************************************************************************** */
  while (true)
    {
      if (ITERATOR_AT_END_OF_LINE_P (&it)
          || FETCH_BYTE (IT_BYTEPOS (it)) == '\n'
          || rc == MOVE_POS_MATCH_OR_ZV)
        break;
/* ******************************************************************************
                       DUMP RELEVANT GLYPH INFORMATION
****************************************************************************** */
      if (w->hscroll > 0)
        {
          int w_hscroll = w->hscroll;
          fprintf (stderr, "\n%d.  %s\n\
    it.c (%d)\n\
    w->hscroll (%d)\n\
    it.current_x (%d)\n\
    it.pixel_width (%d)\n",
                 count,
                 (it.c == 0
                   ? "NOTHING"
                   : it.c == 187
                     ? "TAB CHARACTER"
                   : it.c == '\t'
                     ? "TAB STRETCH"
                   : "TEXT"),
                 it.c,
                 w_hscroll,
                 it.current_x,
                 it.pixel_width);
        }
/* ******************************************************************************
                       MOVE IT -- INCREMENT == IT.PIXEL_WIDTH 
****************************************************************************** */
      rc = move_it_in_display_line_to (&it, ZV, it.current_x + it.pixel_width,
                                       MOVE_TO_POS | MOVE_TO_X);
      count = count + 1;
      if (rc == MOVE_LINE_CONTINUED)
        break;
      if (it.current_x - it.first_visible_x + font->space_width >=
          window_box_width (w, TEXT_AREA))
        break;
    }
/* ******************************************************************************
                         REDISPLAY AND DUMP_GLPYH_ROW
****************************************************************************** */
  redisplay_internal ();
  fprintf (stderr, "\n");
  struct glyph_row *glyph_row = MATRIX_ROW (w->current_matrix, it.vpos);
  dump_glyph_row (glyph_row, it.vpos, 2);
  bidi_unshelve_cache (itdata, false);
  return Qnil;
}