/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is       *
 * provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.                                           *
 ************************************************************************/
/*
 * Modified: October 1988, by Tom Hageman [TRH]
 * add support for hybrid System V/BSD systems without job control.
 * [22-Aug-91] add VMS support.
 */

#include "tune.h"

#ifdef IPROCS					/* the rest of this file */

RCS("$Id: iproc.c,v 14.31.0.12 1994/06/23 02:54:50 tom Exp tom $")

#include "jove.h"
#include "ctype.h"
#include "io.h"
#include "process.h"
#include "re.h"
#include "tty.h"

#define DEAD	0	/* Dead but haven't informed user yet */
#define STOPPED 1	/* Job stopped */
#define RUNNING 2	/* Just running */
#define NEW	3	/* This process is brand new */

/* If process is dead, flags says how. */
#define EXITED	1
#define KILLED	2

#define proc_buf(p)	((p)->p_buffer->b_name)
#define proc_cmd(p)	((p)->p_name)
#define proc_state(p)	((p)->p_state)
#define proc_note(p,s)	(updmodline(), (p)->p_state = (s))

#define isdead(p)	((p) == NULL || proc_state(p) == DEAD || (p)->p_fd < 0)

private Process *procs ZERO;
int	NumProcs ZERO;

private void
	free_proc __(( Process *_(p) )),
	proc_kill __(( Process *_(p), int _(sig) )),
	proc_rec __(( Process *_(p), const char *_(buf) ));
private Process *
	new_proc __(( const char *_(bufname), int _(clobber), va_list _(ap) ));
private void
	unzero __(( char *_(str), int _(n) ));

#ifdef PIPEPROCS
#   include "ipr-pipe.ci"
#else
#   if !vms
#	include "ipr-pty.ci"
#   else
#	include "ipr-vms.ci"
#   endif
#endif

DEF_STR( "process-prompt", proc_prompt, 128, V_REGEXP ) _IF(def IPROCS) = "% "; _IF(def PRIVATE)

public const char *
pstate(p)
register Process *p;
{
	static char	resultbuf[30];
	register char	*result = resultbuf;

	if (p == NULL)
		return "No Process";

	switch (proc_state(p)) {
	case NEW:
		result = "DBX New";
		break;
	case STOPPED:
		result = "DBX Stopped";
		break;
	case RUNNING:
		result = "DBX Running";
		break;
	case DEAD:
		sprintf(result,
			(p->p_howdied == KILLED) ? "Killed %d" :
			(p->p_reason == EXIT_SUCCESS) ? "Done" :
						   "Exit %d", p->p_reason);
		return result;
	default:
		return "Unknown state";
	}
	if (!p->p_dbx_mode)
		result += 4;	/* skip "DBX " */

	return result;
}

void
KillProcs()
{
	register Process *p;
	register int	asked = NO;

	if (p = procs) do {
		if (isdead(p))
			continue;
		if (!asked &&
		    !(asked = yes_or_no_p('Y', "Should I kill your i-processes? ")))
			return;

		proc_kill(p, SIGKILL);
	} while (p = p->p_next);
}

int
pbufalivep(b)
Buffer *b;
{
	register Process	*p;

	return (b && (p = b->b_process, !isdead(p)));
}

void
pbuftiedp(b)
register Buffer	*b;
{
	register Process	*p = b->b_process;

	if (!isdead(p))
		complain("Process %s, attached to %b, is %s.",
			 proc_cmd(p), b, pstate(p));
}

DEF_STR( "dbx-format-string", dbx_parse_fmt, 128, V_REGEXP ) _IF(def IPROCS)_IF(def PRIVATE) =
"line \\([0-9]*\\) in \\{file,\\} *\"\\([^\"]*\\)\"\
\\| at \\([^ ]+\\):\\([0-9]+\\)$\\|^\\{0x[0-9a-fA-F]+\t,\\}\\([0-9]+\\)[^:]";
	/* dbx syntax\|Gnu Debugger syntax. */
DEF_STR( "dbx-program-pattern", dbx_programs, 128, V_REGEXP ) _IF(def IPROCS)_IF(def PRIVATE) =
	"dbx\\|gdb";

DEF_CMD( "process-dbx-output", DBXpoutput, NO ) _IF(def IPROCS)
{
	register Process	*p;

	if ((p = curbuf->b_process) == NULL)
		complain("[Must be in a process buffer to enable dbx mode]");
	p->p_dbx_mode ^= 1;	/* toggle */
	updmodline();
}

