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

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

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

char id_exedit[] = "$Id: exedit.c,v 2.36 1996/10/01 19:46:29 steve Exp $";

#include "elvis.h"


/* This command implements the :insert, :append, and :change commands */
RESULT	ex_append(xinf)
	EXINFO	*xinf;
{
	MARK	where;

	assert(xinf->command == EX_APPEND || xinf->command == EX_CHANGE
		|| xinf->command == EX_INSERT);

	/* this only works on the window's main buffer */
	if (!xinf->window->state->acton)
	{
		msg(MSG_ERROR, "not main buffer");
		return RESULT_ERROR;
	}

	/* different behavior, depending on the command... */
	if (xinf->command == EX_CHANGE)
	{
		cutyank('\0', xinf->fromaddr, xinf->toaddr, 'L', True);
		where = markdup(xinf->fromaddr);
	}
	else if (xinf->command == EX_APPEND)
	{
		where = markdup(xinf->toaddr);
	}
	else
	{
		where = markdup(xinf->fromaddr);
	}

	/* Was the new text given as an argument to the command? */
	if (xinf->rhs)
	{
		/* yes, insert the new text at the appropriate place */
		bufreplace(where, where, xinf->rhs, (long)CHARlen(xinf->rhs));
		markaddoffset(where, CHARlen(xinf->rhs));
		bufreplace(where, where, toCHAR("\n"), 1);
		markaddoffset(where, 1);
	}
	xinf->newcurs = where;
	return RESULT_COMPLETE;
}


/* This function implements the :delete and :yank command */
RESULT	ex_delete(xinf)
	EXINFO	*xinf;
{
	/* check the cut buffer name */
	if (xinf->cutbuf && !isalnum(xinf->cutbuf) && xinf->cutbuf != '<' && xinf->cutbuf != '>')
	{
		msg(MSG_ERROR, "bad cut buffer");
		return RESULT_ERROR;
	}

	/* do the command */
	cutyank(xinf->cutbuf, xinf->fromaddr, xinf->toaddr, (CHAR)'L', (BOOLEAN)(xinf->command == EX_DELETE));
	return RESULT_COMPLETE;
}


RESULT	ex_global(xinf)
	EXINFO	*xinf;
{
	CHAR	*cp;
	long	lenln, endln;
	RESULT	ret = RESULT_COMPLETE;
	MARK	cursor;
	MARK	orig;
	MARKBUF	thisln;

	assert(xinf->command == EX_GLOBAL || xinf->command == EX_VGLOBAL);

	/* a command is required */
	if (!xinf->rhs)
	{
		msg(MSG_ERROR, "[s]$1 requires a command", xinf->cmdname);
		return RESULT_ERROR;
	}

	/* only works when applied to window's main buffer */
	if (!xinf->window->state->acton || xinf->window->state->acton->acton)
	{
		msg(MSG_ERROR, "[s]$1 only works on main buffer", xinf->cmdname);
		return RESULT_ERROR;
	}

	/* ":g!" is like ":v" */
	if (xinf->bang)
		xinf->command = EX_VGLOBAL;

	/* remember the cursor's original position.  Inside the following loop,
	 * we'll force the cursor onto each matching line; when we're done,
	 * we'll move the cursor back and return the final position so the
	 * experform() function can move the cursor there in the conventional
	 * way -- that will be important if we switch buffers.
	 */
	if (xinf->window->state->acton)
		cursor = xinf->window->state->acton->cursor;
	else
		cursor = xinf->window->cursor;
	if (markbuffer(cursor) != markbuffer(xinf->fromaddr))
	{
		marksetbuffer(cursor, markbuffer(xinf->fromaddr));
		marksetoffset(cursor, markoffset(xinf->fromaddr));
	}
	orig = markdup(cursor);

	/* for each line... */
	(void)scanalloc(&cp, xinf->fromaddr);
	ret = RESULT_COMPLETE;
	while (cp && markoffset(xinf->fromaddr) < markoffset(xinf->toaddr))
	{
		/* find the end of this line */
		for (lenln = 0; cp && *cp != '\n'; lenln++)
			(void)scannext(&cp);
		if (cp)
		{
			(void)scannext(&cp);
			lenln++;
		}

		/* move "fromaddr" to end of this line (start of next line) */
		thisln = *xinf->fromaddr;
		marksetoffset(xinf->fromaddr,
		    cp ? markoffset(scanmark(&cp)) : markoffset(xinf->toaddr));

		/* is this a selected line? */
		if ((regexec(xinf->re, &thisln, True) ? EX_GLOBAL : EX_VGLOBAL)
			== xinf->command)
		{

			/* move the cursor to the matching line */
			endln = markoffset(scanmark(&cp));
			marksetoffset(cursor, endln - lenln);

			/* free the scan pointer -- can't make changes
			 * while scanning.
			 */
			scanfree(&cp);

			/* execute the command */
			ret = exstring(xinf->window, xinf->rhs);

			/* reallocate the scan pointer */
			(void)scanalloc(&cp, xinf->fromaddr);

			/* if the ex command failed, then exit */
			if (ret != RESULT_COMPLETE)
				break;
		}

		/* if user wants to abort operation, then exit */
		if (guipoll(False))
			break;
	}
	scanfree(&cp);

	/* move the cursor back to its original position, and then return
	 * the final position so the cursor will be moved there in a graceful
	 * way.
	 */
	xinf->newcurs = markdup(cursor);
	if (markbuffer(cursor) != markbuffer(orig))
		marksetbuffer(cursor, markbuffer(orig));
	marksetoffset(cursor, markoffset(orig));
	markfree(orig);
	return ret;
}


