/*
   exec_cmd.c
   a set of procedures to support executing commands (including the
   communication between two processes using pipes); used mainly to
   support UNIX filters

   A. Stochniol, Last revision 1.09.1994

*/

/*
 * Copyright 1991 - 1994,  Andrzej Stochniol, London, UK
 *
 * ASEDIT text editor, both binary and source (hereafter, Software) is
 * copyrighted by Andrzej Stochniol (hereafter, AS) and ownership remains
 * with AS.
 *
 * AS grants you (hereafter, Licensee) a license to use the Software
 * for academic, research and internal business purposes only, without a
 * fee.  Licensee may distribute the binary and source code (if released)
 * to third parties provided that the copyright notice and this statement
 * appears on all copies and that no charge is associated with such copies.
 *
 * Licensee may make derivative works.  However, if Licensee distributes
 * any derivative work based on or derived from the Software, then
 * Licensee will:
 * (1) notify AS regarding its distribution of the derivative work, and
 * (2) clearly notify users that such derivative work is a modified version
 *      and not the original ASEDIT distributed by AS.
 *
 * Any Licensee wishing to make commercial use of the Software should
 * contact AS to negotiate an appropriate license for such commercial use.
 * Commercial use includes:
 * (1) integration of all or part of the source code into a product for sale
 *     or license by or on behalf of Licensee to third parties, or
 * (2) distribution of the binary code or source code to third parties that
 *     need it to utilize a commercial product sold or licensed by or on
 *     behalf of Licensee.
 *
 * A. STOCHNIOL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS
 * SOFTWARE FOR ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR
 * IMPLIED WARRANTY.  IN NO EVENT SHALL A. STOCHNIOL BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * By using or copying this Software, Licensee agrees to abide by the
 * copyright law and all other applicable laws, and the terms of this
 * license.
 * AS shall have the right to terminate this license immediately by
 * written notice upon Licensee's breach of, or non-compliance with, any
 * of its terms.  Licensee may be held legally responsible for any
 * copyright infringement that is caused or encouraged by Licensee's
 * failure to abide by the terms of this license.
 *
 *
 *      Andrzej Stochniol       (A.Stochniol@ic.ac.uk)
 *      30 Hatch Road
 *      London SW16 4PN
 *      UK
 */


#include <Xm/Text.h>
#include <Xm/MessageB.h>
#include <Xm/SelectioB.h>
#include <X11/Intrinsic.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>              /* to define toupper function */

#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>


#ifdef SYSV
# ifndef __hpux
#  define vfork() fork()
# endif /* __hpux */
#endif  /* SYSV */

#include <Xm/Protocols.h>    /* needed because of XmAddWMProtocolCallback;
                                   available since X11R4 */


#include "asedit.h"





extern Widget toplevel;
extern XmStringCharSet charset;

#define PARTIAL_BUFSIZ 128

typedef struct partialBufStruct
{
        struct partialBufStruct *prev;          /* pointer to the previous partial buffer */
        int    len;
        char   txt[PARTIAL_BUFSIZ];
} partialBufStruct;


typedef struct
{
        int fd;                         /* -1 for closed pipe */
        Boolean collect;
        char *txt;
        long len;
        long pos;
	partialBufStruct *pbufLast;     /* last of chained partial buffers (follow the chain using pbufLast->prev)*/
	void    *ccs;                   /* back-pointer to the childCommStruct (needs casting) */
} commPipeStruct;

/* childOutDepot might be (defined as enum in asedit.h):
	TO_NONE
	TO_STDOUT
	TO_STDERR
	TO_CURRENT	-> with that the left and right make sense only
	TO_NEW
	TO_TEXT_DIALOG
*/

typedef struct
{
    pid_t		childpid;	/* child process id */
    aseditWindowStruct *win;		/* pointer to the parent edit structure */
    int			childOutDepot;	/* where we want the child's stdout to be shown */
    char	       *dlgTitle;	/* dialog title (valid only for TO_*_DIALOG case) */
    char               *dialogsTitle;   /* title for all other dialogs (work, message etc) */
    int                 noOutDialog;	/* type of dialog when no output was obtained */
    XmString        	noOutMsg;	/* message when no output was obtained (and noOutDialog declared) */
    XmTextPosition	left;		/* left position of the text to be replaced with the child's output */
    XmTextPosition	right;		/* right ... (respectively); make sense only for the TO_CURRENT case */
    commPipeStruct 	in;		/* input pipe structure */
    commPipeStruct 	out;		/* output pipe structure (output will be deposited according to childOutDepot) */
    commPipeStruct	err;		/* error pipe structure (error pipe output is always shown in a question dialog */
    XtIntervalId	intervalId;	/* interval Id for the timeout procedure */
    Widget		progress;	/* widget to show progress of the command */
    Boolean		abortedByUser;	/* a flag set by destroy CB */
} childCommStruct;


#ifdef _NO_PROTO
static void closePipe(p)
    int *p;
#else  /* ! _NO_PROTO */

static void closePipe(int *p)
#endif
{
    if(p[0] >= 0) close(p[0]);
    if(p[1] >= 0) close(p[1]);
}

#ifdef _NO_PROTO
static void closePipes( inPipe, outPipe, errPipe)
    int *inPipe;
    int *outPipe;
    int *errPipe;
#else  /* ! _NO_PROTO */

static void closePipes( int *inPipe, int *outPipe, int *errPipe)
#endif
{
    closePipe(inPipe);
    closePipe(outPipe);
    closePipe(errPipe);
}


#ifdef _NO_PROTO
static void acceptOrCancelCB (w, response, cbs)
    Widget 	w;
    int 	*response;
    XmAnyCallbackStruct *cbs;

#else  /* ! _NO_PROTO */

static void acceptOrCancelCB (Widget w, int *response, XmAnyCallbackStruct *cbs)
#endif
{
    if (cbs->reason == XmCR_OK)
	*response = 1;
    else if (cbs->reason == XmCR_CANCEL)
	*response = 0;
    else	/* called from the WM_DELETE_WINDOW protocol message sent by the wm */
	*response = 0;		/* assign cancel situation */

}   /* acceptOrCancelCB */


/* acceptChildOutput: stand-alone procedure to create modal dialog and ask
   the user question about proceeding by showing the error string
   Note that the instance name depends on the defaultButtonType
*/
#ifdef _NO_PROTO
Boolean acceptChildOutput(parent, errstr, defaultButtonType)
    Widget parent;
    char   *errstr;
    int    defaultButtonType;
#else  /* ! _NO_PROTO */

