🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.windows.news
Article #709

NeWS version of elvis (the vi clone) part 3 of 9

#709
From: Mike H.
Date: Fri, 11 Jan 1991 01:57
4650 lines
95581 bytes
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	alias.c
#	atari.c
#	blk.c
#	cmd1.c
#	cmd2.c
#	ctags.c
#	curses.c
#	cut.c
# This archive created: Thu Jan 10 17:21:07 1991
export PATH; PATH=/bin:$PATH
if test -f 'alias.c'
then
	echo shar: will not over-write existing file "'alias.c'"
else
cat << \SHAR_EOF > 'alias.c'
/* alias.c */

/* Author:
 *		Peter Reinig
 *		Universitaet Kaiserslautern
 *		Postfach 3049
 *		7650 Kaiserslautern
 *		W. Germany
 *		reinig@physik.uni-kl.de
 */

/* This tiny program executes elvis with the flags that are appropriate
 * for a given command name.  This program is used only on systems that
 * don't allow UNIX-style file links.
 *
 * The benefit of this program is: instead of having 5 copies of elvis
 * on your disk, you only need one copy of elvis and 4 copies of this
 * little program.
 */

#include <stdio.h>
#include "config.h"

#if OSK
#define ELVIS	"/dd/usr/cmds/elvis"
#else
#define ELVIS	"elvis"
#endif

extern char **environ;
extern int errno;
extern char *malloc();

main(argc, argv)
	int	argc;
	char	*argv[];
{
	int	pid, i, j;
	int	letter;
	char	**argblk;
#if OSK
	extern int chainc();
#endif

	/* allocate enough space for a copy of the argument list, plus a
	 * terminating NULL, plus maybe an added flag.
	 */
	argblk = (char **) malloc((argc + 2) * sizeof(char *));
	if (!argblk)
	{
#if OSK
		_errmsg(errno, "Can't get enough memory\n");
#else
		perror(argv[0]);
#endif
		exit(2);
	}

	/* find the last letter in the invocation name of this program */
	i = strlen(argv[0]);
#if MSDOS || TOS
	/* we almost certainly must bypass ".EXE" or ".TTP" from argv[0] */
	if (i > 4 && argv[0][i - 4] == '.')
		i -= 4;
#endif
	letter = argv[0][i - 1];

	/* copy argv to argblk, possibly inserting a flag such as "-R" */
	argblk[0] = ELVIS;
	i = j = 1;
	switch (letter)
	{
	  case 'w':			/* "view" */
	  case 'W':
		argblk[i++] = "-R";
		break;
#if !OSK
	  case 'x':			/* "ex" */
	  case 'X':
		argblk[i++] = "-e";
		break;
#endif
	  case 't':			/* "input" */
	  case 'T':
		argblk[i++] = "-i";
		break;
	}
	while (j < argc)
	{
		argblk[i++] = argv[j++];
	}
	argblk[i] = (char *)0;

	/* execute the real ELVIS program */
#if OSK
	pid = os9exec(chainc, argblk[0], argblk, environ, 0, 0, 3);
	fprintf(stderr, "%s: cannot execute\n", argblk[0]);
#else
	(void)execvp(argblk[0], argblk);
	perror(ELVIS);
#endif
}
SHAR_EOF
fi # end of overwriting check
if test -f 'atari.c'
then
	echo shar: will not over-write existing file "'atari.c'"
else
cat << \SHAR_EOF > 'atari.c'
/* atari.c */

/* Author:
 *	Guntram Blohm
 *	Buchenstrasse 19
 *	7904 Erbach, West Germany
 *	Tel. ++49-7305-6997
 *	sorry - no regular network connection
 */

/*
 * This file contains the 'standard' functions which are not supported
 * by Atari/Mark Williams, and some other TOS-only requirements.
 */
 
#include "config.h"
#include "vi.h"

#if TOS
#include <osbind.h>

/* vi uses mode==0 only ... */
int access(file, mode)
	char *file;
{
	int fd=Fopen(file, 0);
	if (fd<0)
		return -1;
	Fclose(fd);
	return 0;
}

char *mktemp(template)
	char *template;
{
	return template;
}

/* read -- text mode, compress \r\n to \n
 * warning: might fail when maxlen==1 and at eol
 */

int tread(fd, buf, maxlen)
	int fd;
	char *buf;
	int maxlen;
{
	int i, j, nread=read(fd, buf, (unsigned)maxlen);

	if (nread && buf[nread-1]=='\r')
	{	nread--;
		lseek(fd, -1l, 1);
	}
	for (i=j=0; j<nread; i++,j++)
	{	if (buf[j]=='\r' && buf[j+1]=='\n')
			j++;
		buf[i]=buf[j];
	}
	return i;
}

int twrite(fd, buf, maxlen)
	int fd;
	char *buf;
	int maxlen;
{
	int i, j, nwritten=0, hadnl=0;
	char writbuf[BLKSIZE];

	for (i=j=0; j<maxlen; )
	{
		if ((writbuf[i++]=buf[j++])=='\n')
		{	writbuf[i-1]='\r';
			if (i<BLKSIZE)
				writbuf[i++]='\n';
			else
				hadnl=1;
		}
		if (i==BLKSIZE)
		{
			write(fd, writbuf, (unsigned)i);
			i=0;
		}
		if (hadnl)
		{
			writbuf[i++]='\n';
			hadnl=0;
		}
	}
	if (i)
		write(fd, writbuf, (unsigned)i);
	return j;
}
#endif
SHAR_EOF
fi # end of overwriting check
if test -f 'blk.c'
then
	echo shar: will not over-write existing file "'blk.c'"
else
cat << \SHAR_EOF > 'blk.c'
/* blk.c */

/* Author:
 *	Steve Kirkendall
 *	14407 SW Teal Blvd. #C
 *	Beaverton, OR 97005
 *	kirkenda@cs.pdx.edu
 */


/* This file contains the functions that get/put blocks from the temp file.
 * It also contains the "do" and "undo" functions.
 */

#include "config.h"
#include "vi.h"

#ifndef NBUFS
# define NBUFS	5		/* must be at least 3 -- more is better */
#endif

extern long lseek();

/*------------------------------------------------------------------------*/

BLK		hdr;		/* buffer for the header block */

static int	b4cnt;		/* used to count context of beforedo/afterdo */
static struct _blkbuf
{
	BLK		buf;		/* contents of a text block */
	unsigned short	logical;	/* logical block number */
	int		dirty;		/* must the buffer be rewritten? */
}
		blk[NBUFS],	/* buffers for text[?] blocks */
		*toonew,	/* buffer which shouldn't be recycled yet */
		*newtoo,	/* another buffer which should be recycled */
		*recycle = blk;	/* next block to be recycled */





/* This function wipes out all buffers */
void blkinit()
{
	int	i;

	for (i = 0; i < NBUFS; i++)
	{
		blk[i].logical = 0;
		blk[i].dirty = FALSE;
	}
	for (i = 0; i < MAXBLKS; i++)
	{
		hdr.n[i] = 0;
	}
}

/* This function allocates a buffer and fills it with a given block's text */
BLK *blkget(logical)
	int	logical;	/* logical block number to fetch */
{
	REG struct _blkbuf	*this;	/* used to step through blk[] */
	REG int	i;

	/* if logical is 0, just return the hdr buffer */
	if (logical == 0)
	{
		return &hdr;
	}

	/* see if we have that block in mem already */
	for (this = blk; this < &blk[NBUFS]; this++)
	{
		if (this->logical == logical)
		{
			newtoo = toonew;
			toonew = this;
			return &this->buf;
		}
	}

	/* choose a block to be recycled */
	do
	{
		this = recycle++;
		if (recycle == &blk[NBUFS])
		{
			recycle = blk;
		}
	} while (this == toonew || this == newtoo);

	/* if it contains a block, flush that block */
	blkflush(this);

	/* fill this buffer with the desired block */
	this->logical = logical;
	if (hdr.n[logical])
	{
		/* it has been used before - fill it from tmp file */
		lseek(tmpfd, (long)hdr.n[logical] * (long)BLKSIZE, 0);
		if (read(tmpfd, this->buf.c, (unsigned)BLKSIZE) != BLKSIZE)
		{
			msg("Error reading back from tmp file!");
		}
	}
	else
	{
		/* it is new - zero it */
		for (i = 0; i < BLKSIZE; i++)
		{
			this->buf.c[i] = 0;
		}
	}

	/* This isn't really a change, but it does potentially invalidate
	 * the kinds of shortcuts that the "changes" variable is supposed
	 * to protect us from... so count it as a change.
	 */
	changes++;

	/* mark it as being "not dirty" */
	this->dirty = 0;

	/* return it */
	newtoo = toonew;
	toonew = this;
	return &this->buf;
}



/* This function writes a block out to the temporary file */
void blkflush(this)
	REG struct _blkbuf	*this;	/* the buffer to flush */
{
	long		seekpos;	/* seek position of the new block */
	unsigned short	physical;	/* physical block number */

	/* if its empty (an orphan blkadd() maybe?) then make it dirty */
	if (this->logical && !*this->buf.c)
	{
		blkdirty(&this->buf);
	}

	/* if it's an empty buffer or a clean version is on disk, quit */
	if (!this->logical || hdr.n[this->logical] && !this->dirty)
	{
		return;
	}

	/* find a free place in the file */
#ifndef NO_RECYCLE
	seekpos = allocate();
	lseek(tmpfd, seekpos, 0);
#else
	seekpos = lseek(tmpfd, 0L, 2);
#endif
	physical = seekpos / BLKSIZE;

	/* put the block there */
	if (write(tmpfd, this->buf.c, (unsigned)BLKSIZE) != BLKSIZE)
	{
		msg("Trouble writing to tmp file");
	}
	this->dirty = FALSE;

	/* update the header so it knows we put it there */
	hdr.n[this->logical] = physical;
}


/* This function sets a block's "dirty" flag or deletes empty blocks */
void blkdirty(bp)
	BLK	*bp;	/* buffer returned by blkget() */
{
	REG int		i, j;
	REG char	*scan;
	REG int		k;

	/* find the buffer */
	for (i = 0; i < NBUFS && bp != &blk[i].buf; i++)
	{
	}
#ifdef DEBUG
	if (i >= NBUFS)
	{
		msg("blkdirty() called with unknown buffer at 0x%lx", bp);
		return;
	}
	if (blk[i].logical == 0)
	{
		msg("blkdirty called with freed buffer");
		return;
	}
#endif

	/* if this block ends with line# INFINITY, then it must have been
	 * allocated unnecessarily during tmpstart().  Forget it.
	 */
	if (lnum[blk[i].logical] == INFINITY)
	{
#ifdef DEBUG
		if (blk[i].buf.c[0])
		{
			msg("bkldirty called with non-empty extra BLK");
		}
#endif
		blk[i].logical = 0;
		blk[i].dirty = FALSE;
		return;
	}

	/* count lines in this block */
	for (j = 0, scan = bp->c; *scan && scan < bp->c + BLKSIZE; scan++)
	{
		if (*scan == '\n')
		{
			j++;
		}
	}

	/* adjust lnum, if necessary */
	k = blk[i].logical;
	j += (lnum[k - 1] - lnum[k]);
	if (j != 0)
	{
		nlines += j;
		while (k < MAXBLKS && lnum[k] != INFINITY)
		{
			lnum[k++] += j;
		}
	}

	/* if it still has text, mark it as dirty */
	if (*bp->c)
	{
		blk[i].dirty = TRUE;
	}
	else /* empty block, so delete it */
	{
		/* adjust the cache */
		k = blk[i].logical;
		for (j = 0; j < NBUFS; j++)
		{
			if (blk[j].logical >= k)
			{
				blk[j].logical--;
			}
		}

		/* delete it from hdr.n[] and lnum[] */
		blk[i].logical = 0;
		blk[i].dirty = FALSE;
		while (k < MAXBLKS - 1)
		{
			hdr.n[k] = hdr.n[k + 1];
			lnum[k] = lnum[k + 1];
			k++;
		}
		hdr.n[MAXBLKS - 1] = 0;
		lnum[MAXBLKS - 1] = INFINITY;
	}
}


/* insert a new block into hdr, and adjust the cache */
BLK *blkadd(logical)
	int	logical;	/* where to insert the new block */
{
	REG int	i;

	/* adjust hdr and lnum[] */
	for (i = MAXBLKS - 1; i > logical; i--)
	{
		hdr.n[i] = hdr.n[i - 1];
		lnum[i] = lnum[i - 1];
	}
	hdr.n[logical] = 0;
	lnum[logical] = lnum[logical - 1];

	/* adjust the cache */
	for (i = 0; i < NBUFS; i++)
	{
		if (blk[i].logical >= logical)
		{
			blk[i].logical++;
		}
	}

	/* return the new block, via blkget() */
	return blkget(logical);
}


/* This function forces all dirty blocks out to disk */
void blksync()
{
	int	i;

	for (i = 0; i < NBUFS; i++)
	{
		/* blk[i].dirty = TRUE; */
		blkflush(&blk[i]);
	}
	if (*o_sync)
	{
		sync();
	}
}

/*------------------------------------------------------------------------*/

static MARK	undocurs;	/* where the cursor should go if undone */
static long	oldnlines;
static long	oldlnum[MAXBLKS];


/* This function should be called before each command that changes the text.
 * It defines the state that undo() will reset the file to.
 */
