#ifndef NO_IDENT
static char *Id = "$Id: dircmd.c,v 1.15 1995/10/27 11:37:00 tom Exp $";
#endif

/*
 * Title:	dircmd.c
 * Author:	T.E.Dickey
 * Created:	10 May 1984
 * Last update:
 *		27 Oct 1995, added "create" command.
 *		26 Oct 1995, mods for animated "Working..." message
 *		24 Oct 1995, added commands to sort-by-username
 *		27 May 1995, prototypes
 *		04 Nov 1988- added "/dexpired" table entry, and matching sort
 *		11 Jul 1988- fix side-effect of "<number> -" from dclarg fix.
 *		01 Jul 1988- added LSEDIT command
 *		12 Nov 1985, added '?owner' command.
 *		05 Oct 1985, added '/width' command to permit 80/132-column
 *			     switch on VT100's.  Also, permit additional
 *			     keyword on HELP.
 *		21 Sep 1985, merged all VCMD2-tables by handling '/', '?'
 *			     prefixes in 'dirarg' module.  Added '/protection'
 *			     as alias for PROTECT (SET PROTECTION) and '?daytime'
 *			     as alias for ?DATE (SHOW [DAY]TIME).  Use 'debrief'
 *			     to show possible commands user may mean if he forgets
 *			     the unambiguous-abbreviation.
 *		31 Jul 1985, added 'UPDATE' command.
 *		25 Jul 1985, added '?READ' command.
 *		20 Jul 1985, added DUMP command, for debugging structured heap.
 *		05 Jul 1985, corrected release of memory in 'dircmd_doit'.
 *		03 Jul 1985, cleanup of 'filelist' definition.  Use 'scanint'
 *			     instead of 'sscanf' to bypass CC2.0 bug.
 *		15 Jun 1985, reference '(func)' as '(*func)' to make CC2.0 happy
 *		10 Jun 1985, added 'SHELL', '?FIND' commands
 *		24 May 1985, corrected treatment of '?' (for '?HELP').  Added
 *			     log-file for /MARK and other commands which imply
 *			     operations on the current file.
 *		19 May 1985, added '/CWIDTH' command.
 *		18 May 1985, added '/' alone as a scroll-to-top command.
 *		14 May 1985, thrashed for a while before finding a bug in the
 *			     cmdstk module.  Leave debug-code inline for later
 *			     use.
 *		05 May 1985, completed 'keydefs' code.  (Must still merge the
 *			     two vcmd-tables and check arguments).
 *		01 May 1985, began 'keydefs' code, restructured command invokes
 *			     to use a single (redefinable) table of key-defs,
 *			     rather than the combination return-code to main-case
 *			     with special keypad stuff, and then visible commands
 *			     treated as the third case.
 *		30 Apr 1985, broke 'dircmd_doit' from 'dircmd_read'.
 *		27 Apr 1985, corrected repeated-command test (in 'edtcmd').
 *		25 Apr 1985, re-use 'edtcmd' with BROWSE by adding delimiter-arg.
 *		18 Apr 1985, chopped out 'edtcmd' code
 *		13 Apr 1985, added call on 'flist_log'
 *		11 Apr 1985, added call to 'gotraw' to make typeahead run faster.
 *		30 Mar 1985, 'didbeep()' is a superset of 'didwarn' test.
 *			     Added '?MEMORY'.
 *		22 Mar 1985, added "/SIDENTIFIER", "/SLENGTH".
 *		14 Mar 1985, renamed 'more' to 'browse'
 *		05 Mar 1985, had stepped-on repeated-error latch.
 *		23 Feb 1985, added magic-intercept for ^D
 *		05 Jan 1985, added "NEXT" keyword.  Use 'read_dft' for FIND
 *			     and NFIND defaults.
 *		29 Dec 1984, added "/SBACKUP", "/SCREATED", "/SREVISED", and
 *			     relative (e.g., "12-") numeric scrolling.  Show
 *			     SELECT-state (via 'dds_line').
 *			     Put BROWSE on key-9.
 *		24 Dec 1984, added "/DSHORT", "/DLONG"
 *		23 Dec 1984, added "/CLEFT", "/CRIGHT".
 *		22 Dec 1984, added "NFIND", changed record-attributes to 'X'
 *			     to provide for 'allocate', 'length'.  In command-
 *			     edit, permit up/down arrows to traverse 'cmdstk'.
 *		19 Dec 1984, use ^E as ==> (go to end of command), also moved
 *			     'cmdstk' init calls to main program.
 *		18 Dec 1984, treat ^Y as ^U if we ever see it.
 *		15 Dec 1984, added 'cpy_dft' argument to 'dclarg'.
 *		11 Dec 1984, added '?QUOTA', '/SFORMAT', '/SATTRIBUTE'.
 *		17 Nov 1984, added 'INSPECT'
 *		22 Oct 1984
 *
 * Function:	This module performs the actual command input for the "FLIST"
 *		program.  Commands are of three types:
 *
 *		(a)	Set command-processor state (e.g., GOLD, FORWARD, and
 *			BACKWARD keys).
 *		(b)	Keypad commands, or
 *		(c)	Visible commands, requiring multiple keystrokes.  All
 *			visible-commands are terminated with ENTER/RETURN.
 *			This mode of operation is assumed whenever the user
 *			types a printing character (instead of an escape
 *			sequence or control character).  Visible commands
 *			which correspond to keypad commands are translated
 *			and returned to the main program.  Those which do not
 *			are executed under control of this module.
 */

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

