/*
 *  ed - standard editor
 *  ~~
 *	Authors: Brian Beattie, Kees Bot, and others
 *
 * Copyright 1987 Brian Beattie Rights Reserved.
 * Permission to copy or distribute granted under the following conditions:
 * 1). No charge may be made other than reasonable charges for reproduction.
 * 2). This notice must remain intact.
 * 3). No further restrictions may be added.
 * 4). Except meaningless ones.
 *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 *  TurboC mods and cleanup 8/17/88 RAMontante.
 *  Further information (posting headers, etc.) at end of file.
 *  RE stuff replaced with Spencerian version, sundry other bugfix+speedups
 *  Ian Phillipps. Version incremented to "5".
 * _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 */

int	version = 5;	/* used only in the "set" function, for i.d. */

#include <stdio.h>
#include <string.h>
/* Regexp is Henry Spencer's package. WARNING: regsub is modified to return
 * a pointer to the \0 after the destination string, and this program refers
 * to the "private" reganch field in the struct regexp.
 */
#include "lint.h"
#include "regexp.h"
#include "interpret.h"
#include "object.h"
#include "config.h"

/*
 *	#defines for non-printing ASCII characters
 */
#define NUL	0x00	/* ^@ */
#define EOS	0x00	/* end of string */
#define SOH	0x01	/* ^A */
#define STX	0x02	/* ^B */
#define ETX	0x03	/* ^C */
#define EOT	0x04	/* ^D */
#define ENQ	0x05	/* ^E */
#define ACK	0x06	/* ^F */
#define BEL	0x07	/* ^G */
#define BS	0x08	/* ^H */
#define HT	0x09	/* ^I */
#define LF	0x0a	/* ^J */
#define NL	'\n'
#define VT	0x0b	/* ^K */
#define FF	0x0c	/* ^L */
#define CR	0x0d	/* ^M */
#define SO	0x0e	/* ^N */
#define SI	0x0f	/* ^O */
#define DLE	0x10	/* ^P */
#define DC1	0x11	/* ^Q */
#define DC2	0x12	/* ^R */
#define DC3	0x13	/* ^S */
#define DC4	0x14	/* ^T */
#define NAK	0x15	/* ^U */
#define SYN	0x16	/* ^V */
#define ETB	0x17	/* ^W */
#define CAN	0x18	/* ^X */
#define EM	0x19	/* ^Y */
#define SUB	0x1a	/* ^Z */
#define ESC	0x1b	/* ^[ */
#define FS	0x1c	/* ^\ */
#define GS	0x1d	/* ^] */
#define RS	0x1e	/* ^^ */
#define US	0x1f	/* ^_ */
#define SP	0x20	/* space */
#define DEL	0x7f	/* DEL*/
#define ESCAPE  '\\'


#define TRUE	1
#define FALSE	0
#define ERR		-2
#define FATAL		(ERR-1)
#define CHANGED		(ERR-2)
#define SET_FAIL	(ERR-3)
#define SUB_FAIL	(ERR-4)
#define MEM_FAIL	(ERR-5)


#define	BUFFER_SIZE	2048	/* stream-buffer size:  == 1 hd cluster */

#define LINFREE	1	/* entry not in use */
#define LGLOB	2       /* line marked global */

#define MAXLINE	512	/* max number of chars per line */
#define MAXPAT	256	/* max number of chars per replacement pattern */
#define MAXFNAME 256	/* max file name size */


/**  Global variables  **/

struct	line {
	int		l_stat;		/* empty, mark */
	struct line	*l_prev;
	struct line	*l_next;
	char		l_buff[1];
};
typedef struct line	LINE;

extern struct object *command_giver;
void set_prompt PROT((char *));

int doprnt PROT((int, int));
int ins PROT((char *));
int deflt PROT((int, int));

#define P_DIAG		(command_giver->ed_buffer->diag)
#define P_TRUNCFLG	(command_giver->ed_buffer->truncflg)
#define P_EIGHTBIT	(command_giver->ed_buffer->eightbit)
#define P_NONASCII	(command_giver->ed_buffer->nonascii)
#define P_NULLCHAR	(command_giver->ed_buffer->nullchar)
#define P_TRUNCATED	(command_giver->ed_buffer->truncated)
#define P_FNAME		(command_giver->ed_buffer->fname)
#define P_FCHANGED	(command_giver->ed_buffer->fchanged)
#define P_NOFNAME	(command_giver->ed_buffer->nofname)
#define P_MARK		(command_giver->ed_buffer->mark)
#define P_OLDPAT	(command_giver->ed_buffer->oldpat)
#define P_LINE0		(command_giver->ed_buffer->Line0)
#define P_LINE0		(command_giver->ed_buffer->Line0)
#define P_CURLN		(command_giver->ed_buffer->CurLn)
#define P_CURPTR	(command_giver->ed_buffer->CurPtr)
#define P_LASTLN	(command_giver->ed_buffer->LastLn)
#define P_LINE1		(command_giver->ed_buffer->Line1)
#define P_LINE2		(command_giver->ed_buffer->Line2)
#define P_NLINES	(command_giver->ed_buffer->nlines)
#define P_PFLAG		(command_giver->ed_buffer->pflag)
#define P_NFLG		(command_giver->ed_buffer->nflg)
#define P_LFLG		(command_giver->ed_buffer->lflg)
#define P_PFLG		(command_giver->ed_buffer->pflg)
#define P_APPENDING	(command_giver->ed_buffer->appending)