RESULT	ex_join(xinf)
	EXINFO	*xinf;
{
	CHAR	prevchar;	/* character before newline */
	CHAR	*cp;		/* used while scanning for newlines */
	long	newlines;	/* number of newlines to be clobbered */
	long	nspaces;	/* number of spaces to insert */
	MARK	start, end;	/* region around a newline */
	long	offset;		/* position of last change */
	CHAR	*endlist;	/* string of sentence ending punctuation */
 static CHAR	spaces[3] = {' ', ' ', ' '};

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

	/* initialize endlist from options (if set) or literals */
	endlist = (o_sentenceend ? o_sentenceend : toCHAR(".!?"));

	/* We're going to be replacing newlines with blanks.  The number of
	 * newlines we want to replace is equal to the number lines affected
	 * minus one... except that if the user requested a "join" of a single
	 * line then we should assume we're supposed to join two lines.
	 */
	newlines = (xinf->from == xinf->to) ? 1 : (xinf->to - xinf->from);
	if (xinf->from + newlines > o_buflines(markbuffer(xinf->fromaddr)))
	{
		msg(MSG_ERROR, "nothing to join with this line");
		return RESULT_ERROR;;
	}

	/* scan the text for newlines */
	prevchar = ' ';
	for (scanalloc(&cp, xinf->fromaddr); cp && newlines > 0; scannext(&cp))
	{
		/* if newline, then clobber it */
		if (*cp == '\n')
		{
			start = markdup(scanmark(&cp));
			offset = markoffset(start);
			if (scannext(&cp))
			{
				/* figure out how many spaces to insert */
				if (xinf->bang || *cp == ')' || isspace(prevchar))
					nspaces = 0;
				else if (CHARchr(toCHAR(endlist), prevchar))
					nspaces = o_sentencegap;
				else
					nspaces = 1;

				/* skip any leading whitespace in next line */
				while (!xinf->bang && cp && (*cp == ' ' || *cp == '\t'))
				{
					scannext(&cp);
				}

				/* Find the end mark */
				end = (cp ? markdup(scanmark(&cp))
					  : markalloc(markbuffer(start), o_bufchars(markbuffer(start))));

				/* free the scan context during the change;
				 * can't mix scanning & updates.
				 */
				scanfree(&cp);

				/* replace the newline (and trailing whitespace)
				 * with a given number of spaces.
				 */
				bufreplace(start, end, spaces, nspaces);
				marksetoffset(end, offset + nspaces - 1);
					/* NOTE: the "- 1" is to compensate for
					 * the scannext() at the top of the loop
					 */

				/* resume scanning */
				scanalloc(&cp, end);
				markfree(end);
				if (nspaces > 0)
				{
					prevchar = ' ';
				}
			}
			markfree(start);

			/* All that to clobber one newline! */
			newlines--;
		}
		else if ((*cp != '"' && *cp != ')') || isspace(prevchar))
		{
			/* remember the character.  When we hit a newline,
			 * the previous character will be checked to determine
			 * how many spaces to insert.  Note that we ignore
			 * quote and parenthesis characters except after a
			 * blank.
			 */
			prevchar = *cp;
		}
	}
	scanfree(&cp);

	/* Choose a new cursor position: at the start of last joint */
	xinf->newcurs = markalloc(markbuffer(xinf->fromaddr), offset);

	return RESULT_COMPLETE;
}


