ftp.nice.ch/pub/next/unix/editor/elvis-2.0.N.bs.tar.gz#/elvis-2.0.N.bs/move.c

This is move.c in view mode; [Download] [Up]

/* move.c */
/* Copyright 1995 by Steve Kirkendall */

char id_move[] = "$Id: move.c,v 2.39 1996/09/10 16:51:38 steve Exp $";

#include "elvis.h"


/* This function implements the "h" command */
RESULT m_left(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	MARK	tmp;
	DEFAULT(1);

	/* find the start of this line */
	tmp = dispmove(win, 0L, 0L);

	/* if already at the start of the line, then fail */
	if (markoffset(tmp) == markoffset(win->state->cursor))
	{
		return RESULT_ERROR;
	}

	/* move either the requested number of characters left, or to the
	 * start of the line, whichever is closer
	 */
	if (markoffset(win->state->cursor) - vinf->count > markoffset(tmp))
	{
		markaddoffset(win->state->cursor, -vinf->count);
	}
	else
	{
		marksetoffset(win->state->cursor, markoffset(tmp));
	}

	return RESULT_COMPLETE;
}


/* This function implements the "l" command */
RESULT m_right(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	MARK	tmp;
	DEFAULT(1);

	/* find the end of this line.  This is complicated by the fact that
	 * when used as the target of an operator, the l command can move
	 * past the end of the line.
	 */
	if (vinf->oper && !win->state->acton)
		tmp = (*win->md->move)(win, win->cursor, 0L, INFINITY, False);
	else
		tmp = dispmove(win, 0L, INFINITY);

	/* if already at the end of the line, then fail */
	if (markoffset(tmp) == markoffset(win->state->cursor))
	{
		return RESULT_ERROR;
	}

	/* move either the requested number of characters right, or to the
	 * end of the line, whichever is closer
	 */
	if (markoffset(win->state->cursor) + vinf->count < markoffset(tmp))
	{
		markaddoffset(win->state->cursor, vinf->count);
	}
	else
	{
		marksetoffset(win->state->cursor, markoffset(tmp));
	}

	return RESULT_COMPLETE;
}


/* This function implements the "j" and "k" functions */
RESULT m_updown(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	MARK	tmp = NULL;
	DEFAULT(1);

	/* do the move */
	switch (vinf->command)
	{
	  case '_':
		/* decremement count & then treat like <Enter>... */
		vinf->count--;
		/* fall through... */

	  case ELVCTRL('J'):
	  case ELVCTRL('M'):
	  case ELVCTRL('N'):
	  case '+':
	  case 'j':
		tmp = dispmove(win, vinf->count, win->wantcol);
		break;

	  case ELVCTRL('P'):
	  case '-':
	  case 'k':
		tmp = dispmove(win, -vinf->count, win->wantcol);
		break;

#ifndef NDEBUG
	  default:
		abort();
#endif
	}

	/* check for goofy return values */
	if (markoffset(tmp) < 0
	 || markoffset(tmp) >= o_bufchars(markbuffer(win->state->cursor))
	 || (markoffset(tmp) == markoffset(win->state->cursor) && vinf->count != 0))
	{
		return RESULT_ERROR;
	}

	/* It's good! */
	marksetoffset(win->state->cursor, markoffset(tmp));
	return RESULT_COMPLETE;
}


/* This function implements the "^" function */
RESULT m_front(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	CHAR	*cp;

	/* scan from the start of the line, to the first non-space */
	scanalloc(&cp, dispmove(win, 0, 0));
	if (cp && (*cp == '\t' || *cp == ' '))
	{
		do
		{
			scannext(&cp);
		} while (cp && (*cp == ' ' || *cp == '\t'));
		if (*cp == '\n')
		{
			scanprev(&cp);
		}
	}
	if (!cp)
	{
		return RESULT_ERROR;
	}
	marksetoffset(win->state->cursor, markoffset(scanmark(&cp)));
	scanfree(&cp);
	return RESULT_COMPLETE;
}

/* This function implements the <Shift-G>, <Control-G>, and <%> commands, which
 * move the cursor to a specific line, character, or percentage of the buffer.
 * The number is given in the "count" field; if no number is given, then either
 * move to the last line, show buffer statistics, or move to matching bracket,
 * respectively.
 */
