🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.windows.news
Article #710

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

#710
From: Mike H.
Date: Fri, 11 Jan 1991 02:01
1546 lines
34840 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:
#	date.c
#	ex.c
#	input.c
# This archive created: Thu Jan 10 17:21:08 1991
export PATH; PATH=/bin:$PATH
if test -f 'date.c'
then
	echo shar: will not over-write existing file "'date.c'"
else
cat << \SHAR_EOF > 'date.c'

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

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


/* This file contains the code for reading ex commands. */

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

#ifndef isascii
# define isascii(c) !((c)&~0x7f)
#endif

/* This data type is used to describe the possible argument combinations */
typedef short ARGT;
#define FROM	1		/* allow a linespec */
#define	TO	2		/* allow a second linespec */
#define BANG	4		/* allow a ! after the command name */
#define EXTRA	8		/* allow extra args after command name */
#define XFILE	16		/* expand wildcards in extra part */
#define NOSPC	32		/* no spaces allowed in the extra part */
#define	DFLALL	64		/* default file range is 1,$ */
#define DFLNONE	128		/* no default file range */
#define NODFL	256		/* do not default to the current file name */
#define EXRCOK	512		/* can be in a .exrc file */
#define NL	1024		/* if mode!=MODE_EX, then write a newline first */
#define PLUS	2048		/* allow a line number, as in ":e +32 foo" */
#define ZERO	4096		/* allow 0 to be given as a line number */
#define FILES	(XFILE + EXTRA)	/* multiple extra files allowed */
#define WORD1	(EXTRA + NOSPC)	/* one extra word allowed */
#define FILE1	(FILES + NOSPC)	/* 1 file allowed, defaults to current file */
#define NAMEDF	(FILE1 + NODFL)	/* 1 file allowed, defaults to "" */
#define NAMEDFS	(FILES + NODFL)	/* multiple files allowed, default is "" */
#define RANGE	(FROM + TO)	/* range of linespecs allowed */
#define NONE	0		/* no args allowed at all */

/* This array maps ex command names to command codes. The order in which
 * command names are listed below is significant -- ambiguous abbreviations
 * are always resolved to be the first possible match.  (e.g. "r" is taken
 * to mean "read", not "rewind", because "read" comes before "rewind")
 */
