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