RESULT m_absolute(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	BUFFER	buf = markbuffer(win->state->cursor);
	CHAR	match;	/* the parenthesis we want (for <%> command) */
	CHAR	nest = 0;/* the starting parenthesis */
	CHAR	*cp;	/* used for scanning through text */
	long	count;	/* nesting depth */
	EXINFO	xinfb;	/* an ex command */

	assert(vinf->command == 'G' || vinf->command == ELVCTRL('G') || vinf->command == '%');

	switch (vinf->command)
	{
	  case 'G':
		DEFAULT(o_buflines(buf));

		/* Try to go to the requestedc line.  Catch errors, including a
		 * numberless <G> in an empty buffer.
		 */
		if (!marksetline(win->state->cursor, vinf->count))
		{
			msg(MSG_ERROR, "[d]only $1 lines", o_buflines(buf));
			return RESULT_ERROR;
		}
		break;

	  case ELVCTRL('G'):
		if (!vinf->count)
		{
			/* no count, just show buffer status */
			memset((char *)&xinfb, 0, sizeof xinfb);
			xinfb.window = win;
			xinfb.defaddr = *win->state->cursor;
			xinfb.command = EX_FILE;
			ex_file(&xinfb);
		}
		else if (vinf->count < 0 || vinf->count > o_bufchars(buf))
		{
			/* request offset is out of range */
			return RESULT_ERROR;
		}
		else
		{
			/* set the cursor to the requested offset */
			marksetoffset(win->state->cursor, vinf->count - 1);
		}
		break;

	  case '%':
		if (!vinf->count)
		{
			/* search forward within line for one of "[](){}" */
			for (match = '\0', scanalloc(&cp, win->state->cursor); !match;)
			{
				/* if hit end-of-line or end-of-buffer without
				 * finding a parenthesis, then fail.
				 */
				if (!cp || *cp == '\n')
				{
					scanfree(&cp);
					return RESULT_ERROR;
				}

				/* if parenthesis, great! else keep looking */
				switch (*cp)
				{
				  case '[':	match = ']';	break;
				  case ']':	match = '[';	break;
				  case '(':	match = ')';	break;
				  case ')':	match = '(';	break;
				  case '{':	match = '}';	break;
				  case '}':	match = '{';	break;
				  default:	scannext(&cp);
				}
			}
			assert(cp != NULL);
			nest = *cp;

			/* search forward or backward for match */
			if (match == '(' || match == '[' || match == '{')/*)]}*/
			{
				/* search backward */
				for (count = 1; count > 0; )
				{
					/* back up 1 char; give up at top of buffer */
					if (!scanprev(&cp))
					{
						break;
					}

					/* check the char */
					if (*cp == match)
						count--;
					else if (*cp == nest)
						count++;
				}
			}
			else
			{
				/* search forward */
				for (count = 1; count > 0; )
				{
					/* advance 1 char; give up at end of buffer */
					if (!scannext(&cp))
					{
						break;
					}

					/* check the char */
					if (*cp == match)
						count--;
					else if (*cp == nest)
						count++;
				}
			}

			/* if we hit the end of the buffer, then fail */
			if (!cp)
			{
				scanfree(&cp);
				return RESULT_ERROR;
			}

			/* move the cursor to the match */
			marksetoffset(win->state->cursor, markoffset(scanmark(&cp)));
			scanfree(&cp);
		}
		else if (vinf->count < 1 || vinf->count > 100)
		{
			msg(MSG_ERROR, "bad percentage");
			return RESULT_ERROR;
		}
		else
		{
			/* Compute the character offset, given the percentage.
			 * I'm slightly careful here to avoid overflowing
			 * the long int which stores the offset.
			 */
			if (o_bufchars(buf) > 1000000L)
			{
				marksetoffset(win->state->cursor,
					(o_bufchars(buf) / 100) * vinf->count);
			}
			else
			{
				marksetoffset(win->state->cursor,
					(o_bufchars(buf) * vinf->count) / 100);
			}
			vinf->tweak |= TWEAK_FRONT;
		}
		return RESULT_COMPLETE;
	}

	return RESULT_COMPLETE;
}

/* Move to a mark.  This function implements the <'> and <`> commands. */
RESULT m_mark(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	MARK	newmark;

	/* check for <'><'> or <`><`> */
	if (vinf->command == vinf->key2)
	{
		if (win->prevcursor)
		{
			assert(markbuffer(win->state->cursor) == markbuffer(win->prevcursor));
			marksetoffset(win->state->cursor, markoffset(win->prevcursor));
			return RESULT_COMPLETE;
		}
		return RESULT_ERROR;
	}

	/* else key2 had better be a lowercase ASCII letter */
	if (vinf->key2 < 'a' || vinf->key2 > 'z')
	{
		return RESULT_ERROR;
	}

	/* look up the named mark */
	newmark = namedmark[vinf->key2 - 'a'];
	if (!newmark)
	{
		msg(MSG_ERROR, "[C]'$1 unset", vinf->key2);
		return RESULT_ERROR;
	}

	/* if the named mark is in a different buffer, fail. */
	/* (A later version of elvis may be able to do this!) */
	if (markbuffer(newmark) != markbuffer(win->state->cursor))
	{
		msg(MSG_ERROR, "[C]'$1 in other buffer", vinf->key2);
		return RESULT_ERROR;
	}

	/* move to the named mark */
	marksetoffset(win->state->cursor, markoffset(newmark));
	return RESULT_COMPLETE;
}

/* This function implements the whitespace-delimited word movement commands:
 * <Shift-W>, <Shift-B>, and <Shift-E>.
 */