private void watch_input __(( Mark *_(m) ));
private void
watch_input(m)
Mark	*m;
{
	Bufpos	save;
	Bufpos	*next;

	DOTsave(&save);
	ToMark(m);
	while ((next = dosearch(dbx_parse_fmt, FORWARD, YES)) != NULL) {
		char		fnamebuf[FILESIZE];
		register char	*fname = fnamebuf;
		register int	lnum = get_FL_info(fname);
		static Buffer	*b ZERO;
		register Window	*savew = curwind;

		SetDot(next);

		if (fname[0])
			b = do_find((Window *) 0, fname, YES);
		if (lnum <= 0 || b == NULL)
			continue;
		pop_wind(b->b_name, NO, NO);
		/* [TRH] although do_find() is now capable of skipping to
		   a given line number in the buffer, we cannot use this here
		   because pop_wind() positions to the *window*s idea of
		   point if the buffer is already visible in that window.
		   So we have to do it ourselves... */
		to_line(lnum);
#if (HIGHLIGHT)
		WHighLight(curwind, b->b_dot);
#endif
		SetWind(savew);
	}
	SetDot(&save);
}

/* Process receive: receives the characters in buf, and appends them to
   the buffer associated with p. */

private void
proc_rec(p, buf)
register Process *p;
const char	*buf;
{
	Buffer		*saveb = curbuf;
	register Window	*w = curwind;
	register Buffer *pb = p->p_buffer;
	register Mark	*savepoint;
	int		/*- sameplace = NO, -*/
			do_disp = NO;

	/* display only if the point where we left off is visible in a window */
	if (w->w_bufp == pb || (w = windbp(pb)))
		if (in_window(w, p->p_mark->m_line) >= 0)
			do_disp++;
	SetBuf(pb);
	savepoint = MakeMark(pb->b_dot, pb->b_char, FLOATER);
	ToMark(p->p_mark);	/* Where output last stopped. */
#if NO
	/* [TRH] this is obsolete now FLOATERing marks float along with Point */
	if (savepoint->m_line == pb->b_dot && savepoint->m_char == pb->b_char)
		sameplace++;
#endif
#ifdef PROC_WRAP
	buf = chop((char *)buf, -1);
	do {
		ins_str(buf, YES);
	} while ((buf = chop((char *)0, 0)) && (LineInsert(1), TRUE));
#else
	ins_str(buf, YES);
#endif
	if (do_disp && p->p_dbx_mode)
		watch_input(p->p_mark);
		/* output from the process should be buffered for this to
		   work. So PIPEPROCS usually work OK, but for instance CDB
		   through pty fails (on hp-ux 5.5). */
	MarkSet(p->p_mark, pb->b_dot, pb->b_char);
#if NO
	if (!sameplace)
#endif
		ToMark(savepoint);	/* Back to where we were. */
	DelMark(savepoint);
	/* redisplay now, instead of right after the ins_str, so that
	   we don't get a bouncing effect if point is not the same as
	   the process output position */
	if (do_disp) {
		w->w_line = pb->b_dot;
		w->w_char = 0;	/* so long lines won't bounce if this is not
				   the active window. */
		if (inIOread) {	/* don't disturb Typeout or similar */
			inIOread--;	/* so charp() will work */
			redisplay();
			inIOread++;
		}
	}
	SetBuf(saveb);
}

private void
proc_kill(p, sig)
register Process	*p;
{
	if ((p) == NULL || proc_state(p) == DEAD)
		return;
	if (p->p_pid <= 0 || killpg(p->p_pid, sig) < 0)
		s_mess("Cannot kill %b!", p->p_buffer);
}

/* Create a Process descriptor, and bring it up in a window */