void beforedo(forundo)
	int		forundo;	/* boolean: is this for an undo? */
{
	REG int		i;
	REG long	l;

	/* if this is a nested call to beforedo, quit! Use larger context */
	if (b4cnt++ > 0)
	{
		return;
	}

	/* force all block buffers to disk */
	blksync();

#ifndef NO_RECYCLE
	/* perform garbage collection on blocks from tmp file */
	garbage();
#endif

	/* force the header out to disk */
	lseek(tmpfd, 0L, 0);
	if (write(tmpfd, hdr.c, (unsigned)BLKSIZE) != BLKSIZE)
	{
		msg("Trouble writing header to tmp file ");
	}

	/* copy or swap oldnlines <--> nlines, oldlnum <--> lnum */
	if (forundo)
	{
		for (i = 0; i < MAXBLKS; i++)
		{
			l = lnum[i];
			lnum[i] = oldlnum[i];
			oldlnum[i] = l;
		}
		l = nlines;
		nlines = oldnlines;
		oldnlines = l;
	}
	else
	{
		for (i = 0; i < MAXBLKS; i++)
		{
			oldlnum[i] = lnum[i];
		}
		oldnlines = nlines;
	}

	/* save the cursor position */
	undocurs = cursor;

	/* upon return, the calling function continues and makes changes... */
}

/* This function marks the end of a (nested?) change to the file */
void afterdo()
{
	if (--b4cnt)
	{
		/* after abortdo(), b4cnt may decribe nested beforedo/afterdo
		 * pairs incorrectly.  If it is decremented to often, then
		 * keep b4cnt sane but don't do anything else.
		 */
		if (b4cnt < 0)
			b4cnt = 0;

		return;
	}

	/* make sure the cursor wasn't left stranded in deleted text */
	if (markline(cursor) > nlines)
	{
		cursor = MARK_LAST;
	}
	/* NOTE: it is still possible that markidx(cursor) is after the
	 * end of a line, so the Vi mode will have to take care of that
	 * itself */

	/* if a significant change has been made to this file, then set the
	 * MODIFIED flag.
	 */
	if (significant)
	{
		setflag(file, MODIFIED);
	}	
}

/* This function cuts short the current set of changes.  It is called after
 * a SIGINT.
 */
void abortdo()
{
	/* finish the operation immediately. */
	if (b4cnt > 0)
	{
		b4cnt = 1;
		afterdo();
	}

	/* in visual mode, the screen is probably screwed up */
	if (mode == MODE_COLON)
	{
		mode = MODE_VI;
	}
	if (mode == MODE_VI)
	{
		redraw(MARK_UNSET, FALSE);
	}
}

/* This function discards all changes made since the last call to beforedo() */
int undo()
{
	BLK		oldhdr;

	/* if beforedo() has never been run, fail */
	if (!tstflag(file, MODIFIED))
	{
		msg("You haven't modified this file yet.");
		return FALSE;
	}

	/* read the old header form the tmp file */
	lseek(tmpfd, 0L, 0);
	if (read(tmpfd, oldhdr.c, (unsigned)BLKSIZE) != BLKSIZE)
	{
		msg("Trouble rereading the old header from tmp file");
	}

	/* "do" the changed version, so we can undo the "undo" */
	cursor = undocurs;
	beforedo(TRUE);
	afterdo();

	/* wipe out the block buffers - we can't assume they're correct */
	blkinit();

	/* use the old header -- and therefore the old text blocks */
	hdr = oldhdr;

	/* This is a change */
	changes++;

	return TRUE;
}
SHAR_EOF
fi # end of overwriting check
if test -f 'cmd1.c'
then
	echo shar: will not over-write existing file "'cmd1.c'"
else
cat << \SHAR_EOF > 'cmd1.c'
/* cmd1.c */

/* Author:
 *	Steve Kirkendall
 *	14407 SW Teal Blvd. #C
 *	Beaverton, OR 97005
 *	kirkenda@cs.pdx.edu
 */


/* This file contains some of the EX commands - mostly ones that deal with
 * files, options, etc. -- anything except text.
 */

#include "config.h"
#include <ctype.h>
#include "vi.h"
#include "regexp.h"

#if MSDOS
#define	DATE __DATE__
#endif

#ifdef DEBUG
/* print the selected lines with info on the blocks */
/*ARGSUSED*/
void cmd_debug(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	REG char	*scan;
	REG long	l;
	REG int		i;
	int		len;

	/* scan lnum[] to determine which block its in */
	l = markline(frommark);
	for (i = 1; l > lnum[i]; i++)
	{
	}

	do
	{
		/* fetch text of the block containing that line */
		scan = blkget(i)->c;

		/* calculate its length */
		if (scan[BLKSIZE - 1])
		{
			len = BLKSIZE;
		}
		else
		{
			len = strlen(scan);
		}

		/* print block stats */
		msg("##### hdr[%d]=%d, lnum[%d-1]=%ld, lnum[%d]=%ld (%ld lines)",
			i, hdr.n[i], i, lnum[i-1], i, lnum[i], lnum[i] - lnum[i - 1]);
		msg("##### len=%d, buf=0x%lx, %sdirty",
			len, scan, ((int *)scan)[MAXBLKS + 1] ? "" : "not ");
		if (bang)
		{
			while (--len >= 0)
			{
				addch(*scan);
				scan++;
			}
		}
		exrefresh();

		/* next block */
		i++;
	} while (i < MAXBLKS && lnum[i] && lnum[i - 1] < markline(tomark));
}


/* This function checks a lot of conditions to make sure they aren't screwy */
/*ARGSUSED*/
void cmd_validate(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	char	*scan;
	int	i;
	int	nlcnt;	/* used to count newlines */
	int	len;	/* counts non-NUL characters */

	/* check lnum[0] */
	if (lnum[0] != 0L)
	{
		msg("lnum[0] = %ld", lnum[0]);
	}

	/* check each block */
	for (i = 1; lnum[i] <= nlines; i++)
	{
		scan = blkget(i)->c;
		if (scan[BLKSIZE - 1])
		{
			msg("block %d has no NUL at the end", i);
		}
		else
		{
			for (nlcnt = len = 0; *scan; scan++, len++)
			{
				if (*scan == '\n')
				{
					nlcnt++;
				}
			}
			if (scan[-1] != '\n')
			{
				msg("block %d doesn't end with '\\n' (length %d)", i, len);
			}
			if (bang || nlcnt != lnum[i] - lnum[i - 1])
			{
				msg("block %d (line %ld?) has %d lines, but should have %ld",
					i, lnum[i - 1] + 1L, nlcnt, lnum[i] - lnum[i - 1]);
			}
		}
		exrefresh();
	}

	/* check lnum again */
	if (lnum[i] != INFINITY)
	{
		msg("hdr.n[%d] = %d, but lnum[%d] = %ld",
			i, hdr.n[i], i, lnum[i]);
	}

	msg("# = \"%s\", %% = \"%s\"", prevorig, origname);
}
#endif /* DEBUG */


/*ARGSUSED*/
void cmd_mark(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	/* validate the name of the mark */
	if (!extra || *extra < 'a' || *extra > 'z' || extra[1])
	{
		msg("Invalid mark name");
		return;
	}

	mark[*extra - 'a'] = tomark;
}

/*ARGSUSED*/
void cmd_write(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	int		fd;
	int		append;	/* boolean: write in "append" mode? */
	REG long	l;
	REG char	*scan;
	REG int		i;

	/* if all lines are to be written, use tmpsave() */
	if (frommark == MARK_FIRST && tomark == MARK_LAST)
	{
		tmpsave(extra, bang);
		return;
	}

	/* see if we're going to do this in append mode or not */
	append = FALSE;
	if (extra[0] == '>' && extra[1] == '>')
	{
		extra += 2;
		append = TRUE;
	}

	/* either the file must not exist, or we must have a ! or be appending */
	if (access(extra, 0) == 0 && !bang && !append)
	{
		msg("File already exists - Use :w! to overwrite");
		return;
	}

	/* else do it line-by-line, like cmd_print() */
	if (append)
	{
#ifdef O_APPEND
		fd = open(extra, O_WRONLY|O_APPEND);
#else
		fd = open(extra, O_WRONLY);
		if (fd >= 0)
		{
			lseek(fd, 0L, 2);
		}
#endif
	}
	else
	{
		fd = -1; /* so we know the file isn't open yet */
	}

	if (fd < 0)
	{
		fd = creat(extra, FILEPERMS);
		if (fd < 0)
		{
			msg("Can't write to \"%s\"", extra);
			return;
		}
	}
	for (l = markline(frommark); l <= markline(tomark); l++)
	{
		/* get the next line */
		scan = fetchline(l);
		i = strlen(scan);
		scan[i++] = '\n';

		/* print the line */
		twrite(fd, scan, i);
	}
	close(fd);
}	


/*ARGSUSED*/
void cmd_shell(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	static char	prevextra[80];

	/* special case: ":sh" means ":!sh" */
	if (cmd == CMD_SHELL)
	{
		extra = o_shell;
		frommark = tomark = 0L;
	}

	/* if extra is "!", substute previous command */
	if (*extra == '!')
	{
		if (!*prevextra)
		{
			msg("No previous shell command to substitute for '!'");
			return;
		}
		extra = prevextra;
	}
	else if (cmd == CMD_BANG && strlen(extra) < sizeof(prevextra) - 1)
	{
		strcpy(prevextra, extra);
	}

	/* if no lines were specified, just run the command */
	Suspend_curses();
	if (frommark == 0L)
	{
		system(extra);
	}
	else /* pipe lines from the file through the command */
	{
		filter(frommark, tomark, extra);
	}

	/* resume curses quietly for MODE_EX, but noisily otherwise */
	Resume_curses(mode == MODE_EX);
}