char	inlin[MAXLINE];
char	*inptr;		/* tty input buffer */
struct ed_buffer {
	int	diag;		/* diagnostic-output? flag */
	int	truncflg;	/* truncate long line flag */
	int	eightbit;	/* save eighth bit */
	int	nonascii;	/* count of non-ascii chars read */
	int	nullchar;	/* count of null chars read */
	int	truncated;	/* count of lines truncated */
	char	fname[MAXFNAME];
	int	fchanged;	/* file-changed? flag */
	int	nofname;
	int	mark['z'-'a'+1];
	regexp	*oldpat;
	
	LINE	Line0;
	int	CurLn;
	LINE	*CurPtr;	/* CurLn and CurPtr must be kept in step */
	int	LastLn;
	int	pflag;
	int	Line1, Line2, nlines;
	int	nflg;		/* print line number flag */
	int	lflg;		/* print line in verbose mode */
	int	pflg;		/* print current line after each command */
	int	appending;
};

#if 0

struct tbl {
	char	*t_str;
	int	*t_ptr;
	int	t_val;
} *t, tbl[] = {
	"number",	&nflg,		TRUE,
	"nonumber",	&nflg,		FALSE,
	"list",		&lflg,		TRUE,
	"nolist",	&lflg,		FALSE,
	"eightbit",	&eightbit,	TRUE,
	"noeightbit",	&eightbit,	FALSE,
	0
};
#endif


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

extern	char	*strcpy(), *strncpy();
extern	char	*xalloc();
extern	LINE	*getptr();
extern	char	*gettxt();
extern	char	*gettxtl();
extern	char	*catsub();
extern	void	prntln(), putcntl(), error();
regexp	*optpat();


/*________  Macros  ________________________________________________________*/

#ifndef max
#  define max(a,b)	((a) > (b) ? (a) : (b))
#endif

#ifndef min
#  define min(a,b)	((a) < (b) ? (a) : (b))
#endif

#ifndef toupper
#  define toupper(c)	((c >= 'a' && c <= 'z') ? c-32 : c )
#endif

#define nextln(l)	((l)+1 > P_LASTLN ? 0 : (l)+1)
#define prevln(l)	((l)-1 < 0 ? P_LASTLN : (l)-1)

#define gettxtl(lin)	((lin)->l_buff)
#define gettxt(num)	(gettxtl( getptr(num) ))

#define getnextptr(p)	((p)->l_next)
#define getprevptr(p)	((p)->l_prev)

#define setCurLn( lin )	( P_CURPTR = getptr( P_CURLN = (lin) ) )
#define nextCurLn()	( P_CURLN = nextln(P_CURLN), P_CURPTR = getnextptr( P_CURPTR ) )
#define prevCurLn()	( P_CURLN = prevln(P_CURLN), P_CURPTR = getprevptr( P_CURPTR ) )

#define clrbuf()	del(1, P_LASTLN)

#define	Skip_White_Space	{while (*inptr==SP || *inptr==HT) inptr++;}

#define relink(a, x, y, b) { (x)->l_prev = (a); (y)->l_next = (b); }


/*________  functions  ______________________________________________________*/


/*	append.c	*/


int append(line, glob)
int	line, glob;
{
	if(glob)
		return(ERR);
	setCurLn( line );
	P_APPENDING = 1;
	set_prompt("*\b");
	return 0;
}

int more_append(str)
	char *str;
{
	if(P_NFLG)
		add_message("%6d. ",P_CURLN+1);
	if(str[0] == '.' && str[1] == '\0') {
		P_APPENDING = 0;
		set_prompt(":");
		return(0);
	}
	if( ins(str) < 0)
		return( MEM_FAIL );
	return 0;
}

/*	ckglob.c	*/

int ckglob()
{
	regexp	*glbpat;
	char	c, delim, *lin;
	int	num;
	LINE	*ptr;

	c = *inptr;

	if(c != 'g' && c != 'v')
		return(0);
	if (deflt(1, P_LASTLN) < 0)
		return(ERR);

	delim = *++inptr;
	if(delim <= ' ')
		return(ERR);

	glbpat = optpat();
	if(*inptr == delim)
		inptr++;
	ptr = getptr(1);
	for (num=1; num<=P_LASTLN; num++) {
		ptr->l_stat &= ~LGLOB;
		if (P_LINE1 <= num && num <= P_LINE2) {
			lin = gettxtl(ptr);
			if(regexec(glbpat, lin )) {
				if (c=='g') ptr->l_stat |= LGLOB;
			} else {
				if (c=='v') ptr->l_stat |= LGLOB;
			}
		ptr = getnextptr(ptr);
		}
	}
	return(1);
}


/*  deflt.c
 *	Set P_LINE1 & P_LINE2 (the command-range delimiters) if the file is
 *	empty; Test whether they have valid values.
 */

int deflt(def1, def2)
int	def1, def2;
{
	if(P_NLINES == 0) {
		P_LINE1 = def1;
		P_LINE2 = def2;
	}
	return ( (P_LINE1>P_LINE2 || P_LINE1<=0) ? ERR : 0 );
}