Boolean acceptChildOutput(Widget parent,  char *errstr, int defaultButtonType)
#endif
{
    Widget dlg, shell;
    Arg                 al[5];          /*  arg list            */
    register  int       ac = 0;         /*  arg count           */
    XmString  xmstr;                    /* work XmString */
    int       response = -1;
    XtAppContext app = XtWidgetToApplicationContext(parent);
    Atom WM_DELETE_WINDOW;

    if(errstr)		/* otherwise take the string from resources */
    {
    	xmstr = XmStringCreateLtoR (errstr, charset);
    	XtSetArg(al[ac], XmNmessageString, xmstr);	ac++;
    }
    /* do NOT do anything when the user presses the Close button in the system menu;
       what kind of answer would be that;
    */
    XtSetArg (al[ac], XmNdeleteResponse, XmDO_NOTHING);	ac++;
    /****XtSetArg(al[ac], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); ac++; ***/
    XtSetArg(al[ac], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); ac++;
    XtSetArg(al[ac], XmNdefaultButtonType, defaultButtonType);	       ac++;
    if(defaultButtonType == XmDIALOG_CANCEL_BUTTON)
    	dlg = XmCreateQuestionDialog (parent, "errorChild", al, ac);
    else
    	dlg = XmCreateQuestionDialog (parent, "messageChild", al, ac);


    XtAddCallback (dlg, XmNokCallback,
		 (XtCallbackProc)acceptOrCancelCB, (XtPointer)&response);
    XtAddCallback (dlg, XmNcancelCallback,
		 (XtCallbackProc)acceptOrCancelCB, (XtPointer)&response);

    XtUnmanageChild (XmMessageBoxGetChild (dlg, XmDIALOG_HELP_BUTTON));
    XtManageChild (dlg);

    /* take care for the situation when the window manager sends the
       WM_DELETE_WINDOW protocol message
    */
    shell = XtParent (dlg);
    WM_DELETE_WINDOW = XmInternAtom
        (XtDisplay (parent), "WM_DELETE_WINDOW", False);
    XmAddWMProtocolCallback (shell, WM_DELETE_WINDOW, 
		(XtCallbackProc)acceptOrCancelCB, (XtPointer)&response);


    /* loop until we get the response */
    while (response == -1)
    {
	XtAppProcessEvent (app, XtIMAll);
	XSync (XtDisplay (dlg), 0);
    }

    XtUnmanageChild (dlg);
    XSync (XtDisplay (dlg), 0);
    XmUpdateDisplay (dlg);

    XtDestroyWidget (dlg);

    if(response > 0) return True;
    else             return False;

}   /* acceptChildOutput */


#ifdef _NO_PROTO
static void freePartialBuffers(ps)
    commPipeStruct *ps;
#else  /* ! _NO_PROTO */

static void freePartialBuffers(commPipeStruct *ps)
#endif
{
    partialBufStruct *pbuf, *pbuf_prev;

    /* we don't need partial buffers any more; free the memory (if it
       wasn't freed yet)
    */

    pbuf=ps->pbufLast;
    while(pbuf != NULL)
    {
        pbuf_prev = pbuf->prev;
        XtFree((char *)pbuf);
        pbuf = pbuf_prev;
    }
    ps->pbufLast = (partialBufStruct *) NULL;

}   /* freePartialBuffers */


#ifdef _NO_PROTO
static void dialogCloseCB(dialog, client_data, call_data)
    Widget      dialog;
    XtPointer     client_data;
    XtPointer     call_data;
#else  /* ! _NO_PROTO */

static void dialogCloseCB(Widget dialog, XtPointer client_data, XtPointer call_data)
#endif
{
    /* called when the user presses the cancel button; we simply destroy
       the widget (that would cause the call to dialogDestroyCB);
       when the user dsmisses the dialog by pressing Close button in the
       window menu the progressDestroyCB is called directly;
    */
    XtDestroyWidget(dialog);
}


#ifdef _NO_PROTO
static void dialogDestroyCB(dialog, client_data, call_data)
    Widget      dialog;
    XtPointer     client_data;
    XtPointer     call_data;
#else  /* ! _NO_PROTO */

static void dialogDestroyCB(Widget dialog, XtPointer client_data, XtPointer call_data)
#endif
{
    /* called when the dialog is destroyed; see above */
    ;	/* do nothing here */
}

#ifdef _NO_PROTO
static void showNewTextDialog(win, txt, dlgTitle)
    aseditWindowStruct *win;
    char *txt;
    char *dlgTitle;
#else  /* ! _NO_PROTO */

static void showNewTextDialog(aseditWindowStruct *win, char *txt, char *dlgTitle)
#endif
{
    /* create new, simple text dialog and show the txt in it; make the dialog as small as possible */
    Widget kid[5];                 /* Children to unmanage */
    Arg al[18];                    /* Arg List */
    register int ac = 0;           /* Arg Count */
    Dimension rows=1, cols=1;
    long lines=1L, columns=1L, colsMax=1L;
    long i, len;
    int defColsMax=40, defRowsMax=12;	/* hardcoded in the free version (half of the standard window)*/
    Widget dialog, text;
    XmString      xmstr;        /* work XmString */
    int    tab_size = win->tabsize;
    

    XtSetArg(al[ac], XmNdeleteResponse, XmDESTROY); ac++;   /* for Close in control menu */
    XtSetArg(al[ac], XmNdefaultButtonType, XmDIALOG_CANCEL_BUTTON); ac++;
    if(dlgTitle)
    {
	xmstr =XmStringCreateLtoR(dlgTitle, charset);
	XtSetArg (al[ac], XmNdialogTitle, xmstr);   ac++;
    }

    if(xmUseVersion >= 1002)
    	dialog = XmCreateWarningDialog(win->menu_bar, "text_dialog", al, ac);
    else	/* use workaround for all versions before Motif 1.2 */
     	dialog = XmCreatePromptDialog(win->menu_bar, "text_dialog", al, ac);

    if(dlgTitle) XmStringFree(xmstr);        /* free memory allocated for XmString */

    if(dialog)          /* safety check */
    {
        /* unmanage the unneeded buttons and elements */
	i = 0;
	if(xmUseVersion >= 1002)
	{
            kid[i++] = XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON);
            kid[i++] = XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
	    kid[i++] = XmMessageBoxGetChild(dialog, XmDIALOG_MESSAGE_LABEL);
	    kid[i++] = XmMessageBoxGetChild(dialog, XmDIALOG_SYMBOL_LABEL);
	}
	else
	{
	    kid[i++] = XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON);
            kid[i++] = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
            kid[i++] = XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL);
            kid[i++] = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT);
	}

	
	XtUnmanageChildren (kid, i);

	/* find the number of lines and the maximum line width of the text to be displayed 
	   (it is used to minimize the size of the dialog; note that the algorithm is correct
	    only when there is no wrapping;)
	*/

	if(txt && (len=strlen(txt)) > 0)
	{
	     for(i=0; i<len; i++)
	     {
		if(txt[i] == '\n')
		{
		    lines++;
		    if(columns > colsMax) colsMax = columns;
		    columns = 1L;
		    if(i == len -1) lines--;	/* do not show the last line if
						   it only consist of line feed */
		}
		else
		{
		    if(txt[i] != '\t') columns++;
		    else columns = ((columns+tab_size-1)/tab_size)*tab_size + 1L;
		}
	    }
	    /* check the last line */
	    if(columns > colsMax) colsMax = columns;


	}

	if(colsMax > defColsMax) cols = defColsMax;
	else		     cols = colsMax;

	if(lines > defRowsMax)   rows = defRowsMax;
	else		     rows = lines;

	/* now add the scrolled text */
        ac = 0;
        XtSetArg (al[ac], XmNrows, rows);	   ac++;
	XtSetArg (al[ac], XmNcolumns, cols);	   ac++;
        XtSetArg (al[ac], XmNresizeWidth, False);  ac++;
        XtSetArg (al[ac], XmNresizeHeight, False);  ac++;
        XtSetArg (al[ac], XmNscrollVertical, True);  ac++;
	XtSetArg (al[ac], XmNeditable, False);		  ac++;
        XtSetArg (al[ac], XmNeditMode, XmMULTI_LINE_EDIT);  ac++;
        XtSetArg (al[ac], XmNbackground, text_read_only_background);	ac++;
	XtSetArg (al[ac], XmNvalue,      txt);				ac++;
	/* add the vertical scrolling for long output */
	if(lines > defRowsMax) 
	{
	    XtSetArg(al[ac], XmNscrollVertical, True);	ac++;
	}
	else
	{
	    XtSetArg(al[ac], XmNscrollVertical, False);	ac++;
	}

	/* Note that we set wordWrap and scrollHorizontal in the app defaults file */

	text = XmCreateScrolledText (dialog, "text", al, ac);

	XtManageChild(text);

        XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)dialogCloseCB, NULL);
        XtAddCallback(dialog, XmNdestroyCallback, (XtCallbackProc)dialogDestroyCB, NULL);
        XtManageChild(dialog);
    }

}   /* showNewTextDialog */

