[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.58, Thu Mar 19 21:22:15 2015 UTC (9 years, 2 months ago) by bcallah
Branch: MAIN
Changes since 1.57: +10 -6 lines

Clean up the includes in mg.
This does the following:
Moves all POSIX headers from sysdef.h into the individual .c files so that
each file now only includes what it needs. All headers are properly sorted.
Moves the remainder of sysdef.h to other files (mostly def.h) and deletes
sysdef.h now that it's no longer contains anything.
Tweak a comment that references sysdef.h so that it no longer does that.
ok florian@

/*	$OpenBSD: echo.c,v 1.58 2015/03/19 21:22:15 bcallah Exp $	*/

/* This file is in the public domain. */

/*
 *	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 <sys/queue.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <term.h>

#include "def.h"
#include "funmap.h"
#include "key.h"
#include "macro.h"

static char	*veread(const char *, char *, size_t, int, va_list);
static int	 complt(int, int, char *, size_t, int, int *);
static int	 complt_list(int, char *, int);
static void	 eformat(const char *, va_list);
static void	 eputi(int, int);
static void	 eputl(long, int);
static void	 eputs(const char *);
static void	 eputc(char);
static struct list	*copy_list(struct list *);

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

/*
 * Erase the echo line.
 */
void
eerase(void)
{
	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(const char *sp)
{
	int	 s;

	if (inmacro)
		return (TRUE);

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

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

	if (inmacro)
		return (TRUE);

	ewprintf("%s? (y, n or r) ", sp);
	for (;;) {
		s = getkey(FALSE);
		if (s == 'y' || s == 'Y' || s == ' ')
			return (TRUE);
		if (s == 'n' || s == 'N' || s == CCHR('M'))
			return (FALSE);
		if (s == 'r' || s == 'R')
			return (REVERT);
		if (s == CCHR('G'))
			return (ctrlg(FFRAND, 1));
		ewprintf("Please answer y, n or r.");
	}
	/* NOTREACHED */
}

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

	if (inmacro)
		return (TRUE);

	rep = eread("%s? (yes or no) ", buf, sizeof(buf),
	    EFNUL | EFNEW | EFCR, sp);
	for (;;) {
		if (rep == NULL)
			return (ABORT);
		if (rep[0] != '\0') {
			if (macrodef) {
				struct line	*lp = maclcur;

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

/*
 * 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").
 * XXX: When checking for an empty return value, always check rep, *not* buf
 * as buf may be freed in pathological cases.
 */
/* VARARGS */
char *
eread(const char *fmt, char *buf, size_t nbuf, int flag, ...)
{
	va_list	 ap;
	char	*rep;

	va_start(ap, flag);
	rep = veread(fmt, buf, nbuf, flag, ap);
	va_end(ap);
	return (rep);
}

static char *
veread(const char *fp, char *buf, size_t nbuf, int flag, va_list ap)
{
	int	 dynbuf = (buf == NULL);
	int	 cpos, epos;		/* cursor, end position in buf */
	int	 c, i, y;
	int	 cplflag = FALSE;	/* display completion list */
	int	 cwin = FALSE;		/* completion list created */
	int	 mr = 0;		/* match left arrow */
	int	 ml = 0;		/* match right arrow */
	int	 esc = 0;		/* position in esc pattern */
	struct buffer	*bp;			/* completion list buffer */
	struct mgwin	*wp;			/* window for compl list */
	int	 match;			/* esc match found */
	int	 cc, rr;		/* saved ttcol, ttrow */
	char	*ret;			/* return value */

	static char emptyval[] = "";	/* XXX hackish way to return err msg*/

	if (inmacro) {
		if (dynbuf) {
			if ((buf = malloc(maclcur->l_used + 1)) == NULL)
				return (NULL);
		} else if (maclcur->l_used >= nbuf)
			return (NULL);
		bcopy(maclcur->l_text, buf, maclcur->l_used);
		buf[maclcur->l_used] = '\0';
		maclcur = maclcur->l_fp;
		return (buf);
	}
	epos = cpos = 0;
	ml = mr = esc = 0;
	cplflag = FALSE;

	if ((flag & EFNEW) != 0 || ttrow != nrow - 1) {
		ttcolor(CTEXT);
		ttmove(nrow - 1, 0);
		epresf = TRUE;
	} else
		eputc(' ');
	eformat(fp, ap);
	if ((flag & EFDEF) != 0) {
		if (buf == NULL)
			return (NULL);
		eputs(buf);
		epos = cpos += strlen(buf);
	}
	tteeol();
	ttflush();
	for (;;) {
		c = getkey(FALSE);
		if ((flag & EFAUTO) != 0 && c == CCHR('I')) {
			if (cplflag == TRUE) {
				complt_list(flag, buf, cpos);
				cwin = TRUE;
			} else if (complt(flag, c, buf, nbuf, epos, &i) == TRUE) {
				cplflag = TRUE;
				epos += i;
				cpos = epos;
			}
			continue;
		}
		cplflag = FALSE;

		if (esc > 0) { /* ESC sequence started */
			match = 0;
			if (ml == esc && key_left[ml] && c == key_left[ml]) {
				match++;
				if (key_left[++ml] == '\0') {
					c = CCHR('B');
					esc = 0;
				}
			}
			if (mr == esc && key_right[mr] && c == key_right[mr]) {
				match++;
				if (key_right[++mr] == '\0') {
					c = CCHR('F');
					esc = 0;
				}
			}
			if (match == 0) {
				esc = 0;
				continue;
				/* hack. how do we know esc pattern is done? */
			}
			if (esc > 0) {
				esc++;
				continue;
			}
		}
		switch (c) {
		case CCHR('A'): /* start of line */
			while (cpos > 0) {
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					--ttcol;
				}
				ttputc('\b');
				--ttcol;
			}
			ttflush();
			break;
		case CCHR('D'):
			if (cpos != epos) {
				tteeol();
				epos--;
				rr = ttrow;
				cc = ttcol;
				for (i = cpos; i < epos; i++) {
					buf[i] = buf[i + 1];
					eputc(buf[i]);
				}
				ttmove(rr, cc);
				ttflush();
			}
			break;
		case CCHR('E'): /* end of line */
			while (cpos < epos) {
				eputc(buf[cpos++]);
			}
			ttflush();
			break;
		case CCHR('B'): /* back */
			if (cpos > 0) {
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					--ttcol;
				}
				ttputc('\b');
				--ttcol;
				ttflush();
			}
			break;
		case CCHR('F'): /* forw */
			if (cpos < epos) {
				eputc(buf[cpos++]);
				ttflush();
			}
			break;
		case CCHR('Y'): /* yank from kill buffer */
			i = 0;
			while ((y = kremove(i++)) >= 0 && y != '\n') {
				int t;
				if (dynbuf && epos + 1 >= nbuf) {
					void *newp;
					size_t newsize = epos + epos + 16;
					if ((newp = realloc(buf, newsize))
					    == NULL)
						goto memfail;
					buf = newp;
					nbuf = newsize;
				}
				if (!dynbuf && epos + 1 >= nbuf) {
					dobeep();
					ewprintf("Line too long");
					return (emptyval);
				}
				for (t = epos; t > cpos; t--)
					buf[t] = buf[t - 1];
				buf[cpos++] = (char)y;
				epos++;
				eputc((char)y);
				cc = ttcol;
				rr = ttrow;
				for (t = cpos; t < epos; t++)
					eputc(buf[t]);
				ttmove(rr, cc);
			}
			ttflush();
			break;
		case CCHR('K'): /* copy here-EOL to kill buffer */
			kdelete();
			for (i = cpos; i < epos; i++)
				kinsert(buf[i], KFORW);
			tteeol();
			epos = cpos;
			ttflush();
			break;
		case CCHR('['):
			ml = mr = esc = 1;
			break;
		case CCHR('J'):
			c = CCHR('M');
			/* FALLTHROUGH */
		case CCHR('M'):			/* return, done */
			/* if there's nothing in the minibuffer, abort */
			if (epos == 0 && !(flag & EFNUL)) {
				(void)ctrlg(FFRAND, 0);
				ttflush();
				return (NULL);
			}
			if ((flag & EFFUNC) != 0) {
				if (complt(flag, c, buf, nbuf, epos, &i)
				    == FALSE)
					continue;
				if (i > 0)
					epos += i;
			}
			buf[epos] = '\0';
			if ((flag & EFCR) != 0) {
				ttputc(CCHR('M'));
				ttflush();
			}
			if (macrodef) {
				struct line	*lp;

				if ((lp = lalloc(cpos)) == NULL)
					goto memfail;
				lp->l_fp = maclcur->l_fp;
				maclcur->l_fp = lp;
				lp->l_bp = maclcur;
				maclcur = lp;
				bcopy(buf, lp->l_text, cpos);
			}
			ret = buf;
			goto done;
		case CCHR('G'):			/* bell, abort */
			eputc(CCHR('G'));
			(void)ctrlg(FFRAND, 0);
			ttflush();
			ret = NULL;
			goto done;
		case CCHR('H'):			/* rubout, erase */
		case CCHR('?'):
			if (cpos != 0) {
				y = buf[--cpos];
				epos--;
				ttputc('\b');
				ttcol--;
				if (ISCTRL(y) != FALSE) {
					ttputc('\b');
					ttcol--;
				}
				rr = ttrow;
				cc = ttcol;
				for (i = cpos; i < epos; i++) {
					buf[i] = buf[i + 1];
					eputc(buf[i]);
				}
				ttputc(' ');
				if (ISCTRL(y) != FALSE) {
					ttputc(' ');
					ttputc('\b');
				}
				ttputc('\b');
				ttmove(rr, cc);
				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;
				}
				epos--;
			}
			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;
				}
				epos--;
			}
			while ((cpos > 0) && ISWORD(buf[cpos - 1])) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
				epos--;
			}
			ttflush();
			break;
		case CCHR('\\'):
		case CCHR('Q'):			/* quote next */
			c = getkey(FALSE);
			/* FALLTHROUGH */
		default:
			if (dynbuf && epos + 1 >= nbuf) {
				void *newp;
				size_t newsize = epos + epos + 16;
				if ((newp = realloc(buf, newsize)) == NULL)
					goto memfail;
				buf = newp;
				nbuf = newsize;
			}
			if (!dynbuf && epos + 1 >= nbuf) {
				dobeep();
				ewprintf("Line too long");
				return (emptyval);
			}
			for (i = epos; i > cpos; i--)
				buf[i] = buf[i - 1];
			buf[cpos++] = (char)c;
			epos++;
			eputc((char)c);
			cc = ttcol;
			rr = ttrow;
			for (i = cpos; i < epos; i++)
				eputc(buf[i]);
			ttmove(rr, cc);
			ttflush();
		}
	}
