/* $Header: /afs/athena.mit.edu/astaff/project/atdev/src/cli/RCS/Cli.c,v 3.5 91/07/15 15:21:53 dot Exp Locker: dot $ */

/* 
  Copyright (C) 1990 by the Massachusetts Institute of Technology

   Export of this software from the United States of America is assumed
   to require a specific license from the United States Government.
   It is the responsibility of any person or organization contemplating
   export to obtain such a license before exporting.

WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
distribute this software and its documentation for any purpose and
without fee is hereby granted, provided that the above copyright
notice appear in all copies and that both that copyright notice and
this permission notice appear in supporting documentation, and that
the name of M.I.T. not be used in advertising or publicity pertaining
to distribution of the software without specific, written prior
permission.  M.I.T. makes no representations about the suitability of
this software for any purpose.  It is provided "as is" without express
or implied warranty.

  */

#include <stdio.h>
#include <Xm/Xm.h>

#ifdef _AtDevelopment_
#include "CliP.h"
#else
#include <At/CliP.h>
#endif
    
#ifdef SYSV
#define bcopy(s1, s2, l) memcpy (s2, s1, l)
#endif /* SYSV */
    
#define offset(field) XtOffset(AtCliWidget, field)
static XtResource resources[] = {
    {
	XmNnumHistoryItems, XmCNumHistoryItems, XmRInt,
	sizeof(int), offset(cli.numHistoryItems),
	XmRImmediate, (caddr_t) 25
    },
    {
	XmNgrabStdout, XmCGrabStdout, XmRBoolean,
	sizeof(Boolean), offset(cli.grabStdout),
	XmRImmediate, (caddr_t) False
    },
    {
	XmNgrabStderr, XmCGrabStderr, XmRBoolean,
	sizeof(Boolean), offset(cli.grabStderr),
	XmRImmediate, (caddr_t) False
    },
    {
	XmNpageMode, XmCPageMode, XmRBoolean,
	sizeof(Boolean), offset(cli.pageMode),
	XmRImmediate, (caddr_t) False
    },
    {
	XmNinputCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), offset(cli.inputCallback),
	XmRPointer, (caddr_t) NULL
    },
    {
	XmNsaveLines, XmCSaveLines, XmRInt,
	sizeof(int), offset(cli.saveLines),
	XmRImmediate, (caddr_t)64
    }
};

#undef offset

/* Core Class Functions */
    
static void previous_command(), next_command(), EndInput();
static void InitHistory(), AddHistoryItem();
static char *NextHistoryItem(), *PrevHistoryItem();
static void SaveCursorPosition(), RestoreCursorPosition();
static void TrimScrolledLines();

static XtActionsRec cli_actions[] = {
    {"previous-command",	(XtActionProc)previous_command},
    {"next-command",		(XtActionProc)next_command},
    {"end-input",               (XtActionProc)EndInput},
    {"save-cursor-pos",         (XtActionProc)SaveCursorPosition},
    {"restore-cursor-pos",      (XtActionProc)RestoreCursorPosition},
    {"trim-scrolled-lines",     (XtActionProc)TrimScrolledLines},
};

static char cli_translations[] = "\
	<Key>Return:		end-input() trim-scrolled-lines()\n\
        <Key>Up:		previous-command()\n\
	<Key>Down:		next-command()\n\
	Ctrl<Key>N:		next-command() \n\
	Ctrl<Key>P:		previous-command() \n\
	Ctrl<Key>D:		delete-next-character() \n\
	Meta<Key>D:		kill-next-word() \n\
	Ctrl<Key>K:		kill-to-end-of-line() \n\
	Ctrl<Key>W:		kill-selection() \n\
	Ctrl<Key>Y:		unkill() \n\
	Ctrl<Key>F:		forward-character() \n\
	Ctrl<Key>B:		backward-character() \n\
	Meta<Key>F:		forward-word() \n\
	Meta<Key>B:		backward-word() \n\
	Ctrl<Key>A:		beginning-of-line() \n\
	Ctrl<Key>E:		end-of-line() \n\
	Ctrl<Key>L:		redraw-display() \n\
        Shift<Key>Tab:		prev-tab-group() \n\
        Ctrl<Key>Tab:		next-tab-group() \n\
        <Key>Tab:		next-tab-group() \n\
        Ctrl<Key>Right:		forward-word()\n\
        Shift<Key>Right:	key-select(right)\n\
        Meta<Key>Right:	        key-select(right)\n\
	<Key>Right:		forward-character()\n\
	Ctrl<Key>Left:		backward-word()\n\
	Shift<Key>Left:		key-select(left)\n\
	Meta<Key>Left:		key-select(left)\n\
	<Key>Left:		backward-character()\n\
	Shift<Key>Delete:	delete-previous-word()\n\
	Meta<Key>Delete:	delete-previous-word()\n\
        <Key>Delete:            delete-previous-character()\n\
        Shift<Key>BackSpace:	delete-previous-word()\n\
	<Key>BackSpace:		delete-previous-character()\n\
	~Ctrl <Key>:		self-insert()\n\
        ~Meta <Btn1Down>:      save-cursor-pos() grab-focus() select-start()\n\
        ~Meta <Btn1Motion>:     extend-adjust()\n\
        ~Meta <Btn1Up>:         extend-end() restore-cursor-pos()\n\
        ~Meta <Btn3Down>:       save-cursor-pos() extend-start()\n\
        ~Meta <Btn3Motion>:     extend-adjust()\n\
        ~Meta <Btn3Up>:         extend-end() restore-cursor-pos()\n\
        ~Meta <Btn2Up>:         stuff()\n\
        <LeaveWindow>:		leave()\n\
        <FocusIn>:		focusIn()\n\
        <FocusOut>:		focusOut()\n\
	<Unmap>:		unmap()";


