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