]> pd.if.org Git - pd_readline/blobdiff - mg/echo.c
Added mg from an OpenBSD mirror site. Many thanks to the OpenBSD team and the mg...
[pd_readline] / mg / echo.c
diff --git a/mg/echo.c b/mg/echo.c
new file mode 100644 (file)
index 0000000..ce173f6
--- /dev/null
+++ b/mg/echo.c
@@ -0,0 +1,964 @@
+/*     $OpenBSD: echo.c,v 1.50 2012/04/12 04:47:59 lum 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 "def.h"
+#include "key.h"
+#include "macro.h"
+
+#include "funmap.h"
+
+#include <stdarg.h>
+#include <term.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 */
+}
+
+/*
+ * 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 == ' ' || 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();
+                               y = buf[cpos];
+                               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) {
+                                       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) {
+                               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);
+       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 = MAX(ncol, maxwidth) + 1;
+       if ((linebuf = malloc(linesize)) == NULL)
+               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();               /* 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);
+}