Changeset View
Changeset View
Standalone View
Standalone View
contrib/mg/echo.c
- This file was added.
| /* $OpenBSD: echo.c,v 1.68 2021/03/02 15:03:35 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 <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 == ' ') { | |||||
| ewprintf(""); | |||||
| return (TRUE); | |||||
| } | |||||
| if (s == 'n' || s == 'N' || s == CCHR('M')) { | |||||
| ewprintf(""); | |||||
| return (FALSE); | |||||
| } | |||||
| if (s == CCHR('G')) { | |||||
| ewprintf(""); | |||||
| 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 == ' ') { | |||||
| ewprintf(""); | |||||
| return (TRUE); | |||||
| } | |||||
| if (s == 'n' || s == 'N' || s == CCHR('M')) { | |||||
| ewprintf(""); | |||||
| return (FALSE); | |||||
| } | |||||
| if (s == 'r' || s == 'R') { | |||||
| ewprintf(""); | |||||
| return (REVERT); | |||||
| } | |||||
| if (s == CCHR('G')) { | |||||
| ewprintf(""); | |||||
| 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) { | |||||
| ewprintf(""); | |||||
| 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) { | |||||
| ewprintf(""); | |||||
| return (TRUE); | |||||
| } | |||||
| if (strcasecmp(rep, "no") == 0) { | |||||
| ewprintf(""); | |||||
| 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); | |||||
| } | |||||