#ifndef NO_IDENT
static char *Id = "$Id: edtcmd.c,v 1.12 1995/06/06 09:44:14 tom Exp $";
#endif

/*
 * Title:	edtcmd.c
 * Author:	Thomas E. Dickey
 * Created:	16 Apr 1985 (from DIRCMD, 10 May 1984)
 * Last update:
 *		19 Feb 1995, prototypes
 *		18 Feb 1995, port to AXP (renamed 'alarm').
 *		27 Apr 1985
 *
 * Entry:	edtcmd:		Accept & edit a command
 *		edtcmd_init:	Initialize this module
 *		edtcmd_get:	Get a single character
 *		edtcmd_last:	Compare argument with last from 'edtcmd_get'.
 *		edtcmd_crt:	Merge input+reference buffers to display.
 */

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

#include	"bool.h"
#include	"cmdstk.h"
#include	"getpad.h"
#include	"getraw.h"
#include	"edtcmd.h"

#include	"strutils.h"

#define	RUBOUT	'\177'
#define	CTL(c)	(037 & (c))

/*
 * 'lastcommand' is used as a 1-level stack to suppress repeated error messages
 *	due to repeated (or stuck) key.  The logic is tested in the main program
 *	of 'FLIST', where all commands are reduced to a single code.
 */
static	int	lastcommand;
static	int	thiscommand;	/* 1-level stack for repeat test */
static	char	*cmdbfr	= nullC;

/* <edtcmd_get>:
 * Read a character, saving the previous one, for repeated-key testing.
 * We do screen-dump and refresh here as well:
 */
int
edtcmd_get (void)
{
	lastcommand = thiscommand;
	for (;;)
	{
		switch (thiscommand = getpad())
		{
		case CTL('W'):	crt_refresh ();		break;
		case CTL('K'):	snapshot ();		break;
		default:	return (thiscommand);
		}
	}
}

/* <edtcmd_last>:
 * Test if the argument is the same as the previous character.  This is used
 * to latch multiple-keystroke errors (such as from a stuck key).
 */
int
edtcmd_last (int c)
{
	return ((c > 0) && (c == lastcommand));
}

/* <edtcmd>:
 * Read/edit a "visible" command
 */

#define	doARROW	(flags & 8)	/* Return arrow keys in 128-255 code	*/
#define	doHIGH	(flags & 4)	/* Highlight command as it is entered	*/
#define	doLOWER	(flags & 2)	/* Lowercase command as it is entered	*/
#define	doTRIM	(flags & 1)	/* Trim leading/trailing blanks		*/

#define	THIS	cmdbfr[col]
#define	PREV	cmdbfr[col-1]
#define	DELETE	{strcpy (&PREV, &THIS); col--; }
#define	SHOWIT	edtcmd_crt (cmdbfr, refbfr, doHIGH, do_col, do_line, col)
#define	COMMAND	(doLOWER ? _tolower(command) : _toupper(command))

