
/*
 * COMMAND.C
 *
 *	(C)Copyright 1987 by Matthew Dillon, All Rights Reserved
 *
 * )c		     single character (typing)
 * 'c                single character (typing)
 * `string'          string of characters w/ embedded `' allowed!
 * (string)             same thing w/ embedded () allowed!
 * \c		     override
 *
 * name arg arg      command name. The arguments are interpreted as strings
 *		     for the command.
 *
 * $scanf	     macro insert scanf'd variable
 * $filename	     macro insert current file name
 *
 * Any string arguments not part of a command are considered to be typed
 * text.
 */

#include "defs.h"
#include <stdio.h>

#if AREXX
extern int foundcmd;	   /* control for implicit ARexx macro invocation   */
extern int cmderr;	   /* global command error flag for do_rexx()'s use */
#endif

#define CF_COK	1   /*	Can be executed while in command line mode	*/
#define CF_PAR	2   /*	ESCIMM special flag.. save rest of command line */
		    /*	so it can be executed after user entry		*/

#define CF_ICO	4   /*	OK to execute if iconified, else uniconify first*/

extern char *breakout();

typedef struct {
   char *name;	    /* command name	  */
   ubyte args;
   ubyte flags;
   int (*func)();   /* function           */
} COMM;

extern int  do_map(),       do_unmap(),     do_up(),        do_down(),
	    do_left(),      do_right(),     do_return(),    do_bs(),
	    do_del(),       do_esc(),       do_downadd(),   do_lastcolumn(),
	    do_firstcolumn(),do_edit(),     do_tab(),       do_backtab(),
	    do_save(),      do_saveas(),    do_deline(),    do_insline(),
	    do_top(),       do_bottom(),    do_source(),    do_firstnb(),
	    do_quit(),      do_find(),      do_page(),      do_savetabs(),
	    do_split(),     do_goto(),      do_screentop(), do_screenbottom(),
	    do_join(),      do_repeat(),    do_tabstop(),   do_insertmode(),
	    do_block(),     do_bdelete(),   do_bcopy(),     do_bmove(),
	    do_bsave(),     do_wleft(),     do_wright(),    do_remeol(),
	    do_savemap(),   do_toggle(),    do_if(),        do_tlate(),
	    do_bsource(),   do_findr(),     do_findstr(),   do_newwindow(),
	    do_windowparm(),do_resize(),    do_margin(),    do_wordwrap(),
	    do_reformat(),  do_execute(),   do_chfilename(),do_scrollup(),
	    do_scrolldown(),do_recall(),    do_scanf(),     do_iconify(),
	    do_tomouse(),   do_refs(),      do_arpload(),   do_arpsave(),
	    do_arpinsfile(),do_setfont(),   do_ignorecase();

extern int  do_menu(), do_menuclear(), do_menuadd(), do_menudel(),
	    do_menudelhdr(), do_menuon(), do_menuoff();

extern int  do_null(), do_rx();

extern int  do_pushmark(),  do_popmark(),   do_swapmark(),  do_purgemark(),
	    do_ping(),      do_pong(),      do_undo();

#if AREXX
extern int  do_rx(),        do_rx1(),       do_rx2();
#endif

/*============================================================================*/

/*
 *  WLEFT/WRIGHT will check command line mode themselves, and thus can
 *  be marked flags=1 even though they can change the line number.
 *
 *  No more than 255 commands may exist unless you change the type of hindex[]
 *
 *  Command names MUST be sorted by their first character
 */

unsigned char hindex[26];   /*	alpha hash into table	*/

	/*	  args flags	*/