static struct
{
	char	*name;	/* name of the command */
	CMD	code;	/* enum code of the command */
	void	(*fn)();/* function which executes the command */
	ARGT	argt;	/* command line arguments permitted/needed/used */
}
	cmdnames[] =
{   /*	cmd name	cmd code	function	arguments */
	{"append",	CMD_APPEND,	cmd_append,	FROM+ZERO	},
#ifdef DEBUG
	{"bug",		CMD_DEBUG,	cmd_debug,	RANGE+BANG+EXTRA+NL},
#endif
	{"change",	CMD_CHANGE,	cmd_append,	RANGE		},
	{"delete",	CMD_DELETE,	cmd_delete,	RANGE+WORD1	},
	{"edit",	CMD_EDIT,	cmd_edit,	BANG+FILE1+PLUS	},
	{"file",	CMD_FILE,	cmd_file,	NAMEDF		},
	{"global",	CMD_GLOBAL,	cmd_global,	RANGE+BANG+EXTRA+DFLALL},
	{"insert",	CMD_INSERT,	cmd_append,	FROM		},
	{"join",	CMD_INSERT,	cmd_join,	RANGE		},
	{"k",		CMD_MARK,	cmd_mark,	FROM+WORD1	},
	{"list",	CMD_LIST,	cmd_print,	RANGE+NL	},
	{"move",	CMD_MOVE,	cmd_move,	RANGE+EXTRA	},
	{"next",	CMD_NEXT,	cmd_next,	BANG+NAMEDFS	},
	{"Next",	CMD_PREVIOUS,	cmd_next,	BANG		},
	{"print",	CMD_PRINT,	cmd_print,	RANGE+NL	},
	{"quit",	CMD_QUIT,	cmd_xit,	BANG		},
	{"read",	CMD_READ,	cmd_read,	FROM+ZERO+NAMEDF},
	{"substitute",	CMD_SUBSTITUTE,	cmd_substitute,	RANGE+EXTRA	},
	{"to",		CMD_COPY,	cmd_move,	RANGE+EXTRA	},
	{"undo",	CMD_UNDO,	cmd_undo,	NONE		},
	{"vglobal",	CMD_VGLOBAL,	cmd_global,	RANGE+EXTRA+DFLALL},
	{"write",	CMD_WRITE,	cmd_write,	RANGE+BANG+FILE1+DFLALL},
	{"xit",		CMD_XIT,	cmd_xit,	BANG+NL		},
	{"yank",	CMD_YANK,	cmd_delete,	RANGE+WORD1	},

	{"!",		CMD_BANG,	cmd_shell,	EXRCOK+RANGE+NAMEDFS+DFLNONE+NL},
	{"<",		CMD_SHIFTL,	cmd_shift,	RANGE		},
	{">",		CMD_SHIFTR,	cmd_shift,	RANGE		},
	{"=",		CMD_EQUAL,	cmd_file,	RANGE		},
	{"&",		CMD_SUBAGAIN,	cmd_substitute,	RANGE		},
#ifndef NO_AT
	{"@",		CMD_AT,		cmd_at,		EXTRA		},
#endif

#ifndef NO_ABBR
	{"abbreviate",	CMD_ABBR,	cmd_abbr,	EXRCOK+EXTRA	},
#endif
	{"args",	CMD_ARGS,	cmd_args,	EXRCOK+NAMEDFS	},
#ifndef NO_ERRLIST
	{"cc",		CMD_CC,		cmd_make,	BANG+FILES	},
#endif
	{"cd",		CMD_CD,		cmd_cd,		EXRCOK+NAMEDF	},
	{"copy",	CMD_COPY,	cmd_move,	RANGE+EXTRA	},
#ifndef NO_DIGRAPH
	{"digraph",	CMD_DIGRAPH,	cmd_digraph,	EXRCOK+BANG+EXTRA},
#endif
#ifndef NO_ERRLIST
	{"errlist",	CMD_ERRLIST,	cmd_errlist,	BANG+NAMEDF	},
#endif
	{"ex",		CMD_EDIT,	cmd_edit,	BANG+FILE1	},
	{"map",		CMD_MAP,	cmd_map,	EXRCOK+BANG+EXTRA},
#ifndef NO_MKEXRC
	{"mkexrc",	CMD_MKEXRC,	cmd_mkexrc,	NAMEDF		},
#endif
	{"number",	CMD_NUMBER,	cmd_print,	RANGE+NL	},
	{"put",		CMD_PUT,	cmd_put,	FROM+ZERO+WORD1	},
	{"set",		CMD_SET,	cmd_set,	EXRCOK+EXTRA	},
	{"shell",	CMD_SHELL,	cmd_shell,	NL		},
	{"source",	CMD_SOURCE,	cmd_source,	EXRCOK+NAMEDF	},
	{"tag",		CMD_TAG,	cmd_tag,	BANG+WORD1	},
	{"version",	CMD_VERSION,	cmd_version,	EXRCOK+NONE	},
	{"visual",	CMD_VISUAL,	cmd_visual,	NONE		},
	{"wq",		CMD_WQUIT,	cmd_xit,	NL		},

#ifdef DEBUG
	{"debug",	CMD_DEBUG,	cmd_debug,	RANGE+BANG+EXTRA+NL},
	{"validate",	CMD_VALIDATE,	cmd_validate,	BANG+NL		},
#endif
	{"chdir",	CMD_CD,		cmd_cd,		EXRCOK+NAMEDF	},
#ifndef NO_ERRLIST
	{"make",	CMD_MAKE,	cmd_make,	BANG+NAMEDFS	},
#endif
	{"mark",	CMD_MARK,	cmd_mark,	FROM+WORD1	},
	{"previous",	CMD_PREVIOUS,	cmd_next,	BANG		},
	{"rewind",	CMD_REWIND,	cmd_next,	BANG		},
	{"unmap",	CMD_UNMAP,	cmd_map,	EXRCOK+BANG+EXTRA},
#ifndef NO_ABBR
	{"unabbreviate",CMD_UNABBR,	cmd_abbr,	EXRCOK+WORD1	},
#endif

	{(char *)0}
};


/* This function parses a search pattern - given a pointer to a / or ?,
 * it replaces the ending / or ? with a \0, and returns a pointer to the
 * stuff that came after the pattern.
 */
char	*parseptrn(ptrn)
	REG char	*ptrn;
{
	REG char 	*scan;

	for (scan = ptrn + 1;
	     *scan && *scan != *ptrn;
	     scan++)
	{
		/* allow backslashed versions of / and ? in the pattern */
		if (*scan == '\\' && scan[1] != '\0')
		{
			scan++;
		}
	}
	if (*scan)
	{
		*scan++ = '\0';
	}

	return scan;
}


/* This function parses a line specifier for ex commands */
char *linespec(s, markptr)
	REG char	*s;		/* start of the line specifier */
	MARK		*markptr;	/* where to store the mark's value */
{
	long		num;
	REG char	*t;

	/* parse each ;-delimited clause of this linespec */
	do
	{
		/* skip an initial ';', if any */
		if (*s == ';')
		{
			s++;
		}

		/* skip leading spaces */
		while (isascii(*s) && isspace(*s))
		{
			s++;
		}
	
		/* dot means current position */
		if (*s == '.')
		{
			s++;
			*markptr = cursor;
		}
		/* '$' means the last line */
		else if (*s == '$')
		{
			s++;
			*markptr = MARK_LAST;
		}
		/* digit means an absolute line number */
		else if (isascii(*s) && isdigit(*s))
		{
			for (num = 0; isascii(*s) && isdigit(*s); s++)
			{
				num = num * 10 + *s - '0';
			}
			*markptr = MARK_AT_LINE(num);
		}
		/* appostrophe means go to a set mark */
		else if (*s == '\'')
		{
			s++;
			*markptr = m_tomark(cursor, 1L, (int)*s);
			s++;
		}
		/* slash means do a search */
		else if (*s == '/' || *s == '?')
		{
			/* put a '\0' at the end of the search pattern */
			t = parseptrn(s);
	
			/* search for the pattern */
			*markptr &= ~(BLKSIZE - 1);
			if (*s == '/')
			{
				pfetch(markline(*markptr));
				if (plen > 0)
					*markptr += plen - 1;
				*markptr = m_fsrch(*markptr, s);
			}
			else
			{
				*markptr = m_bsrch(*markptr, s);
			}
	
			/* adjust command string pointer */
			s = t;
		}
	
		/* if linespec was faulty, quit now */
		if (!*markptr)
		{
			return s;
		}
	
		/* maybe add an offset */
		t = s;
		if (*t == '-' || *t == '+')
		{
			s++;
			for (num = 0; *s >= '0' && *s <= '9'; s++)
			{
				num = num * 10 + *s - '0';
			}
			if (num == 0)
			{
				num = 1;
			}
			*markptr = m_updnto(*markptr, num, *t);
		}
	} while (*s == ';' || *s == '+' || *s == '-');

	return s;
}