#include	<rms.h>

#include	"cmdstk.h"
#include	"flist.h"
#include	"getpad.h"

#include	"acpcopy.h"
#include	"dclarg.h"
#include	"dds.h"
#include	"freelist.h"
#include	"dircmd.h"
#include	"edtcmd.h"

#include	"strutils.h"

static	void	dircmd_doit (int *curfile_, char *cmdbfr, int history);
static	int	dircmd_getc (int *curfile_);
static	void	dircmd_kcmd (int *curfile_, int command);
static	void	dircmd_keydefs (int code, int flags, char *string);
static	void	dircmd_read (int *curfile_, int command, char *original);

/*
 * Special command-processing routines are called:
 *	(tocall)(&curfile, xcmd_, xdcl_);
 *
 * where:
 *	curfile - index in 'filelist[]' to current (working) file.  The address
 *		  is passed to support search/scrolling commands.
 *	xcmd_	=> lowercased-string, equal to substituted tokens & logicals.
 *	xdcl_	=> DCLARG-list (filespecs are uppercased).
 */

import(filelist);

/*
 * Local (static) data:
 */
#define	DO_NEXT	0		/* value if 'command' completed, do next*/

#define	RUBOUT	'\177'

/*
 * 'dir_flg' is used locally to control the interpretation of the keypad keys
 *	which control scrolling/searching.
 * 'selected' is used in the 'dirsrt.c' module to determine the resulting
 *	index into 'dirent[]' at which the cursor will be placed after a sort.
 *	This variable supports the notion of a fixed-point sort.  (If negative,
 *	it is inactive.)
 * 'read_dft' is used as the default name-string for READ, EDIT (with multiple-
 *	files), and is toggled by the FLIST option "VERSION, NOVERSION".
 */
int	multi_quit;			/* Nonzero if multi-quit	*/

static	int	dir_flg	= TRUE;		/* TRUE if "forward"		*/
static	int	selected;		/* index to "selected" file	*/

static	char	read_dft[]	= "*.*;*";

typedef	struct	{
	unsigned char flags;		/* nonzero iff we must edit it	*/
	char	*string;
	} KEYDEFS;

static	KEYDEFS	*keydefs	= 0;

/*
 * Symbol table to relate "visible" commands to actual controls.  The
 * actual-text may not be printing-ASCII, because this is trapped by the
 * visible-command-detection loop.  Commands which are also control characters
 * are mapped into these control characters.  (Those having no "actual" code
 * are decoded on the basis of the "func" component, which always overrides.)
 *
 * patch: This table could be re-structured to be initialized in 'dircmd_init'
 *	to accept a profile-file (a la FLIST), relating all visible-commands
 *	to control keys or escape sequences.
 */
