1 /*- 2 * Copyright (c) 1991, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * Copyright (c) 1991, 1993, 1994, 1995, 1996 5 * Keith Bostic. All rights reserved. 6 * 7 * See the LICENSE file for redistribution information. 8 */ 9 10 #include "config.h" 11 12 #ifndef lint 13 static const char sccsid[] = "$Id: msg.c,v 11.0 2012/10/17 06:34:37 zy Exp $"; 14 #endif /* not lint */ 15 16 #include <sys/types.h> 17 #include <sys/queue.h> 18 #include <sys/stat.h> 19 20 #include <bitstring.h> 21 #include <ctype.h> 22 #include <errno.h> 23 #include <fcntl.h> 24 #include <limits.h> 25 #include <locale.h> 26 #include <stdarg.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <unistd.h> 31 32 #include "common.h" 33 #include "../vi/vi.h" 34 35 /* 36 * msgq -- 37 * Display a message. 38 * 39 * PUBLIC: void msgq __P((SCR *, mtype_t, const char *, ...)); 40 */ 41 void 42 msgq( 43 SCR *sp, 44 mtype_t mt, 45 const char *fmt, 46 ...) 47 { 48 #ifndef NL_ARGMAX 49 #define __NL_ARGMAX 20 /* Set to 9 by System V. */ 50 struct { 51 const char *str; /* String pointer. */ 52 size_t arg; /* Argument number. */ 53 size_t prefix; /* Prefix string length. */ 54 size_t skip; /* Skipped string length. */ 55 size_t suffix; /* Suffix string length. */ 56 } str[__NL_ARGMAX]; 57 #endif 58 static int reenter; /* STATIC: Re-entrancy check. */ 59 GS *gp; 60 size_t blen, len, mlen, nlen; 61 const char *p; 62 char *bp, *mp; 63 va_list ap; 64 #ifndef NL_ARGMAX 65 int ch; 66 char *rbp, *s_rbp; 67 const char *t, *u; 68 size_t cnt1, cnt2, soff; 69 #endif 70 71 /* 72 * !!! 73 * It's possible to enter msg when there's no screen to hold the 74 * message. If sp is NULL, ignore the special cases and put the 75 * message out to stderr. 76 */ 77 if (sp == NULL) { 78 gp = NULL; 79 if (mt == M_BERR) 80 mt = M_ERR; 81 else if (mt == M_VINFO) 82 mt = M_INFO; 83 } else { 84 gp = sp->gp; 85 switch (mt) { 86 case M_BERR: 87 if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) { 88 F_SET(gp, G_BELLSCHED); 89 return; 90 } 91 mt = M_ERR; 92 break; 93 case M_VINFO: 94 if (!O_ISSET(sp, O_VERBOSE)) 95 return; 96 mt = M_INFO; 97 /* FALLTHROUGH */ 98 case M_INFO: 99 if (F_ISSET(sp, SC_EX_SILENT)) 100 return; 101 break; 102 case M_ERR: 103 case M_SYSERR: 104 break; 105 default: 106 abort(); 107 } 108 } 109 110 /* 111 * It's possible to reenter msg when it allocates space. We're 112 * probably dead anyway, but there's no reason to drop core. 113 * 114 * XXX 115 * Yes, there's a race, but it should only be two instructions. 116 */ 117 if (reenter++) 118 return; 119 120 /* Get space for the message. */ 121 nlen = 1024; 122 if (0) { 123 retry: FREE_SPACE(sp, bp, blen); 124 nlen *= 2; 125 } 126 bp = NULL; 127 blen = 0; 128 GET_SPACE_GOTOC(sp, bp, blen, nlen); 129 130 /* 131 * Error prefix. 132 * 133 * mp: pointer to the current next character to be written 134 * mlen: length of the already written characters 135 * blen: total length of the buffer 136 */ 137 #define REM (blen - mlen) 138 mp = bp; 139 mlen = 0; 140 if (mt == M_SYSERR) { 141 p = msg_cat(sp, "020|Error: ", &len); 142 if (REM < len) 143 goto retry; 144 memcpy(mp, p, len); 145 mp += len; 146 mlen += len; 147 } 148 149 /* 150 * If we're running an ex command that the user didn't enter, display 151 * the file name and line number prefix. 152 */ 153 if ((mt == M_ERR || mt == M_SYSERR) && 154 sp != NULL && gp != NULL && gp->if_name != NULL) { 155 CHAR_T *wp; 156 size_t wlen; 157 158 CHAR2INT(sp, gp->if_name, strlen(gp->if_name) + 1, wp, wlen); 159 for (; *wp != '\0'; ++wp) { 160 len = snprintf(mp, REM, "%s", KEY_NAME(sp, *wp)); 161 mp += len; 162 if ((mlen += len) > blen) 163 goto retry; 164 } 165 len = snprintf(mp, REM, ", %d: ", gp->if_lno); 166 mp += len; 167 if ((mlen += len) > blen) 168 goto retry; 169 } 170 171 /* If nothing to format, we're done. */ 172 if (fmt == NULL) 173 goto nofmt; 174 fmt = msg_cat(sp, fmt, NULL); 175 176 #ifndef NL_ARGMAX 177 /* 178 * Nvi should run on machines that don't support the numbered argument 179 * specifications (%[digit]*$). We do this by reformatting the string 180 * so that we can hand it to vsprintf(3) and it will use the arguments 181 * in the right order. When vsprintf returns, we put the string back 182 * into the right order. It's undefined, according to SVID III, to mix 183 * numbered argument specifications with the standard style arguments, 184 * so this should be safe. 185 * 186 * In addition, we also need a character that is known to not occur in 187 * any vi message, for separating the parts of the string. As callers 188 * of msgq are responsible for making sure that all the non-printable 189 * characters are formatted for printing before calling msgq, we use a 190 * random non-printable character selected at terminal initialization 191 * time. This code isn't fast by any means, but as messages should be 192 * relatively short and normally have only a few arguments, it won't be 193 * too bad. Regardless, nobody has come up with any other solution. 194 * 195 * The result of this loop is an array of pointers into the message 196 * string, with associated lengths and argument numbers. The array 197 * is in the "correct" order, and the arg field contains the argument 198 * order. 199 */ 200 for (p = fmt, soff = 0; soff < __NL_ARGMAX;) { 201 for (t = p; *p != '\0' && *p != '%'; ++p); 202 if (*p == '\0') 203 break; 204 ++p; 205 if (!isdigit(*p)) { 206 if (*p == '%') 207 ++p; 208 continue; 209 } 210 for (u = p; *++p != '\0' && isdigit(*p);); 211 if (*p != '$') 212 continue; 213 214 /* Up to, and including the % character. */ 215 str[soff].str = t; 216 str[soff].prefix = u - t; 217 218 /* Up to, and including the $ character. */ 219 str[soff].arg = atoi(u); 220 str[soff].skip = (p - u) + 1; 221 if (str[soff].arg >= __NL_ARGMAX) 222 goto ret; 223 224 /* Up to, and including the conversion character. */ 225 for (u = p; (ch = *++p) != '\0';) 226 if (isalpha(ch) && 227 strchr("diouxXfeEgGcspn", ch) != NULL) 228 break; 229 str[soff].suffix = p - u; 230 if (ch != '\0') 231 ++p; 232 ++soff; 233 } 234 235 /* If no magic strings, we're done. */ 236 if (soff == 0) 237 goto format; 238 239 /* Get space for the reordered strings. */ 240 if ((rbp = malloc(nlen)) == NULL) 241 goto ret; 242 s_rbp = rbp; 243 244 /* 245 * Reorder the strings into the message string based on argument 246 * order. 247 * 248 * !!! 249 * We ignore arguments that are out of order, i.e. if we don't find 250 * an argument, we continue. Assume (almost certainly incorrectly) 251 * that whoever created the string knew what they were doing. 252 * 253 * !!! 254 * Brute force "sort", but since we don't expect more than one or two 255 * arguments in a string, the setup cost of a fast sort will be more 256 * expensive than the loop. 257 */ 258 for (cnt1 = 1; cnt1 <= soff; ++cnt1) 259 for (cnt2 = 0; cnt2 < soff; ++cnt2) 260 if (cnt1 == str[cnt2].arg) { 261 memmove(s_rbp, str[cnt2].str, str[cnt2].prefix); 262 memmove(s_rbp + str[cnt2].prefix, 263 str[cnt2].str + str[cnt2].prefix + 264 str[cnt2].skip, str[cnt2].suffix); 265 s_rbp += str[cnt2].prefix + str[cnt2].suffix; 266 *s_rbp++ = 267 gp == NULL ? DEFAULT_NOPRINT : gp->noprint; 268 break; 269 } 270 *s_rbp = '\0'; 271 fmt = rbp; 272 #endif 273 274 #ifndef NL_ARGMAX 275 format: /* Format the arguments into the string. */ 276 #endif 277 va_start(ap, fmt); 278 len = vsnprintf(mp, REM, fmt, ap); 279 va_end(ap); 280 if (len >= nlen) 281 goto retry; 282 283 #ifndef NL_ARGMAX 284 if (soff == 0) 285 goto nofmt; 286 287 /* 288 * Go through the resulting string, and, for each separator character 289 * separated string, enter its new starting position and length in the 290 * array. 291 */ 292 for (p = t = mp, cnt1 = 1, 293 ch = gp == NULL ? DEFAULT_NOPRINT : gp->noprint; *p != '\0'; ++p) 294 if (*p == ch) { 295 for (cnt2 = 0; cnt2 < soff; ++cnt2) 296 if (str[cnt2].arg == cnt1) 297 break; 298 str[cnt2].str = t; 299 str[cnt2].prefix = p - t; 300 t = p + 1; 301 ++cnt1; 302 } 303 304 /* 305 * Reorder the strings once again, putting them back into the 306 * message buffer. 307 * 308 * !!! 309 * Note, the length of the message gets decremented once for 310 * each substring, when we discard the separator character. 311 */ 312 for (s_rbp = rbp, cnt1 = 0; cnt1 < soff; ++cnt1) { 313 memmove(rbp, str[cnt1].str, str[cnt1].prefix); 314 rbp += str[cnt1].prefix; 315 --len; 316 } 317 memmove(mp, s_rbp, rbp - s_rbp); 318 319 /* Free the reordered string memory. */ 320 free(s_rbp); 321 #endif 322 323 nofmt: mp += len; 324 if ((mlen += len) > blen) 325 goto retry; 326 if (mt == M_SYSERR) { 327 len = snprintf(mp, REM, ": %s", strerror(errno)); 328 mp += len; 329 if ((mlen += len) > blen) 330 goto retry; 331 mt = M_ERR; 332 } 333 334 /* Add trailing newline. */ 335 if ((mlen += 1) > blen) 336 goto retry; 337 *mp = '\n'; 338 339 if (sp != NULL) 340 (void)ex_fflush(sp); 341 if (gp != NULL) 342 gp->scr_msg(sp, mt, bp, mlen); 343 else 344 (void)fprintf(stderr, "%.*s", (int)mlen, bp); 345 346 /* Cleanup. */ 347 #ifndef NL_ARGMAX 348 ret: 349 #endif 350 FREE_SPACE(sp, bp, blen); 351 alloc_err: 352 reenter = 0; 353 } 354 355 /* 356 * msgq_wstr -- 357 * Display a message with an embedded string. 358 * 359 * PUBLIC: void msgq_wstr __P((SCR *, mtype_t, const CHAR_T *, const char *)); 360 */ 361 void 362 msgq_wstr( 363 SCR *sp, 364 mtype_t mtype, 365 const CHAR_T *str, 366 const char *fmt) 367 { 368 size_t nlen; 369 CONST char *nstr; 370 371 if (str == NULL) { 372 msgq(sp, mtype, "%s", fmt); 373 return; 374 } 375 INT2CHAR(sp, str, STRLEN(str) + 1, nstr, nlen); 376 msgq_str(sp, mtype, nstr, fmt); 377 } 378 379 /* 380 * msgq_str -- 381 * Display a message with an embedded string. 382 * 383 * PUBLIC: void msgq_str __P((SCR *, mtype_t, const char *, const char *)); 384 */ 385 void 386 msgq_str( 387 SCR *sp, 388 mtype_t mtype, 389 const char *str, 390 const char *fmt) 391 { 392 int nf, sv_errno; 393 char *p; 394 395 if (str == NULL) { 396 msgq(sp, mtype, "%s", fmt); 397 return; 398 } 399 400 sv_errno = errno; 401 p = msg_print(sp, str, &nf); 402 errno = sv_errno; 403 msgq(sp, mtype, fmt, p); 404 if (nf) 405 FREE_SPACE(sp, p, 0); 406 } 407 408 /* 409 * mod_rpt -- 410 * Report on the lines that changed. 411 * 412 * !!! 413 * Historic vi documentation (USD:15-8) claimed that "The editor will also 414 * always tell you when a change you make affects text which you cannot see." 415 * This wasn't true -- edit a large file and do "100d|1". We don't implement 416 * this semantic since it requires tracking each line that changes during a 417 * command instead of just keeping count. 418 * 419 * Line counts weren't right in historic vi, either. For example, given the 420 * file: 421 * abc 422 * def 423 * the command 2d}, from the 'b' would report that two lines were deleted, 424 * not one. 425 * 426 * PUBLIC: void mod_rpt __P((SCR *)); 427 */ 428 void 429 mod_rpt(SCR *sp) 430 { 431 static char * const action[] = { 432 "293|added", 433 "294|changed", 434 "295|deleted", 435 "296|joined", 436 "297|moved", 437 "298|shifted", 438 "299|yanked", 439 }; 440 static char * const lines[] = { 441 "300|line", 442 "301|lines", 443 }; 444 recno_t total; 445 u_long rptval; 446 int first, cnt; 447 size_t blen, len, tlen; 448 const char *t; 449 char * const *ap; 450 char *bp, *p; 451 452 /* Change reports are turned off in batch mode. */ 453 if (F_ISSET(sp, SC_EX_SILENT)) 454 return; 455 456 /* Reset changing line number. */ 457 sp->rptlchange = OOBLNO; 458 459 /* 460 * Don't build a message if not enough changed. 461 * 462 * !!! 463 * And now, a vi clone test. Historically, vi reported if the number 464 * of changed lines was > than the value, not >=, unless it was a yank 465 * command, which used >=. No lie. Furthermore, an action was never 466 * reported for a single line action. This is consistent for actions 467 * other than yank, but yank didn't report single line actions even if 468 * the report edit option was set to 1. In addition, setting report to 469 * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an 470 * unknown reason (this bug was fixed in System III/V at some point). 471 * I got complaints, so nvi conforms to System III/V historic practice 472 * except that we report a yank of 1 line if report is set to 1. 473 */ 474 #define ARSIZE(a) sizeof(a) / sizeof (*a) 475 #define MAXNUM 25 476 rptval = O_VAL(sp, O_REPORT); 477 for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt) 478 total += sp->rptlines[cnt]; 479 if (total == 0) 480 return; 481 if (total <= rptval && sp->rptlines[L_YANKED] < rptval) { 482 for (cnt = 0; cnt < ARSIZE(action); ++cnt) 483 sp->rptlines[cnt] = 0; 484 return; 485 } 486 487 /* Build and display the message. */ 488 GET_SPACE_GOTOC(sp, bp, blen, sizeof(action) * MAXNUM + 1); 489 for (p = bp, first = 1, tlen = 0, 490 ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt) 491 if (sp->rptlines[cnt] != 0) { 492 if (first) 493 first = 0; 494 else { 495 *p++ = ';'; 496 *p++ = ' '; 497 tlen += 2; 498 } 499 len = snprintf(p, MAXNUM, "%lu ", 500 (u_long)sp->rptlines[cnt]); 501 p += len; 502 tlen += len; 503 t = msg_cat(sp, 504 lines[sp->rptlines[cnt] == 1 ? 0 : 1], &len); 505 memcpy(p, t, len); 506 p += len; 507 tlen += len; 508 *p++ = ' '; 509 ++tlen; 510 t = msg_cat(sp, *ap, &len); 511 memcpy(p, t, len); 512 p += len; 513 tlen += len; 514 sp->rptlines[cnt] = 0; 515 } 516 517 /* Add trailing newline. */ 518 *p = '\n'; 519 ++tlen; 520 521 (void)ex_fflush(sp); 522 sp->gp->scr_msg(sp, M_INFO, bp, tlen); 523 524 FREE_SPACE(sp, bp, blen); 525 alloc_err: 526 return; 527 528 #undef ARSIZE 529 #undef MAXNUM 530 } 531 532 /* 533 * msgq_status -- 534 * Report on the file's status. 535 * 536 * PUBLIC: void msgq_status __P((SCR *, recno_t, u_int)); 537 */ 538 void 539 msgq_status( 540 SCR *sp, 541 recno_t lno, 542 u_int flags) 543 { 544 recno_t last; 545 size_t blen, len; 546 int cnt, needsep; 547 const char *t; 548 char **ap, *bp, *np, *p, *s, *ep; 549 CHAR_T *wp; 550 size_t wlen; 551 552 /* Get sufficient memory. */ 553 len = strlen(sp->frp->name); 554 GET_SPACE_GOTOC(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128); 555 p = bp; 556 ep = bp + blen; 557 558 /* Convert the filename. */ 559 CHAR2INT(sp, sp->frp->name, len + 1, wp, wlen); 560 561 /* Copy in the filename. */ 562 for (; *wp != '\0'; ++wp) { 563 len = KEY_LEN(sp, *wp); 564 memcpy(p, KEY_NAME(sp, *wp), len); 565 p += len; 566 } 567 np = p; 568 *p++ = ':'; 569 *p++ = ' '; 570 571 /* Copy in the argument count. */ 572 if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) { 573 for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt); 574 if (cnt > 1) { 575 (void)snprintf(p, ep - p, 576 msg_cat(sp, "317|%d files to edit", NULL), cnt); 577 p += strlen(p); 578 *p++ = ':'; 579 *p++ = ' '; 580 } 581 F_CLR(sp, SC_STATUS_CNT); 582 } 583 584 /* 585 * See nvi/exf.c:file_init() for a description of how and when the 586 * read-only bit is set. 587 * 588 * !!! 589 * The historic display for "name changed" was "[Not edited]". 590 */ 591 needsep = 0; 592 if (F_ISSET(sp->frp, FR_NEWFILE)) { 593 F_CLR(sp->frp, FR_NEWFILE); 594 t = msg_cat(sp, "021|new file", &len); 595 memcpy(p, t, len); 596 p += len; 597 needsep = 1; 598 } else { 599 if (F_ISSET(sp->frp, FR_NAMECHANGE)) { 600 t = msg_cat(sp, "022|name changed", &len); 601 memcpy(p, t, len); 602 p += len; 603 needsep = 1; 604 } 605 if (needsep) { 606 *p++ = ','; 607 *p++ = ' '; 608 } 609 if (F_ISSET(sp->ep, F_MODIFIED)) 610 t = msg_cat(sp, "023|modified", &len); 611 else 612 t = msg_cat(sp, "024|unmodified", &len); 613 memcpy(p, t, len); 614 p += len; 615 needsep = 1; 616 } 617 if (F_ISSET(sp->frp, FR_UNLOCKED)) { 618 if (needsep) { 619 *p++ = ','; 620 *p++ = ' '; 621 } 622 t = msg_cat(sp, "025|UNLOCKED", &len); 623 memcpy(p, t, len); 624 p += len; 625 needsep = 1; 626 } 627 if (O_ISSET(sp, O_READONLY)) { 628 if (needsep) { 629 *p++ = ','; 630 *p++ = ' '; 631 } 632 t = msg_cat(sp, "026|readonly", &len); 633 memcpy(p, t, len); 634 p += len; 635 needsep = 1; 636 } 637 if (needsep) { 638 *p++ = ':'; 639 *p++ = ' '; 640 } 641 if (LF_ISSET(MSTAT_SHOWLAST)) { 642 if (db_last(sp, &last)) 643 return; 644 if (last == 0) { 645 t = msg_cat(sp, "028|empty file", &len); 646 memcpy(p, t, len); 647 p += len; 648 } else { 649 t = msg_cat(sp, "027|line %lu of %lu [%ld%%]", &len); 650 (void)snprintf(p, ep - p, t, lno, last, 651 ((u_long)lno * 100) / last); 652 p += strlen(p); 653 } 654 } else { 655 t = msg_cat(sp, "029|line %lu", &len); 656 (void)snprintf(p, ep - p, t, (u_long)lno); 657 p += strlen(p); 658 } 659 #ifdef DEBUG 660 (void)snprintf(p, ep - p, " (pid %lu)", (u_long)getpid()); 661 p += strlen(p); 662 #endif 663 *p++ = '\n'; 664 len = p - bp; 665 666 /* 667 * There's a nasty problem with long path names. Cscope and tags files 668 * can result in long paths and vi will request a continuation key from 669 * the user as soon as it starts the screen. Unfortunately, the user 670 * has already typed ahead, and chaos results. If we assume that the 671 * characters in the filenames and informational messages only take a 672 * single screen column each, we can trim the filename. 673 * 674 * XXX 675 * Status lines get put up at fairly awkward times. For example, when 676 * you do a filter read (e.g., :read ! echo foo) in the top screen of a 677 * split screen, we have to repaint the status lines for all the screens 678 * below the top screen. We don't want users having to enter continue 679 * characters for those screens. Make it really hard to screw this up. 680 */ 681 s = bp; 682 if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) { 683 for (; s < np && (*s != '/' || (p - s) > sp->cols - 3); ++s); 684 if (s == np) { 685 s = p - (sp->cols - 5); 686 *--s = ' '; 687 } 688 *--s = '.'; 689 *--s = '.'; 690 *--s = '.'; 691 len = p - s; 692 } 693 694 /* Flush any waiting ex messages. */ 695 (void)ex_fflush(sp); 696 697 sp->gp->scr_msg(sp, M_INFO, s, len); 698 699 FREE_SPACE(sp, bp, blen); 700 alloc_err: 701 return; 702 } 703 704 /* 705 * msg_open -- 706 * Open the message catalogs. 707 * 708 * PUBLIC: int msg_open __P((SCR *, char *)); 709 */ 710 int 711 msg_open( 712 SCR *sp, 713 char *file) 714 { 715 /* 716 * !!! 717 * Assume that the first file opened is the system default, and that 718 * all subsequent ones user defined. Only display error messages 719 * if we can't open the user defined ones -- it's useful to know if 720 * the system one wasn't there, but if nvi is being shipped with an 721 * installed system, the file will be there, if it's not, then the 722 * message will be repeated every time nvi is started up. 723 */ 724 static int first = 1; 725 nl_catd catd; 726 char *p; 727 int rval = 0; 728 729 if ((p = strrchr(file, '/')) != NULL && p[1] == '\0') { 730 /* Confirms to XPG4. */ 731 if ((p = join(file, setlocale(LC_MESSAGES, NULL))) == NULL) { 732 msgq(sp, M_SYSERR, NULL); 733 return (1); 734 } 735 } else { 736 /* Make sure it's recognized as a path by catopen(3). */ 737 if ((p = join(".", file)) == NULL) { 738 msgq(sp, M_SYSERR, NULL); 739 return (1); 740 } 741 } 742 errno = 0; 743 if ((catd = catopen(p, NL_CAT_LOCALE)) == (nl_catd)-1) { 744 if (first) { 745 first = 0; 746 rval = 1; 747 goto ret; 748 } 749 750 /* 751 * POSIX.1-2008 gives no instruction on how to report a 752 * corrupt catalog file. Errno == 0 is not rare; add 753 * EFTYPE, which is seen on FreeBSD, for a good measure. 754 */ 755 if (errno == 0 || errno == EFTYPE) 756 msgq_str(sp, M_ERR, p, 757 "030|The file %s is not a message catalog"); 758 else 759 msgq_str(sp, M_SYSERR, p, "%s"); 760 rval = 1; 761 goto ret; 762 } 763 first = 0; 764 765 msg_close(sp->gp); 766 sp->gp->catd = catd; 767 ret: free(p); 768 return (rval); 769 } 770 771 /* 772 * msg_close -- 773 * Close the message catalogs. 774 * 775 * PUBLIC: void msg_close __P((GS *)); 776 */ 777 void 778 msg_close(GS *gp) 779 { 780 if (gp->catd != (nl_catd)-1) 781 (void)catclose(gp->catd); 782 } 783 784 /* 785 * msg_cont -- 786 * Return common continuation messages. 787 * 788 * PUBLIC: const char *msg_cmsg __P((SCR *, cmsg_t, size_t *)); 789 */ 790 const char * 791 msg_cmsg( 792 SCR *sp, 793 cmsg_t which, 794 size_t *lenp) 795 { 796 switch (which) { 797 case CMSG_CONF: 798 return (msg_cat(sp, "268|confirm? [ynq]", lenp)); 799 case CMSG_CONT: 800 return (msg_cat(sp, "269|Press any key to continue: ", lenp)); 801 case CMSG_CONT_EX: 802 return (msg_cat(sp, 803 "270|Press any key to continue [: to enter more ex commands]: ", 804 lenp)); 805 case CMSG_CONT_R: 806 return (msg_cat(sp, "161|Press Enter to continue: ", lenp)); 807 case CMSG_CONT_S: 808 return (msg_cat(sp, "275| cont?", lenp)); 809 case CMSG_CONT_Q: 810 return (msg_cat(sp, 811 "271|Press any key to continue [q to quit]: ", lenp)); 812 default: 813 abort(); 814 } 815 /* NOTREACHED */ 816 } 817 818 /* 819 * msg_cat -- 820 * Return a single message from the catalog, plus its length. 821 * 822 * !!! 823 * Only a single catalog message can be accessed at a time, if multiple 824 * ones are needed, they must be copied into local memory. 825 * 826 * PUBLIC: const char *msg_cat __P((SCR *, const char *, size_t *)); 827 */ 828 const char * 829 msg_cat( 830 SCR *sp, 831 const char *str, 832 size_t *lenp) 833 { 834 GS *gp; 835 char *p; 836 int msgno; 837 838 /* 839 * If it's not a catalog message, i.e. has doesn't have a leading 840 * number and '|' symbol, we're done. 841 */ 842 if (isdigit(str[0]) && 843 isdigit(str[1]) && isdigit(str[2]) && str[3] == '|') { 844 msgno = atoi(str); 845 str = &str[4]; 846 847 gp = sp == NULL ? NULL : sp->gp; 848 if (gp != NULL && gp->catd != (nl_catd)-1 && 849 (p = catgets(gp->catd, 1, msgno, str)) != NULL) { 850 if (lenp != NULL) 851 *lenp = strlen(p); 852 return (p); 853 } 854 } 855 if (lenp != NULL) 856 *lenp = strlen(str); 857 return (str); 858 } 859 860 /* 861 * msg_print -- 862 * Return a printable version of a string, in allocated memory. 863 * 864 * PUBLIC: char *msg_print __P((SCR *, const char *, int *)); 865 */ 866 char * 867 msg_print( 868 SCR *sp, 869 const char *s, 870 int *needfree) 871 { 872 size_t blen, nlen; 873 char *bp, *ep, *p, *t; 874 CHAR_T *wp, *cp; 875 size_t wlen; 876 877 *needfree = 0; 878 879 /* XXX Not good for debugging ex_read & ex_filter.*/ 880 CHAR2INT5(sp, EXP(sp)->ibcw, (char *)s, strlen(s) + 1, wp, wlen); 881 for (cp = wp; *cp != '\0'; ++cp) 882 if (!ISPRINT(*cp)) 883 break; 884 if (*cp == '\0') 885 return ((char *)s); /* SAFE: needfree set to 0. */ 886 887 nlen = 0; 888 if (0) { 889 retry: if (sp == NULL) 890 free(bp); 891 else 892 FREE_SPACE(sp, bp, blen); 893 *needfree = 0; 894 } 895 nlen += 256; 896 if (sp == NULL) { 897 if ((bp = malloc(nlen)) == NULL) 898 goto alloc_err; 899 } else 900 GET_SPACE_GOTOC(sp, bp, blen, nlen); 901 if (0) { 902 alloc_err: return (""); 903 } 904 *needfree = 1; 905 906 for (p = bp, ep = (bp + blen) - 1; *wp != '\0' && p < ep; ++wp) 907 for (t = KEY_NAME(sp, *wp); *t != '\0' && p < ep; *p++ = *t++); 908 if (p == ep) 909 goto retry; 910 *p = '\0'; 911 return (bp); 912 } 913