static void Initialize();	
    
AtCliClassRec atCliClassRec = {
  {
/* core_class fields */	
    /* superclass	  */	(WidgetClass) &xmTextClassRec,
    /* class_name	  */	"AtCli",
    /* widget_size	  */	sizeof(AtCliRec),
    /* class_initialize   */    NULL,
    /* class_part_initiali*/	NULL,
    /* class_inited       */	FALSE,
    /* initialize	  */	Initialize,
    /* initialize_hook    */	NULL,
    /* realize		  */	XtInheritRealize,
    /* actions		  */    cli_actions,
    /* num_actions	  */	XtNumber(cli_actions),
    /* resources	  */	resources,
    /* num_resources	  */	XtNumber(resources),
    /* xrm_class	  */	NULLQUARK,
    /* compress_motion	  */	TRUE,
    /* compress_exposure  */	TRUE,
    /* compress_enterleave*/	TRUE,
    /* visible_interest	  */	FALSE,
    /* destroy		  */    NULL,
    /* resize		  */	XtInheritResize,
    /* expose		  */	XtInheritExpose,
    /* set_values	  */	NULL,
    /* set_values_hook	  */	NULL,
    /* set_values_almost  */	XtInheritSetValuesAlmost,
    /* get_values_hook    */	NULL,
    /* accept_focus	  */	NULL,
    /* version		  */	XtVersion,
    /* callback_private   */	NULL,
    /* tm_table		  */	cli_translations,
    /* query_geometry     */    NULL,
    /* display accel	  */	XtInheritDisplayAccelerator,
    /* extension	  */	NULL,
  },

   {				/* primitive_class fields 	*/
      _XtInherit,   		/* Primitive border_highlight   */
      _XtInherit,   		/* Primitive border_unhighlight */
      NULL,         		/* translations                 */
      NULL,         		/* arm_and_activate           	*/
      NULL,  	    	        /* get resources 	        */
      0,	                /* num get_resources            */
      NULL,         		/* extension                    */
   },

   {				/* text class fields */
      NULL,             	/* extension         */
   },
   {                            /* cli class field */
      NULL,                     /* unused */
   }
};

WidgetClass atCliWidgetClass = (WidgetClass) &atCliClassRec;

static void DoOutput();
static void DoStderr();

static void VerifyCursorMotion(w,tag,client_data)
AtCliWidget w;
caddr_t tag;
XmTextVerifyCallbackStruct *client_data;
{
    Arg a;

    /* for scrolled cli widgets, forces a scroll to end on input */
    XmTextShowPosition((Widget)w,XmTextGetInsertionPosition((Widget)w));

    if (client_data->newInsert < w->cli.inputpos) {
        client_data->doit = FALSE;
	XtSetArg(a,XmNcursorPosition,w->cli.inputpos);
	XtSetValues(w,&a,1);

    }

}


static void VerifyModify(w,tag,client_data)
AtCliWidget w;
caddr_t tag;
XmTextVerifyCallbackStruct *client_data;
{
     if (client_data->startPos > 0) {
	  /* for scrolled cli widgets, forces a scroll to end on input */
	  XmTextShowPosition((Widget)w,XmTextGetInsertionPosition((Widget)w)); 

	  if (client_data->startPos < w->cli.inputpos) {
	       client_data->doit = FALSE;
	  }
     }
}