#define	OZ(flag)	(v_nonDCL | flag),0
static
VCMD2	vcmd2[]	= {
	"show",		3, not_impl,	OZ(0),	/* "?"-prefix	  */
	"set",		3, not_impl,	OZ(0),	/* "/"-prefix	  */
						/* Status+display */
	"?help",	1, flshow,	OZ(v_ARGS),
	"?columns",	2, flshow,	OZ(0),
	"?date",	2, flshow,	OZ(0),
	"?daytime",	2, flshow,	OZ(0),
	"?find",	2, flshow, 	OZ(0),
	"?level",	2, flshow,	OZ(0),
	"?memory",	2, flshow,	OZ(0),
	"?owner",	2, flshow,	OZ(0),
	"?quota",	2, flshow,	OZ(0),
	"?read",	2, flshow,	OZ(0),
	"?size",	2, flshow,	OZ(0),
	"?times",	2, flshow,	OZ(0),
	"?version",	2, flshow,	OZ(0),
						/* Help-display	*/
	"/help",	4, flshow,	OZ(v_ARGS),
	"/hlp",		4, flshow,	OZ(v_ARGS),
	"help",		1, flshow,	OZ(v_ARGS),
						/* Commands	*/
	"quit", 	1, flquit,	OZ(v_ARGS),
						/* Trap messages*/
	"/hold",	4, flset_hold,	OZ(0),
	"/nohold",	4, flset_hold,	OZ(0),
						/* Selection	*/
	"/mark",	4, flset_mark,	OZ(v_NAME),
	"/nomark",	4, flset_mark,	OZ(0),
						/* Scrolling	*/
	"/top",		4, flpage,	OZ(0),
	"/end", 	4, flpage,	OZ(0),
	"/page",	2, flpage,	OZ(v_ARGS),
	"/backward",	2, flpage,	OZ(v_ARGS),
	"/home",	2, flpage,	OZ(0),
	"/middle",	2, flpage,	OZ(0),
	"/low",		2, flpage,	OZ(0),
	"/forward",	2, flpage,	OZ(v_ARGS),
						/* Display form */
	"/columns",	2, flcols,	OZ(v_ARGS),
	"/cleft",	3, flcols_left,	OZ(0),
	"/cright",	3, flcols_right,OZ(0),
	"/cwidth",	3, flcols_width,OZ(v_ARGS),
	"/times",	3, flshow,	OZ(0),
	"/width",	2, flcols_132,	OZ(v_ARGS),
						/* Date-display	*/
	"/dcreated",	2, flset_date,	OZ(0),	/* (default) */
	"/dbackup",	3, flset_date,	OZ(0),
	"/drevised",	3, flset_date,	OZ(0),
	"/dexpired",	3, flset_date,	OZ(0),
	"/dshort",	3, flset_date,	OZ(0),
	"/dlong",	3, flset_date,	OZ(0),
						/* Normal-sort	*/
	"/salloc",	3, flsort,	OZ(0),
	"/sbackup",	3, flsort,	OZ(0),
	"/screated",	3, flsort,	OZ(0),
	"/sdate",	3, flsort,	OZ(0),
	"/sexpired",	3, flsort,	OZ(0),
	"/sformat",	3, flsort,	OZ(0),
	"/shour",	3, flsort,	OZ(0),
	"/sidentifier",	3, flsort,	OZ(0),
	"/slength",	3, flsort,	OZ(0),
	"/smask",	3, flsort,	OZ(0),
	"/sname",	2, flsort,	OZ(0),	/* (default)	*/
	"/sowner",	3, flsort,	OZ(0),
	"/spath",	3, flsort,	OZ(0),
	"/srevised",	3, flsort,	OZ(0),
	"/ssize",	3, flsort,	OZ(0),
	"/stype",	3, flsort,	OZ(0),
	"/suser",	3, flsort,	OZ(0),
	"/svers",	3, flsort,	OZ(0),
	"/sweek",	3, flsort,	OZ(0),
	"/sxab",	3, flsort,	OZ(0),
						/* Reverse-sort	*/
	"/ralloc",	3, flsort,	OZ(0),
	"/rbackup",	3, flsort,	OZ(0),
	"/rcreated",	3, flsort,	OZ(0),
	"/rdate",	3, flsort,	OZ(0),
	"/rexpired",	3, flsort,	OZ(0),
	"/rformat",	3, flsort,	OZ(0),
	"/rhour",	3, flsort,	OZ(0),
	"/ridentifier",	3, flsort,	OZ(0),
	"/rlength",	3, flsort,	OZ(0),
	"/rmask",	3, flsort,	OZ(0),
	"/rname",	2, flsort,	OZ(0),	/* (default)	*/
	"/rowner",	3, flsort,	OZ(0),
	"/rpath",	3, flsort,	OZ(0),
	"/rrevised",	3, flsort,	OZ(0),
	"/rsize",	3, flsort,	OZ(0),
	"/rtype",	3, flsort,	OZ(0),
	"/ruser",	3, flsort,	OZ(0),
	"/rvers",	3, flsort,	OZ(0),
	"/rweek",	3, flsort,	OZ(0),
	"/rxab",	3, flsort,	OZ(0),

/*
 * Symbol table for commands which (may) require DCL arguments or options.
 * The command parser performs some checking, to ensure that the command
 * looks something like it is supposed to.
 */
	"append",	2, flcopy,	v_M_1,		0,
	"browse",	1, flmore,	v_1,		0,
	"copy",		2, flcopy,	v_M_1,		0,
	"create",	2, flcrea,	v_1d,		0,
	"delete",	3, fldlet,	v_M0,		0,
#ifdef	DEBUG
	"dump",		2, fldump,	v_OMIT,		0,
#endif
	"edit",		1, fledit,	v_M,		read_dft,
	"find",		1, flfind,	v_1a,		read_dft,
	"inspect",	1, flscan,	v_1c,		read_dft,
	"lsedit",	1, fledit,	v_M,		read_dft,
	"next",		1, flfind,	v_OMIT,		0,
	"nfind",	2, flfind,	v_1a,		read_dft,
	"print",	2, flnoid,	v_M,		".LIS",
	"/protection",	3, flprot,	v_1b,		0,
	"protect",	3, flprot,	v_1b,		0,
	"purge",	3, fldlet,	v_M0,		0,
	"read",		1, flread,	v_1c,		read_dft,
	"rename",	3, flrnam,	v_1_1,		0,
	"shell",	2, flescp,	v_OMIT,		0,
	"spawn",	2, flescp,	v_OMIT,		0,
	"submit",	2, flnoid,	v_1,		".COM",
	"update",	1, flread,	v_1c,		read_dft,
	"verify",	2, flscan,	v_1c,		read_dft,
	"view",		1, fledit,	v_M,		read_dft
	};