RESULT m_bigword(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	BOOLEAN	whitespace;	/* do we include following whitespace? */
	BOOLEAN backward;	/* are we searching backwards? */
	long	offset;		/* offset of *cp character */
	long	count;		/* number of words to skip */
	long	end;		/* offset of the end of the buffer */
	BOOLEAN	inword;		/* are we currently in a word? */
	CHAR	*cp;		/* used for scanning chars of buffer */

	DEFAULT(1);

	/* start the scan */
	offset = markoffset(win->state->cursor);
	scanalloc(&cp, win->state->cursor);
	assert(cp != NULL);
	count = vinf->count;
	end = o_bufchars(markbuffer(win->state->cursor));

	/* figure out which type of movement we'll be doing */
	switch (vinf->command)
	{
	  case 'B':
		backward = True;
		whitespace = False;
		inword = False;
		break;

	  case 'E':
		backward = False;
		whitespace = False;
		inword = False;
		break;

	  default:
		backward = False;
		inword = (BOOLEAN)!isspace(*cp);
		if (vinf->oper == 'c')
		{
			/* "cW" acts like "cE", pretty much */
			whitespace = False;
			vinf->tweak |= TWEAK_INCL;

			/* starting on whitespace? */
			if (!inword)
			{
				/* When "cW" starts on whitespace, it changes
				 * one less word than normal.  If it would
				 * normally change just one word, then it
				 * should change a single whitespace character.
				 */
				vinf->count--;
				if (vinf->count == 0)
				{
					scanfree(&cp);
					return RESULT_COMPLETE;
				}
			}
			else if (markoffset(win->state->cursor) > 0)
			{
				/* When "cW" starts on the last character of a
				 * word, then it should change just that last
				 * character.  By temporarily moving the
				 * cursor back one char, we can achieve this
				 * effect without affecting the results of any
				 * other movement.
				 */
				scanprev(&cp);
				offset--;
			}
		}
		else
		{
			whitespace = True;
		}
		break;
	}

	/* continue... */
	if (backward)
	{
		/* move backward until we hit the top of the buffer, or
		 * the start of the desired word.
		 */
		while (count > 0 && offset > 0)
		{
			scanprev(&cp);
			assert(cp != NULL);
			if (isspace(*cp))
			{
				if (inword)
				{
					count--;
				}
				inword = False;
			}
			else
			{
				inword = True;
			}
			if (count > 0)
			{
				offset--;
				if (offset == 0 && count == 1)
				{
					count = 0;
				}
			}
		}
	}
	else
	{
		/* move forward until we hit the end of the buffer, or
		 * the start of the desired word.
		 */
		while (count > 0 && offset < end - 1)
		{
			scannext(&cp);
			assert(cp != NULL);
			if (isspace(*cp))
			{
				if (vinf->oper && *cp == '\n' && count == 1)
				{
					count = 0;
					if (!(vinf->tweak & TWEAK_INCL))
						offset++;
				}
				else if (inword && !whitespace)
				{
					count--;
				}
				inword = False;
				if (count > 0)
				{
					offset++;
				}
			}
			else
			{
				if (!inword && whitespace)
				{
					count--;
				}
				inword = True;
				offset++;
			}
		}
	}

	/* cleanup */
	scanfree(&cp);

	/* if the count didn't reach 0, we failed */
	if (count > 0)
	{
		return RESULT_ERROR;
	}

	/* else set the cursor's offset */
	assert(offset < end && offset >= 0);
	marksetoffset(win->state->cursor, offset);
	return RESULT_COMPLETE;
}

/* This function implements the <b>, <e>, and <w> word movement commands
 * by calling the mode-dependent wordmove function.
 */
RESULT m_word(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	BOOLEAN	whitespace;	/* include trailing whitespace? */
	BOOLEAN backward;	/* are we moving backward? */
	long	span;		/* offset of starting position */
	long	newline;	/* offset of newline */
	CHAR	*cp;		/* used for scanning backward for newline */
	CHAR	atcursor;	/* character at the cursor position */

	DEFAULT(1);

	/* figure out which type of movement we'll be doing */
	switch (vinf->command)
	{
	  case 'b':
		backward = True;
		whitespace = False;
		break;

	  case 'e':
		backward = False;
		whitespace = False;
		break;

	  default:
		backward = False;
		if (vinf->oper == 'c')
		{
			/* "cw" acts like "ce", pretty much */
			whitespace = False;
			vinf->tweak |= TWEAK_INCL;

			/* starting on whitespace? */
			atcursor = scanchar(win->state->cursor);
			if (atcursor == ' ' || atcursor == '\t')
			{
				/* When "cw" starts on whitespace, it changes
				 * one less word than normal.  If it would
				 * normally change just one word, then it
				 * should change a single whitespace character.
				 */
				vinf->count--;
				if (vinf->count == 0)
				{
					return RESULT_COMPLETE;
				}
			}
			else if (markoffset(win->state->cursor) > 0)
			{
				/* When "cw" starts on the last character of a
				 * word, then it should change just that last
				 * character.  By temporarily moving the
				 * cursor back one char, we can achieve this
				 * effect without affecting the results of any
				 * other movement.
				 */
				markaddoffset(win->state->cursor, -1);
			}
		}
		else
		{
			whitespace = True;
		}
		break;
	}

	/* remember the starting position */
	span = markoffset(win->state->cursor);

	/* Call the mode-dependent function.  If we're editing a history buffer
	 * then always use dmnormal's version; else (for the window's main
	 * buffer) use the window's mode's function.
	 */
	if (!(*(win->state->acton ? dmnormal.wordmove : win->md->wordmove))
		(win->state->cursor, vinf->count, backward, whitespace))
	{
		/* movement failed */
		return RESULT_ERROR;
	}

	/* NOTE: If we get here, then the word movement succeeded and the
	 * cursor has been moved.
	 */

	/* We need to avoid newlines for <w> movements that are used as the
	 * target of an operator (except for <c><w> which doesn't include
	 * whitespace).
	 */
	if (whitespace && vinf->oper && vinf->oper != 'c')
	{
		newline = markoffset(win->state->cursor);
		span = newline - span;
		scanalloc(&cp, win->state->cursor);
		while (scanprev(&cp) && span-- > 0)
		{
			if (*cp == '\n')
				newline = markoffset(scanmark(&cp));
		}
		scanfree(&cp);
		marksetoffset(win->state->cursor, newline);
	}
	else if (whitespace && !vinf->oper && scanchar(win->state->cursor) == '\n')
	{
		/* tried a plain old "w" command at end of file -- 
		 * move cursor back to starting point and fail.
		 */
		marksetoffset(win->state->cursor, span);
		return RESULT_ERROR;
	}

	return RESULT_COMPLETE;
}

