/*
 *		Extended (M-X) commands.
 */
#include	"def.h"

/*
 * This function modifies the keyboard
 * binding table, by adjusting the entries in the
 * big "bindings" array. Most of the grief deals with the
 * prompting for additional arguments.
 */
extern KEY *kbdnext ;
/*ARGSUSED*/
bindtokey(f, n, k) {
	register int	s;
	register SYMBOL	*sp;
	int		c;
	char		xname[NXNAME];

	if (kbdmop == NULL)
		ewprintf("Set key globally: ") ;
	c = (int) getkey(0);
	if ((s=eread("Set key %c to command: ", xname, NXNAME, EFNEW|EFFUNC,
#ifdef VARARGS
		     c
#else
		     (char *) &c, (char *) NULL
#endif
		     )) != TRUE)
		return (s);
	if ((sp=symlookup(xname)) == NULL) {
		ewprintf("[No match]");
		return (FALSE);
	}
	binding[(KEY) c] = sp;			/* rebind new.		*/
	return (TRUE);
}

/*
 * User function to unbind keys. Just call the unbind we already have. 
 */
/*ARGSUSED*/
unsetkey(f, n, k) {
	register KEY	key;
	
	if (kbdmop == NULL) ewprintf("Unset key globally: ") ;
	key = getkey(0);
	if (key == (KCTRL|'G') || key == (KCTLX|KCTRL|'G')
	|| key == (KMETA|KCTRL|'G')) {
		(VOID) ctrlg(FALSE, 1, KRANDOM);
		return ABORT;
	}
	binding[key] = NULL;
	return TRUE;
}

/*
 * Extended command. Call the message line
 * routine to read in the command name and apply autocompletion
 * to it. When it comes back, look the name up in the symbol table
 * and run the command if it is found and has the right type.
 * Print an error if there is anything wrong.
 */
/*ARGSUSED*/
extend(f, n, k) {
	register SYMBOL	*sp;
	register int	s;
	char		xname[NXNAME];

	if (f == FALSE)
		s = eread("M-x ", xname, NXNAME, EFNEW|EFFUNC
#ifndef VARARGS
			 , (char *) NULL
#endif
			 ) ;
	else
		s = eread("%d M-x ", xname, NXNAME, EFNEW|EFFUNC, 
#ifdef	VARARGS
			 n
#else
			 (char *) &n, (char *) NULL
#endif
			 ) ;
	if (s != TRUE) return (s);
	if ((sp=symlookup(xname)) != NULL)
		return ((*sp->s_funcp)(f, n, KRANDOM));
	ewprintf("[No match]");
	return FALSE;
}

/*
 * Read a key from the keyboard, and look it
 * up in the binding table. Display the name of the function
 * currently bound to the key. Say that the key is not bound
 * if it is indeed not bound, or if the type is not a
 * "builtin". This is a bit of overkill, because this is the
 * only kind of function there is.
 */
/*ARGSUSED*/
desckey(f, n, k) {
	register SYMBOL	*sp;
	register KEY	c;

	if (kbdmop == NULL) ewprintf("Describe key briefly: ");
	c = getkey(0);
	if (kbdmop != NULL) return TRUE;
	if ((sp=binding[c]) == NULL)
		ewprintf("%c is undefined", (int) c);
	else
		ewprintf("%c runs the command %s", (int) c, sp->s_name);
	return (TRUE);
}

/*
 * This function creates a table, listing all
 * of the command keys and their current bindings, and stores
 * the table in the standard pop-op buffer (the one used by the
 * directory list command, the buffer list command, etc.). This
 * lets MicroEMACS produce it's own wall chart. The bindings to
 * "ins-self" are only displayed if there is an argument.
 */