RESULT	ex_move(xinf)
	EXINFO	*xinf;
{
	long	oldto;

	/* detect errors */
	if (markbuffer(xinf->fromaddr) == markbuffer(xinf->destaddr)
	 && markoffset(xinf->fromaddr) <= markoffset(xinf->destaddr)
	 && markoffset(xinf->destaddr) < markoffset(xinf->toaddr))
	{
		msg(MSG_ERROR, "destination can't be inside source");
		return RESULT_ERROR;
	}

	/* copy the text */
	xinf->newcurs = markdup(xinf->destaddr);
	markaddoffset(xinf->newcurs, 1);
	oldto = markoffset(xinf->toaddr);
	bufpaste(xinf->destaddr, xinf->fromaddr, xinf->toaddr);

	/* leave the cursor on the last line of the destination */
	if (markoffset(xinf->newcurs) > 1)
		markaddoffset(xinf->newcurs, -2);
	marksetoffset(xinf->newcurs, markoffset(
		(*dmnormal.move)(xinf->window, xinf->newcurs, 0L, 0L, False)));

	/* If moving (not copying) then delete source */
	if (xinf->command == EX_MOVE)
	{
		/* be careful about the "to" offset.  If the destination was
		 * immediately after the source, then we just inserted the
		 * text at "to", so "to" was adjusted... but we only want to
		 * delete up to the old value of "to".
		 */
		if (markbuffer(xinf->toaddr) == markbuffer(xinf->destaddr)
		 && markoffset(xinf->toaddr) == markoffset(xinf->destaddr))
		{
			marksetoffset(xinf->toaddr, oldto);
		}

		bufreplace(xinf->fromaddr, xinf->toaddr, NULL, 0);
	}

	return RESULT_COMPLETE;
}


RESULT	ex_print(xinf)
	EXINFO	*xinf;
{
	long	last;	/* offset of start of last line */
	PFLAG	pflag;	/* how to print */

	/* generate a pflag from the command name and any supplied pflag */
	switch (xinf->pflag)
	{
	  case PF_NONE:
	  case PF_PRINT:
		pflag = (xinf->command == EX_NUMBER
			    ? (xinf->command == EX_LIST ? PF_NUMLIST : PF_NUMBER)
			    : (xinf->command == EX_LIST ? PF_LIST : PF_PRINT));
		break;

	  case PF_LIST:
		pflag = (xinf->command == EX_NUMBER ? PF_NUMLIST : PF_LIST);
		break;

	  case PF_NUMBER:
		pflag = (xinf->command == EX_LIST ? PF_NUMLIST : PF_NUMBER);
		break;	/* !!it B4: this break seems to be needed here */

	  default:
		pflag = PF_NUMLIST;
		break;
	}

	/* print the lines */
	last = exprintlines(xinf->window, xinf->fromaddr, xinf->to - xinf->from + 1, pflag);

	/* leave the cursor at the start of the last line */
	xinf->newcurs = markalloc(markbuffer(xinf->fromaddr), last);
	return RESULT_COMPLETE;
}


RESULT	ex_put(xinf)
	EXINFO	*xinf;
{
	MARK	newcurs;

	newcurs = cutput(xinf->cutbuf, xinf->window, xinf->fromaddr, (BOOLEAN)(xinf->from > 0), False, False);
	if (newcurs)
	{
		xinf->newcurs = markdup(newcurs);
		return RESULT_COMPLETE;
	}
	return RESULT_ERROR;
}