static XmTextPosition SavedCursorPosition = -1;

static void SaveCursorPosition(w)
AtCliWidget w;
{
    if (SavedCursorPosition == -1) {
	SavedCursorPosition = XmTextGetInsertionPosition((Widget)w);
	XtRemoveCallback((Widget)w,XmNmotionVerifyCallback,VerifyCursorMotion,NULL);
	XtRemoveCallback((Widget)w,XmNmodifyVerifyCallback,VerifyModify,NULL);
    }
}

static void RestoreCursorPosition(w)
AtCliWidget w;
{
    if (SavedCursorPosition != -1) {
	XtAddCallback((Widget)w,XmNmotionVerifyCallback,VerifyCursorMotion,NULL);
	XtAddCallback((Widget)w,XmNmodifyVerifyCallback,VerifyModify,NULL);
	XmTextSetInsertionPosition((Widget)w,SavedCursorPosition);
	SavedCursorPosition = -1;
    }
}


/* ARGSUSED */
#ifdef __STDC__
static void EndInput(AtCliWidget w, caddr_t tag, caddr_t client_data)
#else
static void EndInput(w, tag, client_data)
AtCliWidget w;
caddr_t tag, client_data;
#endif
{
    if (w->cli.doing_modal_input == True)
	longjmp(w->cli.mark,1);
    else {
	char *buf, s[512];

	buf = XmTextGetString((Widget)w);
	strncpy(s,&buf[w->cli.inputpos],511);
	XtFree(buf);
	w->cli.inputpos = XmTextGetLastPosition((Widget)w);
	AtCliPuts("\n",w);
	AddHistoryItem(w,s);
	XtCallCallbacks((Widget)w,XmNinputCallback,s);
    }
}

static void TrimScrolledLines(w)
AtCliWidget w;
{
     
     char *savedLines;
     register int nlines = 0;
     register char *linePtr;
     Arg al[5];
     int ac,  diff;
     XmTextPosition top_pos;

     ac = 0;
     XtSetArg (al[ac], XmNtopCharacter, &top_pos); ac++;
     XtGetValues ((Widget)w, al, ac);
     
     savedLines = XmTextGetString ((Widget)w);
     for (linePtr = savedLines + top_pos; 
	  linePtr > savedLines && nlines <= w->cli.saveLines;
	  linePtr--) {
	  
	  if (*linePtr == '\n') 
	       nlines++;
	  
     }
     if (nlines > w->cli.saveLines) {
	  /* start new string beyond newline */
	  diff = linePtr + 2 - savedLines;
	  XmTextReplace ((Widget)w, 0, diff, "");
	  w->cli.inputpos = XmTextGetLastPosition ((Widget)w);
     }
     XtFree (savedLines);

}

static void Initialize(request, new) 
AtCliWidget request, new;
{
    Arg al[10];
    int ac;
    static char *CliTranslationsText = "\
	<Key>Return:		end-input()\n\
        <Key>Up:		previous-command()\n\
	<Key>Down:		next-command()";
    
    static XtTranslations CliTranslations = NULL;

    /* we have to do this because multi_line_edit mode manages to
       override the some of the translations in the default-translations
       field in the class structure. */
    if (CliTranslations == NULL)
	CliTranslations = XtParseTranslationTable(CliTranslationsText);
    XtOverrideTranslations(new,CliTranslations);
    
    new->cli.inputpos = 0;
    new->cli.doing_modal_input = False;
    XtAddCallback(new,XmNmotionVerifyCallback,VerifyCursorMotion,NULL);
    XtAddCallback(new,XmNmodifyVerifyCallback,VerifyModify,NULL);

/*    XmTextSetEditable(new,False); */
    
    InitHistory(new);

    if (new->cli.grabStdout == True) {
	if (pipe(new->cli.fildes) == -1) {
	    perror("Cli widget: pipe");
	}
	XtAppAddInput(XtWidgetToApplicationContext(new), new->cli.fildes[0],
		      (XtPointer) XtInputReadMask,DoOutput, new);
	dup2(new->cli.fildes[1],1); 
    }

    if (new->cli.grabStderr == True) {
	if (pipe(new->cli.stderr_fildes) == -1) {
	    perror("Cli widget: pipe");
	}
	XtAppAddInput(XtWidgetToApplicationContext(new),
		      new->cli.stderr_fildes[0],
		      (XtPointer) XtInputReadMask,DoStderr, new);
	dup2(new->cli.stderr_fildes[1],2); 
    }

    ac = 0;
    XtSetArg(al[ac], XmNcursorPositionVisible, True); ac++;
    XtSetValues(new,al,ac);
}

