[BACK]Return to echo.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / mg

File: [local] / src / usr.bin / mg / echo.c (download)

Revision 1.4, Mon Jan 29 01:58:07 2001 UTC (23 years, 3 months ago) by niklas
Branch: MAIN
CVS Tags: OPENBSD_2_9_BASE, OPENBSD_2_9
Changes since 1.3: +2 -0 lines

$OpenBSD$

/*	$OpenBSD: echo.c,v 1.4 2001/01/29 01:58:07 niklas Exp $	*/

/*
 *	Echo line reading and writing.
 *
 * Common routines for reading and writing characters in the echo line area
 * of the display screen. Used by the entire known universe.
 */

#include "def.h"
#include "key.h"
#ifndef NO_MACRO
#include "macro.h"
#endif /* !NO_MACRO */

#ifdef	__STDC__
#include <stdarg.h>
#else /* __STDC__ */
#include <varargs.h>
#endif /* __STDC__ */

static int	veread		__P((const char *, char *buf, int, int, va_list));
static int      complt		__P((int, int, char *, int));
static int      complt_list	__P((int, int, char *, int));
static VOID     eformat		__P((const char *, va_list));
static VOID     eputi		__P((int, int));
static VOID     eputl		__P((long, int));
static VOID     eputs		__P((char *));
static VOID     eputc		__P((char));
static VOID	free_file_list	__P((LIST *));
static LIST    *copy_list	__P((LIST *));

int             epresf = FALSE;		/* stuff in echo line flag */

/*
 * Erase the echo line.
 */
VOID
eerase()
{
	ttcolor(CTEXT);
	ttmove(nrow - 1, 0);
	tteeol();
	ttflush();
	epresf = FALSE;
}

/*
 * Ask a "yes" or "no" question.  Return ABORT if the user answers the 
 * question with the abort ("^G") character.  Return FALSE for "no" and 
 * TRUE for "yes".  No formatting services are available.  No newline 
 * required.
 */
int
eyorn(sp)
	char *sp;
{
	int	 s;

#ifndef NO_MACRO
	if (inmacro)
		return TRUE;
#endif /* !NO_MACRO */
	ewprintf("%s? (y or n) ", sp);
	for (;;) {
		s = getkey(FALSE);
		if (s == 'y' || s == 'Y')
			return TRUE;
		if (s == 'n' || s == 'N')
			return FALSE;
		if (s == CCHR('G'))
			return ctrlg(FFRAND, 1);
		ewprintf("Please answer y or n.  %s? (y or n) ", sp);
	}
	/* NOTREACHED */
}

/*
 * Like eyorn, but for more important questions.  User must type all of
 * "yes" or "no" and the trainling newline.
 */
int
eyesno(sp)
	char *sp;
{
	int	 s;
	char	 buf[64];

#ifndef NO_MACRO
	if (inmacro)
		return TRUE;
#endif /* !NO_MACRO */
	s = ereply("%s? (yes or no) ", buf, sizeof(buf), sp);
	for (;;) {
		if (s == ABORT)
			return ABORT;
		if (s != FALSE) {
#ifndef NO_MACRO
			if (macrodef) {
				LINE	*lp = maclcur;

				maclcur = lp->l_bp;
				maclcur->l_fp = lp->l_fp;
				free((char *)lp);
			}
#endif /* !NO_MACRO */
			if ((buf[0] == 'y' || buf[0] == 'Y')
			    && (buf[1] == 'e' || buf[1] == 'E')
			    && (buf[2] == 's' || buf[2] == 'S')
			    && (buf[3] == '\0'))
				return TRUE;
			if ((buf[0] == 'n' || buf[0] == 'N')
			    && (buf[1] == 'o' || buf[0] == 'O')
			    && (buf[2] == '\0'))
				return FALSE;
		}
		s = ereply("Please answer yes or no.  %s? (yes or no) ",
		    buf, sizeof(buf), sp);
	}
	/* NOTREACHED */
}

/*
 * Write out a prompt and read back a reply.  The prompt is now written 
 * out with full "ewprintf" formatting, although the arguments are in a 
 * rather strange place.  This is always a new message, there is no auto
 * completion, and the return is echoed as such.
 */
/* VARARGS */
int
#ifdef __STDC__
ereply(const char *fmt, char *buf, int nbuf, ...)
#else /* __STDC__ */
ereply(va_alist)
	va_dcl