done:
	if (cwin == TRUE) {
		/* blow away cpltion window */
		bp = bfind("*Completions*", TRUE);
		if ((wp = popbuf(bp, WEPHEM)) != NULL) {
			if (wp->w_flag & WEPHEM) {
				curwp = wp;
				delwind(FFRAND, 1);
			} else {
				killbuffer(bp);
			}
		}
	}
	return (ret);
memfail:
	if (dynbuf && buf)
		free(buf);
	dobeep();
	ewprintf("Out of memory");
	return (emptyval);
}

/*
 * Do completion on a list of objects.
 * c is SPACE, TAB, or CR
 * return TRUE if matched (or partially matched)
 * FALSE is result is ambiguous,
 * ABORT on error.
 */
static int
complt(int flags, int c, char *buf, size_t nbuf, int cpos, int *nx)
{
	struct list	*lh, *lh2;
	struct 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';
		wholelist = lh = complete_function_list(buf);
	} else if ((flags & EFBUF) != 0) {
		lh = &(bheadp->b_list);
	} else if ((flags & EFFILE) != 0) {
		buf[cpos] = '\0';
		wholelist = lh = make_file_list(buf);
	} 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;

	for (; lh != NULL; lh = lh->l_next) {
		if (memcmp(buf, lh->l_name, cpos) != 0)
			continue;
		if (nhits == 0)
			lh2 = lh;
		++nhits;
		if (lh->l_name[cpos] == '\0')
			nxtra = -1; /* exact match */
		else {
			bxtra = getxtra(lh, lh2, cpos, wflag);
			if (bxtra < nxtra)
				nxtra = bxtra;
			lh2 = lh;
		}
	}
	if (nhits == 0)
		msg = " [No match]";
	else if (nhits > 1 && nxtra == 0)
		msg = " [Ambiguous. Ctrl-G to cancel]";
	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 && cpos < nbuf; ++i) {
			buf[cpos] = lh2->l_name[cpos];
			eputc(buf[cpos++]);
		}
		/* XXX should grow nbuf */
		ttflush();
		free_file_list(wholelist);
		*nx = nxtra;
		if (nxtra < 0 && c != CCHR('M')) /* exact */
			*nx = 0;
		return (TRUE);
	}

	/*
	 * 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!		 */
	*nx = nxtra;
	return ((nhits > 0) ? TRUE : FALSE);
}