/*ARGSUSED*/
wallchart(f, n, k) {
	register int	key;
	register SYMBOL	*sp;
	register char	*cp1;
	register char	*cp2;
	BUFFER		*bp;
	char		buf[64];

	bp = bfind("*Help*", TRUE);
	if (bclear(bp) != TRUE)			/* Clear it out.	*/
		return TRUE;
	for (key=0; key<NKEYS; ++key) {		/* For all keys.	*/
		sp = binding[key];
		if (sp != NULL
		&& (f!=FALSE
		|| strcmp(sp->s_name, "self-insert-command")!=0)) {
			keyname(buf, key);
			cp1 = &buf[0];		/* Find end.		*/
			while (*cp1 != 0)
				++cp1;
			while (cp1 < &buf[32])	/* Goto column 32.	*/
				*cp1++ = ' ';				
			cp2 = sp->s_name;	/* Add function name.	*/
			while (*cp1++ = *cp2++)
				;
			if (addline(bp, buf) == FALSE)
				return (FALSE);
		}
	}
	return popbuf(bp) == NULL ? FALSE : TRUE;
}

#ifdef	STARTUP
/*
 * Define the commands needed to do startup-file processing.
 * This code is mostly a kludge just so we can get startup-file processing.
 *
 * If you're serious about having this code, you should rewrite it.
 * To wit: 
 *	It has lots of funny things in it to make the startup-file look
 *	like a GNU startup file; mostly dealing with parens and semicolons.
 *	This should all vanish.
 *
 *	It uses the same buffer as keyboard macros. The fix is easy (make
 *	a new function "execmacro" that takes a pointer to char and
 *	does what ctlxe does on it. Make ctlxe and excline both call it.)
 *	but would slow down the non-micro version.
 *
 * We define eval-expression because it's easy. It's pretty useless,
 * since it duplicates the functionality of execute-extended-command.
 * All of this is just to support startup files, and should be turned
 * off for micros.
 */

/*
 * evalexpr - get one line from the user, and run it. Identical in function
 *	to extend, but easy.
 */
/*ARGSUSED*/
evalexpr(f, n, k) {
	register int	s;
	char		exbuf[NKBDM];

	if ((s = ereply("Eval: ", exbuf, NKBDM)) != TRUE)
		return s;
	return excline(exbuf);
}
/*
 * evalbuffer - evaluate the current buffer as line commands. Useful
 *	for testing startup files.
 */
/*ARGSUSED*/
evalbuffer(f, n, k) {
	register LINE	*lp;
	register BUFFER	*bp = curbp;
	register int	s;
	static char	excbuf[NKBDM];
	char		*strncpy();

	for (lp = lforw(bp->b_linep); lp != bp->b_linep; lp = lforw(lp)) {
		if (llength(lp) >= NKBDM + 1) return FALSE ;
		(VOID) strncpy(excbuf, ltext(lp), NKBDM);
		if ((s = excline(excbuf)) != TRUE) return s;
	}
	return TRUE;
}
/*
 * evalfile - go get a file and evaluate it as line commands. You can
 *	go get your own startup file if need be.
 */
/*ARGSUSED*/
evalfile(f, n, k) {
	register int	s;
	char		fname[NFILEN];

	if ((s = ereply("Load file: ", fname, NFILEN)) != TRUE)
		return s;
	return load(fname);
}

/*
 * load - go load the file name we got passed.
 */
load(fname) char *fname; {
	register int	s;
	char		excbuf[NKBDM];

	if (((s = ffropen(fname)) == FIOERR) || (s == FIOFNF))
		return FALSE;
	while ((s = ffgetline(excbuf, NKBDM)) == FIOSUC)
		if (excline(excbuf) != TRUE) break;
	(VOID) ffclose();
	return s == FIOEOF;
}
int myupper(c)
int c ;
{
   if ('a'<=c&&c<='z')
      return(c - ('a'-'A')) ;
   else
      return(c) ;
}
char *skipwhite(s)
char *s ;
{
   while (*s && *s <= ' ')
      s++ ;
   return(s) ;
}
/*
 * excline - run a line from a load file or eval-expression.
 * Now broken into two pieces; the piece which gets the
 * line into the macro buffer starting at kbdnext, and the
 * part which decides what to do with it.
 *
 *   This has been rewritten considerably.  Now, everything
 *   must be typed explicitly, just as if it were coming in
 *   from the keyboard.  Thus, you might have:
 *
 *   ^U 10 $X 'kill-line'
 *
 *   Anything in quotes is taken verbatim, including spaces.
 *   A ^ is combined with the preceding character into a control
 *   character.  A \nnn is turned into that character.  White
 *   space is ignored outside of quotes.  A \" gets turned into
 *   a single quote.  $ gets turned into a meta.  Note that
 *   we currently allow macros to be defined by this.  The end of
 *   a ' string gets turned into a null, as it is then an argument
 *   to something.
 */
