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