Function: vertical-motion

vertical-motion is a function defined in indent.c.

Signature

(vertical-motion LINES &optional WINDOW CUR-COL)

Documentation

Move point to start of the screen line LINES lines down.

If LINES is negative, this means moving up.

This function is an ordinary cursor motion function which calculates the new position based on how text would be displayed. The new position may be the start of a line, or the start of a continuation line, or the start of the visible portion of a horizontally-scrolled line.

The function returns number of screen lines moved over; that usually equals LINES, but may be closer to zero if beginning or end of buffer was reached.

The optional second argument WINDOW specifies the window to use for parameters such as width, horizontal scrolling, and so on. The default is to use the selected window's parameters.

If LINES is zero, point will move to the first visible character on the current screen line.

LINES can optionally take the form (COLS . LINES), in which case the motion will stop at the COLSth column from the visual start of the line (if such column exists on that line, that is). If the line is scrolled horizontally, COLS is interpreted visually, i.e., as addition to the columns of text beyond the left edge of the window. If LINES is a cons cell, its car COLS can be a float, which allows specifying an accurate position of point on a screen line that mixes fonts or uses variable-pitch font: COLS is interpreted in units of the canonical character width, and is internally converted to pixel units; point will then stop at the position closest to that pixel coordinate. The cdr of the cons, LINES, must be an integer; if it is zero, this function moves point horizontally in the current screen line, to the position specified by COLS.

The optional third argument CUR-COL specifies the horizontal window-relative coordinate of point, in units of frame's canonical character width, where the function is invoked. If this argument is omitted or nil, the function will determine the point coordinate by going back to the beginning of the line.

vertical-motion always uses the current buffer, regardless of which buffer is displayed in WINDOW. This is consistent with other cursor motion functions and makes it possible to use vertical-motion in any buffer, whether or not it is currently displayed in some window.

View in manual

Probably introduced at or before Emacs version 19.23.

Source Code