private Process *
new_proc(bufname, clobber, ap)
const char	*bufname;
va_register va_list ap;
{
	register Process *newp = (Process *) emalloc(sizeof(Process));
	register Buffer *newbuf;

	newp->p_next = procs;
	procs = newp;

	newp->p_fd = -1;	/* no I/O channel yet; so looks dead */
	newp->p_state = NEW;
	newp->p_reason = 0;
#ifdef IPARSE
	newp->p_terminate = NULL;
#endif
    {				/* build command line */
	register char	*cp,
			*d = genbuf;

	/* don't show "/bin/sh -c" (or whatever is in Shell and ShFlags) */
	if ((cp = va_arg(ap, char *)) == Shell) {
		if ((cp = va_arg(ap, char *)) == ShFlags)
			cp = va_arg(ap, char *);
		else
			d = appcpy(d, Shell), *d++ = ' ';
	}
	/* Automagically enable DBX mode, based on program name. */
	newp->p_dbx_mode = LookingAt(dbx_programs, basename(cp), 0);

	while ((d = appcpy(d, cp)), (cp = va_arg(ap, char *)))
		*d++ = ' ';
    }
	newp->p_name = copystr(genbuf);
	newbuf = do_select((Window *) 0, bufname);
	SETBUFTYPE(newbuf, B_IPROCESS);
#ifdef FIXED_MAPS
	/* This kludgery is needed since SetBuf won't PushPB when new
	   i-process buffer is the same as curbuf.  Argh! */
	if (newbuf == curbuf && newbuf->b_process == NULL)
		PushPBs();
#endif
	newp->p_buffer = newbuf;
	newbuf->b_process = newp;	/* sorta circular, eh? */
	pop_wind(bufname, clobber, B_IPROCESS);
	/* Pop_wind() after everything is set up; important!
	   Bindings won't work right unless newbuf->b_process is already
	   set up BEFORE NEWBUF is first SetBuf()'d. */
	ToLast();
	if (!bolp(newbuf))
		LineInsert(1);
	/*
	 * [TRH] !!this mark should NOT float around with Point, so use
	 * "semi"-floatering mode...
	 */
	newp->p_mark = MakeMark(newbuf->b_dot, newbuf->b_char, 1);

	NumProcs++;

	return newp;
}

/* Deal with a process' death.  does all the necessary cleanups */

private void
free_proc(child)
register Process	*child;
{
	register Process	**hp;
	register Buffer		*cb = curbuf;

	if (!isdead(child))
		return;
	for (hp = &procs; (*hp) != child; hp = &(*hp)->p_next)
		if ((*hp) == NULL) {
			f_mess("?process?");
			return;
		}
	(*hp) = child->p_next;
	proc_close(child);		/* if not already closed */

	/* It's possible that the buffer has been given another process
	   between the time CHILD dies and CHILD's death is noticed (via
	   list-processes).  So we only set it the buffer's process to
	   NULL if CHILD is still the controlling process. */

	if (child->p_buffer->b_process == child) {
#ifdef FIXED_MAPS
		if (child->p_buffer == cb)
			PopPBs();		/* not a process anymore */
#endif
		child->p_buffer->b_process = NULL;
	}
	if ((curbuf = child->p_buffer) != NULL && child->p_mark != NULL)
		DelMark(child->p_mark);
	curbuf = cb;
	free((void_*) child->p_name);
	free((void_*) child);
}

DEF_CMD( "kill-process", ProcKill, NO ) _IF(def IPROCS)
{
	register const Buffer	*b = curbuf;

	if (b->b_process == NULL) {	/* choose a suitable default */
		if (procs == NULL)
			complain("[No subprocesses]");
		b = procs->p_buffer;
	}
	if ((b = buf_exists(ask_buf(b))) == NULL)
		complain("[No such buffer]");
	if (b->b_process == NULL)
		complain("[%b not tied to a process]", b);
	proc_kill(b->b_process, SIGKILL);
}

DEF_CMD( "list-processes", ProcList, NO ) _IF(def IPROCS)
{
	register Process	*p,
				*next;
	register const char	*fmt = "%-15s  %-15s  %-8s %s";

	if ((p = procs) == NULL) {
		message("[No subprocesses]");
		return;
	}
	TOstart("*Process list*", TRUE);

	Typeout(fmt, "Buffer", "Status", "Pid", "Command");
	Typeout(fmt, "------", "------", "---", "-------");
	do {
		Typeout(fmt, proc_buf(p), pstate(p), itos(p->p_pid), p->p_name);
		next = p->p_next;
		if (isdead(p)) {
			free_proc(p);
			updmodline();
		}
	} while (p = next);

	TOstop();
}

#ifdef CHDIR

DEF_INT( "shell-cwd-sync", CwdSync, V_BOOL ) _IF(def IPROCS)_IF(def CHDIR)_IF(def PRIVATE) ZERO;