char *
edtcmd (
	int	command,	/* Initial and scratch value		*/
	char	*delim,		/* Delimiters (if used, for repeat-count) */
	int	flags,		/* Bits: highlight, lowercase, trim	*/
	int	do_line,	/* Line on which to enter command 	*/
	int	do_col,		/* Column at which to begin command	*/
	char	*do_hlp,	/* Help-interface string		*/
	char	*do_1st,	/* Starting contents of command-result	*/
	char	*do_ref)	/* Reference copy of command-line	*/
{
	int	len,			/* Current command-length	*/
		col,			/* Edit-index into 'cmdbfr[]'	*/
		maxcols	= crt_width()-do_col-2,	/* Max length of cmd-string */
		complete= FALSE,
		j,
		get_dir	= 1;		/* Normal sense of RETRIEVE	*/
	char	refbfr[CRT_COLS],
		tmp[CRT_COLS];


	cmdstk_tos ();
	strcpy (cmdbfr, do_1st);
	strcpy (refbfr, do_ref);

retrieve:
	if (command == RETRIEVE)
	{
		if (cmdstk_get (cmdbfr, get_dir))
		{
			if (! doLOWER)	strucpy (cmdbfr, nullC);
		}
		else
		{
			sound_alarm ();
			goto did_abort;
		}
	}
	else if (isprint(command) && isascii(command))
		sprintf (cmdbfr, "%c", COMMAND);

	col	= strlen (cmdbfr);
	get_dir	= 1;			/* Normal sense of RETRIEVE	*/

	SHOWIT;				/* show the new command	*/
	/*
	 * Perform restricted command editing a la EDT, by recognizing
	 * backspace, linefeed, delete, left/right arrows for editing.
	 * Normal printing characters (including space) are inserted at
	 * cursor position.
	 *
	 * If 'doARROW' is set, we do not use arrow-keys for editing/retrieval,
	 * but instead pass them back mapped into the range 128-255 on the end
	 * of the input string.
	 */
	while (!complete)
	{
		ctlx_clr();		/* Reset CTRL/X flag	*/
		switch (command = edtcmd_get())
		{
		case padUP:
			if (doARROW)	goto do_arrow;
		case RETRIEVE:
			command	= RETRIEVE;
			get_dir	= 1;
			goto retrieve;
		case padDOWN:
			if (doARROW)	goto do_arrow;
			command	= RETRIEVE;
			get_dir = -1;
			goto retrieve;
		case RUBOUT:
			if (col)	DELETE
			else		sound_alarm ();
			break;
		case CTL('X'):
		case CTL('U'):
		case CTL('C'):
		case CTL('Y'):
			cmdbfr[0] = cmdbfr[1] = col = 0;
			break;
		case padENTER:
		case '\r':
			if (delim)
			{
				if (strchr(delim, command))
					complete = TRUE;
				else
					sound_alarm();
			}
			else
				complete = TRUE;
			break;
		case '\n':
			if (col)
			{
				if (isspace(PREV))
				{
					while (col && isspace(PREV))
						DELETE
				}
				while (col && !isspace(PREV))
					DELETE
			}
			else		sound_alarm();
			break;
		case padLEFT:		/* left-arrow	*/
			if (doARROW)	goto do_arrow;
		case CTL('D'):		/* (cf: VMS 4.0)*/
			if (col > 0)	col--;
			else		sound_alarm();
			break;

		case padRIGHT:		/* right-arrow	*/
			if (doARROW)	goto do_arrow;
		case CTL('F'):		/* (cf: VMS 4.0)*/
			if (col < maxcols-1)
			{
				if (++col >= strlen(cmdbfr))
					strcat (cmdbfr, " ");
			}
			else
				sound_alarm();
			break;		

		case '\b':
		case pad0:
			col = 0;
			break;
		case pad2:
		case CTL('E'):		/* (a la VMS 4.0)	*/
			col = strlen(cmdbfr);
			break;
		case HELPKEY:
			crt_help (nullC, do_hlp);
			break;
		case ' ':
			if (doTRIM && col == 0)
			{
				sound_alarm ();
				break;
			}
		default:
			if (isascii(command)
			&&  isprint(command)
			&&  strlen(cmdbfr) < maxcols)
			{
			register int use_it = TRUE;
				/*
				 * If we have a delimiter, it will be returned
				 * as the last character on the command-string
				 * (unless it happens to be '\n').
				 */
				command = COMMAND;
				if (delim)
				{
					if (strchr(delim, command))
						complete = TRUE;
					else if (!isdigit(command))
						use_it = FALSE;	
				}
				if (use_it)
				{
					strcpy (tmp, &THIS);
					strtrim (tmp);
					sprintf (&THIS, "%c%s", command, tmp);
					col++;
				}
				else
					sound_alarm ();
			}
			else
				sound_alarm ();
		}	/* end:switch(command) */
		/*
		 * Cursor movement may temporarily lengthen the buffer.  Trim
		 * trailing blanks:
		 */
		len	= doTRIM ? strtrim(cmdbfr) : strlen(cmdbfr);
		j	= max(len, col) - len;
		while (j-- > 0)
			strcat  (cmdbfr, " ");

		if (!gotraw() || complete) SHOWIT;
		if (strlen(cmdbfr) <= 0)
		{
			cmdbfr[0] = EOS;
			goto did_abort;
		}

		/*
		 * Enter this block to return an arrow-key (or other keypad
		 * control, as required), mapped into the range 128-255.
		 */
		command	= EOS;
do_arrow:	if (command > 255)	/* 'pad' codes are 256-511	*/
		{
			SHOWIT;
			sprintf (tmp, "%c", command | 128);
			strcat (cmdbfr, tmp);
			complete = TRUE;
		}

	}	/* end:while (! complete) */
did_abort:
	lastcommand = EOS;
	ctlx_clr ();		/* Reset CTRL/X flag	*/
	return (cmdbfr);
}

/* <edtcmd_init>:
 * Initialize this module.  We need a buffer in which to build commands,
 * 'cmdbfr', as well as a buffer in which to store the prior-state of a
 * line which we are receiving upon.
 */
void
edtcmd_init (void)
{
	cmdbfr = calloc(1, CRT_COLS+1);
	lastcommand = EOS;
}

/* <edtcmd_crt>:
 * Display the text for the current "visible-command".  The command-text is
 * displayed *highlighted*, with a trailing space (so that if we are overstriking
 * a display-line, there is a gap).  The cursor is positioned to the current-
 * column.
 *
 * This procedure is self-contained (does not directly reference any static
 * data).
 */
void
edtcmd_crt (
	char	*cmd_,	/* Text to overlay 'ref_[]'		(asciz)	*/
	char	*ref_,	/* Reference, to restore/update			*/
	int	highlight, /* Do highlighting of new text if true	*/
	int	rcol,	/* Column at which 'cmd_[]' is put	 1..n	*/
	int	rline,	/* Reference: line in screen		 1..n	*/
	int	ccol)	/* Reference: cursor-column in 'cmd_[]', 0..n	*/
{
	int	j	= strlen(ref_),
		len	= strlen(cmd_);
	char	*c_,
		tocmp	[CRT_COLS],
		toshow	[CRT_COLS];

	for (c_ = toshow; *cmd_; c_++, cmd_++)
	{
		if (highlight)
			*c_ = tohigh(*cmd_);
		else
			*c_ = *cmd_;
	}

	strcpy (tocmp, ref_);
	while (j < rcol)	strcpy (&tocmp[j++], " ");
	strncpy (&tocmp[rcol-1], toshow, len);
	j	 = rcol + len - 1;
	tocmp[j] = ' ';			/* add a space on the end */
	if (strlen(ref_) <= j)
		tocmp[j+1] = '\0';	/* ...but keep trailing null */

	crt_text (tocmp, rline, 0);
	crt_move (rline+1, rcol+ccol);
}
