/************************************************************************
 * 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.						*
 ************************************************************************/
/*
 * [May 89 TRH] for this to work non-unix systems, You have to supply
 * the routine ShellToBuf(bufname, disp, wsize, clobber, cmd)
 * [Jan 91 TRH] add I-Process parsing.
 * [Jun 91 TRH] revision: now the routine
 *	UnixToBuf(bufname, disp, wsize, clobber, infile, command ... )
 * is the interface (and a misnomer for non-unix systems...life is wonderful!).
 */

#define NO_PROCDECL		/* kludge for teensy pdp11 compiler */

#include "jove.h"

RCS("$Id: proc.c,v 14.32.0.12 1994/06/15 12:02:29 tom Exp tom $")

#include "ctype.h"
#include "io.h"
#define Extern	public	/* force definition of any variables in "process.h" */
#include "process.h"
#include "re.h"
#if (HIGHLIGHT)
#   include "termcap.h"
#endif

/* This disgusting RE search string parses output from the GREP
   family, from the pdp11 compiler, pcc, and lint.  Jay (HACK)
   Fenlasen changed this to work for the lint errors.
   [TRH] added the (Atari-ST) Turbo C errors, the {GEM,MS}DOS
   absolute pathnames, and the Turbo/GNU C in-line position guess.
   {{This is about as complex as RE strings can get with current RE_SIZE;
     especially [...]s take a relatively large amount of space}} */

DEF_STR( "error-format-string", ErrFmt, 200, V_REGEXP ) _IF(def PRIVATE) =
#if !vms
"\
^\\{Error ,cc: \",\"?\\}\\(\\{.:\\\\,.:/,\\}[^:\" (\t]+\\)\
\\{ ,\"\\, line ,:,(\\}\t* *\\([0-9]+\\)\\{:,)\\}\
\\{.*[`'\"]\\([^'\"]*\\)\\{',\"\\},\\}\
\\|\
\\{::  *,( \\}\\([^(]*\\)(\\(.+\\))\\{ )\\,,$\\}\
";
#else
"\
^\\{cc: \",\"?\\}\\([^\" (\t]+\\)\
\\{ ,\"\\, line ,:,(\\}\t* *\\([0-9]+\\)\\{:,)\\}\
\\{.*[`'\"]\\([^'\"]*\\)\\{',\"\\},\\}\
\\|\
^\t\tAt line number \\([0-9]+\\) in \\(.*\\);[0-9]*\\.$\
";
#endif

struct error {
	Buffer		*er_buf;	/* Buffer error is in */
	Line		*er_mess;	/* Actual error message */
	Mark		er_mark;	/* Pointer to actual error */
	/* Note that this is an embedded mark, NOT a pointer to mark.
	   Old names are retained for backward compatibility. */
#define		er_text	er_mark.m_line	/* Actual error */
#define		er_char er_mark.m_char	/* char pos of error */
	struct error	*er_prev,	/* List of errors */
			*er_next;
};

private struct error	*cur_error ZERO,
			*errorlist ZERO;
Buffer			*perr_buf ZERO; /* Buffer with error messages */

DEF_INT( "write-files-on-make", WtOnMk, V_BOOL ) = YES; _IF(def PRIVATE)
	/* Write the modified files when we make */

DEF_INT( "error-window-size", EWSize, V_BASE10|MAX(100) ) = 20;
	/* percentage of screen the error window should be */

#ifdef COLOR
#   include "screen.h"		/* for color definitions. */

DEF_INT( "error-buffer-color", ErrColor, V_COLOR ) _IF(def COLOR)_IF(def PRIVATE) ZERO;
	/* give current error buffer this color. */

private int saved_color;

#   define set_errbuf(errbuf)	(saved_color = (perr_buf = (errbuf))->b_color)
#else
#   define set_errbuf(errbuf)	(perr_buf = errbuf)
#endif

#ifdef IPARSE			/* Support for incremental error parsing. */
private void	(*err_more)__(( void )) ZERO;	/* Parse more errors. */
#endif

void
set_wsize(fraction)
{
	extern int	LI;
	register Window	*w = curwind;
	register int	wsize;

	if (one_windp(w) || (wsize = fraction) <= 0)
		return;
	if ((wsize = (LI * wsize) / 100) == 0)
		wsize = 1;
	WindSize(w, wsize - SIZE(w));
}

/* Add an error to the end of the list of errors.  This is used for
   parse-{C,LINT}-errors and for the spell-buffer command */

/* Free up all the errors */

