[mod.sources] Vnews part 7

sources-request@genrad.UUCP (01/28/85)

# From: ka@hou3c
#
# Welcome to vnews release 2.11-B 1/17/85.
# This is part 7 out of 7.
# Feed me into sh (NOT csh).

if test ! -d vnews
then	mkdir vnews
fi

cat > vnews/virtterm.c <<\!E!O!F!
/*
 *  Virtual terminal handler
 *  Written by Kenneth Almquist, AGS Computers  (HO 4C601, X7105).
 *  Modified by Stephen Hemminger, to use TERMCAP (without curses)
 */

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

#define ANSI	/* support multiple line insert/delete */
#define MAXPLEN 24
#define MAXLLEN 80
#define BOTLINE (ROWS - 1)
#define DIRTY 01

/* terminal escape sequences from termcap */
#define HO _tstr[0]		/* home */
#define CL _tstr[1]		/* clear screen */
#define CD _tstr[2]		/* clear to end of screen */
#define CE _tstr[3]		/* clear to end of line */
#define xUP _tstr[4]		/* up one line */
#define DO _tstr[5]		/* down one line */
#define US _tstr[6]		/* underline */
#define UE _tstr[7]		/* underline end */
#define BT _tstr[8]		/* backtab */
#define xBC _tstr[9]		/* backspace */
#define AL _tstr[10]		/* insert line */
#define DL _tstr[11]		/* delete line */
#define CM _tstr[12]		/* cursor move */
#define CH _tstr[13]		/* cursor horizontal move */
#define CV _tstr[14]		/* cursor vertical move */
#define CS _tstr[15]		/* scrolling region */
#define SF _tstr[16]		/* scroll forwards */
#define SR _tstr[17]		/* scroll backwards */
#define TI _tstr[18]		/* start cursor mode */
#define TE _tstr[19]		/* end cursor mode */
#define TA _tstr[20]		/* tab char (if not \t) */
#define CR _tstr[21]		/* carriage return (if not \r) */
#define xPC _tstr[22]		/* for reading pad character */
#ifdef ANSI
#define ALC _tstr[23]		/* insert multiple lines */
#define DLC _tstr[24]		/* delete multiple lines */
#endif
char PC;			/* pad character */
char *BC, *UP;			/* external variables for tgoto */

#ifdef ANSI
static char sname[] = "hoclcdceupdousuebtbcaldlcmchcvcssfsrtitetacrpcALDL";
char *_tstr[25];
#else
static char sname[] = "hoclcdceupdousuebtbcaldlcmchcvcssfsrtitetacrpc";
char *_tstr[23];
#endif
int     HOlen;			/* length of HO string */


/* terminal flags */
#define BS _tflg[0]		/* can backspace */
#define AM _tflg[1]		/* has auto margins */
#define XN _tflg[2]		/* no newline after wrap */
#define RET !_tflg[3]		/* has carriage return */
#define NS _tflg[4]		/* has SF (scroll forward) */
#define PT _tflg[5]		/* has tabs */
#define XT _tflg[6]		/* tabs are destructive */
int	GT = 1;			/* tab stops on terminal are set */

static char bname[] = "bsamxnncnsptxt";
char _tflg[7];


extern char *tgoto(), *tgetstr();
extern char *getenv(), *strcpy();

#define ULINE 0200
#define CURSEEN 1

/* Constants accessable by user */
int     hasscroll;		/* scrolling type, 0 == no scrolling */
int     ROWS;			/* number of lines on screen */
int     COLS;			/* width of screen */
int	needbeep;		/* user sets this to generate beep */

struct line {
	char    len;
	char    flags;
	char    l[MAXLLEN];
};

int     _row, _col;
int     _srow, _scol;
struct line _virt[MAXPLEN], _actual[MAXPLEN];
int     _uline = 0;
int     _junked = 1;
int     _curjunked;
int	_partupd;
int     _dir = 1;
int	_shifttop, _shiftbot;
int	_shift;
int	_scratched;
int     vputc();



/*
 * Tell refresh to shift lines in region upwards count lines.  Count
 * may be negative.  The virtual image is not shifted; this may change
 * later.  The variable _scratched is set to supress all attempts to
 * shift.
 */

ushift(top, bot, count) {
	if (_scratched)
		return;
	if (_shift != 0 && (_shifttop != top || _shiftbot != bot)) {
		_scratched++;
		return;
	}
	_shifttop = top;
	_shiftbot = bot;
	_shift += count;
}



/*
 * generate a beep on the terminal
 */

beep() {
	vputc('\7');
}

/*
 * Move to one line below the bottom of the screen.
 */

botscreen() {
	_amove(BOTLINE, 0);
	vputc('\n');
	vflush();
}



move(row, col) {
	if (row < 0 || row >= ROWS || col < 0 || col >= COLS)
		return;
	_row = row;
	_col = col;
}



/*
 * Output string at specified location.
 */

mvaddstr(row, col, str)
	char *str;
	{
	move(row, col);
	addstr(str);
}


addstr(s)
char   *s;
{
	register char  *p;
	register struct line   *lp;
	register int    col = _col;

	lp = &_virt[_row];
	if (lp->len < col) {
		p = &lp->l[lp->len];
		while (lp->len < col) {
			*p++ = ' ';
			lp->len++;
		}
	}
	for (p = s; *p != '\0'; p++) {
		if (*p == '\n') {
			lp->len = col;
			lp->flags |= DIRTY;
			col = 0;
			if (++_row >= ROWS)
				_row = 0;
			lp = &_virt[_row];
		}
		else {
			lp->l[col] = *p;
			lp->flags |= DIRTY;
			if (++col >= COLS) {
				lp->len = COLS;
				col = 0;
				if (++_row >= ROWS)
					_row = 0;
				lp = &_virt[_row];
			}
		}
	}
	if (lp->len <= col)
		lp->len = col;
	_col = col;
}



addch (c) {
	register struct line   *lp;
	register char  *p;

	lp = &_virt[_row];
	if (lp->len < _col) {
		p = &lp->l[lp->len];
		while (lp->len < _col) {
			*p++ = ' ';
			lp->len++;
		}
	}
	lp->l[_col] = c;
	if (lp->len == _col)
		lp->len++;
	if (++_col >= COLS) {
		_col = 0;
		if (++_row >= ROWS)
			_row = 0;
	}
	lp->flags |= DIRTY;
}



clrtoeol() {
	register struct line   *lp;

	lp = &_virt[_row];
	if (lp->len > _col) {
		lp->len = _col;
		lp->flags |= DIRTY;
	}
}


/*
 * Clear an entire line.
 */

clrline(row) {
	register struct line   *lp;

	lp = &_virt[row];
	if (lp->len > 0) {
		lp->len = 0;
		lp->flags |= DIRTY;
	}
}



clear() {
	erase();
	_junked++;
}



erase() {
	register    i;

	for (i = 0; i < ROWS; i++) {
		_virt[i].len = 0;
		_virt[i].flags |= DIRTY;
	}
}



/*
 * Leave lines as they are on the screen.
 */

nochange(line1, line2) {
	while (line1 <= line2) {
		_virt[line1] = _actual[line1] ;
		if (_junked)
			clrline(line1) ;
		line1++ ;
	}
}



refresh() {
	register int i;
	register int j;
	int len;
	int needchk;
	register char *p, *q;

/*
	if (checkin())
		return;
*/
	needchk = 0;
	i = 1;
	if (_junked) {
		_sclear();
		_junked = 0;
	} else if (! _scratched) {
		if (_shift) {
			needchk++;
			if (_shift > 0)
			_ushift(_shifttop, _shiftbot, _shift);
			else
			i = _dshift(_shifttop, _shiftbot, -_shift);
		} else {
			i = _dir;
		}
	}
	_dir = i;
	_dir = 1;		/* forget about _dir stuff */
	_shift = 0;
	_partupd = 1;
	if (needchk && checkin())
		return;
	_scratched = 0;
	_fixlines();
	for (i = _dir > 0 ? 0 : BOTLINE; i >= 0 && i < ROWS; i += _dir) {
		if ((_virt[i].flags & DIRTY) == 0)
			continue;
		_ckclrlin(i);		/* decide whether to do a clear line */
					/* probably should consider cd as well */
		len = _virt[i].len;
		if (_actual[i].len < len)
			len = _actual[i].len;
		p = _virt[i].l;
		q = _actual[i].l;
		for (j = 0; j < len; j++) {
			if (*p != *q) {
				_amove(i, j);
				_aputc(*p);
				*q = *p;
			}
			p++, q++;
		}
		len = _virt[i].len;
		if (_actual[i].len > len) {
			_clrtoeol(i, len);
		}
		else {
			for (; j < len; j++) {
				if (*p != ' ') {
					_amove(i, j);
					_aputc(*p);
				}
				*q++ = *p++;
			}
			_actual[i].len = len;
		}
		if (checkin())
			return;
	}
	_dir = 1;
	if (needbeep) {
		beep();
		needbeep = 0;
	}
	if (CURSEEN)
		_amove(_row, _col);
	vflush();			/* flush output buffer */
	_partupd = 0;
}