/*	del.c	*/

/* One of the calls to this function tests its return value for an error
 * condition.  But del doesn't return any error value, and it isn't obvious
 * to me what errors might be detectable/reportable.  To silence a warning
 * message, I've added a constant return statement. -- RAM
 * ... It could check to<=P_LASTLN ... igp
 */

int del(from, to)
int	from, to;
{
	LINE	*first, *last, *next, *tmp;

	if(from < 1)
		from = 1;
	first = getprevptr( getptr( from ) );
	last = getnextptr( getptr( to ) );
	next = first->l_next;
	while(next != last && next != &P_LINE0) {
		tmp = next->l_next;
		free((char *)next);
		next = tmp;
	}
	relink(first, last, first, last);
	P_LASTLN -= (to - from)+1;
	setCurLn( prevln(from) );
	return(0);
}


int dolst(line1, line2)
int line1, line2;
{
	int oldlflg=P_LFLG, p;

	P_LFLG = 1;
	p = doprnt(line1, line2);
	P_LFLG = oldlflg;
	return p;
}


/*	esc.c
 * Map escape sequences into their equivalent symbols.  Returns the
 * correct ASCII character.  If no escape prefix is present then s
 * is untouched and *s is returned, otherwise **s is advanced to point
 * at the escaped character and the translated character is returned.
 */
int esc(s)
char	**s;
{
	register int	rval;

	if (**s != ESCAPE) {
		rval = **s;
	} else {
		(*s)++;
		switch(toupper(**s)) {
		case '\000':
			rval = ESCAPE;	break;
		case 'S':
			rval = ' ';	break;
		case 'N':
			rval = '\n';	break;
		case 'T':
			rval = '\t';	break;
		case 'B':
			rval = '\b';	break;
		case 'R':
			rval = '\r';	break;
		default:
			rval = **s;	break;
		}
	}
	return (rval);
}


/*	doprnt.c	*/

int doprnt(from, to)
int	from, to;
{
	from = (from < 1) ? 1 : from;
	to = (to > P_LASTLN) ? P_LASTLN : to;

	if(to != 0) {
		setCurLn( from );
		while( P_CURLN <= to ) {
			prntln( gettxtl( P_CURPTR ), P_LFLG, (P_NFLG ? P_CURLN : 0));
			if( P_CURLN == to )
				break;
			nextCurLn();
		}
	}
	return(0);
}


void prntln(str, vflg, lin)
char	*str;
int	vflg, lin;
{
	if(lin)
		add_message("%7d ",lin);
	while(*str && *str != NL) {
		if(*str < ' ' || *str >= 0x7f) {
			switch(*str) {
			case '\t':
				if(vflg)
					putcntl(*str, stdout);
				else
					add_message("%c", *str);
				break;

			case DEL:
				putc('^', stdout);
				putc('?', stdout);
				break;

			default:
				putcntl(*str, stdout);
				break;
			}
		} else
			add_message("%c", *str);
		str++;
	}
	if(vflg)
		add_message("$");
	add_message("\n");
}


void putcntl(c, stream)
char	c;
FILE	*stream;
{
	putc('^', stream);
	putc((c&31)|'@', stream);
}


/*	egets.c	*/

int egets(str,size,stream)
char	*str;
int	size;
FILE	*stream;
{
	int	c, count;
	char	*cp;

	for(count = 0, cp = str; size > count;) {
		c = getc(stream);
		if(c == EOF) {
			*cp = EOS;
			if(count)
				add_message("[Incomplete last line]\n");
			return(count);
		}
		else if(c == NL) {
			*cp = EOS;
			return(++count);
		}
		else if (c == 0)
			P_NULLCHAR++;	/* count nulls */
		else {
			if(c > 127) {
				if(!P_EIGHTBIT)		/* if not saving eighth bit */
					c = c&127;	/* strip eigth bit */
				P_NONASCII++;		/* count it */
			}
			*cp++ = c;	/* not null, keep it */
			count++;
		}
	}
	str[count-1] = EOS;
	if(c != NL) {
		add_message("truncating line\n");
		P_TRUNCATED++;
		while((c = getc(stream)) != EOF)
			if(c == NL)
				break;
	}
	return(count);
}  /* egets */


int doread(lin, fname)
int	lin;
char	*fname;
{
	FILE	*fp;
	int	err;
	unsigned long	bytes;
	unsigned int	lines;
	static char	str[MAXLINE];

	err = 0;
	P_NONASCII = P_NULLCHAR = P_TRUNCATED = 0;

	if (P_DIAG) add_message("\"%s\" ",fname);
	if( (fp = fopen(fname, "r")) == NULL ) {
		add_message(" isn't readable.\n");
		return( ERR );
	}
	setCurLn( lin );
	for(lines = 0, bytes = 0;(err = egets(str,MAXLINE,fp)) > 0;) {
		bytes += err;
		if(ins(str) < 0) {
			err = MEM_FAIL;
			break;
		}
		lines++;
	}
	fclose(fp);
	if(err < 0)
		return(err);
	if (P_DIAG) {
		add_message("%u lines %u bytes",lines,bytes);
		if(P_NONASCII)
			add_message(" [%d non-ascii]",P_NONASCII);
		if(P_NULLCHAR)
			add_message(" [%d nul]",P_NULLCHAR);
		if(P_TRUNCATED)
			add_message(" [%d lines truncated]",P_TRUNCATED);
		add_message("\n");
	}
	return( err );
}  /* doread */