/*ARGSUSED*/
void cmd_global(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;	/* rest of the command line */
{
	char	*cmdptr;	/* the command from the command line */
	char	cmdln[100];	/* copy of the command from the command line */
	char	*line;		/* a line from the file */
	long	l;		/* used as a counter to move through lines */
	long	lqty;		/* quantity of lines to be scanned */
	long	nchanged;	/* number of lines changed */
	regexp	*re;		/* the compiled search expression */

	/* can't nest global commands */
	if (doingglobal)
	{
		msg("Can't nest global commands.");
		rptlines = -1L;
		return;
	}

	/* ":g! ..." is the same as ":v ..." */
	if (bang)
	{
		cmd = CMD_VGLOBAL;
	}

	/* make sure we got a search pattern */
	if (*extra != '/' && *extra != '?')
	{
		msg("Usage: %c /regular expression/ command", cmd == CMD_GLOBAL ? 'g' : 'v');
		return;
	}

	/* parse & compile the search pattern */
	cmdptr = parseptrn(extra);
	if (!extra[1])
	{
		msg("Can't use empty regular expression with '%c' command", cmd == CMD_GLOBAL ? 'g' : 'v');
		return;
	}
	re = regcomp(extra + 1);
	if (!re)
	{
		/* regcomp found & described an error */
		return;
	}

	/* for each line in the range */
	doingglobal = TRUE;
	ChangeText
	{
		/* NOTE: we have to go through the lines in a forward order,
		 * otherwise "g/re/p" would look funny.  *BUT* for "g/re/d"
		 * to work, simply adding 1 to the line# on each loop won't
		 * work.  The solution: count lines relative to the end of
		 * the file.  Think about it.
		 */
		for (l = nlines - markline(frommark),
			lqty = markline(tomark) - markline(frommark) + 1L,
			nchanged = 0L;
		     lqty > 0 && nlines - l >= 0 && nchanged >= 0L;
		     l--, lqty--)
		{
			/* fetch the line */
			line = fetchline(nlines - l);

			/* if it contains the search pattern... */
			if ((!regexec(re, line, 1)) == (cmd != CMD_GLOBAL))
			{
				/* move the cursor to that line */
				cursor = MARK_AT_LINE(nlines - l);

				/* do the ex command (without mucking up
				 * the original copy of the command line)
				 */
				strcpy(cmdln, cmdptr);
				rptlines = 0L;
				doexcmd(cmdln);
				nchanged += rptlines;
			}
		}
	}
	doingglobal = FALSE;

	/* free the regexp */
	free(re);

	/* Reporting...*/
	rptlines = nchanged;
}


/*ARGSUSED*/
void cmd_file(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
#ifndef CRUNCH
	/* if we're given a new filename, use it as this file's name */
	if (extra && *extra)
	{
		strcpy(origname, extra);
	}
#endif
	if (cmd == CMD_FILE)
	{
		msg("\"%s\" %s%s %ld lines,  line %ld [%ld%%]",
			*origname ? origname : "[NO FILE]",
			tstflag(file, MODIFIED) ? "[MODIFIED]" : "",
			tstflag(file, READONLY) ? "[READONLY]" : "",
			nlines,
			markline(frommark),
			markline(frommark) * 100 / nlines);
	}
	else if (markline(frommark) == markline(tomark))
	{
		msg("%ld", markline(frommark));
	}
	else
	{
		msg("range \"%ld,%ld\" contains %ld lines",
			markline(frommark),
			markline(tomark),
			markline(tomark) - markline(frommark) + 1L);
	}
}


/*ARGSUSED*/
void cmd_edit(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	long	line = 1L;	/* might be set to prevline */

	/* Editing previous file?  Then start at previous line */
	if (!strcmp(extra, prevorig))
	{
		line = prevline;
	}

#ifndef CRUNCH
	/* if we were given an explicit starting line, then start there */
	if (*extra == '+')
	{
		for (extra++, line = 0L; *extra >= '0' && *extra <= '9'; extra++)
		{
			line *= 10L;
			line += (*extra - '0');
		}
		while (isascii(*extra) && isspace(*extra))
		{
			extra++;
		}
	}
#endif /* not CRUNCH */

	/* switch files */
	if (tmpabort(bang))
	{
		Tmpstart(extra);
		if (line <= nlines && line >= 1L)
		{
			cursor = MARK_AT_LINE(line);
		}
	}
	else
	{
		msg("Use edit! to abort changes, or w to save changes");

		/* so we can say ":e!#" next time... */
		strcpy(prevorig, extra);
		prevline = 1L;
	}
}

/* This code is also used for rewind -- GB */

/*ARGSUSED*/
void cmd_next(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	int	i, j;
	char	*scan;
	char	*build;

	/* if extra stuff given, use ":args" to define a new args list */
	if (cmd == CMD_NEXT && extra && *extra)
	{
		cmd_args(frommark, tomark, cmd, bang, extra);
	}

	/* move to the next arg */
	if (cmd == CMD_NEXT)
	{
		i = argno + 1;
	}
	else if (cmd == CMD_PREVIOUS)
	{
		i = argno - 1;
	}
	else /* cmd == CMD_REWIND */
	{
		i = 0;
	}	
	if (i < 0 || i >= nargs)
	{
		msg("No %sfiles to edit", cmd == CMD_REWIND ? "" : "more ");
		return;
	}

	/* find & isolate the name of the file to edit */
	for (j = i, scan = args; j > 0; j--)
	{
		while(!isascii(*scan) || !isspace(*scan))
		{
			scan++;
		}
		while (isascii(*scan) && isspace(*scan))
		{
			scan++;
		}
	}
	for (build = tmpblk.c; *scan && (!isascii(*scan) || !isspace(*scan)); )
	{
		*build++ = *scan++;
	}
	*build = '\0';

	/* switch to the next file */
	if (tmpabort(bang))
	{
		Tmpstart(tmpblk.c);
		argno = i;
	}
	else
	{
		msg("Use :%s! to abort changes, or w to save changes",
			cmd == CMD_NEXT ? "next" :
			cmd == CMD_PREVIOUS ? "previous" :
					"rewind");
	}
}

/* also called from :wq -- always writes back in this case */

/*ARGSUSED*/
void cmd_xit(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	static long	whenwarned;	/* when the user was last warned of extra files */
	int		oldflag;

	/* if there are more files to edit, then warn user */
	if (argno + 1 < nargs && whenwarned != changes && (!bang || cmd != CMD_QUIT))
	{
		msg("More files to edit -- Use \":n\" to go to next file");
		whenwarned = changes;
		return;
	}

	if (cmd == CMD_QUIT)
	{
		if (tmpabort(bang))
		{
			mode = MODE_QUIT;
		}
		else
		{
			msg("Use q! to abort changes, or wq to save changes");
		}
	}
	else
	{
		/* else try to save this file */
		oldflag = tstflag(file, MODIFIED);
		if (cmd == CMD_WQUIT)
			setflag(file, MODIFIED);
		if (tmpend(bang))
		{
			mode = MODE_QUIT;
		}
		else
		{
			msg("Could not save file -- use quit! to abort changes, or w filename");
		}
		if (!oldflag)
			clrflag(file, MODIFIED);
	}
}


/*ARGSUSED*/
void cmd_args(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	char	*scan;
	char	*eow;
	int	col;
	int	arg;
	int	addcols;
	int	scrolled = 0;

	/* if no extra names given, or just current name, then report the args
	 * we have now.
	 */
	if (!extra || !*extra)
	{
		for (scan = args, col=arg=0; *scan; )
		{
			while (*scan && isascii(*scan) && isspace(*scan))
				scan++;
			eow = scan;
			while (*eow && (!isascii(*++eow) || !isspace(*eow)))
				;
			if (arg == argno)
				addcols = 2;
			else
				addcols = 0;	
			if (col+addcols+(int)(eow-scan)+1>=COLS)
			{
				addch('\n');
				scrolled=1;
				col=0;
			}
			else if (arg)
			{	qaddch(' ');
				col++;
			}
			if (arg == argno)
				qaddch('[');
			while (scan < eow)
			{	qaddch(*scan++);
				col++;
			}
			if (arg == argno)
				qaddch(']');	
			arg++;	
			col+=addcols;
		}
		/* write a trailing newline */
		if ((mode == MODE_EX || mode == MODE_COLON || scrolled) && col)
			addch('\n');
		exrefresh();	
	}
	else /* new args list given */
	{
		strcpy(args, extra);
		argno = -1; /* before the first, so :next will go to first */

		/* count the names */
		for (nargs = 0, scan = args; *scan; nargs++)
		{
			while (*scan && (!isascii(*scan) || !isspace(*scan)))
			{
				scan++;
			}
			while (isascii(*scan) && isspace(*scan))
			{
				scan++;
			}
		}
		msg("%d files to edit", nargs);
	}
}


/*ARGSUSED*/
void cmd_cd(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	char	*getenv();

	/* default directory name is $HOME */
	if (!*extra)
	{
		extra = getenv("HOME");
		if (!extra)
		{
			msg("environment variable $HOME not set");
			return;
		}
	}

	/* go to the directory */
	if (chdir(extra) < 0)
	{
		perror(extra);
	}
}


/*ARGSUSED*/
void cmd_map(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	char	*mapto;

	/* "map" with no extra will dump the map table contents */
	if (!*extra)
	{
		dumpkey(bang ? WHEN_VIINP|WHEN_VIREP : WHEN_VICMD);
	}
	else
	{
		/* "extra" is key to map, followed my what it maps to */
		for (mapto = extra; *mapto && *mapto != ' ' && *mapto!= '\t'; mapto++)
		{
		}
		while (*mapto == ' ' || *mapto == '\t')
		{
			*mapto++ = '\0';
		}

		mapkey(extra, mapto, bang ? WHEN_VIINP|WHEN_VIREP : WHEN_VICMD, (char *)0);
	}
}


/*ARGSUSED*/
void cmd_set(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	if (!*extra)
	{
		dumpopts(FALSE);/* "FALSE" means "don't dump all" - only set */
	}
	else if (!strcmp(extra, "all"))
	{
		dumpopts(TRUE);	/* "TRUE" means "dump all" - even unset vars */
	}
	else
	{
		setopts(extra);

		/* That option may have affected the appearence of text */
		changes++;
	}
}

/*ARGSUSED*/
void cmd_tag(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	char	*scan;	/* used to scan through the tmpblk.c */
	char	*cmp;	/* char of tag name we're comparing, or NULL */
	char	*end;	/* marks the end of chars in tmpblk.c */
	int	fd;	/* file descriptor used to read the file */
#ifndef NO_MAGIC
	char	wasmagic; /* preserves the original state of o_magic */
#endif
	static char prevtag[30];

	/* if no tag is given, use the previous tag */
	if (!extra || !*extra)
	{
		if (!*prevtag)
		{
			msg("No previous tag");
			return;
		}
		extra = prevtag;
	}
	else
	{
		strncpy(prevtag, extra, sizeof prevtag);
	}

	/* open the tags file */
	fd = open(TAGS, O_RDONLY);
	if (fd < 0)
	{
		msg("No tags file");
		return;
	}

	/* Hmmm... this would have been a lot easier with <stdio.h> */

	/* find the line with our tag in it */
	for(scan = end = tmpblk.c, cmp = extra; ; scan++)
	{
		/* read a block, if necessary */
		if (scan >= end)
		{
			end = tmpblk.c + tread(fd, tmpblk.c, BLKSIZE);
			scan = tmpblk.c;
			if (scan >= end)
			{
				msg("tag \"%s\" not found", extra);
				close(fd);
				return;
			}
		}

		/* if we're comparing, compare... */
		if (cmp)
		{
			/* matched??? wow! */
			if (!*cmp && *scan == '\t')
			{
				break;
			}
			if (*cmp++ != *scan)
			{
				/* failed! skip to newline */
				cmp = (char *)0;
			}
		}

		/* if we're skipping to newline, do it fast! */
		if (!cmp)
		{
			while (scan < end && *scan != '\n')
			{
				scan++;
			}
			if (scan < end)
			{
				cmp = extra;
			}
		}
	}

	/* found it! get the rest of the line into memory */
	for (cmp = tmpblk.c, scan++; scan < end && *scan != '\n'; )
	{
		*cmp++ = *scan++;
	}
	if (scan == end)
	{
		tread(fd, cmp, BLKSIZE - (cmp - tmpblk.c));
	}

	/* we can close the tags file now */
	close(fd);

	/* extract the filename from the line, and edit the file */
	for (cmp = tmpblk.c; *cmp != '\t'; cmp++)
	{
	}
	*cmp++ = '\0';
	if (strcmp(origname, tmpblk.c) != 0)
	{
		if (!tmpabort(bang))
		{
			msg("Use :tag! to abort changes, or :w to save changes");
			return;
		}
		Tmpstart(tmpblk.c);
	}

	/* move to the desired line (or to line 1 if that fails) */
#ifndef NO_MAGIC
	wasmagic = *o_magic;
	*o_magic = FALSE;
#endif
	cursor = MARK_FIRST;
	linespec(cmp, &cursor);
	if (cursor == MARK_UNSET)
	{
		cursor = MARK_FIRST;
	}
#ifndef NO_MAGIC
	*o_magic = wasmagic;
#endif
}



/*ARGSUSED*/
void cmd_visual(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	mode = MODE_VI;
	msg("");
}





/* describe this version of the program */
/*ARGSUSED*/
void cmd_version(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
#ifndef DATE
	msg("%s", VERSION);
#else
	msg("%s  (%s)", VERSION, DATE);
#endif
#ifdef COMPILED_BY
	msg("Compiled by %s", COMPILED_BY);
#endif
#ifdef CREDIT
	msg("%s", CREDIT);
#endif
#ifdef COPYING
	msg("%s", COPYING);
#endif
}


#ifndef NO_MKEXRC
/* make a .exrc file which describes the current configuration */
/*ARGSUSED*/
void cmd_mkexrc(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	int	fd;

	/* the default name for the .exrc file EXRC */
	if (!*extra)
	{
		extra = EXRC;
	}

	/* create the .exrc file */
	fd = creat(extra, FILEPERMS);
	if (fd < 0)
	{
		msg("Couldn't create a new \"%s\" file", extra);
		return;
	}

	/* save stuff */
	savekeys(fd);
	saveopts(fd);
#ifndef NO_DIGRAPH
	savedigs(fd);
#endif
#ifndef	NO_ABBR
	saveabbr(fd);
#endif

	/* close the file */
	close(fd);
	msg("Created a new \"%s\" file", extra);
}
#endif

#ifndef NO_DIGRAPH
/*ARGSUSED*/
void cmd_digraph(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	do_digraph(bang, extra);
}
#endif


#ifndef NO_ERRLIST 
static char	errfile[256];	/* the name of a file containing an error */
static long	errline;	/* the line number for an error */

/* This static function tries to parse an error message.
 *
 * For most compilers, the first word is taken to be the name of the erroneous
 * file, and the first number after that is taken to be the line number where
 * the error was detected.  The description of the error follows, possibly
 * preceded by an "error ... :" or "warning ... :" label which is skipped.
 *
 * For Coherent, error messages look like "line#: filename: message".
 *
 * For non-error lines, or unparsable error lines, this function returns NULL.
 * Normally, though, it alters errfile and errline, and returns a pointer to
 * the description.
 */
static char *parse_errmsg(text)
	REG char	*text;
{
	REG char	*cpy;
	long		atol();
# if COHERENT || TOS /* any Mark Williams compiler */
	/* Get the line number.  If no line number, then ignore this line. */
	errline = atol(text);
	if (errline == 0L)
		return (char *)0;

	/* Skip to the start of the filename */
	while (*text && *text++ != ':')
	{
	}
	if (!*text++)
		return (char *)0;

	/* copy the filename to errfile */
	for (cpy = errfile; *text && (*cpy++ = *text++) != ':'; )
	{
	}
	if (!*text++)
		return (char *)0;
	cpy[-1] = '\0';

	return text;
# else /* not a Mark Williams compiler */
	char		*errmsg;

	/* the error message is the whole line, by default */
	errmsg = text;

	/* skip leading garbage */
	while (*text && !(isascii(*text) && isalnum(*text)))
	{
		text++;
	}

	/* copy over the filename */
	cpy = errfile;
	while(isascii(*text) && isalnum(*text) || *text == '.')
	{
		*cpy++ = *text++;
	}
	*cpy = '\0';

	/* ignore the name "Error" and filenames that contain a '/' */
	if (*text == '/' || !strcmp(errfile + 1, "rror") || access(errfile, 0) < 0)
	{
		return (char *)0;
	}

	/* skip garbage between filename and line number */
	while (*text && !(isascii(*text) && isdigit(*text)))
	{
		text++;
	}

	/* if the number is part of a larger word, then ignore this line */
	if (*text && isascii(text[-1]) && isalpha(text[-1]))
	{
		return (char *)0;
	}

	/* get the error line */
	errline = 0L;
	while (isascii(*text) && isdigit(*text))
	{
		errline *= 10;
		errline += (*text - '0');
		text++;
	}

	/* any line which lacks a filename or line number should be ignored */
	if (!errfile[0] || !errline)
	{
		return (char *)0;
	}

	/* locate the beginning of the error description */
	while (*text && isascii(*text) && !isspace(*text))
	{
		text++;
	}
	while (*text)
	{
#  ifndef CRUNCH
		/* skip "error #:" and "warning #:" clauses */
		if (!strncmp(text + 1, "rror ", 5)
		 || !strncmp(text + 1, "arning ", 7)
		 || !strncmp(text + 1, "atal error", 10))
		{
			do
			{
				text++;
			} while (*text && *text != ':');
			continue;
		}
#  endif

		/* anything other than whitespace or a colon is important */
		if (!isascii(*text) || (!isspace(*text) && *text != ':'))
		{
			errmsg = text;
			break;
		}

		/* else keep looking... */
		text++;
	}

	return errmsg;
# endif /* not COHERENT */
}

/*ARGSUSED*/
void cmd_errlist(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	static long	endline;/* original number of lines in this file */
	static long	offset;	/* offset of the next line in the errlist file */
	static int	fd = -2;/* fd of the errlist file */
	int		i;
	char		*errmsg;

	/* if a new errlist file is named, open it */
	if (extra && extra[0])
	{
		/* close the old one */
		if (fd >= 0)
		{
			close(fd);
		}

		fd = open(extra, O_RDONLY);
		offset = 0L;
	}
	else if (fd < 0)
	{
		fd = open(ERRLIST, O_RDONLY);
		offset = 0L;
	}

	/* do we have an errlist file now? */
	if (fd < 0)
	{
		msg("There is no errlist file");
		beep();
		return;
	}

	/* find the next error message in the file */
	do
	{
		/* read the next line from the errlist */
		lseek(fd, offset, 0);
		if (tread(fd, tmpblk.c, (unsigned)BLKSIZE) <= 0)
		{
			msg("No more errors");
			beep();
			close(fd);
			return;
		}
		for (i = 0; tmpblk.c[i] != '\n'; i++)
		{
		}
		tmpblk.c[i++] = 0;

		/* look for an error message in the line */
		errmsg = parse_errmsg(tmpblk.c);
		if (!errmsg)
		{
			offset += i;
		}

	} while (!errmsg);

	/* switch to the file containing the error, if this isn't it */
	if (strcmp(origname, errfile))
	{
		if (!tmpabort(bang))
		{
			msg("Use :er! to abort changes, or :w to save changes");
			beep();
			return;
		}
		Tmpstart(errfile);
		endline = nlines;
	}
	else if (endline == 0L)
	{
		endline = nlines;
	}

	/* go to the line where the error was detected */
	cursor = MARK_AT_LINE(errline + (nlines - endline));
	if (cursor > MARK_LAST)
	{
		cursor = MARK_LAST;
	}
	if (mode == MODE_VI)
	{
		redraw(cursor, FALSE);
	}

	/* display the error message */
	if (nlines > endline)
	{
		msg("line %ld(+%ld): %.60s", errline, nlines - endline, errmsg);
	}
	else if (nlines < endline)
	{
		msg("line %ld(-%ld): %.60s", errline, endline - nlines, errmsg);
	}
	else
	{
		msg("line %ld: %.65s", errline, errmsg);
	}

	/* remember where the NEXT error line will start */
	offset += i;
}


/*ARGSUSED*/
void cmd_make(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	BLK	buf;

	/* if the file hasn't been saved, then complain unless ! */
	if (tstflag(file, MODIFIED) && !bang)
	{
		msg("\"%s\" not saved yet", origname);
		return;
	}

	/* build the command */
	sprintf(buf.c, "%s %s %s%s", (cmd == CMD_CC ? o_cc : o_make), extra, REDIRECT, ERRLIST);
	qaddstr(buf.c);
	addch('\n');

	/* run the command, with curses temporarily disabled */
	Suspend_curses();
	system(buf.c);
	Resume_curses(mode == MODE_EX);
	if (mode == MODE_COLON)
		mode = MODE_VI;

	/* run the "errlist" command */
	cmd_errlist(MARK_UNSET, MARK_UNSET, cmd, bang, ERRLIST);
}
#endif


#ifndef NO_ABBR
/*ARGSUSED*/
void cmd_abbr(frommark, tomark, cmd, bang, extra)
	MARK	frommark, tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	do_abbr(extra);
}
#endif
SHAR_EOF
fi # end of overwriting check
if test -f 'cmd2.c'
then
	echo shar: will not over-write existing file "'cmd2.c'"