#ifdef _NO_PROTO
static void showNoOutDialog(parent, dlgType, title, msg)
    Widget parent;
    int dlgType;
    char *title;
    XmString msg;
#else  /* ! _NO_PROTO */

static void showNoOutDialog(Widget parent, int dlgType, char *title, XmString msg)
#endif
{
    register int  ac;           /* arg count                */
    Arg           al[7];        /* arg list                 */
    XmString      xmstr;        /* work XmString */
    Widget	  dialog;

    ac = 0;
    if(title)
    {
        xmstr =XmStringCreateLtoR(title, charset);
        XtSetArg(al[ac], XmNdialogTitle, xmstr);        ac++;
    }
    if(msg != (XmString) NULL) { XtSetArg(al[ac], XmNmessageString, msg);     ac++; }
    XtSetArg(al[ac], XmNdeleteResponse, XmDESTROY); ac++;   /* for Close in control menu */
    XtSetArg(al[ac], XmNdefaultButtonType, XmDIALOG_CANCEL_BUTTON); ac++;


    switch(dlgType)
    {
	case XmDIALOG_ERROR:
            dialog = XmCreateErrorDialog(parent, "noOutDialog", al, ac);
	    break;

	case XmDIALOG_INFORMATION:
            dialog = XmCreateInformationDialog(parent, "noOutDialog", al, ac);
            break;

	case XmDIALOG_MESSAGE:
            dialog = XmCreateMessageDialog(parent, "noOutDialog", al, ac);
            break;

	case XmDIALOG_WARNING:
            dialog = XmCreateWarningDialog(parent, "noOutDialog", al, ac);
            break;

        default:
            fprintf(stderr, "\nUnknown noOutDialog type!");
            break;
    }
    if(dialog)          /* safety check */
    {
        XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON));
        XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));

        XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)dialogCloseCB, NULL);
        XtAddCallback(dialog, XmNdestroyCallback, (XtCallbackProc)dialogDestroyCB, NULL);
        XtManageChild(dialog);
    }
    if(title) XtFree((char *)xmstr);        /* free memory allocated */

}   /* showNoOutDialog */


#ifdef _NO_PROTO
static void processChildOutput(ccs)
    childCommStruct *ccs;
#else  /* ! _NO_PROTO */