COMM Comm[] = {
    "arpinsfile",    0,      0, do_arpinsfile,
    "arpload",       0,      0, do_arpload,
    "arpsave",       0,      0, do_arpsave,
    "back",          0, CF_COK, do_bs,
    "backtab",       0, CF_COK, do_backtab,
    "bcopy",         0,      0, do_bcopy,
    "bdelete",       0,      0, do_bdelete,
    "block",         0,      0, do_block,    /* checks com name for mode */
    "bmove",         0,      0, do_bmove,
    "bottom",        0,      0, do_bottom,
    "bs",            0, CF_COK, do_bs,
    "bsave",         1, CF_COK, do_bsave,
    "bsource",       0,      0, do_bsource,
    "chfilename",    1,      0, do_chfilename,
    "del",           0, CF_COK, do_del,
    "deline",        0,      0, do_deline,
    "down",          0,      0, do_down,
    "downadd",       0,      0, do_downadd,
    "esc",           0, CF_COK, do_esc,
    "escimm",        1, CF_PAR, do_esc,
    "execute",       1, CF_ICO, do_execute,
    "find",          1,      0, do_find,     /* checks com name for mode */
    "findr",         2,      0, do_findr,    /* checks com name for mode */
    "findstr",       1, CF_COK, do_findstr,  /* checks com name for mode */
    "first",         0, CF_COK, do_firstcolumn,
    "firstnb",       0, CF_COK, do_firstnb,
    "goto",          1,      0, do_goto,
    "height",        1, CF_COK, do_windowparm,
    "iconify",       0, CF_ICO, do_iconify,
    "if",            2, CF_COK, do_if,
    "ifelse",        3, CF_COK, do_if,
    "ignorecase",    1, CF_COK, do_ignorecase,
    "insertmode",    1, CF_COK, do_insertmode,
    "insfile",       1,      0, do_edit,
    "insline",       0,      0, do_insline,
    "join",          0,      0, do_join,
    "last",          0, CF_COK, do_lastcolumn,
    "left",          0, CF_COK, do_left,
    "leftedge",      1, CF_COK, do_windowparm,
    "map",           2, CF_COK, do_map,
    "margin",        1, CF_COK, do_margin,
    "menuon",        0,      0, do_menuon,
    "menuoff",       0,      0, do_menuoff,
    "menuadd",       3,      0, do_menuadd,
    "menudel",       2,      0, do_menudel,
    "menudelhdr",    1,      0, do_menudelhdr,
    "menuclear",     0,      0, do_menuclear,
    "newfile",       1,      0, do_edit,     /* checks com name for mode */
    "newwindow",     0, CF_ICO, do_newwindow,
    "next",          0,      0, do_find,
    "nextr",         0,      0, do_findr,
    "null",          0, CF_COK, do_null,
    "pagedown",      0,      0, do_page,
    "pageset",       1,      0, do_page,
    "pageup",        0,      0, do_page,
    "ping",          1, CF_ICO, do_ping,
    "pong",          1,      0, do_pong,
    "prev",          0,      0, do_find,
    "prevr",         0,      0, do_findr,
    "popmark",       0,      0, do_popmark,
    "purgemark",     0,      0, do_purgemark,
    "pushmark",      0,      0, do_pushmark,
    "quit",          0, CF_ICO, do_quit,
    "recall",        0, CF_COK, do_recall,
    "ref",           0,      0, do_refs,
    "reformat",      0,      0, do_reformat,
    "remeol",        0, CF_COK, do_remeol,
    "repeat",        2, CF_ICO|CF_COK, do_repeat,
    "repstr",        1, CF_COK, do_findstr,
    "resettoggle",   1, CF_COK, do_toggle,
    "resize",        2,      0, do_resize,
    "return",        0, CF_COK, do_return,   /* special meaning in command line mode */
    "right",         0, CF_COK, do_right,
#if AREXX
    "rx",            1,      0, do_rx,       /* explicit ARexx macro invocation      */
    "rx1",           2,      0, do_rx1,      /* explicit, with 1 arg  to ARexx macro */
    "rx2",           3,      0, do_rx2,      /* explicit, with 2 args to ARexx macro */
#endif
    "saveas",        1, CF_ICO|CF_COK, do_saveas,
    "savemap",       1, CF_ICO|CF_COK, do_savemap,  /* checks com name for mode */
    "saveold",       0, CF_ICO|CF_COK, do_save,
    "savesmap",      1, CF_ICO|CF_COK, do_savemap,
    "savetabs",      1, CF_ICO|CF_COK, do_savetabs,
    "scanf",         1, CF_COK, do_scanf,
    "screenbottom",  0,      0, do_screenbottom,
    "screentop",     0,      0, do_screentop,
    "scrollup",      0,      0, do_scrollup,
    "scrolldown",    0,      0, do_scrolldown,
    "setfont",       2,      0, do_setfont,
    "settoggle",     1, CF_COK, do_toggle,
    "source",        1, CF_COK, do_source,
    "split",         0,      0, do_split,
    "swapmark",      0,      0, do_swapmark,
    "tab",           0, CF_COK, do_tab,
    "tabstop",       1, CF_COK, do_tabstop,
    "tlate",         1, CF_COK, do_tlate,
    "tmpheight",     1, CF_COK, do_windowparm,
    "tmpwidth",      1, CF_COK, do_windowparm,
    "toggle",        1, CF_COK, do_toggle,
    "tomouse",       0,      0, do_tomouse,
    "top",           0,      0, do_top,
    "topedge",       1, CF_COK, do_windowparm,
    "unblock",       0,      0, do_block,
    "undo",          0,      0, do_undo,
    "unmap",         1, CF_ICO|CF_COK, do_unmap,
    "up",            0,      0, do_up,
    "while",         2, CF_ICO|CF_COK, do_if,
    "width",         1, CF_COK, do_windowparm,
    "wleft",         0, CF_COK, do_wleft,
    "wordwrap",      1, CF_COK, do_wordwrap,
    "wright",        0, CF_COK, do_wright,
    NULL, 0, 0, NULL
};