/* This function reads an ex command and executes it. */
void ex()
{
	char		cmdbuf[80];
	REG int		cmdlen;
	static long	oldline;
	
	significant = FALSE;
	oldline = markline(cursor);

	while (mode == MODE_EX)
	{
		/* read a line */
		cmdlen = vgets(':', cmdbuf, sizeof cmdbuf);
		if (cmdlen < 0)
		{
			return;
		}

		/* if empty line, assume ".+1" */
		if (cmdlen == 0)
		{
			strcpy(cmdbuf, ".+1");
			qaddch('\r');
			clrtoeol();
		}
		else
		{
			addch('\n');
		}
		refresh();

		/* parse & execute the command */
		doexcmd(cmdbuf);

		/* handle autoprint */
		if (significant || markline(cursor) != oldline)
		{
			significant = FALSE;
			oldline = markline(cursor);
			if (*o_autoprint && mode == MODE_EX)
			{
				cmd_print(cursor, cursor, CMD_PRINT, FALSE, "");
			}
		}
	}
}

void doexcmd(cmdbuf)
	char		*cmdbuf;	/* string containing an ex command */
{
	REG char	*scan;		/* used to scan thru cmdbuf */
	MARK		frommark;	/* first linespec */
	MARK		tomark;		/* second linespec */
	REG int		cmdlen;		/* length of the command name given */
	CMD		cmd;		/* what command is this? */
	ARGT		argt;		/* argument types for this command */
	short		forceit;	/* bang version of a command? */
	REG int		cmdidx;		/* index of command */
	REG char	*build;		/* used while copying filenames */
	int		iswild;		/* boolean: filenames use wildcards? */
	int		isdfl;		/* using default line ranges? */
	int		didsub;		/* did we substitute file names for % or # */


	/* ex commands can't be undone via the shift-U command */
	U_line = 0L;

	/* ignore command lines that start with a double-quote */
	if (*cmdbuf == '"')
	{
		return;
	}

	/* permit extra colons at the start of the line */
	while (*cmdbuf == ':')
	{
		cmdbuf++;
	}

	/* parse the line specifier */
	scan = cmdbuf;
	if (nlines < 1)
	{
		/* no file, so don't allow addresses */
	}
	else if (*scan == '%')
	{
		/* '%' means all lines */
		frommark = MARK_FIRST;
		tomark = MARK_LAST;
		scan++;
	}
	else if (*scan == '0')
	{
		frommark = tomark = MARK_UNSET;
		scan++;
	}
	else
	{
		frommark = cursor;
		scan = linespec(scan, &frommark);
		tomark = frommark;
		if (frommark && *scan == ',')
		{
			scan++;
			scan = linespec(scan, &tomark);
		}
		if (!tomark)
		{
			/* faulty line spec -- fault already described */
			return;
		}
		if (frommark > tomark)
		{
			msg("first address exceeds the second");
			return;
		}
	}
	isdfl = (scan == cmdbuf);

	/* skip whitespace */
	while (isascii(*scan) && isspace(*scan))
	{
		scan++;
	}

	/* if no command, then just move the cursor to the mark */
	if (!*scan)
	{
		cursor = tomark;
		return;
	}

	/* figure out how long the command name is */
	if (isascii(*scan) && !isalpha(*scan))
	{
		cmdlen = 1;
	}
	else
	{
		for (cmdlen = 1;
		     !isascii(scan[cmdlen]) || isalpha(scan[cmdlen]);
		     cmdlen++)
		{
		}
	}

	/* lookup the command code */
	for (cmdidx = 0;
	     cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, cmdlen);
	     cmdidx++)
	{
	}
	argt = cmdnames[cmdidx].argt;
	cmd = cmdnames[cmdidx].code;
	if (cmd == CMD_NULL)
	{
#if OSK
		msg("Unknown command \"%s\"", scan);
#else
		msg("Unknown command \"%.*s\"", cmdlen, scan);
#endif
		return;
	}

	/* if the command ended with a bang, set the forceit flag */
	scan += cmdlen;
	if ((argt & BANG) && *scan == '!')
	{
		scan++;
		forceit = 1;
	}
	else
	{
		forceit = 0;
	}

	/* skip any more whitespace, to leave scan pointing to arguments */
	while (isascii(*scan) && isspace(*scan))
	{
		scan++;
	}

	/* a couple of special cases for filenames */
	if (argt & XFILE)
	{
		/* if names were given, process them */
		if (*scan)
		{
			for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++)
			{
				switch (*scan)
				{
				  case '%':
					if (!*origname)
					{
						msg("No filename to substitute for %%");
						return;
					}
					strcpy(build, origname);
					while (*build)
					{
						build++;
					}
					didsub = TRUE;
					break;
	
				  case '#':
					if (!*prevorig)
					{
						msg("No filename to substitute for #");
						return;
					}
					strcpy(build, prevorig);
					while (*build)
					{
						build++;
					}
					didsub = TRUE;
					break;
	
				  case '*':
				  case '?':
#if !(MSDOS || TOS)
				  case '[':
				  case '`':
				  case '{': /* } */
				  case '$':
				  case '~':
#endif
					*build++ = *scan;
					iswild = TRUE;
					break;

				  default:
					*build++ = *scan;
				}
			}
			*build = '\0';
	
			if (cmd == CMD_BANG
			 || cmd == CMD_READ && tmpblk.c[0] == '!'
			 || cmd == CMD_WRITE && tmpblk.c[0] == '!')
			{
				if (didsub)
				{
					if (mode != MODE_EX)
					{
						addch('\n');
					}
					addstr(tmpblk.c);
					addch('\n');
					exrefresh();
				}
			}
			else
			{
				if (iswild && tmpblk.c[0] != '>')
				{
					scan = wildcard(tmpblk.c);
				}
			}
		}
		else /* no names given, maybe assume origname */
		{
			if (!(argt & NODFL))
			{
				strcpy(tmpblk.c, origname);
			}
			else
			{
				*tmpblk.c = '\0';
			}
		}

		scan = tmpblk.c;
	}

	/* bad arguments? */
	if (!(argt & EXRCOK) && nlines < 1L)
	{
		msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC);
		return;
	}
	if (!(argt & (ZERO | EXRCOK)) && frommark == MARK_UNSET)
	{
		msg("Can't use address 0 with \"%s\" command.", cmdnames[cmdidx].name);
		return;
	}
	if (!(argt & FROM) && frommark != cursor && nlines >= 1L)
	{
		msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name);
		return;
	}
	if (!(argt & TO) && tomark != frommark && nlines >= 1L)
	{
		msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name);
		return;
	}
	if (!(argt & EXTRA) && *scan)
	{
		msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name);
		return;
	}
	if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!')))
	{
		build = scan;
#ifndef CRUNCH
		if ((argt & PLUS) && *build == '+')
		{
			while (*build && !(isascii(*build) && isspace(*build)))
			{
				build++;
			}
			while (*build && isascii(*build) && isspace(*build))
			{
				build++;
			}
		}
#endif /* not CRUNCH */
		for (; *build; build++)
		{
			if (isspace(*build))
			{
				msg("Too many %s to \"%s\" command.",
					(argt & XFILE) ? "filenames" : "arguments",
					cmdnames[cmdidx].name);
				return;
			}
		}
	}

	/* some commands have special default ranges */
	if (isdfl && (argt & DFLALL))
	{
		frommark = MARK_FIRST;
		tomark = MARK_LAST;
	}
	else if (isdfl && (argt & DFLNONE))
	{
		frommark = tomark = 0L;
	}

	/* write a newline if called from visual mode */
	if ((argt & NL) && mode != MODE_EX && !exwrote)
	{
		addch('\n');
		exrefresh();
	}

	/* act on the command */
	(*cmdnames[cmdidx].fn)(frommark, tomark, cmd, forceit, scan);
}


