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