int dowrite(from, to, fname, apflg)
int	from, to;
char	*fname;
int	apflg;
{
	FILE	*fp;
	int	lin, err;
	unsigned int	lines;
	unsigned long	bytes;
	char	*str;
	LINE	*lptr;

	err = 0;
	lines = bytes = 0;

	add_message("\"%s\" ",fname);
	if((fp = fopen(fname,(apflg?"a":"w"))) == NULL) {
		add_message(" can't be opened for writing!\n");
		return( ERR );
	}

	lptr = getptr(from);
	for(lin = from; lin <= to; lin++) {
		str = lptr->l_buff;
		lines++;
		bytes += strlen(str) + 1;	/* str + '\n' */
		if(fputs(str, fp) == EOF) {
			add_message("file write error\n");
			err++;
			break;
		}
		fputc('\n', fp);
		lptr = lptr->l_next;
	}
	add_message("%u lines %lu bytes\n",lines,bytes);
	fclose(fp);
	return( err );
}  /* dowrite */


/*	find.c	*/

int find(pat, dir)
regexp	*pat;
int	dir;
{
	int	i, num;
	LINE	*lin;

	num = P_CURLN;
	lin = P_CURPTR;
	for(i=0; i<P_LASTLN; i++ ) {
		if(regexec( pat, gettxtl( lin ) ))
			return(num);
		if( dir )
			num = nextln(num), lin = getnextptr(lin);
		else
			num = prevln(num), lin = getprevptr(lin);
	}
	return ( ERR );
}


/*	getfn.c	*/

char *getfn(writeflg)
    int writeflg;
{
	static char	file[MAXFNAME];
	char	*cp;
	extern char *check_file_name();

	if(*inptr == NL) {
		P_NOFNAME=TRUE;
		strcpy(file, P_FNAME);
	} else {
		char *file2;
		P_NOFNAME=FALSE;
		Skip_White_Space;

		cp = file;
		while(*inptr && *inptr != NL && *inptr != SP && *inptr != HT)
			*cp++ = *inptr++;
		*cp = '\0';

		if(strlen(file) == 0) {
			add_message("bad file name\n");
			return( NULL );
		}
		file2 = check_file_name(file, writeflg);
		if (!file2)
		    return( NULL );
		strncpy(file, file2, MAXFNAME-1);
		file[MAXFNAME-1] = 0;
	}

	if(strlen(file) == 0) {
		add_message("no file name\n");
		return(NULL);
	}
	return( file );
}  /* getfn */


int getnum(first)
int first;
{
	regexp	*srchpat;
	int	num;
	char	c;

	Skip_White_Space;

	if(*inptr >= '0' && *inptr <= '9') {	/* line number */
		for(num = 0; *inptr >= '0' && *inptr <= '9'; ++inptr) {
			num = (num * 10) + (*inptr - '0');
		}
		return num;
	}

	switch(c = *inptr) {
	case '.':
		inptr++;
		return (P_CURLN);

	case '$':
		inptr++;
		return (P_LASTLN);

	case '/':
	case '?':
		srchpat = optpat();
		if(*inptr == c)
			inptr++;
		return(find(srchpat,c == '/'?1:0));

	case '-':
	case '+':
		return(first ? P_CURLN : 1);

	case '\'':
		inptr++;
		if (*inptr < 'a' || *inptr > 'z')
			return(EOF);
		return P_MARK[ *inptr++ - 'a' ];

	default:
		return ( first ? EOF : 1 );	/* unknown address */
	}
}  /* getnum */


/*  getone.c
 *	Parse a number (or arithmetic expression) off the command line.
 */
#define FIRST 1
#define NOTFIRST 0

int getone()
{
	int	c, i, num;

	if((num = getnum(FIRST)) >= 0) {
		for (;;) {
			Skip_White_Space;
			if(*inptr != '+' && *inptr != '-')
				break;	/* exit infinite loop */

                        c = *inptr++;
			if((i = getnum(NOTFIRST)) < 0)
				return ( i );
			if(c == '+')
				num += i;
			else
				num -= i;
		}
	}
	return ( num>P_LASTLN ? ERR : num );
}  /* getone */


int getlst()
{
	int	num;

	P_LINE2 = 0;
	for(P_NLINES = 0; (num = getone()) >= 0;)
	{
		P_LINE1 = P_LINE2;
		P_LINE2 = num;
		P_NLINES++;
		if(*inptr != ',' && *inptr != ';')
			break;
		if(*inptr == ';')
			setCurLn( num );
		inptr++;
	}
	P_NLINES = min(P_NLINES, 2);
	if(P_NLINES == 0)
		P_LINE2 = P_CURLN;
	if(P_NLINES <= 1)
		P_LINE1 = P_LINE2;

	return ( (num == ERR) ? num : P_NLINES );
}  /* getlst */


/*	getptr.c	*/