RESULT	ex_read(xinf)
	EXINFO	*xinf;
{
	long	offset;

	if (!xinf->rhs && xinf->nfiles != 1)
	{
		msg(MSG_ERROR, "filename required");
		return RESULT_ERROR;
	}

	/* remember where we started inserting */
	offset = markoffset(xinf->toaddr);
	xinf->newcurs = markdup(xinf->toaddr);

	/* read in the text */
	if (!bufread(xinf->toaddr, xinf->rhs ? tochar8(xinf->rhs) : xinf->file[0]))
	{
		return RESULT_ERROR;
	}

	/* Choose a place to leave the cursor.  If reading due to visual <:>
	 * command, this should be the start of the first line read; else it
	 * should be the start of the last line read.
	 */
	if (xinf->window->state->flags & ELVIS_1LINE)
	{
		marksetoffset(xinf->newcurs, offset);
	}
	else
	{
		marksetoffset(xinf->newcurs, markoffset(
			(*xinf->window->md->move)(xinf->window, xinf->newcurs, -1, 0, False)));
	}
	return RESULT_COMPLETE;
}


/* This function implements the :< and :> commands.  It is also used to do
 * the real work for the visual <<> and <>> operators.
 */
RESULT	ex_shift(xinf)
	EXINFO	*xinf;
{
	long	shift;	/* amount to shift by */
	long	ws;	/* amount of whitespace currently */
	CHAR	*cp;	/* used for scanning through a line's whitespace */
	long	line;	/* used for counting through line numbers */
	MARKBUF	start;	/* start of the line */
	MARKBUF	end;	/* end of the line's whitespace */
	CHAR	str[50];/* buffer for holding whitespace */
	long	i;

	/* compute the amount of shifting required */
	shift = o_shiftwidth(markbuffer(&xinf->defaddr)) * xinf->multi;
	if (xinf->command == EX_SHIFTL)
	{
		shift = -shift;
	}

	/* for each line... */
	start = xinf->defaddr;
	for (line = xinf->from; line <= xinf->to; line++)
	{
		/* count the current whitespace */
		scanalloc(&cp, marksetline(&start, line));
		for (ws = 0; cp && (*cp == ' ' || *cp == '\t'); scannext(&cp))
		{
			if (*cp == ' ')
			{
				ws++;
			}
			else
			{
				ws = ws + o_tabstop(markbuffer(&xinf->defaddr))
					- ws % o_tabstop(markbuffer(&xinf->defaddr));
			}
		}
		end = *scanmark(&cp);

		/* if this is an empty line, and no ! was given on the command
		 * line, then do nothing to this line.
		 */
		if (ws == 0 && *cp == '\n' && !xinf->bang)
		{
			scanfree(&cp);
			continue;
		}
		scanfree(&cp);

		/* compute the amount of whitespace we want to have */
		ws += shift;
		if (ws < 0)
		{
			ws = 0;
		}

		/* Replace the old whitespace with new whitespace.  Since our
		 * buffer for holding new whitespace is of limited size, we
		 * may need to make several bufreplace() calls to do this.
		 */
		while (markoffset(&start) != markoffset(&end) || ws > 0)
		{
			/* build new whitespace (as much of it as possible) */
			i = 0;
			if (o_autotab(markbuffer(&xinf->defaddr)))
			{
				for (;
				     ws >= o_tabstop(markbuffer(&xinf->defaddr))
					&& i < QTY(str);
				     i++, ws -= o_tabstop(markbuffer(&xinf->defaddr)))
				{
					str[i] = '\t';
				}
			}
			for (; ws > 0 && i < QTY(str); i++, ws--)
			{
				str[i] = ' ';
			}

			/* replace old whitespace with new */
			bufreplace(&start, &end, str, i);
			markaddoffset(&start, i);
			marksetoffset(&end, markoffset(&start));
		}
	}

	if (xinf->to - xinf->from + 1 >= o_report)	/* !!it E5: report on shift */
		msg(MSG_INFO,"[dC]$1 lines $2ed",xinf->to - xinf->from + 1,(xinf->command == EX_SHIFTL)? '<' : '>');

	return RESULT_COMPLETE;
}