static void processChildOutput(childCommStruct *ccs)
#endif
{
    int status;
    Boolean abnormal_end = False, acceptOutput;
    aseditWindowStruct *win;
    Widget dialog;
    /* check if all pipes are closed; if so now is the time
       to deal with the child output; if not return and wait for the
       next time to be called
    */

    if(ccs->out.fd >=0 || ccs->err.fd >= 0 || ccs->in.fd >= 0) return;

    /* wait for the child to complete (reap the child); check the return status*/
    waitpid(ccs->childpid, &status, 0);

    /* if there is an error output, ask the user about proceeding; note
       that error might be related to abnormal termination of the child (see status)
       or just a message sent to stderr (like adding the final line feed); we
       distinguish between severity of errors by providing different default button
       for the user to decide what to do
    */


    /* If you get errors around WIFEXITED then read the note below:
       Note: that on some systems you might have to add "-D_POSIX_SOURCE" definition
       for the compiler to properly include the <sys/wait.h>;
       That was the case for IBM RS/6000 AIX 3.2.3 without xmkmf (but on
       AIX 3.2.5 all was OK).
    */
    if(WIFEXITED(status))
    {
	abnormal_end = False;  /* normal termination; exit status might be
					  obtained using WEXITSTATUS(status) */
	if(WEXITSTATUS(status) != 0) abnormal_end = True;	/* lets mark
							this as abnormal as well;
							(e.g. this is the case
							when command caled from
							inside a shell was not found!) */
    }
    else if (WIFSIGNALED(status)) abnormal_end = True;	/* the third possiblity
							   is that the child stopped */

    /* we also mark the abnormal termination if any of the pipes was closed
       with an error (for example as a result of kill command)
    */
    if(ccs->out.fd < -1 || ccs->err.fd < -1 ||  ccs->in.fd < -1) abnormal_end = True;


    win = ccs->win;		
    if(ccs->abortedByUser)	
    {
	acceptOutput = False;	/* no matter how the child has  finished (or was reported to finish)
				   do not process the output */
    }
    else
    {
	XFlush(XtDisplay(ccs->win->menu_bar));
	if(ccs->progress)
	{
	    /* get rid off the progress dialog */
	    XtDestroyWidget(ccs->progress);
	}
	else
	    XtRemoveTimeOut(ccs->intervalId);	/* remove the timer NOW */
        if(abnormal_end)
        {
	    /* if there was some output on stderr it would be used, if not
	       the string from resources will be used (because ccs->err.txt is NULL in such case)
            */
	    acceptOutput = acceptChildOutput(win->menu_bar, ccs->err.txt, XmDIALOG_CANCEL_BUTTON);
        }
        else if (ccs->err.len)
        {
	    acceptOutput = acceptChildOutput(win->menu_bar, ccs->err.txt, XmDIALOG_OK_BUTTON);
        }
        else acceptOutput = True;
    }

    if(acceptOutput)
    {
	/* do what is neccessary  ... */
	/* first check if the length of the output is zero and if the user
  	   specified noOutDialog; if so show this dialog; if not process
	   as usual
	*/
	if(ccs->out.len == 0 && (ccs->noOutDialog == XmDIALOG_ERROR ||
				 ccs->noOutDialog == XmDIALOG_INFORMATION ||
				 ccs->noOutDialog == XmDIALOG_MESSAGE ||
				 ccs->noOutDialog == XmDIALOG_WARNING) )
	{
	    showNoOutDialog(win->menu_bar, ccs->noOutDialog, ccs->dialogsTitle, ccs->noOutMsg);
	}
	else switch(ccs->childOutDepot)
	{
	    case TO_NONE:
		;		/* do nothing (discard output) */
		break;

	    case TO_STDOUT:
		if(ccs->out.txt) fprintf(stdout, ccs->out.txt);
		break;

	    case TO_STDERR:
		if(ccs->out.txt) fprintf(stderr, ccs->out.txt);
		break;

	    case TO_CURRENT:
		/* replace the original selection and be sure that the insertion
		   point is moved just after the inserted text
		*/
		disableRedisplay(win);      /* to prevent visual flashing */
		XmTextReplace(win->edit_text, ccs->left, ccs->right, ccs->out.txt);
		if(ccs->out.txt)	/* not NULL */
		{
    		    /* set the selection again */
		    /* note that XmTextSelection position changes the insertion to the right pos. */
    		    XmTextSetSelection(win->edit_text, ccs->left, ccs->left + strlen(ccs->out.txt), CurrentTime);

		    /** XmTextSetInsertionPosition(win->edit_text, ccs->left + strlen(ccs->out.txt)); not needed */
		}
		enableRedisplay(win);


		break;

	    case TO_NEW:
		next_window(asedit_last_window);	/* create next window */
		open_file_in_last_window(NULL);   /* open a new default file ...  */
		ccs->left = ccs->right = (XmTextPosition) 0;
		XmTextReplace(asedit_last_window->edit_text, ccs->left, ccs->right, ccs->out.txt);
		if(ccs->out.txt)	/* not NULL */
		    XmTextSetInsertionPosition(asedit_last_window->edit_text, ccs->left + strlen(ccs->out.txt));

		break;

	    case TO_TEXT_DIALOG:
		/* only show it if there was any output; otherwise show standard info */
		if(ccs->out.len > 0) showNewTextDialog(win, ccs->out.txt, ccs->dlgTitle);
		else 
		     showNoOutDialog(win->menu_bar, XmDIALOG_INFORMATION, ccs->dialogsTitle, ccs->noOutMsg);
		break;

	    default:
		fprintf(stderr,"\nUnknown depository for the child's output!");
		fprintf(stderr,"\nUsing parent's stderr! The output follows:");
		if(ccs->out.txt) fprintf(stderr, ccs->out.txt);
		break;


	}

    }

    /* now release all memory */

    freePartialBuffers(&ccs->in);
    freePartialBuffers(&ccs->out);
    freePartialBuffers(&ccs->err);

    if(ccs->in.txt)  XtFree(ccs->in.txt);
    if(ccs->out.txt) XtFree(ccs->out.txt);
    if(ccs->err.txt) XtFree(ccs->err.txt);

    /* free allocated memory */
    if(ccs->dlgTitle) XtFree(ccs->dlgTitle);
    if(ccs->dialogsTitle) XtFree(ccs->dialogsTitle);


    if(ccs->progress)	/* if we were extremely unlucky get rid of the progress dialog NOW */
            XtDestroyWidget(ccs->progress);


    XtFree((char *)ccs);

}   /* processChildOutput */


#ifdef _NO_PROTO
static void createTextFromPartialBuffers(ps)
    commPipeStruct *ps;
#else  /* ! _NO_PROTO */

static void createTextFromPartialBuffers(commPipeStruct *ps)
#endif
{
    long l, len = 0L;
    int  i;
    partialBufStruct *pbuf;

    /* find the how much data is stored in partial buffers and allocate
       memory for the whole lot
    */

    for (pbuf=ps->pbufLast; pbuf != NULL; pbuf=pbuf->prev)
		len += pbuf->len;

    if(len == 0L) { ps->len = 0; return; }       /* nothing stored, return immediately */

    ps->txt = XtMalloc(len+1);


    /* store the length, end the text with a null, then construct the text
       starting from the end */
    ps->len = l = len;
    ps->txt[l--] = '\0';

    for (pbuf=ps->pbufLast; pbuf != NULL; pbuf=pbuf->prev)
    {
	for(i = pbuf->len-1; i>=0; i--)
		ps->txt[l--] = pbuf->txt[i];
    }

    /* set the initial position of the text (it must be now 0) */
    ps->pos = l+1;

    /* we don't need partial buffers any more; free the memory */
    freePartialBuffers(ps);


}   /* createTextFromPartialBuffers */

#ifdef _NO_PROTO
static void childOutErrPipeCB(client_data, source, id)
    XtPointer 	client_data;
    int 	*source;
    XtInputId 	*id;
#else  /* ! _NO_PROTO */

static void childOutErrPipeCB(XtPointer client_data, int *source,  XtInputId *id)
#endif
{
    commPipeStruct  *cps = (commPipeStruct *) client_data;
    partialBufStruct *pbuf;
    int i, nbytes;

    pbuf = (partialBufStruct *) XtMalloc(sizeof(partialBufStruct));


    nbytes = read(*source, pbuf->txt, PARTIAL_BUFSIZ);

    if( nbytes == -1)
    {
	/* error */
	/* check for the following non-blocking and interrupt i/o */
	/* AIX returns EAGAIN where 4.3BSD used EWOULDBLOCK; 
	   check for systems that return either EAGAIN or EWOULDBLOCK.
	*/

	if (errno == EWOULDBLOCK || errno == EAGAIN)
	{
	    return;	/* do nothing */		
	}


    }

    if (nbytes == 0 || nbytes == -1)
    {
	/*  nbytes=0 means EOF on pipe */

	XtFree((char *)pbuf);
	XtRemoveInput(*id);
	close(*source);


	createTextFromPartialBuffers(cps);  /* put the partial buffers together */
        cps->fd = nbytes-1;                /* source was closed; we use -2 to
                                                find that it was closed with an error */

	/* check if the other pipes were closed already; if so now is the time
	   to deal with the child output
	*/
	processChildOutput(cps->ccs);

	return;
    }
    else
    {

	/*   process the input, i.e. store it in the partial buffer */
	pbuf->len = nbytes;
	pbuf->prev = cps->pbufLast;
	cps->pbufLast = pbuf;


    }


}   /* childOutErrPipeCB */

