Subject: bug#28936: move_it_in_display_line_to returns MOVE_POS_MATCH_OR_ZV before ZV

From: Keith David Bershatsky <[email-masked]>
Date: Sat, 21 Oct 2017 22:14:39 -0700

Here is a second draft move_it_in_display_line_to_x, with compatibility for word-wrap and also horizontal scrolling the current line that erroneously returns MOVE_LINE_TRUNCATED when trying to reach a target X that is about 1,000 pixels from the beginning of the line.

int
move_it_in_display_line_to_x (struct window *w, struct it *it, int target_x)
{
  struct it saved_it;
  void *saved_data = bidi_shelve_cache ();
  enum move_it_result rc = MOVE_X_REACHED;
  int new_x, prev_x;
  /* Advance straight to `it->first_visible_x` if IT is prior thereto. */
  if (it->current_x < it->first_visible_x)
    move_it_in_display_line_to (it, ZV, it->first_visible_x, MOVE_TO_POS | MOVE_TO_X);
  /* When horizontal scrolling a long line that approach or exceed an `it.current.x`
  of approximately 1000, `rc` will erroneously return early as MOVE_LINE_TRUNCATED
  without pushing on forwards until it reaches the target_x.  As a workaround, we
  ignore MOVE_LINE_TRUNCATED.  It is uncertain whether this is a bug. */
  while (it->current_x + it->pixel_width <= target_x
         && (rc == MOVE_X_REACHED
             || rc == MOVE_LINE_TRUNCATED
             || (it->line_wrap == WORD_WRAP
                 && rc == MOVE_POS_MATCH_OR_ZV)))
    {
      SAVE_IT (saved_it, *it, saved_data);
      new_x = it->current_x + it->pixel_width;
      if (new_x == it->current_x)
        new_x++;
      rc = move_it_in_display_line_to (it, ZV, new_x, MOVE_TO_POS | MOVE_TO_X);
      if (ITERATOR_AT_END_OF_LINE_P (it)
          || FETCH_BYTE (IT_BYTEPOS (*it)) == '\n'
          /* There is a bug in `move_it_in_display_line_to' such that it returns
          MOVE_POS_MATCH_OR_ZV before reaching ZV when the latter is at the end
          of the line:  abcdefg[EOB].  The workaround is to add an extra check
          using IT_CHARPOS and comparing it to ZV. */
          || (rc == MOVE_POS_MATCH_OR_ZV
              && IT_CHARPOS (*it) == ZV))
        break;
    }
  /* When word-wrap is on, TO_X may lie past the end of a wrapped line.
  Then it->current is the character on the next line, so backtrack to the
  space before the wrap point.  */
  if (it->line_wrap == WORD_WRAP
      && rc == MOVE_LINE_CONTINUED)
    {
      prev_x = max (it->current_x - 1, 0);
      RESTORE_IT (it, &saved_it, saved_data);
      move_it_in_display_line_to (it, -1, prev_x, MOVE_TO_X);
    }
  bidi_unshelve_cache (saved_data, true);
  return rc;
}