/* This function scrolls the screen, implementing the <Control-E>, <Control-Y>,
 * <Control-F>, <Control-B>, <Control-D>, and <Control-U> commands.
 */
RESULT	m_scroll(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	MARKBUF	tmp;

	assert(!win->state->acton);
	assert(!(win->state->flags & ELVIS_BOTTOM) || vinf->command == ELVCTRL('D'));

	/* adjust the count */
	if (vinf->command == ELVCTRL('U') || vinf->command == ELVCTRL('D'))
	{
		if (vinf->count == 0)
		{
			vinf->count = o_scroll(win);
		}
		else
		{
			if (vinf->count > o_lines(win) - 1)
			{
				vinf->count = o_lines(win) - 1;
			}
			o_scroll(win) = vinf->count;
		}
	}
	else
	{
		DEFAULT(1);
	}

	/* Do the scroll */
	switch (vinf->command)
	{
	  case ELVCTRL('U'):
		win->di->topline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), -vinf->count, 0, True));
		win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->bottomline), -vinf->count, 0, True));
		marksetoffset(win->cursor, markoffset(dispmove(win, -vinf->count, 0)));
		break;

	  case ELVCTRL('D'):
		win->di->topline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), vinf->count, 0, True));
		win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->bottomline), vinf->count, 0, True));
		marksetoffset(win->cursor, markoffset(dispmove(win, vinf->count, 0)));
		break;

	  case ELVCTRL('Y'):
		win->di->topline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), -vinf->count, 0, True));
		win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->bottomline), -vinf->count, 0, True));
		marksetoffset(win->cursor, markoffset(dispmove(win, 0, win->wantcol)));
		if (markoffset(win->cursor) >= win->di->bottomline)
		{
			marksetoffset(win->cursor, win->di->bottomline);
			marksetoffset(win->cursor, markoffset(dispmove(win, -1, win->wantcol)));
		}
		break;

	  case ELVCTRL('E'):
		win->di->topline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), vinf->count, 0, True));
		win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->bottomline), vinf->count, 0, True));
		if (markoffset(win->cursor) < win->di->topline)
		{
			marksetoffset(win->cursor, win->di->topline);
			marksetoffset(win->cursor, markoffset(dispmove(win, 0, win->wantcol)));
		}
		break;

	  case ELVCTRL('F'):
		marksetoffset(win->cursor, win->di->bottomline);
		win->di->topline = markoffset(dispmove(win, -1, 0));
		marksetoffset(win->cursor, win->di->topline);
		win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), o_lines(win), 0, True));
		break;

	  case ELVCTRL('B'):
		/* note: this adjustment of topline can be sloppy, because
		 * the drawimage() function will perform slop scrolling, if
		 * necessary, to keep the cursor in the screen.
		 */
		marksetoffset(win->cursor, win->di->topline);
		win->di->topline = markoffset(dispmove(win, -o_lines(win), 0));
		win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), o_lines(win), 0, True));
		break;
	}

	/* partially disable optimization for the next redraw - it doesn't
	 * automatically realize that scrolling is a type of change.
	 */
	win->di->logic = DRAW_CHANGED;

	return RESULT_COMPLETE;
}

/* This function moves the cursor to a given column.  The window's desired
 * column ("wantcol") is set to the requested column.  This implements the
 * <|>, <0>, <Control-X>, and <$> commands.
 */
RESULT	m_column(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	MARK	dest;

	/* choose a column number */
	switch (vinf->command)
	{
	  case '|':
	  case '0':
		DEFAULT(1);
		break;

	  case ELVCTRL('X'):
		DEFAULT(o_columns(win));
		vinf->count += win->di->skipped;
		break;

	  case '$':
		vinf->count = INFINITY;
		break;
	}

	/* move to the requested column (or as close as possible). */
	dest = dispmove(win, 0L, vinf->count - 1);
	marksetoffset(win->state->cursor, markoffset(dest));

	/* if the window is editing the main buffer, set wantcol...
	 * even if the cursor didn't quite make it to the requested column.
	 */
	win->wantcol = vinf->count - 1;

	return RESULT_COMPLETE;
}


/* This function implements the character-search commands: f, t, F, T, comma,
 * and semicolon.
 */