private void
catch_cwd(from)
Mark	*from;
{
	register Bufpos	*bp;

	ToMark(from);

	while (bp = dosearch("\\<\\(\\{cd,pushd,popd,dirs,pwd\\}\\)\\>[ \t]*\\([^ \t;]*\\)",
			     FORWARD, YES)) {
		char	cmdbuf[10],
			dirbuf[FILESIZE];
		int	matchlen = REbom - REeom;	/* sic! */

		putmatch(1, cmdbuf, sizeof cmdbuf);
		putmatch(2, dirbuf, sizeof dirbuf);

		Eof();	/* in case the command fails */

		if (dirbuf[0] == '\0' && cmdbuf[0] == 'c' /* cd */)
			strcpy(dirbuf, HomeDir);

		/* check that JOVE can chdir to it. */
		if (dirbuf[0] != '\0') {
			char	tmpdir[FILESIZE];

			env_expand(strcpy(tmpdir, dirbuf));
			if (chdir(PathParse(tmpdir, genbuf)) != 0) {
				rbell();
				s_mess("[JOVE cannot \"%s %s\"; possibly out of sync with shell]",
				       cmdbuf, dirbuf);
				break;
			}
		}

		/* execute the command in JOVE */
		sprintf(Inputp = genbuf, "%s %s\n", cmdbuf, dirbuf);
		Extend();
		Inputp = NULL;

		if (cmdbuf[1] == 'i' /* dirs */ || cmdbuf[1] == 'w' /* pwd */)
			continue;

		/* replace the match with an explicit cd to the directory,
		   so shells that don't have pushd/popd also benefit. */
		SetDot(bp);
		exp = matchlen, exp_p = NO, DelNChar();
#   if !vms
		ins_str(sprint("cd %s", pwd()), NO);
#   else
		ins_str(sprint("set default %s", ux2vms(genbuf, pwd())), NO);
#   endif
	}
	Eof();
}
#endif /* CHDIR */

private void do_rtp __(( Mark *_(mp) ));
private void
do_rtp(mp)
register Mark	*mp;
{
	register Buffer	*cb = curbuf;
	register Process *p = cb->b_process;
	register Line	*line;
	register int	length;
	Line		*line1 = cb->b_dot,
			*line2 = mp->m_line;
	int		char1 = cb->b_char,
			char2 = mp->m_char;

	if (isdead(p) || p->p_buffer != cb)
		return;

	fixorder(&line1, &char1, &line2, &char2);
	line = line1;
	do {
		length = ltobuf(line, genbuf);
		if (line == line2)
			length = char2;
		else
			genbuf[length++] = '\n';
		proc_write(p, genbuf + char1, length - char1);
		char1 = 0;
	} while (line != line2 && (line = line->l_next));
}

DEF_CMD( "process-newline", SendData, ARG(YES) ) _IF(def IPROCS);
DEF_CMD( "process-send-data-no-return", SendData, ARG(NO) ) _IF(def IPROCS)
{
	register Buffer		*cb = curbuf;
	register Process	*p = cb->b_process;

	if (isdead(p))
		return;
#ifdef ABBREV
	if (BufMinorMode(cb, Abbrev))
		AbbrevExpand();
#endif
	if (lastp(cb, cb->b_dot)) {
		Eol();
		if (LastCmd->Type & ARG(YES))
			LineInsert(1);
#ifdef CHDIR
		if (True(CwdSync) && cb->b_name[0] == '*' /* "*shell*" */)
			/* all other i-process buffer names start with '[' */
			catch_cwd(p->p_mark);
#endif
		do_rtp(p->p_mark);
		MarkSet(p->p_mark, cb->b_dot, cb->b_char);
	} else {
		register int	match = 0;

		/* Either we're looking at a prompt, or we're not, in
		   which case we want to strip off the beginning of the
		   line anything that looks like what the prompt at the
		   end of the file is.  In other words, if "(dbx) stop in
		   ProcessNewline" is the line we're on, and the last
		   line in the buffer is "(dbx) ", then we strip off the
		   leading "(dbx) " from this line, because we know it's
		   part of the prompt.  But this only happens if "(dbx) "
		   isn't one of the process prompts ... follow what I'm
		   saying? */

		strcpy(genbuf, linebuf);
		Eof();
		while (LookingAt(proc_prompt, genbuf, match) && REeom > match)
			match = REeom;
		if (!match)
			match = numcomp(genbuf, linebuf);
		ins_str(genbuf + match, NO);
	}
}