#ifdef _NO_PROTO
static void childInPipeCB(client_data, source,  id)
    XtPointer 	client_data;
    int 	*source;
    XtInputId 	*id;
#else  /* ! _NO_PROTO */

static void childInPipeCB(XtPointer client_data, int *source,  XtInputId *id)
#endif
{
    commPipeStruct  *cps = (commPipeStruct *) client_data;
    int i, nbytes;


    if(cps->pos < cps->len) nbytes = write(*source, cps->txt+cps->pos, cps->len - cps->pos);
    else		    nbytes = 0;		/* we've already written out everything */

    if( nbytes == -1)
    {
	/* error */
	/* check for the following non-blocking and interrupt i/o */
	/* AIX returns EAGAIN where 4.3BSD used EWOULDBLOCK;
	   check for systems that return either EAGAIN or EWOULDBLOCK.
	*/

	/******old
	if (errno != EWOULDBLOCK && errno != EAGAIN)
	{
	    |* really fatal ! *|
	    |* perror .... *|
	}
	******/
	if (errno == EWOULDBLOCK || errno == EAGAIN)
        {
            return;     /* do nothing */                
        }



    }

    if (nbytes == 0 || nbytes == -1)
    {
	/*  nbytes=0 means EOF on pipe */

	XtRemoveInput(*id);
	close(*source);

	cps->fd = nbytes-1;		   /* source was closed; we use -2 to
				  		find that it was closed with an error */

	/* check if the other pipes were closed already; if so now is the time
	   to deal with the child output
	*/
	processChildOutput(cps->ccs);

	return;
    }
    else
    {

	/* change the position in the parent output buffer */
	cps->pos += nbytes;



    }


}   /* childInPipeCB */





/* forkAnPipe is used to execute a process (command) and establish two way
   communications using pipes between the parent and the child processes
   Returns childpid if succesfull.
*/
#ifdef _NO_PROTO
static pid_t forkAndPipe(w, cmd, inPipe, outPipe, errPipe)
    Widget w;
    String cmd;
    int    *inPipe;
    int    *outPipe;
    int    *errPipe;
#else  /* ! _NO_PROTO */

static pid_t forkAndPipe(Widget w, String cmd, int *inPipe, int *outPipe, int *errPipe)
#endif
{
    /*  all *Pipe's should point to arrays of 2 !!! */
    pid_t childpid;

#ifdef __hpux
    int (*istat)(), (*qstat)();
#else
    void (*istat)(), (*qstat)();
#endif


    /* set the default values to a value < 0 to recognize if a file descriptor exists */
    inPipe[0] = inPipe[1] = -1;
    errPipe[0] = errPipe[1] = -1;
    outPipe[0] = outPipe[1] = -1;


    if (pipe(inPipe) < 0 || pipe(outPipe) < 0 || pipe(errPipe) < 0  )
    {
	closePipes(inPipe, outPipe, errPipe);
	return -2;    		/*we might want to treat that as fatal error */
    }


    if ( (childpid = fork()) < 0)		/** on some systems use vfork !!! ***/
    {
	closePipes(inPipe, outPipe, errPipe);
	return -1;	/* we might want to treat that as fatal error */
    }
    else if (childpid == 0)
    {
	/* first reset the handling of SIGINT, SIGQUIT and SIGHUP */
	signal(SIGINT, SIG_DFL);
	signal(SIGHUP, SIG_DFL);
#ifdef SIGQUIT          /* not all systems define that (HP-UX does not) */
	signal(SIGQUIT, SIG_DFL);
#endif


	/* child process */
	dup2(inPipe[0], fileno(stdin));		/* stdin */
	dup2(outPipe[1], fileno(stdout));	/* stdout */
	dup2(errPipe[1], fileno(stderr));	/* stderr */


	/* close unused pipe ends */
	close(inPipe[1]);			/* write end */
	close(outPipe[0]);			/* read end */
	close(errPipe[0]);

	(void) close(XConnectionNumber(XtDisplay(w))); /**** or simply close(XConnectionNumber(dpy)); ***/

	/* set the process group ID and session ID of the calling process to the process
	   ID of the calling process (in other words make it a process group leader);
	   this will allow us to kill this and all its
	   subprocesses with a single kill command (with negative pid value; on BSD
	   you can use killpg, but don't mix POSIX and BSD !)
	*/
	setsid();


	/* to run a program use:
		execlp("li", "li", "-al", 0);
	*/
	/* to run the shell to interpret the command use:
		execl("/usr/bin/sh", "sh", "-c", "li -l *.c", 0);
	*/
	execl("/bin/sh", "sh", "-c", cmd, 0);

	_exit(127);

    }
    else
    {
	/* parent */
	/* close unused pipe ends for the parent side */
	close(inPipe[0]);
	close(outPipe[1]);
	close(errPipe[1]);

	signal(SIGPIPE, SIG_IGN);	/* ignore pipe signals (we will catch the child
					   processed that died or was killed in our *PipeCB's*/

    }
    return childpid;

}   /* forkAndPipe */



#ifdef _NO_PROTO
static void set_nonblock(fd)
    int fd;
#else  /* ! _NO_PROTO */

static void set_nonblock(int fd)
#endif
{
    int val;
    if ( (val = fcntl(fd, F_GETFL, 0)) < 0)
	perror("asedit - set_nonblock: fcntl F_GETFL error");
    val |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, val) < 0)
	perror("asedit - set_nonblock: fcntl F_SETFL error");
}


#ifdef _NO_PROTO
void progressStopCB(dialog, client_data, call_data)
    Widget 	dialog;
    XtPointer 	client_data;
    XtPointer 	call_data;
#else  /* ! _NO_PROTO */

void progressStopCB(Widget dialog, XtPointer client_data, XtPointer call_data)
#endif
{
    /* called when the user presses the OK button; we simply destroy
       the widget (that would cause the call to progressDestroyCB);
       when the user dsmisses the dialog by pressing Close button in the
       window menu the progressDestroyCB is called directly;
    */
    XtDestroyWidget(dialog);
}


#ifdef _NO_PROTO
void progressDestroyCB(dialog, client_data, call_data)
    Widget 	dialog;
    XtPointer 	client_data;
    XtPointer 	call_data;
#else  /* ! _NO_PROTO */

void progressDestroyCB(Widget dialog, XtPointer client_data, XtPointer call_data)
#endif
{
    /* called when the dialog is destroyed; see above */
    childCommStruct *ccs = (childCommStruct *)client_data;

    XtRemoveTimeOut(ccs->intervalId);

    kill(-ccs->childpid, SIGTERM);	/* kill the child process group;
				           we will collect the output and release
					   memory in other callbacks */
    ccs->abortedByUser = True;		/* set the flag so we won't ask the user
					   if proceed with partial results
					*/
    ccs->progress = NULL;

}