else
cat << \SHAR_EOF > 'cmd2.c'
/* cmd2.c */

/* Author:
 *	Steve Kirkendall
 *	14407 SW Teal Blvd. #C
 *	Beaverton, OR 97005
 *	kirkenda@cs.pdx.edu
 */


/* This file contains some of the commands - mostly ones that change text */

#include <ctype.h>
#include "config.h"
#include "vi.h"
#include "regexp.h"
#if TOS
# include <stat.h>
#else
# if OSK
#  include "osk.h"
# else
#  include <sys/stat.h>
# endif
#endif


/*ARGSUSED*/
void cmd_substitute(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;	/* rest of the command line */
{
	char	*line;	/* a line from the file */
	regexp	*re;	/* the compiled search expression */
	char	*subst;	/* the substitution string */
	char	*opt;	/* substitution options */
	long	l;	/* a line number */
	char	*s, *d;	/* used during subtitutions */
	char	*conf;	/* used during confirmation */
	long	chline;	/* # of lines changed */
	long	chsub;	/* # of substitutions made */
	static	optp;	/* boolean option: print when done? */
	static	optg;	/* boolean option: substitute globally in line? */
	static	optc;	/* boolean option: confirm before subst? */


	/* for now, assume this will fail */
	rptlines = -1L;

	if (cmd == CMD_SUBAGAIN)
	{
#ifndef NO_MAGIC
		if (*o_magic)
			subst = "~";
		else
#endif
		subst = "\\~";
		re = regcomp("");

		/* if visual "&", then turn off the "p" and "c" options */
		if (bang)
		{
			optp = optc = FALSE;
		}
	}
	else
	{
		/* make sure we got a search pattern */
		if (*extra != '/' && *extra != '?')
		{
			msg("Usage: s/regular expression/new text/");
			return;
		}

		/* parse & compile the search pattern */
		subst = parseptrn(extra);
		re = regcomp(extra + 1);
	}

	/* abort if RE error -- error message already given by regcomp() */
	if (!re)
	{
		return;
	}

	if (cmd == CMD_SUBSTITUTE)
	{
		/* parse the substitution string & find the option string */
		for (opt = subst; *opt && *opt != *extra; opt++)
		{
			if (*opt == '\\' && opt[1])
			{
				opt++;
			}
		}
		if (*opt)
		{
			*opt++ = '\0';
		}

		/* analyse the option string */
		if (!*o_edcompatible)
		{
			optp = optg = optc = FALSE;
		}
		while (*opt)
		{
			switch (*opt++)
			{
			  case 'p':	optp = !optp;	break;
			  case 'g':	optg = !optg;	break;
			  case 'c':	optc = !optc;	break;
			  case ' ':
			  case '\t':			break;
			  default:
				msg("Subst options are p, c, and g -- not %c", opt[-1]);
				return;
			}
		}
	}

	/* if "c" or "p" flag was given, and we're in visual mode, then NEWLINE */
	if ((optc || optp) && mode == MODE_VI)
	{
		addch('\n');
		exrefresh();
	}

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

		/* for each selected line */
		for (l = markline(frommark); l <= markline(tomark); l++)
		{
			/* fetch the line */
			line = fetchline(l);

			/* if it contains the search pattern... */
			if (regexec(re, line, TRUE))
			{
				/* increment the line change counter */
				chline++;

				/* initialize the pointers */
				s = line;
				d = tmpblk.c;

				/* do once or globally ... */
				do
				{
#ifndef CRUNCH
					/* confirm, if necessary */
					if (optc)
					{
						for (conf = line; conf < re->startp[0]; conf++)
							addch(*conf);
						standout();
						for ( ; conf < re->endp[0]; conf++)
							addch(*conf);
						standend();
						for (; *conf; conf++)
							addch(*conf);
						addch('\n');
						exrefresh();
						if (getkey(0) != 'y')
						{
							/* copy accross the original chars */
							while (s < re->endp[0])
								*d++ = *s++;

							/* skip to next match on this line, if any */
							continue;
						}
					}
#endif /* not CRUNCH */

					/* increment the substitution change counter */
					chsub++;

					/* this may be the first line to redraw */
					redrawrange(l, l + 1L, l + 1L);

					/* copy stuff from before the match */
					while (s < re->startp[0])
					{
						*d++ = *s++;
					}
	
					/* substitute for the matched part */
					regsub(re, subst, d);
					s = re->endp[0];
					d += strlen(d);

				} while (optg && regexec(re, s, FALSE));

				/* copy stuff from after the match */
				while (*d++ = *s++)	/* yes, ASSIGNMENT! */
				{
				}

				/* replace the old version of the line with the new */
				d[-1] = '\n';
				d[0] = '\0';
				change(MARK_AT_LINE(l), MARK_AT_LINE(l + 1), tmpblk.c);

				/* if supposed to print it, do so */
				if (optp)
				{
					addstr(tmpblk.c);
					exrefresh();
				}

				/* move the cursor to that line */
				cursor = MARK_AT_LINE(l);
			}
		}
	}

	/* tweak for redrawing */
	mustredraw = TRUE;

	/* free the regexp */
	free(re);

	/* if done from within a ":g" command, then finish silently */
	if (doingglobal)
	{
		rptlines = chline;
		rptlabel = "changed";
		return;
	}

	/* Reporting */
	if (chsub == 0)
	{
		msg("Substitution failed");
	}
	else if (chline >= *o_report)
	{
		msg("%ld substitutions on %ld lines", chsub, chline);
	}
}




/*ARGSUSED*/
void cmd_delete(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	MARK	curs2;	/* an altered form of the cursor */

	/* choose your cut buffer */
	if (*extra == '"')
	{
		extra++;
	}
	if (*extra)
	{
		cutname(*extra);
	}

	/* make sure we're talking about whole lines here */
	frommark = frommark & ~(BLKSIZE - 1);
	tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;

	/* yank the lines */
	cut(frommark, tomark);

	/* if CMD_DELETE then delete the lines */
	if (cmd != CMD_YANK)
	{
		curs2 = cursor;
		ChangeText
		{
			/* delete the lines */
			delete(frommark, tomark);
		}
		if (curs2 > tomark)
		{
			cursor = curs2 - tomark + frommark;
		}
		else if (curs2 > frommark)
		{
			cursor = frommark;
		}
	}
}


/*ARGSUSED*/
void cmd_append(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	long	l;	/* line counter */

	ChangeText
	{
		/* if we're doing a change, delete the old version */
		if (cmd == CMD_CHANGE)
		{
			/* delete 'em */
			cmd_delete(frommark, tomark, cmd, bang, extra);
		}

		/* new lines start at the frommark line, or after it */
		l = markline(frommark);
		if (cmd == CMD_APPEND)
		{
 			l++;
		}

		/* get lines until no more lines, or "." line, and insert them */
		while (vgets('\0', tmpblk.c, BLKSIZE) >= 0)
		{
			addch('\n');
			if (!strcmp(tmpblk.c, "."))
			{
				break;
			}

			strcat(tmpblk.c, "\n");
			add(MARK_AT_LINE(l), tmpblk.c);
			l++;
		}
	}

	/* on the odd chance that we're calling this from vi mode ... */
	redraw(MARK_UNSET, FALSE);
}


/*ARGSUSED*/
void cmd_put(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	/* choose your cut buffer */
	if (*extra == '"')
	{
		extra++;
	}
	if (*extra)
	{
		cutname(*extra);
	}

	/* paste it */
	ChangeText
	{
		cursor = paste(frommark, TRUE, FALSE);
	}
}


/*ARGSUSED*/
void cmd_join(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	long	l;
	char	*scan;
	int	len;	/* length of the new line */

	/* if only one line is specified, assume the following one joins too */
	if (markline(frommark) == nlines)
	{
		msg("Nothing to join with this line");
		return;
	}
	if (markline(frommark) == markline(tomark))
	{
		tomark += BLKSIZE;
	}

	/* get the first line */
	l = markline(frommark);
	strcpy(tmpblk.c, fetchline(l));
	len = strlen(tmpblk.c);

	/* build the longer line */
	while (++l <= markline(tomark))
	{
		/* get the next line */
		scan = fetchline(l);

		/* remove any leading whitespace */
		while (*scan == '\t' || *scan == ' ')
		{
			scan++;
		}

		/* see if the line will fit */
		if (strlen(scan) + len + 3 > BLKSIZE)
		{
			msg("Can't join -- the resulting line would be too long");
			return;
		}

		/* catenate it, with a space (or two) in between */
		if (len >= 1 &&
			(tmpblk.c[len - 1] == '.'
			 || tmpblk.c[len - 1] == '?'
			 || tmpblk.c[len - 1] == '!'))
		{
			 tmpblk.c[len++] = ' ';
		}
		tmpblk.c[len++] = ' ';
		strcpy(tmpblk.c + len, scan);
		len += strlen(scan);
	}
	tmpblk.c[len++] = '\n';
	tmpblk.c[len] = '\0';

	/* make the change */
	ChangeText
	{
		frommark &= ~(BLKSIZE - 1);
		tomark &= ~(BLKSIZE - 1);
		tomark += BLKSIZE;
		change(frommark, tomark, tmpblk.c);
	}

	/* Reporting... */
	rptlines = markline(tomark) - markline(frommark) - 1L;
	rptlabel = "joined";
}



/*ARGSUSED*/
void cmd_shift(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	long	l;	/* line number counter */
	int	oldidx;	/* number of chars previously used for indent */
	int	newidx;	/* number of chars in the new indent string */
	int	oldcol;	/* previous indent amount */
	int	newcol;	/* new indent amount */
	char	*text;	/* pointer to the old line's text */

	/* figure out how much of the screen we must redraw (for vi mode) */
	if (markline(frommark) != markline(tomark))
	{
		mustredraw = TRUE;
		redrawrange(markline(frommark), markline(tomark) + 1L, markline(tomark) + 1L);
	}

	ChangeText
	{
		/* for each line to shift... */
		for (l = markline(frommark); l <= markline(tomark); l++)
		{
			/* get the line - ignore empty lines unless ! mode */
			text = fetchline(l);
			if (!*text && !bang)
				continue;

			/* calc oldidx and oldcol */
			for (oldidx = 0, oldcol = 0;
			     text[oldidx] == ' ' || text[oldidx] == '\t';
			     oldidx++)
			{
				if (text[oldidx] == ' ')
				{
					oldcol += 1;
				}
				else
				{
					oldcol += *o_tabstop - (oldcol % *o_tabstop);
				}
			}
	
			/* calc newcol */
			if (cmd == CMD_SHIFTR)
			{
				newcol = oldcol + (*o_shiftwidth & 0xff);
			}
			else
			{
				newcol = oldcol - (*o_shiftwidth & 0xff);
				if (newcol < 0)
					newcol = 0;
			}

			/* if no change, then skip to next line */
			if (oldcol == newcol)
				continue;

			/* build a new indent string */
			newidx = 0;
			while (newcol >= *o_tabstop)
			{
				tmpblk.c[newidx++] = '\t';
				newcol -= *o_tabstop;
			}
			while (newcol > 0)
			{
				tmpblk.c[newidx++] = ' ';
				newcol--;
			}
			tmpblk.c[newidx] = '\0';
			
			/* change the old indent string into the new */
			change(MARK_AT_LINE(l), MARK_AT_LINE(l) + oldidx, tmpblk.c);
		}
	}

	/* Reporting... */
	rptlines = markline(tomark) - markline(frommark) + 1L;
	if (cmd == CMD_SHIFTR)
	{
		rptlabel = ">ed";
	}
	else
	{
		rptlabel = "<ed";
	}
}