/* This function implements the :substitute command, and the :& and :~
 * variations of that command.  It is also used to perform the real work
 * of visual <&> command.
 */
RESULT	ex_substitute(xinf)
	EXINFO	*xinf;
{
	CHAR	*opt;	/* substitution options */
	long	chline;	/* # of lines changed */
	long	chsub;	/* # of substitutions made */
	long	cursoff;/* offset where cursor should be moved to */
 static PFLAG	pflag;	/* printing flag */
 static BOOLEAN	optg;	/* boolean option: substitute globally in line? */
 static BOOLEAN	optx;	/* boolean option: execute instead of substitute? */
 static long	count;	/* numeric option: which instance in each line to sub */
	MARKBUF	posn;	/* position within a line to be replaced */
	long	instance;/* number of instances matched so far within line */
	CHAR	*newp;	/* replacement text */
	CHAR	*scan;	/* used for scanning to find end of line */
	int	match;


	assert(xinf->command == EX_SUBSTITUTE || xinf->command == EX_SUBAGAIN);

	/* initialize "cursoff" just to silence a compiler warning */
	cursoff = 0;

	/* ":s" is equivalent to ":&". */
	if (!xinf->re)
		xinf->command = EX_SUBAGAIN;

	if (xinf->command == EX_SUBAGAIN)
	{
		/* same regular expression as last time */
		xinf->re = regcomp(toCHAR(""), xinf->window->state->cursor);
		if (!xinf->re)
		{
			/* error message already given by regcomp() */
			return RESULT_ERROR;
		}

		/* same replacement text as last time */
		newp = regtilde(toCHAR(o_magic ? "~" : "\\~"));

		/* if visual "&", then turn off the "p" and "c" options */
		if (xinf->bang)
		{
			pflag= PF_NONE;
		}
	}
	else /* xinf->command == CMD_SUBSTITUTE */
	{
		/* generate the new text */
		newp = regtilde(xinf->lhs ? xinf->lhs : toCHAR(""));

		/* analyse the option string */
		if (!o_edcompatible)
		{
			pflag = xinf->pflag;
			optg = optx = False;
			count = 0;
		}
		for (opt = xinf->rhs; opt && *opt; opt++)
		{
			switch (*opt)
			{
			  case 'g':
				optg = (BOOLEAN)!optg;
				break;

			  case 'x':
				optx = (BOOLEAN)!optx;
				break;

			  case 'p':
				pflag = (pflag==PF_PRINT) ? PF_PRINT : PF_NONE;
				break;

			  case 'l':
				pflag = (pflag==PF_LIST) ? PF_LIST : PF_NONE;
				break;

			  case '#':
				pflag = (pflag==PF_NUMBER) ? PF_NUMBER : PF_NONE;
				break;

			  case '0':
			  case '1':
			  case '2':
			  case '3':
			  case '4':
			  case '5':
			  case '6':
			  case '7':
			  case '8':
			  case '9':
				count = 0;
				do
				{
					count = count * 10 + *opt++ - '0';
				} while (isdigit(*opt));
				opt--;
				break;

			  default:
				if (!isspace(*opt))
				{
					msg(MSG_ERROR, "[C]unsupported flag '$1'", *opt);
					return RESULT_ERROR;
				}
			}
		}

		/* sanity checks */
		if (count != 0 && optg)
		{
			msg(MSG_ERROR, "can't mix number and 'g' flag");
			return RESULT_ERROR;
		}

		/* default behavior is to either replace only first instance,
		 * or all instances, depending on the "gdefault" option.
		 */
		if (count == 0 && !optg)
		{
			if (o_gdefault && !o_edcompatible)
				optg = True;
			else
				count = 1;
		}
	}

	/* if no replacement text, fail */
	if (!newp)
	{
		return RESULT_ERROR;
	}

	/* this command does its own printing; disable auto printing */
	xinf->pflag = PF_NONE;

	/* reset the change counters */
	chline = chsub = 0L;

	/* for each line in the range... */
	for (posn = *xinf->fromaddr; markoffset(&posn) < markoffset(xinf->toaddr); )
	{
		/* for each instance within the line... */
		for (instance = 0, match = regexec(xinf->re, &posn, True);
		     match && (optg || instance < count);
		     match = regexec(xinf->re, &posn, False))
		{
			/* increment the substitution change counter */
			chsub++;
			instance++;

			/* if this is an instance we care about... */
			if (optg || instance == count)
			{
				/* Either execute the replacement, or perform
				 * the substitution
				 */
				opt = regsub(xinf->re, newp, (BOOLEAN)!optx);
				if (!opt)
				{
					return RESULT_ERROR;
				}
				if (optx)
				{
					exstring(xinf->window, opt);
				}
				safefree(opt);

				/* remember the offset of this change so we can
				 * move the cursor there later.
				 */
				cursoff = xinf->re->startp[0];
			}

			/* Move "posn" to the end of the matched region.  If
			 * the regexp could conceivably match a zero-length
			 * string, then skip one character.
			 */
			marksetoffset(&posn, xinf->re->endp[0]);
			if (xinf->re->minlen == 0)
			{
				/* markaddoffset(&posn, 1);*/
				if (scanchar(&posn) == '\n')
					break;
			}
		}

		/* if any changes were made, then increment chline */
		if (optg ? instance > 0 : instance == count)
		{
			chline++;
		}

		/* scan forward for the end of the line */
		for (scanalloc(&scan, &posn); scan && *scan != '\n'; scannext(&scan))
		{
		}
		if (scan)
			scannext(&scan);
		if (scan)
			posn = *scanmark(&scan);
		else
			marksetoffset(&posn, o_bufchars(markbuffer(xinf->fromaddr)));
		scanfree(&scan);
	}

	/* If done from within a ":g" command, or used with "x" flag,
	 * then finish silently.
	 */
	if (xinf->global || optx)
	{
#if 0
		rptlines = chline;
		rptlabel = "changed";
#endif
		return RESULT_COMPLETE;
	}

	/* Reporting */
	if (chsub == 0)
	{
		msg(MSG_WARNING, "substitution failed");
	}
	else if (chline >= o_report)
	{
		msg(MSG_INFO, "[dd]$1 substitutions on $2 lines", chsub, chline);
		xinf->newcurs = markalloc(xinf->re->buffer, cursoff);
	}
	return RESULT_COMPLETE;
}