/* showCmdProgress - show progress of the command, restarts timer and
   saves timer id; first time creates the special dialog to show progress
   Note that if the output of the filter goes to the current window
   we create the dialog as a PRIMARY_APPLICATION_MODAL, so the user
   can't spoil anything in the editor.
*/

#ifdef _NO_PROTO
static void showCmdProgress(client_data, id)
    XtPointer 	 client_data;
    XtIntervalId *id;
#else  /* ! _NO_PROTO */

static void showCmdProgress(XtPointer client_data, XtIntervalId *id)
#endif
{
    childCommStruct *ccs = (childCommStruct *)client_data;
    XtAppContext app_context = XtWidgetToApplicationContext(ccs->win->menu_bar);


    if(ccs->progress == NULL)
    {
	/* first create the progress dialog and register a callback
	   to stop the command
	*/
	Arg                 al[5];          /*  arg list            */
	register  int       ac = 0;         /*  arg count           */

	XtSetArg(al[ac], XmNdeleteResponse, XmDESTROY);	ac++;	/* for Close in control menu */
        XtSetArg(al[ac], XmNdefaultButtonType, XmDIALOG_CANCEL_BUTTON); ac++;
	if(ccs->childOutDepot == TO_CURRENT)
	       { XtSetArg(al[ac], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); ac++; }

	ccs->progress = XmCreateWorkingDialog(ccs->win->menu_bar, "progress", al, ac);
	if(ccs->progress == NULL) return;	/* should not happen */

	XtUnmanageChild(XmMessageBoxGetChild(ccs->progress, XmDIALOG_OK_BUTTON));
	XtUnmanageChild(XmMessageBoxGetChild(ccs->progress, XmDIALOG_HELP_BUTTON));

	XtAddCallback(ccs->progress, XmNcancelCallback, (XtCallbackProc)progressStopCB, NULL);
	XtAddCallback(ccs->progress, XmNdestroyCallback, (XtCallbackProc)progressDestroyCB, (XtPointer)ccs);

	XtManageChild(ccs->progress);
	XFlush(XtDisplay(ccs->progress));

	/**** XtPopup(XtParent(ccs->progress), XtGrabNone); ****/

    }
    if(ccs->in.fd > 0)	
    {
	/* only attempt to show progress if the input pipe is opened */
        if(ccs->in.len > 0)
	{
#ifdef SHOW_PROGRESS_ON_INPUT_PIPE
	    /* well, for most applications the prg percentage is a very crude estimate; it
	       is mainly because the child standard input is usually buffered (4096 bytes typically);
	       so starting from version 1.3 we don't use that at all (as standard) ; we just show
	       the working dialog with a user suplied message;  
	    */
	    Arg          al[2];          /*  arg list            */
    	    register int ac = 0;         /*  arg count           */
	    int prg;
	    char work[80];
	    XmString xmstr;

	    prg = 100.0 * ccs->in.pos/(float)ccs->in.len;
	    sprintf(work, "Done in %d %s", prg, "%");
	    xmstr = XmStringCreateLtoR(work, charset);	

    	    XtSetArg(al[ac], XmNmessageString, xmstr);  ac++;
    	    XtSetValues(ccs->progress, al, ac);
    	    XmStringFree(xmstr);       /* free memory allocated for XmString */
#endif
	   ;			/* don't do anything in 1.3 here (unless you want) */
	}
    }

	
	

    ccs->intervalId = XtAppAddTimeOut(app_context, (unsigned long)lstr.progressInterval,
			   showCmdProgress, (XtPointer)ccs);


}   /* showCmdProgress */


#ifdef _NO_PROTO
Boolean executeShellCmd(win, cmd, cmdInput, left, right, childOutDepot,
			dlgTitle, dialogsTitle, noOutDialog, noOutMsg)
    aseditWindowStruct *win;
    char 	    *cmd;
    char 	    *cmdInput;
    XmTextPosition  left;
    XmTextPosition  right;
    int 	    childOutDepot;
    char 	    *dlgTitle;
    char 	    *dialogsTitle;
    int 	    noOutDialog;
    XmString 	    noOutMsg;
#else  /* ! _NO_PROTO */

Boolean executeShellCmd(aseditWindowStruct *win, char *cmd, char *cmdInput,
		     XmTextPosition left, XmTextPosition right,
		     int childOutDepot, char *dlgTitle,
		     char *dialogsTitle, int noOutDialog,
		     XmString noOutMsg)