/*ARGSUSED*/
void cmd_read(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	int	fd, rc;	/* used while reading from the file */
	char	*scan;	/* used for finding NUL characters */
	int	hadnul;	/* boolean: any NULs found? */
	int	addnl;	/* boolean: forced to add newlines? */
	int	len;	/* number of chars in current line */
	long	lines;	/* number of lines in current block */
	struct stat statb;

	/* special case: if ":r !cmd" then let the filter() function do it */
	if (extra[0] == '!')
	{
		filter(frommark, MARK_UNSET, extra + 1);
		return;
	}

	/* open the file */
	fd = open(extra, O_RDONLY);
	if (fd < 0)
	{
		msg("Can't open \"%s\"", extra);
		return;
	}

#ifndef CRUNCH
	if (stat(extra, &statb) < 0)
	{
		msg("Can't stat \"%s\"", extra);
	}
# if TOS
	if (statb.st_mode & S_IJDIR)
# else
#  if OSK
	if (statb.st_mode & S_IFDIR)
#  else
	if ((statb.st_mode & S_IFMT) != S_IFREG)
#  endif
# endif
	{
		msg("\"%s\" is not a regular file", extra);
		return;
	}
#endif /* not CRUNCH */

	/* get blocks from the file, and add them */
	ChangeText
	{
		/* insertion starts at the line following frommark */
		tomark = frommark = (frommark | (BLKSIZE - 1L)) + 1L;
		len = 0;
		hadnul = addnl = FALSE;

		/* add an extra newline, so partial lines at the end of
		 * the file don't trip us up
		 */
		add(tomark, "\n");

		/* for each chunk of text... */
		while ((rc = tread(fd, tmpblk.c, BLKSIZE - 1)) > 0)
		{
			/* count newlines, convert NULs, etc. ... */
			for (lines = 0, scan = tmpblk.c; rc > 0; rc--, scan++)
			{
				/* break up long lines */
				if (*scan != '\n' && len + 2 > BLKSIZE)
				{
					*scan = '\n';
					addnl = TRUE;
				}

				/* protect against NUL chars in file */
				if (!*scan)
				{
					*scan = 0x80;
					hadnul = TRUE;
				}

				/* starting a new line? */
				if (*scan == '\n')
				{
					/* reset length at newline */
					len = 0;
					lines++;
				}
				else
				{
					len++;
				}
			}

			/* add the text */
			*scan = '\0';
			add(tomark, tmpblk.c);
			tomark += MARK_AT_LINE(lines) + len - markidx(tomark);
		}

		/* if partial last line, then retain that first newline */
		if (len > 0)
		{
			msg("Last line had no newline");
			tomark += BLKSIZE; /* <- for the rptlines calc */
		}
		else /* delete that first newline */
		{
			delete(tomark, (tomark | (BLKSIZE - 1L)) + 1L);
		}
	}

	/* close the file */
	close(fd);

	/* Reporting... */
	rptlines = markline(tomark) - markline(frommark);
	rptlabel = "read";

	if (addnl)
		msg("Newlines were added to break up long lines");
	if (hadnul)
		msg("NULs were converted to 0x80");
}



/*ARGSUSED*/
void cmd_undo(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	undo();
}


/* print the selected lines */
/*ARGSUSED*/
void cmd_print(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	REG char	*scan;
	REG long	l;
	REG int		col;

	for (l = markline(frommark); l <= markline(tomark); l++)
	{
		/* display a line number, if CMD_NUMBER */
		if (cmd == CMD_NUMBER)
		{
			sprintf(tmpblk.c, "%6ld  ", l);
			qaddstr(tmpblk.c);
			col = 8;
		}
		else
		{
			col = 0;
		}

		/* get the next line & display it */
		for (scan = fetchline(l); *scan; scan++)
		{
			/* expand tabs to the proper width */
			if (*scan == '\t' && cmd != CMD_LIST)
			{
				do
				{
					qaddch(' ');
					col++;
				} while (col % *o_tabstop != 0);
			}
			else if (*scan >= 0 && *scan < ' ' || *scan == '\177')
			{
				qaddch('^');
				qaddch(*scan ^ 0x40);
				col += 2;
			}
			else if ((*scan & 0x80) && cmd == CMD_LIST)
			{
				sprintf(tmpblk.c, "\\%03o", *scan);
				qaddstr(tmpblk.c);
				col += 4;
			}
			else
			{
				qaddch(*scan);
				col++;
			}

			/* wrap at the edge of the screen */
			if (!has_AM && col >= COLS)
			{
				addch('\n');
				col -= COLS;
			}
		}
		if (cmd == CMD_LIST)
		{
			qaddch('$');
		}
		addch('\n');
		exrefresh();
	}
}


/* move or copy selected lines */
/*ARGSUSED*/
void cmd_move(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	MARK	destmark;

	/* parse the destination linespec.  No defaults.  Line 0 is okay */
	destmark = cursor;
	if (!strcmp(extra, "0"))
	{
		destmark = 0L;
	}
	else if (linespec(extra, &destmark) == extra || !destmark)
	{
		msg("invalid destination address");
		return;
	}

	/* flesh the marks out to encompass whole lines */
	frommark &= ~(BLKSIZE - 1);
	tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;
	destmark = (destmark & ~(BLKSIZE - 1)) + BLKSIZE;

	/* make sure the destination is valid */
	if (cmd == CMD_MOVE && destmark >= frommark && destmark < tomark)
	{
		msg("invalid destination address");
	}

	/* Do it */
	ChangeText
	{
		/* save the text to a cut buffer */
		cutname('\0');
		cut(frommark, tomark);

		/* if we're not copying, delete the old text & adjust destmark */
		if (cmd != CMD_COPY)
		{
			delete(frommark, tomark);
			if (destmark >= frommark)
			{
				destmark -= (tomark - frommark);
			}
		}

		/* add the new text */
		paste(destmark, FALSE, FALSE);
	}

	/* move the cursor to the last line of the moved text */
	cursor = destmark + (tomark - frommark) - BLKSIZE;
	if (cursor < MARK_FIRST || cursor >= MARK_LAST + BLKSIZE)
	{
		cursor = MARK_LAST;
	}

	/* Reporting... */
	rptlabel = ( (cmd == CMD_COPY) ? "copied" : "moved" );
}



/* execute EX commands from a file */
/*ARGSUSED*/
void cmd_source(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	/* must have a filename */
	if (!*extra)
	{
		msg("\"source\" requires a filename");
		return;
	}

	doexrc(extra);
}


#ifndef NO_AT
/*ARGSUSED*/
void cmd_at(frommark, tomark, cmd, bang, extra)
	MARK	frommark;
	MARK	tomark;
	CMD	cmd;
	int	bang;
	char	*extra;
{
	static	nest = FALSE;
	int	result;
	char	buf[MAXRCLEN];

	/* don't allow nested macros */
	if (nest)
	{
		msg("@ macros can't be nested");
		return;
	}
	nest = TRUE;

	/* require a buffer name */
	if (*extra == '"')
		extra++;
	if (!*extra || !isascii(*extra) ||!islower(*extra))
	{
		msg("@ requires a cut buffer name (a-z)");
	}

	/* get the contents of the buffer */
	result = cb2str(*extra, buf, (unsigned)(sizeof buf));
	if (result <= 0)
	{
		msg("buffer \"%c is empty", *extra);
	}
	else if (result >= sizeof buf)
	{
		msg("buffer \"%c is too large to execute", *extra);
	}
	else
	{
		/* execute the contents of the buffer as ex commands */
		exstring(buf, result);
	}

	nest = FALSE;
}
#endif
SHAR_EOF
fi # end of overwriting check
if test -f 'ctags.c'
then
	echo shar: will not over-write existing file "'ctags.c'"
else
cat << \SHAR_EOF > 'ctags.c'
/* ctags.c */

/* Author:
 *	Steve Kirkendall
 *	14407 SW Teal Blvd. #C
 *	Beaverton, OR 97005
 *	kirkenda@cs.pdx.edu
 */


/* This file contains the complete source to the ctags program. */

/* Special abilities:
 * Can also make a "refs" file for use by the "ref" program.
 */

/* Limitations:
 * This version of ctags always writes its output to the file "tags".
 * It assumes that every command-line argument (but "-r") is a C source file.
 * It does not sort the list of tags, unless CFLAGS=-DSORT.
 * It does not recognize duplicate definitions.
 * It does not try to handle "static" functions in a clever way.
 * It probably won't scan ANSI-C source code very well.
 */

/* Implementation:
 * Lines are scanned one-at-a-time.
 * The context of lines is tracked via a finite state machine.
 * Contexts are:
 *	EXPECTFN - we're looking for a function name.
 *	ARGS	 - between function name and its opening {
 *	BODY	 - we found a function name, skip to end of body.
 *
 * Function tags are referenced by a search string, so that lines may be
 * inserted or deleted without mucking up the tag search.
 *
 * Macro tags are referenced by their line number, because 1) they usually
 * occur near the top of a file, so their line# won't change much; 2)  They
 * often contain characters that are hard to search for; and 3)  Their #define
 * line is likely to be altered.
 *
 * Each line of the resulting "tags" file describes one tag.  Lines begin with
 * the tag name, then a tab, then the file name, then a tab, and then either
 * a line number or a slash-delimited search string.
 */

#include <ctype.h>
#include <stdio.h>
#include "config.h"

#define REFS	"refs"

#if OSK
#define NUMFMT	"%%.%ds\t%%s\t%%ld\n"
#define SRCHFMT	"%%.%ds\t%%s\t/^%%s$/\n"
#define MAINFMT	"M%%.%ds\t%%s\t/^%%s$/\n"
static char fmt[256];
#else
#define NUMFMT	"%.*s\t%s\t%ld\n"
#define SRCHFMT	"%.*s\t%s\t/^%s$/\n"
#define MAINFMT	"M%.*s\t%s\t/^%s$/\n"
#endif

#ifdef VERBOSE
# define SAY(x)	fprintf(stderr, "%s\n", x);
#else
# define SAY(x)
#endif

#define EXPECTFN 1
#define	ARGS	 2
#define BODY	 3

extern char	*fgets();

char		*progname;	/* argv[0], used for diagnostic output */

main(argc, argv)
	int	argc;
	char	**argv;
{
	FILE	*fp;
	int	i;
	FILE	*refs;	/* used to write to the refs file */

#if MSDOS || TOS
	char **wildexpand();
	argv=wildexpand(&argc, argv);
#endif
	/* notice the program name */
	progname = argv[0];

	/* create the "refs" file if first arg is "-r" */
	if (argc > 1 && !strcmp(argv[1], "-r"))
	{
		/* delete the "-r" flag from the args list */
		argc--;
		argv++;

		/* open the "refs" file for writing */
		refs = fopen(REFS, "w");
		if (!refs)
		{
			fprintf(stderr, "%s: could not create \"%s\"\n", progname, REFS);
			exit(2);
		}
	}
	else
	{
		refs = (FILE *)0;
	}

	/* process each file named on the command line, or complain if none */
	if (argc > 1)
	{
		/* redirect stdout to go to the "tags" file */
		if (!freopen("tags", "w", stdout))
		{
			fprintf(stderr, "%s: could not create \"%s\"\n", progname, TAGS);
			exit(2);
		}

		for (i = 1; i < argc; i++)
		{
			/* process this named file */
			fp = fopen(argv[i], "r");
			if (!fp)
			{
				fprintf(stderr, "%s: could not read \"%s\"\n", progname, argv[i]);
				continue;
			}
			ctags(fp, argv[i], refs);
			fclose(fp);
		}
#ifdef SORT
		/* This is a hack which will sort the tags list.   It should
		 * on UNIX and Minix.  You may have trouble with csh.   Note
		 * that the tags list only has to be sorted if you intend to
		 * use it with the real vi;  elvis permits unsorted tags.
		 */
		fflush(stdout);
#if OSK
		fclose(stdout);
		system("qsort tags >-_tags; -nx; del tags; rename _tags tags");
#else	
		system("sort tags >_tags$$; mv _tags$$ tags");
#endif
#endif
		exit(0);
	}
	else
	{
		fprintf(stderr, "usage: %s *.[ch]\n", progname);
		exit(2);
	}
}