/* <dircmd>:
 * Loop, reading characters and executing commands until we either delete the
 * last file in the list, or we receive a QUIT-command.
 */
void	dircmd (void)
{
	int	command,
		curfile	= 0;

	while (! dds_last (&curfile))
	{
		command = dircmd_getc (&curfile);

		if (	(command == RETRIEVE)
		||	(isascii(command) && isprint(command)) )
			dircmd_read (&curfile, command, "");
		else if (command != DO_NEXT)
			dircmd_kcmd (&curfile, command);
	}
}

/* <dircmd_kcmd>:
 * Given that a legal keypad or control code was input, complete the processing:
 */
static
void	dircmd_kcmd (int *curfile_, int command)
{
	register int	j;
	register int	gold	= (command == GOLDKEY);
	register char	*s_;
	char	cmdbfr[CRT_COLS];

	if (gold)
	{
		while (command == GOLDKEY)
			command = dircmd_getc (curfile_);
	}

	/*
	 * Check for a GOLD followed by a non-keypad/non-control key:
	 */
	if (! (is_PAD(command) || (command < ' ')))
		sound_alarm();		/* ...if so, ignore the key	*/

	j = toascii(command) + (gold ? 128 : 0);
	if (s_ = keydefs[j].string)
	{
		if (keydefs[j].flags & 2)
			dircmd_read (curfile_, EOS, s_);
		else
		{
			strcpy (cmdbfr, s_);
			dircmd_doit (curfile_, cmdbfr, FALSE);
		}
	}
	else
	{
		set_beep ();	/* Set flag indicating error	*/
		sound_alarm ();
	}
	if (multi_quit <= 0)	dds_index (*curfile_);
}

/*
 * Set/Query the selected-file index.  (A "-2" will not alter the value.)
 * We use 'dds_line' to alter the display to show the selected/deselected file.
 */
int	dircmd_select (int val)
{
	if (val >= -1)
	{
	int	was_sel	 = selected;
		selected = val;
		if (was_sel != selected)
		{
			if (was_sel  >= 0)	dds_line (was_sel);
			if (selected >= 0)	dds_line (selected);
		}
	}
	return (selected);
}

/* <dircmd_init>:
 * Initialize this module
 */
