xref: /openbsd-src/usr.bin/mg/echo.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: echo.c,v 1.16 2001/05/24 09:47:33 art Exp $	*/
2 
3 /*
4  *	Echo line reading and writing.
5  *
6  * Common routines for reading and writing characters in the echo line area
7  * of the display screen. Used by the entire known universe.
8  */
9 
10 #include "def.h"
11 #include "key.h"
12 #ifndef NO_MACRO
13 #include "macro.h"
14 #endif /* !NO_MACRO */
15 
16 #include "funmap.h"
17 
18 #include <stdarg.h>
19 
20 static int	veread		__P((const char *, char *buf, int, int, va_list));
21 static int	complt		__P((int, int, char *, int));
22 static int	complt_list	__P((int, int, char *, int));
23 static void	eformat		__P((const char *, va_list));
24 static void	eputi		__P((int, int));
25 static void	eputl		__P((long, int));
26 static void	eputs		__P((char *));
27 static void	eputc		__P((char));
28 static LIST	*copy_list	__P((LIST *));
29 
30 int		epresf = FALSE;		/* stuff in echo line flag */
31 
32 /*
33  * Erase the echo line.
34  */
35 void
36 eerase()
37 {
38 	ttcolor(CTEXT);
39 	ttmove(nrow - 1, 0);
40 	tteeol();
41 	ttflush();
42 	epresf = FALSE;
43 }
44 
45 /*
46  * Ask a "yes" or "no" question.  Return ABORT if the user answers the
47  * question with the abort ("^G") character.  Return FALSE for "no" and
48  * TRUE for "yes".  No formatting services are available.  No newline
49  * required.
50  */
51 int
52 eyorn(sp)
53 	char *sp;
54 {
55 	int	 s;
56 
57 #ifndef NO_MACRO
58 	if (inmacro)
59 		return TRUE;
60 #endif /* !NO_MACRO */
61 	ewprintf("%s? (y or n) ", sp);
62 	for (;;) {
63 		s = getkey(FALSE);
64 		if (s == 'y' || s == 'Y')
65 			return TRUE;
66 		if (s == 'n' || s == 'N')
67 			return FALSE;
68 		if (s == CCHR('G'))
69 			return ctrlg(FFRAND, 1);
70 		ewprintf("Please answer y or n.  %s? (y or n) ", sp);
71 	}
72 	/* NOTREACHED */
73 }
74 
75 /*
76  * Like eyorn, but for more important questions.  User must type all of
77  * "yes" or "no" and the trainling newline.
78  */
79 int
80 eyesno(sp)
81 	char *sp;
82 {
83 	int	 s;
84 	char	 buf[64];
85 
86 #ifndef NO_MACRO
87 	if (inmacro)
88 		return TRUE;
89 #endif /* !NO_MACRO */
90 	s = ereply("%s? (yes or no) ", buf, sizeof(buf), sp);
91 	for (;;) {
92 		if (s == ABORT)
93 			return ABORT;
94 		if (s != FALSE) {
95 #ifndef NO_MACRO
96 			if (macrodef) {
97 				LINE	*lp = maclcur;
98 
99 				maclcur = lp->l_bp;
100 				maclcur->l_fp = lp->l_fp;
101 				free((char *)lp);
102 			}
103 #endif /* !NO_MACRO */
104 			if ((buf[0] == 'y' || buf[0] == 'Y')
105 			    && (buf[1] == 'e' || buf[1] == 'E')
106 			    && (buf[2] == 's' || buf[2] == 'S')
107 			    && (buf[3] == '\0'))
108 				return TRUE;
109 			if ((buf[0] == 'n' || buf[0] == 'N')
110 			    && (buf[1] == 'o' || buf[0] == 'O')
111 			    && (buf[2] == '\0'))
112 				return FALSE;
113 		}
114 		s = ereply("Please answer yes or no.  %s? (yes or no) ",
115 		    buf, sizeof(buf), sp);
116 	}
117 	/* NOTREACHED */
118 }
119 
120 /*
121  * Write out a prompt and read back a reply.  The prompt is now written
122  * out with full "ewprintf" formatting, although the arguments are in a
123  * rather strange place.  This is always a new message, there is no auto
124  * completion, and the return is echoed as such.
125  */
126 /* VARARGS */
127 int
128 ereply(const char *fmt, char *buf, int nbuf, ...)
129 {
130 	va_list	 ap;
131 	int	 i;
132 	va_start(ap, nbuf);
133 	i = veread(fmt, buf, nbuf, EFNEW | EFCR, ap);
134 	va_end(ap);
135 	return i;
136 }
137 
138 /*
139  * This is the general "read input from the echo line" routine.  The basic
140  * idea is that the prompt string "prompt" is written to the echo line, and
141  * a one line reply is read back into the supplied "buf" (with maximum
142  * length "len"). The "flag" contains EFNEW (a new prompt), an EFFUNC
143  * (autocomplete), or EFCR (echo the carriage return as CR).
144  */
145 /* VARARGS */
146 int
147 eread(const char *fmt, char *buf, int nbuf, int flag, ...)
148 {
149 	int	 i;
150 	va_list	 ap;
151 	va_start(ap, flag);
152 	i = veread(fmt, buf, nbuf, flag, ap);
153 	va_end(ap);
154 	return i;
155 }
156 
157 static int
158 veread(const char *fp, char *buf, int nbuf, int flag, va_list ap)
159 {
160 	int	 cpos;
161 	int	 i;
162 	int	 c;
163 
164 #ifndef NO_MACRO
165 	if (inmacro) {
166 		bcopy(maclcur->l_text, buf, maclcur->l_used);
167 		buf[maclcur->l_used] = '\0';
168 		maclcur = maclcur->l_fp;
169 		return TRUE;
170 	}
171 #endif /* !NO_MACRO */
172 	cpos = 0;
173 	if ((flag & EFNEW) != 0 || ttrow != nrow - 1) {
174 		ttcolor(CTEXT);
175 		ttmove(nrow - 1, 0);
176 		epresf = TRUE;
177 	} else
178 		eputc(' ');
179 	eformat(fp, ap);
180 	if ((flag & EFDEF) != 0) {
181 		eputs(buf);
182 		cpos += strlen(buf);
183 	}
184 	tteeol();
185 	ttflush();
186 	for (;;) {
187 		c = getkey(FALSE);
188 		if ((flag & EFAUTO) != 0 && (c == ' ' || c == CCHR('I'))) {
189 			cpos += complt(flag, c, buf, cpos);
190 			continue;
191 		}
192 		if ((flag & EFAUTO) != 0 && c == '?') {
193 			complt_list(flag, c, buf, cpos);
194 			continue;
195 		}
196 		switch (c) {
197 		case CCHR('J'):
198 			c = CCHR('M');
199 			/* and continue */
200 		case CCHR('M'):			/* return, done */
201 			if ((flag & EFFUNC) != 0) {
202 				if ((i = complt(flag, c, buf, cpos)) == 0)
203 					continue;
204 				if (i > 0)
205 					cpos += i;
206 			}
207 			buf[cpos] = '\0';
208 			if ((flag & EFCR) != 0) {
209 				ttputc(CCHR('M'));
210 				ttflush();
211 			}
212 #ifndef NO_MACRO
213 			if (macrodef) {
214 				LINE	*lp;
215 
216 				if ((lp = lalloc(cpos)) == NULL)
217 					return FALSE;
218 				lp->l_fp = maclcur->l_fp;
219 				maclcur->l_fp = lp;
220 				lp->l_bp = maclcur;
221 				maclcur = lp;
222 				bcopy(buf, lp->l_text, cpos);
223 			}
224 #endif /* !NO_MACRO */
225 			goto done;
226 		case CCHR('G'):			/* bell, abort */
227 			eputc(CCHR('G'));
228 			(void)ctrlg(FFRAND, 0);
229 			ttflush();
230 			return ABORT;
231 		case CCHR('H'):			/* rubout, erase */
232 		case CCHR('?'):
233 			if (cpos != 0) {
234 				ttputc('\b');
235 				ttputc(' ');
236 				ttputc('\b');
237 				--ttcol;
238 				if (ISCTRL(buf[--cpos]) != FALSE) {
239 					ttputc('\b');
240 					ttputc(' ');
241 					ttputc('\b');
242 					--ttcol;
243 				}
244 				ttflush();
245 			}
246 			break;
247 		case CCHR('X'):			/* kill line */
248 		case CCHR('U'):
249 			while (cpos != 0) {
250 				ttputc('\b');
251 				ttputc(' ');
252 				ttputc('\b');
253 				--ttcol;
254 				if (ISCTRL(buf[--cpos]) != FALSE) {
255 					ttputc('\b');
256 					ttputc(' ');
257 					ttputc('\b');
258 					--ttcol;
259 				}
260 			}
261 			ttflush();
262 			break;
263 		case CCHR('W'):			/* kill to beginning of word */
264 			while ((cpos > 0) && !ISWORD(buf[cpos - 1])) {
265 				ttputc('\b');
266 				ttputc(' ');
267 				ttputc('\b');
268 				--ttcol;
269 				if (ISCTRL(buf[--cpos]) != FALSE) {
270 					ttputc('\b');
271 					ttputc(' ');
272 					ttputc('\b');
273 					--ttcol;
274 				}
275 			}
276 			while ((cpos > 0) && ISWORD(buf[cpos - 1])) {
277 				ttputc('\b');
278 				ttputc(' ');
279 				ttputc('\b');
280 				--ttcol;
281 				if (ISCTRL(buf[--cpos]) != FALSE) {
282 					ttputc('\b');
283 					ttputc(' ');
284 					ttputc('\b');
285 					--ttcol;
286 				}
287 			}
288 			ttflush();
289 			break;
290 		case CCHR('\\'):
291 		case CCHR('Q'):			/* quote next */
292 			c = getkey(FALSE);
293 			/* and continue */
294 		default:			/* all the rest */
295 			if (cpos < nbuf - 1) {
296 				buf[cpos++] = (char)c;
297 				eputc((char)c);
298 				ttflush();
299 			}
300 		}
301 	}
302 done:
303 	return buf[0] != '\0';
304 }
305 
306 /*
307  * do completion on a list of objects.
308  */
309 static int
310 complt(flags, c, buf, cpos)
311 	int   flags, c, cpos;
312 	char *buf;
313 {
314 	LIST	*lh, *lh2;
315 	LIST	*wholelist = NULL;
316 	int	 i, nxtra, nhits, bxtra, msglen, nshown;
317 	int	 wflag = FALSE;
318 	char	*msg;
319 
320 	lh = lh2 = NULL;
321 
322 	if ((flags & EFFUNC) != 0) {
323 		buf[cpos] = '\0';
324 		wholelist = lh = complete_function_list(buf, c);
325 	} else if ((flags & EFBUF) != 0) {
326 		lh = &(bheadp->b_list);
327 	} else if ((flags & EFFILE) != 0) {
328 		buf[cpos] = '\0';
329 		wholelist = lh = make_file_list(buf);
330 	} else
331 		panic("broken complt call: flags");
332 
333 	if (c == ' ')
334 		wflag = TRUE;
335 	else if (c != '\t' && c != CCHR('M'))
336 		panic("broken complt call: c");
337 
338 	nhits = 0;
339 	nxtra = HUGE;
340 
341 	for (; lh != NULL; lh = lh->l_next) {
342 		if (memcmp(buf, lh->l_name, cpos) != 0)
343 			continue;
344 		if (nhits == 0)
345 			lh2 = lh;
346 		++nhits;
347 		if (lh->l_name[cpos] == '\0')
348 			nxtra = -1;
349 		else {
350 			bxtra = getxtra(lh, lh2, cpos, wflag);
351 			if (bxtra < nxtra)
352 				nxtra = bxtra;
353 			lh2 = lh;
354 		}
355 	}
356 	if (nhits == 0)
357 		msg = " [No match]";
358 	else if (nhits > 1 && nxtra == 0)
359 		msg = " [Ambiguous]";
360 	else {
361 		/*
362 		 * Being lazy - ought to check length, but all things
363 		 * autocompleted have known types/lengths.
364 		 */
365 		if (nxtra < 0 && nhits > 1 && c == ' ')
366 			nxtra = 1;
367 		for (i = 0; i < nxtra; ++i) {
368 			buf[cpos] = lh2->l_name[cpos];
369 			eputc(buf[cpos++]);
370 		}
371 		ttflush();
372 		free_file_list(wholelist);
373 		if (nxtra < 0 && c != CCHR('M'))
374 			return 0;
375 		return nxtra;
376 	}
377 
378 	/*
379 	 * wholelist is null if we are doing buffers.  want to free lists
380 	 * that were created for us, but not the buffer list!
381 	 */
382 	free_file_list(wholelist);
383 
384 	/* Set up backspaces, etc., being mindful of echo line limit */
385 	msglen = strlen(msg);
386 	nshown = (ttcol + msglen + 2 > ncol) ?
387 		ncol - ttcol - 2 : msglen;
388 	eputs(msg);
389 	ttcol -= (i = nshown);	/* update ttcol!		 */
390 	while (i--)		/* move back before msg		 */
391 		ttputc('\b');
392 	ttflush();		/* display to user		 */
393 	i = nshown;
394 	while (i--)		/* blank out on next flush	 */
395 		eputc(' ');
396 	ttcol -= (i = nshown);	/* update ttcol on BS's		 */
397 	while (i--)
398 		ttputc('\b');	/* update ttcol again!		 */
399 	return 0;
400 }
401 
402 /*
403  * do completion on a list of objects, listing instead of completing
404  */
405 static int
406 complt_list(flags, c, buf, cpos)
407 	int   flags;
408 	int   c;
409 	char *buf;
410 	int   cpos;
411 {
412 	LIST	*lh, *lh2, *lh3;
413 	LIST	*wholelist = NULL;
414 	BUFFER	*bp;
415 	int	 i, maxwidth, width;
416 	int	 preflen = 0;
417 	int	 oldrow = ttrow;
418 	int	 oldcol = ttcol;
419 	int	 oldhue = tthue;
420 	char	 linebuf[NCOL + 1];
421 	char	*cp;
422 
423 	lh = NULL;
424 
425 	ttflush();
426 
427 	/* the results are put into a help buffer */
428 	bp = bfind("*help*", TRUE);
429 	if (bclear(bp) == FALSE)
430 		return FALSE;
431 
432 	{	/* this {} present for historical reasons */
433 
434 		/*
435 		 * first get the list of objects.  This list may contain only
436 		 * the ones that complete what has been typed, or may be the
437 		 * whole list of all objects of this type.  They are filtered
438 		 * later in any case.  Set wholelist if the list has been
439 		 * cons'ed up just for us, so we can free it later.  We have
440 		 * to copy the buffer list for this function even though we
441 		 * didn't for complt.  The sorting code does destructive
442 		 * changes to the list, which we don't want to happen to the
443 		 * main buffer list!
444 		 */
445 		if ((flags & EFBUF) != 0)
446 			wholelist = lh = copy_list(&(bheadp->b_list));
447 		else if ((flags & EFFUNC) != 0) {
448 			buf[cpos] = '\0';
449 			wholelist = lh = complete_function_list(buf, c);
450 		} else if ((flags & EFFILE) != 0) {
451 			buf[cpos] = '\0';
452 			wholelist = lh = make_file_list(buf);
453 			/*
454 			 * We don't want to display stuff up to the / for file
455 			 * names preflen is the list of a prefix of what the
456 			 * user typed that should not be displayed.
457 			 */
458 			cp = strrchr(buf, '/');
459 			if (cp)
460 				preflen = cp - buf + 1;
461 		} else
462 			panic("broken complt call: flags");
463 
464 
465 		/*
466 		 * Sort the list, since users expect to see it in alphabetic
467 		 * order.
468 		 */
469 		lh2 = lh;
470 		while (lh2) {
471 			lh3 = lh2->l_next;
472 			while (lh3) {
473 				if (strcmp(lh2->l_name, lh3->l_name) > 0) {
474 					cp = lh2->l_name;
475 					lh2->l_name = lh3->l_name;
476 					lh3->l_name = cp;
477 				}
478 				lh3 = lh3->l_next;
479 			}
480 			lh2 = lh2->l_next;
481 		}
482 
483 		/*
484 		 * First find max width of object to be displayed, so we can
485 		 * put several on a line.
486 		 */
487 		maxwidth = 0;
488 		lh2 = lh;
489 		while (lh2 != NULL) {
490 			for (i = 0; i < cpos; ++i) {
491 				if (buf[i] != lh2->l_name[i])
492 					break;
493 			}
494 			if (i == cpos) {
495 				width = strlen(lh2->l_name);
496 				if (width > maxwidth)
497 					maxwidth = width;
498 			}
499 			lh2 = lh2->l_next;
500 		}
501 		maxwidth += 1 - preflen;
502 
503 		/*
504 		 * Now do the display.  objects are written into linebuf until
505 		 * it fills, and then put into the help buffer.
506 		 */
507 		cp = linebuf;
508 		width = 0;
509 		lh2 = lh;
510 		while (lh2 != NULL) {
511 			for (i = 0; i < cpos; ++i) {
512 				if (buf[i] != lh2->l_name[i])
513 					break;
514 			}
515 			if (i == cpos) {
516 				if ((width + maxwidth) > ncol) {
517 					*cp = 0;
518 					addline(bp, linebuf);
519 					cp = linebuf;
520 					width = 0;
521 				}
522 				strcpy(cp, lh2->l_name + preflen);
523 				i = strlen(lh2->l_name + preflen);
524 				cp += i;
525 				for (; i < maxwidth; i++)
526 					*cp++ = ' ';
527 				width += maxwidth;
528 			}
529 			lh2 = lh2->l_next;
530 		}
531 		if (width > 0) {
532 			*cp = 0;
533 			addline(bp, linebuf);
534 		}
535 	}
536 	/*
537 	 * Note that we free lists only if they are put in wholelist lists
538 	 * that were built just for us should be freed.  However when we use
539 	 * the buffer list, obviously we don't want it freed.
540 	 */
541 	free_file_list(wholelist);
542 	popbuftop(bp);		/* split the screen and put up the help
543 				 * buffer */
544 	update();		/* needed to make the new stuff actually
545 				 * appear */
546 	ttmove(oldrow, oldcol);	/* update leaves cursor in arbitrary place */
547 	ttcolor(oldhue);	/* with arbitrary color */
548 	ttflush();
549 	return 0;
550 }
551 
552 /*
553  * The "lp1" and "lp2" point to list structures.  The "cpos" is a horizontal
554  * position in the name.  Return the longest block of characters that can be
555  * autocompleted at this point.  Sometimes the two symbols are the same, but
556  * this is normal.
557  */
558 int
559 getxtra(lp1, lp2, cpos, wflag)
560 	LIST *lp1, *lp2;
561 	int   cpos;
562 	int   wflag;
563 {
564 	int	i;
565 
566 	i = cpos;
567 	for (;;) {
568 		if (lp1->l_name[i] != lp2->l_name[i])
569 			break;
570 		if (lp1->l_name[i] == '\0')
571 			break;
572 		++i;
573 		if (wflag && !ISWORD(lp1->l_name[i - 1]))
574 			break;
575 	}
576 	return (i - cpos);
577 }
578 
579 /*
580  * Special "printf" for the echo line.  Each call to "ewprintf" starts a
581  * new line in the echo area, and ends with an erase to end of the echo
582  * line.  The formatting is done by a call to the standard formatting
583  * routine.
584  */
585 /* VARARGS */
586 void
587 ewprintf(const char *fmt, ...)
588 {
589 	va_list	 ap;
590 
591 #ifndef NO_MACRO
592 	if (inmacro)
593 		return;
594 #endif /* !NO_MACRO */
595 	va_start(ap, fmt);
596 	ttcolor(CTEXT);
597 	ttmove(nrow - 1, 0);
598 	eformat(fmt, ap);
599 	va_end(ap);
600 	tteeol();
601 	ttflush();
602 	epresf = TRUE;
603 }
604 
605 /*
606  * Printf style formatting. This is called by both "ewprintf" and "ereply"
607  * to provide formatting services to their clients.  The move to the start
608  * of the echo line, and the erase to the end of the echo line, is done by
609  * the caller.
610  * Note: %c works, and prints the "name" of the character.
611  * %k prints the name of a key (and takes no arguments).
612  */
613 static void
614 eformat(const char *fp, va_list ap)
615 {
616 	char	kname[NKNAME], *cp;
617 	int	c;
618 
619 	while ((c = *fp++) != '\0') {
620 		if (c != '%')
621 			eputc(c);
622 		else {
623 			c = *fp++;
624 			switch (c) {
625 			case 'c':
626 				keyname(kname, sizeof(kname), va_arg(ap, int));
627 				eputs(kname);
628 				break;
629 
630 			case 'k':
631 				for (cp = kname, c = 0; c < key.k_count; c++) {
632 					if (c)
633 						*cp++ = ' ';
634 					cp = keyname(cp, sizeof(kname) -
635 					    (cp - kname) - 1, key.k_chars[c]);
636 				}
637 				eputs(kname);
638 				break;
639 
640 			case 'd':
641 				eputi(va_arg(ap, int), 10);
642 				break;
643 
644 			case 'o':
645 				eputi(va_arg(ap, int), 8);
646 				break;
647 
648 			case 's':
649 				eputs(va_arg(ap, char *));
650 				break;
651 
652 			case 'l':
653 				/* explicit longword */
654 				c = *fp++;
655 				switch (c) {
656 				case 'd':
657 					eputl(va_arg(ap, long), 10);
658 					break;
659 				default:
660 					eputc(c);
661 					break;
662 				}
663 				break;
664 
665 			default:
666 				eputc(c);
667 			}
668 		}
669 	}
670 }
671 
672 /*
673  * Put integer, in radix "r".
674  */
675 static void
676 eputi(i, r)
677 	int i, r;
678 {
679 	int	 q;
680 
681 	if (i < 0) {
682 		eputc('-');
683 		i = -i;
684 	}
685 	if ((q = i / r) != 0)
686 		eputi(q, r);
687 	eputc(i % r + '0');
688 }
689 
690 /*
691  * Put long, in radix "r".
692  */
693 static void
694 eputl(l, r)
695 	long l;
696 	int  r;
697 {
698 	long	 q;
699 
700 	if (l < 0) {
701 		eputc('-');
702 		l = -l;
703 	}
704 	if ((q = l / r) != 0)
705 		eputl(q, r);
706 	eputc((int)(l % r) + '0');
707 }
708 
709 /*
710  * Put string.
711  */
712 static void
713 eputs(s)
714 	char *s;
715 {
716 	int	 c;
717 
718 	while ((c = *s++) != '\0')
719 		eputc(c);
720 }
721 
722 /*
723  * Put character.  Watch for control characters, and for the line getting
724  * too long.
725  */
726 static void
727 eputc(c)
728 	char	 c;
729 {
730 	if (ttcol + 2 < ncol) {
731 		if (ISCTRL(c)) {
732 			eputc('^');
733 			c = CCHR(c);
734 		}
735 		ttputc(c);
736 		++ttcol;
737 	}
738 }
739 
740 void
741 free_file_list(lp)
742 	LIST *lp;
743 {
744 	LIST	*next;
745 
746 	while (lp) {
747 		next = lp->l_next;
748 		free(lp);
749 		lp = next;
750 	}
751 }
752 
753 static LIST *
754 copy_list(lp)
755 	LIST *lp;
756 {
757 	LIST	*current, *last;
758 
759 	last = NULL;
760 	while (lp) {
761 		current = (LIST *)malloc(sizeof(LIST));
762 		current->l_next = last;
763 		current->l_name = lp->l_name;
764 		last = (LIST *)current;
765 		lp = lp->l_next;
766 	}
767 	return (last);
768 }
769