#define badshift(i, count) (_actual[i].len != _virt[i+count].len || strncmp(_actual[i].l, _virt[i+count].l, _actual[i].len) != 0)

_dshift(top, bot, count) {
	register    i;

	if (count >= bot - top || hasscroll < 4) {  /* must have CS or AL/DL */
		_scratched++;
		return 1;
	}
	for (i = bot - count; _actual[i].len == 0 || _partupd && badshift(i, count); i--)
		if (i == top)
			return 1;
	for (i = top; i <= bot; i++)
		_virt[i].flags |= DIRTY;
	for (i = bot; i >= top + count; i--)
		_actual[i] = _actual[i - count];
	for (; i >= top; i--)
		_actual[i].len = 0;

	if (hasscroll != 5) {		/* can we define scrolling region, and scroll back */
		tputs(tgoto(CS, bot, top), 1, vputc);/* define scroll region */
		_curjunked = 1;
		_amove(top, 0);
		for (i = count; --i >= 0;)
			tputs(SR, 1, vputc);/* scroll back */
		tputs(tgoto(CS, BOTLINE, 0), 1, vputc);
		_curjunked = 1;
	} else {
		_amove(bot - count + 1, 0);
		if (CD && bot == BOTLINE)
			tputs(CD, 1, vputc);
		else {
#ifdef ANSI
			if (DLC && count > 1)
				tputs(tgoto(DLC, count, count), 0, vputc);
			else
#endif
			for (i = count; --i >= 0;)
				tputs(DL, ROWS - _srow, vputc);
		}
		_amove(top, 0);
#ifdef ANSI
		if (count > 1 && ALC)
			tputs(tgoto(ALC, count, count), 0, vputc), count=0;
#endif
		for (i = count; --i >= 0;)
			tputs(AL, ROWS - _srow, vputc);
	}
	return -1;
}


_ushift(top, bot, count) {
	register    i;

	if (count >= bot - top || hasscroll == 0) {
		return;
	}
	for (i = top + count; _actual[i].len == 0 || _partupd && badshift(i, -count); i++)
		if (i == bot)
			return;
	if (hasscroll == 1 || hasscroll == 3) {
		/* we cheat and shift the entire screen */
		/* be sure we are shifting more lines into than out of position */
		if ((bot - top + 1) - count <= ROWS - (bot - top + 1))
			return;
		top = 0, bot = BOTLINE;
	}
	for (i = top; i <= bot; i++)
		_virt[i].flags |= DIRTY;
	for (i = top; i <= bot - count; i++)
		_actual[i] = _actual[i + count];
	for (; i <= bot; i++)
		_actual[i].len = 0;

	if (hasscroll != 5) {
		if (top != 0 || bot != BOTLINE) {
			tputs(tgoto(CS, bot, top), 0, vputc);
			_curjunked = 1;
		}
		_amove(bot, 0);	/* move to bottom */
		for (i = 0; i < count; i++) {
			if (SF)		/* scroll forward */
				tputs(SF, 1, vputc);
			else
				vputc('\n');
		}
		if (top != 0 || bot != BOTLINE) {
			tputs(tgoto(CS, BOTLINE, 0), 0, vputc);
			_curjunked = 1;
		}
	} else {
		_amove(top, 0);
#ifdef ANSI
		if (DLC && count > 1)
			tputs(tgoto(DLC, count, count), 0, vputc);
		else
#endif
		for (i = count; --i >= 0;)
			tputs(DL, ROWS - _srow, vputc);
		if (bot < BOTLINE) {
			_amove(bot - count + 1, 0);
#ifdef ANSI
			if (ALC && count > 1)
				tputs(tgoto(ALC, count, count), 0, vputc), count=0;
#endif
			for (i = count; --i >= 0;)
				tputs(AL, ROWS - _srow, vputc);
		}
	}
}


_sclear() {
	register struct line   *lp;

	tputs(CL, 0, vputc);
	_srow = _scol = 0;
	for (lp = _actual; lp < &_actual[ROWS]; lp++) {
		lp->len = 0;
	}
	for (lp = _virt; lp < &_virt[ROWS]; lp++) {
		if (lp->len != 0)
			lp->flags |= DIRTY;
	}
}


_clrtoeol(row, col) {
	register struct line *lp = &_actual[row];
	register i;

	if (CE && lp->len > col + 1) {
		_amove(row, col);
		tputs(CE, 1, vputc);
	} else {
		for (i = col ; i < lp->len ; i++) {
			if (lp->l[i] != ' ') {
				_amove(row, i);
				_aputc(' ');
			}
		}
	}
	lp->len = col;
}


_fixlines() {
	register struct line   *lp;
	register char  *p;
	register int    i;

	for (i = 0; i < ROWS; i++) {
		lp = &_virt[i];
		if (lp->flags & DIRTY) {
			lp = &_virt[i];
			for (p = &lp->l[lp->len]; --p >= lp->l && *p == ' ';);
			lp->len = (int)(p - lp->l) + 1;
			if (lp->len == _actual[i].len && strncmp(lp->l, _actual[i].l, lp->len) == 0)
				lp->flags &= ~DIRTY;
		}
	}
}


/*
 * Consider clearing the line before overwriting it.
 * We always clear a line if it has underlined characters in it
 * because these can cause problems.  Otherwise decide whether
 * that will decrease the number of characters to change.  This
 * routine could probably be simplified with no great loss.
 */

_ckclrlin(i) {
	int     eval;
	int     len;
	int     first;
	register struct line   *vp, *ap;
	register int    j;

	if (!CE)
		return;
	ap = &_actual[i];
	vp = &_virt[i];
	len = ap->len;
	eval = -strlen(CE);
	if (len > vp->len) {
		len = vp->len;
		eval = 0;
	}
	for (j = 0; j < len && vp->l[j] == ap->l[j]; j++);
	if (j == len)
		return;
	first = j;
	while (j < len) {
		if (vp->l[j] == ' ') {
			if (ap->l[j] != ' ') {
				while (++j < len && vp->l[j] == ' ' && ap->l[j] != ' ') {
					eval++;
				}
				if (j == len)
					eval++;
				continue;
			}
		}
		else {
			if (vp->l[j] == ap->l[j]) {
				while (++j < len && vp->l[j] == ap->l[j]) {
					eval--;
				}
				continue;
			}
		}
		j++;
	}
	if (US) {
		for (j = 0 ; j < ap->len ; j++) {
			if (ap->l[j] & ULINE) {
				eval = 999;
				if (first > j)
					first = j;
				break;
			}
		}
	}
	for (j = first; --j >= 0;)
		if (vp->l[j] != ' ')
			break;
	if (j < 0)
		first = 0;
	if (eval > 0) {
		_amove(i, first);
		tputs(CE, 0, vputc);
		_actual[i].len = first;
	}
}



/*
 * Move routine
 * 	first compute direct cursor address string and cost
 *	then relative motion string and cost,
 *	then home then relative and cost
 *	choose smallest and do it.
 *
 *	The plod stuff is to build the strings (with padding) then decide
 */
static char *plodstr;		/* current location in relmove string */

plodput(c) { *plodstr++ = c; }

_amove(row, col) {
	char direct[20];
	char rel[4 * MAXPLEN + 3 * MAXLLEN];
	char ho[3 * MAXPLEN + 3 * MAXLLEN];
	int cost, newcost;
	register char *movstr;

	if (row == _srow && col == _scol && _curjunked == 0)
		return;
	_setul(0);

	cost = 999;
	if (CM) {
		plodstr = direct;
		tputs(tgoto(CM, col, row), 0, plodput);
		*plodstr = '\0';
		cost = plodstr - direct;
		movstr = direct;
	}
	if (_curjunked == 0) {
		plodstr = rel;
		if (_vmove(_srow, row) >= 0
		 && plodstr - rel < cost
		 && _hmove(_scol, col, row) >= 0
		 && (newcost = plodstr - rel) < cost) {
			*plodstr = '\0';
			cost = newcost;
			movstr = rel;
		}
	}
	if (cost > HOlen) {	/* is it worth calculating */
		plodstr = ho;
		tputs(HO, 0, plodput);
		if (_vmove(0, row) >= 0
		 && plodstr - rel < cost
		 && _hmove(0, col, row) >= 0
		 && (newcost = plodstr - ho) < cost) {
			*plodstr = '\0';
			cost = newcost;
			movstr = ho;
		}
	}

	while (--cost >= 0) {
		vputc(*movstr++);
	}
	_srow = row, _scol = col;
	_curjunked = 0;
}