/* This function executes EX commands from a file.  It returns 1 normally, or
 * 0 if the file could not be opened for reading.
 */
int doexrc(filename)
	char	*filename;	/* name of a ".exrc" file */
{
	int	fd;		/* file descriptor */
	int	len;		/* length of the ".exrc" file */
	char	buf[MAXRCLEN];	/* buffer, holds the entire .exrc file */

	/* open the file, read it, and close */
	fd = open(filename, O_RDONLY);
	if (fd < 0)
	{
		return 0;
	}
	len = tread(fd, buf, MAXRCLEN);
	close(fd);

	/* execute the string */
	exstring(buf, len);

	return 1;
}

void exstring(buf, len)
	char	*buf;	/* the commands to execute */
	int	len;	/* the length of the string */
{
	char	*cmd;		/* start of a command */
	char	*end;		/* used to search for the end of cmd */

	/* find & do each command */
	for (cmd = buf; cmd < &buf[len]; cmd = end + 1)
	{
		/* find the end of the command */
		for (end = cmd; end < &buf[len] && *end != '\n' && *end != '|'; end++)
		{
		}
		*end = '\0';

		/* do it */
		doexcmd(cmd);
	}
}
SHAR_EOF
fi # end of overwriting check
if test -f 'input.c'
then
	echo shar: will not over-write existing file "'input.c'"
else
cat << \SHAR_EOF > 'input.c'
/* input.c */

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


/* This file contains the input() function, which implements vi's INPUT mode.
 * It also contains the code that supports digraphs.
 */

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