/* this function finds all tags in a given file */
ctags(fp, name, refs)
	FILE	*fp;		/* stream of the file to scan */
	char	*name;		/* name of the file being scanned */
	FILE	*refs;		/* NULL, or where to write refs lines */
{
	int	context;	/* context - either EXPECTFN, ARGS, or BODY */
	long	lnum;		/* line number */
	char	text[1000];	/* a line of text from the file */
	char	*scan;		/* used for searching through text */
	int	len;		/* length of the line */

	/* for each line of the file... */
	for (context = EXPECTFN, lnum = 1; fgets(text, sizeof text, fp); lnum++)
	{
#ifdef VERBOSE
		switch(context)
		{
		  case EXPECTFN:	scan = "EXPECTFN";	break;
		  case ARGS:		scan = "ARGS    ";	break;
		  case BODY:		scan = "BODY    ";	break;
		  default:		scan = "context?";
		}
		fprintf(stderr, "%s:%s", scan, text);
#endif

		/* start of body? */
		if (text[0] == '{')
		{
			context = BODY;
			SAY("Start of BODY");
			continue;
		}

		/* argument line, to be written to "refs" ? */
		if (refs && context == ARGS)
		{
			if (text[0] != '\t')
			{
				putc('\t', refs);
			}
			fputs(text, refs);
			SAY("Argument line");
			continue;
		}

		/* ignore empty or indented lines */
		if (text[0] <= ' ')
		{
			SAY("Empty or indented");
			continue;
		}

		/* end of body? */
		if (text[0] == '}')
		{
			context = EXPECTFN;
			SAY("End of BODY");
			continue;
		}

		/* ignore lines in the body of a function */
		if (context != EXPECTFN)
		{
			SAY("BODY or ARGS");
			continue;
		}

		/* strip the newline */
		len = strlen(text);
		text[--len] = '\0';

		/* a preprocessor line? */
		if (text[0] == '#')
		{
			/* find the preprocessor directive */
			for (scan = &text[1]; isspace(*scan); scan++)
			{
			}

			/* if it's a #define, make a tag out of it */
			if (!strncmp(scan, "define", 6))
			{
				/* find the start of the symbol name */
				for (scan += 6; isspace(*scan); scan++)
				{
				}

				/* find the length of the symbol name */
				for (len = 1;
				     isalnum(scan[len]) || scan[len] == '_';
				     len++)
				{
				}
#if OSK
				sprintf(fmt, NUMFMT, len);
				printf(fmt, scan, name, lnum);
#else			
				printf(NUMFMT, len, scan, name, lnum);
#endif
			}
			SAY("Preprocessor line");
			continue;
		}

		/* an extern or static declaration? */
		if (text[len - 1] == ';'
		 || !strncmp(text, "extern", 6)
		 || !strncmp(text, "EXTERN", 6)
		 || !strncmp(text, "static", 6)
		 || !strncmp(text, "PRIVATE", 7))
		{
			SAY("Extern or static");
			continue;
		}

		/* if we get here & the first punctuation other than "*" is
		 * a "(" which is immediately preceded by a name, then
		 * assume the name is that of a function.
		 */
		for (scan = text; *scan; scan++)
		{
			if (ispunct(*scan)
			 && !isspace(*scan) /* in BSD, spaces are punctuation?*/
			 && *scan != '*' && *scan != '_' && *scan != '(')
			{
				SAY("Funny punctuation");
				goto ContinueContinue;
			}

			if (*scan == '(')
			{
				/* permit 0 or 1 spaces between name & '(' */
				if (scan > text && scan[-1] == ' ')
				{
					scan--;
				}

				/* find the start & length of the name */
				for (len = 0, scan--;
				     scan >= text && (isalnum(*scan) || *scan == '_');
				     scan--, len++)
				{
				}
				scan++;

				/* did we find a function? */
				if (len > 0)
				{
					/* found a function! */
					if (len == 4 && !strncmp(scan, "main", 4))
					{
#if OSK
						sprintf(fmt, MAINFMT, strlen(name) - 2);
						printf(fmt, name, name, text);
#else			
						printf(MAINFMT, strlen(name) - 2, name, name, text);
#endif
					}
#if OSK
					sprintf(fmt, SRCHFMT, len);
					printf(fmt, scan, name, text);
#else				
					printf(SRCHFMT, len, scan, name, text);
#endif
					context = ARGS;

					/* add a line to refs, if needed */
					if (refs)
					{
						fputs(text, refs);
						putc('\n', refs);
					}

					goto ContinueContinue;
				}
			}
			else
			{
				SAY("No parenthesis");
			}
		}
		SAY("No punctuation");

ContinueContinue:;
	}
}

#if MSDOS || TOS
#define		WILDCARD_NO_MAIN
#include	"wildcard.c"
#endif
SHAR_EOF
fi # end of overwriting check
if test -f 'curses.c'
then
	echo shar: will not over-write existing file "'curses.c'"
else
cat << \SHAR_EOF > 'curses.c'
/* curses.c */

/* Author:
 *	Steve Kirkendall
 *	14407 SW Teal Blvd. #C
 *	Beaverton, OR 97005
 *	kirkenda@cs.pdx.edu
 */


/* This file contains the functions & variables needed for a tiny subset of
 * curses.  The principle advantage of this version of curses is its
 * extreme speed.  Disadvantages are potentially larger code, few supported
 * functions, limited compatibility with full curses, and only stdscr.
 */

#include "config.h"
#include "vi.h"

#if ANY_UNIX
# if UNIXV
#  include	<termio.h>
#  undef	TIOCWINSZ	/* we can't handle it correctly yet */
# else
#  include	<sgtty.h>
# endif
#endif

#if TOS
# include	<osbind.h>
#endif

#if OSK
# include	<sgstat.h>
#endif

#include <signal.h>

extern char	*getenv();
void	 starttcap();

/* variables, publicly available & used in the macros */
short	ospeed;		/* speed of the tty, eg B2400 */
#if OSK
char	PC_;	/* Pad char */
#else
char	PC;		/* Pad char */
#endif
WINDOW	*stdscr;	/* pointer into kbuf[] */
WINDOW	kbuf[KBSIZ];	/* a very large output buffer */
int	LINES;		/* :li#: number of rows */
int	COLS;		/* :co#: number of columns */
int	AM;		/* :am:  boolean: auto margins? */
int	PT;		/* :pt:  boolean: physical tabs? */
char	*BC = "\b";	/* :bc   backspace character string */
char	*VB;		/* :vb=: visible bell */
char	*UP;		/* :up=: move cursor up */
char	*SO;		/* :so=: standout start */
char	*SE;		/* :se=: standout end */
char	*US = "";	/* :us=: underline start */
char	*UE = "";	/* :ue=: underline end */
char	*MD = "";	/* :md=: bold start */
char	*ME = "";	/* :me=: bold end */
char	*AS;		/* :as=: alternate (italic) start */
char	*AE;		/* :ae=: alternate (italic) end */
char	*CM;		/* :cm=: cursor movement */
char	*CE;		/* :ce=: clear to end of line */
char	*CD;		/* :cd=: clear to end of screen */
char	*AL;		/* :al=: add a line */
char	*DL;		/* :dl=: delete a line */
#if OSK
char	*SR_;		/* :sr=: scroll reverse */
#else
char	*SR;		/* :sr=: scroll reverse */
#endif
char	*KS;		/* :ks=: init string for cursor */
char	*KE;		/* :ke=: restore string for cursor */
char	*KU;		/* :ku=: key sequence sent by up arrow */
char	*KD;		/* :kd=: key sequence sent by down arrow */
char	*KL;		/* :kl=: key sequence sent by left arrow */
char	*KR;		/* :kr=: key sequence sent by right arrow */
char	*HM;		/* :HM=: key sequence sent by the <Home> key */
char	*EN;		/* :EN=: key sequence sent by the <End> key */
char	*PU;		/* :PU=: key sequence sent by the <PgUp> key */
char	*PD;		/* :PD=: key sequence sent by the <PgDn> key */
char	*IM;		/* :im=: insert mode start */
char	*IC = "";	/* :ic=: insert the following character */
char	*EI;		/* :ei=: insert mode end */
char	*DC;		/* :dc=: delete a character */
char	*TI;		/* :ti=: terminal init */	/* GB */
char	*TE;		/* :te=: terminal exit */	/* GB */
#ifndef NO_CURSORSHAPE
char	*CQ = (char *)0;/* :cQ=: normal cursor */
char	*CX = (char *)1;/* :cX=: cursor used for EX command/entry */
char	*CV = (char *)2;/* :cV=: cursor used for VI command mode */
char	*CI = (char *)3;/* :cI=: cursor used for VI input mode */
char	*CR = (char *)4;/* :cR=: cursor used for VI replace mode */
#endif
char	*aend = "";	/* end an attribute -- either UE or ME */
char	ERASEKEY;	/* backspace key taken from ioctl structure */

#if ANY_UNIX
# if UNIXV
static struct termio	oldtermio;	/* original tty mode */
static struct termio	newtermio;	/* cbreak/noecho tty mode */
# else
static struct sgttyb	oldsgttyb;	/* original tty mode */
static struct sgttyb	newsgttyb;	/* cbreak/nl/noecho tty mode */
static int		oldint;		/* ^C or DEL, the "intr" character */
#  ifdef TIOCSLTC
static int		oldswitch;	/* ^Z, the "suspend" character */
static int		oldquote;	/* ^V, the "quote next char" char */
#  endif
# endif
#endif

#if OSK
static struct sgbuf	oldsgttyb;	/* orginal tty mode */
static struct sgbuf	newsgttyb;	/* noecho tty mode */
#endif

static char	*capbuf;	/* capability string buffer */


void initscr()
{
	/* make sure TERM variable is set */
#if MSDOS
	char *val;
	if (! (val = getenv("TERM"))
	|| !strcmp(val, "pcbios"))
#else
	if (!getenv("TERM"))
#endif
	{
#if ANY_UNIX
		write(2, "Environment variable TERM must be set\n", (unsigned)38);
		exit(1);
#endif
#if OSK
		writeln(2, "Environment variable TERM must be set\n", (unsigned)38);
		exit(1);
#endif
#if MSDOS || TOS
		Getsize(0);
#endif
	}
	else
	{
#if MSDOS
		*o_pcbios=0;
#endif
		/* start termcap stuff */
		starttcap();
	}

	/* create stdscr and curscr */
	stdscr = kbuf;

	/* change the terminal mode to cbreak/noecho */
#if ANY_UNIX
# if UNIXV
	ioctl(2, TCGETA, &oldtermio);
# else
	ioctl(2, TIOCGETP, &oldsgttyb);
# endif
#endif

#if OSK
	_gs_opt(0, &oldsgttyb);
#endif
	Resume_curses(TRUE);
}


void endwin()
{
	/* change the terminal mode back the way it was */
	Suspend_curses();
}


static int curses_active = FALSE;

void suspend_curses()
{
#if ANY_UNIX && !UNIXV
	struct tchars	tbuf;
# ifdef TIOCSLTC
	struct ltchars	ltbuf;
# endif
#endif
#ifndef NO_CURSORSHAPE
	if (has_CQ)
	{
		do_CQ();
	}
#endif
	if (has_TE)					/* GB */
	{
		do_TE();
	}
	if (has_KE)
	{
		do_KE();
	}
	refresh();

	/* change the terminal mode back the way it was */
#if ANY_UNIX
# if UNIXV
	ioctl(2, TCSETAW, &oldtermio);
# else
	ioctl(2, TIOCSETP, &oldsgttyb);

	ioctl(2, TIOCGETC, &tbuf);
	tbuf.t_intrc = oldint;
	ioctl(2, TIOCSETC, &tbuf);

#  ifdef TIOCSLTC
	ioctl(2, TIOCGLTC, &ltbuf);
	ltbuf.t_suspc = oldswitch;
	ltbuf.t_lnextc = oldquote;
	ioctl(2, TIOCSLTC, &ltbuf);
#  endif
# endif
#endif
#if OSK
	_ss_opt(0, &oldsgttyb);
#endif
	curses_active = FALSE;
}

void resume_curses(quietly)
	int	quietly;
{
	if (!curses_active)
	{
		/* change the terminal mode to cbreak/noecho */
#if ANY_UNIX
# if UNIXV
		ospeed = (oldtermio.c_cflag & CBAUD);
		ERASEKEY = oldtermio.c_cc[VERASE];
		newtermio = oldtermio;
		newtermio.c_iflag &= (IXON|IXOFF|IXANY|ISTRIP|IGNBRK);
		newtermio.c_oflag &= ~OPOST;
		newtermio.c_lflag &= ISIG;
		newtermio.c_cc[VINTR] = ctrl('C'); /* always use ^C for interrupts */
		newtermio.c_cc[VMIN] = 1;
		newtermio.c_cc[VTIME] = 0;
#  ifdef VSWTCH
		newtermio.c_cc[VSWTCH] = 0;
#  endif
		ioctl(2, TCSETAW, &newtermio);
# else /* BSD or V7 or Coherent or Minix */
		struct tchars	tbuf;
#  ifdef TIOCSLTC
		struct ltchars	ltbuf;
#  endif

		ospeed = oldsgttyb.sg_ospeed;
		ERASEKEY = oldsgttyb.sg_erase;
		newsgttyb = oldsgttyb;
		newsgttyb.sg_flags |= CBREAK;
		newsgttyb.sg_flags &= ~(CRMOD|ECHO|XTABS);
		ioctl(2, TIOCSETP, &newsgttyb);

		ioctl(2, TIOCGETC, &tbuf);
		oldint = tbuf.t_intrc;
		tbuf.t_intrc = ctrl('C');	/* always use ^C for interrupts */
		ioctl(2, TIOCSETC, &tbuf);

#  ifdef TIOCSLTC
		ioctl(2, TIOCGLTC, &ltbuf);
		oldswitch = ltbuf.t_suspc;
		ltbuf.t_suspc = 0;		/* disable ^Z for elvis */
		oldquote = ltbuf.t_lnextc;
		ltbuf.t_lnextc = 0;		/* disable ^V for elvis */
		ioctl(2, TIOCSLTC, &ltbuf);
#  endif

# endif
#endif
#if OSK
		newsgttyb = oldsgttyb;
		newsgttyb.sg_echo = 0;
		newsgttyb.sg_eofch = 0;
		newsgttyb.sg_kbach = 0;
		newsgttyb.sg_kbich = ctrl('C');
		_ss_opt(0, &newsgttyb);
		ospeed = oldsgttyb.sg_baud;
		ERASEKEY = oldsgttyb.sg_bspch;
#endif

		if (has_TI)					/* GB */
		{
			do_TI();
		}
		if (has_KS)
		{
			do_KS();
		}

		curses_active = TRUE;
	}

	/* If we're supposed to quit quietly, then we're done */
	if (quietly)
	{
		return;
	}

	signal(SIGINT, SIG_IGN);

	move(LINES - 1, 0);
	do_SO();
	qaddstr("[Press <RETURN> to continue]");
	do_SE();
	refresh();
	ttyread(kbuf, 20); /* in RAW mode, so <20 is very likely */
	if (kbuf[0] == ':')
	{
		mode = MODE_COLON;
		addch('\n');
		refresh();
	}
	else
	{
		mode = MODE_VI;
		redraw(MARK_UNSET, FALSE);
	}	
	exwrote = FALSE;

#if TURBOC
	signal(SIGINT, (void(*)()) trapint);
#else
	signal(SIGINT, trapint);
#endif
}