_vmove(orow, nrow) {
	char direct[64];
	char *saveplod = plodstr;

	if (CV) {
		plodstr = direct;
		tputs(tgoto(CV, nrow, nrow), 0, plodput);
		*plodstr = '\0';
		plodstr = saveplod;
	}
	if (orow > nrow) {		/* cursor up */
		if (! UP)
			return -1;
		while (orow > nrow) {
			tputs(UP, 1, plodput);
			orow--;
		}
	}
	while (orow < nrow) {		/* cursor down */
		if (DO)
			tputs(DO, 1, plodput);
		else
			*plodstr++ = '\n';
		orow++;
	}
	if (CV && plodstr - saveplod >= strlen(direct)) {
		register char *p;
		plodstr = saveplod;
		for (p = direct ; *plodstr = *p++ ; plodstr++);
	}
	return 0;
}


_hmove(ocol, ncol, row) {
	char direct[64];
	char ret[3 * MAXLLEN];
	char *saveplod = plodstr;
	char *movstr;
	int cost, newcost;

	cost = 999;
	if (CH) {
		plodstr = direct;
		tputs(tgoto(CH, ncol, ncol), 0, plodput);
		cost = plodstr - direct;
		movstr = direct;
		plodstr = saveplod;
	}
	if (RET && ocol > ncol) {	/* consider doing carriage return */
		plodstr = ret;
		if (CR)
			tputs(CR, 1, plodput);
		else
			*plodstr++ = '\r';
		if (_relhmove(0, ncol, row) >= 0
		 && (newcost = plodstr - ret) < cost) {
			cost = newcost;
			movstr = ret;
		}
		plodstr = saveplod;
	}
	if (_relhmove(ocol, ncol, row) < 0) {
		if (cost == 999)
			return -1;
		goto copy;
	}
	if (plodstr - saveplod > cost) {
copy:		plodstr = saveplod;
		while (--cost >= 0)
			*plodstr++ = *movstr++;
	}
	return 0;
}



_relhmove(ocol, ncol, row) {
	int tab;

	if (ocol < ncol && PT && GT) {	/* tab (nondestructive) */
		while ((tab = (ocol + 8) & ~07) <= ncol) {
			if (TA)
				tputs(TA, 1, plodput);
			else
				*plodstr++ = '\t';
			ocol = tab;
		}
		if (tab < COLS && tab - ncol < ncol - ocol) {
			if (TA)
				tputs(TA, 1, plodput);
			else
				*plodstr++ = '\t';
			ocol = tab;
		}
	} else if (BT && GT && ocol > ncol) {	/* backwards tab */
		while ((tab = (ocol - 1) &~ 07) >= ncol) {
			if (BS && tab == ocol - 1) {
				if (BC)
					tputs(BC, 1, plodput);
				else
					*plodstr++ = '\b';
			} else
				tputs(BT, 1, plodput);
			ocol = tab;
		}
		if (ncol - tab + 1 < ocol - ncol) {
			tputs(BT, 1, plodput);
			ocol = tab;
		}
	}
	if (ocol > ncol) {			/* cursor left */
		if (! BS)
			return -1;
		while (ocol > ncol) {
			if (BC != NULL)
				tputs(BC, 1, plodput);
			else
				*plodstr++ = '\b';
			ocol--;
		}
	}
	if (ocol < ncol) {			/* cursor right */
		register struct line *lp = &_actual[row];
		/*
		 * This code doesn't move over underlined characters properly,
		 * but in practice this doesn't seem to matter.
		 */
		while (ocol < ncol) {
			if (ocol < lp->len)
				*plodstr++ = lp->l[ocol];
			else
				*plodstr++ = ' ';
			ocol++;
		}
	}
	return 0;
}



_aputc(c) {
	if (AM && _scol == COLS - 1 && _srow == ROWS - 1)
		return;
	_setul(c & ULINE);
	vputc(c & ~ULINE);
	if (++_scol >= COLS) {
		if (! AM) {
			_scol--;
		} else  if (XN) {
			_curjunked++;
		} else {
			_scol = 0;
			++_srow;
		}
	}
}


_setul(on) {
	if (on) {
		if (_uline == 0 && US != NULL) {
			tputs(US, 1, vputc);
			_uline = 1;
		}
	}
	else {
		if (_uline != 0 && UE != NULL) {
			tputs(UE, 1, vputc);
			_uline = 0;
		}
	}
}


/*
 * Initialize termcap strings for later use.
 */
initterm() {
	static char tcbuf[1024];	/* termcap buffer */
	register char  *cp;

	if ((cp = getenv("TERM")) == NULL)
		xerror("TERM not set in environment");

	switch (tgetent(tcbuf, cp)) {
		case 0:
			xerror("Terminal not found in TERMCAP");
		case -1:
			xerror("Can't open /etc/termcap");
		case 1:
			break;
	}

	if ((ROWS = tgetnum("li")) == -1
	 || (COLS = tgetnum("co")) == -1)
		xerror("Can't get screen size");
	_zap();

	if (CL == NULL)
		xerror ("No clear screen defined");

	if (HO == NULL && CM == NULL)
		xerror("No home or cursor addressing");
	if (HO)
		HOlen = strlen(HO);
	else
		HOlen = 999;

	PC = xPC ? xPC[0] : 0;
	BC = xBC;
	UP = xUP;

	if (tgetnum("ug") > 0)
		US = UE = NULL;

	if (XT)				/* Destructive tab code not included */
		PT = 0;			/* to keep things simple */

	if (ROWS > MAXPLEN)
		ROWS = MAXPLEN;
	if (COLS > MAXLLEN) {
		COLS = MAXLLEN;
		AM = XN = 1;
	}

	/* Select article scrolling algorithm.  We prefer scrolling region
	   over insert/delete line because it's faster on the HP */
	hasscroll = 0;
	if (!NS) {
		hasscroll = 1;
		if (SR)
			hasscroll = 3;
		if (CS)
			hasscroll++;
	}
	if (AL && DL && hasscroll != 4)
		hasscroll = 5;
}


rawterm() {
	if (TI != NULL)
		tputs(TI, 0, vputc);
}


cookedterm() {
	if (TE != NULL) {
		tputs(TE, 0, vputc);
		vflush() ;
	}
}


/* get strings from termcap */
_zap() {
	static char tstrbuf[1024];
	static char *tp;
	register char  *namp, **sp, *bp;

	tp = tstrbuf;
	sp = _tstr;
	for (namp = sname; *namp; namp += 2) {
		*sp++ = tgetstr(namp, &tp);
	}
	bp = _tflg;
	for (namp = bname; *namp; namp += 2) {
		*bp++ = tgetflag(namp, &tp);
	}
}
!E!O!F!

cat > vnews/virtterm.doc <<\!E!O!F!
.. Run this file through nroff (no macro packages required).
.ll 72
.na
.hy
The new virtterm.c uses termcap.  This makes it larger, but not excessively
so.
The cursor motion commands are longer for the HP and VT100 terminals
due to the inability of termcap to completely describe these terminals,
but this doesn't make a big difference.
Virtterm handles both insert/delete line and VT100 style scrolling regions.
It also handles terminals with sticky underlining (like some HP terminals).

The following is the list of the terminal capabilities virtterm uses:
.nf

	bc, bs		Cursor left
	do		Cursor down
	up		Cursor up
	ta, pt		Nondestructive tab
	bt		Backwards tab
	ho		Cursor home
	cr, nc		Carriage return
	cm		Cursor to specified location
	ch, cv		Cursor to specified column or row
	am, xn		Terminal wrap-around
	al, dl		Insert and delete line
	AL, DL		Insert and delete multiple lines
	cs, sr		Scrolling region (a al VT100)
	sf, ns		Scroll entire screen
	ce		Clear to end of line
	cl		Clear screen and move cursor home
	us, ue		Underscore mode
	ti, te		Turn visual mode on/off

.fi
In order to run with virtterm, the terminal *must* have:
.in 11
.ti -3
1)\ Clear screen.
.ti -3
2)\ Non-relative cursor motion (either cm or ho).
.ti -3
3)\ Cursor down (linefeed will be used if do is not specified).
.ti -3
4)\ Us and ue, otherwise no underlining will be done.
.ti -3
5)\ Tabstops set every 8 columns if terminal has tabs.
Virtterm doesn't bother to set the tabs; it assumes they are already set.
.in 0

The new virtterm can't do its job very well if capabilities are missing
from the termcap entry.
For example, in our version of /etc/termcap, us and ue entries
were not specified the HP-2621.
!E!O!F!