void	dircmd_init (void)
{
	edtcmd_init();
	dircmd_dirflg (TRUE);		/* forward-direction	*/
	selected  = -1;			/* Index of fixed-point	*/
	multi_quit= 0;			/* No "quit" in progress*/

	keydefs	  = calloc (256, sizeof(KEYDEFS));
	dircmd_keydefs (CTL('B'),	0,	"/backward");
	dircmd_keydefs (CTL('E'),	0,	"/end");
	dircmd_keydefs (CTL('F'),	0,	"/forward");
	dircmd_keydefs (CTL('G'), 	0,	"/width");
	dircmd_keydefs (CTL('H'),	0,	"1-");
	dircmd_keydefs (CTL('M'),	0,	"1+");
	dircmd_keydefs (CTL('N'),	0,	"next");
	dircmd_keydefs (CTL('P'),	0,	"protect");
	dircmd_keydefs (CTL('R'),	0,	"update /n./t;/v");
	dircmd_keydefs (CTL('T'),	0,	"/top");
	dircmd_keydefs (CTL('V'),	0,	"verify");
	dircmd_keydefs (CTL('Z'),	0,	"quit");

	dircmd_keydefs (padUP,		0,	"1-");
	dircmd_keydefs (padUP,		1,	"/backward 1");
	dircmd_keydefs (padDOWN,	0,	"1+");
	dircmd_keydefs (padDOWN,	1,	"/forward 1");

	dircmd_keydefs (HELPKEY,	0,	"?help");
	dircmd_keydefs (HELPKEY,	1,	"?help");
	dircmd_keydefs (padPF3, 	0,	"next");
	dircmd_keydefs (padPF3, 	1+2,	"find ");
	dircmd_keydefs (padPF4, 	0,	"/cleft");
	dircmd_keydefs (padPF4, 	1,	"/cright");
	dircmd_keydefs (pad0,		0,	"/sname");
	dircmd_keydefs (pad0,		1,	"/rname");
	dircmd_keydefs (pad1,		0,	"/stype");
	dircmd_keydefs (pad1,		1,	"/rtype");
	dircmd_keydefs (pad2,		0,	"/sdate");
	dircmd_keydefs (pad2,		1,	"/rdate");
	dircmd_keydefs (pad3,		0,	"/ssize");
	dircmd_keydefs (pad3,		1,	"/rsize");
	dircmd_keydefs (pad4,		0,	"/forward 0");
	dircmd_keydefs (pad4,		1,	"/end");
	dircmd_keydefs (pad5,		0,	"/backward 0");
	dircmd_keydefs (pad5,		1,	"/top");
	dircmd_keydefs (pad6,		0,	"?time");
	dircmd_keydefs (pad6,		1,	"/time");
	dircmd_keydefs (pad7,		0,	"edit");
	dircmd_keydefs (pad7,		1,	"view");
	dircmd_keydefs (pad8,		0,	"/page");
	dircmd_keydefs (pad9,		0,	"browse");
	dircmd_keydefs (pad9,		1,	"browse/mark/over:3");
	dircmd_keydefs (padMINUS,	0+2,	"-");
	dircmd_keydefs (padMINUS,	1+2,	"-");
	dircmd_keydefs (padCOMMA,	0,	"?memory");
	dircmd_keydefs (padDOT,		0,	"/mark");
	dircmd_keydefs (padDOT,		1,	"/nomark");
	dircmd_keydefs (padENTER,	0,	"1+");
	dircmd_keydefs (padENTER,	1,	"1-");

	strcpy (read_dft, dirent_dft());
}

/* <dircmd_keydefs>:
 * Copy a new key-definition into the 'keydefs[]' table.  We map the control
 * characters to themselves, and the keypad codes down to ASCII.  Use the
 * gold-key flag to drive a shift into 128-255.
 */
static
void	dircmd_keydefs (int code, int flags, char *string)
{
	register int c = toascii(code) + ((flags & 1) ? 128 : 0);
	keydefs[c].flags = flags & ~1;
	keydefs[c].string = string;
}

/* <dircmd_getc>:
 * Read a single command (keystroke-level), suppressing characters such
 * as control/S and control/Q which may be passed back by 'getpad()',
 * which are not used as single-key commands by "FLIST".
 *
 * In particular, absorb all incoming nulls, since this code is used (DO_NEXT)
 * to tell the higher-level routines to continue reading command-input.
 */