RESULT	ex_undo(xinf)
	EXINFO	*xinf;
{
	long	l = 1;

	assert(xinf->command == EX_UNDO || xinf->command == EX_REDO);

	/* choose an undo/redo level to recover */
	if (xinf->lhs)
	{
		if (!calcnumber(xinf->lhs) || (l = CHAR2long(xinf->lhs)) < 1)
		{
			msg(MSG_ERROR, "bad undo level");
			return RESULT_ERROR;
		}
	}

	/* if redo, then negate the undo value */
	if (xinf->command == EX_REDO)
		l = -l;

	/* try to revert to the undo level */
	l = bufundo(xinf->window->cursor, l);

	/* if successful, adjust the cursor position */
	if (l >= 0)
	{
		marksetoffset(xinf->window->cursor, l);
		return RESULT_COMPLETE;
	}

	return RESULT_ERROR;
}


RESULT	ex_write(xinf)
	EXINFO	*xinf;
{
	char	*name;
	BOOLEAN	success;

	if (xinf->rhs)
	{
		name = tochar8(xinf->rhs);
	}
	else if (xinf->nfiles >= 1)
	{
		assert(xinf->nfiles == 1);
		name = xinf->file[0];
	}
	else
	{
		name = tochar8(o_filename(markbuffer(xinf->fromaddr)));
		if (!name)
		{
			msg(MSG_ERROR, "no file name");
			return RESULT_ERROR;	/* nishi */
		}
	}

	/* if writing to a different filename, remember that name */
	if (name[0] != '!'
	 && o_filename(markbuffer(xinf->fromaddr))
	 && CHARcmp(name, o_filename(markbuffer(xinf->fromaddr))))
	{
		optprevfile(toCHAR(name), 1);
	}

	/* actually write the file */
	success = bufwrite(xinf->fromaddr, xinf->toaddr, name, xinf->bang);
	return success ? RESULT_COMPLETE : RESULT_ERROR;
}


