home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Spezial / SPEZIAL2_97.zip / SPEZIAL2_97.iso / ANWEND / EDITOR / NVI179B / NVI179B.ZIP / common / msg.c < prev    next >
C/C++ Source or Header  |  1996-09-15  |  21KB  |  896 lines

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