void
ErrFree()
{
    {
	register struct error	*ep, *prev;

	for (ep = errorlist; (prev = ep) != NULL; ) {
		ep = ep->er_next;
		if (bufno(prev->er_buf))
			/* delete mark only if buffer still exists. */
			del_mark(&prev->er_mark, prev->er_buf);
		free((void_*) prev);
	}
    }
	errorlist = cur_error = NULL;
#if (HIGHLIGHT)
    {
	register Window	*ew;

	if (ew = windbp(perr_buf))
		WUnHighLight(ew);
    }
#endif
#ifdef COLOR
	if (perr_buf)
		perr_buf->b_color = saved_color;
#endif
#ifdef IPARSE
	err_more = NULL;
#endif
}

private struct error *AddError __(( struct error *_(laste), Line *_(errline),
				    Buffer *_(buf), Line *_(line), int _(charpos) ));
private struct error *
AddError(laste, errline, buf, line, charpos)
register struct error	*laste;
Line	*errline,
	*line;
register Buffer	*buf;
{
	register struct error	*new = (struct error *) emalloc(sizeof *new);

	if (new->er_prev = laste)
		laste->er_next = new;
	else {
#if NO
		if (errorlist)		/* Free up old errors */
			ErrFree();
#endif		/* This is now done explicitly before a new list is built. */
		cur_error = errorlist = new;
	}
	new->er_next = NULL;
	new->er_buf = buf;
	init_mark(&new->er_mark, buf, line, charpos, MarksShouldFloat);
	new->er_mess = errline;

	return new;
}

private int okay_error __(( void ));
private int
okay_error()
{
	register struct error	*e = cur_error;

	return (inlist(perr_buf->b_first, e->er_mess) &&
		bufno(e->er_buf) &&
		inlist(e->er_buf->b_first, e->er_text));
}

/* Show the current error, i.e. put the line containing the error message
   in one window, and the buffer containing the actual error in another
   window. */

private const char	noerrs[] = "No errors!";
#ifdef IPARSE
private const char	noerrs_yet[] = "No errors! (yet)";
#   define NOERRS	(pbufalivep(perr_buf) ? noerrs_yet : noerrs)
#else
#   define NOERRS	noerrs
#endif

DEF_CMD( "current-error", ShowErr, NO )
{
	register Window *err_wind,
			*buf_wind;
	register struct error
			*ce;

	if ((ce = cur_error) == NULL
#ifdef IPARSE
	    && !(err_more && ((*err_more)(), ce = cur_error))
#endif
	    ) {
		message(NOERRS);
		rbell();
		return;
	}
	if (!okay_error()) {
		rbell();
		return;
	}
	err_wind = windbp(perr_buf);
	buf_wind = windbp(ce->er_buf);

	if (!buf_wind || !err_wind) {
		if (err_wind)
			SetWind(err_wind);
		if (!buf_wind) {
			pop_wind(ce->er_buf->b_name, NO, NO);
			buf_wind = curwind;
		} else
			SetWind(buf_wind);
		if (!err_wind) {
			pop_wind(perr_buf->b_name, NO, NO);
			err_wind = curwind;
		}
	}
	/* Put the current error message at the top of its Window */
	SetWind(err_wind);
	SetLine(ce->er_mess);
	set_wsize(EWSize);
	SetTop(err_wind, (err_wind->w_line = ce->er_mess));
#ifdef COLOR
	err_wind->w_bufp->b_color = ErrColor;
#endif
#if (HIGHLIGHT)
	WHighLight(err_wind, ce->er_mess);
	WHighLight(buf_wind, ce->er_text);
	if (BO && True(HighLight))
		CentWind(err_wind);	/* I [TRH] think this is nicer. */
#endif
	/* now go to the the line with the error in the other window */
	SetWind(buf_wind);
	DotTo(ce->er_text, ce->er_char);
}

/* Get file name and line number from match; these can be in any order.
   You lose if you have the order <lineno> <filename> and filename is a
   valid number, but that doesn't happen all too often I hope.
   Returns the line number, and deposits file name in `buf' */

int
get_FL_info(buf)
register char	buf[FILESIZE];
{
	char	tmp[FILESIZE];
	register int	lnum;

	/* first assume <file> <lineno>; if this fails we have filename
	   already in the right buffer, so we use a temp buffer to store
	   match for lineno. */
	putmatch(2, buf, FILESIZE);
	if ((lnum = chr_to_int(buf, 10, YES)) < 0)
		buf = tmp;

	putmatch(1, buf, FILESIZE);
	if (lnum <= 0 && (lnum = chr_to_int(buf, 10, YES)) > 0)
		buf[0] = '\0';
		/* Invalidate this as a file name in case `buf' still
		   points to the filename buffer. (so that the caller
		   knows it should use a default filename.) */

	return lnum;
}

/* Parse for {C,LINT} errors (or anything that matches ErrFmt) in the
   current buffer.  Set up for the current-error command.  This is neat
   because this will work for any kind of output that prints a file
   name and a line number on the same line.
   Use default format string if no numeric argument is present,
   otherwise ask for a format. */