RESULT	m_csearch(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	static CHAR	prevcmd;	/* previous command, for <,> and <;> */
	static CHAR	prevtarget;	/* previous target character */
	CHAR		*cp;		/* used for scanning text */

	DEFAULT(1);

	assert(strchr("fFtT,;", vinf->command));

	/* comma and semicolon recall the previous character search */
	if (vinf->command == ';' || vinf->command == ',')
	{
		/* fail if there was no previous command */
		if (!prevcmd)
		{
			msg(MSG_ERROR, "no previous char search");
			return RESULT_ERROR;
		}

		/* use the previous command, or its opposite */
		if (vinf->command == ';')
		{
			vinf->command = prevcmd;
		}
		else if (isupper(prevcmd))
		{
			vinf->command = tolower(prevcmd);
		}
		else
		{
			vinf->command = toupper(prevcmd);
		}

		/* use the previous target character, too */
		vinf->key2 = prevtarget;
	}
	else /* not comma or semicolon */
	{
		/* remember this command so it can be repeated later */
		prevcmd = vinf->command;
		prevtarget = vinf->key2;
	}

	/* Which way should we scan?  Forward or backward? */
	if (islower(vinf->command))
	{
		/* scan forward */
		for (scanalloc(&cp, win->state->cursor);
		     vinf->count > 0 && scannext(&cp) && *cp != '\n';
		     )
		{
			if (*cp == vinf->key2)
			{
				vinf->count--;
			}
		}
	}
	else
	{
		/* scan backward */
		for (scanalloc(&cp, win->state->cursor);
		     vinf->count > 0 && scanprev(&cp) && *cp != '\n';
		     )
		{
			if (*cp == vinf->key2)
			{
				vinf->count--;
			}
		}
	}

	/* if hit EOF or newline, then fail */
	if (!cp || *cp == '\n')
	{
		scanfree(&cp);
		return RESULT_ERROR;
	}

	/* if <t> or <T> then back off one character */
	if (vinf->command == 't')
	{
		scanprev(&cp);
	}
	else if (vinf->command == 'T')
	{
		scannext(&cp);
	}

	/* move the cursor */
	marksetoffset(win->state->cursor, markoffset(scanmark(&cp)));
	scanfree(&cp);
	return RESULT_COMPLETE;
}

/* This funtion moves the cursor to the next tag in the current buffer */
RESULT m_tag(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	MARK	next;	/* where the next tag is located */

	/* This only works when editing the main stratum */
	if (win->state->acton)
		return RESULT_ERROR;

	/* If the display mode has no "next tag" function, then fail */
	if (!win->md->tagnext)
		return RESULT_ERROR;

	/* else call the "next tag" function */
	next = (*win->md->tagnext)(win->cursor);
	if (!next)
		return RESULT_ERROR;

	/* move the cursor to the next tag */
	assert(markbuffer(next) == markbuffer(win->state->cursor));
	marksetoffset(win->state->cursor, markoffset(next));
	return RESULT_COMPLETE;
}


/* This function implements [[ and { movement commands */
RESULT m_bsection(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	BUFFER	buf;	/* buffer being addressed */
	CHAR	*cp;
	CHAR	nroff[3];	/* characters after current position */
	CHAR	*codes;
	BOOLEAN	sect;		/* are we looking through section? */

	DEFAULT(1);

	assert(vinf->command == '[' || vinf->command == '{');

	/* if this is the start of a "learn" mode, do that! */
	if (vinf->command == '[' && vinf->key2 != '[')
	{
		return maplearn(vinf->key2, True);
	}

	/* search backward for a section or paragraph */
	buf = markbuffer(win->state->cursor);
	scanalloc(&cp, win->state->cursor);
	memset(nroff, 0, sizeof nroff);
	nroff[0] = (*cp == '{' ? ' ' : *cp);
	nroff[1] = '\n';
	do
	{
		/* move back one character */
		if (!scanprev(&cp))
			break;

		/* if this is a newline, look for special stuff... */
		if (*cp == '\n')
		{
			if (nroff[0] == '{')
				vinf->count--;
			else if (nroff[1] != '\n' && nroff[0] == '\n' && vinf->command == '{')
				vinf->count--;
			else if (nroff[0] == '.')
			{
				for (codes = o_sections(buf), sect = (BOOLEAN)(vinf->command == '{');
				     codes && *codes;
				     )
				{
					if (codes[0] == nroff[1] &&
						(codes[1] == nroff[2] ||
						    (!isalnum(nroff[2]) &&
							(!codes[1] ||
							    codes[1] == ' '
							)
						     )
						)
					   )
					{
						vinf->count--;
						break;
					}

					/* after section, chain to paragraph */
					if ((!codes[1] || !codes[2]) && sect)
					{
						codes = o_paragraphs(buf);
						sect = False;
					}
					else if (!codes[1])
						codes++;
					else
						codes += 2;
				}
			}
		}

		/* shift this character into "nroff" string */
		nroff[2] = nroff[1];
		nroff[1] = nroff[0];
		nroff[0] = *cp;

	} while (vinf->count > 0);

	/* At this point, cp either points to the newline before a section,
	 * or it is NULL because we hit the top of the buffer.  If it is NULL
	 * and we were only looking to go back one more section/paragraph, and
	 * the cursor wasn't already at the top of the buffer, then we should
	 * move the cursor to the top of the buffer.  Otherwise a NULL cp
	 * indicates an error.  A non-NULL cp should cause the cursor to be
	 * left after the newline that it points to.
	 */

	if (!cp && vinf->count == 1 && markoffset(win->state->cursor) != 0)
	{
		marksetoffset(win->state->cursor, 0);
	}
	else if (!cp)
	{
		scanfree(&cp);
		return RESULT_ERROR;
	}
	else
	{
		marksetoffset(win->state->cursor, markoffset(scanmark(&cp)) + 1);
	}
	scanfree(&cp);
	return RESULT_COMPLETE;
}



