1 /* $OpenBSD: buffer.c,v 1.31 2003/06/26 23:04:10 vincent Exp $ */ 2 3 /* 4 * Buffer handling. 5 */ 6 7 #include "def.h" 8 #include "kbd.h" /* needed for modes */ 9 #include <stdarg.h> 10 11 static BUFFER *makelist(void); 12 13 int 14 togglereadonly(int f, int n) 15 { 16 if (!(curbp->b_flag & BFREADONLY)) { 17 curbp->b_flag |= BFREADONLY; 18 ewprintf("Now readonly"); 19 } else { 20 curbp->b_flag &=~ BFREADONLY; 21 if (curbp->b_flag & BFCHG) 22 ewprintf("Warning: Buffer was modified"); 23 } 24 curwp->w_flag |= WFMODE; 25 26 return(1); 27 } 28 29 /* 30 * Attach a buffer to a window. The values of dot and mark come 31 * from the buffer if the use count is 0. Otherwise, they come 32 * from some other window. *scratch* is the default alternate 33 * buffer. 34 */ 35 /* ARGSUSED */ 36 int 37 usebuffer(int f, int n) 38 { 39 BUFFER *bp; 40 int s; 41 char bufn[NBUFN]; 42 43 /* Get buffer to use from user */ 44 if ((curbp->b_altb == NULL) && 45 ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL)) 46 s = eread("Switch to buffer: ", bufn, NBUFN, EFNEW | EFBUF); 47 else 48 s = eread("Switch to buffer: (default %s) ", bufn, NBUFN, 49 EFNEW | EFBUF, curbp->b_altb->b_bname); 50 51 if (s == ABORT) 52 return s; 53 if (s == FALSE && curbp->b_altb != NULL) 54 bp = curbp->b_altb; 55 else if ((bp = bfind(bufn, TRUE)) == NULL) 56 return FALSE; 57 58 /* and put it in current window */ 59 curbp = bp; 60 return showbuffer(bp, curwp, WFFORCE | WFHARD); 61 } 62 63 /* 64 * pop to buffer asked for by the user. 65 */ 66 /* ARGSUSED */ 67 int 68 poptobuffer(int f, int n) 69 { 70 BUFFER *bp; 71 MGWIN *wp; 72 int s; 73 char bufn[NBUFN]; 74 75 /* Get buffer to use from user */ 76 if ((curbp->b_altb == NULL) && 77 ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL)) 78 s = eread("Switch to buffer in other window: ", bufn, NBUFN, 79 EFNEW | EFBUF); 80 else 81 s = eread("Switch to buffer in other window: (default %s) ", 82 bufn, NBUFN, EFNEW | EFBUF, curbp->b_altb->b_bname); 83 if (s == ABORT) 84 return s; 85 if (s == FALSE && curbp->b_altb != NULL) 86 bp = curbp->b_altb; 87 else if ((bp = bfind(bufn, TRUE)) == NULL) 88 return FALSE; 89 90 /* and put it in a new window */ 91 if ((wp = popbuf(bp)) == NULL) 92 return FALSE; 93 curbp = bp; 94 curwp = wp; 95 return TRUE; 96 } 97 98 /* 99 * Dispose of a buffer, by name. 100 * Ask for the name. Look it up (don't get too 101 * upset if it isn't there at all!). Clear the buffer (ask 102 * if the buffer has been changed). Then free the header 103 * line and the buffer header. Bound to "C-X K". 104 */ 105 /* ARGSUSED */ 106 int 107 killbuffer(int f, int n) 108 { 109 BUFFER *bp; 110 BUFFER *bp1; 111 BUFFER *bp2; 112 MGWIN *wp; 113 int s; 114 char bufn[NBUFN]; 115 struct undo_rec *rec, *next; 116 117 if ((s = eread("Kill buffer: (default %s) ", bufn, NBUFN, EFNEW | EFBUF, 118 curbp->b_bname)) == ABORT) 119 return (s); 120 else if (s == FALSE) 121 bp = curbp; 122 else if ((bp = bfind(bufn, FALSE)) == NULL) 123 return FALSE; 124 125 /* 126 * Find some other buffer to display. try the alternate buffer, 127 * then the first different buffer in the buffer list. If there's 128 * only one buffer, create buffer *scratch* and make it the alternate 129 * buffer. Return if *scratch* is only buffer... 130 */ 131 if ((bp1 = bp->b_altb) == NULL) { 132 bp1 = (bp == bheadp) ? bp->b_bufp : bheadp; 133 if (bp1 == NULL) { 134 /* only one buffer. see if it's *scratch* */ 135 if (bp == bfind("*scratch*", FALSE)) 136 return FALSE; 137 /* create *scratch* for alternate buffer */ 138 if ((bp1 = bfind("*scratch*", TRUE)) == NULL) 139 return FALSE; 140 } 141 } 142 if (bclear(bp) != TRUE) 143 return TRUE; 144 for (wp = wheadp; bp->b_nwnd > 0; wp = wp->w_wndp) { 145 if (wp->w_bufp == bp) { 146 bp2 = bp1->b_altb; /* save alternate buffer */ 147 if (showbuffer(bp1, wp, WFMODE | WFFORCE | WFHARD)) 148 bp1->b_altb = bp2; 149 else 150 bp1 = bp2; 151 } 152 } 153 if (bp == curbp) 154 curbp = bp1; 155 free(bp->b_linep); /* Release header line. */ 156 bp2 = NULL; /* Find the header. */ 157 bp1 = bheadp; 158 while (bp1 != bp) { 159 if (bp1->b_altb == bp) 160 bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb; 161 bp2 = bp1; 162 bp1 = bp1->b_bufp; 163 } 164 bp1 = bp1->b_bufp; /* Next one in chain. */ 165 if (bp2 == NULL) /* Unlink it. */ 166 bheadp = bp1; 167 else 168 bp2->b_bufp = bp1; 169 while (bp1 != NULL) { /* Finish with altb's */ 170 if (bp1->b_altb == bp) 171 bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb; 172 bp1 = bp1->b_bufp; 173 } 174 rec = LIST_FIRST(&bp->b_undo); 175 while (rec != NULL) { 176 next = LIST_NEXT(rec, next); 177 free_undo_record(rec); 178 rec = next; 179 } 180 free((char *)bp->b_bname); /* Release name block */ 181 free(bp); /* Release buffer block */ 182 return TRUE; 183 } 184 185 /* 186 * Save some buffers - just call anycb with the arg flag. 187 */ 188 /* ARGSUSED */ 189 int 190 savebuffers(int f, int n) 191 { 192 if (anycb(f) == ABORT) 193 return ABORT; 194 return TRUE; 195 } 196 197 /* 198 * Listing buffers. 199 */ 200 static int listbuf_ncol; 201 202 static int listbuf_goto_buffer(int f, int n); 203 204 static PF listbuf_pf[] = { 205 listbuf_goto_buffer, 206 }; 207 208 static struct KEYMAPE (1 + IMAPEXT) listbufmap = { 209 1, 210 1 + IMAPEXT, 211 rescan, 212 { 213 { CCHR('M'), CCHR('M'), listbuf_pf, NULL }, 214 } 215 }; 216 217 /* 218 * Display the buffer list. This is done 219 * in two parts. The "makelist" routine figures out 220 * the text, and puts it in a buffer. "popbuf" 221 * then pops the data onto the screen. Bound to 222 * "C-X C-B". 223 */ 224 /* ARGSUSED */ 225 int 226 listbuffers(int f, int n) 227 { 228 static int initialized = 0; 229 BUFFER *bp; 230 MGWIN *wp; 231 232 if (!initialized) { 233 maps_add((KEYMAP *)&listbufmap, "listbufmap"); 234 initialized = 1; 235 } 236 237 if ((bp = makelist()) == NULL || (wp = popbuf(bp)) == NULL) 238 return FALSE; 239 wp->w_dotp = bp->b_dotp;/* fix up if window already on screen */ 240 wp->w_doto = bp->b_doto; 241 bp->b_modes[0] = name_mode("fundamental"); 242 bp->b_modes[1] = name_mode("listbufmap"); 243 bp->b_nmodes = 1; 244 245 return TRUE; 246 } 247 248 /* 249 * This routine rebuilds the text for the 250 * list buffers command. Return TRUE if 251 * everything works. Return FALSE if there 252 * is an error (if there is no memory). 253 */ 254 static BUFFER * 255 makelist(void) 256 { 257 int w = ncol / 2; 258 BUFFER *bp, *blp; 259 LINE *lp; 260 261 262 if ((blp = bfind("*Buffer List*", TRUE)) == NULL) 263 return NULL; 264 if (bclear(blp) != TRUE) 265 return NULL; 266 blp->b_flag &= ~BFCHG; /* Blow away old. */ 267 blp->b_flag |= BFREADONLY; 268 269 listbuf_ncol = ncol; /* cache ncol for listbuf_goto_buffer */ 270 271 if (addlinef(blp, "%-*s%s", w, " MR Buffer", "Size File") == FALSE || 272 addlinef(blp, "%-*s%s", w, " -- ------", "---- ----") == FALSE) 273 return NULL; 274 275 for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { 276 RSIZE nbytes; 277 278 nbytes = 0; /* Count bytes in buf. */ 279 if (bp != blp) { 280 lp = lforw(bp->b_linep); 281 while (lp != bp->b_linep) { 282 nbytes += llength(lp) + 1; 283 lp = lforw(lp); 284 } 285 if (nbytes) 286 nbytes--; /* no bonus newline */ 287 } 288 289 if (addlinef(blp, "%c%c%c %-*.*s%c%-6d %-*s", 290 (bp == curbp) ? '.' : ' ', /* current buffer ? */ 291 ((bp->b_flag & BFCHG) != 0) ? '*' : ' ', /* changed ? */ 292 ((bp->b_flag & BFREADONLY) != 0) ? ' ' : '*', 293 w - 5, /* four chars already written */ 294 w - 5, /* four chars already written */ 295 bp->b_bname, /* buffer name */ 296 strlen(bp->b_bname) < w - 5 ? ' ' : '$', /* truncated? */ 297 nbytes, /* buffer size */ 298 w - 7, /* seven chars already written */ 299 bp->b_fname) == FALSE) 300 return NULL; 301 } 302 blp->b_dotp = lforw(blp->b_linep); /* put dot at beginning of 303 * buffer */ 304 blp->b_doto = 0; 305 return blp; /* All done */ 306 } 307 308 static int 309 listbuf_goto_buffer(int f, int n) 310 { 311 BUFFER *bp; 312 MGWIN *wp; 313 char *line; 314 int i; 315 316 if (curwp->w_dotp->l_text[listbuf_ncol/2 - 1] == '$') { 317 ewprintf("buffer name truncated"); 318 return FALSE; 319 } 320 321 if ((line = malloc(listbuf_ncol/2)) == NULL) 322 return FALSE; 323 324 memcpy(line, curwp->w_dotp->l_text + 4, listbuf_ncol/2 - 5); 325 for (i = listbuf_ncol/2 - 6; i > 0; i--) { 326 if (line[i] != ' ') { 327 line[i + 1] = '\0'; 328 break; 329 } 330 } 331 if (i == 0) { 332 return FALSE; 333 } 334 335 for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { 336 if (strcmp(bp->b_bname, line) == 0) 337 break; 338 } 339 if (bp == NULL) { 340 return FALSE; 341 } 342 if ((wp = popbuf(bp)) == NULL) 343 return FALSE; 344 curbp = bp; 345 curwp = wp; 346 347 return TRUE; 348 } 349 350 /* 351 * The argument "text" points to a format string. Append this line to the 352 * buffer. Handcraft the EOL on the end. Return TRUE if it worked and 353 * FALSE if you ran out of room. 354 */ 355 int 356 addlinef(BUFFER *bp, char *fmt, ...) 357 { 358 va_list ap; 359 LINE *lp; 360 361 if ((lp = lalloc(0)) == NULL) 362 return (FALSE); 363 va_start(ap, fmt); 364 if (vasprintf(&lp->l_text, fmt, ap) == -1) { 365 lfree(lp); 366 va_end(ap); 367 return (FALSE); 368 } 369 lp->l_used = strlen(lp->l_text); 370 va_end(ap); 371 372 bp->b_linep->l_bp->l_fp = lp; /* Hook onto the end */ 373 lp->l_bp = bp->b_linep->l_bp; 374 bp->b_linep->l_bp = lp; 375 lp->l_fp = bp->b_linep; 376 377 return TRUE; 378 } 379 380 /* 381 * Look through the list of buffers, giving the user a chance to save them. 382 * Return TRUE if there are any changed buffers afterwards. Buffers that 383 * don't have an associated file don't count. Return FALSE if there are 384 * no changed buffers. 385 */ 386 int 387 anycb(int f) 388 { 389 BUFFER *bp; 390 int s = FALSE, save = FALSE; 391 char prompt[NFILEN + 11]; 392 393 for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { 394 if (bp->b_fname != NULL && *(bp->b_fname) != '\0' && 395 (bp->b_flag & BFCHG) != 0) { 396 snprintf(prompt, sizeof prompt, "Save file %s", 397 bp->b_fname); 398 if ((f == TRUE || (save = eyorn(prompt)) == TRUE) && 399 buffsave(bp) == TRUE) { 400 bp->b_flag &= ~BFCHG; 401 upmodes(bp); 402 } else 403 s = TRUE; 404 if (save == ABORT) 405 return (save); 406 save = TRUE; 407 } 408 } 409 if (save == FALSE /* && kbdmop == NULL */ ) /* experimental */ 410 ewprintf("(No files need saving)"); 411 return s; 412 } 413 414 /* 415 * Search for a buffer, by name. 416 * If not found, and the "cflag" is TRUE, 417 * create a buffer and put it in the list of 418 * all buffers. Return pointer to the BUFFER 419 * block for the buffer. 420 */ 421 BUFFER * 422 bfind(const char *bname, int cflag) 423 { 424 BUFFER *bp; 425 LINE *lp; 426 int i; 427 428 bp = bheadp; 429 while (bp != NULL) { 430 if (strcmp(bname, bp->b_bname) == 0) 431 return bp; 432 bp = bp->b_bufp; 433 } 434 if (cflag != TRUE) 435 return NULL; 436 437 if ((bp = malloc(sizeof(BUFFER))) == NULL) { 438 ewprintf("Can't get %d bytes", sizeof(BUFFER)); 439 return NULL; 440 } 441 if ((bp->b_bname = strdup(bname)) == NULL) { 442 ewprintf("Can't get %d bytes", strlen(bname) + 1); 443 free(bp); 444 return NULL; 445 } 446 if ((lp = lalloc(0)) == NULL) { 447 free((char *) bp->b_bname); 448 free(bp); 449 return NULL; 450 } 451 bp->b_altb = bp->b_bufp = NULL; 452 bp->b_dotp = lp; 453 bp->b_doto = 0; 454 bp->b_markp = NULL; 455 bp->b_marko = 0; 456 bp->b_flag = defb_flag; 457 bp->b_nwnd = 0; 458 bp->b_linep = lp; 459 bp->b_nmodes = defb_nmodes; 460 LIST_INIT(&bp->b_undo); 461 bp->b_undoptr = NULL; 462 memset(&bp->b_undopos, 0, sizeof bp->b_undopos); 463 i = 0; 464 do { 465 bp->b_modes[i] = defb_modes[i]; 466 } while (i++ < defb_nmodes); 467 bp->b_fname[0] = '\0'; 468 bzero(&bp->b_fi, sizeof(bp->b_fi)); 469 lp->l_fp = lp; 470 lp->l_bp = lp; 471 bp->b_bufp = bheadp; 472 bheadp = bp; 473 return bp; 474 } 475 476 /* 477 * This routine blows away all of the text 478 * in a buffer. If the buffer is marked as changed 479 * then we ask if it is ok to blow it away; this is 480 * to save the user the grief of losing text. The 481 * window chain is nearly always wrong if this gets 482 * called; the caller must arrange for the updates 483 * that are required. Return TRUE if everything 484 * looks good. 485 */ 486 int 487 bclear(BUFFER *bp) 488 { 489 LINE *lp; 490 int s; 491 492 if ((bp->b_flag & BFCHG) != 0 && /* Changed. */ 493 (s = eyesno("Buffer modified; kill anyway")) != TRUE) 494 return (s); 495 bp->b_flag &= ~BFCHG; /* Not changed */ 496 while ((lp = lforw(bp->b_linep)) != bp->b_linep) 497 lfree(lp); 498 bp->b_dotp = bp->b_linep; /* Fix "." */ 499 bp->b_doto = 0; 500 bp->b_markp = NULL; /* Invalidate "mark" */ 501 bp->b_marko = 0; 502 return TRUE; 503 } 504 505 /* 506 * Display the given buffer in the given window. Flags indicated 507 * action on redisplay. 508 */ 509 int 510 showbuffer(BUFFER *bp, MGWIN *wp, int flags) 511 { 512 BUFFER *obp; 513 MGWIN *owp; 514 515 if (wp->w_bufp == bp) { /* Easy case! */ 516 wp->w_flag |= flags; 517 return TRUE; 518 } 519 /* First, dettach the old buffer from the window */ 520 if ((bp->b_altb = obp = wp->w_bufp) != NULL) { 521 if (--obp->b_nwnd == 0) { 522 obp->b_dotp = wp->w_dotp; 523 obp->b_doto = wp->w_doto; 524 obp->b_markp = wp->w_markp; 525 obp->b_marko = wp->w_marko; 526 } 527 } 528 /* Now, attach the new buffer to the window */ 529 wp->w_bufp = bp; 530 531 if (bp->b_nwnd++ == 0) { /* First use. */ 532 wp->w_dotp = bp->b_dotp; 533 wp->w_doto = bp->b_doto; 534 wp->w_markp = bp->b_markp; 535 wp->w_marko = bp->b_marko; 536 } else 537 /* already on screen, steal values from other window */ 538 for (owp = wheadp; owp != NULL; owp = wp->w_wndp) 539 if (wp->w_bufp == bp && owp != wp) { 540 wp->w_dotp = owp->w_dotp; 541 wp->w_doto = owp->w_doto; 542 wp->w_markp = owp->w_markp; 543 wp->w_marko = owp->w_marko; 544 break; 545 } 546 wp->w_flag |= WFMODE | flags; 547 return TRUE; 548 } 549 550 /* 551 * Pop the buffer we got passed onto the screen. 552 * Returns a status. 553 */ 554 MGWIN * 555 popbuf(BUFFER *bp) 556 { 557 MGWIN *wp; 558 559 if (bp->b_nwnd == 0) { /* Not on screen yet. */ 560 if ((wp = wpopup()) == NULL) 561 return NULL; 562 } else 563 for (wp = wheadp; wp != NULL; wp = wp->w_wndp) 564 if (wp->w_bufp == bp) { 565 wp->w_flag |= WFHARD | WFFORCE; 566 return wp; 567 } 568 if (showbuffer(bp, wp, WFHARD) != TRUE) 569 return NULL; 570 return wp; 571 } 572 573 /* 574 * Insert another buffer at dot. Very useful. 575 */ 576 /* ARGSUSED */ 577 int 578 bufferinsert(int f, int n) 579 { 580 BUFFER *bp; 581 LINE *clp; 582 int clo; 583 int nline; 584 int s; 585 char bufn[NBUFN]; 586 587 /* Get buffer to use from user */ 588 if (curbp->b_altb != NULL) 589 s = eread("Insert buffer: (default %s) ", bufn, NBUFN, 590 EFNEW | EFBUF, &(curbp->b_altb->b_bname), NULL); 591 else 592 s = eread("Insert buffer: ", bufn, NBUFN, EFNEW | EFBUF, NULL); 593 if (s == ABORT) 594 return (s); 595 if (s == FALSE && curbp->b_altb != NULL) 596 bp = curbp->b_altb; 597 else if ((bp = bfind(bufn, FALSE)) == NULL) 598 return FALSE; 599 600 if (bp == curbp) { 601 ewprintf("Cannot insert buffer into self"); 602 return FALSE; 603 } 604 /* insert the buffer */ 605 nline = 0; 606 clp = lforw(bp->b_linep); 607 for (;;) { 608 for (clo = 0; clo < llength(clp); clo++) 609 if (linsert(1, lgetc(clp, clo)) == FALSE) 610 return FALSE; 611 if ((clp = lforw(clp)) == bp->b_linep) 612 break; 613 if (newline(FFRAND, 1) == FALSE) /* fake newline */ 614 return FALSE; 615 nline++; 616 } 617 if (nline == 1) 618 ewprintf("[Inserted 1 line]"); 619 else 620 ewprintf("[Inserted %d lines]", nline); 621 622 clp = curwp->w_linep; /* cosmetic adjustment */ 623 if (curwp->w_dotp == clp) { /* for offscreen insert */ 624 while (nline-- && lback(clp) != curbp->b_linep) 625 clp = lback(clp); 626 curwp->w_linep = clp; /* adjust framing. */ 627 curwp->w_flag |= WFHARD; 628 } 629 return (TRUE); 630 } 631 632 /* 633 * Turn off the dirty bit on this buffer. 634 */ 635 /* ARGSUSED */ 636 int 637 notmodified(int f, int n) 638 { 639 MGWIN *wp; 640 641 curbp->b_flag &= ~BFCHG; 642 wp = wheadp; /* Update mode lines. */ 643 while (wp != NULL) { 644 if (wp->w_bufp == curbp) 645 wp->w_flag |= WFMODE; 646 wp = wp->w_wndp; 647 } 648 ewprintf("Modification-flag cleared"); 649 return TRUE; 650 } 651 652 #ifndef NO_HELP 653 /* 654 * Popbuf and set all windows to top of buffer. Currently only used by 655 * help functions. 656 */ 657 int 658 popbuftop(BUFFER *bp) 659 { 660 MGWIN *wp; 661 662 bp->b_dotp = lforw(bp->b_linep); 663 bp->b_doto = 0; 664 if (bp->b_nwnd != 0) { 665 for (wp = wheadp; wp != NULL; wp = wp->w_wndp) 666 if (wp->w_bufp == bp) { 667 wp->w_dotp = bp->b_dotp; 668 wp->w_doto = 0; 669 wp->w_flag |= WFHARD; 670 } 671 } 672 return popbuf(bp) != NULL; 673 } 674 #endif 675