#ifdef IPARSE
private char	*err_fmt ZERO;		/* Actual error format string. */
private void	ParseMore __(( void ));
#endif

DEF_CMD( "parse-errors",	 ParseAll, NO )
{
	/* [TRH Jul-90] we can use compbuf since LookingAt now uses a local
	   RE buffer. (LookingAt may be called by do_find via DoAutoExec) */

	if (exp_p)
#ifdef IPARSE
		set_str(&err_fmt,
#endif
			re_ask(ErrFmt, YES, "With error format: ")
#ifdef IPARSE
			)
#endif
			;
	else
#ifdef IPARSE
		if (err_fmt) {
			free(err_fmt);
			err_fmt = NULL;
		}
#else
		REcompile(ErrFmt, YES, compbuf);
#endif

	ErrFree();
	set_errbuf(curbuf);
#ifdef IPARSE
	err_more = ParseMore;	/* Setup for incremental error parse. */
	ShowErr();		/* Invokes incremental error parse. */
}

private void
ParseMore()
{
	Buffer	*orgbuf = curbuf;

	if (perr_buf == NULL)
		return;

	REcompile(err_fmt ? err_fmt : ErrFmt, YES, compbuf);

	SetBuf(perr_buf);
#endif /* IPARSE */
	/* {This is still a part of ParseAll() if not IPARSE.} */
    {
	char		fnamebuf[FILESIZE];
	register struct error
			*e = NULL;
	register Buffer *b = NULL;
	register int	num;
	register Bufpos	*bp = NULL;

	ToFirst();
#ifdef IPARSE
	if (e = cur_error) {
		/*
		 * Start parsing from where we left off.
		 * {NOTE: assumes cur_error points to last error entry.}
		 */
		b = e->er_buf;
		DotTo(e->er_mess, length(e->er_mess));
	}
#endif
	/* Find a line with a number on it. */
	while (bp = docompiled(FORWARD, compbuf, bp, (Line *) 0)) {
		register const char	*fname = fnamebuf;

		num = get_FL_info((char *)fname);

		/* (optional match to pinpoint error more precisely) */
		putmatch(3, genbuf, LBSIZE);

		/* [TRH 07-Dec-91] implicit filename for error formats
		   without <filename> in each error message. */

		if (fnamebuf[0] == '\0') {
			if (b == NULL)		/* No filename found yet. */
				continue;
			fname = b->b_fname;
		}
		b = do_find((Window *) 0, fname, num);

		if (num <= 0)
			continue;

		/* [15-Jan-91] `do_find' now positions to the right line... */
#	define	errline	(b->b_dot)

		/* one error per line is nicer */
		if (e && e->er_text == errline)
			continue;

		num = 0;
		if (genbuf[0] && (num = sindex(genbuf, lcontents(errline))))
			num--;

		e = AddError(e, bp->p_line, b, errline, num);
#	undef errline
	}
    }
#ifdef IPARSE
	SetBuf(orgbuf);
#else
	ShowErr();
#endif
}

/* NextError sets cur_error to the next error, taking the
   argument count, supplied by the user, into consideration.
   Go the the next error, if there is one.  Put the error buffer in
   one window and the buffer with the error in another window.
   It checks to make sure that the error actually exists. */

DEF_CMD( "previous-error",	NextError, NEGATE );
DEF_CMD( "next-error",		NextError, NO )
{
	register int		num = exp;
	register struct error	*e;
	register char 		*bad = NULL;

	if ((e = cur_error) == NULL)
#ifdef IPARSE
		/* See if any new errors have arrived. If there are,
		   adjust `num' so we get the FIRST error as the `next' one. */
		if (err_more && ((*err_more)(), e = cur_error)) {
			if (num > 0)
				--num;
		}
		else
#endif
			complain(NOERRS);
	do {
		if	(num < 0) {		/* previous error */
			bad = "first";
			do {
				if ((e = e->er_prev) == NULL) {
					break;
				}
				cur_error = e;
				bad = NULL;
			} while (++num);
			--num;	/* num = -1; */
		}
		else if (num > 0) {		/* next error */
			bad = "last";
			do {
				if ((e = e->er_next) == NULL) {
#ifdef IPARSE
					if (!(err_more &&
					      ((*err_more)(),
					       e = cur_error->er_next)))
#endif
					break;
				}
				cur_error = e;
				bad = NULL;
			} while (--num);
			++num;	/* num = 1; */
		}
		else /* (num == 0) */ {		/* current error */
			++num;	/* num = 1; */
		}
		if (bad)
			complain("You're at the %s error.", bad);
	} while (!okay_error());
	ShowErr();
}

#ifndef NOPROCS

char	*ShcomBuf ZERO;