static void lacking(s)
	char	*s;
{
	write(2, "This termcap entry lacks the :", (unsigned)30);
	write(2, s, (unsigned)2);
	write(2, "=: capability\n", (unsigned)14);
#if OSK
	write(2, "\l", 1);
#endif
	exit(1);
}

void starttcap()
{
	char	*str;
	static char	cbmem[800];
#define MUSTHAVE(T,s)	if (!(T = Tgetstr(s, &capbuf))) lacking(s)
#define MAYHAVE(T,s)	if (str = Tgetstr(s, &capbuf)) T = str
#define PAIR(T,U,sT,sU)	T=Tgetstr(sT,&capbuf);U=Tgetstr(sU,&capbuf);if (!T||!U)T=U=""

	/* allocate memory for capbuf */
	capbuf = cbmem;

	/* get the termcap entry */
	switch (Tgetent(kbuf, getenv("TERM")))
	{
	  case -1:
		write(2, "Can't read /etc/termcap\n", (unsigned)24);
#if OSK
		write(2, "\l", 1);
#endif
		exit(2);

	  case 0:
		write(2, "Unrecognized TERM type\n", (unsigned)23);
#if OSK
		write(2, "\l", 1);
#endif
		exit(3);
	}

	/* get strings */
	MUSTHAVE(UP, "up");
	MAYHAVE(BC, "bc");
	MAYHAVE(VB, "vb");
	MUSTHAVE(CM, "cm");
	PAIR(SO, SE, "so", "se");
	PAIR(TI, TE, "ti", "te");
	if (Tgetnum("ug") <= 0)
	{
		PAIR(US, UE, "us", "ue");
		PAIR(MD, ME, "md", "me");

		/* get italics, or have it default to underline */
		PAIR(AS, AE, "as", "ae");
		if (!*AS)
		{
			AS = US;
			AE = UE;
		}
	}
	MAYHAVE(AL, "al");
	MAYHAVE(DL, "dl");
	MUSTHAVE(CE, "ce");
	MAYHAVE(CD, "cd");
#if OSK
	MAYHAVE(SR_, "sr");
#else	
	MAYHAVE(SR, "sr");
#endif
	PAIR(IM, EI, "im", "ei");
	MAYHAVE(IC, "ic");
	MAYHAVE(DC, "dc");

	/* other termcap stuff */
	AM = Tgetflag("am");
	PT = Tgetflag("pt");
	Getsize(0);

	/* Key sequences */
	PAIR(KS, KE, "ks", "ke");
	MAYHAVE(KU, "ku");		/* up */
	MAYHAVE(KD, "kd");		/* down */
	MAYHAVE(KL, "kl");		/* left */
	MAYHAVE(KR, "kr");		/* right */
	MAYHAVE(PU, "kP");		/* PgUp */
	MAYHAVE(PD, "kN");		/* PgDn */
	MAYHAVE(HM, "kh");		/* Home */
	MAYHAVE(EN, "kH");		/* End */
#ifndef CRUNCH
	if (!PU) MAYHAVE(PU, "K2");	/* "3x3 pad" names for PgUp, etc. */
	if (!PD) MAYHAVE(PD, "K5");
	if (!HM) MAYHAVE(HM, "K1");
	if (!EN) MAYHAVE(EN, "K4");

	MAYHAVE(PU, "PU");		/* old XENIX names for PgUp, etc. */
	MAYHAVE(PD, "PD");		/* (overrides others, if used.) */
	MAYHAVE(HM, "HM");
	MAYHAVE(EN, "EN");
#endif

#ifndef NO_CURSORSHAPE
	/* cursor shapes */
	CQ = Tgetstr("cQ", &capbuf);
	if (has_CQ)
	{
		CX = Tgetstr("cX", &capbuf);
		if (!CX) CX = CQ;
		CV = Tgetstr("cV", &capbuf);
		if (!CV) CV = CQ;
		CI = Tgetstr("cI", &capbuf);
		if (!CI) CI = CQ;
		CR = Tgetstr("cR", &capbuf);
		if (!CR) CR = CQ;
	}
# ifndef CRUNCH
	else
	{
		PAIR(CQ, CV, "ve", "vs");
		CX = CI = CR = CQ;
	}
# endif /* !CRUNCH */
#endif /* !NO_CURSORSHAPE */

#undef MUSTHAVE
#undef MAYHAVE
#undef PAIR
}


/* This function gets the window size.  It uses the TIOCGWINSZ ioctl call if
 * your system has it, or Tgetnum("li") and Tgetnum("co") if it doesn't.
 * This function is called once during initialization, and thereafter it is
 * called whenever the SIGWINCH signal is sent to this process.
 */
int getsize(signo)
	int	signo;
{
	int	lines;
	int	cols;
#ifdef TIOCGWINSZ
	struct winsize size;
#endif

#ifdef SIGWINCH
	/* reset the signal vector */
	signal(SIGWINCH, getsize);
#endif

	/* get the window size, one way or another. */
	lines = cols = 0;
#ifdef TIOCGWINSZ
	if (ioctl(2, TIOCGWINSZ, &size) >= 0)
	{
		lines = size.ws_row;
		cols = size.ws_col;
	}
#endif
	if ((lines == 0 || cols == 0) && signo == 0)
	{
		LINES = CHECKBIOS(v_rows(), Tgetnum("li"));
		COLS = CHECKBIOS(v_cols(), Tgetnum("co"));
	}
	if (lines >= 2 && cols >= 30)
	{
		LINES = lines;
		COLS = cols;
	}

	/* Make sure we got values that we can live with */
	if (LINES < 2 || COLS < 30)
	{
		write(2, "Screen too small\n", (unsigned)17);
#if OSK
		write(2, "\l", 1);
#endif
		endwin();
		exit(2);
	}

	/* !!! copy the new values into Elvis' options */
	{
		extern char	o_columns[], o_lines[];

		*o_columns = COLS;
		*o_lines = LINES;
	}

	return 0;
}


/* This is a function version of addch() -- it is used by tputs() */
int faddch(ch)
	int	ch;
{
	addch(ch);

	return 0;
}

/* These functions are equivelent to the macros of the same names... */

void qaddstr(str)
	char	*str;
{
	REG char *s_, *d_;

#if MSDOS
	if (o_pcbios[0])
	{
		while (*str)
			qaddch(*str++);
		return;
	}
#endif
	for (s_=(str), d_=stdscr; *d_++ = *s_++; )
	{
	}
	stdscr = d_ - 1;
}

void attrset(a)
	int	a;
{
	do_aend();
	if (a == A_BOLD)
	{
		do_MD();
		aend = ME;
	}
	else if (a == A_UNDERLINE)
	{
		do_US();
		aend = UE;
	}
	else if (a == A_ALTCHARSET)
	{
		do_AS();
		aend = AE;
	}
	else
	{
		aend = "";
	}
}


void insch(ch)
	int	ch;
{
	if (has_IM)
		do_IM();
	do_IC();
	qaddch(ch);
	if (has_EI)
		do_EI();
}

#if MSDOS

static int alarmtime;

/* raw read - #defined to read (0, ...) on non-MSDOS.
 * With MSDOS, am maximum of 1 byte is read.
 * If more bytes should be read, just change the loop.
 * The following code uses the IBM-PC-System-Timer, so probably wont't work
 * on non-compatibles.
 */
/*ARGSUSED*/
ttyread(buf, len)
	char *buf;
	int len;
{
	volatile char far *biostimer;
	char oldtime;
	int nticks = 0;
	int pos = 0;

	biostimer = (char far *)0x0040006cl;
	oldtime = *biostimer;

	while (!pos && (!alarmtime || nticks<alarmtime))
	{	if (kbhit())
			if ((buf[pos++] = getch()) == 0) /* function key */
				buf[pos-1] = '#';
		if (oldtime != *biostimer)
		{	nticks++;
			oldtime = *biostimer;
		}
	}
	return pos;
}

alarm(time)
	int time;
{
	alarmtime = 2 * time;		/* ticks are 1/18 sec. */
}

sleep(seconds)
	unsigned seconds;
{
	volatile char	far *biostimer = (char far *)0x0040006cl;
	char		stop;

	stop = *biostimer + 18 * seconds;
	while (*biostimer != stop)
	{
	}
}
#endif

#if TOS

static int alarmtime;
static long timer;

static gettime()
{
	timer = *(long *)(0x4ba);
}

/*ARGSUSED*/
ttyread(buf, len)
	char *buf;
	int len;
{
	int	pos=0;
	long	l;
	long	endtime;

	Supexec(gettime);
	endtime = timer+alarmtime;

	while (!pos && (!alarmtime || timer<endtime))
	{
		if (Bconstat(2))
		{
			l = Bconin(2);
			if ((buf[pos++]=l) == '\0')
			{
				buf[pos-1]='#';
				buf[pos++]=l>>16;
			}
		}
		Supexec(gettime);
	}
	return pos;
}

alarm(time)
	int time;
{
	alarmtime = 50 * time;		/* ticks are 1/200 sec. */
}

ttywrite(buf, len)
	char *buf;
	int len;
{
	while (len--)
		Bconout(2, *buf++);
}
#endif

#if OSK
ttyread(buf, len)
	char *buf;
	int len;
{
	REG int i;
	if ((i = _gs_rdy(0)) > 0)
		return read(0, buf, i < len ? i : len);
	else
		return read(0, buf, 1);
}

alarm(time)
	int time;
{
#define TIME(secs) ((secs << 8) | 0x80000000)
	static int alrmid;

	if (time)	
		alrmid = alm_set(SIGQUIT, TIME(time));
	else	
		alm_delete(alrmid);
}
#endif /* OSK */
SHAR_EOF
fi # end of overwriting check
if test -f 'cut.c'
then
	echo shar: will not over-write existing file "'cut.c'"
else
cat << \SHAR_EOF > 'cut.c'
/* cut.c */

/* Author:
 *	Steve Kirkendall
 *	14407 SW Teal Blvd. #C
 *	Beaverton, OR 97005
 *	kirkenda@cs.pdx.edu
 */


/* This file contains function which manipulate the cut buffers. */

#include "config.h"
#include "vi.h"
#if TURBOC
#include <process.h>		/* needed for getpid */
#endif
#if TOS
#include <osbind.h>
#define	rename(a,b)	Frename(0,a,b)
#endif

# define NANNONS	9	/* number of annonymous buffers */

static struct cutbuf
{
	short	*phys;	/* pointer to an array of #s of BLKs containing text */
	int	nblks;	/* number of blocks in phys[] array */
	int	start;	/* offset into first block of start of cut */
	int	end;	/* offset into last block of end of cut */
	int	fd;	/* fd of tmp file, or -1 to use tmpfd */
	char	lnmode;	/* boolean: line-mode cut? (as opposed to char-mode) */
}
	named[27],	/* cut buffers "a through "z and ". */
	annon[NANNONS];	/* annonymous cut buffers */

static char	cbname;	/* name chosen for next cut/paste operation */


#ifndef NO_RECYCLE
/* This function builds a list of all blocks needed in the current tmp file
 * for the contents of cut buffers.
 * !!! WARNING: if you have more than ~450000 bytes of text in all of the
 * cut buffers, then this will fail disastrously, because buffer overflow
 * is *not* allowed for.
 */
int cutneeds(need)
	BLK		*need;	/* this is where we deposit the list */
{
	struct cutbuf	*cb;	/* used to count through cut buffers */
	int		i;	/* used to count through blocks of a cut buffer */
	int		n;	/* total number of blocks in list */

	n = 0;

	/* first the named buffers... */
	for (cb = named; cb < &named[27]; cb++)
	{
		if (cb->fd > 0)
			continue;

		for (i = cb->nblks; i-- > 0; )
		{
			need->n[n++] = cb->phys[i];
		}
	}

	/* then the anonymous buffers */
	for (cb = annon; cb < &annon[NANNONS]; cb++)
	{
		if (cb->fd > 0)
			continue;

		for (i = cb->nblks; i-- > 0; )
		{
			need->n[n++] = cb->phys[i];
		}
	}

	return n;
}
#endif

/* This function frees a cut buffer */
static void cutfree(buf)
	struct cutbuf	*buf;
{
	char	cutfname[50];
	int	i;

	/* return immediately if the buffer is already empty */
	if (buf->nblks <= 0)
	{
		return;
	}

	/* else free up stuff */
	buf->nblks = 0;
#ifdef DEBUG
	if (!buf->phys)
		msg("cutfree() tried to free an NULL buf->phys pointer.");
#endif
	free((char *)buf->phys);

	/* see if anybody else needs this tmp file */
	if (buf->fd >= 0)
	{
		for (i = 0; i < 27; i++)
		{
			if (named[i].nblks > 0 && named[i].fd == buf->fd)
			{
				break;
			}
		}
	}

	/* if nobody else needs it, then discard the tmp file */
	if (buf->fd >= 0 && i == 27)
	{
		close(buf->fd);
#if MSDOS || TOS
		strcpy(cutfname, o_directory);
		if ((i = strlen(cutfname)) && !strchr(":/\\", cutfname[i-1]))
			cutfname[i++]=SLASH;
		sprintf(cutfname+i, CUTNAME+3, getpid(), buf->fd);
#else
		sprintf(cutfname, CUTNAME, o_directory, getpid(), buf->fd);
#endif
		unlink(cutfname);
	}
}