static
int	dircmd_getc (int *curfile_)
{
	int	command;

	for (;;)
	{
		switch (command = edtcmd_get())
		{
		case CTL('U'):
		case CTL('X'):
		case CTL('C'):			/* Equate ^C, ^Y to ^U	*/
		case CTL('Y'):
		case ' ':
		case '\t':
		case '\n':
		case RUBOUT:
		case padLEFT:
		case padRIGHT:
			sound_alarm ();		/* Illegal context	*/
		case CTL('Q'):
		case CTL('S'):
		case 0:				/* == DO_NEXT		*/
			break;
		case '-':			/* Gives 1 level w/o keypad */
			command = RETRIEVE;
		default:
			goto got_command;
		}
	}
got_command:
	/*
	 * If a message was pending, wipe it off with the first character
	 * of a new command, except:
	 *	a) if we had an error message, and the current character is
	 *	   a repeat of the offending one, or
	 *	b) if we had an informational message (triggered by a keypad
	 *	   or control-key) and it is repeated.
	 *
	 * patch: should suppress wipeoff if the key is GOLD.
	 */
	if ((command == RETRIEVE) || didbeep() || didwarn())
	{
		register
		int	repeated = edtcmd_last(command) &&
					(iscntrl(command) ||
					!isascii(command) ),
/********/
			isel	= dircmd_select(-2);

		if (repeated
		&&  (isel >= 0)
		&&  (isel != *curfile_)
		&&  (command == CTL('D'))) /* PATCH !! */
		{
			char	iname[MAX_PATH],
				oname[MAX_PATH];
			dirent_glue (iname, FK_(isel));
			dirent_glue (oname, FK_(*curfile_));
			acpcopy (3, iname, oname);
			dirent_chk2 (*curfile_); /* Re-read this entry */
			command = DO_NEXT;
		}
		else
/********/
		if (!repeated)
		{
			clrwarn();		/* ...clear it off screen */
			dds_index (*curfile_);
		}
	}
	return (command);
}

/* <dircmd_read>:
 * Read/edit a "visible" command.  After editing it, execute it:
 */
static
void	dircmd_read (int *curfile_, int command, char *original)
{
	int	line	= *curfile_ - crt_top();

	clrwarn();
	dds_index (*curfile_);		/* clear out any prior messages */

	if (command == '-')	command = RETRIEVE;
	dircmd_doit (
		curfile_,
		edtcmd (command,	/* Initial command-char		*/
			nullC,		/* No special delimiter-list	*/
			7,		/* Highlight+lowercase+trim	*/
			line,		/* ...on which to receive	*/
			dirent_ccol(),	/* beginning command-column	*/
			"FLIST",	/* patch:'whoami': help-entry	*/
			original,
			crtvec[line]),
		TRUE);			/* Save resulting command	*/
}

/* <dircmd_doit>:
 * Execute a command string.  Return a continuation command-code.
 */