/* Make a buffer name given the command `command', i.e. "fgrep -n foo *.c"
   will return the buffer name "[fgrep]".
   NOTE: the result is passed in Minibuf, so be careful. */

char *
MakeName(command)
register const char	*command;
{
	register char	*cp = genbuf;
	register char	c;

	while (c = *command++) {
		if (isspace(c))
			if (cp != genbuf)
				break;
			else
				continue;
		*cp++ = c;
	}
	*cp = '\0';
	cp = Minibuf;
	sprintf(cp, "[%s]", basename(genbuf));

	return cp;
}

/* I-process parsing. */

#define RunAndParse(bname, command, parser, prompt) \
{									\
	MAYBE_IPARSE_ELSE(bname, command, parser, prompt) {		\
		ShellToBuf(bname, YES, EWSize, YES, (char*)0, command);	\
		parser();						\
	}								\
}

#ifndef IPARSE
#   define MAYBE_IPARSE_ELSE(bname, command, parser, prompt)
#else
#   define MAYBE_IPARSE_ELSE(bname, command, parser, prompt) \
	if (True(IParse)) {						\
		static Command _parser = { FUNCTION, prompt, parser };	\
		IprocWTerm(bname, command, (data_obj *) &_parser);	\
	} else

DEF_INT( "i-process-parse", IParse, V_BOOL ) ZERO; _IF(def IPARSE)_IF(def PRIVATE)

#endif

/* Run make, first writing all the modified buffers (if the WtOnMk flag is
   non-zero), parse the errors, and go the first error. */

DEF_STR( "compile-command", make_cmd, 128, V_STRING) _IF(ndef NOPROCS)_IF(def PRIVATE) =
	"make -k";

DEF_CMD( "compile-it", MakeErrors, NO ) _IF(ndef NOPROCS)
{
	register Window	*old = curwind;
	register char	*cmd = make_cmd;

	if (True(WtOnMk))
		put_bufs(NO, NO);
	/* When we're not doing make or cc (i.e., the last command
	   was probably a grep or something) and the user just types
	   C-X C-E, he probably (possibly, hopefully, usually (in my
	   case)) doesn't want to do the grep again but rather wants
	   to do a make again; so we ring the bell and insert the
	   default command and let the person decide. */

	if (exp_p ||
	    (!sindex("make", cmd) && !sindex("cc", cmd) && (rbell(), TRUE))) {
		Inputp = cmd;	/* insert the default for the user */
		exp_p = NO;
		cmd = set_str(&make_cmd, ask(cmd, "Compilation command: "));
	}
#ifdef IPARSE
	if (True(IParse)) {
		IprocWTerm(MakeName(cmd), cmd, NULL);
	}
	else
#endif
		ShellToBuf(MakeName(cmd), YES, EWSize, YES, (char*)0, cmd);
	ParseAll();
	if (!cur_error)
		SetWind(old);
}
#endif /* NOPROCS */

#ifdef SPELL

private void SpelParse __(( const char *_(bname) ));
private void
SpelParse(bname)
const char	*bname;
{
	register Buffer *buftospel,
			*wordsb;
	register Bufpos *bp;
	register struct error	*ep = NULL;

	ErrFree();		/* This is important! */

	buftospel = curbuf;
	wordsb = buf_exists(bname);
	set_errbuf(wordsb);	/* This is important (buffer containing
				   error messages) */
	wordsb->b_dot = wordsb->b_first;
	f_mess("Finding misspelled words ... ");
	while (!lastp(wordsb, wordsb->b_dot)) {
		REcompile(sprint("\\<%s\\>", lcontents(wordsb->b_dot)), YES, compbuf);
		ToFirst();
		bp = NULL;
		while (bp = docompiled(FORWARD, compbuf, bp, NULL)) {
			ep = AddError(ep, wordsb->b_dot, buftospel,
					  bp->p_line, bp->p_char);
		}
		wordsb->b_dot = wordsb->b_dot->l_next;
	}
	add_mess("Done.");
	ShowErr();
}

#ifndef NOPROCS

private	Buffer	*buftospell;
private void	DoSpelBuf __(( void ));

DEF_CMD( "spell-buffer", SpelBuffer, NO ) _IF(ndef NOPROCS)_IF(def SPELL)
{
	SaveFile();
	buftospell = curbuf;
	RunAndParse("Spell", sprint("spell %s", curbuf->b_fname),
		    DoSpelBuf, "Parse spelling errors");
#define SPELL_CMD_FILE(cmd)	((cmd) + 6)		/* kludge */
}

private void
DoSpelBuf()
{
	register Buffer	*wordsb = curbuf;

	if (b_empty(wordsb))
		return;

	message("[Delete the irrelevant words and then type C-X C-C]");
	Recur();
#ifdef IPARSE
    {
	register Process *p;

	if (p = wordsb->b_process)
		buftospell = do_find((Window *)0, SPELL_CMD_FILE(p->p_name), NO);
    }
#endif
	SetBuf(buftospell);
	SpelParse(wordsb->b_name);
}

