diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1996-05-22 11:37:15 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1996-05-22 11:37:15 +0000 |
commit | 0157a77a51c5e35e093ae03581f66dea010edcc8 (patch) | |
tree | 5e8bd32aa4d2b5ed37b7cf3ad26e8bdfc7f20a04 /usr.bin/vi/common/msg.c | |
parent | 806021be093ad00ce2022a532c0f4cc99b0065ac (diff) |
new vi
Diffstat (limited to 'usr.bin/vi/common/msg.c')
-rw-r--r-- | usr.bin/vi/common/msg.c | 1010 |
1 files changed, 726 insertions, 284 deletions
diff --git a/usr.bin/vi/common/msg.c b/usr.bin/vi/common/msg.c index 36639c1c347..27b87b91014 100644 --- a/usr.bin/vi/common/msg.c +++ b/usr.bin/vi/common/msg.c @@ -1,53 +1,32 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. + * Copyright (c) 1991, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * See the LICENSE file for redistribution information. */ +#include "config.h" + #ifndef lint -static char sccsid[] = "@(#)msg.c 8.12 (Berkeley) 8/17/94"; +static const char sccsid[] = "@(#)msg.c 10.36 (Berkeley) 5/15/96"; #endif /* not lint */ -#include <sys/types.h> +#include <sys/param.h> +#include <sys/types.h> /* XXX: param.h may not have included types.h */ #include <sys/queue.h> +#include <sys/stat.h> #include <sys/time.h> #include <bitstring.h> +#include <ctype.h> #include <errno.h> #include <fcntl.h> #include <limits.h> -#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <termios.h> #include <unistd.h> #ifdef __STDC__ @@ -56,195 +35,361 @@ static char sccsid[] = "@(#)msg.c 8.12 (Berkeley) 8/17/94"; #include <varargs.h> #endif -#include "compat.h" -#include <db.h> -#include <regex.h> - -#include "vi.h" +#include "common.h" +#include "../vi/vi.h" /* * msgq -- * Display a message. + * + * PUBLIC: void msgq __P((SCR *, mtype_t, const char *, ...)); */ void #ifdef __STDC__ -msgq(SCR *sp, enum msgtype mt, const char *fmt, ...) +msgq(SCR *sp, mtype_t mt, const char *fmt, ...) #else msgq(sp, mt, fmt, va_alist) SCR *sp; - enum msgtype mt; - char *fmt; + mtype_t mt; + const char *fmt; va_dcl #endif { +#ifndef NL_ARGMAX +#define __NL_ARGMAX 20 /* Set to 9 by System V. */ + struct { + const char *str; /* String pointer. */ + size_t arg; /* Argument number. */ + size_t prefix; /* Prefix string length. */ + size_t skip; /* Skipped string length. */ + size_t suffix; /* Suffix string length. */ + } str[__NL_ARGMAX]; +#endif + static int reenter; /* STATIC: Re-entrancy check. */ + CHAR_T ch; + GS *gp; + size_t blen, cnt1, cnt2, len, mlen, nlen, soff; + const char *p, *t, *u; + char *bp, *mp, *rbp, *s_rbp; va_list ap; - size_t len; - char msgbuf[1024]; -#ifdef __STDC__ - va_start(ap, fmt); -#else - va_start(ap); -#endif /* - * It's possible to enter msg when there's no screen to hold - * the message. If sp is NULL, ignore the special cases and - * just build the message, using __global_list. + * !!! + * It's possible to enter msg when there's no screen to hold the + * message. If sp is NULL, ignore the special cases and put the + * message out to stderr. */ - if (sp == NULL) - goto nullsp; - - switch (mt) { - case M_BERR: - if (!F_ISSET(sp, S_EXSILENT) && - F_ISSET(sp->gp, G_STDIN_TTY) && !O_ISSET(sp, O_VERBOSE)) { - F_SET(sp, S_BELLSCHED); - return; + if (sp == NULL) { + gp = NULL; + if (mt == M_BERR) + mt = M_ERR; + else if (mt == M_VINFO) + mt = M_INFO; + } else { + gp = sp->gp; + switch (mt) { + case M_BERR: + if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) { + F_SET(gp, G_BELLSCHED); + return; + } + mt = M_ERR; + break; + case M_VINFO: + if (!O_ISSET(sp, O_VERBOSE)) + return; + mt = M_INFO; + /* FALLTHROUGH */ + case M_INFO: + if (F_ISSET(sp, SC_EX_SILENT)) + return; + break; + case M_ERR: + case M_SYSERR: + break; + default: + abort(); } - mt = M_ERR; - break; - case M_VINFO: - if (!O_ISSET(sp, O_VERBOSE)) - return; - mt = M_INFO; - /* FALLTHROUGH */ - case M_INFO: - if (F_ISSET(sp, S_EXSILENT)) - return; - break; - case M_ERR: - case M_SYSERR: - break; - default: - abort(); } -nullsp: len = 0; + /* + * It's possible to reenter msg when it allocates space. We're + * probably dead anyway, but there's no reason to drop core. + * + * XXX + * Yes, there's a race, but it should only be two instructions. + */ + if (reenter++) + return; -#define EPREFIX "Error: " - if (mt == M_SYSERR) { - memmove(msgbuf, EPREFIX, sizeof(EPREFIX) - 1); - len += sizeof(EPREFIX) - 1; + /* Get space for the message. */ + nlen = 1024; + if (0) { +retry: FREE_SPACE(sp, bp, blen); + nlen *= 2; } + bp = NULL; + blen = 0; + GET_SPACE_GOTO(sp, bp, blen, nlen); - if (sp != NULL && sp->if_name != NULL) { - len += snprintf(msgbuf + len, sizeof(msgbuf) - len, - "%s, %d: ", sp->if_name, sp->if_lno); - if (len >= sizeof(msgbuf)) - goto err; + /* + * Error prefix. + * + * mp: pointer to the current next character to be written + * mlen: length of the already written characters + * blen: total length of the buffer + */ +#define REM (blen - mlen) + mp = bp; + mlen = 0; + if (mt == M_SYSERR) { + p = msg_cat(sp, "020|Error: ", &len); + if (REM < len) + goto retry; + memmove(mp, p, len); + mp += len; + mlen += len; } - if (fmt != NULL) { - len += vsnprintf(msgbuf + len, sizeof(msgbuf) - len, fmt, ap); - if (len >= sizeof(msgbuf)) - goto err; + /* + * If we're running an ex command that the user didn't enter, display + * the file name and line number prefix. + */ + if ((mt == M_ERR || mt == M_SYSERR) && + sp != NULL && gp != NULL && gp->if_name != NULL) { + for (p = gp->if_name; *p != '\0'; ++p) { + len = snprintf(mp, REM, "%s", KEY_NAME(sp, *p)); + mp += len; + if ((mlen += len) > blen) + goto retry; + } + len = snprintf(mp, REM, ", %d: ", gp->if_lno); + mp += len; + if ((mlen += len) > blen) + goto retry; } - if (mt == M_SYSERR) { - len += snprintf(msgbuf + len, - sizeof(msgbuf) - len, ": %s", strerror(errno)); - if (len >= sizeof(msgbuf)) - goto err; - } + /* If nothing to format, we're done. */ + if (fmt == NULL) + goto nofmt; + fmt = msg_cat(sp, fmt, NULL); +#ifndef NL_ARGMAX /* - * If len >= the size, some characters were discarded. - * Ignore trailing nul. + * Nvi should run on machines that don't support the numbered argument + * specifications (%[digit]*$). We do this by reformatting the string + * so that we can hand it to vsprintf(3) and it will use the arguments + * in the right order. When vsprintf returns, we put the string back + * into the right order. It's undefined, according to SVID III, to mix + * numbered argument specifications with the standard style arguments, + * so this should be safe. + * + * In addition, we also need a character that is known to not occur in + * any vi message, for separating the parts of the string. As callers + * of msgq are responsible for making sure that all the non-printable + * characters are formatted for printing before calling msgq, we use a + * random non-printable character selected at terminal initialization + * time. This code isn't fast by any means, but as messages should be + * relatively short and normally have only a few arguments, it won't be + * too bad. Regardless, nobody has come up with any other solution. + * + * The result of this loop is an array of pointers into the message + * string, with associated lengths and argument numbers. The array + * is in the "correct" order, and the arg field contains the argument + * order. */ -err: if (len >= sizeof(msgbuf)) - len = sizeof(msgbuf) - 1; + for (p = fmt, soff = 0; soff < __NL_ARGMAX;) { + for (t = p; *p != '\0' && *p != '%'; ++p); + if (*p == '\0') + break; + ++p; + if (!isdigit(*p)) { + if (*p == '%') + ++p; + continue; + } + for (u = p; *++p != '\0' && isdigit(*p);); + if (*p != '$') + continue; + + /* Up to, and including the % character. */ + str[soff].str = t; + str[soff].prefix = u - t; + + /* Up to, and including the $ character. */ + str[soff].arg = atoi(u); + str[soff].skip = (p - u) + 1; + if (str[soff].arg >= __NL_ARGMAX) + goto ret; -#ifdef DEBUG - if (sp != NULL) - TRACE(sp, "%.*s\n", len, msgbuf); -#endif - msg_app(__global_list, sp, mt == M_ERR ? 1 : 0, msgbuf, len); -} + /* Up to, and including the conversion character. */ + for (u = p; (ch = *++p) != '\0';) + if (isalpha(ch) && + strchr("diouxXfeEgGcspn", ch) != NULL) + break; + str[soff].suffix = p - u; + if (ch != '\0') + ++p; + ++soff; + } -/* - * msg_app -- - * Append a message into the queue. This can fail, but there's - * nothing we can do if it does. - */ -void -msg_app(gp, sp, inv_video, p, len) - GS *gp; - SCR *sp; - int inv_video; - char *p; - size_t len; -{ - static int reenter; /* STATIC: Re-entrancy check. */ - MSG *mp, *nmp; + /* If no magic strings, we're done. */ + if (soff == 0) + goto format; + + /* Get space for the reordered strings. */ + if ((rbp = malloc(nlen)) == NULL) + goto ret; + s_rbp = rbp; /* - * It's possible to reenter msg when it allocates space. - * We're probably dead anyway, but no reason to drop core. + * Reorder the strings into the message string based on argument + * order. + * + * !!! + * We ignore arguments that are out of order, i.e. if we don't find + * an argument, we continue. Assume (almost certainly incorrectly) + * that whoever created the string knew what they were doing. + * + * !!! + * Brute force "sort", but since we don't expect more than one or two + * arguments in a string, the setup cost of a fast sort will be more + * expensive than the loop. */ - if (reenter) - return; - reenter = 1; + for (cnt1 = 1; cnt1 <= soff; ++cnt1) + for (cnt2 = 0; cnt2 < soff; ++cnt2) + if (cnt1 == str[cnt2].arg) { + memmove(s_rbp, str[cnt2].str, str[cnt2].prefix); + memmove(s_rbp + str[cnt2].prefix, + str[cnt2].str + str[cnt2].prefix + + str[cnt2].skip, str[cnt2].suffix); + s_rbp += str[cnt2].prefix + str[cnt2].suffix; + *s_rbp++ = + gp == NULL ? DEFAULT_NOPRINT : gp->noprint; + break; + } + *s_rbp = '\0'; + fmt = rbp; +#endif + +format: /* Format the arguments into the string. */ +#ifdef __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + len = vsnprintf(mp, REM, fmt, ap); + va_end(ap); + if (len >= nlen) + goto retry; + +#ifndef NL_ARGMAX + if (soff == 0) + goto nofmt; /* - * We can be entered as the result of a signal arriving, trying - * to sync the file and failing. This shouldn't be a hot spot, - * block the signals. + * Go through the resulting string, and, for each separator character + * separated string, enter its new starting position and length in the + * array. */ - SIGBLOCK(gp); + for (p = t = mp, cnt1 = 1, + ch = gp == NULL ? DEFAULT_NOPRINT : gp->noprint; *p != '\0'; ++p) + if (*p == ch) { + for (cnt2 = 0; cnt2 < soff; ++cnt2) + if (str[cnt2].arg == cnt1) + break; + str[cnt2].str = t; + str[cnt2].prefix = p - t; + t = p + 1; + ++cnt1; + } /* - * Find an empty structure, or allocate a new one. Use the - * screen structure if it exists, otherwise the global one. + * Reorder the strings once again, putting them back into the + * message buffer. + * + * !!! + * Note, the length of the message gets decremented once for + * each substring, when we discard the separator character. */ - if (sp != NULL) { - if ((mp = sp->msgq.lh_first) == NULL) { - CALLOC(sp, mp, MSG *, 1, sizeof(MSG)); - if (mp == NULL) - goto ret; - LIST_INSERT_HEAD(&sp->msgq, mp, q); - goto store; - } - } else if ((mp = gp->msgq.lh_first) == NULL) { - CALLOC(sp, mp, MSG *, 1, sizeof(MSG)); - if (mp == NULL) - goto ret; - LIST_INSERT_HEAD(&gp->msgq, mp, q); - goto store; + for (s_rbp = rbp, cnt1 = 0; cnt1 < soff; ++cnt1) { + memmove(rbp, str[cnt1].str, str[cnt1].prefix); + rbp += str[cnt1].prefix; + --len; } - while (!F_ISSET(mp, M_EMPTY) && mp->q.le_next != NULL) - mp = mp->q.le_next; - if (!F_ISSET(mp, M_EMPTY)) { - CALLOC(sp, nmp, MSG *, 1, sizeof(MSG)); - if (nmp == NULL) - goto ret; - LIST_INSERT_AFTER(mp, nmp, q); - mp = nmp; + memmove(mp, s_rbp, rbp - s_rbp); + + /* Free the reordered string memory. */ + free(s_rbp); +#endif + +nofmt: mp += len; + if ((mlen += len) > blen) + goto retry; + if (mt == M_SYSERR) { + len = snprintf(mp, REM, ": %s", strerror(errno)); + mp += len; + if ((mlen += len) > blen) + goto retry; + mt = M_ERR; } - /* Get enough memory for the message. */ -store: if (len > mp->blen && - (mp->mbuf = binc(sp, mp->mbuf, &mp->blen, len)) == NULL) - goto ret; + /* Add trailing newline. */ + if ((mlen += 1) > blen) + goto retry; + *mp = '\n'; - /* Store the message. */ - memmove(mp->mbuf, p, len); - mp->len = len; - mp->flags = inv_video ? M_INV_VIDEO : 0; + if (sp != NULL) + (void)ex_fflush(sp); + if (gp != NULL) + gp->scr_msg(sp, mt, bp, mlen); + else + (void)fprintf(stderr, "%.*s", (int)mlen, bp); + + /* Cleanup. */ +ret: FREE_SPACE(sp, bp, blen); +alloc_err: + reenter = 0; +} -ret: reenter = 0; - SIGUNBLOCK(gp); +/* + * msgq_str -- + * Display a message with an embedded string. + * + * PUBLIC: void msgq_str __P((SCR *, mtype_t, char *, char *)); + */ +void +msgq_str(sp, mtype, str, fmt) + SCR *sp; + mtype_t mtype; + char *str, *fmt; +{ + int nf, sv_errno; + char *p; + + if (str == NULL) { + msgq(sp, mtype, fmt); + return; + } + + sv_errno = errno; + p = msg_print(sp, str, &nf); + errno = sv_errno; + msgq(sp, mtype, fmt, p); + if (nf) + FREE_SPACE(sp, p, 0); } /* - * msg_rpt -- + * msgq_rpt -- * Report on the lines that changed. * * !!! * Historic vi documentation (USD:15-8) claimed that "The editor will also * always tell you when a change you make affects text which you cannot see." - * This isn't true -- edit a large file and do "100d|1". We don't implement - * this semantic as it would require that we track each line that changes - * during a command instead of just keeping count. + * This wasn't true -- edit a large file and do "100d|1". We don't implement + * this semantic since it requires tracking each line that changes during a + * command instead of just keeping count. * * Line counts weren't right in historic vi, either. For example, given the * file: @@ -252,177 +397,474 @@ ret: reenter = 0; * def * the command 2d}, from the 'b' would report that two lines were deleted, * not one. + * + * PUBLIC: void msgq_rpt __P((SCR *)); */ -int -msg_rpt(sp, is_message) +void +msgq_rpt(sp) SCR *sp; - int is_message; { static char * const action[] = { - "added", "changed", "deleted", "joined", "moved", - "left shifted", "right shifted", "yanked", - NULL, + "293|added", + "294|changed", + "295|deleted", + "296|joined", + "297|moved", + "298|shifted", + "299|yanked", + }; + static char * const lines[] = { + "300|line", + "301|lines", }; recno_t total; u_long rptval; int first, cnt; - size_t blen, len; + size_t blen, len, tlen; + const char *t; char * const *ap; - char *bp, *p, number[40]; - - if (F_ISSET(sp, S_EXSILENT)) - return (0); + char *bp, *p; - if ((rptval = O_VAL(sp, O_REPORT)) == 0) - goto norpt; - - GET_SPACE_RET(sp, bp, blen, 512); - p = bp; + /* Change reports are turned off in batch mode. */ + if (F_ISSET(sp, SC_EX_SILENT)) + return; - total = 0; - for (ap = action, cnt = 0, first = 1; *ap != NULL; ++ap, ++cnt) - if (sp->rptlines[cnt] != 0) { - total += sp->rptlines[cnt]; - len = snprintf(number, sizeof(number), - "%s%lu lines %s", - first ? "" : "; ", sp->rptlines[cnt], *ap); - memmove(p, number, len); - p += len; - first = 0; - } + /* Reset changing line number. */ + sp->rptlchange = OOBLNO; /* - * If nothing to report, return. + * Don't build a message if not enough changed. * * !!! - * And now, a special vi clone test. Historically, vi reported if - * the number of changed lines was > than the value, not >=. Which - * means that users can't report on single line changes, btw.) In - * any case, if it was a yank command, it was >=, not >. No lie. I - * got complaints, so we do it right. + * And now, a vi clone test. Historically, vi reported if the number + * of changed lines was > than the value, not >=, unless it was a yank + * command, which used >=. No lie. Furthermore, an action was never + * reported for a single line action. This is consistent for actions + * other than yank, but yank didn't report single line actions even if + * the report edit option was set to 1. In addition, setting report to + * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an + * unknown reason (this bug was fixed in System III/V at some point). + * I got complaints, so nvi conforms to System III/V historic practice + * except that we report a yank of 1 line if report is set to 1. */ - if (total > rptval || sp->rptlines[L_YANKED] >= rptval) { - *p = '\0'; - if (is_message) - msgq(sp, M_INFO, "%s", bp); - else - ex_printf(EXCOOKIE, "%s\n", bp); +#define ARSIZE(a) sizeof(a) / sizeof (*a) +#define MAXNUM 25 + rptval = O_VAL(sp, O_REPORT); + for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt) + total += sp->rptlines[cnt]; + if (total == 0) + return; + if (total <= rptval && sp->rptlines[L_YANKED] < rptval) { + for (cnt = 0; cnt < ARSIZE(action); ++cnt) + sp->rptlines[cnt] = 0; + return; } + /* Build and display the message. */ + GET_SPACE_GOTO(sp, bp, blen, sizeof(action) * MAXNUM + 1); + for (p = bp, first = 1, tlen = 0, + ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt) + if (sp->rptlines[cnt] != 0) { + if (first) + first = 0; + else { + *p++ = ';'; + *p++ = ' '; + tlen += 2; + } + len = snprintf(p, MAXNUM, "%lu ", sp->rptlines[cnt]); + p += len; + tlen += len; + t = msg_cat(sp, + lines[sp->rptlines[cnt] == 1 ? 0 : 1], &len); + memmove(p, t, len); + p += len; + tlen += len; + *p++ = ' '; + ++tlen; + t = msg_cat(sp, *ap, &len); + memmove(p, t, len); + p += len; + tlen += len; + sp->rptlines[cnt] = 0; + } + + /* Add trailing newline. */ + *p = '\n'; + ++tlen; + + (void)ex_fflush(sp); + sp->gp->scr_msg(sp, M_INFO, bp, tlen); + FREE_SPACE(sp, bp, blen); +alloc_err: + return; - /* Clear after each report. */ -norpt: sp->rptlchange = OOBLNO; - memset(sp->rptlines, 0, sizeof(sp->rptlines)); - return (0); +#undef ARSIZE +#undef MAXNUM } /* - * msg_status -- + * msgq_status -- * Report on the file's status. + * + * PUBLIC: void msgq_status __P((SCR *, recno_t, u_int)); */ -int -msg_status(sp, ep, lno, showlast) +void +msgq_status(sp, lno, flags) SCR *sp; - EXF *ep; recno_t lno; - int showlast; + u_int flags; { recno_t last; - char *mo, *nc, *nf, *pid, *ro, *ul; -#ifdef DEBUG - char pbuf[50]; + const char *t; + char *bp, *np, *p, *s; + int needsep; + size_t blen, len; + + len = strlen(sp->frp->name); + GET_SPACE_GOTO(sp, bp, blen, len + 128); + p = bp; + + memmove(p, sp->frp->name, len); + p += len; + np = p; + *p++ = ':'; + *p++ = ' '; - (void)snprintf(pbuf, sizeof(pbuf), " (pid %u)", getpid()); - pid = pbuf; -#else - pid = ""; -#endif /* - * See nvi/exf.c:file_init() for a description of how and - * when the read-only bit is set. + * See nvi/exf.c:file_init() for a description of how and when the + * read-only bit is set. * * !!! * The historic display for "name changed" was "[Not edited]". */ + needsep = 0; if (F_ISSET(sp->frp, FR_NEWFILE)) { F_CLR(sp->frp, FR_NEWFILE); - nf = "new file"; - mo = nc = ""; + t = msg_cat(sp, "021|new file", &len); + memmove(p, t, len); + p += len; + needsep = 1; } else { - nf = ""; if (F_ISSET(sp->frp, FR_NAMECHANGE)) { - nc = "name changed"; - mo = F_ISSET(ep, F_MODIFIED) ? - ", modified" : ", unmodified"; + t = msg_cat(sp, "022|name changed", &len); + memmove(p, t, len); + p += len; + needsep = 1; + } + if (needsep) { + *p++ = ','; + *p++ = ' '; + } + if (F_ISSET(sp->ep, F_MODIFIED)) + t = msg_cat(sp, "023|modified", &len); + else + t = msg_cat(sp, "024|unmodified", &len); + memmove(p, t, len); + p += len; + needsep = 1; + } + if (F_ISSET(sp->frp, FR_UNLOCKED)) { + if (needsep) { + *p++ = ','; + *p++ = ' '; + } + t = msg_cat(sp, "025|UNLOCKED", &len); + memmove(p, t, len); + p += len; + needsep = 1; + } + if (O_ISSET(sp, O_READONLY)) { + if (needsep) { + *p++ = ','; + *p++ = ' '; + } + t = msg_cat(sp, "026|readonly", &len); + memmove(p, t, len); + p += len; + needsep = 1; + } + if (needsep) { + *p++ = ':'; + *p++ = ' '; + } + if (LF_ISSET(MSTAT_SHOWLAST)) { + if (db_last(sp, &last)) + return; + if (last == 0) { + t = msg_cat(sp, "028|empty file", &len); + memmove(p, t, len); + p += len; } else { - nc = ""; - mo = F_ISSET(ep, F_MODIFIED) ? - "modified" : "unmodified"; + t = msg_cat(sp, "027|line %lu of %lu [%ld%%]", &len); + (void)sprintf(p, t, lno, last, (lno * 100) / last); + p += strlen(p); } + } else { + t = msg_cat(sp, "029|line %lu", &len); + (void)sprintf(p, t, lno); + p += strlen(p); } - ro = F_ISSET(sp->frp, FR_RDONLY) ? ", readonly" : ""; - ul = F_ISSET(sp->frp, FR_UNLOCKED) ? ", UNLOCKED" : ""; - if (showlast) { - if (file_lline(sp, ep, &last)) - return (1); - if (last >= 1) - msgq(sp, M_INFO, - "%s: %s%s%s%s%s: line %lu of %lu [%ld%%]%s", - sp->frp->name, nf, nc, mo, ul, ro, lno, - last, (lno * 100) / last, pid); - else - msgq(sp, M_INFO, "%s: %s%s%s%s%s: empty file%s", - sp->frp->name, nf, nc, mo, ul, ro, pid); - } else - msgq(sp, M_INFO, "%s: %s%s%s%s%s: line %lu%s", - sp->frp->name, nf, nc, mo, ul, ro, lno, pid); - return (0); +#ifdef DEBUG + (void)sprintf(p, " (pid %lu)", (u_long)getpid()); + p += strlen(p); +#endif + *p++ = '\n'; + + /* + * There's a nasty problem with long path names. Cscope and tags files + * can result in long paths and vi will request a continuation key from + * the user as soon as it starts the screen. Unfortunately, the user + * has already typed ahead, and chaos results. If we assume that the + * characters in the filenames and informational messages only take a + * single screen column each, we can trim the filename. + */ + s = bp; + if (LF_ISSET(MSTAT_TRUNCATE)) + if ((p - s) >= sp->cols) { + for (; s < np && + (*s != '/' || (p - s) > sp->cols - 3); ++s); + if (s == np) + s = bp; + else { + *--s = '.'; + *--s = '.'; + *--s = '.'; + } + } + len = p - s; + + /* Flush any waiting ex messages. */ + (void)ex_fflush(sp); + + sp->gp->scr_msg(sp, M_INFO, s, len); + + FREE_SPACE(sp, bp, blen); +alloc_err: + return; } -#ifdef MSG_CATALOG /* - * get_msg -- - * Return a format based on a message number. + * msg_open -- + * Open the message catalogs. + * + * PUBLIC: int msg_open __P((SCR *, char *)); */ -char * -get_msg(sp, msgno) +int +msg_open(sp, file) SCR *sp; - char *s_msgno; + char *file; { + /* + * !!! + * Assume that the first file opened is the system default, and that + * all subsequent ones user defined. Only display error messages + * if we can't open the user defined ones -- it's useful to know if + * the system one wasn't there, but if nvi is being shipped with an + * installed system, the file will be there, if it's not, then the + * message will be repeated every time nvi is started up. + */ + static int first = 1; + DB *db; DBT data, key; - GS *gp; recno_t msgno; - char *msg, *p; - - gp = sp == NULL ? __global_list : sp->gp; - if (gp->msgdb == NULL) { - p = sp == NULL ? _PATH_MSGDEF : O_STR(sp, O_CATALOG); - if ((gp->msgdb = dbopen(p, - O_NONBLOCK | O_RDONLY, 444, DB_RECNO, NULL)) == NULL) { - if ((fmt = malloc(256)) == NULL) - return (""); - (void)snprintf(fmt, - "unable to open %s: %s", p, strerror(errno)); - return (fmt); + char *p, *t, buf[MAXPATHLEN]; + + if ((p = strrchr(file, '/')) != NULL && p[1] == '\0' && + ((t = getenv("LANG")) != NULL && t[0] != '\0' || + (t = getenv("LC_MESSAGES")) != NULL && t[0] != '\0')) { + (void)snprintf(buf, sizeof(buf), "%svi_%s", file, t); + p = buf; + } else + p = file; + if ((db = dbopen(p, + O_NONBLOCK | O_RDONLY, 0, DB_RECNO, NULL)) == NULL) { + if (first) { + first = 0; + return (1); } + msgq_str(sp, M_SYSERR, p, "%s"); + return (1); } - msgno = atoi(s_msgno); + + /* + * Test record 1 for the magic string. The msgq call is here so + * the message catalog build finds it. + */ +#define VMC "VI_MESSAGE_CATALOG" key.data = &msgno; key.size = sizeof(recno_t); - switch (gp->msgdb->get(gp->msgdb, &key, &data, 0)) { - case 0: - return (data.data); - case 1: - p = "no catalog record %ls"; - break; - case -1: - p = "catalog record %s: %s"; - break; + msgno = 1; + if (db->get(db, &key, &data, 0) != 0 || + data.size != sizeof(VMC) - 1 || + memcmp(data.data, VMC, sizeof(VMC) - 1)) { + (void)db->close(db); + if (first) { + first = 0; + return (1); + } + msgq_str(sp, M_ERR, p, + "030|The file %s is not a message catalog"); + return (1); } - if ((fmt = malloc(256)) == NULL) - return (""); - (void)snprintf(fmt, p, msgno, strerror(errno)); - return (fmt); + first = 0; + + if (sp->gp->msg != NULL) + (void)sp->gp->msg->close(sp->gp->msg); + sp->gp->msg = db; + return (0); +} + +/* + * msg_close -- + * Close the message catalogs. + * + * PUBLIC: void msg_close __P((GS *)); + */ +void +msg_close(gp) + GS *gp; +{ + if (gp->msg != NULL) + (void)gp->msg->close(gp->msg); +} + +/* + * msg_cont -- + * Return common continuation messages. + * + * PUBLIC: const char *msg_cmsg __P((SCR *, cmsg_t, size_t *)); + */ +const char * +msg_cmsg(sp, which, lenp) + SCR *sp; + cmsg_t which; + size_t *lenp; +{ + switch (which) { + case CMSG_CONF: + return (msg_cat(sp, "268|confirm? [ynq]", lenp)); + case CMSG_CONT: + return (msg_cat(sp, "269|Press any key to continue: ", lenp)); + case CMSG_CONT_EX: + return (msg_cat(sp, + "270|Press any key to continue [: to enter more ex commands]: ", + lenp)); + case CMSG_CONT_R: + return (msg_cat(sp, "161|Press Enter to continue: ", lenp)); + case CMSG_CONT_S: + return (msg_cat(sp, "275| cont?", lenp)); + case CMSG_CONT_Q: + return (msg_cat(sp, + "271|Press any key to continue [q to quit]: ", lenp)); + default: + abort(); + } + /* NOTREACHED */ +} + +/* + * msg_cat -- + * Return a single message from the catalog, plus its length. + * + * !!! + * Only a single catalog message can be accessed at a time, if multiple + * ones are needed, they must be copied into local memory. + * + * PUBLIC: const char *msg_cat __P((SCR *, const char *, size_t *)); + */ +const char * +msg_cat(sp, str, lenp) + SCR *sp; + const char *str; + size_t *lenp; +{ + GS *gp; + DBT data, key; + recno_t msgno; + + /* + * If it's not a catalog message, i.e. has doesn't have a leading + * number and '|' symbol, we're done. + */ + if (isdigit(str[0]) && + isdigit(str[1]) && isdigit(str[2]) && str[3] == '|') { + key.data = &msgno; + key.size = sizeof(recno_t); + msgno = atoi(str); + + /* + * XXX + * Really sleazy hack -- we put an extra character on the + * end of the format string, and then we change it to be + * the nul termination of the string. There ought to be + * a better way. Once we can allocate multiple temporary + * memory buffers, maybe we can use one of them instead. + */ + gp = sp == NULL ? NULL : sp->gp; + if (gp != NULL && gp->msg != NULL && + gp->msg->get(gp->msg, &key, &data, 0) == 0 && + data.size != 0) { + if (lenp != NULL) + *lenp = data.size - 1; + ((char *)data.data)[data.size - 1] = '\0'; + return (data.data); + } + str = &str[4]; + } + if (lenp != NULL) + *lenp = strlen(str); + return (str); +} + +/* + * msg_print -- + * Return a printable version of a string, in allocated memory. + * + * PUBLIC: char *msg_print __P((SCR *, const char *, int *)); + */ +char * +msg_print(sp, s, needfree) + SCR *sp; + const char *s; + int *needfree; +{ + size_t blen, nlen; + const char *cp; + char *bp, *ep, *p, *t; + + *needfree = 0; + + for (cp = s; *cp != '\0'; ++cp) + if (!isprint(*cp)) + break; + if (*cp == '\0') + return ((char *)s); /* SAFE: needfree set to 0. */ + + nlen = 0; + if (0) { +retry: if (sp == NULL) + free(bp); + else + FREE_SPACE(sp, bp, blen); + needfree = 0; + } + nlen += 256; + if (sp == NULL) { + if ((bp = malloc(nlen)) == NULL) + goto alloc_err; + } else + GET_SPACE_GOTO(sp, bp, blen, nlen); + if (0) { +alloc_err: return (""); + } + *needfree = 1; + + for (p = bp, ep = (bp + blen) - 1, cp = s; *cp != '\0' && p < ep; ++cp) + for (t = KEY_NAME(sp, *cp); *t != '\0' && p < ep; *p++ = *t++); + if (p == ep) + goto retry; + *p = '\0'; + return (bp); } -#endif |