1: /* 2: * Copyright (c) 1980 Regents of the University of California. 3: * All rights reserved. The Berkeley software License Agreement 4: * specifies the terms and conditions for redistribution. 5: */ 6: 7: #if !defined(lint) && defined(DOSCCS) 8: static char *sccsid = "@(#)ex_vops2.c 6.8 (Berkeley) 6/7/85"; 9: #endif 10: 11: #include "ex.h" 12: #include "ex_tty.h" 13: #include "ex_vis.h" 14: 15: /* 16: * Low level routines for operations sequences, 17: * and mostly, insert mode (and a subroutine 18: * to read an input line, including in the echo area.) 19: */ 20: extern char *vUA1, *vUA2; /* mjm: extern; also in ex_vops.c */ 21: extern char *vUD1, *vUD2; /* mjm: extern; also in ex_vops.c */ 22: 23: /* 24: * Obleeperate characters in hardcopy 25: * open with \'s. 26: */ 27: bleep(i, cp) 28: register int i; 29: char *cp; 30: { 31: 32: i -= column(cp); 33: do 34: putchar('\\' | QUOTE); 35: while (--i >= 0); 36: rubble = 1; 37: } 38: 39: /* 40: * Common code for middle part of delete 41: * and change operating on parts of lines. 42: */ 43: vdcMID() 44: { 45: register char *cp; 46: 47: squish(); 48: setLAST(); 49: if (FIXUNDO) 50: vundkind = VCHNG, CP(vutmp, linebuf); 51: if (wcursor < cursor) 52: cp = wcursor, wcursor = cursor, cursor = cp; 53: vUD1 = vUA1 = vUA2 = cursor; vUD2 = wcursor; 54: return (column(wcursor - 1)); 55: } 56: 57: /* 58: * Take text from linebuf and stick it 59: * in the VBSIZE buffer BUF. Used to save 60: * deleted text of part of line. 61: */ 62: takeout(BUF) 63: char *BUF; 64: { 65: register char *cp; 66: 67: if (wcursor < linebuf) 68: wcursor = linebuf; 69: if (cursor == wcursor) { 70: beep(); 71: return; 72: } 73: if (wcursor < cursor) { 74: cp = wcursor; 75: wcursor = cursor; 76: cursor = cp; 77: } 78: setBUF(BUF); 79: if ((BUF[0] & (QUOTE|TRIM)) == OVERBUF) 80: beep(); 81: } 82: 83: /* 84: * Are we at the end of the printed representation of the 85: * line? Used internally in hardcopy open. 86: */ 87: ateopr() 88: { 89: register int i, c; 90: register char *cp = vtube[destline] + destcol; 91: 92: for (i = WCOLS - destcol; i > 0; i--) { 93: c = *cp++; 94: if (c == 0) 95: return (1); 96: if (c != ' ' && (c & QUOTE) == 0) 97: return (0); 98: } 99: return (1); 100: } 101: 102: /* 103: * Append. 104: * 105: * This routine handles the top level append, doing work 106: * as each new line comes in, and arranging repeatability. 107: * It also handles append with repeat counts, and calculation 108: * of autoindents for new lines. 109: */ 110: bool vaifirst; 111: bool gobbled; 112: char *ogcursor; 113: 114: vappend(ch, cnt, indent) 115: int ch; /* mjm: char --> int */ 116: int cnt, indent; 117: { 118: register int i; 119: register char *gcursor; 120: bool escape; 121: int repcnt, savedoomed; 122: short oldhold = hold; 123: long oldmask; 124: 125: /* 126: * Before a move in hardopen when the line is dirty 127: * or we are in the middle of the printed representation, 128: * we retype the line to the left of the cursor so the 129: * insert looks clean. 130: */ 131: if (ch != 'o' && state == HARDOPEN && (rubble || !ateopr())) { 132: rubble = 1; 133: gcursor = cursor; 134: i = *gcursor; 135: *gcursor = ' '; 136: wcursor = gcursor; 137: vmove(); 138: *gcursor = i; 139: } 140: vaifirst = indent == 0; 141: 142: /* 143: * Handle replace character by (eventually) 144: * limiting the number of input characters allowed 145: * in the vgetline routine. 146: */ 147: if (ch == 'r') 148: repcnt = 2; 149: else 150: repcnt = 0; 151: 152: /* 153: * If an autoindent is specified, then 154: * generate a mixture of blanks to tabs to implement 155: * it and place the cursor after the indent. 156: * Text read by the vgetline routine will be placed in genbuf, 157: * so the indent is generated there. 158: */ 159: if (value(AUTOINDENT) && indent != 0) { 160: gcursor = genindent(indent); 161: *gcursor = 0; 162: vgotoCL(qcolumn(cursor - 1, genbuf)); 163: } else { 164: gcursor = genbuf; 165: *gcursor = 0; 166: if (ch == 'o') 167: vfixcurs(); 168: } 169: 170: /* 171: * Prepare for undo. Pointers delimit inserted portion of line. 172: */ 173: vUA1 = vUA2 = cursor; 174: 175: /* 176: * If we are not in a repeated command and a ^@ comes in 177: * then this means the previous inserted text. 178: * If there is none or it was too long to be saved, 179: * then beep() and also arrange to undo any damage done 180: * so far (e.g. if we are a change.) 181: */ 182: if ((vglobp && *vglobp == 0) || peekbr()) { 183: if ((INS[0] & (QUOTE|TRIM)) == OVERBUF) { 184: beep(); 185: if (!splitw) 186: ungetkey('u'); 187: doomed = 0; 188: hold = oldhold; 189: return; 190: } 191: /* 192: * Unread input from INS. 193: * An escape will be generated at end of string. 194: * Hold off n^^2 type update on dumb terminals. 195: */ 196: vglobp = INS; 197: hold |= HOLDQIK; 198: } else if (vglobp == 0) 199: /* 200: * Not a repeated command, get 201: * a new inserted text for repeat. 202: */ 203: INS[0] = 0; 204: 205: /* 206: * For wrapmargin to hack away second space after a '.' 207: * when the first space caused a line break we keep 208: * track that this happened in gobblebl, which says 209: * to gobble up a blank silently. 210: */ 211: gobblebl = 0; 212: 213: oldmask = sigblock(sigmask(SIGWINCH)); 214: /* 215: * Text gathering loop. 216: * New text goes into genbuf starting at gcursor. 217: * cursor preserves place in linebuf where text will eventually go. 218: */ 219: if (*cursor == 0 || state == CRTOPEN) 220: hold |= HOLDROL; 221: for (;;) { 222: if (ch == 'r' && repcnt == 0) 223: escape = 0; 224: else { 225: gcursor = vgetline(repcnt, gcursor, &escape, ch); 226: 227: /* 228: * After an append, stick information 229: * about the ^D's and ^^D's and 0^D's in 230: * the repeated text buffer so repeated 231: * inserts of stuff indented with ^D as backtab's 232: * can work. 233: */ 234: if (HADUP) 235: addtext("^"); 236: else if (HADZERO) 237: addtext("0"); 238: while (CDCNT > 0) 239: addtext("\204"), CDCNT--; 240: if (gobbled) 241: addtext(" "); 242: addtext(ogcursor); 243: } 244: repcnt = 0; 245: 246: /* 247: * Smash the generated and preexisting indents together 248: * and generate one cleanly made out of tabs and spaces 249: * if we are using autoindent. 250: */ 251: if (!vaifirst && value(AUTOINDENT)) { 252: i = fixindent(indent); 253: if (!HADUP) 254: indent = i; 255: gcursor = strend(genbuf); 256: } 257: 258: /* 259: * Limit the repetition count based on maximum 260: * possible line length; do output implied 261: * by further count (> 1) and cons up the new line 262: * in linebuf. 263: */ 264: cnt = vmaxrep(ch, cnt); 265: CP(gcursor + 1, cursor); 266: do { 267: CP(cursor, genbuf); 268: if (cnt > 1) { 269: int oldhold = hold; 270: 271: Outchar = vinschar; 272: hold |= HOLDQIK; 273: printf("%s", genbuf); 274: hold = oldhold; 275: Outchar = vputchar; 276: } 277: cursor += gcursor - genbuf; 278: } while (--cnt > 0); 279: endim(); 280: vUA2 = cursor; 281: if (escape != '\n') 282: CP(cursor, gcursor + 1); 283: 284: /* 285: * If doomed characters remain, clobber them, 286: * and reopen the line to get the display exact. 287: */ 288: if (state != HARDOPEN) { 289: DEPTH(vcline) = 0; 290: savedoomed = doomed; 291: if (doomed > 0) { 292: register int cind = cindent(); 293: 294: physdc(cind, cind + doomed); 295: doomed = 0; 296: } 297: i = vreopen(LINE(vcline), lineDOT(), vcline); 298: #ifdef TRACE 299: if (trace) 300: fprintf(trace, "restoring doomed from %d to %d\n", doomed, savedoomed); 301: #endif 302: if (ch == 'R') 303: doomed = savedoomed; 304: } 305: 306: /* 307: * All done unless we are continuing on to another line. 308: */ 309: if (escape != '\n') 310: break; 311: 312: /* 313: * Set up for the new line. 314: * First save the current line, then construct a new 315: * first image for the continuation line consisting 316: * of any new autoindent plus the pushed ahead text. 317: */ 318: killU(); 319: addtext(gobblebl ? " " : "\n"); 320: vsave(); 321: cnt = 1; 322: if (value(AUTOINDENT)) { 323: #ifdef LISPCODE 324: if (value(LISP)) 325: indent = lindent(dot + 1); 326: else 327: #endif 328: if (!HADUP && vaifirst) 329: indent = whitecnt(linebuf); 330: vaifirst = 0; 331: strcLIN(vpastwh(gcursor + 1)); 332: gcursor = genindent(indent); 333: *gcursor = 0; 334: if (gcursor + strlen(linebuf) > &genbuf[LBSIZE - 2]) 335: gcursor = genbuf; 336: CP(gcursor, linebuf); 337: } else { 338: CP(genbuf, gcursor + 1); 339: gcursor = genbuf; 340: } 341: 342: /* 343: * If we started out as a single line operation and are now 344: * turning into a multi-line change, then we had better yank 345: * out dot before it changes so that undo will work 346: * correctly later. 347: */ 348: if (FIXUNDO && vundkind == VCHNG) { 349: vremote(1, yank, 0); 350: undap1--; 351: } 352: 353: /* 354: * Now do the append of the new line in the buffer, 355: * and update the display. If slowopen 356: * we don't do very much. 357: */ 358: vdoappend(genbuf); 359: vundkind = VMANYINS; 360: vcline++; 361: if (state != VISUAL) 362: vshow(dot, NOLINE); 363: else { 364: i += LINE(vcline - 1); 365: vopen(dot, i); 366: if (value(SLOWOPEN)) 367: vscrap(); 368: else 369: vsync1(LINE(vcline)); 370: } 371: strcLIN(gcursor); 372: *gcursor = 0; 373: cursor = linebuf; 374: vgotoCL(qcolumn(cursor - 1, genbuf)); 375: } 376: 377: /* 378: * All done with insertion, position the cursor 379: * and sync the screen. 380: */ 381: hold = oldhold; 382: if (cursor > linebuf) 383: cursor--; 384: if (state != HARDOPEN) 385: vsyncCL(); 386: else if (cursor > linebuf) 387: back1(); 388: doomed = 0; 389: wcursor = cursor; 390: vmove(); 391: (void)sigsetmask(oldmask); 392: } 393: 394: /* 395: * Subroutine for vgetline to back up a single character position, 396: * backwards around end of lines (vgoto can't hack columns which are 397: * less than 0 in general). 398: */ 399: back1() 400: { 401: 402: vgoto(destline - 1, WCOLS + destcol - 1); 403: } 404: 405: /* 406: * Get a line into genbuf after gcursor. 407: * Cnt limits the number of input characters 408: * accepted and is used for handling the replace 409: * single character command. Aescaped is the location 410: * where we stick a termination indicator (whether we 411: * ended with an ESCAPE or a newline/return. 412: * 413: * We do erase-kill type processing here and also 414: * are careful about the way we do this so that it is 415: * repeatable. (I.e. so that your kill doesn't happen, 416: * when you repeat an insert if it was escaped with \ the 417: * first time you did it. commch is the command character 418: * involved, including the prompt for readline. 419: */ 420: char * 421: vgetline(cnt, gcursor, aescaped, commch) 422: int cnt; 423: register char *gcursor; 424: bool *aescaped; 425: char commch; 426: { 427: register int c, ch; 428: register char *cp; 429: int x, y, iwhite, backsl=0; 430: char *iglobp; 431: char cstr[2]; 432: int (*OO)() = Outchar; 433: 434: /* 435: * Clear the output state and counters 436: * for autoindent backwards motion (counts of ^D, etc.) 437: * Remember how much white space at beginning of line so 438: * as not to allow backspace over autoindent. 439: */ 440: *aescaped = 0; 441: ogcursor = gcursor; 442: flusho(); 443: CDCNT = 0; 444: HADUP = 0; 445: HADZERO = 0; 446: gobbled = 0; 447: iwhite = whitecnt(genbuf); 448: iglobp = vglobp; 449: 450: /* 451: * Carefully avoid using vinschar in the echo area. 452: */ 453: if (splitw) 454: Outchar = vputchar; 455: else { 456: Outchar = vinschar; 457: vprepins(); 458: } 459: for (;;) { 460: backsl = 0; 461: if (gobblebl) 462: gobblebl--; 463: if (cnt != 0) { 464: cnt--; 465: if (cnt == 0) 466: goto vadone; 467: } 468: c = getkey(); 469: if (c != ATTN) 470: c &= (QUOTE|TRIM); 471: ch = c; 472: maphopcnt = 0; 473: if (vglobp == 0 && Peekkey == 0 && commch != 'r') 474: while ((ch = map(c, immacs)) != c) { 475: c = ch; 476: if (!value(REMAP)) 477: break; 478: if (++maphopcnt > 256) 479: error("Infinite macro loop"); 480: } 481: if (!iglobp) { 482: 483: /* 484: * Erase-kill type processing. 485: * Only happens if we were not reading 486: * from untyped input when we started. 487: * Map users erase to ^H, kill to -1 for switch. 488: */ 489: #ifndef USG3TTY 490: if (c == tty.sg_erase) 491: c = CTRL(h); 492: else if (c == tty.sg_kill) 493: c = -1; 494: #else 495: if (c == tty.c_cc[VERASE]) 496: c = CTRL(h); 497: else if (c == tty.c_cc[VKILL]) 498: c = -1; 499: #endif 500: switch (c) { 501: 502: /* 503: * ^? Interrupt drops you back to visual 504: * command mode with an unread interrupt 505: * still in the input buffer. 506: * 507: * ^\ Quit does the same as interrupt. 508: * If you are a ex command rather than 509: * a vi command this will drop you 510: * back to command mode for sure. 511: */ 512: case ATTN: 513: case QUIT: 514: ungetkey(c); 515: goto vadone; 516: 517: /* 518: * ^H Backs up a character in the input. 519: * 520: * BUG: Can't back around line boundaries. 521: * This is hard because stuff has 522: * already been saved for repeat. 523: */ 524: case CTRL(h): 525: bakchar: 526: cp = gcursor - 1; 527: if (cp < ogcursor) { 528: if (splitw) { 529: /* 530: * Backspacing over readecho 531: * prompt. Pretend delete but 532: * don't beep. 533: */ 534: ungetkey(c); 535: goto vadone; 536: } 537: beep(); 538: continue; 539: } 540: goto vbackup; 541: 542: /* 543: * ^W Back up a white/non-white word. 544: */ 545: case CTRL(w): 546: wdkind = 1; 547: for (cp = gcursor; cp > ogcursor && isspace(cp[-1]); cp--) 548: continue; 549: for (c = wordch(cp - 1); 550: cp > ogcursor && wordof(c, cp - 1); cp--) 551: continue; 552: goto vbackup; 553: 554: /* 555: * users kill Kill input on this line, back to 556: * the autoindent. 557: */ 558: case -1: 559: cp = ogcursor; 560: vbackup: 561: if (cp == gcursor) { 562: beep(); 563: continue; 564: } 565: endim(); 566: *cp = 0; 567: c = cindent(); 568: vgotoCL(qcolumn(cursor - 1, genbuf)); 569: if (doomed >= 0) 570: doomed += c - cindent(); 571: gcursor = cp; 572: continue; 573: 574: /* 575: * \ Followed by erase or kill 576: * maps to just the erase or kill. 577: */ 578: case '\\': 579: x = destcol, y = destline; 580: putchar('\\'); 581: vcsync(); 582: c = getkey(); 583: #ifndef USG3TTY 584: if (c == tty.sg_erase || c == tty.sg_kill) 585: #else 586: if (c == tty.c_cc[VERASE] 587: || c == tty.c_cc[VKILL]) 588: #endif 589: { 590: vgoto(y, x); 591: if (doomed >= 0) 592: doomed++; 593: goto def; 594: } 595: ungetkey(c), c = '\\'; 596: backsl = 1; 597: break; 598: 599: /* 600: * ^Q Super quote following character 601: * Only ^@ is verboten (trapped at 602: * a lower level) and \n forces a line 603: * split so doesn't really go in. 604: * 605: * ^V Synonym for ^Q 606: */ 607: case CTRL(q): 608: case CTRL(v): 609: x = destcol, y = destline; 610: putchar('^'); 611: vgoto(y, x); 612: c = getkey(); 613: #ifdef TIOCSETC 614: if (c == ATTN) 615: c = nttyc.t_intrc; 616: #endif 617: if (c != NL) { 618: if (doomed >= 0) 619: doomed++; 620: goto def; 621: } 622: break; 623: } 624: } 625: 626: /* 627: * If we get a blank not in the echo area 628: * consider splitting the window in the wrapmargin. 629: */ 630: if (c != NL && !splitw) { 631: if (c == ' ' && gobblebl) { 632: gobbled = 1; 633: continue; 634: } 635: if (value(WRAPMARGIN) && 636: (outcol >= OCOLUMNS - value(WRAPMARGIN) || 637: backsl && outcol==0) && 638: commch != 'r') { 639: /* 640: * At end of word and hit wrapmargin. 641: * Move the word to next line and keep going. 642: */ 643: wdkind = 1; 644: *gcursor++ = c; 645: if (backsl) 646: *gcursor++ = getkey(); 647: *gcursor = 0; 648: /* 649: * Find end of previous word if we are past it. 650: */ 651: for (cp=gcursor; cp>ogcursor && isspace(cp[-1]); cp--) 652: ; 653: if (outcol+(backsl?OCOLUMNS:0) - (gcursor-cp) >= OCOLUMNS - value(WRAPMARGIN)) { 654: /* 655: * Find beginning of previous word. 656: */ 657: for (; cp>ogcursor && !isspace(cp[-1]); cp--) 658: ; 659: if (cp <= ogcursor) { 660: /* 661: * There is a single word that 662: * is too long to fit. Just 663: * let it pass, but beep for 664: * each new letter to warn 665: * the luser. 666: */ 667: c = *--gcursor; 668: *gcursor = 0; 669: beep(); 670: goto dontbreak; 671: } 672: /* 673: * Save it for next line. 674: */ 675: macpush(cp, 0); 676: cp--; 677: } 678: macpush("\n", 0); 679: /* 680: * Erase white space before the word. 681: */ 682: while (cp > ogcursor && isspace(cp[-1])) 683: cp--; /* skip blank */ 684: gobblebl = 3; 685: goto vbackup; 686: } 687: dontbreak:; 688: } 689: 690: /* 691: * Word abbreviation mode. 692: */ 693: cstr[0] = c; 694: if (anyabbrs && gcursor > ogcursor && !wordch(cstr) && wordch(gcursor-1)) { 695: int wdtype, abno; 696: 697: cstr[1] = 0; 698: wdkind = 1; 699: cp = gcursor - 1; 700: for (wdtype = wordch(cp - 1); 701: cp > ogcursor && wordof(wdtype, cp - 1); cp--) 702: ; 703: *gcursor = 0; 704: for (abno=0; abbrevs[abno].mapto; abno++) { 705: if (eq(cp, abbrevs[abno].cap)) { 706: macpush(cstr, 0); 707: macpush(abbrevs[abno].mapto); 708: goto vbackup; 709: } 710: } 711: } 712: 713: switch (c) { 714: 715: /* 716: * ^M Except in repeat maps to \n. 717: */ 718: case CR: 719: if (vglobp) 720: goto def; 721: c = '\n'; 722: /* presto chango ... */ 723: 724: /* 725: * \n Start new line. 726: */ 727: case NL: 728: *aescaped = c; 729: goto vadone; 730: 731: /* 732: * escape End insert unless repeat and more to repeat. 733: */ 734: case ESCAPE: 735: if (lastvgk) 736: goto def; 737: goto vadone; 738: 739: /* 740: * ^D Backtab. 741: * ^T Software forward tab. 742: * 743: * Unless in repeat where this means these 744: * were superquoted in. 745: */ 746: case CTRL(d): 747: case CTRL(t): 748: if (vglobp) 749: goto def; 750: /* fall into ... */ 751: 752: /* 753: * ^D|QUOTE Is a backtab (in a repeated command). 754: */ 755: case CTRL(d) | QUOTE: 756: *gcursor = 0; 757: cp = vpastwh(genbuf); 758: c = whitecnt(genbuf); 759: if (ch == CTRL(t)) { 760: /* 761: * ^t just generates new indent replacing 762: * current white space rounded up to soft 763: * tab stop increment. 764: */ 765: if (cp != gcursor) 766: /* 767: * BUG: Don't hack ^T except 768: * right after initial 769: * white space. 770: */ 771: continue; 772: cp = genindent(iwhite = backtab(c + value(SHIFTWIDTH) + 1)); 773: ogcursor = cp; 774: goto vbackup; 775: } 776: /* 777: * ^D works only if we are at the (end of) the 778: * generated autoindent. We count the ^D for repeat 779: * purposes. 780: */ 781: if (c == iwhite && c != 0) 782: if (cp == gcursor) { 783: iwhite = backtab(c); 784: CDCNT++; 785: ogcursor = cp = genindent(iwhite); 786: goto vbackup; 787: } else if (&cp[1] == gcursor && 788: (*cp == '^' || *cp == '0')) { 789: /* 790: * ^^D moves to margin, then back 791: * to current indent on next line. 792: * 793: * 0^D moves to margin and then 794: * stays there. 795: */ 796: HADZERO = *cp == '0'; 797: ogcursor = cp = genbuf; 798: HADUP = 1 - HADZERO; 799: CDCNT = 1; 800: endim(); 801: back1(); 802: vputchar(' '); 803: goto vbackup; 804: } 805: if (vglobp && vglobp - iglobp >= 2 && 806: (vglobp[-2] == '^' || vglobp[-2] == '0') 807: && gcursor == ogcursor + 1) 808: goto bakchar; 809: continue; 810: 811: default: 812: /* 813: * Possibly discard control inputs. 814: */ 815: if (!vglobp && junk(c)) { 816: beep(); 817: continue; 818: } 819: def: 820: if (!backsl) { 821: int cnt; 822: putchar(c); 823: flush(); 824: } 825: if (gcursor > &genbuf[LBSIZE - 2]) 826: error("Line too long"); 827: *gcursor++ = c & TRIM; 828: vcsync(); 829: if (value(SHOWMATCH) && !iglobp) 830: if (c == ')' || c == '}') 831: lsmatch(gcursor); 832: continue; 833: } 834: } 835: vadone: 836: *gcursor = 0; 837: if (Outchar != termchar) 838: Outchar = OO; 839: endim(); 840: return (gcursor); 841: } 842: 843: int vgetsplit(); 844: char *vsplitpt; 845: 846: /* 847: * Append the line in buffer at lp 848: * to the buffer after dot. 849: */ 850: vdoappend(lp) 851: char *lp; 852: { 853: register int oing = inglobal; 854: 855: vsplitpt = lp; 856: inglobal = 1; 857: ignore(append(vgetsplit, dot)); 858: inglobal = oing; 859: } 860: 861: /* 862: * Subroutine for vdoappend to pass to append. 863: */ 864: vgetsplit() 865: { 866: 867: if (vsplitpt == 0) 868: return (EOF); 869: strcLIN(vsplitpt); 870: vsplitpt = 0; 871: return (0); 872: } 873: 874: /* 875: * Vmaxrep determines the maximum repetitition factor 876: * allowed that will yield total line length less than 877: * LBSIZE characters and also does hacks for the R command. 878: */ 879: vmaxrep(ch, cnt) 880: char ch; 881: register int cnt; 882: { 883: register int len, replen; 884: 885: if (cnt > LBSIZE - 2) 886: cnt = LBSIZE - 2; 887: replen = strlen(genbuf); 888: if (ch == 'R') { 889: len = strlen(cursor); 890: if (replen < len) 891: len = replen; 892: CP(cursor, cursor + len); 893: vUD2 += len; 894: } 895: len = strlen(linebuf); 896: if (len + cnt * replen <= LBSIZE - 2) 897: return (cnt); 898: cnt = (LBSIZE - 2 - len) / replen; 899: if (cnt == 0) { 900: vsave(); 901: error("Line too long"); 902: } 903: return (cnt); 904: }