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