static void DoOutput(w,fd)
AtCliWidget w;
int fd;
{
    char buf[512];
    int cnt;

    cnt = read(w->cli.fildes[0],buf,511);
    buf[cnt] = '\0';
    AtCliPuts(buf,w);
    TrimScrolledLines (w); 
}    

static void DoStderr(w)
AtCliWidget w;
{
    char buf[512];
    int cnt;

    cnt = read(w->cli.stderr_fildes[0],buf,511);
    buf[cnt] = '\0';
    AtCliPuts(buf,w);
}    

void AtCliPuts(s,w)
char *s;
AtCliWidget w;
{
     char buf[1024];

     /* Make sure that s is a writable string */
     strcpy (buf, s);
     XmTextReplace ((Widget)w, w->cli.inputpos, w->cli.inputpos, buf);
     w->cli.inputpos += strlen (s);
     TrimScrolledLines (w); 
     XmTextShowPosition((Widget)w,XmTextGetLastPosition((Widget)w));
    
}


char *AtCliGets(s,n,w)
char *s;
int n;
AtCliWidget w;
{

    int status;

    if (w->cli.grabStdout == True) {
	fflush(stdout);
    }
    
    if (w->cli.doing_modal_input == True) {
	XtWarning("AtCli widget already waiting for input.\n");
	s[0] = '\0';
	return s;
    }

    w->cli.doing_modal_input = True;
    XmTextSetEditable((Widget)w,True);
    status = setjmp(w->cli.mark);

    if (status == 0) {
	XtMainLoop();             /* should be XtAppMainLoop() */
    }
    else {
	char *buf;

	buf = XmTextGetString((Widget)w);
	strncpy(s,&buf[w->cli.inputpos],n-1);
	s[n-1] = '\0';
	/* get rid of excess crap in buf here */
	w->cli.inputpos = strlen(buf);
	XtFree(buf);
	XmTextSetEditable((Widget)w,False);
	w->cli.doing_modal_input = False;
	AtCliPuts("\n",w);
	AddHistoryItem(w,s);
	return s;
    }	
}

static void next_command(w)
AtCliWidget w;
{
    char *new;
    int buflen, end;

    buflen = XmTextGetLastPosition((Widget)w);
    new = NextHistoryItem(w);
    end = w->cli.inputpos + strlen(new);
    
    XmTextReplace((Widget)w,w->cli.inputpos,buflen,new);
    XmTextSetInsertionPosition((Widget)w,end);
    XmTextShowPosition((Widget)w,end);
}

static void previous_command(w)
AtCliWidget w;
{
    char *new;
    int buflen, end;
    
    buflen = XmTextGetLastPosition((Widget)w);
    new = PrevHistoryItem(w);
    end = w->cli.inputpos + strlen(new);
    
    XmTextReplace((Widget)w,w->cli.inputpos,buflen,new);
    XmTextSetInsertionPosition((Widget)w,end);
    XmTextShowPosition((Widget)w,end);
}

static void InitHistory(w)
AtCliWidget w;
{
    if (w->cli.numHistoryItems == 0) {
	w->cli.history = NULL;
	w->cli.historypos = -1;
    }
    else {
	w->cli.history = (char **) XtCalloc(w->cli.numHistoryItems,
					    sizeof(char *));
	w->cli.historypos = -1;
    }
}

static void AddHistoryItem(w,s)
AtCliWidget w;
char *s;
{
    /* don't save empty lines */
    if ((s == NULL) || (s[0] == '\0') || (s[0] == '\n')) return;

    /* move all items one slot into the past.  */
    /* NOTE: overlapping copy operation */
    bcopy(w->cli.history, &w->cli.history[1],
	  (w->cli.numHistoryItems-1) * sizeof(char *));

    /* now make a copy of the line */
    w->cli.history[0] = XtMalloc(strlen(s)+1);
    strcpy(w->cli.history[0],s);

    /* and reset the history pointer to the 'present' */
    w->cli.historypos = -1;
}

static char *PrevHistoryItem(w)
AtCliWidget w;
{
    if ((w->cli.historypos < w->cli.numHistoryItems-1) &&
	(w->cli.history[w->cli.historypos+1] != NULL))
	w->cli.historypos++;

    return w->cli.history[w->cli.historypos];
}

static char *NextHistoryItem(w)
AtCliWidget w;
{
    if (w->cli.historypos >= 0)
	w->cli.historypos--;

    if (w->cli.historypos == -1)
	return "";
    else
	return w->cli.history[w->cli.historypos];
}