LINE *getptr(num)
int	num;
{
	LINE	*ptr;
	int	j;

	if (2*num>P_LASTLN && num<=P_LASTLN) {	/* high line numbers */
		ptr = P_LINE0.l_prev;
		for (j = P_LASTLN; j>num; j--)
			ptr = ptr->l_prev;
	} else {				/* low line numbers */
		ptr = &P_LINE0;
		for(j = 0; j < num; j++)
			ptr = ptr->l_next;
	}
	return(ptr);
}


/*	getrhs.c	*/

int getrhs(sub)
char	*sub;
{
	char delim = *inptr++;
	char *outmax = sub + MAXPAT;
	if( delim == NL || *inptr == NL)	/* check for eol */
		return( ERR );
	while( *inptr != delim && *inptr != NL ) {
		if ( sub > outmax )
			return ERR;
		if ( *inptr == ESCAPE ) {
			switch ( *++inptr ) {
			case 'r':
				*sub++ = '\r';
				inptr++;
				break;
			case ESCAPE:
				*sub++ = ESCAPE;
				*sub++ = ESCAPE;
				inptr++;
			case 'n':
				*sub++ = '\n';
				inptr++;
				break;
			case 'b':
				*sub++ = '\b';
				inptr++;
				break;
			case '0': {
				int i=3;
				*sub = 0;
				do {
					if (*++inptr<'0' || *inptr >'7')
						break;
					*sub = (*sub<<3) | (*inptr-'0');
				} while (--i!=0);
				sub++;
				} break;
			default:
				if ( *inptr != delim )
					*sub++ = ESCAPE;
				*sub++ = *inptr;
				if ( *inptr != NL )
					inptr++;
			}
		}
		else *sub++ = *inptr++;
	}
	*sub = '\0';

	inptr++;		/* skip over delimter */
	Skip_White_Space;
	if(*inptr == 'g') {
		inptr++;
		return( 1 );
	}
	return( 0 );
}

/*	ins.c	*/

int ins(str)
char	*str;
{
	char	*cp;
	LINE	*new, *nxt;
	int	len;

	do {
		for ( cp = str; *cp && *cp != NL; cp++ )
			;
		len = cp - str;
		/* cp now points to end of first or only line */

		if((new = (LINE *)xalloc(sizeof(LINE)+len)) == NULL)
			return( MEM_FAIL ); 	/* no memory */

		new->l_stat=0;
		strncpy(new->l_buff,str,len);	/* build new line */
		new->l_buff[len] = EOS;
		nxt = getnextptr(P_CURPTR);	/* get next line */
		relink(P_CURPTR, new, new, nxt);	/* add to linked list */
		relink(new, nxt, P_CURPTR, new);
		P_LASTLN++;
		P_CURLN++;
		P_CURPTR = new;
		str = cp + 1;
	}
		while( *cp != EOS );
	return 1;
}


/*	join.c	*/

int join(first, last)
int first, last;
{
	char buf[MAXLINE];
	char *cp=buf, *str;
	LINE *lin;
	int num;

	if (first<=0 || first>last || last>P_LASTLN)
		return(ERR);
	if (first==last) {
		setCurLn( first );
		return 0;
	}
	lin = getptr(first);
	for (num=first; num<=last; num++) {
		str=gettxtl(lin);
		while ( *str ) {
			if (cp >= buf + MAXLINE-1 ) {
				add_message("line too long\n");
				return(ERR);
			}
			*cp++ = *str++;
		}
		lin = getnextptr(lin);
	}
	*cp = EOS;
	del(first, last);
	if( ins(buf) < 0 )
		return MEM_FAIL;
	P_FCHANGED = TRUE;
	return 0;
}


/*  move.c
 *	Unlink the block of lines from P_LINE1 to P_LINE2, and relink them
 *	after line "num".
 */

int move(num)
int	num;
{
	int	range;
	LINE	*before, *first, *last, *after;

	if( P_LINE1 <= num && num <= P_LINE2 )
		return( ERR );
	range = P_LINE2 - P_LINE1 + 1;
	before = getptr(prevln(P_LINE1));
	first = getptr(P_LINE1);
	last = getptr(P_LINE2);
	after = getptr(nextln(P_LINE2));

	relink(before, after, before, after);
	P_LASTLN -= range;	/* per AST's posted patch 2/2/88 */
	if (num > P_LINE1)
		num -= range;

	before = getptr(num);
	after = getptr(nextln(num));
	relink(before, first, last, after);
	relink(last, after, before, first);
	P_LASTLN += range;	/* per AST's posted patch 2/2/88 */
	setCurLn( num + range );
	return( 1 );
}


int transfer(num)
int num;
{
	int mid, lin, ntrans;

	if (P_LINE1<=0 || P_LINE1>P_LINE2)
		return(ERR);

	mid= num<P_LINE2 ? num : P_LINE2;

	setCurLn( num );
	ntrans=0;

	for (lin=P_LINE1; lin<=mid; lin++) {
		if( ins(gettxt(lin)) < 0 )
			return MEM_FAIL;
		ntrans++;
	}
	lin+=ntrans;
	P_LINE2+=ntrans;

	for ( ; lin <= P_LINE2; lin += 2 ) {
		if( ins(gettxt(lin)) < 0 )
			return MEM_FAIL;
		P_LINE2++;
	}
	return(1);
}