#endif
{
    pid_t childpid;
    size_t len;
    int inPipe[2], outPipe[2], errPipe[2];
    childCommStruct *ccs = NULL;
    unsigned long    delay_interval;
    XtAppContext app_context = XtWidgetToApplicationContext(win->menu_bar);

    /* first call forkAndPipe ... */

    len = strlen(cmd);
    if(!len) return False;	/* empty command; shouldn't happen */


    if((childpid = forkAndPipe(win->menu_bar, cmd, inPipe, outPipe, errPipe)) < 0)
    {
	char work[256];
	/*
		To FINISH OFF !!!!
		strcpy(work, (char *)lstr.unable_to_fork);  
		show_error_message(win, work);
	*/
	/* error SHOW it to the user .... */

	fprintf(stderr, "\n asedit: ERROR in forkAndPipe (Can't create pipes or fork another process!)");
	return False;
    }
    else
    {
	/* fork was OK */

	/* allocate memory for the childCommStruct */

	ccs = (childCommStruct *) XtMalloc(sizeof(childCommStruct));

	/* store the basic parameters */
	ccs->childpid = childpid;
	ccs->win      = win;		/* the parent win structure */
	ccs->childOutDepot = childOutDepot;
	ccs->dlgTitle	  = dlgTitle;
	ccs->dialogsTitle = dialogsTitle;
	ccs->noOutDialog  = noOutDialog;
	ccs->noOutMsg	  = noOutMsg;
	ccs->left	  = left;
	ccs->right	  = right;


	/* set the back-pointers */
	ccs->in.ccs = (void *)ccs;
	ccs->out.ccs = (void *)ccs;
	ccs->err.ccs = (void *)ccs;

	/* set the pbufLast to NULL pointer in all pipes */
	ccs->in.pbufLast  = (partialBufStruct *) NULL;
	ccs->out.pbufLast = (partialBufStruct *) NULL;
	ccs->err.pbufLast = (partialBufStruct *) NULL;

	/* set the txt to NULL pointers in all pipes */
	ccs->in.txt  = (char *)NULL;
	ccs->out.txt = (char *)NULL;
	ccs->err.txt = (char *)NULL;


	/* set the file descriptors for the pipes (parent end) */
	ccs->out.fd = outPipe[0];
	ccs->err.fd = errPipe[0];
	ccs->in.fd  = inPipe[1];

	/* set non-blocking operations on all pipes (!! compulsory if we
	   don't want to block the whole application !!
	*/
        set_nonblock(ccs->in.fd);
        set_nonblock(ccs->out.fd);
        set_nonblock(ccs->err.fd);
	


	/* set the command input */
	ccs->in.txt = cmdInput;
        if(cmdInput != NULL)
	     ccs->in.len = strlen(cmdInput);
	else ccs->in.len = 0;
	ccs->in.pos = 0;	/* nothing has been written yet */

	ccs->abortedByUser = False;		/* set the abort flag */

	/* register functions to handle output from the child process */


	/* if you don't have app_context handy use: XtWidgetToApplicationContext(w) */
	XtAppAddInput(app_context,	outPipe[0], (XtPointer) XtInputReadMask,
			(XtInputCallbackProc)childOutErrPipeCB, (XtPointer)(&ccs->out));

	XtAppAddInput(app_context,	errPipe[0], (XtPointer) XtInputReadMask,
			(XtInputCallbackProc)childOutErrPipeCB, (XtPointer)(&ccs->err));

	if(ccs->in.len) /* register the callback for the input pipe */
		XtAppAddInput(app_context,	inPipe[1], (XtPointer) XtInputWriteMask,
			(XtInputCallbackProc)childInPipeCB, (XtPointer)(&ccs->in));
	else
	{
	    /* nothing to write; close the inPipe[1] and do NOT register any callback */
	    close(inPipe[1]);
	    ccs->in.fd = -1;	/* CLOSED */
	}

	ccs->progress = NULL;		/* the widget is not created yet */

	/* register a timeout procedure to display a working dialog if
	   the Shell command takes a while to complete
	*/
	/* if the output is to the current window show the dialog immediately
	   and make it PRIMARY MODAL
	*/
	if(ccs->childOutDepot == TO_CURRENT) delay_interval = (unsigned long)lstr.progressShortInitialDelay;
	else 				     delay_interval = (unsigned long)lstr.progressInitialDelay;
	ccs->intervalId = XtAppAddTimeOut(app_context, delay_interval,
			   showCmdProgress, (XtPointer)ccs);



    }
    return True;

}   /* executeShellCmd */

#ifdef _NO_PROTO
static int stringToValueCvt(s, stable, values, table_size, default_string, default_value)
    char *s;
    char *stable[];
    int values[];
    int table_size;
    char *default_string;
    int default_value;
#else  /* ! _NO_PROTO */

static int stringToValueCvt(char *s, char *stable[], int values[], int table_size,
	char *default_string, int default_value)
#endif
{
    /* returns a value from values[i] when s equals stable[i] (case insensitive);
       stable MUST be specified in upper case;
       both stable and values must be of table_size size;
       if s is not equal any of the entries in stable returns default_value;
       Note: this procedure changes s to uppercase!
    */

    int value = default_value;	/* when s is NULL or a value was not found */

    if(s != NULL)
    {
	int i, n, len, len2;

	len = strlen(s);
	if(len)		/* do not process empty strings */
	{
	    /* change all to upper case*/
	    for(i=0; i< len; i++) s[i] = toupper((unsigned char)s[i]);

	    /* get the value from the values table when strings are the same */
	    for(i=0; i< table_size; i++)
	    {
		len2 = strlen(stable[i]);		/* only compare if the string is long enough */
		if(len >= len2 && strncmp(s, stable[i], len2)  == 0)
		{
		    value = values[i];
		    break;
		}
	    }

	    if(i == table_size)		/* string was not found; show error */
	    {
		fprintf(stderr, "\nError in stringToValueCvt: failed to convert '%s' (in uppercase) to a value", s);
		fprintf(stderr, "\nUsing the default value for %s", default_string);
	    }
	}
    }

    return value;

}

static char *depotNames[] = { "TO_NONE", "TO_STDOUT", "TO_STDERR", "TO_CURRENT",
				 "TO_NEW", "TO_TEXT_DIALOG"};
static int  depotValues[] = { TO_NONE, TO_STDOUT, TO_STDERR, TO_CURRENT,
				TO_NEW, TO_TEXT_DIALOG};

static char *dialogNames[] = { "DIALOG_ERROR", 	"DIALOG_INFORMATION",
				"DIALOG_MESSAGE", 	"DIALOG_WARNING"
			     };
static int   dialogType[] =  { 	XmDIALOG_ERROR, 	XmDIALOG_INFORMATION,
				XmDIALOG_MESSAGE,	XmDIALOG_WARNING
			     };

enum extendSelectionValue    { 	NO_EXTEND, 		EXTEND_TO_LEFT_LF,
				EXTEND_TO_RIGHT_LF,	EXTEND_TO_LINES,
				EXTEND_TO_WHOLE_FILE, 	USE_WHOLE_FILE,
				USE_FILE_OR_SELECTION
			     };

static char *extendNames[] = {  "NO_EXTEND", 		"EXTEND_TO_LEFT_LF",
				"EXTEND_TO_RIGHT_LF", 	"EXTEND_TO_LINES",
				"EXTEND_TO_WHOLE_FILE",	"USE_WHOLE_FILE",
				"USE_FILE_OR_SELECTION"
			     };
static int   extendValues[]= {	NO_EXTEND,		EXTEND_TO_LEFT_LF,
				EXTEND_TO_RIGHT_LF,	EXTEND_TO_LINES,
				EXTEND_TO_WHOLE_FILE, 	USE_WHOLE_FILE,
				USE_FILE_OR_SELECTION
			     };

#ifdef _NO_PROTO
void decodeFilterExtensions(filterExt, needSelection, childOutDepot,
	outDialogTitle,	dialogsTitle, noOutDialog, noOutMsg, extendTo)
    XmStringTable      filterExt;
    Boolean	needSelection;
    int 	*childOutDepot;
    char 	**outDialogTitle;
    char 	**dialogsTitle;
    int 	*noOutDialog;
    XmString 	*noOutMsg;
    int 	*extendTo;
#else  /* ! _NO_PROTO */

void decodeFilterExtensions(XmStringTable filterExt, Boolean needSelection,
	int *childOutDepot, char **outDialogTitle, char **dialogsTitle,
	int *noOutDialog, XmString *noOutMsg, int *extendTo)