#endif /* NOPROCS */

DEF_CMD( "parse-spelling-errors-in-buffer", SpelWords, NO ) _IF(def SPELL)
{
	register const char	*buftospel;
	register Buffer *wordsb = curbuf;

	if ((buftospel = ask_buf((Buffer *) 0)) == NULL)
		return;
	SetBuf(do_select(curwind, buftospel));
	SpelParse(wordsb->b_name);
}

#endif /* SPELL */

#ifndef NOPROCS
/* Run the shell command into `bufname'.  Empty the buffer except when we
   give a numeric argument, in which case it inserts the output at the
   current position in the buffer.  If argument is zero, ignore the output
   altogether.  If only a sign is entered (like ESC - ), the current buffer
   is used for output.
#ifdef PROC_TYPEOUT
   If the argument is negative, the output is displayed using Typeout.
#endif
 */
private void DoShell __(( const char *_(bufname), const char *_(cmd) ));
private void
DoShell(bufname, cmd)
const char	*bufname;
const char	*cmd;
{
	register int	disp,
			clobber = NO;
	register Window *savewp = curwind;

	if ((disp = exp) == 0)
		bufname = DevNull;	/* don't insert anything */
	else if (exp_p == YES_NODIGIT)
		disp = 0;		/* insert at point */
	else if (disp == 1)
		clobber++;

	ShellToBuf(bufname, disp, 0, clobber, (char *)0, cmd);
	SetWind(savewp);
}

DEF_CMD( "shell-command-to-buffer", ShToBuf, NO ) _IF(ndef NOPROCS)
{
	char		buf[BNAMESIZE];
	register char	*bufname = buf;

	strcpy(bufname, ask_buf((Buffer *) 0));
	DoShell(bufname, ask(ShcomBuf, "%N: %f %s Command: ", bufname));
}

DEF_CMD( "shell-command", ShellCom, NO ) _IF(ndef NOPROCS)
{
	register char		*cmd;
	register const char	*prompt = ProcFmt;
#ifndef TINY
	if (exp == 0)
		prompt = ": %f-no-buffer ";
	else if (exp_p == YES_NODIGIT)
		prompt = ": %f-at-point ";
	else if (exp < 0)
		prompt = ": %f-with-typeout ";
#endif
	cmd = set_str(&ShcomBuf, ask(ShcomBuf, prompt));
	DoShell(MakeName(cmd), cmd);
}

#if unix

int
dowait(pid)
{
	WAIT_T		w;
	register int	rpid;

	while ((rpid = wait2(&w, 0)) != pid) {
#ifndef TINY
		if (rpid < 0 && errno != EINTR)
			return -errno;
#endif
#ifdef IPROCS
		kill_off(rpid, w);
#endif
	}
	return WEXITSTATUS(w);
}
#endif /* unix */

/* Run the command to bufname, erase the buffer if clobber is non-zero,
   and redisplay if disp is non-zero.  Leaves current buffer in `bufname'
   and leaves any windows it creates lying around.  It's up to the caller
   to fix everything up after we're done.  (Usually there's nothing to
   fix up.)  Note that buffer is selected ONLY if disp is also set.
   (I guess that's a bug, but I can live with it)

   Display scheme has been changed, as follows:
    o disp > 0	- insert output in `bufname';
    o disp = 0	- if bufname = DevNull, ignore output,
		  else insert output at point in current buffer;
#ifdef PROC_TYPEOUT
    o disp < 0	- Typeout output.
#endif
 */
#ifdef PROC_TYPEOUT
private void	(*mfun)__(( const char *_(fmt), ... ));
#endif

int
ShellToBuf(bufname, disp, wsize, clobber, infile, cmd)
const char		*bufname,
			*infile;
register const char	*cmd;
{
	register int	status;

	SyncRec();		/* in case system hangs in program exec. */
#ifdef PROC_TYPEOUT
	mfun = s_mess;		/* eventually set to Typeout in read_pipe */
#	define s_mess   (*mfun)
#   endif
	status = UnixToBuf(bufname, disp, wsize, clobber, infile,
			   Shell, ShFlags, cmd, (char *) 0);
	s_mess((status == EXIT_SUCCESS) ? "[%s: completed successfully]" :
		   (status == -ENOEXEC) ? "[%s: exec failed!]" :
					  "[%s: exited (%d)]", cmd, status);
#ifdef PROC_TYPEOUT
#	undef s_mess
	/* {dumb cast for dumb compilers (AIX!!! IBM, which figures }-> */
	if ((void_*) mfun == (void_*) Typeout) {
		mfun = NULL;
		TOstop();
	}
#endif
	return status;
}

