Edit

IABSD.fr/src/usr.bin/mg/echo.c

Branch :

  • Show log

    Commit

  • Author : op
    Date : 2022-10-15 17:01:14
    Hash : 750a99bf
    Message : replace ewprintf("") calls with eerase(): no functional difference. ok florian@

  • usr.bin/mg/echo.c
  • /*	$OpenBSD: echo.c,v 1.69 2022/10/15 17:01:14 op 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)
    			__attribute__((__format__ (printf, 1, 0)));
    static int	 complt(int, int, char *, size_t, int, int *);
    static int	 complt_list(int, char *, int);
    static void	 eformat(const char *, va_list)
    			__attribute__((__format__ (printf, 1, 0)));
    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 == ' ') {
    			eerase();
    			return (TRUE);
    		}
    		if (s == 'n' || s == 'N' || s == CCHR('M')) {
    			eerase();
    			return (FALSE);
    		}
    		if (s == CCHR('G')) {
    			eerase();
    			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 == ' ') {
    			eerase();
    			return (TRUE);
    		}
    		if (s == 'n' || s == 'N' || s == CCHR('M')) {
    			eerase();
    			return (FALSE);
    		}
    		if (s == 'r' || s == 'R') {
    			eerase();
    			return (REVERT);
    		}
    		if (s == CCHR('G')) {
    			eerase();
    			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) {
    			eerase();
    			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 (strcasecmp(rep, "yes") == 0) {
    				eerase();
    				return (TRUE);
    			}
    			if (strcasecmp(rep, "no") == 0) {
    				eerase();
    				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.
     */
    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;		/* display completion list */
    	int	 cwin = FALSE;		/* completion list created */
    	int	 mr, ml;		/* match left/right arrows */
    	int	 esc;			/* 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 != *curbp->b_nlchr) {
    				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. Press Control-g to escape.");
    					goto skipkey;
    				}
    				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. Press Control-g to escape.");
    				goto skipkey;
    			}
    			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();
    		}
    
    skipkey:	/* ignore key press */
    ;
    	}
    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);
    	bp->b_flag |= BFREADONLY;
    
    	/*
    	 * 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.
     */
    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 "ewprintf" to provide
     * formatting services to its 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);
    }