#endif
{
    char *txtPar1=NULL, *txtPar2=NULL;
    char *txtPar3=NULL;		/* {NO}EXTEND_TO_LF */
    Boolean	   stringTableEOF = False;
    Boolean	   extendToLF = True;		/* as default we extend the selection to the right line feed */

    /* set the default values first */

    *outDialogTitle = *dialogsTitle= NULL;
    *noOutMsg = NULL;
    *childOutDepot=TO_CURRENT;	/* default value */
    *noOutDialog  = TO_NONE;		/* = 0 */

    /* process (decode) the current table (specified as a resource)*/

#define getString_n_checkEOF(nr, str)	if(!stringTableEOF)	\
    { 								\
	XmStringGetLtoR(filterExt[nr], charset, &str);	\
	if(!str) stringTableEOF = True; 			\
    }

#define getXmString_n_checkEOF(nr, str)	if(!stringTableEOF)	\
    { 								\
	if(filterExt[nr]) str = filterExt[nr];	\
	else stringTableEOF = True; 			\
    }


    /* the structure of filterExt is: childOutDepot (int obtained from text comparison),
       outDialogTitle (text - to safeguard us from multi-line xm strings),
       dialogsTitle (text - to safeguard us from multi-line xm strings),
       noOutDialog (int obtained from text comparison), noOutMsg (xmstr)
    */
    getString_n_checkEOF(0,txtPar1);
    getString_n_checkEOF(1,*outDialogTitle);
    getString_n_checkEOF(2,*dialogsTitle);

    getString_n_checkEOF(3,txtPar2);
    getXmString_n_checkEOF(4,*noOutMsg);	/* Note this is XmString here ! */
    getString_n_checkEOF(5,txtPar3);



    *childOutDepot = stringToValueCvt(txtPar1, depotNames, depotValues,
			XtNumber(depotNames), "TO_CURRENT", TO_CURRENT);
    if(!txtPar1) XtFree(txtPar1);


    *noOutDialog   = stringToValueCvt(txtPar2, dialogNames, dialogType,
			XtNumber(dialogNames), "TO_NONE", TO_NONE);
    if(!txtPar2) XtFree(txtPar2);


    /* default value for extendTo depends on needSelection flag */
    if(needSelection)
	*extendTo   = stringToValueCvt(txtPar3, extendNames, extendValues,
			XtNumber(extendNames), "EXTEND_TO_LINES", EXTEND_TO_LINES);
    else
	*extendTo   = stringToValueCvt(txtPar3, extendNames, extendValues,
			XtNumber(extendNames), "NO_EXTEND", NO_EXTEND);
    if(!txtPar3) XtFree(txtPar3);


}

#ifdef _NO_PROTO
void executeFilterCmd(win, cmd, filterExt, needSelection)
    aseditWindowStruct *win;
    char 	       *cmd;
    XmStringTable      filterExt;
    Boolean needSelection;
#else  /* ! _NO_PROTO */

void executeFilterCmd(aseditWindowStruct *win, char *cmd, XmStringTable filterExt,
			Boolean needSelection)
#endif
{
    char *cmdInput=NULL;
    int n;
    XmTextPosition left, right;
    int 	childOutDepot, noOutDialog, extendTo;
    char 	*outDialogTitle=NULL, *dialogsTitle=NULL;
    XmString 	noOutMsg;
    Boolean	ext_status;

    /* make sure that the cmd is not NULL; */
    if(cmd == NULL || strlen(cmd)== 0) return;		/* do not process empty command */

    /* get the command extended parameters first (if any); we have to do it before
       checking the needSelection because of the new WHOLE_FILE* flags */

    decodeFilterExtensions(filterExt, needSelection, &childOutDepot,
		&outDialogTitle, &dialogsTitle, &noOutDialog,
		&noOutMsg, &extendTo);


    /* the following two checks would probably never be used when
       needSelection is set to True;
       appropriate commands should be greyed out when there is no primary
       selection in the edit area (so this procedure would NOT be called
       when there is no selection and needSelection is True!)
    */

    if(needSelection && extendTo != EXTEND_TO_WHOLE_FILE &&
			extendTo != USE_WHOLE_FILE	 &&
                        extendTo != USE_FILE_OR_SELECTION )

    {
	/* classic filter case: check the selection and get the selection positions */
	if(!XmTextGetSelectionPosition(win->edit_text, &left, &right) || left == right)
	{
	    if(outDialogTitle) XtFree(outDialogTitle);
            if(dialogsTitle)   XtFree(dialogsTitle);
	    return;		/* nothing to process, return */
	}
    }
    else
    {
	/* usually the command case; we don't need the selection and we don't use
	   it as the command input; but get the left and right position
	   of the selection if it does exist;
	   the command result will replace current selection (if it is not
	   available or its length is zero, we will put it at the current insertion point)
	*/

	if(!XmTextGetSelectionPosition(win->edit_text, &left, &right) || left==right)
		left = right = XmTextGetInsertionPosition(win->edit_text);

    }


    /* first extend the selection to encompass the last line feed (if not explicitly
       forbidden) ; alternatively
       we could extend the selection to whole lines; the important bit is to
       have the final line feed (sed works on complete lines!)
       Note we support also extend jut to the beginning of line (although
       right now I can't think of application that would use that)
    */
    ext_status = True;
    if      (extendTo == EXTEND_TO_RIGHT_LF)
	ext_status = extendSelectionToLF(win, &left, &right);
    else if (extendTo == EXTEND_TO_LEFT_LF)
	ext_status = extendSelectionToBOL(win, &left, &right);
    else if (extendTo == EXTEND_TO_LINES)
	ext_status = extendSelectionToLines(win, &left, &right);
    else if (extendTo == EXTEND_TO_WHOLE_FILE)
	ext_status = extendSelectionToWholeFile(win, &left, &right);

    if(!ext_status)	/* check the extent status */
    {
	/* we couldn't extend and the user answered "Do NOT proceed" -- return */
	if(outDialogTitle) XtFree(outDialogTitle);
	if(dialogsTitle)   XtFree(dialogsTitle);
	return;
    }



    /* a user can request to use the whole file (disregard the selection) */
    if(extendTo == USE_WHOLE_FILE)
    {
	cmdInput = XmTextGetString(win->edit_text);
    }
    else if(extendTo == USE_FILE_OR_SELECTION)
    {
	if(left == right) /* selection was empty, use the whole file */
	    cmdInput = XmTextGetString(win->edit_text);
	else 	
	    cmdInput = XmTextGetSelection(win->edit_text);
    }
    else
    {
	/* default, pre 1.32 situation */
    	if(needSelection)				/* get the selection */
	    cmdInput = XmTextGetSelection(win->edit_text);
    	else
	    cmdInput = NULL;
    }



    if(executeShellCmd(win, cmd, cmdInput, left, right,
		childOutDepot, outDialogTitle, dialogsTitle,
		noOutDialog, noOutMsg))
    {
	/* the shell command is being executed */
	;
    }

    /* do *NOT* free cmdInput, outDialogTitle, dialogsTitle; their memory will be freed
       when all input is processed (i.e. in processChildOutput)
    */


}   /* executeFilterCmd */