#if unix
/* VARARGS4 */

int
DEFVARG(UnixToBuf, (const char *bufname, int disp, int wsize,
		    int clobber, const char *infile, ...),
		   (bufname, disp, wsize, clobber, infile, va_alist)
	const char	*bufname;
	int		disp;
	int		clobber;
	const char	*infile;)
{
	int		p[2];
	register int	c, d;
	register int	pid = 0;
	sig_tp		(*old_int)__(( int _(sig) ));
	int		old_state;

	if (c = clobber)
		isprocbuf(bufname);
#	define clobber c

	/* Now I will attempt to describe how I deal with signals during
	   the execution of the shell command.	My desire was to be able
	   to interrupt the shell command AS SOON AS the window pops up.
	   So, if we have JOB_CONTROL (i.e., the new signal mechanism) I
	   hold SIGINT, meaning if we interrupt now, we will eventually
	   see the interrupt, but not before we are ready for it.  We
	   fork, the child releases the interrupt, it then sees the
	   interrupt, and so exits.  Meanwhile the parent ignores the
	   signal, so if there was a pending one, it's now lost.

	   With no JOB_CONTROL, the best behavior you can expect is, when
	   you type ^] too very quickly after the window pops up, it may
	   be ignored.	The behavior BEFORE was that it would interrupt
	   JOVE and then you would have to continue JOVE and wait a
	   little while longer before trying again.  Now that is fixed,
	   in that you just have to type it twice. */

#ifdef HOLD_SIGS
#   ifdef IPROCS
#	ifndef PIPEPROCS
	sighold(SIGCHLD);
#	endif
#   endif
	sighold(SIGINT);
#else
	old_int = signal(SIGINT, SIG_IGN);
	old_state = ttsigset(OFF);
#endif

	pid = 0;

	CATCH	/* So that we can restore signal status on errors.
		   If an error occurs, pid has value <= 0 after the catch. */

	message("Starting up...");

#ifdef PROC_TYPEOUT
	if ((d = disp) > 0)
#else
	if ((d = disp))
#endif
#	define disp	d
	{
		pop_wind(bufname, clobber, clobber ? B_PROCESS : B_FILE),
		set_wsize(wsize);
		redisplay();
	}

	pipeopen(p);

	if ((pid = vfork()) <= 0) {
		va_list 	ap;
		char		*argv[32];

		if (pid < 0) {
			pipeclose(p);
			complain("[Fork failed]");
		}

		va_begin(ap, infile);
		make_argv(argv, ap);
		va_end(ap);

		signal(SIGINT, SIG_DFL);
#ifdef HOLD_SIGS
#   ifdef BSD_SIGS
		/* we can't use sigrelse here since that would change JOVEs
		   copy of SigMask (because we're in a vfork). [TRH]
		   {although it does not hurt because the first thing we'll
		   do with them in JOVE is release them.} */

		sigsetmask(SigMask & ~(sigmask(SIGCHLD)|sigmask(SIGINT)));
#   else
#	ifdef IPROCS
#	    ifndef PIPEPROCS
		sigrelse(SIGCHLD);   /* don't know if this matters */
#	    endif
#	endif
		sigrelse(SIGINT);
#   endif
#endif /* BSD_SIGS */
		close(0);
		if (infile == NULL || open(infile, O_RDONLY) != 0)
			open(DevNull, O_RDONLY);
		close(1);
		if (bufname == DevNull)		/* ignore output */
			open(bufname, O_WRONLY);
		else
			dup(p[WRITE]);
		dup2(1, 2);
		pipeclose(p);
		execv(argv[0], &argv[1]);
		exec_fail((char *)0);
	}

	ENDCATCH

#ifdef HOLD_SIGS /* was JOB_CONTROL */
	old_int = signal(SIGINT, SIG_IGN);
#endif
	if (pid > 0) {
		close(p[WRITE]);
		read_pipe(bufname, p[READ], disp);
#		define status d
		status = dowait(pid);
	}
#ifdef HOLD_SIGS /* was JOB_CONTROL */
	sigrelse(SIGINT);
#   ifdef IPROCS
#	ifndef PIPEPROCS
	sigrelse(SIGCHLD);
#	endif
#   endif
#else
	ttsigset(old_state);
#endif
	signal(SIGINT, old_int);

	if (pid <= 0)
		complain((char *)0);	/* propagate the error */

	return status;
#undef status
#undef clobber
#undef disp
}
#endif /* unix */

#ifdef PROC_WRAP

/* {{Process line wrap kludge to keep Jonathan happy; it would be infinitely
     better (and ditto more complex) to solve this in the display engine.}} */

DEF_INT( "wrap-process-lines", WrapProcLines, V_BOOL ) _IF(def PROC_WRAP)_IF(def PRIVATE) ZERO;