static
void	dircmd_doit (int *curfile_, char *cmdbfr, int history)
{
DCLARG	*xdcl_	= nullS(DCLARG);	/* 'dirarg()' parsed list	*/
tDIRCMD((*DO_func)) = 0;		/* external function to execute	*/
int	cmdlen	= 0,			/* length of parsed keyword	*/
	cmdnum	= EXTRN_CMD,		/* index to 'vcmd2', if used	*/
	j,
	prefix	= 0,			/* nonzero iff prefix operator	*/
	k1st	= 0,			/* != 'prefix' if SET/SHOW	*/
	omit	= FALSE,		/* TRUE iff "/o" prefix used	*/
	non_DCL	= FALSE,		/* TRUE iff non-DCL arguments	*/
	temp_fix= FALSE,		/* TRUE iff '//sort'		*/
	sort_cmd= FALSE,		/* '/' prefix only on sort-cmd	*/
	known_cmd = FALSE;
char	*fullname = "",
	*xcmd_	= nullC,		/* 'dirarg()' substituted string*/
	tmp_bfr	[MAX_PATH],		/* temporary buffer		*/
	err_msg	[MAX_PATH];

	clrwarn();			/* Permit current command to continue */
	flist_log ("$ %.80s", cmdbfr);

	if (!*cmdbfr)
		return;		/* Ignore ^U, etc.	*/

	/*
	 * Translate & execute the command.
	 *
	 * The first "token" on the line is the command keyword.  It may
	 * contain a leading "/" for punctuation, but otherwise must be a
	 * legal VMS filespec (i.e., the most general form of a command).
	 * (This rule is used to find the beginning of the argument
	 * substitution list).
	 *
	 * A "/" before a sort command (e.g., "//sn") will cause a flag to
	 * be set selecting a fixed-point sort for that command only.  This
	 * is an extension provided only for sort-commands.
	 *
	 * A "/o" before any non-sorting command sets a flag which disables
	 * the normal practice of using the current file-entry as an implicit
	 * argument.
	 */
	cmdlen	= strtrim (cmdbfr);
	prefix	= 0;
	omit	= FALSE;
	if (cmdbfr[0] == '/')
	{
		if (cmdbfr[1] == '/')
			prefix++;		/* Allow "//sort"	*/
		else if (isdigit(cmdbfr[1]) || isspace(cmdbfr[1]))
			prefix++;		/* Allow "/ number"	*/
		else if (cmdbfr[1] == 'o' && !isalpha(cmdbfr[2]))
		{
			prefix	+= 2;
			omit	= TRUE;
		}
		if (prefix)	/* Allow blanks after prefix-text	*/
			prefix += strskps(&cmdbfr[prefix]) - &cmdbfr[prefix];
	}

	k1st	= prefix;
	if (strchr ("/@?", cmdbfr[k1st]))		k1st++;
	j	= (dclarg_spec (&cmdbfr[k1st], nullC) - &cmdbfr[k1st]) + k1st;
	cmdlen	= j - prefix;

	/*
	 * Lookup the command-verb.
	 */
	strncpy (tmp_bfr, &cmdbfr[prefix], cmdlen);
	tmp_bfr[cmdlen] = EOS;
	err_msg[0] = EOS;

	if (xcmd_ = dirarg (&xdcl_, *curfile_,
			&cmdbfr[prefix + cmdlen], tmp_bfr, omit) )
	{
		/*
		 * Check 'xcmd_', since it is lowercased, with the complete
		 * command-verb.  We must get an index of at least 2, to skip
		 * over the SET/SHOW verbs.
		 */
		j = strlen(xdcl_->dcl_text);
		if (strchr ("/?", *xcmd_))	j++;
		cmdnum = debrief (err_msg, vcmd2, sizeof(VCMD2), SIZEOF(vcmd2),
				xcmd_, j);
		if (cmdnum >= 2)
		{
			if (!(DO_func = vcmd2[cmdnum].v_func))
				DO_func = not_impl;
			fullname  = vcmd2[cmdnum].v_full;
			non_DCL	  = vcmd2[cmdnum].v_mode & v_nonDCL;
			sort_cmd  = (*fullname == '/')
				 && strchr("rs", fullname[1]);
			if ((sort_cmd && prefix) || (vcmd2[cmdnum].v_mode & v_NAME))
			{
				dirent_glue (tmp_bfr, FK_(*curfile_));
				flist_log ("! %.80s", tmp_bfr);
			}
			if (dirchk (xcmd_, xdcl_, vcmd2[cmdnum].v_mode, TRUE))
				known_cmd = TRUE;
			else
				cmdnum	= ERROR_CMD;
		}
		else
			cmdnum	= EXTRN_CMD;
	}

	cmdlen	+= prefix;	/* count the first '/' as part of cmd	*/
	if (prefix && !omit)	/* Did I have a leading '/'?		*/
	{
		if (non_DCL && sort_cmd)
			temp_fix = TRUE;
		else if (!isdigit(cmdbfr[prefix]))
		{
			warn ("'/'-prefix is legal only on sort-command");
			cmdbfr[0] = EOS;
		}
	}

	/*
	 * Common exit point for all completed command-input:
	 */
	dds_line (*curfile_);	/* refresh the screen after receipt	*/

	/*
	 * A number directs "FLIST" to move the cursor to the specified
	 * file-index a la FLIST.  We permit a prefix '/' (actually ignore
	 * all prefixes), and a trailing sign, for relative movement.
	 */
	if (isdigit(cmdbfr[prefix]))
	{
	register char	*d_ = &cmdbfr[cmdlen];
	register int	sign = 0;
	int	inx = dds_inx1 (*curfile_),
		val;

		if ((cmdlen >= 1) && (d_[-1] == '-')) {
			d_--;
			cmdlen--;
		} else
			d_ = strskps(d_);
		if (*d_)
		{
			if (! strcmp (d_, "+"))		sign = 1;
			else if (! strcmp (d_, "-"))	sign = -1;
			else	warn ("<number> uses no arguments: %s", d_);
			cmdbfr[cmdlen] = EOS;
		}
		if (! didwarn ())
		{
			if (scanint(&cmdbfr[prefix], &val) == &cmdbfr[cmdlen])
			{
				if (val == 1 && sign)
					flist_move (curfile_, *curfile_,
						sign > 0 ? DDS_D_1 : DDS_U_1);
				else
				{
					if (sign)	inx += (sign * val);
					else		inx = val;
					*curfile_ = dds_inx2 (inx);
				}
			}
			else
				warn ("Illegal number: %s\n", &cmdbfr[prefix]);
		}
		cmdbfr[0] = EOS;	/* Don't save for retrieval */
		cmdlen	  = 0;
	}
	else if (!strcmp(cmdbfr, "/"))	/* Force current entry to top-of-screen */
	{
		cmdbfr[0] = EOS;
		dds_scroll (crt_end() + (*curfile_ - crt_top()));
	}
	if (history) cmdstk_put (cmdbfr);

	/*
	 * If 'known_cmd', then we found the command name in one of the tables.
	 */
	if (known_cmd && !didwarn())
	{
		if (cmdnum >= 2)
			(*DO_func)(curfile_, xcmd_, xdcl_, temp_fix, history);
	}
	/* patch:
	 * If not found in symbol table, simply make a nice error message.
	 * Later, should consider trying to spawn an appropriate process to
	 * execute non-screenable commands.
	 */
	else if (cmdbfr[0] && !didwarn())
	{
		if (*err_msg)
			warn ("%s", err_msg);
		else if (cmdlen)
		{
			cmdbfr[cmdlen] = EOS;
			warn ("Unknown command '%.30s'.  Type '/help'.",
				(cmdbfr[0] == '/' ? &cmdbfr[prefix]
						  : cmdbfr) );
		}
		else
			warn ("Illegal command: '%.30s'", cmdbfr);
	}

	dds_done();	/* clear the "Working..." message, if any */

	if (xdcl_)	freelist (xdcl_);
	if (xcmd_)	cfree (xcmd_);
}