#ifndef NO_DIGRAPH
static struct _DIG
{
	struct _DIG	*next;
	char		key1;
	char		key2;
	char		dig;
	char		save;
} *digs;

char digraph(key1, key2)
	char	key1;	/* the underlying character */
	char	key2;	/* the second character */
{
	int		newkey;
	REG struct _DIG	*dp;

	/* if digraphs are disabled, then just return the new char */
	if (!*o_digraph)
	{
		return key2;
	}

	/* remember the new key, so we can return it if this isn't a digraph */
	newkey = key2;

	/* sort key1 and key2, so that their original order won't matter */
	if (key1 > key2)
	{
		key2 = key1;
		key1 = newkey;
	}

	/* scan through the digraph chart */
	for (dp = digs;
	     dp && (dp->key1 != key1 || dp->key2 != key2);
	     dp = dp->next)
	{
	}

	/* if this combination isn't in there, just use the new key */
	if (!dp)
	{
		return newkey;
	}

	/* else use the digraph key */
	return dp->dig;
}

/* this function lists or defines digraphs */
void do_digraph(bang, extra)
	int	bang;
	char	extra[];
{
	int		dig;
	REG struct _DIG	*dp;
	struct _DIG	*prev;
	static int	user_defined = FALSE; /* boolean: are all later digraphs user-defined? */
	char		listbuf[8];

	/* if "extra" is NULL, then we've reached the end of the built-ins */
	if (!extra)
	{
		user_defined = TRUE;
		return;
	}

	/* if no args, then display the existing digraphs */
	if (*extra < ' ')
	{
		listbuf[0] = listbuf[1] = listbuf[2] = listbuf[5] = ' ';
		listbuf[7] = '\0';
		for (dig = 0, dp = digs; dp; dp = dp->next)
		{
			if (dp->save || bang)
			{
				dig += 7;
				if (dig >= COLS)
				{
					addch('\n');
					exrefresh();
					dig = 7;
				}
				listbuf[3] = dp->key1;
				listbuf[4] = dp->key2;
				listbuf[6] = dp->dig;
				qaddstr(listbuf);
			}
		}
		addch('\n');
		exrefresh();
		return;
	}

	/* make sure we have at least two characters */
	if (!extra[1])
	{
		msg("Digraphs must be composed of two characters");
		return;
	}

	/* sort key1 and key2, so that their original order won't matter */
	if (extra[0] > extra[1])
	{
		dig = extra[0];
		extra[0] = extra[1];
		extra[1] = dig;
	}

	/* locate the new digraph character */
	for (dig = 2; extra[dig] == ' ' || extra[dig] == '\t'; dig++)
	{
	}
	dig = extra[dig];
	if (!bang && dig)
	{
		dig |= 0x80;
	}

	/* search for the digraph */
	for (prev = (struct _DIG *)0, dp = digs;
	     dp && (dp->key1 != extra[0] || dp->key2 != extra[1]);
	     prev = dp, dp = dp->next)
	{
	}

	/* deleting the digraph? */
	if (!dig)
	{
		if (!dp)
		{
#ifndef CRUNCH
			msg("%c%c not a digraph", extra[0], extra[1]);
#endif
			return;
		}
		if (prev)
			prev->next = dp->next;
		else
			digs = dp->next;
		free(dp);
		return;
	}

	/* if necessary, create a new digraph struct for the new digraph */
	if (dig && !dp)
	{
		dp = (struct _DIG *)malloc(sizeof *dp);
		if (!dp)
		{
			msg("Out of space in the digraph table");
			return;
		}
		if (prev)
			prev->next = dp;
		else
			digs = dp;
		dp->next = (struct _DIG *)0;
	}

	/* assign it the new digraph value */
	dp->key1 = extra[0];
	dp->key2 = extra[1];
	dp->dig = dig;
	dp->save = user_defined;
}

# ifndef NO_MKEXRC
void savedigs(fd)
	int		fd;
{
	static char	buf[] = "digraph! XX Y\n";
	REG struct _DIG	*dp;

	for (dp = digs; dp; dp = dp->next)
	{
		if (dp->save)
		{
			buf[9] = dp->key1;
			buf[10] = dp->key2;
			buf[12] = dp->dig;
			write(fd, buf, (unsigned)14);
		}
	}
}
# endif
#endif


#ifndef NO_ABBR
static struct _AB
{
	struct _AB	*next;
	char		*large;		/* the expanded form */
	char		small[1];	/* the abbreviated form (appended to struct) */
}
	*abbrev;