cat > vnews/vnews.1 <<\!E!O!F!
.TH VNEWS 1
.SH NAME
vnews \- read news articles
.SH SYNOPSIS
.BR vnews " [ " \-a
.IR date " ] [ "
.B \-n
.IR newsgroups " ] [ "
.B \-t
.IR titles " ] [ "
.BR \-rxhfuA " ] [ "
.BR \-c " ]"
.PP
.B "vnews \-s"
.SH DESCRIPTION
.SS "Overview"
.. .RS 5
.B Vnews
is a program for reading USENET news.
It is based on
.BR readnews (1)
but has a CRT oriented user interface.
The command line options are identical,
but only the options listed in the synopsis are supported.
The effect of the
.B -c
option is different in vnews than in readnews; it
causes vnews to display the entire first page of an article
immediately rather than initially showing only the header.
The
.B -A
option causes the newsgroup index to be displayed each time
a new group is entered.
The list of available commands is quite similar, although since
.B vnews
is a "visual" interface, most
.B vnews
commands do not have to be terminated by a newline.
.B Vnews
is somewhat less friendly than
.BR readnews ,
so new users should probably start out with readnews.
.. .P
.. .B Vnews
.. is an experiment in writing a good user interface.
.. The only way I can tell how successful I have been is by the responses I
.. receive, so please send you comments, good or bad, to
.. {harpo|ihnp4|burl|ucbvax!mhtsa}!spanky!ka.
.. (Bug reports should be sent to the same address.)
.P
.B Vnews
uses
all except the last two
.. all except the first two
lines of the screen to display the current article.
The second to the bottom line
.. Line 2
is the secondary prompt line, and is used to input string arguments
to commands.
The bottom line
.. Line 1
contains several fields.
.. The first field is the prompt field.
.. If
.. .B vnews
.. is at the end of an article, the prompt is "next?"; otherwise the prompt is
.. "more?".
The first field contains an approximation of the percentage of the
article that has been read.  It will be 100% if and only if the entire
article has been read.
.. [This is a more informative (and cryptic) replacement for the
.. more/next indication.]  \
..
The second field is the newsgroup field, which displays the current
newsgroup, the number of the current article, and the number of the last
article in the newsgroup.
The third field contains the current time, and the last field contains the
word "mail" if you have mail.
When you receive new mail, the bell on the terminal is rung and the
word "MAIL"
appears in capital letters for 30 seconds.
.RE
.P
.SS "Differences from Readnews"
.. .RS 5
Most of the readnews commands have
.B vnews
counterparts and vice versa.
Commands are not present in
.B Vnews
include:
.TP 10
.B d
The digest command is not yet implemented.
.TP 10
.B K
You can get the same effect by using the
.B n
command with a huge count.
.TP 10
.B P
Use "N-".
.TP 10
.B X
This should be a separate program.
.P
.B Vnews
has commands for moving around in the article which readnews does not have
since they aren't applicable.
It also has a
.I parent
command which will go to the article that the current article is a followup
to, and a
.I write
command that writes out the body of an article without the header.
.P
You can refer to the current article from the shell or while writing a
followup as $A.
.P
You can use the
.B n
command to delete articles while looking at the index page.
.. .P
.. The
.. .I decrypt
.. command always does rotation 13.
.. .P
.. The algorithm for handling articles posted to multiple groups is different;
.. .B vnews
.. attempts to show you these articles only once even if you quit in the
.. middle of running
.. .B vnews
.. and resume it later.
.. .P
.P
.SS "Article selection algorithm"
.P
Newsgroups are shown in the order that they appear in the .newsrc file.
Any newsgroups which contain articles but do not appear in the .newsrc
file are appended to the .newsrc file in the order in which they appear
in the groupfile file.  (This means that if the -x option is specified
you will be shown newsgroups in the order in which they appear in the
groupfile file.)
.P
Vnews has a newsgroup stack which allows the commands
.BR N ,
.BR p ,
and
.B <
to save the current newsgroup.
Only real newsgroups are pushed; not single articles fetched using the
.B p
and
.B <
commands.
The phrase "advance to the next newsgroup" always means
"pop the newsgroup stack if it is nonempty; otherwise go to the next
newsgroup in the .newsrc file."
.P
Within a newsgroup, articles are grouped by discussion.  Within a
discussion, articles are sorted by posting date.  Discussions are
sorted by the posting date of the first new article in the discussion.
.P
Articles within a newsgroup are numbered by
.B vnews
so that you can refer to them.
These numbers are not related to the names of the files containing the
articles.
.P
An article is normally considered to be read if you have
seen the end of it.
The
.B n
and
.B e
commands allow you to explicitly mark an article as read or unread.
.P
When an article has been posted to multiple group, vnews will show
the article only in the leftmost newsgroup in the newsgroup list that
you subscribe to.
.P
.SS "Commands"
.P
Each
.B vnews
command may be preceded by a \fIcount\fR.
Some commands use the \fIcount\fR; others ignore it.
If the \fIcount\fR is omitted, it defaults to one.
Some commands prompt for an argument on the secondary prompt line.
The argument is terminated by a return.
Standard
.SM UNIX
erase and kill processing is performed on commands being entered.
An interrupt (delete or break) or a CONTROL-G
gets you out of any partially entered command.
.P
The following commands exist:
.TP 10
.B SP
A space scrolls forward.
.. [Normally space advances an entire page, but if there are
.. only one or two more lines left in the current article,
.. space will simply advance the article the one or two lines
.. required to show the rest of the article.]  \
..
If you are at the end of a article, it advances to the next article.
If you are at the end of the index, it leaves the index and displays
the current article.
.TP 10
.B "^B"
A CONTROL-B goes backwards \fIcount\fR pages.
.TP 10
.B "^D"
Go forward \fIcount\fR half pages.
.TP 10
.BR "^N" " or " "^Y"
Go forwards \fIcount\fR lines.
.TP 10
.BR "^P" " or " "^Z"
Go backwards \fIcount\fR lines.
.TP 10
.B "^U"
Go backwards \fIcount\fR half pages.
.TP 10
.B a
Switch the display between the index and the current article.
The index is identical to that produced by the
.I readnews
.B a
command except that articles which have been read are marked with "D".
All
.I vnews
commands except
.B D
are functional on the index; the article scrolling commands
scroll the index and all other commands apply to the current article.
[In particular, you can type a sequence of
.BR n 's
and
.BR e 's
on the index page to quickly get rid of uninteresting articles.]
.TP 10
.BR "b" " or " "-"
Subtract \fIcount\fR from the current article number.
If the result would be less than one, an error message is printed.
.. [It would be nice to have a command which
.. always go back to the previous article like the 'b' command in 2.10.]
.TP 10
.B c
Cancel the current article.
.B Vnews
prompts for confirmation before canceling the article.
Carriage return or 'y' means yes; anything else means no.
.TP 10
.B "D"
Decrypts a joke.
It only handles rot 13 jokes.
The
.B D
command is a toggle; typing another D re-encrypts the joke.
.TP 10
.B e
Mark the current article as not read and advance \fIcount\fR unread articles.
If there are fewer than \fIcount\fR unread articles left in the
newsgroup, advance to the next newsgroup.
.TP 10
.B h
Go back to the top of the article and display only the header.
.TP 10
.B H
Show the complete header.
Typing
.B l
(or almost any command)
will restore the original display.
.. [I'm still not convinced of the value of this command,
.. but other people seem to be...]
.TP 10
.BR "l" " or " "d"
Causes the current article to be displayed.
Display of the current article is turned off by commands which scramble the
screen
.RB "(" "!" ","
.BR "f" ","
and
.BR "r" ")."
.. My feeling here is that the user frequently wants to respond to an article
.. and then go on to the next article; she/he shouldn't be forced to wait while
.. the current article is rewritten to the screen.
The
.B l
command will also redisplay the article if the help message
or detailed header is currently
displayed.
The (feeble) mnemonic significance of
.B l
is that is similar to control-L, which redraws the screen.
.. [Blanking out the article display has been of rather questionable value
.. since release 2.10, when checks for pending input were added.
.. However, the 3B20 hardware buffers about 256 characters of output,
.. and some versions of UNIX may not support checks for pending input,
.. so I have left it in.  It is straightforward enough if you are used to
.. it.]
.TP 10
.B n
Mark the current article as read.
Then advance by \fIcount\fR unread articles, marking the skipped articles as
read.  If there are fewer than \fIcount\fR unread articles left in the
newsgroup, mark the unread articles as read and advance to the next
newsgroup.
.TP 10
.B N
Go to a different newsgroup.
You are prompted for a newsgroup name.
.IP "" 10
There are two special cases.
A null newsgroup name 
it advances to the next group.
The name "\-" pops everything off the newsgroup stack and then backs
up to the previous group in the .newsrc file.
[This isn't implemented properly yet; the
current code backs up to the previous group that contains articles,
just like in 2.10.
It should restore the previously read group.]
.IP "" 10
Otherwise, the
.B N
command
.. discards everything on the newsgroup stack,
pushes the current group onto the stack and goes to the specified group.
Both read and unread articles are included in the index.
However, the
.B n
and
.B e
commands will skip over the articles that have been already read.
.TP 10
.B p
Gets you the parent article (the article that the current article is a
followup to).
If no
.B References:
line exists for the current article then the title of the article will
be used to guess the parent; of course this guess may be wrong.
.TP 10
.B "q"
Quit. The .newsrc file is updated.
.TP 10
.B r
Reply to the author of the article.
See the
.B f
command for details.
.TP 10
.B f
Post a followup article.
No warning messages are printed.
[I despise them, and so far nobody has asked for them.]
If the article was posted to a moderated group, the followup is mailed to the
sender of the article.
.IP "" 10
Both \fIreply\fR and \fIfollowup\fR work similarly.
A file containing a header is created and an editor is invoked on the file.
You can modify the header as well as enter the body of the article.
The environment variable
.B "$A"
is set to the current article in case you want to refer to it or quote it in
your response.
.B "$A"
will also be passed as an argument to the editor.
.IP "" 10
If you plan to quote extensively from the article you are writing a followup
to, you may find it easiest to include the entire body of the parent article
and delete the parts you don't want.  If you use EMACS (either Gosling's
or Warren Montgomery's), there is a macro called gparent (get parent article)
which is normally bound to ^X^Y
that reads in the body of the parent article, prefacing each line with a
.BR > .
If you use a different editor, such as
.BR ed (1),
there is a shell procedure which will accomplish the same task if your
editor has the ability to capture the output of a command.  In
.BR ed ,
type
.BR "r\ !gparent" .
.. The second argument to the editor is
.. .IR "$A" ,
.. so that if you run Gosling's Emacs, the article will automaticly appear in
.. the second window.
.IP "" 10
If you change your mind about replying or posting a followup article, exit
the editor without changing the file.
.. [A message will appear on the screen to inform you that the response was not
.. mailed/posted.]
If you change your mind about whether you should have used an
.B f
or
.B r
command, edit the first line of the followup.
.. .IP "" 10
.. [Vnews forks off a process to deliver the reply or followup.
.. In order to avoid messing up the screen, errors are reported by mail.]
.TP 10
.B s
Prompts for a filename and writes the article to the file.
Depending on how the netnews administrator set up your system, the article
may have a "From " line in front of it to allow the file to be read using
your mail program.
.IP "" 10
The
.I save
and
.I write
commands normally append to the specified file,
but if
.I count
is zero any existing file is overwritten.
.IP "" 10
If the filename does not begin with a slash
vnews runs through the following list of kludges.
If the filename is omitted, it defaults to "Articles".
A leading "~/" on the filename is replaced by the name of your login directory.
Finally, if the filename is does not begin with a slash and the environment
variable $NEWSBOX is set, then any "%s" in $NEWSBOX replaced
by the current newsgroup and the result is prepended to the filename.
.. Users frequently set $NEWSBOX to the name of their login directly.
.TP 10
.BR "s|" ", " "w|"
Read a command and pipe the article into it.
The article is formated identically to the
.B s
and
.B w
commands.
.TP 10
.B ud
Unsubscribe to the current discussion.
[This command is still somewhat experimental.
In particular, if you unsubscribe to very may groups reading and writing
the .newsrc file becomes very slow.]
.TP 10
.BR ug " or " un
If \fIcount\fR is nonzero, unsubscribe to the current group and advance
to the next group.
The group will no longer be shown to you unless you specificly ask for
it with the
.B N
command.
If \fIcount\fR is zero, resubscribe to the current group.
.. [This is a two character command to ensure that it is not typed accidentally
.. and to leave room for other types of unsubscribes (e. g. unsubscribe to
.. discussion).]
.TP 10
.B v
Print netnews version.
.TP 10
.B w
Like
.B s
except that the article header is not written.
.TP 10
.B "+"
Add \fIcount\fR to the current article number.
If this would go beyond the end of the current newsgroup an error
message is printed.
.TP 10
.B "^\e"
When sent a quit signal,
.B vnews
terminates without updating .newsrc.
Depending on how your system administrator set up vnews
it may or may not generate a core dump.
[This command is intentionally hard to type.]
.TP 10
.B "!"
Prompts for a
.SM UNIX
command and passes it to the shell.
The environment variable
.I $A
is set to the name of the file containing the current article.
If the last character of the command is a "&", then the "&" is deleted and
the command is run in the background with stdin, stdout and stderr redirected
to /dev/null.
If the command is missing, the shell is invoked.
Use the
.B l
command (or essentially any other command) to turn on the display after the
program terminates.
.. .TP 10
.. .B "#"
.. Prints the numbers of the current article and the last article in the current
.. newsgroup.
.TP 10
.B "^L"
Redraws the screen.
CONTROL-L is not a real command; it may be typed while in the middle of
entering another command.
.TP 10
.B "<"
Prompts for an article ID or the rest of a message ID,
and displays the article if it exists.
.TP 10
.B CR
Carriage return goes to the article numbered \fIcount\fR in the current
newsgroup if count is specified.
If count is omitted, then a carriage return is treated like a space.
.TP 10
.B "?"
Displays a synopsis of commands.
The synopsis will be removed from the screen when the next command is
executed.
If you want to remove the synopsis without doing anything else, use the
.B l
command.
.SH AUTHOR
Kenneth Almquist
.br
{harpo, ihnp4, burl, akgua}!hou3c!ka.
.SH BUGS
Netnews release 2.10.2 stores dates in GMT, which is probably not
what the user expects.
.P
As with other visual interfaces,
.B vnews
does not handle typing errors gracefully.
Perhaps there should be an "undo" command.
.P
No "digest" command is provided.
.P
The
.I save
and
.I write
commands should create nonexistent directories.
!E!O!F!