#endif /* __STDC__ */
{
	va_list	 ap;
	int	 i;
#ifdef __STDC__
	va_start(ap, nbuf);
#else /* __STDC__ */
	char	*fmt, *buf;
	int	 nbuf;

	va_start(ap);
	fmt = va_arg(ap, char *);
	buf = va_arg(ap, char *);
	nbuf = va_arg(ap, int);
#endif /* __STDC__ */
	i = veread(fmt, buf, nbuf, EFNEW | EFCR, ap);
	va_end(ap);
	return i;
}

/*
 * This is the general "read input from the echo line" routine.  The basic 
 * idea is that the prompt string "prompt" is written to the echo line, and
 * a one line reply is read back into the supplied "buf" (with maximum 
 * length "len"). The "flag" contains EFNEW (a new prompt), an EFFUNC 
 * (autocomplete), or EFCR (echo the carriage return as CR).
 */
/* VARARGS */
int
#ifdef __STDC__
eread(const char *fmt, char *buf, int nbuf, int flag, ...)
#else /* __STDC__ */
eread(va_alist)
	char *fmt, *buf;
	int   buf, flag;
	va_dcl
#endif /* __STDC__ */
{
	int	 i;
	va_list	 ap;
#ifdef __STDC__
	va_start(ap, flag);
#else /* __STDC__ */
	char	*fmt, *buf;
	int	 nbuf;

	va_start(ap);
	fmt = va_arg(ap, char *);
	buf = va_arg(ap, char *);
	nbuf = va_arg(ap, int);
	flag = va_arg(ap, int);
#endif /* __STDC__ */
	i = veread(fmt, buf, nbuf, flag, ap);
	va_end(ap);
	return i;
}

static int
veread(fp, buf, nbuf, flag, ap)
	const char *fp;
	char       *buf;
	int         nbuf, flag;
	va_list     ap;
{
	int	 cpos;
	int	 i;
	int	 c;

#ifndef NO_MACRO
	if (inmacro) {
		bcopy(maclcur->l_text, buf, maclcur->l_used);
		buf[maclcur->l_used] = '\0';
		maclcur = maclcur->l_fp;
		return TRUE;
	}
#endif /* !NO_MACRO */
	cpos = 0;
	if ((flag & EFNEW) != 0 || ttrow != nrow - 1) {
		ttcolor(CTEXT);
		ttmove(nrow - 1, 0);
		epresf = TRUE;
	} else
		eputc(' ');
	eformat(fp, ap);
	tteeol();
	ttflush();
	for (;;) {
		c = getkey(FALSE);
		if ((flag & EFAUTO) != 0 && (c == ' ' || c == CCHR('I'))) {
			cpos += complt(flag, c, buf, cpos);
			continue;
		}
		if ((flag & EFAUTO) != 0 && c == '?') {
			complt_list(flag, c, buf, cpos);
			continue;
		}
		switch (c) {
		case CCHR('J'):
			c = CCHR('M');
			/* and continue */
		case CCHR('M'):			/* return, done */
			if ((flag & EFFUNC) != 0) {
				if ((i = complt(flag, c, buf, cpos)) == 0)
					continue;
				if (i > 0)
					cpos += i;
			}
			buf[cpos] = '\0';
			if ((flag & EFCR) != 0) {
				ttputc(CCHR('M'));
				ttflush();
			}
#ifndef NO_MACRO
			if (macrodef) {
				LINE	*lp;

				if ((lp = lalloc(cpos)) == NULL)
					return FALSE;
				lp->l_fp = maclcur->l_fp;
				maclcur->l_fp = lp;
				lp->l_bp = maclcur;
				maclcur = lp;
				bcopy(buf, lp->l_text, cpos);
			}
#endif /* !NO_MACRO */
			goto done;
		case CCHR('G'):			/* bell, abort */
			eputc(CCHR('G'));
			(VOID)ctrlg(FFRAND, 0);
			ttflush();
			return ABORT;
		case CCHR('H'):			/* rubout, erase */
		case CCHR('?'):	
			if (cpos != 0) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
				ttflush();
			}
			break;
		case CCHR('X'):			/* kill line */
		case CCHR('U'):
			while (cpos != 0) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
			}
			ttflush();
			break;
		case CCHR('W'):			/* kill to beginning of word */
			while ((cpos > 0) && !ISWORD(buf[cpos - 1])) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
			}
			while ((cpos > 0) && ISWORD(buf[cpos - 1])) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
			}
			ttflush();
			break;
		case CCHR('\\'):
		case CCHR('Q'):			/* quote next */
			c = getkey(FALSE);
			/* and continue */
		default:			/* all the rest */
			if (cpos < nbuf - 1) {
				buf[cpos++] = (char)c;
				eputc((char)c);
				ttflush();
			}
		}
	}
