119304Speter/*- 219304Speter * Copyright (c) 1991, 1993, 1994 319304Speter * The Regents of the University of California. All rights reserved. 419304Speter * Copyright (c) 1991, 1993, 1994, 1995, 1996 519304Speter * Keith Bostic. All rights reserved. 619304Speter * 719304Speter * See the LICENSE file for redistribution information. 819304Speter */ 919304Speter 1019304Speter#include "config.h" 1119304Speter 1219304Speter#ifndef lint 13254225Speterstatic const char sccsid[] = "$Id: msg.c,v 11.0 2012/10/17 06:34:37 zy Exp $"; 1419304Speter#endif /* not lint */ 1519304Speter 16254225Speter#include <sys/types.h> 1719304Speter#include <sys/queue.h> 1819304Speter#include <sys/stat.h> 1919304Speter 2019304Speter#include <bitstring.h> 2119304Speter#include <ctype.h> 2219304Speter#include <errno.h> 2319304Speter#include <fcntl.h> 2419304Speter#include <limits.h> 25254225Speter#include <locale.h> 26254225Speter#include <stdarg.h> 2719304Speter#include <stdio.h> 2819304Speter#include <stdlib.h> 2919304Speter#include <string.h> 3019304Speter#include <unistd.h> 3119304Speter 3219304Speter#include "common.h" 3319304Speter#include "../vi/vi.h" 3419304Speter 3519304Speter/* 3619304Speter * msgq -- 3719304Speter * Display a message. 3819304Speter * 3919304Speter * PUBLIC: void msgq __P((SCR *, mtype_t, const char *, ...)); 4019304Speter */ 4119304Spetervoid 42254225Spetermsgq( 43254225Speter SCR *sp, 44254225Speter mtype_t mt, 45254225Speter const char *fmt, 46254225Speter ...) 4719304Speter{ 4819304Speter#ifndef NL_ARGMAX 4919304Speter#define __NL_ARGMAX 20 /* Set to 9 by System V. */ 5019304Speter struct { 5119304Speter const char *str; /* String pointer. */ 5219304Speter size_t arg; /* Argument number. */ 5319304Speter size_t prefix; /* Prefix string length. */ 5419304Speter size_t skip; /* Skipped string length. */ 5519304Speter size_t suffix; /* Suffix string length. */ 5619304Speter } str[__NL_ARGMAX]; 5719304Speter#endif 5819304Speter static int reenter; /* STATIC: Re-entrancy check. */ 5919304Speter GS *gp; 60254225Speter size_t blen, len, mlen, nlen; 61254225Speter const char *p; 62254225Speter char *bp, *mp; 6319304Speter va_list ap; 64254225Speter#ifndef NL_ARGMAX 65254225Speter int ch; 66254225Speter char *rbp, *s_rbp; 67254225Speter const char *t, *u; 68254225Speter size_t cnt1, cnt2, soff; 69254225Speter#endif 7019304Speter 7119304Speter /* 7219304Speter * !!! 7319304Speter * It's possible to enter msg when there's no screen to hold the 7419304Speter * message. If sp is NULL, ignore the special cases and put the 7519304Speter * message out to stderr. 7619304Speter */ 7719304Speter if (sp == NULL) { 7819304Speter gp = NULL; 7919304Speter if (mt == M_BERR) 8019304Speter mt = M_ERR; 8119304Speter else if (mt == M_VINFO) 8219304Speter mt = M_INFO; 8319304Speter } else { 8419304Speter gp = sp->gp; 8519304Speter switch (mt) { 8619304Speter case M_BERR: 8719304Speter if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) { 8819304Speter F_SET(gp, G_BELLSCHED); 8919304Speter return; 9019304Speter } 9119304Speter mt = M_ERR; 9219304Speter break; 9319304Speter case M_VINFO: 9419304Speter if (!O_ISSET(sp, O_VERBOSE)) 9519304Speter return; 9619304Speter mt = M_INFO; 9719304Speter /* FALLTHROUGH */ 9819304Speter case M_INFO: 9919304Speter if (F_ISSET(sp, SC_EX_SILENT)) 10019304Speter return; 10119304Speter break; 10219304Speter case M_ERR: 10319304Speter case M_SYSERR: 10419304Speter break; 10519304Speter default: 10619304Speter abort(); 10719304Speter } 10819304Speter } 10919304Speter 11019304Speter /* 11119304Speter * It's possible to reenter msg when it allocates space. We're 11219304Speter * probably dead anyway, but there's no reason to drop core. 11319304Speter * 11419304Speter * XXX 11519304Speter * Yes, there's a race, but it should only be two instructions. 11619304Speter */ 11719304Speter if (reenter++) 11819304Speter return; 11919304Speter 12019304Speter /* Get space for the message. */ 12119304Speter nlen = 1024; 12219304Speter if (0) { 12319304Speterretry: FREE_SPACE(sp, bp, blen); 12419304Speter nlen *= 2; 12519304Speter } 12619304Speter bp = NULL; 12719304Speter blen = 0; 128254225Speter GET_SPACE_GOTOC(sp, bp, blen, nlen); 12919304Speter 13019304Speter /* 13119304Speter * Error prefix. 13219304Speter * 13319304Speter * mp: pointer to the current next character to be written 13419304Speter * mlen: length of the already written characters 13519304Speter * blen: total length of the buffer 13619304Speter */ 13719304Speter#define REM (blen - mlen) 13819304Speter mp = bp; 13919304Speter mlen = 0; 14019304Speter if (mt == M_SYSERR) { 14119304Speter p = msg_cat(sp, "020|Error: ", &len); 14219304Speter if (REM < len) 14319304Speter goto retry; 14419304Speter memcpy(mp, p, len); 14519304Speter mp += len; 14619304Speter mlen += len; 14719304Speter } 14819304Speter 14919304Speter /* 15019304Speter * If we're running an ex command that the user didn't enter, display 15119304Speter * the file name and line number prefix. 15219304Speter */ 15319304Speter if ((mt == M_ERR || mt == M_SYSERR) && 15419304Speter sp != NULL && gp != NULL && gp->if_name != NULL) { 155254225Speter CHAR_T *wp; 156254225Speter size_t wlen; 157254225Speter 158254225Speter CHAR2INT(sp, gp->if_name, strlen(gp->if_name) + 1, wp, wlen); 159254225Speter for (; *wp != '\0'; ++wp) { 160254225Speter len = snprintf(mp, REM, "%s", KEY_NAME(sp, *wp)); 16119304Speter mp += len; 16219304Speter if ((mlen += len) > blen) 16319304Speter goto retry; 16419304Speter } 16519304Speter len = snprintf(mp, REM, ", %d: ", gp->if_lno); 16619304Speter mp += len; 16719304Speter if ((mlen += len) > blen) 16819304Speter goto retry; 16919304Speter } 17019304Speter 17119304Speter /* If nothing to format, we're done. */ 17219304Speter if (fmt == NULL) 17319304Speter goto nofmt; 17419304Speter fmt = msg_cat(sp, fmt, NULL); 17519304Speter 17619304Speter#ifndef NL_ARGMAX 17719304Speter /* 17819304Speter * Nvi should run on machines that don't support the numbered argument 17919304Speter * specifications (%[digit]*$). We do this by reformatting the string 18019304Speter * so that we can hand it to vsprintf(3) and it will use the arguments 18119304Speter * in the right order. When vsprintf returns, we put the string back 18219304Speter * into the right order. It's undefined, according to SVID III, to mix 18319304Speter * numbered argument specifications with the standard style arguments, 18419304Speter * so this should be safe. 18519304Speter * 18619304Speter * In addition, we also need a character that is known to not occur in 18719304Speter * any vi message, for separating the parts of the string. As callers 18819304Speter * of msgq are responsible for making sure that all the non-printable 18919304Speter * characters are formatted for printing before calling msgq, we use a 19019304Speter * random non-printable character selected at terminal initialization 19119304Speter * time. This code isn't fast by any means, but as messages should be 19219304Speter * relatively short and normally have only a few arguments, it won't be 19319304Speter * too bad. Regardless, nobody has come up with any other solution. 19419304Speter * 19519304Speter * The result of this loop is an array of pointers into the message 19619304Speter * string, with associated lengths and argument numbers. The array 19719304Speter * is in the "correct" order, and the arg field contains the argument 19819304Speter * order. 19919304Speter */ 20019304Speter for (p = fmt, soff = 0; soff < __NL_ARGMAX;) { 20119304Speter for (t = p; *p != '\0' && *p != '%'; ++p); 20219304Speter if (*p == '\0') 20319304Speter break; 20419304Speter ++p; 20519304Speter if (!isdigit(*p)) { 20619304Speter if (*p == '%') 20719304Speter ++p; 20819304Speter continue; 20919304Speter } 21019304Speter for (u = p; *++p != '\0' && isdigit(*p);); 21119304Speter if (*p != '$') 21219304Speter continue; 21319304Speter 21419304Speter /* Up to, and including the % character. */ 21519304Speter str[soff].str = t; 21619304Speter str[soff].prefix = u - t; 21719304Speter 21819304Speter /* Up to, and including the $ character. */ 21919304Speter str[soff].arg = atoi(u); 22019304Speter str[soff].skip = (p - u) + 1; 22119304Speter if (str[soff].arg >= __NL_ARGMAX) 22219304Speter goto ret; 22319304Speter 22419304Speter /* Up to, and including the conversion character. */ 22519304Speter for (u = p; (ch = *++p) != '\0';) 22619304Speter if (isalpha(ch) && 22719304Speter strchr("diouxXfeEgGcspn", ch) != NULL) 22819304Speter break; 22919304Speter str[soff].suffix = p - u; 23019304Speter if (ch != '\0') 23119304Speter ++p; 23219304Speter ++soff; 23319304Speter } 23419304Speter 23519304Speter /* If no magic strings, we're done. */ 23619304Speter if (soff == 0) 23719304Speter goto format; 23819304Speter 23919304Speter /* Get space for the reordered strings. */ 24019304Speter if ((rbp = malloc(nlen)) == NULL) 24119304Speter goto ret; 24219304Speter s_rbp = rbp; 24319304Speter 24419304Speter /* 24519304Speter * Reorder the strings into the message string based on argument 24619304Speter * order. 24719304Speter * 24819304Speter * !!! 24919304Speter * We ignore arguments that are out of order, i.e. if we don't find 25019304Speter * an argument, we continue. Assume (almost certainly incorrectly) 25119304Speter * that whoever created the string knew what they were doing. 25219304Speter * 25319304Speter * !!! 25419304Speter * Brute force "sort", but since we don't expect more than one or two 25519304Speter * arguments in a string, the setup cost of a fast sort will be more 25619304Speter * expensive than the loop. 25719304Speter */ 25819304Speter for (cnt1 = 1; cnt1 <= soff; ++cnt1) 25919304Speter for (cnt2 = 0; cnt2 < soff; ++cnt2) 26019304Speter if (cnt1 == str[cnt2].arg) { 26119304Speter memmove(s_rbp, str[cnt2].str, str[cnt2].prefix); 26219304Speter memmove(s_rbp + str[cnt2].prefix, 26319304Speter str[cnt2].str + str[cnt2].prefix + 26419304Speter str[cnt2].skip, str[cnt2].suffix); 26519304Speter s_rbp += str[cnt2].prefix + str[cnt2].suffix; 26619304Speter *s_rbp++ = 26719304Speter gp == NULL ? DEFAULT_NOPRINT : gp->noprint; 26819304Speter break; 26919304Speter } 27019304Speter *s_rbp = '\0'; 27119304Speter fmt = rbp; 27219304Speter#endif 27319304Speter 274254225Speter#ifndef NL_ARGMAX 27519304Speterformat: /* Format the arguments into the string. */ 276254225Speter#endif 27719304Speter va_start(ap, fmt); 27819304Speter len = vsnprintf(mp, REM, fmt, ap); 27919304Speter va_end(ap); 28019304Speter if (len >= nlen) 28119304Speter goto retry; 28219304Speter 28319304Speter#ifndef NL_ARGMAX 28419304Speter if (soff == 0) 28519304Speter goto nofmt; 28619304Speter 28719304Speter /* 28819304Speter * Go through the resulting string, and, for each separator character 28919304Speter * separated string, enter its new starting position and length in the 29019304Speter * array. 29119304Speter */ 29219304Speter for (p = t = mp, cnt1 = 1, 29319304Speter ch = gp == NULL ? DEFAULT_NOPRINT : gp->noprint; *p != '\0'; ++p) 29419304Speter if (*p == ch) { 29519304Speter for (cnt2 = 0; cnt2 < soff; ++cnt2) 29619304Speter if (str[cnt2].arg == cnt1) 29719304Speter break; 29819304Speter str[cnt2].str = t; 29919304Speter str[cnt2].prefix = p - t; 30019304Speter t = p + 1; 30119304Speter ++cnt1; 30219304Speter } 30319304Speter 30419304Speter /* 30519304Speter * Reorder the strings once again, putting them back into the 30619304Speter * message buffer. 30719304Speter * 30819304Speter * !!! 30919304Speter * Note, the length of the message gets decremented once for 31019304Speter * each substring, when we discard the separator character. 31119304Speter */ 31219304Speter for (s_rbp = rbp, cnt1 = 0; cnt1 < soff; ++cnt1) { 31319304Speter memmove(rbp, str[cnt1].str, str[cnt1].prefix); 31419304Speter rbp += str[cnt1].prefix; 31519304Speter --len; 31619304Speter } 31719304Speter memmove(mp, s_rbp, rbp - s_rbp); 31819304Speter 31919304Speter /* Free the reordered string memory. */ 32019304Speter free(s_rbp); 32119304Speter#endif 32219304Speter 32319304Speternofmt: mp += len; 32419304Speter if ((mlen += len) > blen) 32519304Speter goto retry; 32619304Speter if (mt == M_SYSERR) { 32719304Speter len = snprintf(mp, REM, ": %s", strerror(errno)); 32819304Speter mp += len; 32919304Speter if ((mlen += len) > blen) 33019304Speter goto retry; 33119304Speter mt = M_ERR; 33219304Speter } 33319304Speter 33419304Speter /* Add trailing newline. */ 33519304Speter if ((mlen += 1) > blen) 33619304Speter goto retry; 33719304Speter *mp = '\n'; 33819304Speter 33919304Speter if (sp != NULL) 34019304Speter (void)ex_fflush(sp); 34119304Speter if (gp != NULL) 34219304Speter gp->scr_msg(sp, mt, bp, mlen); 34319304Speter else 34419304Speter (void)fprintf(stderr, "%.*s", (int)mlen, bp); 34519304Speter 34619304Speter /* Cleanup. */ 347254225Speter#ifndef NL_ARGMAX 348254225Speterret: 349254225Speter#endif 350254225Speter FREE_SPACE(sp, bp, blen); 35119304Speteralloc_err: 35219304Speter reenter = 0; 35319304Speter} 35419304Speter 35519304Speter/* 356254225Speter * msgq_wstr -- 357254225Speter * Display a message with an embedded string. 358254225Speter * 359254225Speter * PUBLIC: void msgq_wstr __P((SCR *, mtype_t, const CHAR_T *, const char *)); 360254225Speter */ 361254225Spetervoid 362254225Spetermsgq_wstr( 363254225Speter SCR *sp, 364254225Speter mtype_t mtype, 365254225Speter const CHAR_T *str, 366254225Speter const char *fmt) 367254225Speter{ 368254225Speter size_t nlen; 369254225Speter CONST char *nstr; 370254225Speter 371254225Speter if (str == NULL) { 372254225Speter msgq(sp, mtype, "%s", fmt); 373254225Speter return; 374254225Speter } 375254225Speter INT2CHAR(sp, str, STRLEN(str) + 1, nstr, nlen); 376254225Speter msgq_str(sp, mtype, nstr, fmt); 377254225Speter} 378254225Speter 379254225Speter/* 38019304Speter * msgq_str -- 38119304Speter * Display a message with an embedded string. 38219304Speter * 383254225Speter * PUBLIC: void msgq_str __P((SCR *, mtype_t, const char *, const char *)); 38419304Speter */ 38519304Spetervoid 386254225Spetermsgq_str( 387254225Speter SCR *sp, 388254225Speter mtype_t mtype, 389254225Speter const char *str, 390254225Speter const char *fmt) 39119304Speter{ 39219304Speter int nf, sv_errno; 39319304Speter char *p; 39419304Speter 39519304Speter if (str == NULL) { 396254225Speter msgq(sp, mtype, "%s", fmt); 39719304Speter return; 39819304Speter } 39919304Speter 40019304Speter sv_errno = errno; 40119304Speter p = msg_print(sp, str, &nf); 40219304Speter errno = sv_errno; 40319304Speter msgq(sp, mtype, fmt, p); 40419304Speter if (nf) 40519304Speter FREE_SPACE(sp, p, 0); 40619304Speter} 40719304Speter 40819304Speter/* 40919304Speter * mod_rpt -- 41019304Speter * Report on the lines that changed. 41119304Speter * 41219304Speter * !!! 41319304Speter * Historic vi documentation (USD:15-8) claimed that "The editor will also 41419304Speter * always tell you when a change you make affects text which you cannot see." 41519304Speter * This wasn't true -- edit a large file and do "100d|1". We don't implement 41619304Speter * this semantic since it requires tracking each line that changes during a 41719304Speter * command instead of just keeping count. 41819304Speter * 41919304Speter * Line counts weren't right in historic vi, either. For example, given the 42019304Speter * file: 42119304Speter * abc 42219304Speter * def 42319304Speter * the command 2d}, from the 'b' would report that two lines were deleted, 42419304Speter * not one. 42519304Speter * 42619304Speter * PUBLIC: void mod_rpt __P((SCR *)); 42719304Speter */ 42819304Spetervoid 429254225Spetermod_rpt(SCR *sp) 43019304Speter{ 43119304Speter static char * const action[] = { 43219304Speter "293|added", 43319304Speter "294|changed", 43419304Speter "295|deleted", 43519304Speter "296|joined", 43619304Speter "297|moved", 43719304Speter "298|shifted", 43819304Speter "299|yanked", 43919304Speter }; 44019304Speter static char * const lines[] = { 44119304Speter "300|line", 44219304Speter "301|lines", 44319304Speter }; 44419304Speter recno_t total; 44519304Speter u_long rptval; 44619304Speter int first, cnt; 44719304Speter size_t blen, len, tlen; 44819304Speter const char *t; 44919304Speter char * const *ap; 45019304Speter char *bp, *p; 45119304Speter 45219304Speter /* Change reports are turned off in batch mode. */ 45319304Speter if (F_ISSET(sp, SC_EX_SILENT)) 45419304Speter return; 45519304Speter 45619304Speter /* Reset changing line number. */ 45719304Speter sp->rptlchange = OOBLNO; 45819304Speter 45919304Speter /* 46019304Speter * Don't build a message if not enough changed. 46119304Speter * 46219304Speter * !!! 46319304Speter * And now, a vi clone test. Historically, vi reported if the number 46419304Speter * of changed lines was > than the value, not >=, unless it was a yank 46519304Speter * command, which used >=. No lie. Furthermore, an action was never 46619304Speter * reported for a single line action. This is consistent for actions 46719304Speter * other than yank, but yank didn't report single line actions even if 46819304Speter * the report edit option was set to 1. In addition, setting report to 46919304Speter * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an 47019304Speter * unknown reason (this bug was fixed in System III/V at some point). 47119304Speter * I got complaints, so nvi conforms to System III/V historic practice 47219304Speter * except that we report a yank of 1 line if report is set to 1. 47319304Speter */ 47419304Speter#define ARSIZE(a) sizeof(a) / sizeof (*a) 47519304Speter#define MAXNUM 25 47619304Speter rptval = O_VAL(sp, O_REPORT); 47719304Speter for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt) 47819304Speter total += sp->rptlines[cnt]; 47919304Speter if (total == 0) 48019304Speter return; 48119304Speter if (total <= rptval && sp->rptlines[L_YANKED] < rptval) { 48219304Speter for (cnt = 0; cnt < ARSIZE(action); ++cnt) 48319304Speter sp->rptlines[cnt] = 0; 48419304Speter return; 48519304Speter } 48619304Speter 48719304Speter /* Build and display the message. */ 488254225Speter GET_SPACE_GOTOC(sp, bp, blen, sizeof(action) * MAXNUM + 1); 48919304Speter for (p = bp, first = 1, tlen = 0, 49019304Speter ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt) 49119304Speter if (sp->rptlines[cnt] != 0) { 49219304Speter if (first) 49319304Speter first = 0; 49419304Speter else { 49519304Speter *p++ = ';'; 49619304Speter *p++ = ' '; 49719304Speter tlen += 2; 49819304Speter } 49938022Sbde len = snprintf(p, MAXNUM, "%lu ", 50038022Sbde (u_long)sp->rptlines[cnt]); 50119304Speter p += len; 50219304Speter tlen += len; 50319304Speter t = msg_cat(sp, 50419304Speter lines[sp->rptlines[cnt] == 1 ? 0 : 1], &len); 50519304Speter memcpy(p, t, len); 50619304Speter p += len; 50719304Speter tlen += len; 50819304Speter *p++ = ' '; 50919304Speter ++tlen; 51019304Speter t = msg_cat(sp, *ap, &len); 51119304Speter memcpy(p, t, len); 51219304Speter p += len; 51319304Speter tlen += len; 51419304Speter sp->rptlines[cnt] = 0; 51519304Speter } 51619304Speter 51719304Speter /* Add trailing newline. */ 51819304Speter *p = '\n'; 51919304Speter ++tlen; 52019304Speter 52119304Speter (void)ex_fflush(sp); 52219304Speter sp->gp->scr_msg(sp, M_INFO, bp, tlen); 52319304Speter 52419304Speter FREE_SPACE(sp, bp, blen); 52519304Speteralloc_err: 52619304Speter return; 52719304Speter 52819304Speter#undef ARSIZE 52919304Speter#undef MAXNUM 53019304Speter} 53119304Speter 53219304Speter/* 53319304Speter * msgq_status -- 53419304Speter * Report on the file's status. 53519304Speter * 53619304Speter * PUBLIC: void msgq_status __P((SCR *, recno_t, u_int)); 53719304Speter */ 53819304Spetervoid 539254225Spetermsgq_status( 540254225Speter SCR *sp, 541254225Speter recno_t lno, 542254225Speter u_int flags) 54319304Speter{ 54419304Speter recno_t last; 54519304Speter size_t blen, len; 54619304Speter int cnt, needsep; 54719304Speter const char *t; 548254225Speter char **ap, *bp, *np, *p, *s, *ep; 549254225Speter CHAR_T *wp; 550254225Speter size_t wlen; 55119304Speter 55219304Speter /* Get sufficient memory. */ 55319304Speter len = strlen(sp->frp->name); 554254225Speter GET_SPACE_GOTOC(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128); 55519304Speter p = bp; 556254225Speter ep = bp + blen; 55719304Speter 558254225Speter /* Convert the filename. */ 559254225Speter CHAR2INT(sp, sp->frp->name, len + 1, wp, wlen); 560254225Speter 56119304Speter /* Copy in the filename. */ 562254225Speter for (; *wp != '\0'; ++wp) { 563254225Speter len = KEY_LEN(sp, *wp); 564254225Speter memcpy(p, KEY_NAME(sp, *wp), len); 56519304Speter p += len; 56619304Speter } 56719304Speter np = p; 56819304Speter *p++ = ':'; 56919304Speter *p++ = ' '; 57019304Speter 57119304Speter /* Copy in the argument count. */ 57219304Speter if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) { 57319304Speter for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt); 57419304Speter if (cnt > 1) { 575254225Speter (void)snprintf(p, ep - p, 57619304Speter msg_cat(sp, "317|%d files to edit", NULL), cnt); 57719304Speter p += strlen(p); 57819304Speter *p++ = ':'; 57919304Speter *p++ = ' '; 58019304Speter } 58119304Speter F_CLR(sp, SC_STATUS_CNT); 58219304Speter } 58319304Speter 58419304Speter /* 58519304Speter * See nvi/exf.c:file_init() for a description of how and when the 58619304Speter * read-only bit is set. 58719304Speter * 58819304Speter * !!! 58919304Speter * The historic display for "name changed" was "[Not edited]". 59019304Speter */ 59119304Speter needsep = 0; 59219304Speter if (F_ISSET(sp->frp, FR_NEWFILE)) { 59319304Speter F_CLR(sp->frp, FR_NEWFILE); 59419304Speter t = msg_cat(sp, "021|new file", &len); 59519304Speter memcpy(p, t, len); 59619304Speter p += len; 59719304Speter needsep = 1; 59819304Speter } else { 59919304Speter if (F_ISSET(sp->frp, FR_NAMECHANGE)) { 60019304Speter t = msg_cat(sp, "022|name changed", &len); 60119304Speter memcpy(p, t, len); 60219304Speter p += len; 60319304Speter needsep = 1; 60419304Speter } 60519304Speter if (needsep) { 60619304Speter *p++ = ','; 60719304Speter *p++ = ' '; 60819304Speter } 60919304Speter if (F_ISSET(sp->ep, F_MODIFIED)) 61019304Speter t = msg_cat(sp, "023|modified", &len); 61119304Speter else 61219304Speter t = msg_cat(sp, "024|unmodified", &len); 61319304Speter memcpy(p, t, len); 61419304Speter p += len; 61519304Speter needsep = 1; 61619304Speter } 61719304Speter if (F_ISSET(sp->frp, FR_UNLOCKED)) { 61819304Speter if (needsep) { 61919304Speter *p++ = ','; 62019304Speter *p++ = ' '; 62119304Speter } 62219304Speter t = msg_cat(sp, "025|UNLOCKED", &len); 62319304Speter memcpy(p, t, len); 62419304Speter p += len; 62519304Speter needsep = 1; 62619304Speter } 62719304Speter if (O_ISSET(sp, O_READONLY)) { 62819304Speter if (needsep) { 62919304Speter *p++ = ','; 63019304Speter *p++ = ' '; 63119304Speter } 63219304Speter t = msg_cat(sp, "026|readonly", &len); 63319304Speter memcpy(p, t, len); 63419304Speter p += len; 63519304Speter needsep = 1; 63619304Speter } 63719304Speter if (needsep) { 63819304Speter *p++ = ':'; 63919304Speter *p++ = ' '; 64019304Speter } 64119304Speter if (LF_ISSET(MSTAT_SHOWLAST)) { 64219304Speter if (db_last(sp, &last)) 64319304Speter return; 64419304Speter if (last == 0) { 64519304Speter t = msg_cat(sp, "028|empty file", &len); 64619304Speter memcpy(p, t, len); 64719304Speter p += len; 64819304Speter } else { 649254225Speter t = msg_cat(sp, "027|line %lu of %lu [%ld%%]", &len); 650254225Speter (void)snprintf(p, ep - p, t, lno, last, 651254225Speter ((u_long)lno * 100) / last); 65219304Speter p += strlen(p); 65319304Speter } 65419304Speter } else { 65519304Speter t = msg_cat(sp, "029|line %lu", &len); 656254225Speter (void)snprintf(p, ep - p, t, (u_long)lno); 65719304Speter p += strlen(p); 65819304Speter } 65919304Speter#ifdef DEBUG 660254225Speter (void)snprintf(p, ep - p, " (pid %lu)", (u_long)getpid()); 66119304Speter p += strlen(p); 66219304Speter#endif 66319304Speter *p++ = '\n'; 66419304Speter len = p - bp; 66519304Speter 66619304Speter /* 66719304Speter * There's a nasty problem with long path names. Cscope and tags files 66819304Speter * can result in long paths and vi will request a continuation key from 66919304Speter * the user as soon as it starts the screen. Unfortunately, the user 67019304Speter * has already typed ahead, and chaos results. If we assume that the 67119304Speter * characters in the filenames and informational messages only take a 67219304Speter * single screen column each, we can trim the filename. 67319304Speter * 67419304Speter * XXX 67519304Speter * Status lines get put up at fairly awkward times. For example, when 67619304Speter * you do a filter read (e.g., :read ! echo foo) in the top screen of a 67719304Speter * split screen, we have to repaint the status lines for all the screens 67819304Speter * below the top screen. We don't want users having to enter continue 67919304Speter * characters for those screens. Make it really hard to screw this up. 68019304Speter */ 68119304Speter s = bp; 68219304Speter if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) { 68319304Speter for (; s < np && (*s != '/' || (p - s) > sp->cols - 3); ++s); 68419304Speter if (s == np) { 68519304Speter s = p - (sp->cols - 5); 68619304Speter *--s = ' '; 68719304Speter } 68819304Speter *--s = '.'; 68919304Speter *--s = '.'; 69019304Speter *--s = '.'; 69119304Speter len = p - s; 69219304Speter } 69319304Speter 69419304Speter /* Flush any waiting ex messages. */ 69519304Speter (void)ex_fflush(sp); 69619304Speter 69719304Speter sp->gp->scr_msg(sp, M_INFO, s, len); 69819304Speter 69919304Speter FREE_SPACE(sp, bp, blen); 70019304Speteralloc_err: 70119304Speter return; 70219304Speter} 70319304Speter 70419304Speter/* 70519304Speter * msg_open -- 70619304Speter * Open the message catalogs. 70719304Speter * 70819304Speter * PUBLIC: int msg_open __P((SCR *, char *)); 70919304Speter */ 71019304Speterint 711254225Spetermsg_open( 712254225Speter SCR *sp, 713254225Speter char *file) 71419304Speter{ 71519304Speter /* 71619304Speter * !!! 71719304Speter * Assume that the first file opened is the system default, and that 71819304Speter * all subsequent ones user defined. Only display error messages 71919304Speter * if we can't open the user defined ones -- it's useful to know if 72019304Speter * the system one wasn't there, but if nvi is being shipped with an 72119304Speter * installed system, the file will be there, if it's not, then the 72219304Speter * message will be repeated every time nvi is started up. 72319304Speter */ 72419304Speter static int first = 1; 725254225Speter nl_catd catd; 726254225Speter char *p; 727254225Speter int rval = 0; 72819304Speter 729254225Speter if ((p = strrchr(file, '/')) != NULL && p[1] == '\0') { 730254225Speter /* Confirms to XPG4. */ 731254225Speter if ((p = join(file, setlocale(LC_MESSAGES, NULL))) == NULL) { 732254225Speter msgq(sp, M_SYSERR, NULL); 73319304Speter return (1); 73419304Speter } 735254225Speter } else { 736254225Speter /* Make sure it's recognized as a path by catopen(3). */ 737254225Speter if ((p = join(".", file)) == NULL) { 738254225Speter msgq(sp, M_SYSERR, NULL); 739254225Speter return (1); 740254225Speter } 74119304Speter } 742254225Speter errno = 0; 743254225Speter if ((catd = catopen(p, NL_CAT_LOCALE)) == (nl_catd)-1) { 74419304Speter if (first) { 74519304Speter first = 0; 746254225Speter rval = 1; 747254225Speter goto ret; 74819304Speter } 749254225Speter 750254225Speter /* 751254225Speter * POSIX.1-2008 gives no instruction on how to report a 752254225Speter * corrupt catalog file. Errno == 0 is not rare; add 753254225Speter * EFTYPE, which is seen on FreeBSD, for a good measure. 754254225Speter */ 755254225Speter if (errno == 0 || errno == EFTYPE) 756254225Speter msgq_str(sp, M_ERR, p, 757254225Speter "030|The file %s is not a message catalog"); 758254225Speter else 759254225Speter msgq_str(sp, M_SYSERR, p, "%s"); 760254225Speter rval = 1; 761254225Speter goto ret; 76219304Speter } 76319304Speter first = 0; 76419304Speter 765254225Speter msg_close(sp->gp); 766254225Speter sp->gp->catd = catd; 767254225Speterret: free(p); 768254225Speter return (rval); 76919304Speter} 77019304Speter 77119304Speter/* 77219304Speter * msg_close -- 77319304Speter * Close the message catalogs. 77419304Speter * 77519304Speter * PUBLIC: void msg_close __P((GS *)); 77619304Speter */ 77719304Spetervoid 778254225Spetermsg_close(GS *gp) 77919304Speter{ 780254225Speter if (gp->catd != (nl_catd)-1) 781254225Speter (void)catclose(gp->catd); 78219304Speter} 78319304Speter 78419304Speter/* 78519304Speter * msg_cont -- 78619304Speter * Return common continuation messages. 78719304Speter * 78819304Speter * PUBLIC: const char *msg_cmsg __P((SCR *, cmsg_t, size_t *)); 78919304Speter */ 79019304Speterconst char * 791254225Spetermsg_cmsg( 792254225Speter SCR *sp, 793254225Speter cmsg_t which, 794254225Speter size_t *lenp) 79519304Speter{ 79619304Speter switch (which) { 79719304Speter case CMSG_CONF: 79819304Speter return (msg_cat(sp, "268|confirm? [ynq]", lenp)); 79919304Speter case CMSG_CONT: 80019304Speter return (msg_cat(sp, "269|Press any key to continue: ", lenp)); 80119304Speter case CMSG_CONT_EX: 80219304Speter return (msg_cat(sp, 80319304Speter "270|Press any key to continue [: to enter more ex commands]: ", 80419304Speter lenp)); 80519304Speter case CMSG_CONT_R: 80619304Speter return (msg_cat(sp, "161|Press Enter to continue: ", lenp)); 80719304Speter case CMSG_CONT_S: 80819304Speter return (msg_cat(sp, "275| cont?", lenp)); 80919304Speter case CMSG_CONT_Q: 81019304Speter return (msg_cat(sp, 81119304Speter "271|Press any key to continue [q to quit]: ", lenp)); 81219304Speter default: 81319304Speter abort(); 81419304Speter } 81519304Speter /* NOTREACHED */ 81619304Speter} 81719304Speter 81819304Speter/* 81919304Speter * msg_cat -- 82019304Speter * Return a single message from the catalog, plus its length. 82119304Speter * 82219304Speter * !!! 82319304Speter * Only a single catalog message can be accessed at a time, if multiple 82419304Speter * ones are needed, they must be copied into local memory. 82519304Speter * 82619304Speter * PUBLIC: const char *msg_cat __P((SCR *, const char *, size_t *)); 82719304Speter */ 82819304Speterconst char * 829254225Spetermsg_cat( 830254225Speter SCR *sp, 831254225Speter const char *str, 832254225Speter size_t *lenp) 83319304Speter{ 83419304Speter GS *gp; 835254225Speter char *p; 836254225Speter int msgno; 83719304Speter 83819304Speter /* 83919304Speter * If it's not a catalog message, i.e. has doesn't have a leading 84019304Speter * number and '|' symbol, we're done. 84119304Speter */ 84219304Speter if (isdigit(str[0]) && 84319304Speter isdigit(str[1]) && isdigit(str[2]) && str[3] == '|') { 84419304Speter msgno = atoi(str); 845254225Speter str = &str[4]; 84619304Speter 84719304Speter gp = sp == NULL ? NULL : sp->gp; 848254225Speter if (gp != NULL && gp->catd != (nl_catd)-1 && 849254225Speter (p = catgets(gp->catd, 1, msgno, str)) != NULL) { 85019304Speter if (lenp != NULL) 851254225Speter *lenp = strlen(p); 852254225Speter return (p); 85319304Speter } 85419304Speter } 85519304Speter if (lenp != NULL) 85619304Speter *lenp = strlen(str); 85719304Speter return (str); 85819304Speter} 85919304Speter 86019304Speter/* 86119304Speter * msg_print -- 86219304Speter * Return a printable version of a string, in allocated memory. 86319304Speter * 86419304Speter * PUBLIC: char *msg_print __P((SCR *, const char *, int *)); 86519304Speter */ 86619304Speterchar * 867254225Spetermsg_print( 868254225Speter SCR *sp, 869254225Speter const char *s, 870254225Speter int *needfree) 87119304Speter{ 87219304Speter size_t blen, nlen; 87319304Speter char *bp, *ep, *p, *t; 874254225Speter CHAR_T *wp, *cp; 875254225Speter size_t wlen; 87619304Speter 87719304Speter *needfree = 0; 87819304Speter 879254225Speter /* XXX Not good for debugging ex_read & ex_filter.*/ 880254225Speter CHAR2INT5(sp, EXP(sp)->ibcw, (char *)s, strlen(s) + 1, wp, wlen); 881254225Speter for (cp = wp; *cp != '\0'; ++cp) 882254225Speter if (!ISPRINT(*cp)) 88319304Speter break; 88419304Speter if (*cp == '\0') 88519304Speter return ((char *)s); /* SAFE: needfree set to 0. */ 88619304Speter 88719304Speter nlen = 0; 88819304Speter if (0) { 88919304Speterretry: if (sp == NULL) 89019304Speter free(bp); 89119304Speter else 89219304Speter FREE_SPACE(sp, bp, blen); 893254225Speter *needfree = 0; 89419304Speter } 89519304Speter nlen += 256; 89619304Speter if (sp == NULL) { 89719304Speter if ((bp = malloc(nlen)) == NULL) 89819304Speter goto alloc_err; 89919304Speter } else 900254225Speter GET_SPACE_GOTOC(sp, bp, blen, nlen); 90119304Speter if (0) { 90219304Speteralloc_err: return (""); 90319304Speter } 90419304Speter *needfree = 1; 90519304Speter 906254225Speter for (p = bp, ep = (bp + blen) - 1; *wp != '\0' && p < ep; ++wp) 907254225Speter for (t = KEY_NAME(sp, *wp); *t != '\0' && p < ep; *p++ = *t++); 90819304Speter if (p == ep) 90919304Speter goto retry; 91019304Speter *p = '\0'; 91119304Speter return (bp); 91219304Speter} 913