cat > vnews/vnews.h <<\!E!O!F!
#ifndef EXTERN
#define EXTERN extern
#endif

#ifdef HCURSES		/* Mark Horton's version of curses */
#define CURSES
#endif
#ifdef ACURSES		/* Ken Arnold's version of curses */
#define CURSES
#endif
#ifdef CURSES
#include <curses.h>
#define ROWS LINES
int hasscroll = 0;
#else
#endif CURSES

#include <errno.h>
#include "rparams.h"
#include "artfile.h"
#include "newsrc.h"

#ifdef CURSES
#undef LINES
#endif

#define ARTWLEN	(ROWS-2)/* number of lines used to display article */
#ifdef STATTOP
#define SECPRLEN 81	/* length of secondary prompter */
#else
#define SECPRLEN 100	/* length of secondary prompter */
#endif
#define INTR	'\7'	/* interrupt character */
/* prun flags */
#define CWAIT	0001	/* type "continue?" and wait for return */
#define BKGRND	0002	/* run process in the background */
/* values of curflag */
#define CURP1	1	/* cursor after prompt */
#define CURP2	2	/* cursor after secondary prompt */
#define CURHOME	3	/* cursor at home position */
/* types of article lists */
#define SVNEWSGROUP 0	/* a newsgroup was saved (must be zero) */
#define SVARTICLE   1	/* a sigle article was saved */
#if BSDREL >= 42
#define sigset signal
#endif

struct window {
	int w_artlin;			/* line to appear at top of window */
	int w_svartlin;			/* line currently at top of window */
	int (*w_dump)();		/* routine to dump window to screen */
	int w_force;			/* set to force window update */
};


struct svartlist {
	int al_type;			/* type of article list */
	int al_tfoffset;			/* offset into temp file where saved */
};

struct artinfo {
	ARTNO i_artnum;		/* number in spool directory */
	long  i_subtime;	/* date/time article posted, in internal GMT */
	long  i_basetime;	/* subtime of base article */
	DPTR  i_dptr;		/* artfile entry */
	long  i_nlines;		/* lines: header (number of lines) */
	char  i_title[37];	/* subject */
	char  i_from[31];	/* sender of message */
};

extern int errno;

/* terminal handler stuff */
extern int _junked;
#define clearok(xxx, flag) _junked = flag
extern int COLS;
extern int ROWS;
extern int hasscroll;
extern int needbeep;

EXTERN int ngrp;			/* set on entry to new group	*/
EXTERN char filename[BUFLEN];		/* file containing current art	*/