done:	return buf[0] != '\0';
}

/*
 * do completion on a list of objects.
 */
static int
complt(flags, c, buf, cpos)
	int   flags, c, cpos;
	char *buf;
{
	LIST	*lh, *lh2;
	LIST	*wholelist = NULL;
	int	 i, nxtra, nhits, bxtra, msglen, nshown;
	int	 wflag = FALSE;
	char	*msg;

	lh = lh2 = NULL;

	if ((flags & EFFUNC) != 0) {
		buf[cpos] = '\0';
		i = complete_function(buf, c);
		if (i > 0) {
			eputs(&buf[cpos]);
			ttflush();
			return i;
		}
		switch (i) {
		case -3:
			msg = " [Ambiguous]";
			break;
		case -2:
			i = 0;
			msg = " [No match]";
			break;
		case -1:
		case 0:
			return i;
		default:
			msg = " [Internal error]";
			break;
		}
	} else {
		if ((flags & EFBUF) != 0)
			lh = &(bheadp->b_list);
		else if ((flags & EFFILE) != 0) {
			buf[cpos] = '\0';
			wholelist = lh = make_file_list(buf, 0);
		} else
			panic("broken complt call: flags");

		if (c == ' ')
			wflag = TRUE;
		else if (c != '\t' && c != CCHR('M'))
			panic("broken complt call: c");

		nhits = 0;
		nxtra = HUGE;

		while (lh != NULL) {
			for (i = 0; i < cpos; ++i) {
				if (buf[i] != lh->l_name[i])
					break;
			}
			if (i == cpos) {
				if (nhits == 0)
					lh2 = lh;
				++nhits;
				if (lh->l_name[i] == '\0')
					nxtra = -1;
				else {
					bxtra = getxtra(lh, lh2, cpos, wflag);
					if (bxtra < nxtra)
						nxtra = bxtra;
					lh2 = lh;
				}
			}
			lh = lh->l_next;
		}
		if (nhits == 0)
			msg = " [No match]";
		else if (nhits > 1 && nxtra == 0)
			msg = " [Ambiguous]";
		else {
			/*
			 * Being lazy - ought to check length, but all things
			 * autocompleted have known types/lengths.
			 */
			if (nxtra < 0 && nhits > 1 && c == ' ')
				nxtra = 1;
			for (i = 0; i < nxtra; ++i) {
				buf[cpos] = lh2->l_name[cpos];
				eputc(buf[cpos++]);
			}
			ttflush();
			free_file_list(wholelist);
			if (nxtra < 0 && c != CCHR('M'))
				return 0;
			return nxtra;
		}
	}

	/*
	 * wholelist is null if we are doing buffers.  want to free lists
	 * that were created for us, but not the buffer list!
	 */
	free_file_list(wholelist);

	/* Set up backspaces, etc., being mindful of echo line limit */
	msglen = strlen(msg);
	nshown = (ttcol + msglen + 2 > ncol) ?
		ncol - ttcol - 2 : msglen;
	eputs(msg);
	ttcol -= (i = nshown);	/* update ttcol!		 */
	while (i--)		/* move back before msg		 */
		ttputc('\b');
	ttflush();		/* display to user		 */
	i = nshown;
	while (i--)		/* blank out on next flush	 */
		eputc(' ');
	ttcol -= (i = nshown);	/* update ttcol on BS's		 */
	while (i--)
		ttputc('\b');	/* update ttcol again!		 */
	return 0;
}

/*
 * do completion on a list of objects, listing instead of completing
 */