/* <dircmd_ask>:
 * This procedure may be called by a visible-command to verify an action.
 * Prompt for:
 *	G - (2) go, selecting Y (if loop) for all successive prompts
 *	Y - (1) yes
 *	N - (0) disallow this particular selection
 *	Q - (-1) same as N, but exit loop, if any
 */
int	dircmd_ask (int curfile, char *msg_)
{
	int	reply	= 256,
		rline	= curfile - crt_top();
	char	*s_,
		tell	[80];
	static	char	ok[]	= "QNYG";

	sprintf (tell, "%.60s (G,Y/N,Q): ", msg_);
	edtcmd_crt (tell, crtvec[rline], TRUE, dirent_ccol(), rline, strlen(tell)-1);
	for (;;)
	{
		for (reply = 256; (!isascii(reply) || !isprint(reply));)
			reply = edtcmd_get ();
		if (s_ = strchr (ok, _toupper(reply)))	break;
		sound_alarm();		/* warn if incorrect reply	*/
	}

	dds_line (curfile);		/* refresh display */

	return ((s_-ok)-1);
}

/* <dircmd_vcmd2>:
 * Do table lookup to find FLIST's full-name for a "visible" command.  This
 * lookup is used only for the VCMD2-entries (which can specify arguments,
 * hence must be parsed).  Because the command name may be found via lookup
 * in the logical-symbol table, this procedure may be called more than once
 * to interpret a command.
 *
 * If the name is not found in FLIST's table, assume it is the name of an
 * external command.
 */
int	dircmd_vcmd2 (char *cmd_, int len)
{
	int	j;

	for (j = 0; j < SIZEOF(vcmd2); j++)
	{
		if ((len >= vcmd2[j].v_len)
		&&  !strncmp (cmd_, vcmd2[j].v_full, len))
			return (j);
	}
	return (EXTRN_CMD);	/* not found, return an illegal index	*/
}

/* <dircmd_full>:
 * Return a pointer to a specified component of 'vcmd2[]'.  This is needed
 * in 'dirarg', which does a lookup into 'vcmd2[]' before testing for a
 * logical-symbol.
 */
VCMD2*	dircmd_full (int cmdnum)
{
	return (&vcmd2[cmdnum]);	/* patch: limits on 'cmdnum' ?	*/
}

/* <dircmd_dirflg>:
 * Return the direction-flag to make it public:
 */
int	dircmd_dirflg (int flag)
{
	if (flag > 0)		dir_flg = TRUE;
	else if (flag == 0)	dir_flg = FALSE;
	return (dir_flg);
}

/* <not_impl>:
 * Print an appropriate error message for unimplemented routines:
 */
tDIRCMD(not_impl)
{
	warn ("?NOT implemented: %.60s", xcmd_ ? xcmd_ : xdcl_->dcl_text);
}