/* This function is called when we are about to abort a tmp file.  If any
 * cut buffers still need the file, then a copy of the file should be
 * created for use by the cut buffers.
 *
 * To minimize the number of extra files lying around, only named cut buffers
 * are preserved in a file switch; the annonymous buffers just go away.
 */
void cutswitch(tmpname)
	char	*tmpname; /* name of the tmp file */
{
	char	cutfname[50];	/* used to build a new name for the tmp file */
	int	fd;		/* a new fd for the current tmp file */
	int	i;
#if MSDOS || TOS
	int	j;
#endif

	/* discard all annonymous cut buffers */
	for (i = 0; i < NANNONS; i++)
	{
		cutfree(&annon[i]);
	}

	/* find the first named buffer that uses this tmp file */
	for (i = 0; i < 27; i++)
	{
		if (named[i].nblks > 0 && named[i].fd < 0)
		{
			break;
		}
	}

	/* if none of them use this tmp file, then we're done */
	if (i == 27)
	{
		return;
	}

	/* else we'll need this file and an fd a little longer */
#if MSDOS || TOS
	strcpy(cutfname, o_directory);
	if ((j = strlen(cutfname)) && !strchr(":/\\", cutfname[j-1]))
		cutfname[j++]=SLASH;
	close(tmpfd);
	fd = open(tmpname, O_RDONLY|O_BINARY);
	close(fd);
	sprintf(cutfname+j, CUTNAME+3, getpid(), fd);
	rename(tmpname, cutfname);
	fd = open(cutfname, O_RDONLY|O_BINARY);
	tmpfd = -1; /* we'll try to close this in tmp.c, but who cares? */
#else
	fd = dup(tmpfd);
# if OSK
	sprintf(cutfname, CUTNAME, "", getpid(), fd);
	if (!link(tmpname, &cutfname[1])) /* skip slash */
		unlink(tmpname);
# else	
	sprintf(cutfname, CUTNAME, o_directory, getpid(), fd);
	link(tmpname, cutfname) || unlink(tmpname);
# endif
#endif

	/* have all cut buffers use the new fd instead */
	for (; i < 27; i++)
	{
		if (named[i].nblks > 0 && named[i].fd < 0)
		{
			named[i].fd = fd;
		}
	}
}

/* This function should be called just before termination of vi */
void cutend()
{
	int	i;

	/* free all named cut buffers, since they might be forcing an older
	 * tmp file to be retained.
	 */
	for (i = 0; i < 27; i++)
	{
		cutfree(&named[i]);
	}
}


/* This function is used to select the cut buffer to be used next */
void cutname(name)
	int	name;	/* a single character */
{
	cbname = name;
}




/* This function copies a selected segment of text to a cut buffer */
void cut(from, to)
	MARK	from;		/* start of text to cut */
	MARK	to;		/* end of text to cut */
{
	int		first;	/* logical number of first block in cut */
	int		last;	/* logical number of last block used in cut */
	long		line;	/* a line number */
	int		lnmode;	/* boolean: will this be a line-mode cut? */
	MARK		delthru;/* end of text temporarily inserted for apnd */
	REG struct cutbuf *cb;
	REG long	l;
	REG int		i;
	REG char	*scan;
	char		*blkc;

	/* detect whether this must be a line-mode cut or char-mode cut */
	if (markidx(from) == 0 && markidx(to) == 0)
		lnmode = TRUE;
	else
		lnmode = FALSE;

	/* by default, we don't "delthru" anything */
	delthru = MARK_UNSET;

	/* decide which cut buffer to use */
	if (!cbname)
	{
		/* free up the last annonymous cut buffer */
		cutfree(&annon[NANNONS - 1]);

		/* shift the annonymous cut buffers */
		for (i = NANNONS - 1; i > 0; i--)
		{
			annon[i] = annon[i - 1];
		}

		/* use the first annonymous cut buffer */
		cb = annon;
		cb->nblks = 0;
	}
	else if (cbname >= 'a' && cbname <= 'z')
	{
		cb = &named[cbname - 'a'];
		cutfree(cb);
	}
#ifndef CRUNCH
	else if (cbname >= 'A' && cbname <= 'Z')
	{
		cb = &named[cbname - 'A'];
		if (cb->nblks > 0)
		{
			/* resolve linemode/charmode differences */
			if (!lnmode && cb->lnmode)
			{
				from &= ~(BLKSIZE - 1);
				if (markidx(to) != 0 || to == from)
				{
					to = to + BLKSIZE - markidx(to);
				}
				lnmode = TRUE;
			}

			/* insert the old cut-buffer before the new text */
			mark[28] = to;
			delthru = paste(from, FALSE, TRUE);
			if (delthru == MARK_UNSET)
			{
				return;
			}
			delthru++;
			to = mark[28];
		}
		cutfree(cb);
	}
#endif /* not CRUNCH */
	else if (cbname == '.')
	{
		cb = &named[26];
		cutfree(cb);
	}
	else
	{
		msg("Invalid cut buffer name: \"%c", cbname);
		cbname = '\0';
		return;
	}
	cbname = '\0';
	cb->fd = -1;

	/* detect whether we're doing a line mode cut */
	cb->lnmode = lnmode;

	/* ---------- */

	/* Reporting... */	
	if (markidx(from) == 0 && markidx(to) == 0)
	{
		rptlines = markline(to) - markline(from);
		rptlabel = "yanked";
	}

	/* ---------- */

	/* make sure each block has a physical disk address */
	blksync();

	/* find the first block in the cut */
	line = markline(from);
	for (first = 1; line > lnum[first]; first++)
	{
	}

	/* fetch text of the block containing that line */
	blkc = scan = blkget(first)->c;

	/* find the mark in the block */
	for (l = lnum[first - 1]; ++l < line; )
	{
		while (*scan++ != '\n')
		{
		}
	}
	scan += markidx(from);

	/* remember the offset of the start */
	cb->start = scan - blkc;

	/* ---------- */

	/* find the last block in the cut */
	line = markline(to);
	for (last = first; line > lnum[last]; last++)
	{
	}

	/* fetch text of the block containing that line */
	if (last != first)
	{
		blkc = scan = blkget(last)->c;
	}
	else
	{
		scan = blkc;
	}

	/* find the mark in the block */
	for (l = lnum[last - 1]; ++l < line; )
	{
		while (*scan++ != '\n')
		{
		}
	}
	if (markline(to) <= nlines)
	{
		scan += markidx(to);
	}

	/* remember the offset of the end */
	cb->end = scan - blkc;

	/* ------- */

	/* remember the physical block numbers of all included blocks */
	cb->nblks = last - first;
	if (cb->end > 0)
	{
		cb->nblks++;
	}
#ifdef lint
	cb->phys = (short *)0;
#else
	cb->phys = (short *)malloc((unsigned)(cb->nblks * sizeof(short)));
#endif
	for (i = 0; i < cb->nblks; i++)
	{
		cb->phys[i] = hdr.n[first++];
	}

#ifndef CRUNCH
	/* if we temporarily inserted text for appending, then delete that
	 * text now -- before the user sees it.
	 */
	if (delthru)
	{
		line = rptlines;
		delete(from, delthru);
		rptlines = line;
		rptlabel = "yanked";
	}
#endif /* not CRUNCH */
}


static void readcutblk(cb, blkno)
	struct cutbuf	*cb;
	int		blkno;
{
	int		fd;	/* either tmpfd or cb->fd */

	/* decide which fd to use */
	if (cb->fd >= 0)
	{
		fd = cb->fd;
	}
	else
	{
		fd = tmpfd;
	}

	/* get the block */
	lseek(fd, (long)cb->phys[blkno] * (long)BLKSIZE, 0);
	if (read(fd, tmpblk.c, (unsigned)BLKSIZE) != BLKSIZE)
	{
		msg("Error reading back from tmp file for pasting!");
	}
}


/* This function inserts text from a cut buffer, and returns the MARK where
 * insertion ended.  Return MARK_UNSET on errors.
 */
MARK paste(at, after, retend)
	MARK	at;	/* where to insert the text */
	int	after;	/* boolean: insert after mark? (rather than before) */
	int	retend;	/* boolean: return end of text? (rather than start) */
{
	REG struct cutbuf	*cb;
	REG int			i;

	/* decide which cut buffer to use */
	if (cbname >= 'A' && cbname <= 'Z')
	{
		cb = &named[cbname - 'A'];
	}
	else if (cbname >= 'a' && cbname <= 'z')
	{
		cb = &named[cbname - 'a'];
	}
	else if (cbname >= '1' && cbname <= '9')
	{
		cb = &annon[cbname - '1'];
	}
	else if (cbname == '.')
	{
		cb = &named[26];
	}
	else if (!cbname)
	{
		cb = annon;
	}
	else
	{
		msg("Invalid cut buffer name: \"%c", cbname);
		cbname = '\0';
		return MARK_UNSET;
	}

	/* make sure it isn't empty */
	if (cb->nblks == 0)
	{
		if (cbname)
			msg("Cut buffer \"%c is empty", cbname);
		else
			msg("Cut buffer is empty");
		cbname = '\0';
		return MARK_UNSET;
	}
	cbname = '\0';

	/* adjust the insertion MARK for "after" and line-mode cuts */
	if (cb->lnmode)
	{
		at &= ~(BLKSIZE - 1);
		if (after)
		{
			at += BLKSIZE;
		}
	}
	else if (after)
	{
		/* careful! if markidx(at) == 0 we might be pasting into an
		 * empty line -- so we can't blindly increment "at".
		 */
		if (markidx(at) == 0)
		{
			pfetch(markline(at));
			if (plen != 0)
			{
				at++;
			}
		}
		else
		{
			at++;
		}
	}

	/* put a copy of the "at" mark in the mark[] array, so it stays in
	 * sync with changes made via add().
	 */
	mark[27] = at;

	/* simple one-block paste? */
	if (cb->nblks == 1)
	{
		/* get the block */
		readcutblk(cb, 0);

		/* isolate the text we need within it */
		if (cb->end)
		{
			tmpblk.c[cb->end] = '\0';
		}

		/* insert it */
		ChangeText
		{
			add(at, &tmpblk.c[cb->start]);
		}
	}
	else
	{
		/* multi-block paste */

		ChangeText
		{
			i = cb->nblks - 1;

			/* add text from the last block first */
			if (cb->end > 0)
			{
				readcutblk(cb, i);
				tmpblk.c[cb->end] = '\0';
				add(at, tmpblk.c);
				i--;
			}

			/* add intervening blocks */
			while (i > 0)
			{
				readcutblk(cb, i);
				add(at, tmpblk.c);
				i--;
			}

			/* add text from the first cut block */
			readcutblk(cb, 0);
			add(at, &tmpblk.c[cb->start]);
		}
	}

	/* Reporting... */
	rptlines = markline(mark[27]) - markline(at);
	rptlabel = "pasted";

	/* return the mark at the beginning/end of inserted text */
	if (retend)
	{
		return mark[27] - 1L;
	}
	return at;
}




#ifndef NO_AT

/* This function copies characters from a cut buffer into a string.
 * It returns the number of characters in the cut buffer.  If the cut
 * buffer is too large to fit in the string (i.e. if cb2str() returns
 * a number >= size) then the characters will not have been copied.
 * It returns 0 if the cut buffer is empty, and -1 for invalid cut buffers.
 */
int cb2str(name, buf, size)
	int	name;	/* the name of a cut-buffer to get: a-z only! */
	char	*buf;	/* where to put the string */
	unsigned size;	/* size of buf */
{
	REG struct cutbuf	*cb;
	REG char		*src;
	REG char		*dest;

	/* decide which cut buffer to use */
	if (name >= 'a' && name <= 'z')
	{
		cb = &named[name - 'a'];
	}
	else
	{
		return -1;
	}

	/* if the buffer is empty, return 0 */
	if (cb->nblks == 0)
	{
		return 0;
	}

	/* !!! if not a single-block cut, then fail */
	if (cb->nblks != 1)
	{
		return size;
	}

	/* if too big, return the size now, without doing anything */
	if (cb->end - cb->start >= size)
	{
		return cb->end - cb->start;
	}

	/* get the block */
	readcutblk(cb, 0);

	/* isolate the string within that blk */
	if (cb->start == 0)
	{
		tmpblk.c[cb->end] = '\0';
	}
	else
	{
		for (dest = tmpblk.c, src = dest + cb->start; src < tmpblk.c + cb->end; )
		{
			*dest++ = *src++;
		}
		*dest = '\0';
	}

	/* copy the string into the buffer */
	if (buf != tmpblk.c)
	{
		strcpy(buf, tmpblk.c);
	}

	/* return the length */
	return cb->end - cb->start;
}
#endif
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0
Technical Details
Message-ID: <1991Jan11.015719.27142@wlbr.imsd.contel.com>
Path: arc1-ca.pugleaf.net!utzoo!utgpu!news-server.csri.toronto.edu!rutgers!sun-barr!lll-winken!ncis.tis.llnl.gov!blackbird!lonex.radc.af.mil!wlbr!roger.imsd.contel.com!mh

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!utzoo!utgpu!news-server.csri.toronto.edu!rutgers!sun-barr!lll-winken!ncis.tis.llnl.gov!blackbird!lonex.radc.af.mil!wlbr!roger.imsd.contel.com!mh
From: mh@roger.imsd.contel.com (Mike H.)
Newsgroups: comp.windows.news
Subject: NeWS version of elvis (the vi clone) part 3 of 9
Message-ID: <1991Jan11.015719.27142@wlbr.imsd.contel.com>
Date: 11 Jan 91 01:57:19 GMT
Sender: news@wlbr.imsd.contel.com (news)
Distribution: comp
Organization: Contel FSD, Westlake Village, CA
Lines: 4650
Nntp-Posting-Host: roger.imsd.contel.com