/*	optpat.c	*/

regexp *optpat()
{
	char	delim, str[MAXPAT], *cp;

	delim = *inptr++;
	if (delim == NL)
	    return P_OLDPAT;
	cp = str;
	while(*inptr != delim && *inptr != NL && *inptr != EOS && cp < str + MAXPAT - 1) {
		if(*inptr == ESCAPE && inptr[1] != NL)
			*cp++ = *inptr++;
		*cp++ = *inptr++;
	}

	*cp = EOS;
	if(*str == EOS)
		return(P_OLDPAT);
	if(P_OLDPAT)
		free((char *)P_OLDPAT);
	return P_OLDPAT = regcomp(str);
}

/* regerror.c */
void regerror( s )
    char *s;
{
	add_message("ed: %s\n", s );
}


int set()
{
	char	word[16];
	int	i;

	if(*(++inptr) != 't') {
		if(*inptr != SP && *inptr != HT && *inptr != NL)
			return(ERR);
	} else
		inptr++;

	if ( (*inptr == NL))
	{
		add_message("ed version %d.%d\n", version/100, version%100);
		add_message(	"number %s, list %s\n",
			P_NFLG?"ON":"OFF",
			P_LFLG?"ON":"OFF");
		return(0);
	}

	Skip_White_Space;
	for(i = 0; *inptr != SP && *inptr != HT && *inptr != NL;)
		word[i++] = *inptr++;
	word[i] = EOS;
#if 0
	for(t = tbl; t->t_str; t++) {
		if(strcmp(word,t->t_str) == 0) {
			*t->t_ptr = t->t_val;
			return(0);
		}
	}
#endif
	return SET_FAIL;
}

#ifndef relink
void relink(a, x, y, b)
LINE	*a, *x, *y, *b;
{
	x->l_prev = a;
	y->l_next = b;
}
#endif


void set_ed_buf()
{
	relink(&P_LINE0, &P_LINE0, &P_LINE0, &P_LINE0);
	P_CURLN = P_LASTLN = 0;
	P_CURPTR = &P_LINE0;
}


/*	subst.c	*/

int subst(pat, sub, gflg, pflag)
regexp	*pat;
char	*sub;
int	gflg, pflag;
{
	int	nchngd = 0;
	char	*txtptr;
	char	*new, buf[MAXLINE];
	int	still_running = 1;
	LINE	*lastline = getptr( P_LINE2 );

	if(P_LINE1 <= 0)
		return( SUB_FAIL );
	nchngd = 0;		/* reset count of lines changed */

	for( setCurLn( prevln( P_LINE1 ) ); still_running; ) {
		nextCurLn();
		new = buf;
		if ( P_CURPTR == lastline )
			still_running = 0;
		if ( regexec( pat, txtptr = gettxtl( P_CURPTR ) ) ) {
			do
				{
				/* Copy leading text */
				int diff = pat->startp[0] - txtptr;
				strncpy( new, txtptr, diff );
				new += diff;
				/* Do substitution */
				new = regsub( pat, sub, new );
				txtptr = pat->endp[0];
				}
			while( gflg && !pat->reganch && regexec( pat, txtptr ));

			/* Copy trailing chars */
			while( *txtptr ) *new++ = *txtptr++;

			if(new >= buf+MAXLINE)
				return( SUB_FAIL );
			*new++ = EOS;
			del(P_CURLN,P_CURLN);
			if( ins(buf) < 0 )
				return MEM_FAIL;
			nchngd++;
			if(pflag)
				doprnt(P_CURLN, P_CURLN);
		}
        }
	return (( nchngd == 0 && !gflg ) ? SUB_FAIL : nchngd);
}

/*  docmd.c
 *	Perform the command specified in the input buffer, as pointed to
 *	by inptr.  Actually, this finds the command letter first.
 */