EXTERN FILE *tfp;			/* temporary file */
EXTERN long artbody;			/* offset of body into article */
EXTERN long artlength;			/* article length in bytes */
EXTERN int quitflg;			/* if set, then quit */
EXTERN int hupflag;			/* set when hangup signal received */
EXTERN int erased;			/* current article has been erased */
EXTERN int artlines;			/* # lines in article body */
EXTERN int artread;			/* entire article has been read */
EXTERN int hdrstart;			/* beginning of header */
EXTERN int hdrend;			/* end of header */
EXTERN int lastlin;			/* number of lines in tempfile */
EXTERN int tflinno;			/* next line in tempfile */
EXTERN int tfoffset;			/* offset of article in temp file */
EXTERN char secpr[SECPRLEN];		/* secondary prompt */
EXTERN char prompt[30];			/* prompter */
EXTERN short hdronly;			/* if set, only print article header */
EXTERN short curflag;			/* where to locate cursor */
EXTERN char timestr[20];		/* current time */
EXTERN int ismail;			/* true if user has mail */
EXTERN char *mailf;			/* user's mail file */
EXTERN int atend;			/* set if at end of article */
EXTERN char cerase;			/* erase character */
EXTERN char ckill;			/* kill character */
EXTERN int ospeed;			/* terminal speed */
EXTERN int pstatus;			/* status return from process */
EXTERN int indexpg;			/* set if on index page */
EXTERN struct window *curwin;		/* current window */
EXTERN struct window *nextwin;		/* new window */
#ifdef DIGPAGE
EXTERN int endsuba;			/* end of sub-article in digest */
#endif
#ifdef MYDEBUG
EXTERN FILE *debugf;			/* file to write debugging info on */
#endif
extern struct window artwin;		/* window displaying article */
extern struct window indexwin;		/* window containing article index */
extern struct window helpwin;		/* help windown */
extern struct window emptywin;		/* blank screen */
extern struct window hdrwin;		/* window containing header */

EXTERN struct artinfo *thisng;		/* articles in this newsgroup */
EXTERN int numthisng;			/* # of articles in above array */
EXTERN int curind;			/* how far we've gotten into thisng */
EXTERN struct artinfo *curart;		/* how far we've gotten into thisng */


EXTERN long pngsize;			/* Printing ngsize		*/
EXTERN int  news;
EXTERN struct arthead h;		/* header of current article	*/
EXTERN int dgest;
EXTERN FILE *fp;			/* current article to be printed*/
EXTERN int debugging;			/* debugging turned on */
!E!O!F!

cat > vnews/vnews.help <<\!E!O!F!
Vnews commands:    (each may be preceded by a non-negative count)

SP  Next page or article                D   Decrypt a rot 13 joke
n   Go to next article                  CR  Go to article numbered count
e   Mark current article as unread      <   Go to article with given ID
+   Go forwards count articles          p   Go to parent article
b   Go to previous article              ug  Unsubscribe to this group
^B  Go backwards count pages            ud  Unsubscribe to discussion
^N  Go forward count lines              ^L  Redraw screen
^P  Go backwards count lines            v   Print netnews version
^D  Go forward half a page              c   Cancel the current article
a   Switch to/from article index        q   Quit
H   Display article header              ^\  Quit without updating .newsrc
!   Escape to shell
r   Reply to article
f   Post a followup article
l   Display article (use after !, r, f, or ?)
s   Save article in file
w   Save without header
N   Go to newsgroup (next is default)

[Press l to see article again]
!E!O!F!

cat > vnews/vreadr.c <<\!E!O!F!
/*
 * Readr - visual news interface.
 */

#define EXTERN
#include "vnews.h"

#define PIPECHAR '|'	/* indicate save command should pipe to program */
#define META	0200	/* meta chatacter bit (as in emacs) */
#define UNSUB	0400	/* unsubscribe bit (indicates 'u' command) */
/* flags for vsave routine */
#define SVHEAD	01	/* write out article header */
#define OVWRITE	02	/* overwrite the file if it already exists */

char *getmailname();
int onint(), onquit(), onhup();
int onstop();

static char tfname[] = "/tmp/vnXXXXXX";	/* name of temp file */
static char prbuf[SECPRLEN+1];		/* buf for getting command args */
static int errclean;			/* clean up on error		*/
static char *bptr;			/* temp pointer.		*/
extern struct svartlist *alptr;		/* article list stack		*/


readr()
{
#ifdef	SIGCONT
	int (*ocont)();
#endif
	FILE *popen();

#ifdef MYDEBUG
	debugf = fopen("DEBUG", "w");
	setbuf(debugf, NULL);
#endif

/*
	fp = popen("pwd", "r");
	if (fp == NULL)
		xerror("Cannot fork/exec/popen pwd!\n");
	fgets(curdir, sizeof curdir, fp);
	pclose(fp);
	nstrip(curdir);
*/

	mktemp(tfname);
	if ((tfp = fopen(tfname, "w+")) == NULL)
		xerror("Can't create temp file");
	unlink(tfname);
	mailf = getmailname();

	/* loop reading articles. */
	fp = NULL;
	curwin = Aflag? &indexwin : &artwin;
	if (getnxtng(FORWARD)) {
		fprintf(stderr, "No news.\n");
		return;
	}

	ttysave();
	signal(SIGINT, onint);
	signal(SIGQUIT, onquit);
	signal(SIGHUP, onhup);
	ttyraw();
	errclean = 1;
	timer();

	quitflg = 0;
	while (quitflg == 0) {		/* command loop */
		if (hupflag) {
			errclean = 0;
			return;
		}
		if (!indexpg)
			openart();
		vcmd();
	}
	errclean = 0;
	botscreen();
	ttycooked();
}

/*
 * Read and execute a command.
 */

vcmd() {
	register c;
	register char *p, *q;
	char *promptp;
	char *cmdp;
	char cmd[20];
	int count;
	int countset;

	if (indexpg) {
		atend = numthisng + 2 <= indexwin.w_artlin + ARTWLEN;
	} else {
		appfile(fp, artwin.w_artlin + ARTWLEN + 1);
		atend = artlines <= artwin.w_artlin + ARTWLEN
		     && (! hdronly || artlines <= hdrend);
	}
	setprompt();
	if (atend && ! indexpg && ! erased)
		setnew(0);		/* article read */
	curflag = CURP1;
	promptp = prompt + strlen(prompt);
	cmdp = cmd;
	do {
		c = vgetc();
		if (c == cerase) {
			if (cmdp > cmd)
				cmdp--;
		} else if (c == ckill) {
			cmdp = cmd;
		} else {
			if (c == '\\') {
				strcat(prompt, "\\");
				c = vgetc();
			}
			if (c == INTR) {
				secpr[0] = '\0';
				cmdp = cmd;
			} else {
				*cmdp++ = c;
			}
		}
		*cmdp = '\0';

		p = cmd;
		q = promptp;
		countset = 0;
		count = 0;
		while ((c = *p) >= '0' && c <= '9') {
			if (p > cmd + 5)
				*--cmdp = '\0';		/* limit to 5 digits */
			else
				*q++ = c;
			count = (count * 10) + (c - '0');
			countset = 1;
			p++;
		}
		c = 0;
		if (*p == 'u') {
			c = UNSUB;
			*q++ = 'u';
			p++;
		}
		if (*p == '\033') {			/* escape */
			c |= META;
			*q++ = 'M';
			*q++ = '-';
			p++;
		}
		*q = '\0';
		c |= *p;
	} while (p >= cmdp);

	secpr[0] = '\0';
	if (countset == 0)
		count = 1;
	nextwin = NULL;
	docmd(c, count, countset);
	if (nextwin != NULL)
		curwin = nextwin;
	else if (indexpg)
		curwin = &indexwin;
	else
		curwin = &artwin;
	if (artwin.w_artlin > hdrstart && hdronly)
		hdronly = 0, artwin.w_force |= 1;
}


/*
 * Generate the prompt (percentage of article read).
 */
setprompt() {
	int percent;

	if (atend)
		strcpy(prompt, "100% ");
	else if (hdronly && ! indexpg)
		strcpy(prompt, "0% ");
	else {
		if (indexpg) {
			percent = (800L * (indexwin.w_artlin + ARTWLEN))
				/ (numthisng + 2);
		} else {
			percent = ((800 * (ftell(fp) - artbody)) / (artlength - artbody)
				* (artwin.w_artlin + ARTWLEN)) / lastlin;
		}
		percent = (percent + 4) >> 3;
		if (percent > 99)
			percent = 99;
		strcpy(prompt, "00% ");
		prompt[0] = percent / 10 + '0';
		prompt[1] = percent % 10 + '0';
	}
}		


/*
 * Process one command, which has already been typed in.
 */