init_command()
{
    register short hi;
    register COMM *comm;

    hi = sizeof(Comm)/sizeof(Comm[0]) - 2;
    comm = Comm + hi;

    while (hi >= 0) {
	hindex[comm->name[0] - 'a'] = hi;
	--hi;
	--comm;
    }
}

#define MAXIA	5

do_command(str)
char *str;
{
    register char *arg;
    char *aux1, *aux2;
    char *repstr[MAXIA];
    char quoted;
    short repi = 0;
    register short i, j;
    static int level;

    if (++level > 20) {
	title("Recursion Too Deep!");
	--level;
#if AREXX
	foundcmd = 1;	/* to prevent us from trying an ARexx macro */
#endif
	return(0);
    }
    while (arg = breakout(&str, &quoted, &aux1)) {
	if (quoted) {
	    if (Ep->iconmode)
		uniconify();
	    text_write(arg);
	    goto loop;
	}
	for (i = 0; arg[i]; ++i) {
	    if (arg[i] >= 'A' && arg[i] <= 'Z')
		arg[i] += 'a' - 'A';
	}

	if (arg[0] >= 'a' && arg[0] <= 'z') {
	    register COMM *comm = &Comm[hindex[arg[0]-'a']];
	    for (; comm->name && comm->name[0] == arg[0]; ++comm) {
		if (strcmp(arg, comm->name) == 0) {
#if AREXX
		    foundcmd = 1;
#endif
		    av[0] = (ubyte *)comm->name;
		    for (j = 1; j <= comm->args; ++j) {
			av[j] = (ubyte *)breakout(&str, &quoted, &aux2);
			if (aux2) {
			    if (repi == MAXIA) {
				free(aux2);
				title("Command too complex");
				goto fail;
			    } else {
				repstr[repi++] = aux2;
			    }
			}
			if (!av[j]) {
			    title("Bad argument");
			    goto fail;
			}
		    }
		    av[j] = NULL;   /* end of arglist */
		    if ((comm->flags & CF_COK) || !Comlinemode) {
			if (comm->flags & CF_PAR) {
			    if (Partial)
				free(Partial);
			    Partial = (char *)malloc(strlen(str)+1);
			    strcpy(Partial, str);
			    str += strlen(str);     /*  skip string */
			}
			if (Ep->iconmode && !(comm->flags & CF_ICO))
			    uniconify();
			(*comm->func)(-1);
		    }
		    if (Abortcommand)
			goto fail;
		    goto loop;
		}
	    }
	}

	/* Command not found, check for macro	*/

	{
	    char *str;
	    int ret;
	    if ((str = keyspectomacro(arg)) || (str = menutomacro(arg))) {
		str = (char *)strcpy(malloc(strlen(str)+1), str);
		ret = do_command(str);
		free(str);
#if AREXX
		if (ret) {
		    foundcmd = 1;   /* dunno about this yet for ARexx macros */
		    goto loop;
		}
#else
		if (ret)
		    goto loop;
#endif
		goto fail;
	    }
	}

	/* Command still not found, check for public macro  */
	/* code to be added */

#if AREXX
	do_rxImplied(arg, str);
#else
	title("Unknown Command");
#endif
fail:
	--level;
	while (--repi >= 0)
	    free(repstr[repi]);
	if (aux1)
	    free(aux1);
	return(0);
loop:
	if (aux1)
	    free(aux1);
    }
    --level;
    while (--repi >= 0)
	free(repstr[repi]);
    return(1);
}

do_null()
{
}

do_source()
{
    char buf[256];
    long xfi;
    register char *str;

    if (xfi = xfopen(av[1], "r", 512)) {
	while (xfgets(xfi, buf, 256) >= 0) {
	    if (buf[0] == '#')
		continue;
	    for (str = buf; *str; ++str) {
		if (*str == 9)
		    *str = ' ';
	    }
	    do_command(buf);
	}
	xfclose(xfi);
    } else {
	if (av[0])
	    title("File not found");
    }
}


