X-Git-Url: https://pd.if.org/git/?p=pd_readline;a=blobdiff_plain;f=mg%2Fecho.c;fp=mg%2Fecho.c;h=ce173f63128cea084ff03e185ff2306f1699861b;hp=0000000000000000000000000000000000000000;hb=a9843085ec916c175bd245a8398f30e6cc03f984;hpb=26fe4e09c6c3c250334fdeed60ce3061febecde2 diff --git a/mg/echo.c b/mg/echo.c new file mode 100644 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 +#include + +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); +}