int docmd(glob)
int	glob;
{
	static char	rhs[MAXPAT];
	regexp	*subpat;
	int	c, err, line3;
	int	apflg, pflag, gflag;
	int	nchng;
	char	*fptr;

	pflag = FALSE;
	Skip_White_Space;

	c = *inptr++;
	switch(c) {
	case NL:
		if( P_NLINES == 0 && (P_LINE2 = nextln(P_CURLN)) == 0 )
			return(ERR);
		setCurLn( P_LINE2 );
		return (1);

	case '=':
		add_message("%d\n",P_LINE2);
		break;

	case 'a':
		if(*inptr != NL || P_NLINES > 1)
			return(ERR);

		if(append(P_LINE1, glob) < 0)
			return(ERR);
		P_FCHANGED = TRUE;
		break;

	case 'c':
		if(*inptr != NL)
			return(ERR);

		if(deflt(P_CURLN, P_CURLN) < 0)
			return(ERR);

		if(del(P_LINE1, P_LINE2) < 0)
			return(ERR);
		if(append(P_CURLN, glob) < 0)
			return(ERR);
		P_FCHANGED = TRUE;
		break;

	case 'd':
		if(*inptr != NL)
			return(ERR);

		if(deflt(P_CURLN, P_CURLN) < 0)
			return(ERR);

		if(del(P_LINE1, P_LINE2) < 0)
			return(ERR);
		if(nextln(P_CURLN) != 0)
			nextCurLn();
		P_FCHANGED = TRUE;
		break;

	case 'e':
		if(P_NLINES > 0)
			return(ERR);
		if(P_FCHANGED)
			return CHANGED;
		/*FALL THROUGH*/
	case 'E':
		if(P_NLINES > 0)
			return(ERR);

		if(*inptr != ' ' && *inptr != HT && *inptr != NL)
			return(ERR);

		if((fptr = getfn(1)) == NULL)
			return(ERR);

		clrbuf();
		(void)doread(0, fptr);

		strcpy(P_FNAME, fptr);
		P_FCHANGED = FALSE;
		break;

	case 'f':
		if(P_NLINES > 0)
			return(ERR);

		if(*inptr != ' ' && *inptr != HT && *inptr != NL)
			return(ERR);

		if((fptr = getfn(1)) == NULL)
			return(ERR);

		if (P_NOFNAME)
			add_message("%s\n", P_FNAME);
		else
			strcpy(P_FNAME, fptr);
		break;

	case 'i':
		if(*inptr != NL || P_NLINES > 1)
			return(ERR);

		if(append(prevln(P_LINE1), glob) < 0)
			return(ERR);
		P_FCHANGED = TRUE;
		break;

	case 'j':
		if (*inptr != NL || deflt(P_CURLN, P_CURLN+1)<0)
			return(ERR);

		if (join(P_LINE1, P_LINE2) < 0)
			return(ERR);
		break;

	case 'k':
		Skip_White_Space;

		if (*inptr < 'a' || *inptr > 'z')
			return ERR;
		c= *inptr++;

		if(*inptr != ' ' && *inptr != HT && *inptr != NL)
			return(ERR);

		P_MARK[c-'a'] = P_LINE1;
		break;

	case 'l':
		if(*inptr != NL)
			return(ERR);
		if(deflt(P_CURLN,P_CURLN) < 0)
			return(ERR);
		if (dolst(P_LINE1,P_LINE2) < 0)
			return(ERR);
		break;

	case 'm':
		if((line3 = getone()) < 0)
			return(ERR);
		if(deflt(P_CURLN,P_CURLN) < 0)
			return(ERR);
		if(move(line3) < 0)
			return(ERR);
		P_FCHANGED = TRUE;
		break;
	case 'n':
		if(*inptr != NL)
			return(ERR);
		if(P_NFLG)
			P_NFLG=FALSE;
		else
			P_NFLG=TRUE;
		break;
	case 'P':
	case 'p':
		if(*inptr != NL)
			return(ERR);
		if(deflt(P_CURLN,P_CURLN) < 0)
			return(ERR);
		if(doprnt(P_LINE1,P_LINE2) < 0)
			return(ERR);
		break;

	case 'q':
		if(P_FCHANGED)
			return CHANGED;
		/*FALL THROUGH*/
	case 'Q':
		clrbuf();
		if(*inptr == NL && P_NLINES == 0 && !glob)
			return(EOF);
		else
			return(ERR);

	case 'r':
		if(P_NLINES > 1)
			return(ERR);

		if(P_NLINES == 0)			/* The original code tested */
			P_LINE2 = P_LASTLN;		/*	if(P_NLINES = 0)	    */
						/* which looks wrong.  RAM  */

		if(*inptr != ' ' && *inptr != HT && *inptr != NL)
			return(ERR);

		if((fptr = getfn(0)) == NULL)
			return(ERR);

		if((err = doread(P_LINE2, fptr)) < 0)
			return(err);
		P_FCHANGED = TRUE;
		break;

	case 's':
		if(*inptr == 'e')
			return(set());
		Skip_White_Space;
		if((subpat = optpat()) == NULL)
			return(ERR);
		if((gflag = getrhs(rhs)) < 0)
			return(ERR);
		if(*inptr == 'p')
			pflag++;
		if(deflt(P_CURLN, P_CURLN) < 0)
			return(ERR);
		if((nchng = subst(subpat, rhs, gflag, pflag)) < 0)
			return(ERR);
		if(nchng)
			P_FCHANGED = TRUE;
		break;

	case 't':
		if((line3 = getone()) < 0)
			return(ERR);
		if(deflt(P_CURLN,P_CURLN) < 0)
			return(ERR);
		if(transfer(line3) < 0)
			return(ERR);
		P_FCHANGED = TRUE;
		break;

	case 'W':
	case 'w':
		apflg = (c=='W');

		if(*inptr != ' ' && *inptr != HT && *inptr != NL)
			return(ERR);

		if((fptr = getfn(1)) == NULL)
			return(ERR);

		if(deflt(1, P_LASTLN) < 0)
			return(ERR);
		if(dowrite(P_LINE1, P_LINE2, fptr, apflg) < 0)
			return(ERR);
		P_FCHANGED = FALSE;
		break;

	case 'x':
		if(*inptr == NL && P_NLINES == 0 && !glob) {
			if((fptr = getfn(1)) == NULL)
				return(ERR);
			if(dowrite(1, P_LASTLN, fptr, 0) >= 0)
				return(EOF);
		}
		return(ERR);

	case 'z':
		if(deflt(P_CURLN,P_CURLN) < 0)
			return(ERR);

		switch(*inptr) {
		case '-':
			if(doprnt(P_LINE1-21,P_LINE1) < 0)
				return(ERR);
			break;

		case '.':
			if(doprnt(P_LINE1-11,P_LINE1+10) < 0)
				return(ERR);
			break;

		case '+':
		case '\n':
			if(doprnt(P_LINE1,P_LINE1+21) < 0)
				return(ERR);
			break;
		}
		break;

	default:
		return(ERR);
	}
	return (0);
}  /* docmd */