/* Don't want to get the objects in isdigit.c just for this */
#define	isdigit(c)	(((c) >= '0') && ((c) <= '9'))
excline(line) register char *line; {
	register char	*funcp, *argp = NULL;
	int		status;
        KEY             *kbdst, *p ;
	int		inquote ;
        int		buckies ;
        int		c ;
        long            param ;

	if (kbdmip > kbdnext)
           kbdnext = kbdmip + 1 ;
	kbdst = kbdnext + 10 ;
        inquote = 0 ;
        param = 0 ;
        for (funcp = line; *funcp !=0 && kbdst < &kbdm[NKBDM-6]; funcp++) {
           buckies = 0 ;
floop:
	   if (!inquote)
	      funcp = skipwhite(funcp) ;
           if (*funcp != 0) {
              while (*funcp == '^' || *funcp == '$') {
                 if (*funcp == '^')
                    buckies |= KCTRL ;
                 else
                    buckies |= KMETA ;
                 funcp++ ;
                 if (!inquote)
                    funcp = skipwhite(funcp) ;
                 if ((c=myupper(*funcp))=='X' && (buckies & KCTRL)) {
                    buckies = KCTLX ;
                    funcp++ ;
                 }
              }
gloop:
              if (buckies) {
                 if (!inquote)
                    funcp = skipwhite(funcp) ;
                 if ((c = myupper(*funcp))) {
                    if (c=='U' && (buckies & KCTRL)) {
                       if (param == 0)
                          param = 4 ;
                       else
                          param <<= 2 ;
                    } else {
                       if (param != 0) {
                          *kbdst++ = (KCTRL | 'U') ;
                          *kbdst++ = param ;
                          param = 0 ;
                       }
                       *kbdst++ = buckies | c ;
                    }
                    funcp++ ;
                    buckies = 0 ;
                    goto floop ;
                 } else {
                    ewprintf("Need alpha after $ or ^") ;
                    return(FALSE) ;
                 }
              }
              if (param != 0) {
                 if (!inquote)
                    funcp = skipwhite(funcp) ;
                 if (isdigit(*funcp)) {
                    param = 0 ;
                    while (isdigit(*funcp)) {
                       param = param * 10 + *funcp++ - '0' ;
                    }
                 }
                 *kbdst++ = (KCTRL | 'U') ;
                 *kbdst++ = param ;
                 param = 0 ;
                 buckies = 0 ;
                 goto floop ;
              }
              if (*funcp == '"' || *funcp == '\'') {
                 if (*funcp == inquote) {
                    if (inquote == '\'')
                       *kbdst++ = 0 ;
                    inquote = 0 ;
                 } else if (inquote == 0) {
                    inquote = *funcp ;
                 } else {
                    *kbdst++ = *funcp ;
                 }
              } else if (*funcp == '\\') {
                 funcp++ ;
                 if (*funcp == 'F' || *funcp == 'f') {
                    funcp++ ;
                    c = 0 ;
                    while (isdigit(*funcp))
                       c = 10 * c + *funcp++ - '0' ;
                    c += KFIRST ;
                    if (c > KLAST)
                       c = KFIRST ;
                    *kbdst++ = c ;
                 } else if ('0' <= *funcp && *funcp <= '7') {
                    c = 0 ;
                    while (isdigit(*funcp))
                       c = 10 * c + *funcp++ - '0' ;
                    *kbdst++ = c ;
                 } else {
                    *kbdst++ = *funcp ;
                 }
              } else {
                 *kbdst++ = *funcp ;
              }
           }
           if (*funcp == 0)
              break ;
        }
        if (*funcp != 0)
           return(FALSE) ;
        *kbdst++ = (KEY) (KCTLX|')');
        *kbdst++ = (KEY) (KCTLX|')');
        *kbdst++ = '\0';
        status = ctlxe(FALSE, 1, KMACCUR);
	return status;
}
#endif	STARTUP
