1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. 24 */ 25 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 27 /* All Rights Reserved */ 28 29 30 /* Copyright (c) 1981 Regents of the University of California */ 31 32 #include "ex.h" 33 #include "ex_tty.h" 34 #include "ex_vis.h" 35 #ifndef PRESUNEUC 36 #include <wctype.h> 37 /* Undef putchar/getchar if they're defined. */ 38 #ifdef putchar 39 # undef putchar 40 #endif 41 #ifdef getchar 42 # undef getchar 43 #endif 44 #endif /* PRESUNEUC */ 45 46 extern size_t strlcpy(char *, const char *, size_t); 47 48 /* 49 * Low level routines for operations sequences, 50 * and mostly, insert mode (and a subroutine 51 * to read an input line, including in the echo area.) 52 */ 53 extern unsigned char *vUA1, *vUA2; /* extern; also in ex_vops.c */ 54 extern unsigned char *vUD1, *vUD2; /* extern; also in ex_vops.c */ 55 56 #ifdef XPG6 57 /* XPG6 assertion 313 & 254 [count]r\n : Also used in ex_vmain.c */ 58 extern int redisplay; 59 #endif 60 61 int vmaxrep(unsigned char, int); 62 static void imultlinerep(int, line *, int, int); 63 static void omultlinerep(int, line *, int); 64 #ifdef XPG6 65 static void rmultlinerep(int, int); 66 #endif 67 void fixdisplay(void); 68 69 /* 70 * Obleeperate characters in hardcopy 71 * open with \'s. 72 */ 73 void 74 bleep(int i, unsigned char *cp) 75 { 76 77 i -= lcolumn(nextchr(cp)); 78 do 79 putchar('\\' | QUOTE); 80 while (--i >= 0); 81 rubble = 1; 82 } 83 84 /* 85 * Common code for middle part of delete 86 * and change operating on parts of lines. 87 */ 88 int 89 vdcMID(void) 90 { 91 unsigned char *cp; 92 93 squish(); 94 setLAST(); 95 if (FIXUNDO) 96 vundkind = VCHNG, CP(vutmp, linebuf); 97 if (wcursor < cursor) 98 cp = wcursor, wcursor = cursor, cursor = cp; 99 vUD1 = vUA1 = vUA2 = cursor; vUD2 = wcursor; 100 /* 101 * XPG6 assertion 273: Set vmcurs so that undo positions the 102 * cursor column correctly when we've moved off the initial line 103 * that was changed, as with the C, c, and s commands, 104 * when G has moved us off the line, or when a 105 * multi-line change was done. 106 */ 107 fixundo(); 108 return (lcolumn(wcursor)); 109 } 110 111 /* 112 * Take text from linebuf and stick it 113 * in the VBSIZE buffer BUF. Used to save 114 * deleted text of part of line. 115 */ 116 void 117 takeout(unsigned char *BUF) 118 { 119 unsigned char *cp; 120 121 if (wcursor < linebuf) 122 wcursor = linebuf; 123 if (cursor == wcursor) { 124 (void) beep(); 125 return; 126 } 127 if (wcursor < cursor) { 128 cp = wcursor; 129 wcursor = cursor; 130 cursor = cp; 131 } 132 setBUF(BUF); 133 if ((unsigned char)BUF[128] == 0200) 134 (void) beep(); 135 } 136 137 /* 138 * Are we at the end of the printed representation of the 139 * line? Used internally in hardcopy open. 140 */ 141 int 142 ateopr(void) 143 { 144 wchar_t i, c; 145 wchar_t *cp = vtube[destline] + destcol; 146 147 for (i = WCOLS - destcol; i > 0; i--) { 148 c = *cp++; 149 if (c == 0) { 150 /* 151 * Optimization to consider returning early, saving 152 * CPU time. We have to make a special check that 153 * we aren't missing a mode indicator. 154 */ 155 if (destline == WECHO && destcol < WCOLS-11 && vtube[WECHO][WCOLS-20]) 156 return 0; 157 return (1); 158 } 159 if (c != ' ' && (c & QUOTE) == 0) 160 return (0); 161 } 162 return (1); 163 } 164 165 /* 166 * Append. 167 * 168 * This routine handles the top level append, doing work 169 * as each new line comes in, and arranging repeatability. 170 * It also handles append with repeat counts, and calculation 171 * of autoindents for new lines. 172 */ 173 bool vaifirst; 174 bool gobbled; 175 unsigned char *ogcursor; 176 177 static int INSCDCNT; /* number of ^D's (backtabs) in insertion buffer */ 178 179 static int inscdcnt; /* 180 * count of ^D's (backtabs) not seen yet when doing 181 * repeat of insertion 182 */ 183 184 void 185 vappend(int ch, int cnt, int indent) 186 { 187 int i; 188 unsigned char *gcursor; 189 bool escape; 190 int repcnt, savedoomed; 191 short oldhold = hold; 192 int savecnt = cnt; 193 line *startsrcline; 194 int startsrccol, endsrccol; 195 int gotNL = 0; 196 int imultlinecnt = 0; 197 int omultlinecnt = 0; 198 199 if ((savecnt > 1) && (ch == 'o' || ch == 'O')) { 200 omultlinecnt = 1; 201 } 202 #ifdef XPG6 203 if ((savecnt > 1) && (ch == 'a' || ch == 'A' || ch == 'i' || ch == 'I')) 204 imultlinecnt = 1; 205 #endif /* XPG6 */ 206 207 /* 208 * Before a move in hardopen when the line is dirty 209 * or we are in the middle of the printed representation, 210 * we retype the line to the left of the cursor so the 211 * insert looks clean. 212 */ 213 214 if (ch != 'o' && state == HARDOPEN && (rubble || !ateopr())) { 215 rubble = 1; 216 gcursor = cursor; 217 i = *gcursor; 218 *gcursor = ' '; 219 wcursor = gcursor; 220 (void) vmove(); 221 *gcursor = i; 222 } 223 /* 224 * If vrep() passed indent = 0, this is the 'r' command, 225 * so don't autoindent until the last char. 226 */ 227 vaifirst = indent == 0; 228 229 /* 230 * Handle replace character by (eventually) 231 * limiting the number of input characters allowed 232 * in the vgetline routine. 233 */ 234 if (ch == 'r') 235 repcnt = 2; 236 else 237 repcnt = 0; 238 239 /* 240 * If an autoindent is specified, then 241 * generate a mixture of blanks to tabs to implement 242 * it and place the cursor after the indent. 243 * Text read by the vgetline routine will be placed in genbuf, 244 * so the indent is generated there. 245 */ 246 if (value(vi_AUTOINDENT) && indent != 0) { 247 unsigned char x; 248 gcursor = genindent(indent); 249 *gcursor = 0; 250 vgotoCL(nqcolumn(lastchr(linebuf, cursor), genbuf)); 251 } else { 252 gcursor = genbuf; 253 *gcursor = 0; 254 if (ch == 'o') 255 vfixcurs(); 256 } 257 258 /* 259 * Prepare for undo. Pointers delimit inserted portion of line. 260 */ 261 vUA1 = vUA2 = cursor; 262 263 /* 264 * If we are not in a repeated command and a ^@ comes in 265 * then this means the previous inserted text. 266 * If there is none or it was too long to be saved, 267 * then beep() and also arrange to undo any damage done 268 * so far (e.g. if we are a change.) 269 */ 270 switch (ch) { 271 case 'r': 272 break; 273 case 'a': 274 /* 275 * TRANSLATION_NOTE 276 * "A" is a terse mode message corresponding to 277 * "APPEND MODE". 278 * Translated message of "A" must be 1 character (not byte). 279 * Or, just leave it. 280 */ 281 if (value(vi_TERSE)) { 282 vshowmode(gettext("A")); 283 } else { 284 vshowmode(gettext("APPEND MODE")); 285 } 286 break; 287 case 's': 288 /* 289 * TRANSLATION_NOTE 290 * "S" is a terse mode message corresponding to 291 * "SUBSTITUTE MODE". 292 * Translated message of "S" must be 1 character (not byte). 293 * Or, just leave it. 294 */ 295 if (value(vi_TERSE)) { 296 vshowmode(gettext("S")); 297 } else { 298 vshowmode(gettext("SUBSTITUTE MODE")); 299 } 300 break; 301 case 'c': 302 /* 303 * TRANSLATION_NOTE 304 * "C" is a terse mode message corresponding to 305 * "CHANGE MODE". 306 * Translated message of "C" must be 1 character (not byte). 307 * Or, just leave it. 308 */ 309 if (value(vi_TERSE)) { 310 vshowmode(gettext("C")); 311 } else { 312 vshowmode(gettext("CHANGE MODE")); 313 } 314 break; 315 case 'R': 316 /* 317 * TRANSLATION_NOTE 318 * "R" is a terse mode message corresponding to 319 * "REPLACE MODE". 320 * Translated message of "R" must be 1 character (not byte). 321 * Or, just leave it. 322 */ 323 if (value(vi_TERSE)) { 324 vshowmode(gettext("R")); 325 } else { 326 vshowmode(gettext("REPLACE MODE")); 327 } 328 break; 329 case 'o': 330 /* 331 * TRANSLATION_NOTE 332 * "O" is a terse mode message corresponding to 333 * "OPEN MODE". 334 * Translated message of "O" must be 1 character (not byte). 335 * Or, just leave it. 336 */ 337 if (value(vi_TERSE)) { 338 vshowmode(gettext("O")); 339 } else { 340 vshowmode(gettext("OPEN MODE")); 341 } 342 break; 343 case 'i': 344 /* 345 * TRANSLATION_NOTE 346 * "I" is a terse mode message corresponding to 347 * "INSERT MODE" and the following "INPUT MODE". 348 * Translated message of "I" must be 1 character (not byte). 349 * Or, just leave it. 350 */ 351 if (value(vi_TERSE)) { 352 vshowmode(gettext("I")); 353 } else { 354 vshowmode(gettext("INSERT MODE")); 355 } 356 break; 357 default: 358 /* 359 * TRANSLATION_NOTE 360 * "I" is a terse mode message corresponding to 361 * "INPUT MODE" and the previous "INSERT MODE". 362 * Translated message of "I" must be 1 character (not byte). 363 * Or, just leave it. 364 */ 365 if (value(vi_TERSE)) { 366 vshowmode(gettext("I")); 367 } else { 368 vshowmode(gettext("INPUT MODE")); 369 } 370 } 371 ixlatctl(1); 372 if ((vglobp && *vglobp == 0) || peekbr()) { 373 if (INS[128] == 0200) { 374 (void) beep(); 375 if (!splitw) 376 ungetkey('u'); 377 doomed = 0; 378 hold = oldhold; 379 return; 380 } 381 /* 382 * Unread input from INS. 383 * An escape will be generated at end of string. 384 * Hold off n^^2 type update on dumb terminals. 385 */ 386 vglobp = INS; 387 inscdcnt = INSCDCNT; 388 hold |= HOLDQIK; 389 } else if (vglobp == 0) { 390 /* 391 * Not a repeated command, get 392 * a new inserted text for repeat. 393 */ 394 INS[0] = 0; 395 INS[128] = 0; 396 INSCDCNT = 0; 397 } 398 399 /* 400 * For wrapmargin to hack away second space after a '.' 401 * when the first space caused a line break we keep 402 * track that this happened in gobblebl, which says 403 * to gobble up a blank silently. 404 */ 405 gobblebl = 0; 406 407 startsrcline = dot; 408 startsrccol = cursor - linebuf; 409 410 /* 411 * Text gathering loop. 412 * New text goes into genbuf starting at gcursor. 413 * cursor preserves place in linebuf where text will eventually go. 414 */ 415 if (*cursor == 0 || state == CRTOPEN) 416 hold |= HOLDROL; 417 for (;;) { 418 if (ch == 'r' && repcnt == 0) 419 escape = 0; 420 else { 421 ixlatctl(1); 422 /* 423 * When vgetline() returns, gcursor is 424 * pointing to '\0' and vgetline() has 425 * read an ESCAPE or NL. 426 */ 427 gcursor = vgetline(repcnt, gcursor, &escape, ch); 428 if (escape == '\n') { 429 gotNL = 1; 430 #ifdef XPG6 431 if (ch == 'r') { 432 /* 433 * XPG6 assertion 313 [count]r\n : 434 * Arrange to set cursor correctly. 435 */ 436 endsrccol = gcursor - genbuf - 1; 437 } 438 #endif /* XPG6 */ 439 } else { 440 /* 441 * Upon escape, gcursor is pointing to '\0' 442 * terminating the string in genbuf. 443 */ 444 endsrccol = gcursor - genbuf - 1; 445 } 446 ixlatctl(0); 447 448 /* 449 * After an append, stick information 450 * about the ^D's and ^^D's and 0^D's in 451 * the repeated text buffer so repeated 452 * inserts of stuff indented with ^D as backtab's 453 * can work. 454 */ 455 if (HADUP) 456 addtext("^"); 457 else if (HADZERO) 458 addtext("0"); 459 if(!vglobp) 460 INSCDCNT = CDCNT; 461 while (CDCNT > 0) { 462 addtext("\004"); 463 CDCNT--; 464 } 465 if (gobbled) 466 addtext(" "); 467 addtext(ogcursor); 468 } 469 repcnt = 0; 470 471 /* 472 * Smash the generated and preexisting indents together 473 * and generate one cleanly made out of tabs and spaces 474 * if we are using autoindent and this isn't 'r' command. 475 */ 476 if (!vaifirst && value(vi_AUTOINDENT)) { 477 i = fixindent(indent); 478 if (!HADUP) 479 indent = i; 480 gcursor = strend(genbuf); 481 } 482 483 /* 484 * Set cnt to 1 to avoid repeating the text on the same line. 485 * Do this for commands 'i', 'I', 'a', and 'A', if we're 486 * inserting anything with a newline for XPG6. Always do this 487 * for commands 'o' and 'O'. 488 */ 489 if ((imultlinecnt && gotNL) || omultlinecnt) { 490 cnt = 1; 491 } 492 493 /* 494 * Limit the repetition count based on maximum 495 * possible line length; do output implied 496 * by further count (> 1) and cons up the new line 497 * in linebuf. 498 */ 499 cnt = vmaxrep(ch, cnt); 500 /* 501 * cursor points to linebuf 502 * Copy remaining old text (cursor) in original 503 * line to after new text (gcursor + 1) in genbuf. 504 */ 505 CP(gcursor + 1, cursor); 506 /* 507 * For [count] r \n command, when replacing [count] chars 508 * with '\n', this loop replaces [count] chars with "". 509 */ 510 do { 511 /* cp new text (genbuf) into linebuf (cursor) */ 512 CP(cursor, genbuf); 513 if (cnt > 1) { 514 int oldhold = hold; 515 516 Outchar = vinschar; 517 hold |= HOLDQIK; 518 viprintf("%s", genbuf); 519 hold = oldhold; 520 Outchar = vputchar; 521 } 522 /* point cursor after new text in linebuf */ 523 cursor += gcursor - genbuf; 524 } while (--cnt > 0); 525 endim(); 526 vUA2 = cursor; 527 /* add the remaining old text after the cursor */ 528 if (escape != '\n') 529 CP(cursor, gcursor + 1); 530 531 /* 532 * If doomed characters remain, clobber them, 533 * and reopen the line to get the display exact. 534 * eg. c$ to change to end of line 535 */ 536 if (state != HARDOPEN) { 537 DEPTH(vcline) = 0; 538 savedoomed = doomed; 539 if (doomed > 0) { 540 int cind = cindent(); 541 542 physdc(cind, cind + doomed); 543 doomed = 0; 544 } 545 if(MB_CUR_MAX > 1) 546 rewrite = _ON; 547 i = vreopen(LINE(vcline), lineDOT(), vcline); 548 if(MB_CUR_MAX > 1) 549 rewrite = _OFF; 550 #ifdef TRACE 551 if (trace) 552 fprintf(trace, "restoring doomed from %d to %d\n", doomed, savedoomed); 553 #endif 554 if (ch == 'R') 555 doomed = savedoomed; 556 } 557 558 /* 559 * Unless we are continuing on to another line 560 * (got a NL), break out of the for loop (got 561 * an ESCAPE). 562 */ 563 if (escape != '\n') { 564 vshowmode(""); 565 break; 566 } 567 568 /* 569 * Set up for the new line. 570 * First save the current line, then construct a new 571 * first image for the continuation line consisting 572 * of any new autoindent plus the pushed ahead text. 573 */ 574 killU(); 575 addtext(gobblebl ? " " : "\n"); 576 /* save vutmp (for undo state) into temp file */ 577 vsave(); 578 cnt = 1; 579 if (value(vi_AUTOINDENT)) { 580 if (value(vi_LISP)) 581 indent = lindent(dot + 1); 582 else 583 if (!HADUP && vaifirst) 584 indent = whitecnt(linebuf); 585 vaifirst = 0; 586 strcLIN(vpastwh(gcursor + 1)); 587 gcursor = genindent(indent); 588 *gcursor = 0; 589 if (gcursor + strlen(linebuf) > &genbuf[LBSIZE - 2]) 590 gcursor = genbuf; 591 CP(gcursor, linebuf); 592 } else { 593 /* 594 * Put gcursor at start of genbuf to wipe 595 * out previous line in preparation for 596 * the next vgetline() loop. 597 */ 598 CP(genbuf, gcursor + 1); 599 gcursor = genbuf; 600 } 601 602 /* 603 * If we started out as a single line operation and are now 604 * turning into a multi-line change, then we had better yank 605 * out dot before it changes so that undo will work 606 * correctly later. 607 */ 608 if (FIXUNDO && vundkind == VCHNG) { 609 vremote(1, yank, 0); 610 undap1--; 611 } 612 613 /* 614 * Now do the append of the new line in the buffer, 615 * and update the display, ie: append genbuf to 616 * the file after dot. If slowopen 617 * we don't do very much. 618 */ 619 vdoappend(genbuf); 620 vundkind = VMANYINS; 621 vcline++; 622 if (state != VISUAL) 623 vshow(dot, NOLINE); 624 else { 625 i += LINE(vcline - 1); 626 vopen(dot, i); 627 if (value(vi_SLOWOPEN)) 628 vscrap(); 629 else 630 vsync1(LINE(vcline)); 631 } 632 switch (ch) { 633 case 'r': 634 break; 635 case 'a': 636 if (value(vi_TERSE)) { 637 vshowmode(gettext("A")); 638 } else { 639 vshowmode(gettext("APPEND MODE")); 640 } 641 break; 642 case 's': 643 if (value(vi_TERSE)) { 644 vshowmode(gettext("S")); 645 } else { 646 vshowmode(gettext("SUBSTITUTE MODE")); 647 } 648 break; 649 case 'c': 650 if (value(vi_TERSE)) { 651 vshowmode(gettext("C")); 652 } else { 653 vshowmode(gettext("CHANGE MODE")); 654 } 655 break; 656 case 'R': 657 if (value(vi_TERSE)) { 658 vshowmode(gettext("R")); 659 } else { 660 vshowmode(gettext("REPLACE MODE")); 661 } 662 break; 663 case 'i': 664 if (value(vi_TERSE)) { 665 vshowmode(gettext("I")); 666 } else { 667 vshowmode(gettext("INSERT MODE")); 668 } 669 break; 670 case 'o': 671 if (value(vi_TERSE)) { 672 vshowmode(gettext("O")); 673 } else { 674 vshowmode(gettext("OPEN MODE")); 675 } 676 break; 677 default: 678 if (value(vi_TERSE)) { 679 vshowmode(gettext("I")); 680 } else { 681 vshowmode(gettext("INPUT MODE")); 682 } 683 } 684 strcLIN(gcursor); 685 /* zero genbuf */ 686 *gcursor = 0; 687 cursor = linebuf; 688 vgotoCL(nqcolumn(cursor - 1, genbuf)); 689 } /* end for (;;) loop in vappend() */ 690 691 if (imultlinecnt && gotNL) { 692 imultlinerep(savecnt, startsrcline, startsrccol, endsrccol); 693 } else if (omultlinecnt) { 694 omultlinerep(savecnt, startsrcline, endsrccol); 695 #ifdef XPG6 696 } else if (savecnt > 1 && ch == 'r' && gotNL) { 697 /* 698 * XPG6 assertion 313 & 254 : Position cursor for [count]r\n 699 * then insert [count -1] newlines. 700 */ 701 endsrccol = gcursor - genbuf - 1; 702 rmultlinerep(savecnt, endsrccol); 703 #endif /* XPG6 */ 704 } 705 706 /* 707 * All done with insertion, position the cursor 708 * and sync the screen. 709 */ 710 hold = oldhold; 711 if ((imultlinecnt && gotNL) || omultlinecnt) { 712 fixdisplay(); 713 #ifdef XPG6 714 } else if (savecnt > 1 && ch == 'r' && gotNL) { 715 fixdisplay(); 716 /* 717 * XPG6 assertion 313 & 254 [count]r\n : Set flag to call 718 * fixdisplay() after operate() has finished. To be sure that 719 * the text (after the last \n followed by an indent) is always 720 * displayed, fixdisplay() is called right before getting 721 * the next command. 722 */ 723 redisplay = 1; 724 #endif /* XPG6 */ 725 } else if (cursor > linebuf) { 726 cursor = lastchr(linebuf, cursor); 727 #ifdef XPG6 728 /* 729 * XPG6 assertion 313 & 254 [count]r\n : 730 * For 'r' command, when the replacement char causes new 731 * lines to be created, point cursor to first non-blank. 732 * The old code, ie: cursor = lastchr(linebuf, cursor); 733 * set cursor to the blank before the first non-blank 734 * for r\n 735 */ 736 if (ch == 'r' && gotNL && isblank((int)*cursor)) 737 ++cursor; 738 #endif /* XPG6 */ 739 } 740 if (state != HARDOPEN) 741 vsyncCL(); 742 else if (cursor > linebuf) 743 back1(); 744 doomed = 0; 745 wcursor = cursor; 746 (void) vmove(); 747 } 748 749 /* 750 * XPG6 751 * To repeat multi-line input for [count]a, [count]A, [count]i, [count]I, 752 * or a subsequent [count]. : 753 * insert input count-1 more times. 754 */ 755 756 static void 757 imultlinerep(int savecnt, line *startsrcline, int startsrccol, int endsrccol) 758 { 759 int tmpcnt = 2; /* 1st insert counts as 1 repeat */ 760 line *srcline, *endsrcline; 761 size_t destsize = LBSIZE - endsrccol - 1; 762 763 endsrcline = dot; 764 765 /* Save linebuf into temp file before moving off the line. */ 766 vsave(); 767 768 /* 769 * At this point the temp file contains the first iteration of 770 * a multi-line insert, and we need to repeat it savecnt - 1 771 * more times in the temp file. dot is the last line in the 772 * first iteration of the insert. Decrement dot so that 773 * vdoappend() will append each new line before the last line. 774 */ 775 --dot; 776 --vcline; 777 /* 778 * Use genbuf to rebuild the last line in the 1st iteration 779 * of the repeated insert, then copy this line to the temp file. 780 */ 781 (void) strlcpy((char *)genbuf, (char *)linebuf, sizeof (genbuf)); 782 getaline(*startsrcline); 783 if (strlcpy((char *)(genbuf + endsrccol + 1), 784 (char *)(linebuf + startsrccol), destsize) >= destsize) { 785 error(gettext("Line too long")); 786 } 787 vdoappend(genbuf); 788 vcline++; 789 /* 790 * Loop from the second line of the first iteration 791 * through endsrcline, appending after dot. 792 */ 793 ++startsrcline; 794 795 while (tmpcnt <= savecnt) { 796 for (srcline = startsrcline; srcline <= endsrcline; 797 ++srcline) { 798 if ((tmpcnt == savecnt) && 799 (srcline == endsrcline)) { 800 /* 801 * The last line is already in place, 802 * just make it the current line. 803 */ 804 vcline++; 805 dot++; 806 getDOT(); 807 cursor = linebuf + endsrccol; 808 } else { 809 getaline(*srcline); 810 /* copy linebuf to temp file */ 811 vdoappend(linebuf); 812 vcline++; 813 } 814 } 815 ++tmpcnt; 816 } 817 } 818 819 /* 820 * To repeat input for [count]o, [count]O, or a subsequent [count]. : 821 * append input count-1 more times to the end of the already added 822 * text, each time starting on a new line. 823 */ 824 825 static void 826 omultlinerep(int savecnt, line *startsrcline, int endsrccol) 827 { 828 int tmpcnt = 2; /* 1st insert counts as 1 repeat */ 829 line *srcline, *endsrcline; 830 831 endsrcline = dot; 832 /* Save linebuf into temp file before moving off the line. */ 833 vsave(); 834 835 /* 836 * Loop from the first line of the first iteration 837 * through endsrcline, appending after dot. 838 */ 839 while (tmpcnt <= savecnt) { 840 for (srcline = startsrcline; srcline <= endsrcline; ++srcline) { 841 getaline(*srcline); 842 /* copy linebuf to temp file */ 843 vdoappend(linebuf); 844 vcline++; 845 } 846 ++tmpcnt; 847 } 848 cursor = linebuf + endsrccol; 849 } 850 851 #ifdef XPG6 852 /* 853 * XPG6 assertion 313 & 254 : To repeat '\n' for [count]r\n 854 * insert '\n' savecnt-1 more times before the already added '\n'. 855 */ 856 857 static void 858 rmultlinerep(int savecnt, int endsrccol) 859 { 860 int tmpcnt = 2; /* 1st replacement counts as 1 repeat */ 861 862 /* Save linebuf into temp file before moving off the line. */ 863 vsave(); 864 /* 865 * At this point the temp file contains the line followed by '\n', 866 * which is preceded by indentation if autoindent is set. 867 * '\n' must be repeated [savecnt - 1] more times in the temp file. 868 * dot is the current line containing the '\n'. Decrement dot so that 869 * vdoappend() will append each '\n' before the current '\n'. 870 * This will allow only the last line to contain any autoindent 871 * characters. 872 */ 873 --dot; 874 --vcline; 875 876 /* 877 * Append after dot. 878 */ 879 while (tmpcnt <= savecnt) { 880 linebuf[0] = '\0'; 881 /* append linebuf below current line in temp file */ 882 vdoappend(linebuf); 883 vcline++; 884 ++tmpcnt; 885 } 886 /* set the current line to the line after the last '\n' */ 887 ++dot; 888 ++vcline; 889 /* point cursor after (linebuf + endsrccol) */ 890 vcursaft(linebuf + endsrccol); 891 } 892 #endif /* XPG6 */ 893 894 /* 895 * Similiar to a ctrl-l, however always vrepaint() in case the last line 896 * of the repeat would exceed the bottom of the screen. 897 */ 898 899 void 900 fixdisplay(void) 901 { 902 vclear(); 903 vdirty(0, vcnt); 904 if (state != VISUAL) { 905 vclean(); 906 vcnt = 0; 907 vmoveto(dot, cursor, 0); 908 } else { 909 vredraw(WTOP); 910 vrepaint(cursor); 911 vfixcurs(); 912 } 913 } 914 915 /* 916 * Subroutine for vgetline to back up a single character position, 917 * backwards around end of lines (vgoto can't hack columns which are 918 * less than 0 in general). 919 */ 920 void 921 back1(void) 922 { 923 924 vgoto(destline - 1, WCOLS + destcol - 1); 925 } 926 927 /* 928 * Get a line into genbuf after gcursor. 929 * Cnt limits the number of input characters 930 * accepted and is used for handling the replace 931 * single character command. Aescaped is the location 932 * where we stick a termination indicator (whether we 933 * ended with an ESCAPE or a newline/return. 934 * 935 * We do erase-kill type processing here and also 936 * are careful about the way we do this so that it is 937 * repeatable. (I.e. so that your kill doesn't happen, 938 * when you repeat an insert if it was escaped with \ the 939 * first time you did it. commch is the command character 940 * involved, including the prompt for readline. 941 */ 942 unsigned char * 943 vgetline(cnt, gcursor, aescaped, commch) 944 int cnt; 945 unsigned char *gcursor; 946 bool *aescaped; 947 unsigned char commch; 948 { 949 int c, ch; 950 unsigned char *cp, *pcp; 951 int x, y, iwhite, backsl=0; 952 unsigned char *iglobp; 953 int (*OO)() = Outchar; 954 int length, width; 955 unsigned char multic[MULTI_BYTE_MAX+1]; 956 wchar_t wchar = 0; 957 unsigned char *p; 958 int len; 959 960 961 /* 962 * Clear the output state and counters 963 * for autoindent backwards motion (counts of ^D, etc.) 964 * Remember how much white space at beginning of line so 965 * as not to allow backspace over autoindent. 966 */ 967 968 *aescaped = 0; 969 ogcursor = gcursor; 970 flusho(); 971 CDCNT = 0; 972 HADUP = 0; 973 HADZERO = 0; 974 gobbled = 0; 975 iwhite = whitecnt(genbuf); 976 iglobp = vglobp; 977 978 /* 979 * Clear abbreviation recursive-use count 980 */ 981 abbrepcnt = 0; 982 /* 983 * Carefully avoid using vinschar in the echo area. 984 */ 985 if (splitw) 986 Outchar = vputchar; 987 else { 988 Outchar = vinschar; 989 vprepins(); 990 } 991 for (;;) { 992 length = 0; 993 backsl = 0; 994 if (gobblebl) 995 gobblebl--; 996 if (cnt != 0) { 997 cnt--; 998 if (cnt == 0) 999 goto vadone; 1000 } 1001 c = getkey(); 1002 if (c != ATTN) 1003 c &= 0377; 1004 ch = c; 1005 maphopcnt = 0; 1006 if (vglobp == 0 && Peekkey == 0 && commch != 'r') 1007 while ((ch = map(c, immacs, commch)) != c) { 1008 c = ch; 1009 if (!value(vi_REMAP)) 1010 break; 1011 if (++maphopcnt > 256) 1012 error(gettext("Infinite macro loop")); 1013 } 1014 if (!iglobp) { 1015 1016 /* 1017 * Erase-kill type processing. 1018 * Only happens if we were not reading 1019 * from untyped input when we started. 1020 * Map users erase to ^H, kill to -1 for switch. 1021 */ 1022 if (c == tty.c_cc[VERASE]) 1023 c = CTRL('h'); 1024 else if (c == tty.c_cc[VKILL]) 1025 c = -1; 1026 switch (c) { 1027 1028 /* 1029 * ^? Interrupt drops you back to visual 1030 * command mode with an unread interrupt 1031 * still in the input buffer. 1032 * 1033 * ^\ Quit does the same as interrupt. 1034 * If you are a ex command rather than 1035 * a vi command this will drop you 1036 * back to command mode for sure. 1037 */ 1038 case ATTN: 1039 case QUIT: 1040 ungetkey(c); 1041 goto vadone; 1042 1043 /* 1044 * ^H Backs up a character in the input. 1045 * 1046 * BUG: Can't back around line boundaries. 1047 * This is hard because stuff has 1048 * already been saved for repeat. 1049 */ 1050 case CTRL('h'): 1051 bakchar: 1052 cp = lastchr(ogcursor, gcursor); 1053 if (cp < ogcursor) { 1054 if (splitw) { 1055 /* 1056 * Backspacing over readecho 1057 * prompt. Pretend delete but 1058 * don't beep. 1059 */ 1060 ungetkey(c); 1061 goto vadone; 1062 } 1063 (void) beep(); 1064 continue; 1065 } 1066 goto vbackup; 1067 1068 /* 1069 * ^W Back up a white/non-white word. 1070 */ 1071 case CTRL('w'): 1072 wdkind = 1; 1073 for (cp = gcursor; cp > ogcursor && isspace(cp[-1]); cp--) 1074 continue; 1075 pcp = lastchr(ogcursor, cp); 1076 for (c = wordch(pcp); 1077 cp > ogcursor && wordof(c, pcp); cp = pcp, pcp = lastchr(ogcursor, cp)) 1078 continue; 1079 goto vbackup; 1080 1081 /* 1082 * users kill Kill input on this line, back to 1083 * the autoindent. 1084 */ 1085 case -1: 1086 cp = ogcursor; 1087 vbackup: 1088 if (cp == gcursor) { 1089 (void) beep(); 1090 continue; 1091 } 1092 endim(); 1093 *cp = 0; 1094 c = cindent(); 1095 vgotoCL(nqcolumn(lastchr(linebuf, cursor), genbuf)); 1096 1097 if (doomed >= 0) 1098 doomed += c - cindent(); 1099 gcursor = cp; 1100 continue; 1101 1102 /* 1103 * \ Followed by erase or kill 1104 * maps to just the erase or kill. 1105 */ 1106 case '\\': 1107 x = destcol, y = destline; 1108 putchar('\\'); 1109 vcsync(); 1110 c = getkey(); 1111 if (c == tty.c_cc[VERASE] 1112 || c == tty.c_cc[VKILL]) 1113 { 1114 vgoto(y, x); 1115 if (doomed >= 0) 1116 doomed++; 1117 multic[0] = wchar = c; 1118 length = 1; 1119 goto def; 1120 } 1121 ungetkey(c), c = '\\'; 1122 backsl = 1; 1123 break; 1124 1125 /* 1126 * ^Q Super quote following character 1127 * Only ^@ is verboten (trapped at 1128 * a lower level) and \n forces a line 1129 * split so doesn't really go in. 1130 * 1131 * ^V Synonym for ^Q 1132 */ 1133 case CTRL('q'): 1134 case CTRL('v'): 1135 x = destcol, y = destline; 1136 putchar('^'); 1137 vgoto(y, x); 1138 c = getkey(); 1139 #ifdef USG 1140 if (c == ATTN) 1141 c = tty.c_cc[VINTR]; 1142 #endif 1143 if (c != NL) { 1144 if (doomed >= 0) 1145 doomed++; 1146 multic[0] = wchar = c; 1147 length = 1; 1148 goto def; 1149 } 1150 break; 1151 } 1152 } 1153 1154 /* 1155 * If we get a blank not in the echo area 1156 * consider splitting the window in the wrapmargin. 1157 */ 1158 if(!backsl) { 1159 ungetkey(c); 1160 if((length = _mbftowc((char *)multic, &wchar, getkey, &Peekkey)) <= 0) { 1161 (void) beep(); 1162 continue; 1163 } 1164 } else { 1165 length = 1; 1166 multic[0] = '\\'; 1167 } 1168 1169 if (c != NL && !splitw) { 1170 if (c == ' ' && gobblebl) { 1171 gobbled = 1; 1172 continue; 1173 } 1174 if ((width = wcwidth(wchar)) <= 0) 1175 width = (wchar <= 0177 ? 1 : 4); 1176 if (value(vi_WRAPMARGIN) && 1177 (outcol + width - 1 >= OCOLUMNS - value(vi_WRAPMARGIN) || 1178 backsl && outcol==0) && 1179 commch != 'r') { 1180 /* 1181 * At end of word and hit wrapmargin. 1182 * Move the word to next line and keep going. 1183 */ 1184 unsigned char *wp; 1185 int bytelength; 1186 #ifndef PRESUNEUC 1187 unsigned char *tgcursor; 1188 wchar_t wc1, wc2; 1189 tgcursor = gcursor; 1190 #endif /* PRESUNEUC */ 1191 wdkind = 1; 1192 strncpy(gcursor, multic, length); 1193 gcursor += length; 1194 if (backsl) { 1195 #ifdef PRESUNEUC 1196 if((length = mbftowc((char *)multic, &wchar, getkey, &Peekkey)) <= 0) { 1197 #else 1198 if((length = _mbftowc((char *)multic, &wchar, getkey, &Peekkey)) <= 0) { 1199 #endif /* PRESUNEUC */ 1200 (void) beep(); 1201 continue; 1202 } 1203 strncpy(gcursor, multic, length); 1204 gcursor += length; 1205 } 1206 *gcursor = 0; 1207 /* 1208 * Find end of previous word if we are past it. 1209 */ 1210 for (cp=gcursor; cp>ogcursor && isspace(cp[-1]); cp--) 1211 ; 1212 #ifdef PRESUNEUC 1213 /* find screen width of previous word */ 1214 width = 0; 1215 for(wp = cp; *wp; ) 1216 #else 1217 /* count screen width of pending characters */ 1218 width = 0; 1219 for(wp = tgcursor; wp < cp;) 1220 #endif /* PRESUNEUC */ 1221 if((bytelength = mbtowc(&wchar, (char *)wp, MULTI_BYTE_MAX)) < 0) { 1222 width+=4; 1223 wp++; 1224 } else { 1225 int curwidth = wcwidth(wchar); 1226 if(curwidth <= 0) 1227 width += (*wp < 0200 ? 2 : 4); 1228 else 1229 width += curwidth; 1230 wp += bytelength; 1231 } 1232 1233 #ifdef PRESUNEUC 1234 if (outcol+(backsl?OCOLUMNS:0) - width >= OCOLUMNS - value(vi_WRAPMARGIN)) { 1235 #else 1236 if (outcol+(backsl?OCOLUMNS:0) + width -1 >= OCOLUMNS - value(vi_WRAPMARGIN)) { 1237 #endif /* PRESUNEUC */ 1238 /* 1239 * Find beginning of previous word. 1240 */ 1241 #ifdef PRESUNEUC 1242 for (; cp>ogcursor && !isspace(cp[-1]); cp--) 1243 ; 1244 #else 1245 wc1 = wc2 = 0; 1246 while (cp>ogcursor) { 1247 if (isspace(cp[-1])) { 1248 break; 1249 } 1250 if (!multibyte) { 1251 cp--; 1252 continue; 1253 } 1254 wp = (unsigned char *)(cp - 1255 MB_CUR_MAX); 1256 if (wp < ogcursor) 1257 wp = ogcursor; 1258 while (cp > wp) { 1259 /* 7tabs */if (wc2) { 1260 /* 7tabs */ if ((bytelength = mbtowc(&wc1, (char *)wp, cp-wp)) != cp-wp) { 1261 /* 7tabs */ wp++; 1262 /* 7tabs */ wc1 = 0; 1263 /* 7tabs */ continue; 1264 /* 7tabs */ } 1265 /* 7tabs */} else { 1266 /* 7tabs */ if ((bytelength = mbtowc(&wc2, (char *)wp, cp-wp)) != cp-wp) { 1267 /* 7tabs */ wp++; 1268 /* 7tabs */ wc2 = 0; 1269 /* 7tabs */ continue; 1270 /* 7tabs */ } 1271 /* 7tabs */} 1272 /* 7tabs */if (wc1) { 1273 /* 7tabs */ if (wdbdg && (!iswascii(wc1) || !iswascii(wc2))) { 1274 /* 7tabs */ if ((*wdbdg)(wc1, wc2, 2) < 5) { 1275 /* 7tabs */ goto ws; 1276 /* 7tabs */ } 1277 /* 7tabs */ } 1278 /* 7tabs */ wc2 = wc1; 1279 /* 7tabs */ wc1 = 0; 1280 /* 7tabs */ cp -= bytelength - 1; 1281 /* 7tabs */ break; 1282 /* 7tabs */} else { 1283 /* 7tabs */ cp -= bytelength - 1; 1284 /* 7tabs */ break; 1285 /* 7tabs */} 1286 } 1287 cp--; 1288 } 1289 ws: 1290 #endif /* PRESUNEUC */ 1291 if (cp <= ogcursor) { 1292 /* 1293 * There is a single word that 1294 * is too long to fit. Just 1295 * let it pass, but beep for 1296 * each new letter to warn 1297 * the luser. 1298 */ 1299 gcursor -= length; 1300 c = *gcursor; 1301 *gcursor = 0; 1302 (void) beep(); 1303 goto dontbreak; 1304 } 1305 /* 1306 * Save it for next line. 1307 */ 1308 macpush(cp, 0); 1309 #ifdef PRESUNEUC 1310 cp--; 1311 #endif /* PRESUNEUC */ 1312 } 1313 macpush("\n", 0); 1314 /* 1315 * Erase white space before the word. 1316 */ 1317 while (cp > ogcursor && isspace(cp[-1])) 1318 cp--; /* skip blank */ 1319 gobblebl = 3; 1320 goto vbackup; 1321 } 1322 dontbreak:; 1323 } 1324 1325 /* 1326 * Word abbreviation mode. 1327 */ 1328 if (anyabbrs && gcursor > ogcursor && !wordch(multic) && wordch(lastchr(ogcursor, gcursor))) { 1329 int wdtype, abno; 1330 1331 multic[length] = 0; 1332 wdkind = 1; 1333 cp = lastchr(ogcursor, gcursor); 1334 pcp = lastchr(ogcursor, cp); 1335 for (wdtype = wordch(pcp); 1336 cp > ogcursor && wordof(wdtype, pcp); cp = pcp, pcp = lastchr(ogcursor, pcp)) 1337 ; 1338 *gcursor = 0; 1339 for (abno=0; abbrevs[abno].mapto; abno++) { 1340 if (eq(cp, abbrevs[abno].cap)) { 1341 if(abbrepcnt == 0) { 1342 if(reccnt(abbrevs[abno].cap, abbrevs[abno].mapto)) 1343 abbrepcnt = 1; 1344 macpush(multic, 0); 1345 macpush(abbrevs[abno].mapto); 1346 goto vbackup; 1347 } else 1348 abbrepcnt = 0; 1349 } 1350 } 1351 } 1352 1353 switch (c) { 1354 1355 /* 1356 * ^M Except in repeat maps to \n. 1357 */ 1358 case CR: 1359 if (vglobp) { 1360 multic[0] = wchar = c; 1361 length = 1; 1362 goto def; 1363 } 1364 c = '\n'; 1365 /* presto chango ... */ 1366 1367 /* 1368 * \n Start new line. 1369 */ 1370 case NL: 1371 *aescaped = c; 1372 goto vadone; 1373 1374 /* 1375 * escape End insert unless repeat and more to repeat. 1376 */ 1377 case ESCAPE: 1378 if (lastvgk) { 1379 multic[0] = wchar = c; 1380 length = 1; 1381 goto def; 1382 } 1383 goto vadone; 1384 1385 /* 1386 * ^D Backtab. 1387 * ^T Software forward tab. 1388 * 1389 * Unless in repeat where this means these 1390 * were superquoted in. 1391 */ 1392 case CTRL('t'): 1393 if (vglobp) { 1394 multic[0] = wchar = c; 1395 length = 1; 1396 goto def; 1397 } 1398 /* fall into ... */ 1399 1400 *gcursor = 0; 1401 cp = vpastwh(genbuf); 1402 c = whitecnt(genbuf); 1403 if (ch == CTRL('t')) { 1404 /* 1405 * ^t just generates new indent replacing 1406 * current white space rounded up to soft 1407 * tab stop increment. 1408 */ 1409 if (cp != gcursor) 1410 /* 1411 * BUG: Don't hack ^T except 1412 * right after initial 1413 * white space. 1414 */ 1415 continue; 1416 cp = genindent(iwhite = backtab(c + value(vi_SHIFTWIDTH) + 1)); 1417 ogcursor = cp; 1418 goto vbackup; 1419 } 1420 /* 1421 * ^D works only if we are at the (end of) the 1422 * generated autoindent. We count the ^D for repeat 1423 * purposes. 1424 */ 1425 case CTRL('d'): 1426 /* check if ^d was superquoted in */ 1427 if(vglobp && inscdcnt <= 0) { 1428 multic[0] = wchar = c; 1429 length = 1; 1430 goto def; 1431 } 1432 if(vglobp) 1433 inscdcnt--; 1434 *gcursor = 0; 1435 cp = vpastwh(genbuf); 1436 c = whitecnt(genbuf); 1437 if (c == iwhite && c != 0) 1438 if (cp == gcursor) { 1439 iwhite = backtab(c); 1440 CDCNT++; 1441 ogcursor = cp = genindent(iwhite); 1442 goto vbackup; 1443 } else if (&cp[1] == gcursor && 1444 (*cp == '^' || *cp == '0')) { 1445 /* 1446 * ^^D moves to margin, then back 1447 * to current indent on next line. 1448 * 1449 * 0^D moves to margin and then 1450 * stays there. 1451 */ 1452 HADZERO = *cp == '0'; 1453 ogcursor = cp = genbuf; 1454 HADUP = 1 - HADZERO; 1455 CDCNT = 1; 1456 endim(); 1457 back1(); 1458 (void) vputchar(' '); 1459 goto vbackup; 1460 } 1461 1462 if (vglobp && vglobp - iglobp >= 2) { 1463 if ((p = vglobp - MB_CUR_MAX) < iglobp) 1464 p = iglobp; 1465 for ( ; p < &vglobp[-2]; p += len) { 1466 if ((len = mblen((char *)p, MB_CUR_MAX)) <= 0) 1467 len = 1; 1468 } 1469 if ((p == &vglobp[-2]) && 1470 (*p == '^' || *p == '0') && 1471 gcursor == ogcursor + 1) 1472 goto bakchar; 1473 } 1474 continue; 1475 1476 default: 1477 /* 1478 * Possibly discard control inputs. 1479 */ 1480 if (!vglobp && junk(c)) { 1481 (void) beep(); 1482 continue; 1483 } 1484 def: 1485 if (!backsl) { 1486 putchar(wchar); 1487 flush(); 1488 } 1489 if (gcursor + length - 1 > &genbuf[LBSIZE - 2]) 1490 error(gettext("Line too long")); 1491 (void)strncpy(gcursor, multic, length); 1492 gcursor += length; 1493 vcsync(); 1494 if (value(vi_SHOWMATCH) && !iglobp) 1495 if (c == ')' || c == '}') 1496 lsmatch(gcursor); 1497 continue; 1498 } 1499 } 1500 vadone: 1501 *gcursor = 0; 1502 if (Outchar != termchar) 1503 Outchar = OO; 1504 endim(); 1505 return (gcursor); 1506 } 1507 1508 int vgetsplit(); 1509 unsigned char *vsplitpt; 1510 1511 /* 1512 * Append the line in buffer at lp 1513 * to the buffer after dot. 1514 */ 1515 void 1516 vdoappend(unsigned char *lp) 1517 { 1518 int oing = inglobal; 1519 1520 vsplitpt = lp; 1521 inglobal = 1; 1522 (void)append(vgetsplit, dot); 1523 inglobal = oing; 1524 } 1525 1526 /* 1527 * Subroutine for vdoappend to pass to append. 1528 */ 1529 int 1530 vgetsplit(void) 1531 { 1532 1533 if (vsplitpt == 0) 1534 return (EOF); 1535 strcLIN(vsplitpt); 1536 vsplitpt = 0; 1537 return (0); 1538 } 1539 1540 /* 1541 * Vmaxrep determines the maximum repetition factor 1542 * allowed that will yield total line length less than 1543 * LBSIZE characters and also does hacks for the R command. 1544 */ 1545 int 1546 vmaxrep(unsigned char ch, int cnt) 1547 { 1548 int len; 1549 unsigned char *cp; 1550 int repcnt, oldcnt, replen; 1551 if (cnt > LBSIZE - 2) 1552 cnt = LBSIZE - 2; 1553 if (ch == 'R') { 1554 len = strlen(cursor); 1555 oldcnt = 0; 1556 for(cp = cursor; *cp; ) { 1557 oldcnt++; 1558 cp = nextchr(cp); 1559 } 1560 repcnt = 0; 1561 for(cp = genbuf; *cp; ) { 1562 repcnt++; 1563 cp = nextchr(cp); 1564 } 1565 /* 1566 * if number of characters in replacement string 1567 * (repcnt) is less than number of characters following 1568 * cursor (oldcnt), find end of repcnt 1569 * characters after cursor 1570 */ 1571 if(repcnt < oldcnt) { 1572 for(cp = cursor; repcnt > 0; repcnt--) 1573 cp = nextchr(cp); 1574 len = cp - cursor; 1575 } 1576 CP(cursor, cursor + len); 1577 vUD2 += len; 1578 } 1579 len = strlen(linebuf); 1580 replen = strlen(genbuf); 1581 if (len + cnt * replen <= LBSIZE - 2) 1582 return (cnt); 1583 cnt = (LBSIZE - 2 - len) / replen; 1584 if (cnt == 0) { 1585 vsave(); 1586 error(gettext("Line too long")); 1587 } 1588 return (cnt); 1589 } 1590 1591 /* 1592 * Determine how many occurrences of word 'CAP' are in 'MAPTO'. To be 1593 * considered an occurrence there must be both a nonword-prefix, a 1594 * complete match of 'CAP' within 'MAPTO', and a nonword-suffix. 1595 * Note that the beginning and end of 'MAPTO' are considered to be 1596 * valid nonword delimiters. 1597 */ 1598 int 1599 reccnt(unsigned char *cap, unsigned char *mapto) 1600 { 1601 int i, cnt, final; 1602 1603 cnt = 0; 1604 final = strlen(mapto) - strlen(cap); 1605 1606 for (i=0; i <= final; i++) 1607 if ((strncmp(cap, mapto+i, strlen(cap)) == 0) /* match */ 1608 && (i == 0 || !wordch(&mapto[i-1])) /* prefix ok */ 1609 && (i == final || !wordch(&mapto[i+strlen(cap)]))) /* suffix ok */ 1610 cnt++; 1611 return (cnt); 1612 } 1613 1614