/*
 * Do completion on a list of objects, listing instead of completing.
 */
static int
complt_list(int flags, char *buf, int cpos)
{
	struct list	*lh, *lh2, *lh3;
	struct list	*wholelist = NULL;
	struct buffer	*bp;
	int	 i, maxwidth, width;
	int	 preflen = 0;
	int	 oldrow = ttrow;
	int	 oldcol = ttcol;
	int	 oldhue = tthue;
	char	 *linebuf;
	size_t	 linesize, len;
	char *cp;

	lh = NULL;

	ttflush();

	/* The results are put into a completion buffer. */
	bp = bfind("*Completions*", TRUE);
	if (bclear(bp) == FALSE)
		return (FALSE);

	/*
	 * 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);
	} else if ((flags & EFFILE) != 0) {
		buf[cpos] = '\0';
		wholelist = lh = make_file_list(buf);
		/*
		 * 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 != NULL) {
		lh3 = lh2->l_next;
		while (lh3 != NULL) {
			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.
	 */
	linesize = (ncol > maxwidth ? ncol : maxwidth) + 1;
	if ((linebuf = malloc(linesize)) == NULL) {
		free_file_list(wholelist);
		return (FALSE);
	}
	width = 0;

	/*
	 * We're going to strlcat() into the buffer, so it has to be
	 * NUL terminated.
	 */
	linebuf[0] = '\0';
	for (lh2 = lh; lh2 != NULL; lh2 = lh2->l_next) {
		for (i = 0; i < cpos; ++i) {
			if (buf[i] != lh2->l_name[i])
				break;
		}
		/* if we have a match */
		if (i == cpos) {
			/* if it wraps */
			if ((width + maxwidth) > ncol) {
				addline(bp, linebuf);
				linebuf[0] = '\0';
				width = 0;
			}
			len = strlcat(linebuf, lh2->l_name + preflen,
			    linesize);
			width += maxwidth;
			if (len < width && width < linesize) {
				/* pad so the objects nicely line up */
				memset(linebuf + len, ' ',
				    maxwidth - strlen(lh2->l_name + preflen));
				linebuf[width] = '\0';
			}
		}
	}
	if (width > 0)
		addline(bp, linebuf);
	free(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, WEPHEM);	/* split the screen and put up the help
				 * buffer */
	update(CMODE);		/* 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(struct list *lp1, struct list *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
ewprintf(const char *fmt, ...)
{
	va_list	 ap;

	if (inmacro)
		return;

	va_start(ap, fmt);
	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. 
 * %c prints the "name" of the supplied character.
 * %k prints the name of the current key (and takes no arguments).
 * %d prints a decimal integer
 * %o prints an octal integer
 * %p prints a pointer
 * %s prints a string
 * %ld prints a long word
 * Anything else is echoed verbatim
 */
static void
eformat(const char *fp, va_list ap)
{
	char	kname[NKNAME], tmp[100], *cp;
	int	c;

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

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

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

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

			case 'p':
				snprintf(tmp, sizeof(tmp), "%p",
				    va_arg(ap, void *));
				eputs(tmp);
				break;

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

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

			default:
				eputc(c);
			}
		}
	}
}

/*
 * Put integer, in radix "r".
 */
static void
eputi(int i, int 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(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(const 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(char c)
{
	if (ttcol + 2 < ncol) {
		if (ISCTRL(c)) {
			eputc('^');
			c = CCHR(c);
		}
		ttputc(c);
		++ttcol;
	}
}

void
free_file_list(struct list *lp)
{
	struct list	*next;

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

static struct list *
copy_list(struct list *lp)
{
	struct list	*current, *last, *nxt;

	last = NULL;
	while (lp) {
		current = malloc(sizeof(struct list));
		if (current == NULL) {
			/* Free what we have allocated so far */
			for (current = last; current; current = nxt) {
				nxt = current->l_next;
				free(current->l_name);
				free(current);
			}
			return (NULL);
		}
		current->l_next = last;
		current->l_name = strdup(lp->l_name);
		last = current;
		lp = lp->l_next;
	}
	return (last);
}