docmd(c, count, countset)
{
	int i;
	char *ptr1;
	char *findhist();

	switch (c) {

	/* Turn on/off debugging. */
	case 'z':
		debugging = count;
		msg("debugging level %d", debugging);
		break;

	/* No/Next.  Go on to next article. */
	case 'n':
		setnew(0);
		nextart(count, 1);
		break;

	/* go to specific article, or treat like space */
	case '\n':
		if (countset) {
			setartno(count);
			break;
		}
		/* else fall through */

	/* Show more of current article, or advance to next article */
	case ' ':
		if (indexpg) {
			if (atend)
				indexwin.w_artlin = 0, indexpg = 0;
			else if (hasscroll && numthisng - indexwin.w_artlin <= ARTWLEN)
				scroll(numthisng + 2 - indexwin.w_artlin - ARTWLEN);
			else
				scroll(ARTWLEN);
		} else {
			if (atend)
				nextart(1, 1);
			else if (hdronly) {
				hdronly = 0;
				artwin.w_force |= 1;
				if (hasscroll)
					scroll(hdrstart - artwin.w_artlin);}
			else if ((appfile(fp, artwin.w_artlin + 2 * ARTWLEN), artread)
			 && hasscroll && artlines - artwin.w_artlin <= ARTWLEN + 2)
				scroll(artlines - artwin.w_artlin - ARTWLEN);
			else
				scroll(ARTWLEN);
		}
		break;


	/* All headers: show headers for this newsgroup */
	case 'a':
		indexpg = ! indexpg;
		break;

	/* Back up count pages */
	case META|'v':
	case '\2':	/* Control-B */
		scroll(-ARTWLEN * count);
		break;

	/* forward half a page */
	case '\4':	/* Control-D, as in vi */
		scroll(ARTWLEN/2 * count);
		break;

	/* backward half a page */
	case '\25':	/* Control-U */
		scroll(-ARTWLEN/2 * count);
		break;

	/* forward count lines */
	case '\16':	/* Control-N */
	case '\32':	/* Control-Z */
		scroll(count);
		break;

	/* bakcwards count lines */
	case '\20':	/* Control-P */
	case '\31':	/* Control-Y */
		scroll(-count);
		break;

	/* Turn displaying of article back on */
	case 'l':
	case 'd':
		/* this now happens automaticly */
		break;

	/* display header */
	case 'h':
		scroll(hdrstart - curwin->w_artlin);
		hdronly = 1;
		artwin.w_force |= 1;
		break;

	/*
	 * Unsubscribe to the newsgroup and go on to next group
	 */
	case UNSUB|'g':
	case UNSUB|'n':
		if (count) {
			curng->ng_unsub = 1;
			if (getnxtng(FORWARD))
				quitflg++;
		} else /* resubscribe */
			curng->ng_unsub = 0;
		break;

		/* unsubscribe to the current discussion */
	case UNSUB|'d':
		ud_command();
		break;

	case UNSUB|'a': ptr1 = "author";     goto badusub;
	case UNSUB|'s': ptr1 = "site";       goto badusub;
badusub:	msg("Unsubscribing to this %s: not implemented", ptr1);
		break;

		/* Print the current version of news */
	case 'v':
		msg("News version: %s", news_version);
		nextwin = curwin;
		break;


		/* decrypt joke */
	case 'D': 
		if (indexpg)
			goto illegal;
		appfile(fp, 32767);
		for (i = hdrend ; i < artlines ; i++) {
			register char c, *p;
			tfget(bfr, i);
			for (p = bfr ; (c = *p) != '\0' ; p++) {
				if (c >= 'a' && c <= 'z')
					*p = (c - 'a' + 13) % 26 + 'a';
				else if (c >= 'A' && c <= 'Z')
					*p = (c - 'A' + 13) % 26 + 'A';
			}
			tfput(bfr, i);
		}
		artwin.w_force |= 1;
		hdronly = 0;
		break;

		/* write out the article someplace */
	case 's':
	case 'w':
		{
		int wflags;

		nextwin = curwin;
		if (openart())
			break;
		wflags = 0;
		if (c == 's')
			wflags |= SVHEAD;
		if (count != 1)
			wflags |= OVWRITE;
		msg("file: ");
		curflag = CURP2;
		while ((c = vgetc()) == ' ');
		if (c == INTR) {
			secpr[0] = '\0';
			break;
		}
		if (c == '|') {
			prbuf[0] = PIPECHAR;
			if (prget("| ", prbuf+1))
				break;
		} else {
			pushback(c);
			if (prget("file: ", prbuf))
				break;
		}
		bptr = prbuf;
		if (*bptr != PIPECHAR && *bptr != '/') {
			char	hetyped[BUFLEN];
			char	*boxptr;
			strcpy(hetyped, bptr);
			if (boxptr = getenv("NEWSBOX"))
				if (index(boxptr, '%'))
					sprintf(bptr, boxptr, curng->ng_name);
				else
					strcpy(bptr, boxptr);
			else if (hetyped[0] == '~' && hetyped[1] == '/') {
				strcpy(hetyped, bptr+2);
				strcpy(bptr, userhome);
			} else
				bptr[0] = '\0';
			if (bptr[0])
				strcat(bptr, "/");
			if (hetyped[0] != '\0')
				strcat(bptr, hetyped);
			else
				strcat(bptr, "Articles");
		}
		vsave(bptr, wflags);
		}
		break;

		/* back up  */
	case '-':
	case 'b':
		if (curind <= count) {
			msg("Can't back across newsgroup boundary.");
			break;
		}
		setartno(curind - count);
		break;

		/* skip forwards */
	case '+':
		/* this may not be what we want */
		setartno(curind + count);
		break;

	/* exit - time updated to that of most recently read article */
	case 'q':
		quitflg = 1;
		break;

	/* cancel the article. */
	case 'c':
		strcpy(prompt, "cancel? ");	/* be sure not a typo */
		if ((c = vgetc()) != '\n' && c != 'y')
			break;
		cancel_command();
		break;

	/* escape to shell */
	case '!':
		{
		register char *p;
		int flags;

		nextwin = curwin;
		if (prget("!", prbuf))
			break;
		p = prbuf;
		flags = CWAIT;
		if (*p == '\0') {
			strcpy(prbuf, SHELL);
			flags = 0;
		}
		while (*p) p++;
		while (p > prbuf && p[-1] == ' ')
			p--;
		if (*--p == '&') {
			*p = '\0';
			flags = BKGRND;
			strcpy(bfr, prbuf);
		} else if (*p == '|') {
			*p = '\0';
			sprintf(bfr, "(%s)2>&1|mail '%s'", prbuf, username);
			flags |= BKGRND;
		} else {
			nextwin = &emptywin;
			strcpy(bfr, prbuf);
		}
		shcmd(bfr, flags);
		}
		break;

	/* reply/followup */
	case 'r':
	case 'f':
	{
		static char *arg[3] = {bfr, bfr + FPATHLEN, NULL};

#ifdef REPLYPROG
		strcpy(bfr, REPLYPROG);
#else
		sprintf(bfr, "%s/postreply", LIB);
#endif
		sprintf(bfr + FPATHLEN, "-v%c%s", c, filename);
		prun(arg, 0);
		if (pstatus != 0) {
			msg("reply/followup %s", pstatus == 22<<8? "suppressed" : "command failed");
		}
		nextwin = &emptywin;
		break;
	}

	/* next newsgroup */
	case 'N':
		if (next_ng_command())
			quitflg = 1;
		break;

	/* display parent article */
	case 'p':
		parent_command() ;
		break;

	/* specific message ID. */
	case '<':
		if (prget("<", prbuf))
			break;
		goto_id(prbuf);
		break;

	/* erase - pretend we haven't seen this article. */
	case 'e':
		erased = 1;
		setnew(1);
		nextart(count, 0);
		break;

	case 'H':
		if (openart())
			break;
		nextwin = &hdrwin;
		break;

	case '#':
		msg("Article %d of %d (#%d of max %ld)",
			curind, numthisng, curart->i_artnum,
			pngsize);
		nextwin = curwin;
		break;

		/* error */
	case '?':
		nextwin = &helpwin;
		break;

	illegal:
	default:
		beep();
		nextwin = curwin;
		break;
	}

	return FALSE;
}

cancel_command()
{
	struct arthead *hptr = &h;
	char *ptr1;
	int i;

	if (openart())
		return;
	strcpy(bfr, hptr->h_path);
	ptr1 = index(bfr, ' ');
	if (ptr1)
		*ptr1 = 0;
	i = strcmp(username, bfr);
	if (i != 0) {
		if (isadmin())
			msg("Cancelling locally only");
		else {
			msg("Can't cancel what you didn't write.");
			return;
		}
	}
	if (!cancel(stderr, hptr, i) && hptr == &h) {
		nextart(1, 1);
	}
}