/* This functions lists or defines abbreviations */
void do_abbr(extra)
	char	*extra;
{
	int		smlen;	/* length of the small form */
	int		lrg;	/* index of the start of the large form */
	REG struct _AB	*ab;	/* used to move through the abbrev list */
	struct _AB	*prev;

	/* no arguments? */
	if (!*extra)
	{
		/* list all current abbreviations */
		for (ab = abbrev; ab; ab = ab->next)
		{
			qaddstr("abbr ");
			qaddstr(ab->small);
			qaddch(' ');
			qaddstr(ab->large);
			addch('\n');
			exrefresh();
		}
		return;
	}

	/* else one or more arguments.  Parse the first & look up in abbrev[] */
	for (smlen = 0; extra[smlen] && isalnum(extra[smlen]); smlen++)
	{
	}
	for (prev = (struct _AB *)0, ab = abbrev; ab; prev = ab, ab = ab->next)
	{
		if (!strncmp(extra, ab->small, smlen) && !ab->small[smlen])
		{
			break;
		}
	}

	/* locate the start of the large form, if any */
	for (lrg = smlen; extra[lrg] && isascii(extra[lrg]) && isspace(extra[lrg]); lrg++)
	{
	}

	/* only one arg? */
	if (!extra[lrg])
	{
		/* trying to undo an abbreviation which doesn't exist? */
		if (!ab)
		{
#ifndef CRUNCH
			msg("\"%s\" not an abbreviation", extra);
#endif
			return;
		}

		/* undo the abbreviation */
		if (prev)
			prev->next = ab->next;
		else
			abbrev = ab->next;
		free(ab->large);
		free(ab);

		return;
	}

	/* multiple args - [re]define an abbreviation */
	if (ab)
	{
		/* redefining - free the old large form */
		free(ab->large);
	}
	else
	{
		/* adding a new definition - make a new struct */
		ab = (struct _AB *)malloc((unsigned)(smlen + sizeof *ab));
#ifndef CRUNCH
		if (!ab)
		{
			msg("Out of memory -- Sorry");
			return;
		}
#endif
		strncpy(ab->small, extra, smlen);
		ab->small[smlen] = '\0';
		ab->next = (struct _AB *)0;
		if (prev)
			prev->next = ab;
		else
			abbrev = ab;
	}

	/* store the new form */
	ab->large = (char *)malloc((unsigned)(strlen(&extra[lrg]) + 1));
	strcpy(ab->large, &extra[lrg]);
}


# ifndef NO_MKEXRC
/* This function is called from cmd_mkexrc() to save the abbreviations */
void saveabbr(fd)
	int	fd;	/* fd to which the :abbr commands should be written */
{
	REG struct _AB	*ab;

	for (ab = abbrev; ab; ab = ab->next)
	{
		twrite(fd, "abbr ", 5);
		twrite(fd, ab->small, strlen(ab->small));
		twrite(fd, " ", 1);
		twrite(fd, ab->large, strlen(ab->large));
		twrite(fd, "\n", 1);
	}
}
# endif

/* This function should be called before each char is inserted.  If the next
 * char is non-alphanumeric and we're at the end of a word, then that word
 * is checked against the abbrev[] array and expanded, if appropriate.  Upon
 * returning from this function, the new char still must be inserted.
 */
static MARK expandabbr(m, ch)
	MARK		m;	/* the cursor position */
	int		ch;	/* the character to insert */
{
	char		*word;	/* where the word starts */
	int		len;	/* length of the word */
	REG struct _AB	*ab;

	/* if no abbreviations are in effect, or ch is aphanumeric, then
	 * don't do anything
	 */
	if (!abbrev || !isascii(ch) || isalnum(ch))
	{
		return m;
	}

	/* see where the preceding word starts */
	pfetch(markline(m));
	for (word = ptext + markidx(m), len = 0;
	     --word >= ptext && (!isascii(*word) || isalnum(*word));
	     len++)
	{
	}
	word++;

	/* if zero-length, then it isn't a word, really -- so nothing */
	if (len == 0)
	{
		return m;
	}

	/* look it up in the abbrev list */
	for (ab = abbrev; ab; ab = ab->next)
	{
		if (!strncmp(ab->small, word, len) && !ab->small[len])
		{
			break;
		}
	}

	/* not an abbreviation? then do nothing */
	if (!ab)
	{
		return m;
	}

	/* else replace the small form with the large form */
	add(m, ab->large);
	delete(m - len, m);

	/* return with the cursor after the end of the large form */
	return m - len + strlen(ab->large);
}
#endif

		
/* This function allows the user to replace an existing (possibly zero-length)
 * chunk of text with typed-in text.  It returns the MARK of the last character
 * that the user typed in.
 */
