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); | |||||
} |