/* chop line in parts of at most the screen width if "wrap-process-lines"
   is on, else pass line as is.  First call with non-NULL `line' to load
   the line; subsequently calls have `line' == NULL until line is exhausted,
   i.e., until chop() returns NULL.  `start' is the start column;
   start = -1 means calculate position of point and use that as start
   position. */

char *
chop(line, start)
char *line;
{
	if (True(WrapProcLines)) {
		static char	*savelp ZERO;
		static char	savechar;
		register char	*lp;
		register int	pos;
		register int	c;

		if ((lp = line) == NULL) {
			if ((line = lp = savelp) == NULL)
				return line;
			*lp = savechar;
		}
		savelp = NULL;

		if ((pos = start) < 0)
			pos = calc_pos(linebuf, curchar);

		while (c = *lp++) {
#   ifdef IPROCS
			if (c == '\n') {
				pos = 0;
				continue;
			}
#   endif
			if (UpdVisPos(pos, c) >= CO) {
				savechar = *--lp;
				savelp = lp;
				*lp = '\0';
				break;
			}
		}
	}
	return line;
}
#endif /* PROC_WRAP */

#ifndef TINY
DEF_INT( "shell-command-leaves-point-at-end", ShPointAtEnd, V_BOOL ) _IF(ndef TINY)_IF(def PRIVATE) ZERO;
#endif

/* read pipe input into current buffer. Allow redisplay while reading.
   Handles all nasties of interrupted read. */

void
read_pipe(name, fd, disp)
const char	*name;
{
	static const char Chugging[] = "Chugging along...";
	register File	*fp;
	int		eof;
	register int	try = 0;
	int		save_freq;
	Bufpos		savedot;
	int		saveIOread = inIOread;
#if F_BINARY
#   define open_mode (True(BinRdFile) ? F_READ|F_BINARY : F_READ)
#else
#   define open_mode F_READ|F_TEXT
#endif

#ifdef DOS
#   define UPD_RATE	1
#endif
#ifndef UPD_RATE
#   define UPD_RATE	4
#endif
	if ((save_freq = UpdFreq) > UPD_RATE)	/* do it more frequently */
#ifdef PROC_TYPEOUT
	    if (disp >= 0)
#endif
		alarm(UpdFreq = UPD_RATE);
	if (disp > 0)
		inIOread++;

	fp = fd_open(name, open_mode, fd, iobuff, BLKSIZ);
	/* iobuff can be used here since we SyncRec() only within getch() */

	DOTsave(&savedot);

	for (;;) {

		UpdModLine = YES;	/* to communicate with alarm handler */

		eof = f_gets(fp, genbuf, LBSIZE);
		eof |= f_eof(fp);	/* in case of partial line */
#ifdef PROC_WRAP
	    {
	      char	*line = chop(genbuf,
#   ifdef PROC_TYPEOUT
				     (disp < 0 && False(UseBuffers)) ? i_col :
#   endif
				     -1);
	      do {
#else
#   define	line	genbuf
#endif
#ifdef PROC_TYPEOUT
		if (disp >= 0) {
#endif
			ins_str(line, -YES);
			if (
#ifdef PROC_WRAP
			    (line = chop((char *)0, 0)) ||
#endif
			    !eof)
				LineInsert(1);
#if !(unix || vms)
			/* for systems without a real SIGALRM signal handling,
			   this presumably checks for and handles the "alarm" */
			inIOread = saveIOread;
			charp();
			if (disp > 0)
				inIOread++;
#endif
			if (UpdModLine != YES) {
				/* if this has changed, the alarm handler or
				   f_gets() reaching EOF are the only ones
				   that could have done it, so we know it's
				   time to show we're still alive.
				   (but not if invoked from alarm handler.) */
#ifdef LOAD_AV
				register const char	*fmt = Chugging;
				int			theavg = get_la();

				if (theavg < 2*100)
					fmt = "Screaming along.";
				else if (5*100 < theavg)
					fmt = "Crawling along..";
				message(fmt);
#else
				message(Chugging);
#endif /* LOAD_AV */
				/* add dots to show we're still alive */
			    {
				register char	*cp = &mesgbuf[16],
						*ep = &cp[try++ & 63];

				do *cp++ = '.'; while (cp <= ep);
				*cp = '\0';
			    }
				DrawMesg(NO);
			}
#ifdef PROC_TYPEOUT
		}
		else {
			if ((void_*) mfun != (void_*) Typeout) {
				mfun = Typeout;
				TOstart(name, FALSE);
			}
			Typeout("%s", line);
			if (
#ifdef PROC_WRAP
			    (line = chop((char *)0, 0)) ||
#endif
			    !eof) {
				Typeout((char *)0);
				continue;
			}
		}
#endif
#ifdef PROC_WRAP
	      } while(line);
	    }
#else
#   undef	line
#endif
		if (!(fp->f_flags & (F_ERR|F_EOF)))
			continue;			/* long line */
#ifdef EINTR
		if ((fp->f_flags & F_ERR) && (errno == EINTR)) {
			fp->f_flags &= ~(F_ERR|F_EOF);
			continue;			/* interrupt; retry */
		}
#endif
		break;
	}

	UpdFreq = save_freq;
	inIOread = saveIOread;

	f_close(fp);
    {
	register Buffer	*cb = curbuf;
	if (cb->b_dot != savedot.p_line || cb->b_char != savedot.p_char) {
		set_mark();	/* make it a region. */
		SetDot(&savedot);
#ifndef TINY
		if (True(ShPointAtEnd))
			PtToMark();
#endif
	}
    }
#undef open_mode
}