next_ng_command()
{
	struct ngentry *ngp;

	if (prget("group? ", bptr = prbuf))
		return FALSE;
	if (!*bptr || *bptr == '-') {
		return getnxtng(*bptr? BACKWARD : FORWARD);
	}
	while (isspace(*bptr))
		bptr++;
	if ((ngp = findgroup(bptr)) == NULL) {
		msg("No such group: %s.", bptr);
		return FALSE;
	}
	switchng(ngp);
	return FALSE;
}


/*
 * Find parent of article.
 */
parent_command() {
	struct artrec a;
	DPTR dp;

	readrec(curart->i_dptr, &a);
	if (a.a_parent == DNULL) {
		msg("no parent");
		nextwin = curwin;
		return;
	}
	readrec(dp = a.a_parent, &a);
	if (a.a_flags & A_NOFILE) {
		msg("parent not on system");
		nextwin = curwin;
		return;
	}
	spclgrp(dp, &a);
}


/*
 * display an article with a particular article or message ID.
 */
goto_id(ident)
	char *ident;
	{
	struct artrec a;
	DPTR dp;
	char *ptr1, *ptr2;
	char id[NAMELEN];

	bfr[0] = '<';
	strcpy(bfr+1, ident);
	if (index(bfr, '@') == NULL && index(bfr, '>') == NULL) {
		ptr1 = bfr;
		if (*ptr1 == '<')
			ptr1++;
		ptr2 = index(ptr1, '.');
		if (ptr2 != NULL) {
			*ptr2++ = '\0';
			sprintf(prbuf, "<%s@%s.UUCP>", ptr2, ptr1);
			strcpy(bfr, prbuf);
		}
	}
	if (index(bfr, '>') == NULL)
		strcat(bfr, ">");
	scopyn(bfr, id, NAMELEN);
	if ((dp = lookart(id, &a)) == DNULL) {
		msg("%s not found", ident);
		nextwin = curwin;
		return;
	}
	spclgrp(dp, &a);
}



/*
 * Unsubscribe to a discussion.
 */
ud_command() {
	struct artrec a;
	DPTR dp;
	register int i ;

	if (ndunsub >= MAXUNSUB) {
		msg("Unsubscribed to too many discussions");
		return;
	}
	dp = curart->i_dptr;
	readrec(dp, &a);
	while (a.a_parent != DNULL) {
		readrec(dp = a.a_parent, &a);
	}
	dunsub[ndunsub++] = dp;
	msg("unsubscribed to discussion %s", a.a_ident);

	/* Mark any articles in the discussion as read. */
	for (i = 0 ; i < numthisng ; i++) {
		if (thisng[i].i_basetime == a.a_subtime)
			if (alptr->al_type == SVNEWSGROUP)
				clrunread(thisng[i].i_artnum) ;
	}
	nextart(1, 1) ;
}

/*
 * Execute a shell command.
 */

shcmd(cmd, flags)
	char *cmd;
	{
	char *arg[4];

	arg[0] = SHELL, arg[1] = "-c", arg[2] = cmd, arg[3] = NULL;
	prun(arg, flags);
}


prun(args, flags)
	char **args;
	{
	int pid;
	int i;
	int (*savequit)();
	char *env[100], **envp;
	char a[BUFLEN + 2];
	extern char **environ;

	if (!(flags & BKGRND)) {
		botscreen();
		ttycooked();
	}
	while ((pid = fork()) == -1)
		sleep(1);		/* must not clear alarm */
	if (pid == 0) {
		for (i = 3 ; i < 20 ; i++)
			close(i);
		if (flags & BKGRND) {
			signal(SIGINT, SIG_IGN);
			signal(SIGQUIT, SIG_IGN);
			close(0), close(1), close(2);
			open("/dev/null", 2);
			dup(0), dup(0);
		}
		/* set $A */
		sprintf(a, "A=%s", filename);
		env[0] = a;
		for (envp = env + 1 ; *environ != NULL && envp < env + 98 ; environ++)
			if ((*environ)[0] != 'A' || (*environ)[1] != '=')
				*envp++ = *environ;
		*envp = NULL;

		execve(args[0], args, env);
		fprintf(stderr, "%s: not found\n", args[0]);
		exit(20);
	}
	if (!(flags & BKGRND)) {
		savequit = signal(SIGQUIT, SIG_IGN);
		while ((i = wait(&pstatus)) != pid && (i != -1 || errno == EINTR));
		if (flags & CWAIT) {
			fprintf(stderr, "continue? ");
			while ((errno = 0, i = getchar()) != '\n'
				&& (i != EOF || errno == EINTR))
#ifdef old_version /* never worked under BSD */
					if (intflag && i == EOF) {
						fprintf(stderr, "\ncontinue? ");
						intflag = 0;
					}
#else
					;
#endif
		}
		signal(SIGQUIT, savequit);
		ttyraw();
		clearok(curscr, 1);
	}
}

#ifdef DIGPAGE


/*
 * Find end of current subarticle in digets.
 */

findend(l) {
	register i;
	register char *p;

	for (i = l ; i < l + ARTWLEN && i < lastlin ; i++) {
		tfget(bfr, i);
		for (p = bfr ; *p == '-' ; p++);
		if (p > bfr + 24)
			return i + 1;
	}
	return 0;
}

#endif


char *
getmailname() {
	char mailname[FPATHLEN];
	register char *p;

	if( (p = getenv("MAIL")) != NULL)
		return p;
#if BSDREL > 7
	sprintf(mailname, "/usr/spool/mail/%s", username);
#else
	sprintf(mailname, "/usr/mail/%s", username);
#endif
	return savestr(mailname);
}



/*** stolen from rfuncs2.c and modified ***/

vsave(to, flags)
register char *to;
{
	register FILE *ufp;
	int	isprogram = 0;
	int	isnew = 1;
	long	saveoff;
	char	temp[20];
	char	*fname;
	char	prog[BUFLEN + 24];

#define hh h
#define hfp fp
	saveoff = ftell(fp);
	fname = to;
	if (*to == PIPECHAR) {
		isprogram++;
		if (strlen(to) > BUFLEN) {
			msg("Command name too long");
			goto out;
		}
		flags |= OVWRITE;
		strcpy(temp, "/tmp/vnXXXXXX");
		mktemp(temp);
		fname = temp;
		_amove(ROWS - 1, 0);
		vflush();
	}
	if ((flags & OVWRITE) == 0) {
		ufp = fopen(fname, "r");
		if (ufp != NULL) {
			fclose(ufp);
			isnew = 0;
		}
	}
	if ((ufp = fopen(fname, (flags & OVWRITE) == 0? "a" : "w")) == NULL) {
		msg("Cannot open %s", fname);
		goto out;
	}
	/*
	 * V7MAIL code is here to conform to V7 mail format.
	 * If you need a different format to be able to
	 * use your local mail command (such as four ^A's
	 * on the end of articles) substitute it here.
	 */
	if (flags & SVHEAD) {
#ifdef V7MAIL
		fprintf(ufp, "From %s %s",
#ifdef INTERNET
				hh.h_from,
#else
				hh.h_path,
#endif
					ctime(&curart->i_subtime));
#endif
#ifdef V7MAIL
		fseek(fp, 0L, 0);
		while (fgets(bfr, LBUFLEN, fp) != NULL) {
			if (prefix(bfr, "From "))
				putc('>', ufp);
			fputs(bfr, ufp);
		}
		putc('\n', ufp);	/* force blank line at end (ugh) */
#else
		fseek(fp, artbody, 0);
		while (fgets(bfr, LBUFLEN, fp) != NULL) {
			fputs(bfr, ufp);
		}
#endif
	} else {
		fseek(fp, artbody, 0);
		while (fgets(bfr, LBUFLEN, fp) != NULL) {
			fputs(bfr, ufp);
		}
	}

	fclose(ufp);
	if (isprogram) {
		sprintf(prog, "(%s)<%s", to + 1, fname);
		shcmd(prog, CWAIT);
		nextwin = &emptywin;
	} else {
		if ((flags & OVWRITE) == 0)
			msg("file: %s %s", to, isnew ? "created" : "appended");
		else
			msg("file: %s written", to);
	}

out:
	if (isprogram) {
		unlink(fname);
	}
	fseek(fp, saveoff, 0);
}



/*** routines originally in rfuncs.c ***/


xerror(fmt, a1, a2, a3, a4)
char	*fmt;
{
	int clean = errclean;

	errclean = 0;
	fflush(stdout);
	if (clean) {
		botscreen();
		ttycooked();
	}
	fprintf(stderr, "vnews: ");
	fprintf(stderr, fmt, a1, a2, a3, a4);
	fprintf(stderr, ".\n");
	if (clean)
		writeoutrc();
	xxit(1);
}



xxit(status)
int	status;
{
	exit(status);
}
!E!O!F!

echo Part 7 of 7 extracted.