MARK input(from, to, when, indentref)
	MARK	from;	/* where to start inserting text */
	MARK	to;	/* extent of text to delete */
	int	when;	/* either WHEN_VIINP or WHEN_VIREP */
	long	indentref; /* line rel to current mark to use for autoindent
							reference - MCH */
{
	char	key[2];	/* key char followed by '\0' char */
	char	*build;	/* used in building a newline+indent string */
	char	*scan;	/* used while looking at the indent chars of a line */
	MARK	m;	/* some place in the text */
#ifndef NO_EXTENSIONS
	int	quit = FALSE;	/* boolean: are we exiting after this? */
#endif

#ifdef DEBUG
	/* if "from" and "to" are reversed, complain */
	if (from > to)
	{
		msg("ERROR: input(%ld:%d, %ld:%d)",
			markline(from), markidx(from),
			markline(to), markidx(to));
		return MARK_UNSET;
	}
#endif

	key[1] = 0;

	/* if we're replacing text with new text, save the old stuff */
	/* (Alas, there is no easy way to save text for replace mode) */
	if (from != to)
	{
		cut(from, to);
	}

	ChangeText
	{
		/* if doing a dot command, then reuse the previous text */
		if (doingdot)
		{
			/* delete the text that's there now */
			if (from != to)
			{
				delete(from, to);
			}

			/* insert the previous text */
			cutname('.');
			cursor = paste(from, FALSE, TRUE) + 1L;
		}
		else /* interactive version */
		{
			/* if doing a change within the line... */
			if (from != to && markline(from) == markline(to))
			{
				/* mark the end of the text with a "$" */
				change(to - 1, to, "$");
			}
			else
			{
				/* delete the old text right off */
				if (from != to)
				{
					delete(from, to);
				}
				to = from;
			}

			/* handle autoindent of the first line, maybe */
			cursor = from;
			if (*o_autoindent && markline(cursor) > 1L && markidx(cursor) == 0)
			{
				/* Only autoindent blank lines. */
				pfetch(markline(cursor));
				if (plen == 0)
				{
					/* Okay, we really want to autoindent */
					pfetch(markline(cursor) + indentref);
					for (scan = ptext, build = tmpblk.c;
					     *scan == ' ' || *scan == '\t';
					     )
					{
						*build++ = *scan++;
					}
					if (build > tmpblk.c)
					{
						*build = '\0';
						add(cursor, tmpblk.c);
						cursor += (build - tmpblk.c);
					}
				}
			}

			/* repeatedly add characters from the user */
			for (;;)
			{
				/* Get a character */
				redraw(cursor, TRUE);
#ifdef DEBUG
				msg("cursor=%ld.%d, to=%ld.%d",
					markline(cursor), markidx(cursor),
					markline(to), markidx(to));
#endif
				key[0] = getkey(when);

				/* if whitespace & wrapmargin is set & we're
				 * past the warpmargin, then change the
				 * whitespace character into a newline
				 */
				if ((*key == ' ' || *key == '\t')
				 && *o_wrapmargin != 0)
				{
					pfetch(markline(cursor));
					if (idx2col(cursor, ptext, TRUE) > COLS - (*o_wrapmargin & 0xff))
					{
						*key = '\n';
					}
				}

				/* process it */
				switch (*key)
				{
#ifndef NO_EXTENSIONS
				  case 0: /* special movement mapped keys */
					*key = getkey(0);
					switch (*key)
					{
					  case 'h':	m = m_left(cursor, 0L);		break;
					  case 'j':
					  case 'k':	m = m_updnto(cursor, 0L, *key);	break;
					  case 'l':	m = cursor + 1;			break;
					  case 'b':	m = m_bword(cursor, 0L);	break;
					  case 'w':	m = m_fword(cursor, 0L);	break;
					  case '^':	m = m_front(cursor, 0L);	break;
					  case '$':	m = m_rear(cursor, 0L);		break;
					  case ctrl('B'):
					  case ctrl('F'):
							m = m_scroll(cursor, 0L, *key); break;
					  case 'x':	m = v_xchar(cursor, 0L);	break;
					  case 'i':	m = to = from = cursor;		break;
					  default:	m = MARK_UNSET;			break;
					}
					/* adjust the moved cursor */
					m = adjmove(cursor, m, (*key == 'j' || *key == 'k' ? 0x20 : 0));
					if (*key == '$' || (*key == 'l' && m <= cursor))
					{
						m++;
					}
					/* if the cursor is reasonable, use it */
					if (m == MARK_UNSET)
					{
						Beep();
					}
					else
					{
						if (to > cursor)
						{
							delete(cursor, to);
							redraw(cursor, TRUE);
						}
						from = to = cursor = m;
					}
					break;

				  case ctrl('Z'):
					if (getkey(0) == ctrl('Z'))
					{
						quit = TRUE;
						goto BreakBreak;
					}
					break;
#endif

				  case ctrl('['):
#ifndef NO_ABBR
					cursor = expandabbr(cursor, ctrl('['));
#endif
					goto BreakBreak;

				  case ctrl('U'):
					if (markline(cursor) == markline(from))
					{
						cursor = from;
					}
					else
					{
						cursor &= ~(BLKSIZE - 1);
					}
					break;

				  case ctrl('D'):
				  case ctrl('T'):
					if (to > cursor)
					{
						delete(cursor, to);
					}
					mark[27] = cursor;
					cmd_shift(cursor, cursor, *key == ctrl('D') ? CMD_SHIFTL : CMD_SHIFTR, TRUE, "");
					if (mark[27])
					{
						cursor = mark[27];
					}
					else
					{
						cursor = m_front(cursor, 0L);
					}
					to = cursor;
					break;

				  case '\b':
					if (cursor <= from)
					{
						Beep();
					}
					else if (markidx(cursor) == 0)
					{
						cursor -= BLKSIZE;
						pfetch(markline(cursor));
						cursor += plen;
					}
					else
					{
						cursor--;
					}
					break;

				  case ctrl('W'):
					m = m_bword(cursor, 1L);
					if (markline(m) == markline(cursor) && m >= from)
					{
						cursor = m;
						if (from > cursor)
						{
							from = cursor;
						}
					}
					else
					{
						Beep();
					}
					break;

				  case '\n':
#if OSK
				  case '\l':
#else				  
				  case '\r':
#endif
#ifndef NO_ABBR
					cursor = expandabbr(cursor, '\n');
#endif
					build = tmpblk.c;
					*build++ = '\n';
					if (*o_autoindent)
					{
						/* figure out indent for next line */
						pfetch(markline(cursor));
						for (scan = ptext; *scan == ' ' || *scan == '\t'; )
						{
							*build++ = *scan++;
						}

						/* remove indent from this line, if blank */
						if (!*scan && plen > 0)
						{
							to = cursor &= ~(BLKSIZE - 1);
							delete(cursor, cursor + plen);
						}
					}
					*build = 0;
					if (cursor >= to && when != WHEN_VIREP)
					{
						add(cursor, tmpblk.c);
					}
					else
					{
						change(cursor, to, tmpblk.c);
					}
					redraw(cursor, TRUE);
					to = cursor = (cursor & ~(BLKSIZE - 1))
							+ BLKSIZE
							+ (int)(build - tmpblk.c) - 1;
					break;

				  case ctrl('A'):
				  case ctrl('P'):
					if (cursor < to)
					{
						delete(cursor, to);
					}
					if (*key == ctrl('A'))
					{
						cutname('.');
					}
					to = cursor = paste(cursor, FALSE, TRUE) + 1L;
					break;

				  case ctrl('V'):
					if (cursor >= to && when != WHEN_VIREP)
					{
						add(cursor, "^");
					}
					else
					{
						change(cursor, to, "^");
						to = cursor + 1;
					}
					redraw(cursor, TRUE);
					*key = getkey(0);
					if (*key == '\n')
					{
						/* '\n' too hard to handle */
#if OSK
						*key = '\l';
#else
						*key = '\r';
#endif
					}
					change(cursor, cursor + 1, key);
					cursor++;
					if (cursor > to)
					{
						to = cursor;
					}
					break;

				  case ctrl('L'):
				  case ctrl('R'):
					redraw(MARK_UNSET, FALSE);
					break;

				  default:
					if (cursor >= to && when != WHEN_VIREP)
					{
#ifndef NO_ABBR
						cursor = expandabbr(cursor, *key);
#endif
						add(cursor, key);
						cursor++;
						to = cursor;
					}
					else
					{
						pfetch(markline(cursor));
						if (markidx(cursor) == plen)
						{
#ifndef NO_ABBR
							cursor = expandabbr(cursor, *key);
#endif
							add(cursor, key);
						}
						else
						{
#ifndef NO_DIGRAPH
							*key = digraph(ptext[markidx(cursor)], *key);
#endif
#ifndef NO_ABBR
							cursor = expandabbr(cursor, *key);
#endif
							change(cursor, cursor + 1, key);
						}
						cursor++;
					}
#ifndef NO_SHOWMATCH
					/* show matching "({[" if neceesary */
					if (*o_showmatch && strchr(")}]", *key))
					{
						redraw(cursor, TRUE);
						m = m_match(cursor - 1, 0L);
						if (markline(m) >= topline
						 && markline(m) <= botline)
						{
							redraw(m, TRUE);
							refresh();
							sleep(1);
						}
					}
#endif
				} /* end switch(*key) */
			} /* end for(;;) */
BreakBreak:;

			/* delete any excess characters */

			/* this includes any whitespace on a whitespace
			only line if we are in autoindent mode - MCH */

			if (*o_autoindent)
			{
				/* remove indent from this line, if blank */
				pfetch(markline(cursor));
				for (scan = ptext; *scan == ' ' || *scan == '\t'; )
					*scan++;
				if (!*scan && plen > 0)
				{
					to = cursor &= ~(BLKSIZE - 1);
					delete(cursor, cursor + plen);
				}
			}
			
			if (cursor < to)
			{
				delete(cursor, to);
			}

		} /* end if doingdot else */

	} /* end ChangeText */

	/* put the new text into a cut buffer for possible reuse */
	if (!doingdot)
	{
		blksync();
		cutname('.');
		cut(from, cursor);
	}

	/* move to last char that we inputted, unless it was newline */
	if (markidx(cursor) != 0)
	{
		cursor--;
	}
	redraw(cursor, FALSE);

#ifndef NO_EXTENSIONS
	if (quit)
	{
		/* if this is a nested "do", then cut it short */
		abortdo();

		/* exit, unless we can't write out the file */
		cursor = v_xit(cursor, 0L, 'Z');
	}
#endif

	rptlines = 0L;
	return cursor;
}
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0
Technical Details
Message-ID: <1991Jan11.020153.27223@wlbr.imsd.contel.com>
Path: arc1-ca.pugleaf.net!utzoo!utgpu!news-server.csri.toronto.edu!rutgers!apple!voder!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!apple!voder!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 4 of 9
Message-ID: <1991Jan11.020153.27223@wlbr.imsd.contel.com>
Date: 11 Jan 91 02:01:53 GMT
Sender: news@wlbr.imsd.contel.com (news)
Distribution: comp
Organization: Contel FSD, Westlake Village, CA
Lines: 1546
Nntp-Posting-Host: roger.imsd.contel.com