// Defined in /usr/src/emacs/src/indent.c
// Skipping highlighting due to helpful-max-highlight.
{
  struct it it;
  struct text_pos pt;
  struct window *w;
  Lisp_Object lcols = Qnil;
  void *itdata = NULL;
  specpdl_ref count = SPECPDL_INDEX ();

  /* Allow LINES to be of the form (HPOS . VPOS) aka (COLUMNS . LINES).  */
  if (CONSP (lines))
    {
      lcols = XCAR (lines);
      CHECK_NUMBER (lcols);
      lines = XCDR (lines);
    }

  CHECK_FIXNUM (lines);
  w = decode_live_window (window);

  if (XBUFFER (w->contents) != current_buffer)
    {
      /* Set the window's buffer temporarily to the current buffer.  */
      Lisp_Object old = list4 (window, w->contents,
			       make_fixnum (marker_position (w->pointm)),
			       make_fixnum (marker_byte_position (w->pointm)));
      record_unwind_protect (restore_window_buffer, old);
      wset_buffer (w, Fcurrent_buffer ());
      set_marker_both (w->pointm, w->contents,
		       BUF_PT (current_buffer), BUF_PT_BYTE (current_buffer));
    }

  if (noninteractive)
    {
      struct position pos;
      pos = *vmotion (PT, PT_BYTE, XFIXNUM (lines), w);
      SET_PT_BOTH (pos.bufpos, pos.bytepos);
      it.vpos = pos.vpos;
    }
  else
    {
      ptrdiff_t it_start, it_overshoot_count = 0;
      int first_x;
      bool overshoot_handled = 0;
      bool disp_string_at_start_p = 0;
      ptrdiff_t nlines = XFIXNUM (lines);
      int vpos_init = 0;
      double start_col UNINIT;
      int start_x UNINIT;
      int to_x = -1;

      bool start_x_given = !NILP (cur_col);
      if (start_x_given)
	{
	  start_col = extract_float (cur_col);
	  start_x = window_column_x (w, window, start_col, cur_col);
	}

      /* When displaying line numbers, we need to prime IT's
	 lnum_width with the value calculated at window's start, since
	 that's what normal window redisplay does.  Otherwise C-n/C-p
	 will sometimes err by one column.  */
      int lnum_width = 0;
      int lnum_pixel_width = 0;
      if (!NILP (Vdisplay_line_numbers))
	line_number_display_width (w, &lnum_width, &lnum_pixel_width);
      SET_TEXT_POS (pt, PT, PT_BYTE);
      itdata = bidi_shelve_cache ();
      record_unwind_protect_void (unwind_display_working_on_window);
      display_working_on_window_p = true;
      start_display (&it, w, pt);
      it.lnum_width = lnum_width;
      first_x = it.first_visible_x;
      it_start = IT_CHARPOS (it);

      /* See comments below for why we calculate this.  */
      if (it.cmp_it.id >= 0)
	it_overshoot_count = 0;
      else if (it.method == GET_FROM_STRING)
	{
	  const char *s = SSDATA (it.string);
	  const char *e = s + SBYTES (it.string);

	  disp_string_at_start_p =
	  /* If it.area is anything but TEXT_AREA, we need not bother
	     about the display string, as it doesn't affect cursor
	     positioning.  */
	    it.area == TEXT_AREA
	    && it.string_from_display_prop_p
	    /* A display string on anything but buffer text (e.g., on
	       an overlay string) doesn't affect cursor positioning.  */
	    && (it.sp > 0 && it.stack[it.sp - 1].method == GET_FROM_BUFFER);
	  while (s < e)
	    {
	      if (*s++ == '\n')
		it_overshoot_count++;
	    }
	  if (!it_overshoot_count)
	    it_overshoot_count = -1;
	}
      else
	it_overshoot_count =
	  /* If image_id is negative, it's a fringe bitmap, which by
	     definition doesn't affect display in the text area.  */
	  !((it.method == GET_FROM_IMAGE && it.image_id >= 0)
	    || it.method == GET_FROM_STRETCH);

      if (start_x_given)
	{
	  it.hpos = start_col;
	  it.current_x = start_x;
	}
      else
	{
	  /* Scan from the start of the line containing PT.  If we don't
	     do this, we start moving with IT->current_x == 0, while PT is
	     really at some x > 0.  */
	  reseat_at_previous_visible_line_start (&it);
	  it.current_x = it.hpos = 0;
	}
      if (IT_CHARPOS (it) != PT)
	/* We used to temporarily disable selective display here; the
	   comment said this is "so we don't move too far" (2005-01-19
	   checkin by kfs).  But this does nothing useful that I can
	   tell, and it causes Bug#2694 .  -- cyd */
	/* When the position we started from is covered by a display
	   string, move_it_to will overshoot it, while vertical-motion
	   wants to put the cursor _before_ the display string.  So in
	   that case, we move to buffer position before the display
	   string, and avoid overshooting.  But if the position before
	   the display string is a newline, we don't do this, because
	   otherwise we will end up in a screen line that is one too
	   far back.  */
	move_it_to (&it,
		    (!disp_string_at_start_p
		     || FETCH_BYTE (IT_BYTEPOS (it)) == '\n')
		    ? PT
		    : PT - 1,
		    -1, -1, -1, MOVE_TO_POS);

      /* IT may move too far if truncate-lines is on and PT lies
	 beyond the right margin.  IT may also move too far if the
	 starting point is on a Lisp string that has embedded
	 newlines, or spans several screen lines.  In these cases,
	 backtrack.  */
      if (IT_CHARPOS (it) > it_start)
	{
	  /* We need to backtrack also if the Lisp string contains no
	     newlines, but there is a newline right after it.  In this
	     case, IT overshoots if there is an after-string just
	     before the newline.  */
	  if (it_overshoot_count < 0
	      && it.method == GET_FROM_BUFFER
	      && it.c == '\n')
	    it_overshoot_count = 1;
	  else if (it_overshoot_count == 1 && it.vpos == 0
		   && it.current_x < it.last_visible_x)
	    {
	      /* If we came to the same screen line as the one where
		 we started, we didn't overshoot the line, and won't
		 need to backtrack after all.  This happens, for
		 example, when PT is in the middle of a composition.  */
	      it_overshoot_count = 0;
	    }
	  else if (disp_string_at_start_p && it.vpos > 0)
	    {
	      /* This is the case of a display string that spans
		 several screen lines.  In that case, we end up at the
		 end of the string, and it.vpos tells us how many
		 screen lines we need to backtrack.  */
	      it_overshoot_count = it.vpos;
	    }
	  /* We might overshoot if lines are truncated and point lies
	     beyond the right margin of the window.  */
	  if (it.line_wrap == TRUNCATE && it.current_x >= it.last_visible_x
	      && it_overshoot_count == 0 && it.vpos > 0)
	    it_overshoot_count = 1;
	  if (it_overshoot_count > 0)
	    move_it_by_lines (&it, -it_overshoot_count);

	  overshoot_handled = 1;
	}
      else if (IT_CHARPOS (it) == PT - 1
	       && FETCH_BYTE (PT_BYTE - 1) == '\n'
	       && nlines <= 0)
	{
	  /* The position we started from was covered by a display
	     property, so we moved to position before the string, and
	     backed up one line, because the character at PT - 1 is
	     a newline.  So we need one less line to go up (or exactly
	     one line to go down if nlines == 0).  */
	  nlines++;
	  /* But we still need to record that one line, in order to
	     return the correct value to the caller.  */
	  vpos_init = -1;

	  overshoot_handled = 1;
	}
      if (!NILP (lcols))
	to_x =
	  window_column_x (w, window, XFLOATINT (lcols), lcols)
	  + lnum_pixel_width;
      if (nlines <= 0)
	{
	  it.vpos = vpos_init;
	  it.current_y = 0;
	  /* Do this even if LINES is 0, so that we move back to the
	     beginning of the current line as we ought.  */
	  if ((nlines < 0 && IT_CHARPOS (it) > BEGV)
	      || (nlines == 0 && !(start_x_given && start_x <= to_x)))
	    move_it_by_lines (&it, max (PTRDIFF_MIN, nlines));
	}
      else if (overshoot_handled)
	{
	  it.vpos = vpos_init;
	  it.current_y = 0;
	  move_it_by_lines (&it, min (PTRDIFF_MAX, nlines));
	}
      else
	{
	  /* Otherwise, we are at the first row occupied by PT, which
	     might span multiple screen lines (e.g., if it's on a
	     multi-line display string).  We want to start from the
	     last line that it occupies.  */
	  if (it_start < ZV)
	    {
	      if ((it.bidi_it.scan_dir >= 0 || it.vpos == vpos_init)
		  ? IT_CHARPOS (it) < it_start
		  : IT_CHARPOS (it) > it_start)
		{
		  it.vpos = 0;
		  it.current_y = 0;
		  move_it_by_lines (&it, 1);
		}
	      while (IT_CHARPOS (it) == it_start)
		{
		  it.vpos = 0;
		  it.current_y = 0;
		  move_it_by_lines (&it, 1);
		}
	      if (nlines > 1)
		move_it_by_lines (&it, min (PTRDIFF_MAX, nlines - 1));
	    }
	  else	/* it_start = ZV */
	    {
	      it.vpos = 0;
	      it.current_y = 0;
	      move_it_by_lines (&it, min (PTRDIFF_MAX, nlines));
	      /* We could have some display or overlay string at ZV,
		 in which case it.vpos will be nonzero now, while
		 actually we didn't move vertically at all.  */
	      if (IT_CHARPOS (it) == CHARPOS (pt) && CHARPOS (pt) == it_start)
		it.vpos = 0;
	    }
	}

      /* Move to the goal column, if one was specified.  If the window
	 was originally hscrolled, the goal column is interpreted as
	 an addition to the hscroll amount.  */
      if (!NILP (lcols))
	{
	  if (it.method == GET_FROM_STRING && !NILP (it.from_overlay))
	    reseat_at_previous_visible_line_start(&it);

	  move_it_in_display_line (&it, ZV, first_x + to_x, MOVE_TO_X);
	  /* If we find ourselves in the middle of an overlay string
	     which includes a newline after current string position,
	     we need to move by lines until we get out of the string,
	     and then reposition point at the requested X coordinate;
	     if we don't, the cursor will be placed just after the
	     string, which might not be the requested column.  */
	  if (nlines >= 0 && it.area == TEXT_AREA)
	    {
	      while (it.method == GET_FROM_STRING
		     && !it.string_from_display_prop_p
		     && memchr (SSDATA (it.string) + IT_STRING_BYTEPOS (it),
				'\n',
				SBYTES (it.string) - IT_STRING_BYTEPOS (it)))
		{
		  move_it_by_lines (&it, 1);
		  move_it_in_display_line (&it, ZV, first_x + to_x, MOVE_TO_X);
		}
	    }
	}

      SET_PT_BOTH (IT_CHARPOS (it), IT_BYTEPOS (it));
      bidi_unshelve_cache (itdata, 0);
    }

  return unbind_to (count, make_fixnum (it.vpos));
}