/*	doglob.c	*/
int doglob()
{
	int	lin, stat;
	char	*cmd;
	LINE	*ptr;

	cmd = inptr;

	for (;;) {
		ptr = getptr(1);
		for (lin=1; lin<=P_LASTLN; lin++) {
			if (ptr->l_stat & LGLOB)
				break;
			ptr = getnextptr(ptr);
		}
		if (lin > P_LASTLN)
			break;

		ptr->l_stat &= ~LGLOB;
		P_CURLN = lin; P_CURPTR = ptr;
		inptr = cmd;
		if((stat = getlst()) < 0)
			return(stat);
		if((stat = docmd(1)) < 0)
			return(stat);
	}
	return(P_CURLN);
}  /* doglob */


void ed_start(file_arg)
	char *file_arg;
{
	if (command_giver->ed_buffer)
		error("Tried to start an ed session, when already active.\n");
	if (command_giver != current_object)
	    	error("Illegal start of ed.\n");
	command_giver->ed_buffer =
		(struct ed_buffer *)xalloc(sizeof (struct ed_buffer));
	memset((char *)command_giver->ed_buffer, '\0',
	       sizeof (struct ed_buffer));
	command_giver->ed_buffer->truncflg = 1;
	command_giver->ed_buffer->eightbit = 1;
	command_giver->ed_buffer->CurPtr = &command_giver->ed_buffer->Line0;
	set_ed_buf();

	if(file_arg && doread(0,file_arg)==0) {
		setCurLn( 1 );
		strncpy(P_FNAME, file_arg, MAXFNAME-1);
		P_FNAME[MAXFNAME-1] = 0;
		set_prompt(":");
		return;
	}
	set_prompt(":");
}

void ed_cmd(str)
	char *str;
{
	int stat;

	if (P_APPENDING) {
		more_append(str);
		return;
	}
	if (strlen(str) < MAXLINE)
	    strcat(str, "\n");
	
	strncpy(inlin, str, MAXLINE-1);
	inlin[MAXLINE-1] = 0;
	inptr = inlin;
	if(getlst() >= 0)
		if((stat = ckglob()) != 0) {
			if(stat >= 0 && (stat = doglob()) >= 0) {
				setCurLn( stat );
				return;
			}
		} else {
			if((stat = docmd(0)) >= 0) {
				if(stat == 1)
					doprnt(P_CURLN, P_CURLN);
				return;
			}
		}
	switch (stat) {
	case EOF:
		free((char *)command_giver->ed_buffer);
		command_giver->ed_buffer = 0;
		add_message("Exit from ed.\n");
		set_prompt("> ");
		return;
	case FATAL:
		free((char *)command_giver->ed_buffer);
		command_giver->ed_buffer = 0;
		add_message("FATAL ERROR\n");
		set_prompt("> ");
		return;
	case CHANGED:
		add_message("File has been changed.\n");
		break;
	case SET_FAIL:
		add_message("`set' command failed.\n");
		break;
	case SUB_FAIL:
		add_message("string substitution failed.\n");
		break;
	case MEM_FAIL:
		add_message("Out of memory: text may have been lost.\n" );
		break;
	default:
		add_message("Unrecognized or failed command.\n");
		/*  Unrecognized or failed command (this  */
		/*  is SOOOO much better than "?" :-)	  */
	}
}

void save_buffer(ob) 
struct object *ob;
{
	struct object *save_command_giver;
	char buff[MAXFNAME];
	struct value *ret;

	if ((!ob->interactive && !ob->once_interactive) || !ob->ed_buffer)
		return;

	save_command_giver = command_giver;
	command_giver = ob;
	if (P_LASTLN > 0 && P_FCHANGED) {
		if (P_FNAME[0]) {
			sprintf(buff, "%s~", P_FNAME);
		}
		else {
			ret = apply("query_real_name", command_giver, 0);
			if (!ret || (ret->type != T_STRING)) {
				command_giver = save_command_giver;
				return;
			}
			sprintf(buff, "players/%s/ed_dump~", ret->u.string);
		}
		dowrite(1, P_LASTLN, buff, 0);
	}
	else {
		command_giver = save_command_giver;
		return;
	}
	if (command_giver->interactive) {
		add_message("(Saved edit buffer to file: %s)\n", buff);
	}
	else if (save_command_giver->interactive) {
		command_giver = save_command_giver;
		add_message("(Saved edit buffer to file: %s)\n", buff);
	}
	command_giver = save_command_giver;
}

void deallocate_buffer(ob)
struct object *ob;
{
	struct object *save_command_giver = command_giver;

	if ((!ob->interactive && !ob->once_interactive) || !ob->ed_buffer)
		return;
	command_giver = ob;
	clrbuf();
	free((char *)command_giver->ed_buffer);
	command_giver->ed_buffer = 0;
	command_giver = save_command_giver;
}