/* This function implements ]] and } movement commands */
RESULT m_fsection(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	BUFFER	buf;	/* buffer being addressed */
	CHAR	*cp;
	CHAR	nroff[3];	/* characters after current position */
	CHAR	*codes;
	long	offset;		/* offset of potential destination */
	BOOLEAN	sect;		/* are we looking through section? */

	DEFAULT(1);

	assert(vinf->command == ']' || vinf->command == '}');

	/* Initialize "offset" just to silence a compiler warning */
	offset = 0;

	/* if this is the end of a "learn" mode, do that! */
	if (vinf->command == ']' && vinf->key2 != ']')
	{
		return maplearn(vinf->key2, False);
	}

	/* search forward for a section or paragraph */
	buf = markbuffer(win->state->cursor);
	scanalloc(&cp, win->state->cursor);
	memset(nroff, 0, sizeof nroff);
	nroff[2] = *cp;
	nroff[1] = (*cp == '.' ? '\0' : '\n');
	do
	{
		/* move ahead one character */
		if (!scannext(&cp))
			break;

		/* look for special stuff... */
		if (nroff[2] == '\n' && *cp == '{')
		{
			offset = markoffset(scanmark(&cp));
			vinf->count--;
		}
		else if (nroff[1] != '\n' && nroff[2] == '\n' && *cp == '\n' && vinf->command == '}')
		{
			offset = markoffset(scanmark(&cp));
			vinf->count--;
		}
		else if (nroff[0] == '\n' && nroff[1] == '.')
		{
			for (codes = o_sections(buf), sect = (BOOLEAN)(vinf->command == '}');
			     codes && *codes;
			     )
			{
				if (codes[0] == nroff[2] &&
					(codes[1] == *cp ||
					    (!isalnum(*cp) &&
						(!codes[1] || codes[1] == ' ')
					    )
					)
				   )
				{
					offset = markoffset(scanmark(&cp)) - 2;
					vinf->count--;
					break;
				}

				/* after section, chain to paragraph */
				if ((!codes[1] || !codes[2]) && sect)
				{
					codes = o_paragraphs(buf);
					sect = False;
				}
				else if (!codes[1])
					codes++;
				else
					codes += 2;
			}
		}

		/* shift this character into "nroff" string */
		nroff[0] = nroff[1];
		nroff[1] = nroff[2];
		nroff[2] = *cp;

	} while (vinf->count > 0);

	/* At this point, cp either points to the last character of a section
	 * header (and "offset" is the start of that header), or cp is NULL
	 * because we hit the end of the buffer before finding a section.
	 * If it is NULL and we were only looking to go forward 1 more section,
	 * then move the cursor to the end of the buffer.  Otherwise a NULL
	 * cp indicates an error.  A non-NULL cp should cause the cursor to be
	 * left at the "offset" value.
	 */ 

	if (!cp && vinf->count == 1
		&& markoffset(win->state->cursor) < o_bufchars(buf) - 2)
	{
		/* leave cursor on the character before the final newline,
		 * unless the final line consists of only a newline character;
		 * then leave it on that newline.
		 */
		marksetoffset(win->state->cursor, o_bufchars(buf) - 2);
		if (scanchar(win->state->cursor) == '\n')
		{
			markaddoffset(win->state->cursor, 1);
		}

		/* doing a line-mode operator? */
		if (vinf->oper)
		{
			/* include the final line */
			vinf->tweak |= TWEAK_INCL;
		}
	}
	else if (!cp)
	{
		scanfree(&cp);
		return RESULT_ERROR;
	}
	else
	{
		marksetoffset(win->state->cursor, offset);
	}
	scanfree(&cp);
	return RESULT_COMPLETE;
}


/* This implements the screen-relative movement commands: H, M, and L */
RESULT m_scrnrel(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	long	delta;	/* number of lines to move forward */
	long	srcoff;	/* offset of source line */
	MARKBUF	srcbuf;	/* mark of line that we're moving relative to */
	MARK	tmp;	/* value returned by display mode's move() function */
	int	rows;	/* number of rows showing something other than "~" */

	assert(vinf->command == 'H' || vinf->command == 'M' || vinf->command == 'L');
	assert(win->di && win->di->rows > 1);

	DEFAULT(1);

	/* see how many rows are visible */
	for (rows = win->di->rows - 2;
	     rows > 1 && win->di->newrow[rows].lineoffset >= o_bufchars(markbuffer(win->state->cursor));
	     rows--)
	{
	}

	/* choose a source offset and line delta, depending on command */
	switch (vinf->command)
	{
	  case 'H':
		delta = vinf->count - 1;
		srcoff = win->di->newrow[0].lineoffset;
		break;

	  case 'M':
		delta = 0;
		srcoff = win->di->newrow[rows / 2].lineoffset;
		break;

	  default: /* 'L' */
		delta = 1 - vinf->count;
		srcoff = win->di->newrow[rows].lineoffset;
		break;
	}

	/* if bad offset, then fail */
	if (srcoff < 0 || srcoff >= o_bufchars(markbuffer(win->state->cursor)))
		return RESULT_ERROR;

	/* maybe move forward or backward from that line */
	if (delta != 0)
	{
		(void)marktmp(srcbuf, markbuffer(win->state->cursor), srcoff);
		tmp = (*win->md->move)(win, &srcbuf, delta, 0, False);
		if (!tmp)
			return RESULT_ERROR;
		srcoff = markoffset(tmp);
	}

	/* move the cursor to that line */
	marksetoffset(win->state->cursor, srcoff);
	return RESULT_COMPLETE;
}


