From decwrl!elroy.jpl.nasa.gov!usc!cs.utexas.edu!uunet!allbery Sat Mar 10 15:33:43 PST 1990
Article 1386 of comp.sources.misc:
Path: decwrl!elroy.jpl.nasa.gov!usc!cs.utexas.edu!uunet!allbery
From: dmt@pegasus.ATT.COM (Dave Tutelman)
Newsgroups: comp.sources.misc
Subject: v11i011: Stevie 3.69a - 3/6
Message-ID: <80851@uunet.UU.NET>
Date: 10 Mar 90 19:42:00 GMT
Sender: allbery@uunet.UU.NET
Organization: AT&T Bell Labs - Lincroft, NJ
Lines: 2749
Approved: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)

Posting-number: Volume 11, Issue 11
Submitted-by: dmt@pegasus.ATT.COM (Dave Tutelman)
Archive-name: stevie3.69a/part03

: This is a shar archive.  Extract with sh, not csh.
: The rest of this file will extract:
: linefunc.c main.c mark.c misccmds.c normal.c ops.c
echo extracting - linefunc.c
sed 's/^X//' > linefunc.c << '!EOR!'
X/* $Header: /nw/tony/src/stevie/src/RCS/linefunc.c,v 1.2 89/03/11 22:42:32 tony Exp $
X *
X * Basic line-oriented motions.
X */
X
X#include "stevie.h"
X#include "ops.h"
X
X/*
X * nextline(curr)
X *
X * Return a pointer to the beginning of the next line after the one
X * referenced by 'curr'. Return NULL if there is no next line (at EOF).
X */
X
XLPTR *
Xnextline(curr)
XLPTR	*curr;
X{
X	static	LPTR	next;
X
X	if (curr->linep->next != Fileend->linep) {
X		next.index = 0;
X		next.linep = curr->linep->next;
X		return &next;
X	}
X	return (LPTR *) NULL;
X}
X
X/*
X * prevline(curr)
X *
X * Return a pointer to the beginning of the line before the one
X * referenced by 'curr'. Return NULL if there is no prior line.
X */
X
XLPTR *
Xprevline(curr)
XLPTR	*curr;
X{
X	static	LPTR	prev;
X
X	if (curr->linep->prev != Filetop->linep) {
X		prev.index = 0;
X		prev.linep = curr->linep->prev;
X		return &prev;
X	}
X	return (LPTR *) NULL;
X}
X
X/*
X * coladvance(p,col)
X *
X * Try to advance to the specified column, starting at p.
X */
X
XLPTR *
Xcoladvance(p, col)
XLPTR	*p;
Xregister int	col;
X{
X	static	LPTR	lp;
X	register int	c, in;
X
X	lp.linep = p->linep;
X	lp.index = p->index;
X
X	/* If we're on a blank ('\n' only) line, we can't do anything */
X	if (lp.linep->s[lp.index] == '\0')
X		return &lp;
X	/* try to advance to the specified column */
X	for ( c=0; col-- > 0; c++ ) {
X		/* Count a tab for what it's worth (if list mode not on) */
X		if ( gchar(&lp) == TAB && !P(P_LS) ) {
X			in = ((P(P_TS)-1) - c%P(P_TS));
X			col -= in;
X			c += in;
X		}
X		/* Don't go past the end of */
X		/* the file or the line. */
X		if (inc(&lp)) {
X			dec(&lp);
X			break;
X		}
X	}
X	return &lp;
X}
X
X
X/*
X * nextchar(curr)
X *
X * Return a line pointer to the next character after the
X * one referenced by 'curr'. Return NULL if there is no next one (at EOF).
X * NOTE: this COULD point to a \n or \0 character.
X */
X
XLPTR *
Xnextchar(curr)
XLPTR	*curr;
X{
X	static	LPTR	*next;
X	char	c;
X
X	next = curr;
X	c = CHAR( next );
X	if (c=='\n' || c=='\0')		/* end of line */
X		next = nextline (next);
X	else
X		next->index++;
X
X	return (next);
X}
X
X
X/*
X * prevchar(curr)
X *
X * Return a line pointer to the previous character before the
X * one referenced by 'curr'. Return NULL if there is no previous one.
X * Note: this COULD point to a \n or \0 character.
X */
X
XLPTR *
Xprevchar(curr)
XLPTR	*curr;
X{
X	static	LPTR	*prev;
X	char	c;
X
X	prev = curr;
X	if (prev->index == 0) {		/* beginning of line */
X		prev = prevline (prev);		/* jump back */
X		c = CHAR( prev );
X		while (c!='\n' && c!= '\0') {	/* go to end of line */
X			prev->index++;
X			c = CHAR( prev );
X		}
X	}
X	else
X		prev->index--;
X
X	return (prev);
X}
X
X
!EOR!
echo extracting - main.c
sed 's/^X//' > main.c << '!EOR!'
X/* $Header: /nw/tony/src/stevie/src/RCS/main.c,v 1.12 89/08/02 19:53:27 tony Exp $
X *
X * The main routine and routines to deal with the input buffer.
X */
X
X#include "stevie.h"
X
Xint Rows;		/* Number of Rows and Columns */
Xint Columns;		/* in the current window. */
X
Xchar *Realscreen = NULL;	/* What's currently on the screen, a single */
X				/* array of size Rows*Columns. */
Xchar *Nextscreen = NULL;	/* What's to be put on the screen. */
X
Xchar *Filename = NULL;	/* Current file name */
X
XLPTR *Filemem;		/* Pointer to the first line of the file */
X
XLPTR *Filetop;		/* Line 'above' the start of the file */
X
XLPTR *Fileend;		/* Pointer to the end of the file in Filemem. */
X			/* (It points to the byte AFTER the last byte.) */
X
XLPTR *Topchar;		/* Pointer to the byte in Filemem which is */
X			/* in the upper left corner of the screen. */
X
XLPTR *Botchar;		/* Pointer to the byte in Filemem which is */
X			/* just off the bottom of the screen. */
X
XLPTR *Curschar;		/* Pointer to byte in Filemem at which the */
X			/* cursor is currently placed. */
X
Xint Cursrow, Curscol;	/* Current position of cursor */
X
Xint Cursvcol;		/* Current virtual column, the column number of */
X			/* the file's actual line, as opposed to the */
X			/* column number we're at on the screen.  This */
X			/* makes a difference on lines that span more */
X			/* than one screen line. */
X
Xint Curswant = 0;	/* The column we'd like to be at. This is used */
X			/* try to stay in the same column through up/down */
X			/* cursor motions. */
X
Xbool_t set_want_col;	/* If set, then update Curswant the next time */
X			/* through cursupdate() to the current virtual */
X			/* column. */
X
Xint State = NORMAL;	/* This is the current state of the command */
X			/* interpreter. */
X
Xint Prenum = 0;		/* The (optional) number before a command. */
X
XLPTR *Insstart;		/* This is where the latest insert/append */
X			/* mode started. */
X
Xbool_t Changed = 0;	/* Set to 1 if something in the file has been */
X			/* changed and not written out. */
X
Xchar Redobuff[1024];	/* Each command should stuff characters into this */
X			/* buffer that will re-execute itself. */
X
Xchar Insbuff[1024];	/* Each insertion gets stuffed into this buffer. */
X
Xint Ninsert = 0;	/* Number of characters in the current insertion. */
Xchar *Insptr = NULL;
X
Xbool_t	got_int=FALSE;	/* set to TRUE when an interrupt occurs (if possible) */
X
Xbool_t	interactive = FALSE;	/* set TRUE when main() is ready to roll */
X
Xchar **files;		/* list of input files */
Xint  numfiles;		/* number of input files */
Xint  curfile;		/* number of the current file */
X
Xstatic void
Xusage()
X{
X	fprintf(stderr, "usage: stevie [file ...]\n");
X	fprintf(stderr, "       stevie -t tag\n");
X	fprintf(stderr, "       stevie +[num] file\n");
X	fprintf(stderr, "       stevie +/pat  file\n");
X	exit(1);
X}
X
Xmain(argc,argv)
Xint	argc;
Xchar	*argv[];
X{
X	char	*initstr, *getenv();	/* init string from the environment */
X	char	*tag = NULL;		/* tag from command line */
X	char	*pat = NULL;		/* pattern from command line */
X	int	line = -1;		/* line number from command line */
X
X	/*
X	 * Process the command line arguments.
X	 */
X	if (argc > 1) {
X		switch (argv[1][0]) {
X		
X		case '-':			/* -t tag */
X			if (argv[1][1] != 't')
X				usage();
X
X			if (argv[2] == NULL)
X				usage();
X
X			Filename = NULL;
X			tag = argv[2];
X			numfiles = 1;
X			break;
X
X		case '+':			/* +n or +/pat */
X			if (argv[1][1] == '/') {
X				if (argv[2] == NULL)
X					usage();
X				Filename = strsave(argv[2]);
X				pat = &(argv[1][1]);
X				numfiles = 1;
X
X			} else if (isdigit(argv[1][1]) || argv[1][1] == NUL) {
X				if (argv[2] == NULL)
X					usage();
X				Filename = strsave(argv[2]);
X				numfiles = 1;
X
X				line = (isdigit(argv[1][1])) ?
X					atoi(&(argv[1][1])) : 0;
X			} else
X				usage();
X
X			break;
X
X		default:			/* must be a file name */
X			Filename = strsave(argv[1]);
X			files = &(argv[1]);
X			numfiles = argc - 1;
X			break;
X		}
X	} else {
X		Filename = NULL;
X		numfiles = 1;
X	}
X	curfile = 0;
X
X 	if (numfiles > 1)
X 		fprintf(stderr, "%d files to edit\n", numfiles);
X 
X	windinit();
X
X	/*
X	 * Allocate LPTR structures for all the various position pointers
X	 */
X 	if ((Filemem = (LPTR *) malloc(sizeof(LPTR))) == NULL ||
X 	    (Filetop = (LPTR *) malloc(sizeof(LPTR))) == NULL ||
X 	    (Fileend = (LPTR *) malloc(sizeof(LPTR))) == NULL ||
X 	    (Topchar = (LPTR *) malloc(sizeof(LPTR))) == NULL ||
X 	    (Botchar = (LPTR *) malloc(sizeof(LPTR))) == NULL ||
X 	    (Curschar = (LPTR *) malloc(sizeof(LPTR))) == NULL ||
X	    (Insstart = (LPTR *) malloc(sizeof(LPTR))) == NULL ||
X	    (screenalloc() == -1) ) {
X		fprintf(stderr, "Can't allocate data structures\n");
X		windexit(0);
X	}
X
X	filealloc();		/* Initialize Filemem, Filetop, and Fileend */
X
X	screenclear();
X
X	if ((initstr = getenv("EXINIT")) != NULL) {
X		char *lp, buf[128];
X
X		if ((lp = getenv("LINES")) != NULL) {
X			sprintf(buf, "%s lines=%s", initstr, lp);
X			docmdln(buf);
X		} else
X			docmdln(initstr);
X	}
X
X	if (Filename != NULL) {
X		if (readfile(Filename, Filemem, FALSE))
X			filemess("[New File]");
X	} else if (tag == NULL)
X		msg("Empty Buffer");
X
X	setpcmark();
X
X	if (tag) {
X		stuffin(":ta ");
X		stuffin(tag);
X		stuffin("\n");
X
X	} else if (pat) {
X		stuffin(pat);
X		stuffin("\n");
X
X	} else if (line >= 0) {
X		if (line > 0)
X			stuffnum(line);
X		stuffin("G");
X	}
X
X	interactive = TRUE;
X
X	edit();
X
X	windexit(0);
X
X	return 1;		/* shouldn't be reached */
X}
X
X#define	RBSIZE	1024
Xstatic char getcbuff[RBSIZE];
Xstatic char *getcnext = NULL;
X
Xvoid
Xstuffin(s)
Xchar	*s;
X{
X	if (s == NULL) {		/* clear the stuff buffer */
X		getcnext = NULL;
X		return;
X	}
X
X	if (getcnext == NULL) {
X		strcpy(getcbuff,s);
X		getcnext = getcbuff;
X	} else
X		strcat(getcbuff,s);
X}
X
Xvoid
Xstuffnum(n)
Xint	n;
X{
X	char	buf[32];
X
X	sprintf(buf, "%d", n);
X	stuffin(buf);
X}
X
Xint
Xvgetc()
X{
X	register int	c;
X
X	/*
X	 * inchar() may map special keys by using stuffin(). If it does
X	 * so, it returns -1 so we know to loop here to get a real char.
X	 */
X	do {
X		if ( getcnext != NULL ) {
X			int nextc = *getcnext++;
X			if ( *getcnext == NUL ) {
X				*getcbuff = NUL;
X				getcnext = NULL;
X			}
X			return(nextc);
X		}
X		c = inchar();
X	} while (c == -1);
X
X	return c;
X}
X
X/*
X * anyinput
X *
X * Return non-zero if input is pending.
X */
X
Xbool_t
Xanyinput()
X{
X	return (getcnext != NULL);
X}
X
X/*
X * do_mlines() - process mode lines for the current file
X *
X * Returns immediately if the "ml" parameter isn't set.
X */
X#define	NMLINES	5	/* no. of lines at start/end to check for modelines */
X
Xvoid
Xdo_mlines()
X{
X	void	chk_mline();
X	int	i;
X	register LPTR	*p;
X
X	if (!P(P_ML))
X		return;
X
X	p = Filemem;
X	for (i=0; i < NMLINES ;i++) {
X		chk_mline(p->linep->s);
X		if ((p = nextline(p)) == NULL)
X			break;
X	}
X
X	if ((p = prevline(Fileend)) == NULL)
X		return;
X
X	for (i=0; i < NMLINES ;i++) {
X		chk_mline(p->linep->s);
X		if ((p = prevline(p)) == NULL)
X			break;
X	}
X}
X
X/*
X * chk_mline() - check a single line for a mode string
X */
Xstatic void
Xchk_mline(s)
Xregister char	*s;
X{
X	register char	*cs;		/* local copy of any modeline found */
X	register char	*e;
X
X	for (; *s != NUL ;s++) {
X		if (strncmp(s, "vi:", 3) == 0 || strncmp(s, "ex:", 3) == 0) {
X			cs = strsave(s+3);
X			if ((e = strchr(cs, ':')) != NULL) {
X				*e = NUL;
X				stuffin(mkstr(CTRL('o')));
X				docmdln(cs);
X			}
X			free(cs);
X		}
X	}
X}
!EOR!
echo extracting - mark.c
sed 's/^X//' > mark.c << '!EOR!'
X/* $Header: /nw/tony/src/stevie/src/RCS/mark.c,v 1.3 89/03/11 22:42:39 tony Exp $
X *
X * Routines to save and retrieve marks.
X */
X
X#include "stevie.h"
X
X#define	NMARKS	10		/* max. # of marks that can be saved */
X
Xstruct	mark {
X	char	name;
X	LPTR	pos;
X};
X
Xstatic	struct	mark	mlist[NMARKS];
Xstatic	struct	mark	pcmark;		/* previous context mark */
Xstatic	bool_t	pcvalid = FALSE;	/* true if pcmark is valid */
X
X/*
X * setmark(c) - set mark 'c' at current cursor position
X *
X * Returns TRUE on success, FALSE if no room for mark or bad name given.
X */
Xbool_t
Xsetmark(c)
Xregister char	c;
X{
X	register int	i;
X
X	if (!isalpha(c))
X		return FALSE;
X
X	/*
X	 * If there is already a mark of this name, then just use the
X	 * existing mark entry.
X	 */
X	for (i=0; i < NMARKS ;i++) {
X		if (mlist[i].name == c) {
X			mlist[i].pos = *Curschar;
X			return TRUE;
X		}
X	}
X
X	/*
X	 * There wasn't a mark of the given name, so find a free slot
X	 */
X	for (i=0; i < NMARKS ;i++) {
X		if (mlist[i].name == NUL) {	/* got a free one */
X			mlist[i].name = c;
X			mlist[i].pos = *Curschar;
X			return TRUE;
X		}
X	}
X	return FALSE;
X}
X
X/*
X * setpcmark() - set the previous context mark to the current position
X */
Xvoid
Xsetpcmark()
X{
X	pcmark.pos = *Curschar;
X	pcvalid = TRUE;
X}
X
X/*
X * getmark(c) - find mark for char 'c'
X *
X * Return pointer to LPTR or NULL if no such mark.
X */
XLPTR *
Xgetmark(c)
Xregister char	c;
X{
X	register int	i;
X
X	if (c == '\'' || c == '`')	/* previous context mark */
X		return pcvalid ? &(pcmark.pos) : (LPTR *) NULL;
X
X	for (i=0; i < NMARKS ;i++) {
X		if (mlist[i].name == c)
X			return &(mlist[i].pos);
X	}
X	return (LPTR *) NULL;
X}
X
X/*
X * clrall() - clear all marks
X *
X * Used mainly when trashing the entire buffer during ":e" type commands
X */
Xvoid
Xclrall()
X{
X	register int	i;
X
X	for (i=0; i < NMARKS ;i++)
X		mlist[i].name = NUL;
X	pcvalid = FALSE;
X}
X
X/*
X * clrmark(line) - clear any marks for 'line'
X *
X * Used any time a line is deleted so we don't have marks pointing to
X * non-existent lines.
X */
Xvoid
Xclrmark(line)
Xregister LINE	*line;
X{
X	register int	i;
X
X	for (i=0; i < NMARKS ;i++) {
X		if (mlist[i].pos.linep == line)
X			mlist[i].name = NUL;
X	}
X	if (pcvalid && (pcmark.pos.linep == line))
X		pcvalid = FALSE;
X}
!EOR!
echo extracting - misccmds.c
sed 's/^X//' > misccmds.c << '!EOR!'
X/* $Header: /nw/tony/src/stevie/src/RCS/misccmds.c,v 1.14 89/08/06 09:50:17 tony Exp $
X *
X * Various routines to perform specific editing operations or return
X * useful information about the file.
X */
X
X#include "stevie.h"
X
Xstatic	void	openfwd(), openbwd();
X
Xextern	bool_t	did_ai;
X
X/*
X * opencmd
X *
X * Add a blank line above or below the current line.
X */
X
Xvoid
Xopencmd(dir, can_ai)
Xint	dir;
Xint	can_ai;			/* if true, consider auto-indent */
X{
X	if (dir == FORWARD)
X		openfwd(can_ai);
X	else
X		openbwd(can_ai);
X}
X
Xstatic void
Xopenfwd(can_ai)
Xint	can_ai;
X{
X	register LINE	*l;
X	LPTR	*next;
X	register char	*s;	/* string to be moved to new line, if any */
X	int	newindex = 0;	/* index of the cursor on the new line */
X
X	/*
X	 * If we're in insert mode, we need to move the remainder of the
X	 * current line onto the new line. Otherwise the new line is left
X	 * blank.
X	 */
X	if (State == INSERT || State == REPLACE)
X		s = &Curschar->linep->s[Curschar->index];
X	else
X		s = "";
X
X	if ((next = nextline(Curschar)) == NULL)	/* open on last line */
X		next = Fileend;
X
X	/*
X	 * By asking for as much space as the prior line had we make sure
X	 * that we'll have enough space for any auto-indenting.
X	 */
X	if ((l = newline(strlen(Curschar->linep->s) + SLOP)) == NULL)
X		return;
X
X	if (*s != NUL)
X		strcpy(l->s, s);		/* copy string to new line */
X
X	else if (can_ai && P(P_AI) && !anyinput()) {
X		char	*p;
X
X		/*
X		 * Copy prior line, and truncate after white space
X		 */
X		strcpy(l->s, Curschar->linep->s);
X
X		for (p = l->s; *p == ' ' || *p == TAB ;p++)
X			;
X		*p = NUL;
X		newindex = p - l->s;
X
X		/*
X		 * If we just did an auto-indent, then we didn't type
X		 * anything on the prior line, and it should be truncated.
X		 */
X		if (did_ai)
X			Curschar->linep->s[0] = NUL;
X
X		did_ai = TRUE;
X	}
X
X	/* truncate current line at cursor */
X	if (State == INSERT || State == REPLACE)
X		*s = NUL;
X			
X
X	Curschar->linep->next = l;	/* link neighbors to new line */
X	next->linep->prev = l;
X
X	l->prev = Curschar->linep;	/* link new line to neighbors */
X	l->next = next->linep;
X
X	if (next == Fileend)			/* new line at end */
X		l->num = Curschar->linep->num + LINEINC;
X
X	else if ((l->prev->num) + 1 == l->next->num)	/* no gap, renumber */
X		renum();
X
X	else {					/* stick it in the middle */
X		unsigned long	lnum;
X		lnum = ((long)l->prev->num + (long)l->next->num) / 2;
X		l->num = lnum;
X	}
X
X	/*
X	 * Get the cursor to the start of the line, so that 'Cursrow'
X	 * gets set to the right physical line number for the stuff
X	 * that follows...
X	 */
X	Curschar->index = 0;
X	cursupdate();
X
X	/*
X	 * If we're doing an open on the last logical line, then
X	 * go ahead and scroll the screen up. Otherwise, just insert
X	 * a blank line at the right place. We use calls to plines()
X	 * in case the cursor is resting on a long line.
X	 */
X	if (Cursrow + plines(Curschar) == (Rows - 1))
X		scrollup(1);
X	else
X		s_ins(Cursrow+plines(Curschar), 1);
X
X	*Curschar = *nextline(Curschar);	/* cursor moves down */
X	Curschar->index = newindex;
X
X	updatescreen();		/* because Botchar is now invalid... */
X
X	cursupdate();		/* update Cursrow before insert */
X}
X
Xstatic void
Xopenbwd(can_ai)
Xint	can_ai;
X{
X	register LINE	*l;
X	LINE	*prev;
X	int	newindex = 0;
X
X	prev = Curschar->linep->prev;
X
X	if ((l = newline(strlen(Curschar->linep->s) + SLOP)) == NULL)
X		return;
X
X	Curschar->linep->prev = l;	/* link neighbors to new line */
X	prev->next = l;
X
X	l->next = Curschar->linep;	/* link new line to neighbors */
X	l->prev = prev;
X
X	if (can_ai && P(P_AI) && !anyinput()) {
X		char	*p;
X
X		/*
X		 * Copy current line, and truncate after white space
X		 */
X		strcpy(l->s, Curschar->linep->s);
X
X		for (p = l->s; *p == ' ' || *p == TAB ;p++)
X			;
X		*p = NUL;
X		newindex = p - l->s;
X
X		did_ai = TRUE;
X	}
X
X	Curschar->linep = Curschar->linep->prev;
X	Curschar->index = newindex;
X
X	if (prev == Filetop->linep)		/* new start of file */
X		Filemem->linep = l;
X
X	renum();	/* keep it simple - we don't do this often */
X
X	cursupdate();			/* update Cursrow before insert */
X	if (Cursrow != 0)
X		s_ins(Cursrow, 1);		/* insert a physical line */
X
X	updatescreen();
X}
X
Xint
Xcntllines(pbegin,pend)
Xregister LPTR	*pbegin, *pend;
X{
X	register LINE	*lp;
X	int	lnum = 1;
X
X	for (lp = pbegin->linep; lp != pend->linep ;lp = lp->next)
X		lnum++;
X
X	return(lnum);
X}
X
X/*
X * plines(p) - return the number of physical screen lines taken by line 'p'
X */
Xint
Xplines(p)
XLPTR	*p;
X{
X	register int	col = 0;
X	register char	*s;
X
X	s = p->linep->s;
X
X	if (*s == NUL)		/* empty line */
X		return 1;
X
X	for (; *s != NUL ;s++) {
X		if ( *s == TAB && !P(P_LS))
X			col += P(P_TS) - (col % P(P_TS));
X		else
X			col += chars[(unsigned)(*s & 0xff)].ch_size;
X	}
X
X	/*
X	 * If list mode is on, then the '$' at the end of
X	 * the line takes up one extra column.
X	 */
X	if (P(P_LS))
X		col += 1;
X	/*
X	 * If 'number' mode is on, add another 8.
X	 */
X	if (P(P_NU))
X		col += 8;
X
X	return ((col + (Columns-1)) / Columns);
X}
X
Xvoid
Xfileinfo()
X{
X	extern	int	numfiles, curfile;
X	register long	l1, l2;
X
X	if (bufempty()) {
X 		l1 = 0;
X 		l2 = 1;			/* don't div by zero */
X 	} else {
X 		l1 = cntllines(Filemem, Curschar);
X 		l2 = cntllines(Filemem, Fileend) - 1;
X	}
X
X	if (numfiles > 1)
X		smsg("\"%s\"%s line %ld of %ld -- %ld %% -- (file %d of %d)",
X			(Filename != NULL) ? Filename : "No File",
X			Changed ? " [Modified]" : "",
X			l1, l2, (l1 * 100)/l2,
X			curfile+1, numfiles);
X	else
X		smsg("\"%s\"%s line %ld of %ld -- %ld %% --",
X			(Filename != NULL) ? Filename : "No File",
X			Changed ? " [Modified]" : "",
X			l1, l2, (l1 * 100)/l2);
X}
X
X/*
X * gotoline(n) - return a pointer to line 'n'
X *
X * Returns a pointer to the last line of the file if n is zero, or
X * beyond the end of the file.
X */
XLPTR *
Xgotoline(n)
Xregister int	n;
X{
X	static	LPTR	l;
X
X	l.index = 0;
X
X	if ( n == 0 )
X		l = *prevline(Fileend);
X	else {
X		LPTR	*p;
X
X		for (l = *Filemem; --n > 0 ;l = *p)
X			if ((p = nextline(&l)) == NULL)
X				break;
X	}
X	return &l;
X}
X
Xvoid
Xinschar(c)
Xint	c;
X{
X	register char	*p, *pend;
X
X	/* make room for the new char. */
X	if ( ! canincrease(1) )
X		return;
X
X	if (State != REPLACE) {
X		p = &Curschar->linep->s[strlen(Curschar->linep->s) + 1];
X		pend = &Curschar->linep->s[Curschar->index];
X
X		for (; p > pend ;p--)
X			*p = *(p-1);
X
X		*p = c;
X
X	} else {	/* replace mode */
X		/*
X		 * Once we reach the end of the line, we are effectively
X		 * inserting new text, so make sure the string terminator
X		 * stays out there.
X		 */
X		if (gchar(Curschar) == NUL)
X			Curschar->linep->s[Curschar->index+1] = NUL;
X		pchar(Curschar, c);
X	}
X
X	/*
X	 * If we're in insert mode and showmatch mode is set, then
X	 * check for right parens and braces. If there isn't a match,
X	 * then beep. If there is a match AND it's on the screen, then
X	 * flash to it briefly. If it isn't on the screen, don't do anything.
X	 */
X	if (P(P_SM) && State == INSERT && (c == ')' || c == '}' || c == ']')) {
X		LPTR	*lpos, csave;
X
X		if ((lpos = showmatch()) == NULL)	/* no match, so beep */
X			beep();
X		else if (LINEOF(lpos) >= LINEOF(Topchar)) {
X			updatescreen();		/* show the new char first */
X			csave = *Curschar;
X			*Curschar = *lpos;	/* move to matching char */
X			cursupdate();
X			windgoto(Cursrow, Curscol);
X			pause();		/* brief pause */
X			*Curschar = csave;	/* restore cursor position */
X			cursupdate();
X		}
X	}
X
X	inc(Curschar);
X	CHANGED;
X}
X
Xbool_t
Xdelchar(fixpos)
Xbool_t	fixpos;		/* if TRUE, fix the cursor position when done */
X{
X	register int	i;
X
X	/* Check for degenerate case; there's nothing in the file. */
X	if (bufempty())
X		return FALSE;
X
X	if (lineempty())	/* can't do anything */
X		return FALSE;
X
X	/* Delete the char. at Curschar by shifting everything */
X	/* in the line down. */
X	for ( i=Curschar->index+1; i < Curschar->linep->size ;i++)
X		Curschar->linep->s[i-1] = Curschar->linep->s[i];
X
X	/* If we just took off the last character of a non-blank line, */
X	/* we don't want to end up positioned at the newline. */
X	if (fixpos) {
X		if (gchar(Curschar)==NUL && Curschar->index>0 && State!=INSERT)
X			Curschar->index--;
X	}
X	CHANGED;
X
X	return TRUE;
X}
X
X
Xvoid
Xdelline(nlines, can_update)
Xint	nlines;
Xbool_t	can_update;
X{
X	register LINE	*p, *q;
X	int	doscreen;		/* if true, update the screen */
X
X	doscreen = can_update;
X	/*
X	 * There's no point in keeping the screen updated if we're
X	 * deleting more than a screen's worth of lines.
X	 */
X	if (nlines > (Rows - 1) && can_update) {
X		doscreen = FALSE;
X		s_del(Cursrow, Rows-1);	/* flaky way to clear rest of screen */
X	}
X
X	while ( nlines-- > 0 ) {
X
X		if (bufempty())			/* nothing to delete */
X			break;
X
X		if (buf1line()) {		/* just clear the line */
X			Curschar->linep->s[0] = NUL;
X			Curschar->index = 0;
X			break;
X		}
X
X		p = Curschar->linep->prev;
X		q = Curschar->linep->next;
X
X		if (p == Filetop->linep) {	/* first line of file so... */
X			Filemem->linep = q;	/* adjust start of file */
X			Topchar->linep = q;	/* and screen */
X		}
X		p->next = q;
X		q->prev = p;
X
X		clrmark(Curschar->linep);	/* clear marks for the line */
X
X		/*
X		 * Delete the correct number of physical lines on the screen
X		 */
X		if (doscreen)
X			s_del(Cursrow, plines(Curschar));
X
X		/*
X		 * If deleting the top line on the screen, adjust Topchar
X		 */
X		if (Topchar->linep == Curschar->linep)
X			Topchar->linep = q;
X
X		free(Curschar->linep->s);
X		free((char *) Curschar->linep);
X
X		Curschar->linep = q;
X		Curschar->index = 0;		/* is this right? */
X		CHANGED;
X
X		/* If we delete the last line in the file, back up */
X		if ( Curschar->linep == Fileend->linep) {
X			Curschar->linep = Curschar->linep->prev;
X			/* and don't try to delete any more lines */
X			break;
X		}
X	}
X}
!EOR!
echo extracting - normal.c
sed 's/^X//' > normal.c << '!EOR!'
X/* $Header: /nw/tony/src/stevie/src/RCS/normal.c,v 1.25 89/08/06 09:50:25 tony Exp $
X *
X * Contains the main routine for processing characters in command mode.
X * Communicates closely with the code in ops.c to handle the operators.
X */
X
X#include "stevie.h"
X#include "ops.h"
X
X/*
X * Generally speaking, every command in normal() should either clear any
X * pending operator (with CLEAROP), or set the motion type variable.
X */
X
X#define	CLEAROP	(operator=NOP)	/* clear any pending operator */
X
Xint	operator = NOP;		/* current pending operator */
Xint	mtype;			/* type of the current cursor motion */
Xbool_t	mincl;			/* true if char motion is inclusive */
XLPTR	startop;		/* cursor pos. at start of operator */
X
X/*
X * Operators can have counts either before the operator, or between the
X * operator and the following cursor motion as in:
X *
X *	d3w or 3dw
X *
X * If a count is given before the operator, it is saved in opnum. If
X * normal() is called with a pending operator, the count in opnum (if
X * present) overrides any count that came later.
X */
Xstatic	int	opnum = 0;
X
X#define	DEFAULT1(x)	(((x) == 0) ? 1 : (x))
X
X/*
X * normal(c)
X *
X * Execute a command in command mode.
X *
X * This is basically a big switch with the cases arranged in rough categories
X * in the following order:
X *
X *	1. File positioning commands
X *	2. Control commands (e.g. ^G, Z, screen redraw, etc)
X *	3. Character motions
X *	4. Search commands (of various kinds)
X *	5. Edit commands (e.g. J, x, X)
X *	6. Insert commands (e.g. i, o, O, A)
X *	7. Operators
X *	8. Abbreviations (e.g. D, C)
X *	9. Marks
X */
Xvoid
Xnormal(c)
Xregister int	c;
X{
X	register int	n;
X	register char	*s;	/* temporary variable for misc. strings */
X	bool_t	flag = FALSE;
X	int	type = 0;	/* used in some operations to modify type */
X	int	dir = FORWARD;	/* search direction */
X	int	nchar = NUL;
X	bool_t	finish_op;
X
X	/*
X	 * If there is an operator pending, then the command we take
X	 * this time will terminate it. Finish_op tells us to finish
X	 * the operation before returning this time (unless the operation
X	 * was cancelled.
X	 */
X	finish_op = (operator != NOP);
X
X	/*
X	 * If we're in the middle of an operator AND we had a count before
X	 * the operator, then that count overrides the current value of
X	 * Prenum. What this means effectively, is that commands like
X	 * "3dw" get turned into "d3w" which makes things fall into place
X	 * pretty neatly.
X	 */
X	if (finish_op) {
X		if (opnum != 0)
X			Prenum = opnum;
X	} else
X		opnum = 0;
X
X	u_lcheck();	/* clear the "line undo" buffer if we've moved */
X
X	switch (c & 0xff) {
X
X	/*
X	 * Screen positioning commands
X	 */
X	case CTRL('D'):
X		CLEAROP;
X		if (Prenum)
X			P(P_SS) = (Prenum > Rows-1) ? Rows-1 : Prenum;
X		scrollup(P(P_SS));
X		onedown(P(P_SS));
X		updatescreen();
X		break;
X
X	case CTRL('U'):
X		CLEAROP;
X		if (Prenum)
X			P(P_SS) = (Prenum > Rows-1) ? Rows-1 : Prenum;
X		scrolldown(P(P_SS));
X		oneup(P(P_SS));
X		updatescreen();
X		break;
X
X	/*
X	 * This is kind of a hack. If we're moving by one page, the calls
X	 * to stuffin() do exactly the right thing in terms of leaving
X	 * some context, and so on. If a count was given, we don't have
X	 * to worry about these issues.
X	 */
X	case CTRL('F'):
X		CLEAROP;
X		n = DEFAULT1(Prenum);
X		if (n > 1) {
X			if ( ! onedown(Rows * n) )
X				beep();
X			cursupdate();
X		} else {
X			/* screenclear(); */
X			stuffin("Lz\nM");
X		}
X		break;
X
X	case CTRL('B'):
X		CLEAROP;
X		n = DEFAULT1(Prenum);
X		if (n > 1) {
X			if ( ! oneup(Rows * n) )
X				beep();
X			cursupdate();
X		} else {
X			/* screenclear(); */
X			stuffin("Hz-M");
X		}
X		break;
X
X	case CTRL('E'):
X		CLEAROP;
X		scrollup(DEFAULT1(Prenum));
X		updatescreen();
X		break;
X
X	case CTRL('Y'):
X		CLEAROP;
X		scrolldown(DEFAULT1(Prenum));
X		updatescreen();
X		break;
X
X	case 'z':
X		CLEAROP;
X		switch (vgetc()) {
X		case NL:		/* put Curschar at top of screen */
X		case CR:
X			*Topchar = *Curschar;
X			Topchar->index = 0;
X			updatescreen();
X			break;
X
X		case '.':		/* put Curschar in middle of screen */
X			n = Rows/2;
X			goto dozcmd;
X
X		case '-':		/* put Curschar at bottom of screen */
X			n = Rows-1;
X			/* fall through */
X
X		dozcmd:
X			{
X				register LPTR	*lp = Curschar;
X				register int	l = 0;
X
X				while ((l < n) && (lp != NULL)) {
X					l += plines(lp);
X					*Topchar = *lp;
X					lp = prevline(lp);
X				}
X			}
X			Topchar->index = 0;
X			updatescreen();
X			break;
X
X		default:
X			beep();
X		}
X		break;
X
X	/*
X	 * Control commands
X	 */
X	case ':':
X		CLEAROP;
X		if ((s = getcmdln(c)) != NULL)
X			docmdln(s);
X		break;
X
X	case K_HELP:
X		CLEAROP;
X		if (help()) {
X			screenclear();
X			updatescreen();
X		}
X		break;
X
X	case CTRL('L'):
X		CLEAROP;
X		screenclear();
X		updatescreen();
X		break;
X
X
X	case CTRL('O'):			/* ignored */
X		/*
X		 * A command that's ignored can be useful. We use it at
X		 * times when we want to postpone redraws. By stuffing
X		 * in a control-o, redraws get suspended until the editor
X		 * gets back around to processing input.
X		 */
X		break;
X
X	case CTRL('G'):
X		CLEAROP;
X		fileinfo();
X		break;
X
X	case K_CCIRCM:			/* shorthand command */
X		CLEAROP;
X#ifdef TAGSTACK
X		/* If tag stacking compiled in & enabled, this is an untag.
X		 * Otherwise, or if tag stack empty, edit alternate file.
X		 * "untage" is so interpreted by dountag().
X		 */
X		if (P(P_TG))
X			stuffin(":untage\n");
X		else
X#endif
X			stuffin(":e #\n");
X		break;
X
X	case 'Z':			/* write, if changed, and exit */
X		if (vgetc() != 'Z') {
X			beep();
X			break;
X		}
X		doxit();
X		break;
X
X	/*
X	 * Macro evaluates true if char 'c' is a valid identifier character
X	 */
X#	define	IDCHAR(c)	(isalpha(c) || isdigit(c) || (c) == '_')
X
X	case CTRL(']'):			/* :ta to current identifier */
X		CLEAROP;
X		{
X			char	ch;
X			LPTR	save;
X
X			save = *Curschar;
X			/*
X			 * First back up to start of identifier. This
X			 * doesn't match the real vi but I like it a
X			 * little better and it shouldn't bother anyone.
X			 */
X			ch = gchar(Curschar);
X			while (IDCHAR(ch)) {
X				if (!oneleft())
X					break;
X				ch = gchar(Curschar);
X			}
X			if (!IDCHAR(ch))
X				oneright();
X
X			stuffin(":ta ");
X			/*
X			 * Now grab the chars in the identifier
X			 */
X			ch = gchar(Curschar);
X			while (IDCHAR(ch)) {
X				stuffin(mkstr(ch));
X				if (!oneright())
X					break;
X				ch = gchar(Curschar);
X			}
X			stuffin("\n");
X
X			*Curschar = save;	/* restore, in case of error */
X		}
X		break;
X
X	/*
X	 * Character motion commands
X	 */
X	case 'G':
X		mtype = MLINE;
X		*Curschar = *gotoline(Prenum);
X		beginline(TRUE);
X		break;
X
X	case 'H':
X		mtype = MLINE;
X		*Curschar = *Topchar;
X		for (n = Prenum; n && onedown(1) ;n--)
X			;
X		beginline(TRUE);
X		break;
X
X	case 'M':
X		mtype = MLINE;
X		*Curschar = *Topchar;
X		for (n = 0; n < Rows/2 && onedown(1) ;n++)
X			;
X		beginline(TRUE);
X		break;
X
X	case 'L':
X		mtype = MLINE;
X		*Curschar = *prevline(Botchar);
X		for (n = Prenum; n && oneup(1) ;n--)
X			;
X		beginline(TRUE);
X		break;
X
X	case 'l':
X	case K_RARROW:
X	case ' ':
X		mtype = MCHAR;
X		mincl = FALSE;
X		n = DEFAULT1(Prenum);
X		while (n--) {
X			if ( ! oneright() )
X				beep();
X		}
X		set_want_col = TRUE;
X		break;
X
X	case 'h':
X	case K_LARROW:
X	case CTRL('H'):
X		mtype = MCHAR;
X		mincl = FALSE;
X		n = DEFAULT1(Prenum);
X		while (n--) {
X			if ( ! oneleft() )
X				beep();
X		}
X		set_want_col = TRUE;
X		break;
X
X	case '-':
X		flag = TRUE;
X		/* fall through */
X
X	case 'k':
X	case K_UARROW:
X	case CTRL('P'):
X		mtype = MLINE;
X		if ( ! oneup(DEFAULT1(Prenum)) )
X			beep();
X		if (flag)
X			beginline(TRUE);
X		break;
X
X	case '+':
X	case CR:
X	case NL:
X		flag = TRUE;
X		/* fall through */
X
X	case 'j':
X	case K_DARROW:
X	case CTRL('N'):
X		mtype = MLINE;
X		if ( ! onedown(DEFAULT1(Prenum)) )
X			beep();
X		if (flag)
X			beginline(TRUE);
X		break;
X
X	/*
X	 * This is a strange motion command that helps make operators
X	 * more logical. It is actually implemented, but not documented
X	 * in the real 'vi'. This motion command actually refers to "the
X	 * current line". Commands like "dd" and "yy" are really an alternate
X	 * form of "d_" and "y_". It does accept a count, so "d3_" works to
X	 * delete 3 lines.
X	 */
X	case '_':
X	lineop:
X		mtype = MLINE;
X		onedown(DEFAULT1(Prenum)-1);
X		break;
X
X	case '|':
X		mtype = MCHAR;
X		mincl = TRUE;
X		beginline(FALSE);
X		if (Prenum > 0)
X			*Curschar = *coladvance(Curschar, Prenum-1);
X		Curswant = Prenum - 1;
X		break;
X		
X	/*
X	 * Word Motions
X	 */
X
X	case 'B':
X		type = 1;
X		/* fall through */
X
X	case 'b':
X		mtype = MCHAR;
X		mincl = FALSE;
X		set_want_col = TRUE;
X		for (n = DEFAULT1(Prenum); n > 0 ;n--) {
X			LPTR	*pos;
X
X			if ((pos = bck_word(Curschar, type)) == NULL) {
X				beep();
X				CLEAROP;
X				break;
X			} else
X				*Curschar = *pos;
X		}
X		break;
X
X	case 'W':
X		type = 1;
X		/* fall through */
X
X	case 'w':
X		/*
X		 * This is a little strange. To match what the real vi
X		 * does, we effectively map 'cw' to 'ce', and 'cW' to 'cE'.
X		 * This seems impolite at first, but it's really more
X		 * what we mean when we say 'cw'.
X		 */
X		if (operator == CHANGE)
X			goto doecmd;
X
X		mtype = MCHAR;
X		mincl = FALSE;
X		set_want_col = TRUE;
X		for (n = DEFAULT1(Prenum); n > 0 ;n--) {
X			LPTR	*pos;
X
X			if ((pos = fwd_word(Curschar, type)) == NULL) {
X				beep();
X				CLEAROP;
X				break;
X			} else
X				*Curschar = *pos;
X		}
X		break;
X
X	case 'E':
X		type = 1;
X		/* fall through */
X
X	case 'e':
X	doecmd:
X		mtype = MCHAR;
X		mincl = TRUE;
X		set_want_col = TRUE;
X		for (n = DEFAULT1(Prenum); n > 0 ;n--) {
X			LPTR	*pos;
X
X			/*
X			 * The first motion gets special treatment if we're
X			 * do a 'CHANGE'.
X			 */
X			if (n == DEFAULT1(Prenum))
X				pos = end_word(Curschar,type,operator==CHANGE);
X			else
X				pos = end_word(Curschar, type, FALSE);
X
X			if (pos == NULL) {
X				beep();
X				CLEAROP;
X				break;
X			} else
X				*Curschar = *pos;
X		}
X		break;
X
X	case '$':
X		mtype = MCHAR;
X		mincl = TRUE;
X		while ( oneright() )
X			;
X		Curswant = 999;		/* so we stay at the end */
X		break;
X
X	case '^':
X		mtype = MCHAR;
X		mincl = FALSE;
X		beginline(TRUE);
X		break;
X
X	case '0':
X		mtype = MCHAR;
X		mincl = TRUE;
X		beginline(FALSE);
X		break;
X
X	/*
X	 * Searches of various kinds
X	 */
X	case '?':
X	case '/':
X		s = getcmdln(c);	/* get the search string */
X
X		/*
X		 * If they backspaced out of the search command,
X		 * just bag everything.
X		 */
X		if (s == NULL) {
X			CLEAROP;
X			break;
X		}
X
X		mtype = MCHAR;
X		mincl = FALSE;
X		set_want_col = TRUE;
X
X		/*
X		 * If no string given, pass NULL to repeat the prior search.
X		 * If the search fails, abort any pending operator.
X		 */
X		if (!dosearch(
X				(c == '/') ? FORWARD : BACKWARD,
X				(*s == NUL) ? NULL : s
X			     ))
X			CLEAROP;
X		break;
X
X	case 'n':
X		mtype = MCHAR;
X		mincl = FALSE;
X		set_want_col = TRUE;
X		if (!repsearch(0))
X			CLEAROP;
X		break;
X
X	case 'N':
X		mtype = MCHAR;
X		mincl = FALSE;
X		set_want_col = TRUE;
X		if (!repsearch(1))
X			CLEAROP;
X		break;
X
X	/*
X	 * Character searches
X	 */
X	case 'T':
X		dir = BACKWARD;
X		/* fall through */
X
X	case 't':
X		type = 1;
X		goto docsearch;
X
X	case 'F':
X		dir = BACKWARD;
X		/* fall through */
X
X	case 'f':
X	docsearch:
X		mtype = MCHAR;
X		mincl = TRUE;
X		set_want_col = TRUE;
X		if ((nchar = vgetc()) == ESC)	/* search char */
X			break;
X
X		for (n = DEFAULT1(Prenum); n > 0 ;n--) {
X			if (!searchc(nchar, dir, type)) {
X				CLEAROP;
X				beep();
X			}
X		}
X		break;
X
X	case ',':
X		flag = 1;
X		/* fall through */
X
X	case ';':
X		mtype = MCHAR;
X		mincl = TRUE;
X		set_want_col = TRUE;
X		for (n = DEFAULT1(Prenum); n > 0 ;n--) {
X			if (!crepsearch(flag)) {
X				CLEAROP;
X				beep();
X			}
X		}
X		break;
X
X	case '(':			/* sentence searches */
X		dir = BACKWARD;
X		/* fall through */
X
X	case ')':
X		mtype = MCHAR;
X		mincl = FALSE;
X		set_want_col = TRUE;
X		if (findsent(dir) == NULL) {
X			beep();
X			CLEAROP;
X		}
X		break;
X
X	case '{':			/* paragraph searches */
X		dir = BACKWARD;
X		/* fall through */
X
X	case '}':
X		mtype = MCHAR;
X		mincl = FALSE;
X		set_want_col = TRUE;
X		if (!findpara(dir)) {
X			beep();
X			CLEAROP;
X		}
X		break;
X
X	case '[':			/* function searches */
X		dir = BACKWARD;
X		/* fall through */
X
X	case ']':
X		mtype = MLINE;
X		set_want_col = TRUE;
X		if (vgetc() != c) {
X			beep();
X			CLEAROP;
X			break;
X		}
X
X		if (!findfunc(dir)) {
X			beep();
X			CLEAROP;
X		}
X		break;
X
X	case '%':
X		mtype = MCHAR;
X		mincl = TRUE;
X		{
X			LPTR	*pos;
X
X			if ((pos = showmatch()) == NULL) {
X				beep();
X				CLEAROP;
X			} else {
X				setpcmark();
X				*Curschar = *pos;
X				set_want_col = TRUE;
X			}
X		}
X		break;
X		
X	/*
X	 * Edits
X	 */
X	case '.':		/* repeat last change (usually) */
X		/*
X		 * If a delete is in effect, we let '.' help out the same
X		 * way that '_' helps for some line operations. It's like
X		 * an 'l', but subtracts one from the count and is inclusive.
X		 */
X		if (operator == DELETE || operator == CHANGE) {
X			if (Prenum != 0) {
X				n = DEFAULT1(Prenum) - 1;
X				while (n--)
X					if (! oneright())
X						break;
X			}
X			mtype = MCHAR;
X			mincl = TRUE;
X		} else {			/* a normal 'redo' */
X			CLEAROP;
X			stuffin(Redobuff);
X		}
X		break;
X
X	case 'u':
X	case K_UNDO:
X		CLEAROP;
X		u_undo();
X		break;
X
X	case 'U':
X		CLEAROP;
X		u_lundo();
X		break;
X
X	case 'x':
X		CLEAROP;
X		if (lineempty())	/* can't do it on a blank line */
X			beep();
X		if (Prenum)
X			stuffnum(Prenum);
X		stuffin("d.");
X		break;
X
X	case 'X':
X		CLEAROP;
X		if (!oneleft())
X			beep();
X		else {
X			strcpy(Redobuff, "X");
X			u_saveline();
X			delchar(TRUE);
X			updateline();
X		}
X		break;
X
X	case 'r':
X		CLEAROP;
X		if (lineempty()) {	/* Nothing to replace */
X			beep();
X			break;
X		}
X		if ((nchar = vgetc()) == ESC)
X			break;
X
X		if (nchar==CR || nchar==NL) {
X			stuffin("R\n\033");
X			break;
X		}
X
X		if (nchar & 0x80) {
X			beep();
X			break;
X		}
X		u_saveline();
X
X		/* Change current character. */
X		pchar(Curschar, nchar);
X
X		/* Save stuff necessary to redo it */
X		sprintf(Redobuff, "r%c", nchar);
X
X		CHANGED;
X		updateline();
X		break;
X
X	case '~':		/* swap case */
X		if (!P(P_TO)) {
X			CLEAROP;
X			if (lineempty()) {
X				beep();
X				break;
X			}
X			c = gchar(Curschar);
X
X			if (isalpha(c)) {
X				if (islower(c))
X					c = toupper(c);
X				else
X					c = tolower(c);
X			}
X			u_saveline();
X
X			pchar(Curschar, c);	/* Change current character. */
X			oneright();
X
X			strcpy(Redobuff, "~");
X
X			CHANGED;
X			updateline();
X		}
X#ifdef	TILDEOP
X		else {
X			if (operator == TILDE)		/* handle '~~' */
X				goto lineop;
X			if (Prenum != 0)
X				opnum = Prenum;
X			startop = *Curschar;
X			operator = TILDE;
X		}
X#endif
X
X		break;
X
X	case 'J':
X		CLEAROP;
X
X		u_save(Curschar->linep->prev, Curschar->linep->next->next);
X
X		if (!dojoin(TRUE))
X			beep();
X
X		strcpy(Redobuff, "J");
X		updatescreen();
X		break;
X
X	/*
X	 * Inserts
X	 */
X	case 'A':
X		set_want_col = TRUE;
X		while (oneright())
X			;
X		/* fall through */
X
X	case 'a':
X		CLEAROP;
X		/* Works just like an 'i'nsert on the next character. */
X		if (!lineempty())
X			inc(Curschar);
X		u_saveline();
X		startinsert(mkstr(c), FALSE);
X		break;
X
X	case 'I':
X		beginline(TRUE);
X		/* fall through */
X
X	case 'i':
X	case K_INSERT:
X		CLEAROP;
X		u_saveline();
X		startinsert(mkstr(c), FALSE);
X		break;
X
X	case 'o':
X		CLEAROP;
X		u_save(Curschar->linep, Curschar->linep->next);
X		opencmd(FORWARD, TRUE);
X		startinsert("o", TRUE);
X		break;
X
X	case 'O':
X		CLEAROP;
X		u_save(Curschar->linep->prev, Curschar->linep);
X		opencmd(BACKWARD, TRUE);
X		startinsert("O", TRUE);
X		break;
X
X	case 'R':
X		CLEAROP;
X		u_saveline();
X		startinsert("R", FALSE);
X		break;
X
X	/*
X	 * Operators
X	 */
X	case 'd':
X		if (operator == DELETE)		/* handle 'dd' */
X			goto lineop;
X		if (Prenum != 0)
X			opnum = Prenum;
X		startop = *Curschar;
X		operator = DELETE;
X		break;
X
X	case 'c':
X		if (operator == CHANGE)		/* handle 'cc' */
X			goto lineop;
X		if (Prenum != 0)
X			opnum = Prenum;
X		startop = *Curschar;
X		operator = CHANGE;
X		break;
X
X	case 'y':
X		if (operator == YANK)		/* handle 'yy' */
X			goto lineop;
X		if (Prenum != 0)
X			opnum = Prenum;
X		startop = *Curschar;
X		operator = YANK;
X		break;
X
X	case '>':
X		if (operator == RSHIFT)		/* handle >> */
X			goto lineop;
X		if (Prenum != 0)
X			opnum = Prenum;
X		startop = *Curschar;
X		operator = RSHIFT;
X		break;
X
X	case '<':
X		if (operator == LSHIFT)		/* handle << */
X			goto lineop;
X		if (Prenum != 0)
X			opnum = Prenum;
X		startop = *Curschar;	/* save current position */
X		operator = LSHIFT;
X		break;
X
X	case '!':
X		if (operator == FILTER)		/* handle '!!' */
X			goto lineop;
X		if (Prenum != 0)
X			opnum = Prenum;
X		startop = *Curschar;
X		operator = FILTER;
X		break;
X
X	case 'p':
X		doput(FORWARD);
X		break;
X
X	case 'P':
X		doput(BACKWARD);
X		break;
X
X	/*
X	 * Abbreviations
X	 */
X	case 'D':
X		stuffin("d$");
X		break;
X
X	case 'Y':
X		if (Prenum)
X			stuffnum(Prenum);
X		stuffin("yy");
X		break;
X
X	case 'C':
X		stuffin("c$");
X		break;
X
X	case 's':				/* substitute characters */
X		if (Prenum)
X			stuffnum(Prenum);
X		stuffin("c.");
X		break;
X
X	/*
X	 * Marks
X	 */
X	case 'm':
X		CLEAROP;
X		if (!setmark(vgetc()))
X			beep();
X		break;
X
X	case '\'':
X		flag = TRUE;
X		/* fall through */
X
X	case '`':
X		{
X			LPTR	mtmp, *mark;
X
X			mark = getmark(vgetc());
X			if (mark == NULL) {
X				beep();
X				CLEAROP;
X			} else {
X				mtmp = *mark;
X				setpcmark();
X				*Curschar = mtmp;
X				if (flag)
X					beginline(TRUE);
X			}
X			mtype = flag ? MLINE : MCHAR;
X			mincl = FALSE;		/* ignored if not MCHAR */
X			set_want_col = TRUE;
X		}
X		break;
X
X	default:
X		CLEAROP;
X		beep();
X		break;
X	}
X
X	/*
X	 * If an operation is pending, handle it...
X	 */
X	if (finish_op) {		/* we just finished an operator */
X		if (operator == NOP)	/* ... but it was cancelled */
X			return;
X
X		switch (operator) {
X
X		case LSHIFT:
X		case RSHIFT:
X			doshift(operator, c, nchar, Prenum);
X			break;
X
X		case DELETE:
X			dodelete(c, nchar, Prenum);
X			break;
X
X		case YANK:
X			(void) doyank();	/* no redo on yank... */
X			break;
X
X		case CHANGE:
X			dochange(c, nchar, Prenum);
X			break;
X
X		case FILTER:
X			dofilter(c, nchar, Prenum);
X			break;
X
X#ifdef	TILDEOP
X		case TILDE:
X			dotilde(c, nchar, Prenum);
X			break;
X#endif
X
X		default:
X			beep();
X		}
X		operator = NOP;
X	}
X}
!EOR!
echo extracting - ops.c
sed 's/^X//' > ops.c << '!EOR!'
X/* $Header: /nw/tony/src/stevie/src/RCS/ops.c,v 1.5 89/08/06 09:50:42 tony Exp $
X *
X * Contains routines that implement the operators in vi. Everything in this
X * file is called only from code in normal.c
X */
X
X#include "stevie.h"
X#include "ops.h"
X
X/*
X * doshift - handle a shift operation
X */
Xvoid
Xdoshift(op, c1, c2, num)
Xint	op;
Xchar	c1, c2;
Xint	num;
X{
X	void	tabinout();
X	LPTR	top, bot;
X	int	nlines;
X	char	opchar;
X
X	top = startop;
X	bot = *Curschar;
X
X	if (lt(&bot, &top))
X		pswap(&top, &bot);
X
X	u_save(top.linep->prev, bot.linep->next);
X
X	nlines = cntllines(&top, &bot);
X	*Curschar = top;
X	tabinout((op == LSHIFT), nlines);
X
X	/* construct Redo buff */
X	opchar = (op == LSHIFT) ? '<' : '>';
X	if (num != 0)
X		sprintf(Redobuff, "%c%d%c%c", opchar, num, c1, c2);
X	else
X		sprintf(Redobuff, "%c%c%c", opchar, c1, c2);
X
X	/*
X	 * The cursor position afterward is the prior of the two positions.
X	 */
X	*Curschar = top;
X
X	/*
X	 * If we were on the last char of a line that got shifted left,
X	 * then move left one so we aren't beyond the end of the line
X	 */
X	if (gchar(Curschar) == NUL && Curschar->index > 0)
X		Curschar->index--;
X
X	updatescreen();
X
X	if (nlines > P(P_RP))
X		smsg("%d lines %ced", nlines, opchar);
X}
X
X/*
X * dodelete - handle a delete operation
X */
Xvoid
Xdodelete(c1, c2, num)
Xchar	c1, c2;
Xint	num;
X{
X	LPTR	top, bot;
X	int	nlines;
X	int	botindex;
X	register int	n;
X
X	/*
X	 * Do a yank of whatever we're about to delete. If there's too much
X	 * stuff to fit in the yank buffer, then get a confirmation before
X	 * doing the delete. This is crude, but simple. And it avoids doing
X	 * a delete of something we can't put back if we want.
X	 */
X	if (!doyank()) {
X		msg("yank buffer exceeded: press <y> to delete anyway");
X		if (vgetc() != 'y') {
X			msg("delete aborted");
X			*Curschar = startop;
X			return;
X		}
X	}
X
X	top = startop;
X	bot = *Curschar;
X
X	if (lt(&bot, &top))
X		pswap(&top, &bot);
X
X	u_save(top.linep->prev, bot.linep->next);
X	/* Don't leave even the potential for orphan marks */
X	clrmark (top.linep);
X
X	nlines = cntllines(&top, &bot);
X	*Curschar = top;
X	cursupdate();
X
X	if (mtype == MLINE) {
X		delline(nlines, TRUE);
X	} else {
X		botindex = -1;
X		if (!mincl) {
X			botindex = bot.index;	/* where it WAS */
X			if (bot.index != 0)
X				dec(&bot);
X		}
X
X		if (top.linep == bot.linep) {		/* del. within line */
X			n = bot.index - top.index + 1;
X			while (n--)
X				if (!delchar(TRUE))
X					break;
X		} else {				/* del. between lines */
X			n = Curschar->index;
X			while (Curschar->index >= n)
X				if (!delchar(TRUE))
X					break;
X
X			top = *Curschar;
X			*Curschar = *nextline(Curschar);
X			delline(nlines-2, TRUE);
X			Curschar->index = 0;
X			n = bot.index + 1;
X			while (n-- && botindex)
X				if (!delchar(TRUE))
X					break;
X			*Curschar = top;
X			(void) dojoin(FALSE);
X			oneright();	/* we got bumped left up above */
X		}
X	}
X
X	/* construct Redo buff */
X	if (num != 0)
X		sprintf(Redobuff, "d%d%c%c", num, c1, c2);
X	else
X		sprintf(Redobuff, "d%c%c", c1, c2);
X
X	if (mtype == MCHAR && nlines == 1)
X		updateline();
X	else
X		updatescreen();
X
X	if (nlines > P(P_RP))
X		smsg("%d fewer lines", nlines);
X}
X
X/*
X * dofilter - handle a filter operation
X */
X
X#define	ITMP	"viXXXXXX"
X#define	OTMP	"voXXXXXX"
X
Xstatic	char	itmp[32];
Xstatic	char	otmp[32];
X
X
X/*
X * dofilter - filter lines through a command given by the user
X *
X * We use temp files and the system() routine here. This would normally
X * be done using pipes on a UNIX machine, but this is more portable to
X * the machines we usually run on. The system() routine needs to be able
X * to deal with redirection somehow, and should handle things like looking
X * at the PATH env. variable, and adding reasonable extensions to the
X * command name given by the user. All reasonable versions of system()
X * do this.
X */
Xvoid
Xdofilter(c1, c2, num)
Xchar	c1, c2;
Xint	num;
X{
X	char	*mktemp();
X	static	char	*lastcmd = NULL;/* the last thing we did */
X	char	*buff;			/* cmd buffer from getcmdln() */
X	char	cmdln[200];		/* filtering command line */
X	LPTR	top, bot;
X	int	nlines;
X
X	top = startop;
X	bot = *Curschar;
X
X	buff = getcmdln('!');
X
X	if (buff == NULL)	/* user backed out of the command prompt */
X		return;
X
X	if (*buff == '!') {		/* use the 'last' command */
X		if (lastcmd == NULL) {
X			emsg("No previous command");
X			return;
X		}
X		buff = lastcmd;
X	}
X
X	/*
X	 * Remember the current command
X	 */
X	if (lastcmd != NULL)
X		free(lastcmd);
X	lastcmd = strsave(buff);
X
X	if (lt(&bot, &top))
X		pswap(&top, &bot);
X
X	u_save(top.linep->prev, bot.linep->next);
X
X	nlines = cntllines(&top, &bot);
X	*Curschar = top;
X	cursupdate();
X
X	/*
X	 * 1. Form temp file names
X	 * 2. Write the lines to a temp file
X	 * 3. Run the filter command on the temp file
X	 * 4. Read the output of the command into the buffer
X	 * 5. Delete the original lines to be filtered
X	 * 6. Remove the temp files
X	 */
X
X#ifdef	TMPDIR
X	strcpy(itmp, TMPDIR);
X	strcpy(otmp, TMPDIR);
X#else
X	itmp[0] = otmp[0] = NUL;
X#endif
X	strcat(itmp, ITMP);
X	strcat(otmp, OTMP);
X
X	if (mktemp(itmp) == NULL || mktemp(otmp) == NULL) {
X		emsg("Can't get temp file names");
X		return;
X	}
X
X	if (!writeit(itmp, &top, &bot)) {
X		emsg("Can't create input temp file");
X		return;
X	}
X
X	sprintf(cmdln, "%s <%s >%s", buff, itmp, otmp);
X
X	if (system(cmdln) != 0) {
X		emsg("Filter command failed");
X		remove(ITMP);
X		return;
X	}
X
X	if (readfile(otmp, &bot, TRUE)) {
X		emsg("Can't read filter output");
X		return;
X	}
X
X	delline(nlines, TRUE);
X
X	remove(itmp);
X	remove(otmp);
X
X	/* construct Redo buff */
X	if (num != 0)
X		sprintf(Redobuff, "d%d%c%c", num, c1, c2);
X	else
X		sprintf(Redobuff, "d%c%c", c1, c2);
X
X	updatescreen();
X
X	if (nlines > P(P_RP))
X		smsg("%d lines filtered", nlines);
X}
X
X#ifdef	TILDEOP
Xvoid
Xdotilde(c1, c2, num)
Xchar	c1, c2;
Xint	num;
X{
X	LPTR	top, bot;
X	register char	c;
X
X	/* construct Redo buff */
X	if (num != 0)
X		sprintf(Redobuff, "~%d%c%c", num, c1, c2);
X	else
X		sprintf(Redobuff, "~%c%c", c1, c2);
X
X	top = startop;
X	bot = *Curschar;
X
X	if (lt(&bot, &top))
X		pswap(&top, &bot);
X
X	u_save(top.linep->prev, bot.linep->next);
X
X	if (mtype == MLINE) {
X		top.index = 0;
X		bot.index = strlen(bot.linep->s);
X	} else {
X		if (!mincl) {
X			if (bot.index)
X				bot.index--;
X		}
X	}
X
X	for (; ltoreq(&top, &bot) ;inc(&top)) {
X		/*
X		 * Swap case through the range
X		 */
X		c = gchar(&top);
X		if (isalpha(c)) {
X			if (islower(c))
X				c = toupper(c);
X			else
X				c = tolower(c);
X
X			pchar(&top, c);		/* Change current character. */
X			CHANGED;
X		}
X	}
X	*Curschar = startop;
X	updatescreen();
X}
X#endif
X
X/*
X * dochange - handle a change operation
X */
Xvoid
Xdochange(c1, c2, num)
Xchar	c1, c2;
Xint	num;
X{
X	char	sbuf[16];
X	bool_t	doappend;	/* true if we should do append, not insert */
X	bool_t	at_eof;		/* changing through the end of file */
X	LPTR	top, bot;
X
X	top = startop;
X	bot = *Curschar;
X
X	if (lt(&bot, &top))
X		pswap(&top, &bot);
X
X	doappend = endofline(&bot);
X	at_eof = (bot.linep->next == Fileend->linep);
X
X	dodelete(c1, c2, num);
X
X	if (mtype == MLINE) {
X		/*
X		 * If we made a change through the last line of the file,
X		 * then the cursor got backed up, and we need to open a
X		 * new line forward, otherwise we go backward.
X		 */
X		if (at_eof)
X			opencmd(FORWARD, FALSE);
X		else
X			opencmd(BACKWARD, FALSE);
X	} else {
X		if (doappend && !lineempty())
X			inc(Curschar);
X	}
X
X	if (num)
X		sprintf(sbuf, "c%d%c%c", num, c1, c2);
X	else
X		sprintf(sbuf, "c%c%c", c1, c2);
X
X	startinsert(sbuf, mtype == MLINE);
X}
X
X#ifndef	YBSIZE
X#define	YBSIZE	4096
X#endif
X
Xstatic	char	ybuf[YBSIZE];
Xstatic	int	ybtype = MBAD;
X
Xbool_t
Xdoyank()
X{
X	LPTR	top, bot;
X	char	*yptr = ybuf;
X	char	*ybend = &ybuf[YBSIZE-1];
X	int	nlines;
X
X	top = startop;
X	bot = *Curschar;
X
X	if (lt(&bot, &top))
X		pswap(&top, &bot);
X
X	nlines = cntllines(&top, &bot);
X
X	ybtype = mtype;			/* set the yank buffer type */
X
X	if (mtype == MLINE) {
X		top.index = 0;
X		bot.index = strlen(bot.linep->s);
X		/*
X		 * The following statement checks for the special case of
X		 * yanking a blank line at the beginning of the file. If
X		 * not handled right, we yank an extra char (a newline).
X		 */
X		if (dec(&bot) == -1) {
X			ybuf[0] = NUL;
X			if (operator == YANK)
X				*Curschar = startop;
X			return TRUE;
X		}
X	} else {
X		if (!mincl) {
X			if (bot.index)
X				bot.index--;
X			else		/* already first column */
X				bot = *( prevchar (&bot));
X		}
X	}
X
X	for (; ltoreq(&top, &bot) ;inc(&top)) {
X		*yptr = (gchar(&top) != NUL) ? gchar(&top) : NL;
X		if (++yptr >= ybend) {
X			msg("yank too big for buffer");
X			ybtype = MBAD;
X			return FALSE;
X		}
X	}
X
X	*yptr = NUL;
X
X	if (operator == YANK) {	/* restore Curschar if really doing yank */
X		*Curschar = startop;
X
X		if (nlines > P(P_RP))
X			smsg("%d lines yanked", nlines);
X	}
X
X	return TRUE;
X}
X
X/*
X * doput(dir)
X *
X * Put the yank buffer at the current location, using the direction given
X * by 'dir'.
X */
Xvoid
Xdoput(dir)
Xint	dir;
X{
X	void	inslines();
X
X	if (ybtype == MBAD) {
X		beep();
X		return;
X	}
X	
X	u_saveline();
X
X	if (ybtype == MLINE)
X		inslines(Curschar->linep, dir, ybuf);
X	else {
X		/*
X		 * If we did a character-oriented yank, and the buffer
X		 * contains multiple lines, the situation is more complex.
X		 * For the moment, we punt, and pretend the user did a
X		 * line-oriented yank. This doesn't actually happen that
X		 * often.
X		 */
X		if (strchr(ybuf, NL) != NULL)
X			inslines(Curschar->linep, dir, ybuf);
X		else {
X			char	*s;
X			int	len;
X
X			len = strlen(Curschar->linep->s) + strlen(ybuf) + 1;
X			s = alloc((unsigned) len);
X			if (!s)  return;
X			strcpy(s, Curschar->linep->s);
X			if (dir == FORWARD)
X				Curschar->index++;
X			strcpy(s + Curschar->index, ybuf);
X			strcat(s, &Curschar->linep->s[Curschar->index]);
X			free(Curschar->linep->s);
X			Curschar->linep->s = s;
X			Curschar->linep->size = len;
X			updateline();
X		}
X	}
X
X	CHANGED;
X}
X
Xbool_t
Xdojoin(join_cmd)
Xbool_t	join_cmd;		/* handling a real "join" command? */
X{
X	int	scol;		/* save cursor column */
X	int	size;		/* size of the joined line */
X
X	if (nextline(Curschar) == NULL)		/* on last line */
X		return FALSE;
X
X	if (!canincrease(size = strlen(Curschar->linep->next->s)))
X		return FALSE;
X
X	while (oneright())			/* to end of line */
X		;
X
X	strcat(Curschar->linep->s, Curschar->linep->next->s);
X
X	/*
X	 * Delete the following line. To do this we move the cursor
X	 * there briefly, and then move it back. Don't back up if the
X	 * delete made us the last line.
X	 */
X	Curschar->linep = Curschar->linep->next;
X	scol = Curschar->index;
X
X	if (nextline(Curschar) != NULL) {
X		delline(1, TRUE);
X		Curschar->linep = Curschar->linep->prev;
X	} else
X		delline(1, TRUE);
X
X	Curschar->index = scol;
X
X	if (join_cmd)
X		oneright();	/* go to first char. of joined line */
X
X	if (join_cmd && size != 0) {
X		/*
X		 * Delete leading white space on the joined line
X		 * and insert a single space.
X		 */
X		while (gchar(Curschar) == ' ' || gchar(Curschar) == TAB)
X			delchar(TRUE);
X		inschar(' ');
X	}
X
X	return TRUE;
X}
X
Xvoid
Xstartinsert(initstr, startln)
Xchar	*initstr;
Xint	startln;	/* if set, insert point really at start of line */
X{
X	register char	*p, c;
X
X	*Insstart = *Curschar;
X	if (startln)
X		Insstart->index = 0;
X	Ninsert = 0;
X	Insptr = Insbuff;
X	for (p=initstr; (c=(*p++))!='\0'; )
X		*Insptr++ = c;
X
X	if (*initstr == 'R')
X		State = REPLACE;
X	else
X		State = INSERT;
X
X	if (P(P_MO))
X		msg((State == INSERT) ? "Insert Mode" : "Replace Mode");
X}
X/*
X * tabinout(inout,num)
X *
X * If inout==0, add a tab to the begining of the next num lines.
X * If inout==1, delete a tab from the beginning of the next num lines.
X */
Xstatic void
Xtabinout(inout, num)
Xint	inout;
Xint	num;
X{
X	int	ntodo = num;
X	LPTR	*p;
X
X	beginline(FALSE);
X	while (ntodo-- > 0) {
X		beginline(FALSE);
X		if (inout == 0)
X			inschar(TAB);
X		else {
X			if (gchar(Curschar) == TAB)
X				delchar(TRUE);
X		}
X		if ( ntodo > 0 ) {
X			if ((p = nextline(Curschar)) != NULL)
X				*Curschar = *p;
X			else
X				break;
X		}
X	}
X}
X
X/*
X * inslines(lp, dir, buf)
X *
X * Inserts lines in the file from the given buffer. Lines are inserted
X * before or after "lp" according to the given direction flag. Newlines
X * in the buffer result in multiple lines being inserted. The cursor
X * is left on the first of the inserted lines.
X */
Xstatic void
Xinslines(lp, dir, buf)
XLINE	*lp;
Xint	dir;
Xchar	*buf;
X{
X	register char	*cp = buf;
X	register int	len;
X	char	*ep;
X	LINE	*l, *nc = NULL;
X
X	if (dir == BACKWARD)
X		lp = lp->prev;
X
X	do {
X		if ((ep = strchr(cp, NL)) == NULL)
X			len = strlen(cp);
X		else
X			len = ep - cp;
X
X		l = newline(len);
X		if (len != 0)
X			strncpy(l->s, cp, len);
X		l->s[len] = NUL;
X
X		l->next = lp->next;
X		l->prev = lp;
X		lp->next->prev = l;
X		lp->next = l;
X
X		if (nc == NULL)
X			nc = l;
X
X		lp = lp->next;
X
X		cp = ep + 1;
X	} while (ep != NULL);
X
X	if (dir == BACKWARD)	/* fix the top line in case we were there */
X		Filemem->linep = Filetop->linep->next;
X
X	renum();
X
X	updatescreen();
X	Curschar->linep = nc;
X	Curschar->index = 0;
X}
!EOR!


