1 /* $OpenBSD: msg.c,v 1.27 2016/12/18 18:28:39 krw Exp $ */ 2 3 /*- 4 * Copyright (c) 1991, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * Copyright (c) 1991, 1993, 1994, 1995, 1996 7 * Keith Bostic. All rights reserved. 8 * 9 * See the LICENSE file for redistribution information. 10 */ 11 12 #include "config.h" 13 14 #include <sys/queue.h> 15 #include <sys/stat.h> 16 #include <sys/time.h> 17 18 #include <bitstring.h> 19 #include <ctype.h> 20 #include <errno.h> 21 #include <fcntl.h> 22 #include <limits.h> 23 #include <stdarg.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <unistd.h> 28 29 #include "common.h" 30 #include "../vi/vi.h" 31 32 /* 33 * msgq -- 34 * Display a message. 35 * 36 * PUBLIC: void msgq(SCR *, mtype_t, const char *, ...); 37 */ 38 void 39 msgq(SCR *sp, mtype_t mt, const char *fmt, ...) 40 { 41 static int reenter; /* STATIC: Re-entrancy check. */ 42 GS *gp; 43 size_t blen, len, mlen, nlen; 44 const char *p; 45 char *bp, *mp; 46 va_list ap; 47 48 /* 49 * !!! 50 * It's possible to enter msg when there's no screen to hold the 51 * message. If sp is NULL, ignore the special cases and put the 52 * message out to stderr. 53 */ 54 if (sp == NULL) { 55 gp = NULL; 56 if (mt == M_BERR) 57 mt = M_ERR; 58 else if (mt == M_VINFO) 59 mt = M_INFO; 60 } else { 61 gp = sp->gp; 62 switch (mt) { 63 case M_BERR: 64 if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) { 65 F_SET(gp, G_BELLSCHED); 66 return; 67 } 68 mt = M_ERR; 69 break; 70 case M_VINFO: 71 if (!O_ISSET(sp, O_VERBOSE)) 72 return; 73 mt = M_INFO; 74 /* FALLTHROUGH */ 75 case M_INFO: 76 if (F_ISSET(sp, SC_EX_SILENT)) 77 return; 78 break; 79 case M_ERR: 80 case M_SYSERR: 81 break; 82 default: 83 abort(); 84 } 85 } 86 87 /* 88 * It's possible to reenter msg when it allocates space. We're 89 * probably dead anyway, but there's no reason to drop core. 90 * 91 * XXX 92 * Yes, there's a race, but it should only be two instructions. 93 */ 94 if (reenter++) 95 return; 96 97 /* Get space for the message. */ 98 nlen = 1024; 99 if (0) { 100 retry: FREE_SPACE(sp, bp, blen); 101 nlen *= 2; 102 } 103 bp = NULL; 104 blen = 0; 105 GET_SPACE_GOTO(sp, bp, blen, nlen); 106 107 /* 108 * Error prefix. 109 * 110 * mp: pointer to the current next character to be written 111 * mlen: length of the already written characters 112 * blen: total length of the buffer 113 */ 114 #define REM (blen - mlen) 115 mp = bp; 116 mlen = 0; 117 if (mt == M_SYSERR) { 118 p = "Error: "; 119 len = strlen(p); 120 if (REM < len) 121 goto retry; 122 memcpy(mp, p, len); 123 mp += len; 124 mlen += len; 125 } 126 127 /* 128 * If we're running an ex command that the user didn't enter, display 129 * the file name and line number prefix. 130 */ 131 if ((mt == M_ERR || mt == M_SYSERR) && 132 sp != NULL && gp != NULL && gp->if_name != NULL) { 133 for (p = gp->if_name; *p != '\0'; ++p) { 134 len = snprintf(mp, REM, "%s", KEY_NAME(sp, *p)); 135 mp += len; 136 if ((mlen += len) > blen) 137 goto retry; 138 } 139 len = snprintf(mp, REM, ", %d: ", gp->if_lno); 140 mp += len; 141 if ((mlen += len) > blen) 142 goto retry; 143 } 144 145 /* If nothing to format, we're done. */ 146 if (fmt == NULL) { 147 len = 0; 148 goto nofmt; 149 } 150 151 /* Format the arguments into the string. */ 152 va_start(ap, fmt); 153 len = vsnprintf(mp, REM, fmt, ap); 154 va_end(ap); 155 if (len >= nlen) 156 goto retry; 157 158 nofmt: mp += len; 159 if ((mlen += len) > blen) 160 goto retry; 161 if (mt == M_SYSERR) { 162 len = snprintf(mp, REM, ": %s", strerror(errno)); 163 mp += len; 164 if ((mlen += len) > blen) 165 goto retry; 166 mt = M_ERR; 167 } 168 169 /* Add trailing newline. */ 170 if ((mlen += 1) > blen) 171 goto retry; 172 *mp = '\n'; 173 174 if (sp != NULL) 175 (void)ex_fflush(sp); 176 if (gp != NULL) 177 gp->scr_msg(sp, mt, bp, mlen); 178 else 179 (void)fprintf(stderr, "%.*s", (int)mlen, bp); 180 181 /* Cleanup. */ 182 FREE_SPACE(sp, bp, blen); 183 alloc_err: 184 reenter = 0; 185 } 186 187 /* 188 * msgq_str -- 189 * Display a message with an embedded string. 190 * 191 * PUBLIC: void msgq_str(SCR *, mtype_t, char *, char *); 192 */ 193 void 194 msgq_str(SCR *sp, mtype_t mtype, char *str, char *fmt) 195 { 196 int nf, sv_errno; 197 char *p; 198 199 if (str == NULL) { 200 msgq(sp, mtype, fmt); 201 return; 202 } 203 204 sv_errno = errno; 205 p = msg_print(sp, str, &nf); 206 errno = sv_errno; 207 msgq(sp, mtype, fmt, p); 208 if (nf) 209 FREE_SPACE(sp, p, 0); 210 } 211 212 /* 213 * mod_rpt -- 214 * Report on the lines that changed. 215 * 216 * !!! 217 * Historic vi documentation (USD:15-8) claimed that "The editor will also 218 * always tell you when a change you make affects text which you cannot see." 219 * This wasn't true -- edit a large file and do "100d|1". We don't implement 220 * this semantic since it requires tracking each line that changes during a 221 * command instead of just keeping count. 222 * 223 * Line counts weren't right in historic vi, either. For example, given the 224 * file: 225 * abc 226 * def 227 * the command 2d}, from the 'b' would report that two lines were deleted, 228 * not one. 229 * 230 * PUBLIC: void mod_rpt(SCR *); 231 */ 232 void 233 mod_rpt(SCR *sp) 234 { 235 static char * const action[] = { 236 "added", 237 "changed", 238 "deleted", 239 "joined", 240 "moved", 241 "shifted", 242 "yanked", 243 }; 244 static char * const lines[] = { 245 "line", 246 "lines", 247 }; 248 recno_t total; 249 u_long rptval; 250 int first, cnt; 251 size_t blen, len, tlen; 252 const char *t; 253 char * const *ap; 254 char *bp, *p; 255 256 /* Change reports are turned off in batch mode. */ 257 if (F_ISSET(sp, SC_EX_SILENT)) 258 return; 259 260 /* Reset changing line number. */ 261 sp->rptlchange = OOBLNO; 262 263 /* 264 * Don't build a message if not enough changed. 265 * 266 * !!! 267 * And now, a vi clone test. Historically, vi reported if the number 268 * of changed lines was > than the value, not >=, unless it was a yank 269 * command, which used >=. No lie. Furthermore, an action was never 270 * reported for a single line action. This is consistent for actions 271 * other than yank, but yank didn't report single line actions even if 272 * the report edit option was set to 1. In addition, setting report to 273 * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an 274 * unknown reason (this bug was fixed in System III/V at some point). 275 * I got complaints, so nvi conforms to System III/V historic practice 276 * except that we report a yank of 1 line if report is set to 1. 277 */ 278 #define ARSIZE(a) sizeof(a) / sizeof (*a) 279 #define MAXNUM 25 280 rptval = O_VAL(sp, O_REPORT); 281 for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt) 282 total += sp->rptlines[cnt]; 283 if (total == 0) 284 return; 285 if (total <= rptval && sp->rptlines[L_YANKED] < rptval) { 286 for (cnt = 0; cnt < ARSIZE(action); ++cnt) 287 sp->rptlines[cnt] = 0; 288 return; 289 } 290 291 /* Build and display the message. */ 292 GET_SPACE_GOTO(sp, bp, blen, sizeof(action) * MAXNUM + 1); 293 for (p = bp, first = 1, tlen = 0, 294 ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt) 295 if (sp->rptlines[cnt] != 0) { 296 if (first) 297 first = 0; 298 else { 299 *p++ = ';'; 300 *p++ = ' '; 301 tlen += 2; 302 } 303 len = snprintf(p, MAXNUM, "%u ", sp->rptlines[cnt]); 304 p += len; 305 tlen += len; 306 t = lines[sp->rptlines[cnt] == 1 ? 0 : 1]; 307 len = strlen(t); 308 memcpy(p, t, len); 309 p += len; 310 tlen += len; 311 *p++ = ' '; 312 ++tlen; 313 len = strlen(*ap); 314 memcpy(p, *ap, len); 315 p += len; 316 tlen += len; 317 sp->rptlines[cnt] = 0; 318 } 319 320 /* Add trailing newline. */ 321 *p = '\n'; 322 ++tlen; 323 324 (void)ex_fflush(sp); 325 sp->gp->scr_msg(sp, M_INFO, bp, tlen); 326 327 FREE_SPACE(sp, bp, blen); 328 alloc_err: 329 return; 330 331 #undef ARSIZE 332 #undef MAXNUM 333 } 334 335 /* 336 * msgq_status -- 337 * Report on the file's status. 338 * 339 * PUBLIC: void msgq_status(SCR *, recno_t, u_int); 340 */ 341 void 342 msgq_status(SCR *sp, recno_t lno, u_int flags) 343 { 344 recno_t last; 345 size_t blen, len; 346 int cnt, needsep; 347 const char *t; 348 char **ap, *bp, *np, *p, *s, *ep; 349 350 /* Get sufficient memory. */ 351 len = strlen(sp->frp->name); 352 GET_SPACE_GOTO(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128); 353 p = bp; 354 ep = bp + blen; 355 356 /* Copy in the filename. */ 357 for (t = sp->frp->name; *t != '\0'; ++t) { 358 len = KEY_LEN(sp, *t); 359 memcpy(p, KEY_NAME(sp, *t), len); 360 p += len; 361 } 362 np = p; 363 *p++ = ':'; 364 *p++ = ' '; 365 366 /* Copy in the argument count. */ 367 if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) { 368 for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt); 369 if (cnt > 1) { 370 (void)snprintf(p, ep - p, "%d files to edit", cnt); 371 p += strlen(p); 372 *p++ = ':'; 373 *p++ = ' '; 374 } 375 F_CLR(sp, SC_STATUS_CNT); 376 } 377 378 /* 379 * See nvi/exf.c:file_init() for a description of how and when the 380 * read-only bit is set. 381 * 382 * !!! 383 * The historic display for "name changed" was "[Not edited]". 384 */ 385 needsep = 0; 386 if (F_ISSET(sp->frp, FR_NEWFILE)) { 387 F_CLR(sp->frp, FR_NEWFILE); 388 len = strlen("new file"); 389 memcpy(p, "new file", len); 390 p += len; 391 needsep = 1; 392 } else { 393 if (F_ISSET(sp->frp, FR_NAMECHANGE)) { 394 len = strlen("name changed"); 395 memcpy(p, "name changed", len); 396 p += len; 397 needsep = 1; 398 } 399 if (needsep) { 400 *p++ = ','; 401 *p++ = ' '; 402 } 403 t = (F_ISSET(sp->ep, F_MODIFIED)) ? "modified" : "unmodified"; 404 len = strlen(t); 405 memcpy(p, t, len); 406 p += len; 407 needsep = 1; 408 } 409 if (F_ISSET(sp->frp, FR_UNLOCKED)) { 410 if (needsep) { 411 *p++ = ','; 412 *p++ = ' '; 413 } 414 len = strlen("UNLOCKED"); 415 memcpy(p, "UNLOCKED", len); 416 p += len; 417 needsep = 1; 418 } 419 if (O_ISSET(sp, O_READONLY)) { 420 if (needsep) { 421 *p++ = ','; 422 *p++ = ' '; 423 } 424 len = strlen("readonly"); 425 memcpy(p, "readonly", len); 426 p += len; 427 needsep = 1; 428 } 429 if (needsep) { 430 *p++ = ':'; 431 *p++ = ' '; 432 } 433 if (LF_ISSET(MSTAT_SHOWLAST)) { 434 if (db_last(sp, &last)) 435 return; 436 if (last == 0) { 437 len = strlen("emptry file"); 438 memcpy(p, "empty file", len); 439 p += len; 440 } else { 441 (void)snprintf(p, ep - p, "line %lu of %lu [%lu%%]", 442 (unsigned long)lno, (unsigned long)last, 443 (unsigned long)(lno * 100) / last); 444 p += strlen(p); 445 } 446 } else { 447 (void)snprintf(p, ep - p, "line %lu", (unsigned long)lno); 448 p += strlen(p); 449 } 450 #ifdef DEBUG 451 (void)snprintf(p, ep - p, " (pid %ld)", (long)getpid()); 452 p += strlen(p); 453 #endif 454 *p++ = '\n'; 455 len = p - bp; 456 457 /* 458 * There's a nasty problem with long path names. Tags files 459 * can result in long paths and vi will request a continuation key from 460 * the user as soon as it starts the screen. Unfortunately, the user 461 * has already typed ahead, and chaos results. If we assume that the 462 * characters in the filenames and informational messages only take a 463 * single screen column each, we can trim the filename. 464 * 465 * XXX 466 * Status lines get put up at fairly awkward times. For example, when 467 * you do a filter read (e.g., :read ! echo foo) in the top screen of a 468 * split screen, we have to repaint the status lines for all the screens 469 * below the top screen. We don't want users having to enter continue 470 * characters for those screens. Make it really hard to screw this up. 471 */ 472 s = bp; 473 if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) { 474 for (; s < np && (*s != '/' || (p - s) > sp->cols - 3); ++s); 475 if (s == np) { 476 s = p - (sp->cols - 5); 477 *--s = ' '; 478 } 479 *--s = '.'; 480 *--s = '.'; 481 *--s = '.'; 482 len = p - s; 483 } 484 485 /* Flush any waiting ex messages. */ 486 (void)ex_fflush(sp); 487 488 sp->gp->scr_msg(sp, M_INFO, s, len); 489 490 FREE_SPACE(sp, bp, blen); 491 alloc_err: 492 return; 493 } 494 495 /* 496 * msg_cont -- 497 * Return common continuation messages. 498 * 499 * PUBLIC: const char *msg_cmsg(SCR *, cmsg_t, size_t *); 500 */ 501 const char * 502 msg_cmsg(SCR *sp, cmsg_t which, size_t *lenp) 503 { 504 const char *s; 505 switch (which) { 506 case CMSG_CONF: 507 s = "confirm? [ynq]"; 508 break; 509 case CMSG_CONT: 510 s = "Press any key to continue: "; 511 break; 512 case CMSG_CONT_EX: 513 s = "Press any key to continue [: to enter more ex commands]: "; 514 break; 515 case CMSG_CONT_R: 516 s = "Press Enter to continue: "; 517 break; 518 case CMSG_CONT_S: 519 s = " cont?"; 520 break; 521 case CMSG_CONT_Q: 522 s = "Press any key to continue [q to quit]: "; 523 break; 524 default: 525 abort(); 526 } 527 *lenp = strlen(s); 528 return s; 529 } 530 531 /* 532 * msg_print -- 533 * Return a printable version of a string, in allocated memory. 534 * 535 * PUBLIC: char *msg_print(SCR *, const char *, int *); 536 */ 537 char * 538 msg_print(SCR *sp, const char *s, int *needfree) 539 { 540 size_t blen, nlen; 541 const char *cp; 542 char *bp, *ep, *p, *t; 543 544 *needfree = 0; 545 546 for (cp = s; *cp != '\0'; ++cp) 547 if (!isprint(*cp)) 548 break; 549 if (*cp == '\0') 550 return ((char *)s); /* SAFE: needfree set to 0. */ 551 552 nlen = 0; 553 if (0) { 554 retry: if (sp == NULL) 555 free(bp); 556 else 557 FREE_SPACE(sp, bp, blen); 558 *needfree = 0; 559 } 560 nlen += 256; 561 if (sp == NULL) { 562 if ((bp = malloc(nlen)) == NULL) 563 goto alloc_err; 564 blen = 0; 565 } else 566 GET_SPACE_GOTO(sp, bp, blen, nlen); 567 if (0) { 568 alloc_err: return (""); 569 } 570 *needfree = 1; 571 572 for (p = bp, ep = (bp + blen) - 1, cp = s; *cp != '\0' && p < ep; ++cp) 573 for (t = KEY_NAME(sp, *cp); *t != '\0' && p < ep; *p++ = *t++); 574 if (p == ep) 575 goto retry; 576 *p = '\0'; 577 return (bp); 578 } 579