RESULT m_z(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	/* This only works on the window's primary buffer */
	if (win->state->cursor != win->cursor)
		return RESULT_ERROR;

	/* If a count is given, then move the cursor to that line */
	if (vinf->count > 0 && vinf->count < o_buflines(markbuffer(win->cursor)))
	{
		marksetoffset(win->cursor,
		    lowline(bufbufinfo(markbuffer(win->cursor)), vinf->count));
	}

	/* tweak the window's top & bottom to force the current line to
	 * appear in a given location on the screen.
	 */
	switch (vinf->key2)
	{
	  case '\n':
	  case '\r':
	  case '+':
		/* The current line should appear at the top of the screen.
		 * We'll tweak the top & bottom so they both refer to this
		 * line.  When the window is redrawn, the redrawing logic
		 * will cause this line to be output first, and then it'll
		 * just continue showing lines until the bottom of the
		 * screen.
		 */
		win->di->topline = markoffset(dispmove(win, 0L, 0));
		win->di->bottomline = o_bufchars(markbuffer(win->cursor));
		win->di->logic = DRAW_CHANGED;
		break;

	  case '.':
	  case 'z':
		/* The current line should appear in the middle of the screen.
		 * To do this, we'll set the top half a screenful's number of
		 * lines back, and the bottom some point after the cursor.
		 * We'll also set the drawing logic to perform slop-scrolling
		 * until the cursor is in the top half of the screen, just in
		 * case the lines at the top of the screen fill more than one
		 * row.
		 */
		win->di->topline = markoffset(dispmove(win, -(o_lines(win) / 2), 0));
		win->di->bottomline = o_bufchars(markbuffer(win->cursor));
		win->di->logic = DRAW_CENTER;
		break;

	  case '-':
		/* The current line should appear at the bottom of the screen.
		 * To do this, we'll set the top back a whole screenload's
		 * number of lines before the cursor line, and the bottom 
		 * to some point after the current line.  When the window is
		 * redrawn, the drawing logic will start drawing from the
		 * computed top, and then scroll the window if necessary to
		 * bring the current line onto the screen.
		 */
		win->di->topline = markoffset(dispmove(win, -o_lines(win), 0));
		win->di->bottomline = markoffset(win->cursor) + 1;
		win->di->logic = DRAW_CHANGED;
		break;
	}

	return RESULT_COMPLETE;
}


RESULT m_fsentence(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	BOOLEAN	ending;	/* have we seen at least one sentence ender? */
	BOOLEAN	didpara;/* between paragraph and first sentence in paragraph */
	int	spaces;	/* number of spaces seen so far */
	CHAR	*cp;	/* used for scanning through text */
	CHAR	*end;	/* characters that end a sentence */
	CHAR	*quote;	/* quote/parenthesis character that may appear at end */
	CHAR	oper;	/* operator command, or '\0' */
	long	newline;/* offset of first newline in trailing whitespace */
	long	para;
	long	offset;
	long	count;

	DEFAULT(1);

	/* If sentenceend and sentencequote are unset, use default values */
	end = o_sentenceend ? o_sentenceend : toCHAR(".?!");
	quote = o_sentencequote ? o_sentencequote : toCHAR("\")]");

	count = vinf->count;
	oper = vinf->oper;

	/* detect whether we're at the start of a paragraph */
	offset = markoffset(win->state->cursor);
	scanalloc(&cp, win->state->cursor);
	if (*cp != '\n')
		scanprev(&cp);
	else
		while (cp && *cp == '\n')
		{
			scanprev(&cp);
		}
	if (cp)
	{
		marksetoffset(win->state->cursor, markoffset(scanmark(&cp)));
		vinf->count = 1;
		vinf->command = '}';
		m_fsection(win, vinf);
		para = markoffset(win->state->cursor);
	}
	else
	{
		para = 0;
	}
	marksetoffset(win->state->cursor, offset);
	if (para == offset)
		count++;
	else if (para < offset)
	{
		/* find the next paragraph */
		marksetoffset(win->state->cursor, markoffset(scanmark(&cp)));
		vinf->count = 1;
		vinf->command = '}';
		m_fsection(win, vinf);
		para = markoffset(win->state->cursor);
	}

	/* for each count... */
	newline = -1;
	scanseek(&cp, win->state->cursor);
	for (; cp && count > 0; count--)
	{
		/* for each character in the sentence... */
		for (ending = didpara = False, spaces = 0;
		     cp && (!ending || spaces < o_sentencegap || isspace(*cp));
		     scannext(&cp), offset++)
		{
			/* if paragraph, then... */
			if (offset == para)
			{
				/* if still more sentences to skip... */
				if (count > 1)
				{
					/* count this as a sentence, and
					 * arrange for next line to also be a
					 * sentence.
					 */
					count--;
					newline = -1;
					ending = True;
					if (*cp == '\n')
					{
						didpara = False;
						spaces = o_sentencegap;
					}
					else
					{
						didpara = True;
						spaces = 0;
					}

					/* oh, and we need to find the next
					 * paragraph, too.
					 */
					marksetoffset(win->state->cursor, offset);
					vinf->count = 1;
					vinf->command = '}';/*{*/
					if (m_fsection(win, vinf) == RESULT_COMPLETE)
						para = markoffset(win->state->cursor);
					else
						para = -1;

					continue;
				}
				else
				{
					/* we're here! */
					break;
				}
			}

			/* check the character */
			if (*cp == '\n')
			{
				spaces = o_sentencegap;
				didpara = False;
				if (newline < 0)
					newline = markoffset(scanmark(&cp));
			}
			else if (didpara)
				/* skip characters in a ".P" line */;
			else if (isspace(*cp))
				spaces++;
			else if (CHARchr(end, *cp))
				newline = -1, ending = True, spaces = 0;
			else if (!CHARchr(quote, *cp))
				newline = -1, ending = False, spaces = 0;
			else /* quote character */
				newline = -1;
		}
	}

	/* did we find it? */
	if (cp)
	{
		/* if target of operator, and a newline was encountered in the
		 * trailing whitespace, then move the cursor to that newline;
		 * else move the cursor to the start of the following sentence.
		 */
		if (oper && newline >= 0)
			marksetoffset(win->state->cursor, newline);
		else
			marksetoffset(win->state->cursor, markoffset(scanmark(&cp)));
	}
	scanfree(&cp);
	return cp ? RESULT_COMPLETE : RESULT_ERROR;
}