DEF_CMD( "shell", ShellProc, NO ) _IF(def IPROCS)
{
	register const char	*shbuf = "*shell*";
	register const Buffer	*b;
	register Process	*p;
	register const char	*shell;

	if ((shell = IShell) == NULL)
		shell = Shell;
	if ((b = buf_exists(shbuf)) == NULL || (p = b->b_process, isdead(p)))
		proc_strt(shbuf, NO, shell, IShFlags, (char *) 0);
	pop_wind(shbuf, NO, NO);
}

DEF_CMD( "i-shell-command", Iprocess, NO ) _IF(def IPROCS)
{
	char		bnamebuf[BNAMESIZE];
	register Buffer *b;
	register Process *p;
	register char	*command,
			*bname,
			*basenm;
	register int	try = 0;
	Window		*owind = curwind;

	command = set_str(&ShcomBuf, ask(ShcomBuf, ProcFmt));
	bname = basenm = MakeName(command);
	while ((b = buf_exists(bname)) && (p = b->b_process, !isdead(p)))
		sprintf(bname = bnamebuf, "%s'%d", basenm, ++try);
	proc_strt(bname, (exp_p == NO), Shell, ShFlags, command, (char *) 0);
	SetWind(owind);
}

#ifdef IPARSE

/* Run `command' as i-process in buffer `bname' with `term_handler'
   to be invoked when the command terminates. */

void
IprocWTerm(bname, command, term_handler)
register const char	*bname;
const char		*command;
data_obj		*term_handler;
{
	register Buffer		*b;
	register Process	*p;
	char			namebuf[BNAMESIZE];
	register int		try = 0,
				asked = NO;
	register char		*tryname = (char *) bname;

	/* Find any running sub-processes that apparently belong to this family.
	   If any is found, ask for confirmation before creating a parallel
	   session.  After asking, break the loop as soon as a "dead" buffer
	   is encountered. */

	while (b = buf_exists(tryname)) {
		if (p = b->b_process, isdead(p)) {
			if (asked)
				break;
		}
		else if (!asked) {
			confirm("%s is still running. Shall I create a parallel session? ", bname);
			asked++;
		}
		sprintf(tryname = namebuf, "%s'%d", bname, ++try);
	}
	if (asked)
		bname = tryname;

	proc_strt(bname, YES, Shell, ShFlags, command, (char *) 0);

	/* Make sure that the process was indeed created. */
	if ((b = buf_exists(bname)) && (p = b->b_process, !isdead(p))) {
		set_wsize(EWSize);

		p->p_terminate = term_handler;
#if vms
		VMS_FORCE_IPROC_TERMINATION_KLUDGE(p);
#endif
	}
}
#endif /* IPARSE */

void
kill_off(pid, w)
int	pid;
WAIT_T	w;
{
	register Process	*child;
	char			msgbuf[sizeof mesgbuf];
	register char		*msg = msgbuf;
	register Window		*wind;

	if ((child = proc_pid(pid)) == NULL)
		return;

	updmodline();			/* We're changing state ... */

	if (WIFSTOPPED(w))
		child->p_state = STOPPED;
	else {
		if (WIFEXITED(w)) {
			child->p_reason = WEXITSTATUS(w);
			child->p_howdied = EXITED;
		} else if (WIFSIGNALED(w)) {
			child->p_reason = WTERMSIG(w);
			child->p_howdied = KILLED;
		}
		proc_close(child);
		child->p_state = DEAD;	/* This order is important!! */
	}
	sprintf(msg, "[Process %s: %s]", proc_cmd(child), pstate(child));
	dobell(2);

	/* display on message line if not visible in a window */
	if ((wind = windbp(child->p_buffer)) == NULL ||
	    in_window(wind, child->p_mark->m_line) < 0) {
		if (!Asking) {
			message(msg);
			errormsg++;	/* Makes message linger around. */
			redisplay();
		}
	}
	proc_rec(child, msg);
	proc_rec(child, "\n");

#ifdef IPARSE
    {
	register Buffer		*b;
	register data_obj	*cp;

	if ((cp = child->p_terminate) &&
	    /* only if buffer non-empty (apart from what we just inserted.) */
	    (b = child->p_buffer, !firstp(b, b->b_last->l_prev))) {
		int	doit;
		char	savemesg[sizeof mesgbuf];

		strcpy(savemesg, mesgbuf);	/* remember old message */

		doit = yes_or_no_p(NIL, "%s %s? ", msg, cp->Name);

		message(savemesg);

		if (doit) {
			pop_wind(b->b_name, NO, NO);
			exp_p = NO, exp = 1;	/* just once */
			ExecCmd(cp);
		}
		redisplay();
	}
    }
#endif /* IPARSE */
	--NumProcs;
}