static int
complt_list(flags, c, buf, cpos)
	int   flags;
	int   c;
	char *buf;
	int   cpos;
{
	LIST	*lh, *lh2, *lh3;
	LIST	*wholelist = NULL;
	BUFFER	*bp;
	int	 i, maxwidth, width;
	int	 preflen = 0;
	int	 oldrow = ttrow;
	int	 oldcol = ttcol;
	int	 oldhue = tthue;
	char	 linebuf[NCOL + 1];
	char	*cp;

	lh = NULL;

	ttflush();

	/* the results are put into a help buffer */
	bp = bfind("*help*", TRUE);
	if (bclear(bp) == FALSE)
		return FALSE;

	{	/* this {} present for historical reasons */

		/*
		 * first get the list of objects.  This list may contain only
		 * the ones that complete what has been typed, or may be the
		 * whole list of all objects of this type.  They are filtered
		 * later in any case.  Set wholelist if the list has been
		 * cons'ed up just for us, so we can free it later.  We have
		 * to copy the buffer list for this function even though we
		 * didn't for complt.  The sorting code does destructive
		 * changes to the list, which we don't want to happen to the
		 * main buffer list!
		 */
		if ((flags & EFBUF) != 0)
			wholelist = lh = copy_list(&(bheadp->b_list));
		else if ((flags & EFFUNC) != 0) {
			buf[cpos] = '\0';
			wholelist = lh = complete_function_list(buf, c);
		} else if ((flags & EFFILE) != 0) {
			buf[cpos] = '\0';
			wholelist = lh = make_file_list(buf, 1);
			/*
			 * We don't want to display stuff up to the / for file
			 * names preflen is the list of a prefix of what the
			 * user typed that should not be displayed.
			 */
			cp = strrchr(buf, '/');
			if (cp)
				preflen = cp - buf + 1;
		} else
			panic("broken complt call: flags");


		/*
		 * Sort the list, since users expect to see it in alphabetic
		 * order.
		 */
		lh2 = lh;
		while (lh2) {
			lh3 = lh2->l_next;
			while (lh3) {
				if (strcmp(lh2->l_name, lh3->l_name) > 0) {
					cp = lh2->l_name;
					lh2->l_name = lh3->l_name;
					lh3->l_name = cp;
				}
				lh3 = lh3->l_next;
			}
			lh2 = lh2->l_next;
		}

		/*
		 * First find max width of object to be displayed, so we can
		 * put several on a line.
		 */
		maxwidth = 0;
		lh2 = lh;
		while (lh2 != NULL) {
			for (i = 0; i < cpos; ++i) {
				if (buf[i] != lh2->l_name[i])
					break;
			}
			if (i == cpos) {
				width = strlen(lh2->l_name);
				if (width > maxwidth)
					maxwidth = width;
			}
			lh2 = lh2->l_next;
		}
		maxwidth += 1 - preflen;

		/*
		 * Now do the display.  objects are written into linebuf until
		 * it fills, and then put into the help buffer.
		 */
		cp = linebuf;
		width = 0;
		lh2 = lh;
		while (lh2 != NULL) {
			for (i = 0; i < cpos; ++i) {
				if (buf[i] != lh2->l_name[i])
					break;
			}
			if (i == cpos) {
				if ((width + maxwidth) > ncol) {
					*cp = 0;
					addline(bp, linebuf);
					cp = linebuf;
					width = 0;
				}
				strcpy(cp, lh2->l_name + preflen);
				i = strlen(lh2->l_name + preflen);
				cp += i;
				for (; i < maxwidth; i++)
					*cp++ = ' ';
				width += maxwidth;
			}
			lh2 = lh2->l_next;
		}
		if (width > 0) {
			*cp = 0;
			addline(bp, linebuf);
		}
	}
	/*
	 * Note that we free lists only if they are put in wholelist lists
	 * that were built just for us should be freed.  However when we use
	 * the buffer list, obviously we don't want it freed.
	 */
	free_file_list(wholelist);
	popbuftop(bp);		/* split the screen and put up the help
				 * buffer */
	update();		/* needed to make the new stuff actually
				 * appear */
	ttmove(oldrow, oldcol);	/* update leaves cursor in arbitrary place */
	ttcolor(oldhue);	/* with arbitrary color */
	ttflush();
	return 0;
}

/*
 * The "lp1" and "lp2" point to list structures.  The "cpos" is a horizontal
 * position in the name.  Return the longest block of characters that can be 
 * autocompleted at this point.  Sometimes the two symbols are the same, but 
 * this is normal.
 */
int
getxtra(lp1, lp2, cpos, wflag)
	LIST *lp1, *lp2;
	int   cpos;
	int   wflag;
{
	int	i;

	i = cpos;
	for (;;) {
		if (lp1->l_name[i] != lp2->l_name[i])
			break;
		if (lp1->l_name[i] == '\0')
			break;
		++i;
		if (wflag && !ISWORD(lp1->l_name[i - 1]))
			break;
	}
	return (i - cpos);
}