RESULT	ex_z(xinf)
	EXINFO	*xinf;
{
	CHAR	type = '+';		/* type of window to show */
	long	count = o_window;	/* number of lines to show */
	PFLAG	pflag = PF_PRINT;	/* how to show */
	long	line = xinf->from;	/* first line to show */
	long	offset;
	CHAR	*scan;

	/* If we were given arguments, then parse them */
	for (scan = xinf->rhs; scan && *scan; scan++)
	{
		switch (*scan)
		{
		  case '-':
		  case '+':
		  case '.':
		  case '^':
		  case '=':
			type = *scan;
			break;

		  case '0':
		  case '1':
		  case '2':
		  case '3':
		  case '4':
		  case '5':
		  case '6':
		  case '7':
		  case '8':
		  case '9':
			for (count = 0; isdigit(*scan); scan++)
			{
				count = count * 10 + *scan - '0';
			}
			scan--; /* we went one character too far */
			break;

		  case 'l':
			if (pflag == PF_NUMBER || pflag == PF_NUMLIST)
				pflag = PF_NUMLIST;
			else
				pflag = PF_LIST;
			break;

		  case '#':
			if (pflag == PF_LIST || pflag == PF_NUMLIST)
				pflag = PF_NUMLIST;
			else
				pflag = PF_NUMBER;
			break;

		  case ' ':
		  case '\t':
		  case 'p':
			/* ignore */
			break;

		  default:
			msg(MSG_ERROR, "bad argument to :z");
			return RESULT_ERROR;
		}
	}

	/* choose the first line, based on the type */
	switch (type)
	{
	  case '-': /* show the given line at the bottom */
		line = xinf->from - count + 1;
		break;

	  case '+': /* show the given line at the top */
		line = xinf->from;
		break;

	  case '.': /* show the given line in the middle */
		line = xinf->from - count/2;
		break;

	  case '^': /* show the window before the current line */
		line = xinf->from - count * 2 + 1;
		break;
		
	  case '=': /* show it in the middle, surrounded by lines of hyphens */
		count -= 2;
		line = xinf->from - count / 2;
		break;
	}

	/* protect against readed past top or bottom of buffer */
	if (line < 1)
	{
		count -= 1 - line;
		line = 1;
	}
	if (line + count > o_buflines(markbuffer(xinf->fromaddr)))
	{
		count -= line - o_buflines(markbuffer(xinf->fromaddr));
	}

	/* construct a mark for the first line */
	xinf->newcurs = markdup(xinf->fromaddr);
	marksetline(xinf->newcurs, line);

	/* print the lines */
	if (type == '=')
	{
		/* for '=', we need to add lines of hyphens around given line */
		if (line < xinf->from)
		{
			exprintlines(xinf->window, xinf->newcurs, xinf->from - line, pflag);
		}
		drawextext(xinf->window, toCHAR("-------------------------------------------------------------------------------\n"), 80);
		exprintlines(xinf->window, xinf->fromaddr, 1, pflag);
		drawextext(xinf->window, toCHAR("-------------------------------------------------------------------------------\n"), 80);
		count -= (xinf->from - line) + 1;
		if (count > 0)
		{
			marksetline(xinf->newcurs, xinf->from + 1);
			exprintlines(xinf->window, xinf->newcurs, count, pflag);
		}

		/* leave the cursor on the given line */
		marksetoffset(xinf->newcurs, markoffset(xinf->fromaddr));
	}
	else
	{
		/* print the lines all at once */
		offset = exprintlines(xinf->window, xinf->newcurs, count, pflag);

		/* leave the cursor at the start of the last line */
		marksetoffset(xinf->newcurs, offset);
	}

	return RESULT_COMPLETE;
}

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