#ifdef FIXED_MAPS	/* Obsolete with new Keymap mechnism. */

/* Push/pop process bindings.  I openly acknowledge that this is a
   kludge, but I can't be bothered making it right. */

struct proc_bind {
	data_obj	**pb_at;
	data_obj	*pb_push;
	data_obj	*pb_cmd;
	struct proc_bind *pb_next;
};

private struct proc_bind *PBinds ZERO;

void
PopPBs()
{
	register struct proc_bind *p;

	if (p = PBinds) do {
		p->pb_cmd = *p->pb_at;
		*p->pb_at = p->pb_push;
	} while (p = p->pb_next);
}

void
PushPBs()
{
	register struct proc_bind *p;

	if (p = PBinds) do {
		p->pb_push = *p->pb_at;
		*p->pb_at = p->pb_cmd;
	} while (p = p->pb_next);
}

DEF_CMD( "process-bind-to-key", ProcBind, NO ) _IF(def IPROCS)_IF(def FIXED_MAPS)
{
	register data_obj	*d,
				**at;
	register struct proc_bind *p;

	d = findcom(ProcFmt);
	s_mess(ProcArgFmt, d->Name);
	at = MapKey(activemap, MAP_BIND);

	if (p = PBinds) do {
		if (p->pb_at == at)
			break;
	} while (p = p->pb_next);
	if (p == NULL) {
		p = (struct proc_bind *) emalloc(sizeof *p);
		p->pb_next = PBinds;
		p->pb_push = *at;
		PBinds = p;
	}

	p->pb_at = at;
	p->pb_cmd = d;
	if (curbuf->b_process)
		*at = d;
}
#endif /* FIXED_MAPS */

/* Strip null-bytes from string, and zero-terminate. */

private void
unzero(str, n)
char	*str;
{
	register char	*s = str,
			*d,
			*end = s + n;

	while (*s++) {
		if (s >= end) {
			*s = '\0';
#ifdef CRLF
			goto do_crlf;	/* Yuck! but I'm lazy right now... */
#else
			return;
#endif
		}
	}
	d = s - 1;
	while (s < end)
		if ((*d++ = *s++) == '\0')
			--d;
	*d = '\0';
#ifdef CRLF
do_crlf:
	if (True(BinRdFile))
		return;

	/* Ugly hack to convert CRLF pairs to LF. */
	s = str;
	do {
		if (*s == '\0') 
			return;
	} while (!(*s++ == '\r' && (*s == '\n' || *s == '\0')));

	d = s - 1;
	while (*d = *s++) {
		if (*d++ == '\r' && (*s == '\n' || *s == '\0'))
			--d;
	}
#endif
}

#endif /* IPROCS */

/*======================================================================
 * $Log: iproc.c,v $
 * Revision 14.31.0.12  1994/06/23  02:54:50  tom
 * (ProcList): fix name of scratch buffer; (Iprocess): fix uniquized bufname.
 *
 * Revision 14.31.0.10  1994/04/22  18:24:21  tom
 * (new_proc): use `va_register va_list'; (unzero): handle CRLF.
 *
 * Revision 14.31.0.9  1993/12/13  04:11:35  tom
 * (dbx-program-pattern): new user variable; (new_proc): use it;
 * (watch_output): fix && -> || typo.
 *
 * Revision 14.31  1993/02/15  02:01:47  tom
 * remove (void) casts.
 *
 * Revision 14.30  1993/02/05  00:07:29  tom
 * cleanup whitespace; some random optimizations.
 *
 * Revision 14.29  1992/12/30  11:05:27  tom
 * some changes for the benefit of incremental error parsing.
 *
 * Revision 14.28  1992/10/24  01:24:20  tom
 * convert to "port{ansi,defs}.h" conventions.
 *
 * Revision 14.26  1992/08/26  23:56:54  tom
 * fix bug in watch_input() introduced in 14.18; add GDB syntax to
 * default "dbx-format-string"; use explicit "i-shell", "i-shell-flags" in
 * "shell"; make process exit message more persistent; add RCS directives.
 *
 */