/*
 * Special "printf" for the echo line.  Each call to "ewprintf" starts a 
 * new line in the echo area, and ends with an erase to end of the echo 
 * line.  The formatting is done by a call to the standard formatting 
 * routine.
 */
/* VARARGS */
VOID
#ifdef __STDC__
ewprintf(const char *fmt, ...)
#else /* __STDC__ */
ewprintf(va_alist)
	va_dcl
#endif /* __STDC__ */
{
	va_list	 ap;
#ifndef __STDC__
	char	*fmt;
#endif /* !__STDC__ */

#ifndef NO_MACRO
	if (inmacro)
		return;
#endif /* !NO_MACRO */
#ifdef __STDC__
	va_start(ap, fmt);
#else /* __STDC__ */
	va_start(ap);
	fmt = va_arg(ap, char *);
#endif /* __STDC__ */
	ttcolor(CTEXT);
	ttmove(nrow - 1, 0);
	eformat(fmt, ap);
	va_end(ap);
	tteeol();
	ttflush();
	epresf = TRUE;
}

/*
 * Printf style formatting. This is called by both "ewprintf" and "ereply" 
 * to provide formatting services to their clients.  The move to the start 
 * of the echo line, and the erase to the end of the echo line, is done by 
 * the caller.
 * Note: %c works, and prints the "name" of the character.
 * %k prints the name of a key (and takes no arguments).
 */
static VOID
eformat(fp, ap)
	const char *fp;
	va_list     ap;
{
	int	 c;
	char	 kname[NKNAME];
	char	*cp;

	while ((c = *fp++) != '\0') {
		if (c != '%')
			eputc(c);
		else {
			c = *fp++;
			switch (c) {
			case 'c':
				(VOID)keyname(kname, va_arg(ap, int));
				eputs(kname);
				break;

			case 'k':
				cp = kname;
				for (c = 0; c < key.k_count; c++) {
					cp = keyname(cp, key.k_chars[c]);
					*cp++ = ' ';
				}
				*--cp = '\0';
				eputs(kname);
				break;

			case 'd':
				eputi(va_arg(ap, int), 10);
				break;

			case 'o':
				eputi(va_arg(ap, int), 8);
				break;

			case 's':
				eputs(va_arg(ap, char *));
				break;

			case 'l':
				/* explicit longword */
				c = *fp++;
				switch (c) {
				case 'd':
					eputl((long)va_arg(ap, long), 10);
					break;
				default:
					eputc(c);
					break;
				}
				break;

			default:
				eputc(c);
			}
		}
	}
}

/*
 * Put integer, in radix "r".
 */
static VOID
eputi(i, r)
	int i, r;
{
	int	 q;

	if (i < 0) {
		eputc('-');
		i = -i;
	}
	if ((q = i / r) != 0)
		eputi(q, r);
	eputc(i % r + '0');
}

/*
 * Put long, in radix "r".
 */
static VOID
eputl(l, r)
	long l;
	int  r;
{
	long	 q;

	if (l < 0) {
		eputc('-');
		l = -l;
	}
	if ((q = l / r) != 0)
		eputl(q, r);
	eputc((int)(l % r) + '0');
}

/*
 * Put string.
 */
static VOID
eputs(s)
	char *s;
{
	int	 c;

	while ((c = *s++) != '\0')
		eputc(c);
}

/*
 * Put character.  Watch for control characters, and for the line getting 
 * too long.
 */
static VOID
eputc(c)
	char	 c;
{
	if (ttcol + 2 < ncol) {
		if (ISCTRL(c)) {
			eputc('^');
			c = CCHR(c);
		}
		ttputc(c);
		++ttcol;
	}
}

static VOID
free_file_list(lp)
	LIST *lp;
{
	LIST	*next;

	while (lp) {
		next = lp->l_next;
		free(lp);
		lp = next;
	}
}

static LIST *
copy_list(lp)
	LIST *lp;
{
	LIST	*current, *last;

	last = NULL;
	while (lp) {
		current = (LIST *)malloc(sizeof(LIST));
		current->l_next = last;
		current->l_name = lp->l_name;
		last = (LIST *)current;
		lp = lp->l_next;
	}
	return (last);
}