#ifdef GET_PROC_LINE
/* Get first line of output from external command `process'. */

char *
get_proc_line(process)
const char	*process;
{
	register const char	*bufname = "*JUNK*";
	register Buffer		*cb = do_select((Window *)0, bufname);
	register char		*save = Minibuf;

	SetBuf(cb);
	cb->b_type = B_PROCESS;
	cb->b_modified++;	/* frutz... */
	/* Save message buffer since UnixToBuf() munges all over it. */
	strcpy(save, mesgbuf);	/* prutz... */
	UnixToBuf(bufname, NO, 0, NO, (char *)0, process, (char *)0);
	strcpy(mesgbuf, save);	/* klotz... */
	strcpy(save, linebuf);
	kill_buf(cb);		/* this 'll restore original curbuf */

	return save;
}
#endif /* GET_PROC_LINE */


/* Send the current region to a command as input,
   and replace it by the output from the command. */

DEF_CMD( "filter-region", FilterRegion, EDIT ) _IF(ndef NOPROCS)
{
	static char	*FComBuf ZERO;

	register Mark	*m = CurMark();
	register Buffer	*cb = curbuf;
	register File	*fp;
	Window		*save_wind = curwind;
#if unix
	static char	tmpfile[] = "/tmp/jfXXXXXX";
	register char	*tname = mktemp(tmpfile);
#else
	char		tmpfile[FILESIZE];
	register char	*tname = mktmpe(tmpfile, "jfXXXXXX");
#endif
	set_str(&FComBuf, ask(FComBuf, "%N: %f (through command) "));

	fp = open_file(tname, iobuff, F_WRITE|F_TEXT|F_COMPLAIN|F_QUIET);
	putreg(fp, m->m_line, m->m_char, cb->b_dot, cb->b_char, YES);
	close_file(fp);
	DelReg();
    CATCH
	ShellToBuf(cb->b_name, NO, 0, NO, tname, FComBuf);
    ONERROR
	Yank();	/* Restore the region on error */
    ENDCATCH
	unlink(tname);
	SetWind(save_wind);
}

void
isprocbuf(bufname)
const char	*bufname;
{
	register Buffer *bp;

	if ((bp = buf_exists(bufname)) &&
#ifdef IPROCS
	    bp->b_type != B_IPROCESS &&
#endif
	    bp->b_type != B_PROCESS)
		confirm("Over-write buffer %b? ", bp);
}

#endif /* NOPROCS */

/*======================================================================
 * $Log: proc.c,v $
 * Revision 14.32.0.12  1994/06/15  12:02:29  tom
 * (ErrFmt): avoid adjacent space and tab in character sets.
 *
 * Revision 14.32.0.10  1994/04/07  02:34:44  tom
 * (read_pipe): allow CRLF translation on input pipe.
 *
 * Revision 14.32.0.1  1993/07/07  12:12:56  tom
 * (F_TEXT): new option for f_open et al.
 *
 * Revision 14.32  1993/06/09  01:02:20  tom
 * (read_pipe): eliminate F_IGN_0 on fd_open(), use ins_str(..., -1) instead.
 *
 * Revision 14.31  1993/02/16  22:39:33  tom
 * remove implicit size limit in MakeName(); fix nested inIOread botch in
 * read_pipe(); get_proc_line() moved in from io.c; remove (void) casts;
 * lotsa random optimizations.
 *
 * Revision 14.30  1993/02/09  17:44:07  tom
 * cleanup whitespace; some random optimizations.
 *
 * Revision 14.29  1992/12/28  00:38:07  tom
 * implement incremental error parsing to make "i-process-parse" more
 * convenient.
 *
 * Revision 14.28  1992/10/24  01:24:21  tom
 * convert to "port{ansi,defs}.h" conventions; remove compile-time
 * initialization to "error-buffer-color" so it gets expected bgr. color.
 *
 * Revision 14.26  1992/08/26  23:56:57  tom
 * add RCS directives.
 *
 */