do_quit()
{
    extern char Quitflag;

    Quitflag = 1;
}

do_execute()
{
    Execute(av[1], NULL, NULL);
}

/*
 * repeat X command
 *
 * Since repeat takes up 512+ stack, it should not be nested more than
 * twice.
 *
 * (if X is not a number it can be abbr. with 2 chars)
 *
 * X =	N     -number of repeats
 *	line  -current line # (lines begin at 1)
 *	lbot  -#lines to the bottom, inc. current
 *	cleft -column # (columns begin at 0)
 *		(thus is also chars to the left)
 *	cright-#chars to eol, including current char
 *	tr    -#char positions to get to next tab stop
 *	tl    -#char positions to get to next backtab stop
 */

#define SC(a,b) ((a)<<8|(b))

do_repeat()
{
    register ubyte *ptr = av[1];
    register unsigned long n;
    char buf1[256];
    char buf2[256];

    breakreset();
    strcpy(buf1, av[2]);
    switch((ptr[0]<<8)+ptr[1]) {
    case SC('l','i'):
	n = text_lineno();
	break;
    case SC('l','b'):
	n = text_lines() - text_lineno() + 1;
	break;
    case SC('c','l'):
	n = text_colno();
	break;
    case SC('c','r'):
	n = text_cols() - text_colno();
	break;
    case SC('t','r'):
	n = text_tabsize()-(text_colno() % text_tabsize());
	break;
    case SC('t','l'):
	n = text_colno() % text_tabsize();
	if (n == 0)
	    n = text_tabsize();
	break;
    default:
	n = atoi(av[1]);
	break;
    }
    while (n > 0) {
	strcpy(buf2, buf1);
	if (do_command(buf2) == 0 || breakcheck()) {
	    Abortcommand = 1;
	    break;
	}
	--n;
    }
}

/*
 *  BREAKOUT()
 *
 *  Break out the next argument.  The argument is space delimited and
 *  might be quoted with `' or (), or single quoted as 'c or )c
 *
 *  Also:	$var	    -variable insertion
 *		^c	    -control character
 */

char *
breakout(ptr, quoted, paux)
register char **ptr;
char **paux;
char *quoted;
{
    register char *str = *ptr;
    char *base;
    short count = 0;
    char opc = 0;
    char clc = 0;
    char immode = 0;
    char isaux = 0;
    char buf[256];
    short di = 0;

    *quoted = 0;
    *paux = NULL;
    while (*str == ' ')
	++str;
    if (!*str)
	return(NULL);

    *ptr = str;
    base = str;
    while (*str) {
	if (immode) {
	    buf[di++] = *str++;
	    continue;
	}
	if (count == 0) {
	    if (*str == ' ')
		break;
	    if (*str == '\'' || *str == ')')
		clc = *str;
	    if (*str == '`') {
		opc = '`';
		clc = '\'';
	    }
	    if (*str == '(') {
		opc = '(';
		clc = ')';
	    }
	}
	if (*str == opc) {
	    ++count;
	    if (str == *ptr) {
		*quoted = 1;
		base = ++str;
		continue;
	    }
	}
	if (*str == clc) {
	    --count;
	    if (count == 0 && *quoted)     /*  end of argument     */
		break;
	    if (str == *ptr && count < 0) {
		immode = 1;
		*quoted = 1;
		base = ++str;
		continue;
	    }
	}
	if (*str == '$') {
	    if (strncmp(str, "$scanf", 6) == 0) {
		isaux = 1;
		strcpy(buf + di, String);
		di = strlen(buf);
		str += 6;
		continue;
	    }
	    if (strncmp(str, "$filename", 9) == 0) {
		isaux = 1;
		strcpy(buf + di, Ep->Name);
		di = strlen(buf);
		str += 9;
		continue;
	    }
	}
	if (*str == '^' && (str[1] & 0x1F)) {
	    ++str;
	    *str &= 0x1F;
	    isaux = 1;
	}
	if (*str == '\\' && str[1]) {
	    ++str;
	    isaux = 1;
	}
	buf[di++] = *str++;
    }
    buf[di++] = 0;
    if (isaux) {
	*paux = malloc(di);
	strcpy(*paux, buf);
	base = *paux;
    }
    if (*str) {             /*  space ended */
	*str = '\0';
	*ptr = str + 1;     /*	next arg    */
    } else {
	*ptr = str;	    /*	last arg    */
    }
    return(base);
}