RESULT m_bsentence(win, vinf)
	WINDOW	win;	/* window where command was typed */
	VIINFO	*vinf;	/* information about the command */
{
	BOOLEAN	first;	/* True until we pass some mid-sentence stuff */
	BOOLEAN	ending;	/* have we seen at least one sentence ender? */
	BOOLEAN	anynext;/* any text seen on following line */
	BOOLEAN	anythis;/* any text seen on this line */
	int	spaces;	/* number of spaces seen so far */
	CHAR	*cp;	/* used for scanning through text */
	CHAR	*end;	/* characters that end a sentence */
	CHAR	*quote;	/* quote/parenthesis character that may appear at end */
	long	count;	/* sentences to move over */
	long	para;	/* top of current paragraph */
	long	offset;
	RESULT	result;

	DEFAULT(1);

	/* If sentenceend and sentencequote are unset, use default values */
	end = o_sentenceend ? o_sentenceend : toCHAR(".?!");
	quote = o_sentencequote ? o_sentencequote : toCHAR("\")]");

	/* misc initialization */
	anynext = anythis = False;
	offset = markoffset(win->state->cursor);

	/* NOTE: The "first" variable is used to handle the situation where
	 * we start at the beginning of one sentence.  From here, we want
	 * to go back one extra sentence-end; otherwise <(> would just move
	 * us to the start of the same sentence.
	 */
	first = True;

	/* Start scanning at the cursor location */
	scanalloc(&cp, win->state->cursor);
	count = vinf->count;

	/* Find the start of this paragraph.  That counts as a "sentence" */
	vinf->command = '{';
	vinf->count = 1;
	para = (m_bsection(win, vinf) == RESULT_COMPLETE) ? markoffset(win->state->cursor) : -1;

	/* For each count... */
	for (; cp && count > 0; count--)
	{
		/* for each character in the sentence... */
		for (ending = True, anythis = anynext = False, spaces = 0,
			scanprev(&cp), offset = markoffset(scanmark(&cp));
		     cp && offset != para &&
			(!ending || spaces<o_sentencegap || !CHARchr(end,*cp));
		     scanprev(&cp), offset--)
		{
			if (*cp == '\n')
			{
				spaces = o_sentencegap;
				ending = True;
				anynext = anythis;
				anythis = False;
			}
			else if (isspace(*cp))
			{
				spaces++,
				ending = True;
			}
			else if (!CHARchr(quote, *cp))
			{
				first = ending = False;
				anythis = True;
				spaces = 0;
			}
			else
			{
				anythis = True;
			}
		}

		/* If this sentence is actually a paragraph start, and we
		 * still have more sentences to move over, then find the
		 * next higher paragraph.
		 */
		if (offset == para && count > 1)
		{
			vinf->count = 1;
			para = (m_bsection(win, vinf) == RESULT_COMPLETE) ? markoffset(win->state->cursor) : -1;
		}

		/* If this sentence ender was encountered before any
		 * mid-sentence text (i.e., if we started at the front of
		 * a sentence) then we should go back one extra sentence-end.
		 */
		if (first && offset != para)
		{
			count++;
		}
	}

	/* If we hit a paragraph top which occurs on a blank line, then we
	 * need to do a little extra processing because we exited the loop
	 * a little too soon.
	 */
	if (offset == para && cp && *cp == '\n')
	{
		anynext = anythis;
	}

	/* did we find it? */
	if (cp)
	{
		/* move the cursor to the start of the next sentence */
		if (offset > para)
		{
			/* found a sentence end -- move the cursor to the to
			 * start of the following sentence.
			 */
			do
			{
				scannext(&cp);
				assert(cp);
			} while (isspace(*cp) || CHARchr(quote, *cp));
			marksetoffset(win->state->cursor, markoffset(scanmark(&cp)));
		}
		else if (anynext)
		{
			/* bumped into a paragraph start after scanning some
			 * sentence text -- move the cursor to the first text
			 * on the next non-blank line.
			 */
			while (cp && *cp != '\n')
			{
				scannext(&cp);
			}
			while (cp && isspace(*cp))
			{
				scannext(&cp);
			}
			if (&cp)
				marksetoffset(win->state->cursor, markoffset(scanmark(&cp)));
			else
			{
				marksetoffset(win->state->cursor, o_bufchars(markbuffer(win->state->cursor)) - 2);
				if (markoffset(win->state->cursor) < 0)
					marksetoffset(win->state->cursor, 0);
			}
		}
		else
		{
			/* found a paragraph start -- leave the cursor there */
			offset = para;
			marksetoffset(win->state->cursor, para);
		}
		result = RESULT_COMPLETE;
	}
	else if (count == 0)
	{
		/* move the cursor to the first character of the buffer */
		marksetoffset(win->state->cursor, 0);
		result = RESULT_COMPLETE;
	}
	else
	{
		/* tried to move past beginning of buffer */
		result = RESULT_ERROR;
	}
	scanfree(&cp);
	return result;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.